Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
d90ee4bb
Commit
d90ee4bb
authored
Aug 27, 2021
by
Ezekiel Kigbo
Committed by
Doug Stull
Aug 27, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Store fetched group labels in vuex
parent
838d9201
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
283 additions
and
39 deletions
+283
-39
ee/app/assets/javascripts/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue
...mponents/create_value_stream_form/custom_stage_fields.vue
+19
-3
ee/app/assets/javascripts/analytics/cycle_analytics/components/create_value_stream_form/utils.js
...le_analytics/components/create_value_stream_form/utils.js
+2
-0
ee/app/assets/javascripts/analytics/cycle_analytics/components/labels_selector.vue
.../analytics/cycle_analytics/components/labels_selector.vue
+11
-8
ee/app/assets/javascripts/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue
...lytics/components/tasks_by_type/tasks_by_type_filters.vue
+7
-2
ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
...lytics/cycle_analytics/components/type_of_work_charts.vue
+6
-0
ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_form.vue
...nalytics/cycle_analytics/components/value_stream_form.vue
+25
-4
ee/app/assets/javascripts/analytics/cycle_analytics/store/actions.js
...ts/javascripts/analytics/cycle_analytics/store/actions.js
+8
-0
ee/app/assets/javascripts/analytics/cycle_analytics/store/mutation_types.js
...scripts/analytics/cycle_analytics/store/mutation_types.js
+4
-0
ee/app/assets/javascripts/analytics/cycle_analytics/store/mutations.js
.../javascripts/analytics/cycle_analytics/store/mutations.js
+12
-0
ee/app/assets/javascripts/analytics/cycle_analytics/store/state.js
...sets/javascripts/analytics/cycle_analytics/store/state.js
+2
-0
ee/app/assets/javascripts/analytics/cycle_analytics/utils.js
ee/app/assets/javascripts/analytics/cycle_analytics/utils.js
+9
-1
ee/app/helpers/ee/groups/analytics/cycle_analytics_helper.rb
ee/app/helpers/ee/groups/analytics/cycle_analytics_helper.rb
+27
-0
ee/app/views/groups/analytics/cycle_analytics/show.html.haml
ee/app/views/groups/analytics/cycle_analytics/show.html.haml
+1
-4
ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/labels_selector_spec.js.snap
...ics/components/__snapshots__/labels_selector_spec.js.snap
+1
-1
ee/spec/frontend/analytics/cycle_analytics/components/labels_selector_spec.js
...lytics/cycle_analytics/components/labels_selector_spec.js
+25
-7
ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_spec.js
...cs/cycle_analytics/components/type_of_work_charts_spec.js
+30
-5
ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_spec.js
...tics/cycle_analytics/components/value_stream_form_spec.js
+23
-3
ee/spec/frontend/analytics/cycle_analytics/store/actions_spec.js
.../frontend/analytics/cycle_analytics/store/actions_spec.js
+37
-1
ee/spec/frontend/analytics/cycle_analytics/store/mutations_spec.js
...rontend/analytics/cycle_analytics/store/mutations_spec.js
+4
-0
ee/spec/helpers/ee/groups/analytics/cycle_analytics_helper_spec.rb
...elpers/ee/groups/analytics/cycle_analytics_helper_spec.rb
+30
-0
No files found.
ee/app/assets/javascripts/analytics/cycle_analytics/components/create_value_stream_form/custom_stage_fields.vue
View file @
d90ee4bb
<
script
>
<
script
>
import
{
GlFormGroup
,
GlFormInput
,
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
{
GlFormGroup
,
GlFormInput
,
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
{
isLabelEvent
,
getLabelEventsIdentifiers
}
from
'
../../utils
'
;
import
{
isLabelEvent
,
getLabelEventsIdentifiers
,
uniqById
}
from
'
../../utils
'
;
import
LabelsSelector
from
'
../labels_selector.vue
'
;
import
LabelsSelector
from
'
../labels_selector.vue
'
;
import
{
i18n
}
from
'
./constants
'
;
import
{
i18n
}
from
'
./constants
'
;
import
StageFieldActions
from
'
./stage_field_actions.vue
'
;
import
StageFieldActions
from
'
./stage_field_actions.vue
'
;
...
@@ -38,6 +38,11 @@ export default {
...
@@ -38,6 +38,11 @@ export default {
type
:
Array
,
type
:
Array
,
required
:
true
,
required
:
true
,
},
},
defaultGroupLabels
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
},
data
()
{
data
()
{
return
{
return
{
...
@@ -69,6 +74,15 @@ export default {
...
@@ -69,6 +74,15 @@ export default {
selectedEndEventName
()
{
selectedEndEventName
()
{
return
this
.
eventName
(
this
.
stage
.
endEventIdentifier
,
'
SELECT_END_EVENT
'
);
return
this
.
eventName
(
this
.
stage
.
endEventIdentifier
,
'
SELECT_END_EVENT
'
);
},
},
initialGroupLabels
()
{
return
uniqById
(
[
this
.
stage
.
startEventLabelId
?
this
.
stage
.
startEventLabel
:
null
,
this
.
stage
.
endEventLabelId
?
this
.
stage
.
endEventLabel
:
null
,
...
this
.
defaultGroupLabels
,
].
filter
((
l
)
=>
Boolean
(
l
)),
);
},
},
},
methods
:
{
methods
:
{
hasFieldErrors
(
key
)
{
hasFieldErrors
(
key
)
{
...
@@ -150,7 +164,8 @@ export default {
...
@@ -150,7 +164,8 @@ export default {
:invalid-feedback="fieldErrorMessage('startEventLabelId')"
:invalid-feedback="fieldErrorMessage('startEventLabelId')"
>
>
<labels-selector
<labels-selector
:selected-label-id=
"[stage.startEventLabelId]"
:initial-data=
"initialGroupLabels"
:selected-label-ids=
"[stage.startEventLabelId]"
:name=
"`custom-stage-start-label-$
{index}`"
:name=
"`custom-stage-start-label-$
{index}`"
@select-label="$emit('input', { field: 'startEventLabelId', value: $event })"
@select-label="$emit('input', { field: 'startEventLabelId', value: $event })"
/>
/>
...
@@ -193,7 +208,8 @@ export default {
...
@@ -193,7 +208,8 @@ export default {
:invalid-feedback="fieldErrorMessage('endEventLabelId')"
:invalid-feedback="fieldErrorMessage('endEventLabelId')"
>
>
<labels-selector
<labels-selector
:selected-label-id=
"[stage.endEventLabelId]"
:initial-data=
"initialGroupLabels"
:selected-label-ids=
"[stage.endEventLabelId]"
:name=
"`custom-stage-end-label-$
{index}`"
:name=
"`custom-stage-end-label-$
{index}`"
@select-label="$emit('input', { field: 'endEventLabelId', value: $event })"
@select-label="$emit('input', { field: 'endEventLabelId', value: $event })"
/>
/>
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/components/create_value_stream_form/utils.js
View file @
d90ee4bb
...
@@ -191,6 +191,8 @@ const findStageByName = (stages, targetName = '') =>
...
@@ -191,6 +191,8 @@ const findStageByName = (stages, targetName = '') =>
*/
*/
const
prepareCustomStage
=
({
startEventLabel
=
{},
endEventLabel
=
{},
...
rest
})
=>
({
const
prepareCustomStage
=
({
startEventLabel
=
{},
endEventLabel
=
{},
...
rest
})
=>
({
...
rest
,
...
rest
,
startEventLabel
,
endEventLabel
,
startEventLabelId
:
startEventLabel
?.
id
||
null
,
startEventLabelId
:
startEventLabel
?.
id
||
null
,
endEventLabelId
:
endEventLabel
?.
id
||
null
,
endEventLabelId
:
endEventLabel
?.
id
||
null
,
isDefault
:
false
,
isDefault
:
false
,
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/components/labels_selector.vue
View file @
d90ee4bb
...
@@ -26,7 +26,7 @@ export default {
...
@@ -26,7 +26,7 @@ export default {
GlSearchBoxByType
,
GlSearchBoxByType
,
},
},
props
:
{
props
:
{
defaultSelectedLabelIds
:
{
initialData
:
{
type
:
Array
,
type
:
Array
,
required
:
false
,
required
:
false
,
default
:
()
=>
[],
default
:
()
=>
[],
...
@@ -46,7 +46,7 @@ export default {
...
@@ -46,7 +46,7 @@ export default {
required
:
false
,
required
:
false
,
default
:
false
,
default
:
false
,
},
},
selectedLabelId
:
{
selectedLabelId
s
:
{
type
:
Array
,
type
:
Array
,
required
:
false
,
required
:
false
,
default
:
()
=>
[],
default
:
()
=>
[],
...
@@ -67,14 +67,13 @@ export default {
...
@@ -67,14 +67,13 @@ export default {
loading
:
false
,
loading
:
false
,
searchTerm
:
''
,
searchTerm
:
''
,
labels
:
[],
labels
:
[],
selectedLabelIds
:
this
.
defaultSelectedLabelIds
||
[],
};
};
},
},
computed
:
{
computed
:
{
selectedLabel
()
{
selectedLabel
()
{
const
{
selectedLabelId
,
labels
=
[]
}
=
this
;
const
{
selectedLabelId
s
,
labels
=
[]
}
=
this
;
if
(
!
selectedLabelId
.
length
||
!
labels
.
length
)
return
null
;
if
(
!
selectedLabelId
s
.
length
||
!
labels
.
length
)
return
null
;
return
labels
.
find
(({
id
})
=>
selectedLabelId
.
includes
(
id
));
return
labels
.
find
(({
id
})
=>
selectedLabelId
s
.
includes
(
id
));
},
},
maxLabelsSelected
()
{
maxLabelsSelected
()
{
return
this
.
selectedLabelIds
.
length
>=
this
.
maxLabels
;
return
this
.
selectedLabelIds
.
length
>=
this
.
maxLabels
;
...
@@ -89,7 +88,11 @@ export default {
...
@@ -89,7 +88,11 @@ export default {
},
},
},
},
mounted
()
{
mounted
()
{
this
.
fetchData
();
if
(
!
this
.
initialData
.
length
)
{
this
.
fetchData
();
}
else
{
this
.
labels
=
this
.
initialData
;
}
},
},
methods
:
{
methods
:
{
...
mapGetters
([
'
currentGroupPath
'
]),
...
mapGetters
([
'
currentGroupPath
'
]),
...
@@ -121,7 +124,7 @@ export default {
...
@@ -121,7 +124,7 @@ export default {
return
label
?.
name
||
label
.
title
;
return
label
?.
name
||
label
.
title
;
},
},
isSelectedLabel
(
id
)
{
isSelectedLabel
(
id
)
{
return
Boolean
(
this
.
selectedLabelId
?.
includes
(
id
));
return
Boolean
(
this
.
selectedLabelId
s
?.
includes
(
id
));
},
},
isDisabledLabel
(
id
)
{
isDisabledLabel
(
id
)
{
return
Boolean
(
this
.
maxLabelsSelected
&&
!
this
.
isSelectedLabel
(
id
));
return
Boolean
(
this
.
maxLabelsSelected
&&
!
this
.
isSelectedLabel
(
id
));
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue
View file @
d90ee4bb
...
@@ -38,6 +38,11 @@ export default {
...
@@ -38,6 +38,11 @@ export default {
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
defaultGroupLabels
:
{
type
:
Array
,
required
:
false
,
default
:
()
=>
[],
},
},
},
computed
:
{
computed
:
{
subjectFilterOptions
()
{
subjectFilterOptions
()
{
...
@@ -108,10 +113,10 @@ export default {
...
@@ -108,10 +113,10 @@ export default {
<div
class=
"flex-column"
>
<div
class=
"flex-column"
>
<labels-selector
<labels-selector
data-testid=
"type-of-work-filters-label"
data-testid=
"type-of-work-filters-label"
:
default-selected-labels-ids=
"selectedLabelId
s"
:
initial-data=
"defaultGroupLabel
s"
:max-labels=
"maxLabels"
:max-labels=
"maxLabels"
:aria-label=
"__('CycleAnalytics|Display chart filters')"
:aria-label=
"__('CycleAnalytics|Display chart filters')"
:selected-label-id=
"selectedLabelIds"
:selected-label-id
s
=
"selectedLabelIds"
aria-expanded=
"false"
aria-expanded=
"false"
multiselect
multiselect
right
right
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
View file @
d90ee4bb
...
@@ -5,6 +5,7 @@ import { s__, sprintf, __ } from '~/locale';
...
@@ -5,6 +5,7 @@ import { s__, sprintf, __ } from '~/locale';
import
ChartSkeletonLoader
from
'
~/vue_shared/components/resizable_chart/skeleton_loader.vue
'
;
import
ChartSkeletonLoader
from
'
~/vue_shared/components/resizable_chart/skeleton_loader.vue
'
;
import
{
formattedDate
}
from
'
../../shared/utils
'
;
import
{
formattedDate
}
from
'
../../shared/utils
'
;
import
{
TASKS_BY_TYPE_SUBJECT_ISSUE
}
from
'
../constants
'
;
import
{
TASKS_BY_TYPE_SUBJECT_ISSUE
}
from
'
../constants
'
;
import
{
uniqById
}
from
'
../utils
'
;
import
TasksByTypeChart
from
'
./tasks_by_type/tasks_by_type_chart.vue
'
;
import
TasksByTypeChart
from
'
./tasks_by_type/tasks_by_type_chart.vue
'
;
import
TasksByTypeFilters
from
'
./tasks_by_type/tasks_by_type_filters.vue
'
;
import
TasksByTypeFilters
from
'
./tasks_by_type/tasks_by_type_filters.vue
'
;
...
@@ -16,6 +17,7 @@ export default {
...
@@ -16,6 +17,7 @@ export default {
'
isLoadingTasksByTypeChart
'
,
'
isLoadingTasksByTypeChart
'
,
'
isLoadingTasksByTypeChartTopLabels
'
,
'
isLoadingTasksByTypeChartTopLabels
'
,
'
errorMessage
'
,
'
errorMessage
'
,
'
topRankedLabels
'
,
]),
]),
...
mapGetters
(
'
typeOfWork
'
,
[
'
selectedTasksByTypeFilters
'
,
'
tasksByTypeChartData
'
]),
...
mapGetters
(
'
typeOfWork
'
,
[
'
selectedTasksByTypeFilters
'
,
'
tasksByTypeChartData
'
]),
hasData
()
{
hasData
()
{
...
@@ -62,6 +64,9 @@ export default {
...
@@ -62,6 +64,9 @@ export default {
?
this
.
errorMessage
?
this
.
errorMessage
:
__
(
'
There is no data available. Please change your selection.
'
);
:
__
(
'
There is no data available. Please change your selection.
'
);
},
},
initialGroupLabels
()
{
return
uniqById
(
this
.
topRankedLabels
);
},
},
},
methods
:
{
methods
:
{
...
mapActions
(
'
typeOfWork
'
,
[
'
setTasksByTypeFilters
'
]),
...
mapActions
(
'
typeOfWork
'
,
[
'
setTasksByTypeFilters
'
]),
...
@@ -78,6 +83,7 @@ export default {
...
@@ -78,6 +83,7 @@ export default {
<h3>
{{
s__
(
'
CycleAnalytics|Type of work
'
)
}}
</h3>
<h3>
{{
s__
(
'
CycleAnalytics|Type of work
'
)
}}
</h3>
<p>
{{
summaryDescription
}}
</p>
<p>
{{
summaryDescription
}}
</p>
<tasks-by-type-filters
<tasks-by-type-filters
:default-group-labels=
"initialGroupLabels"
:has-data=
"hasData"
:has-data=
"hasData"
:selected-label-ids=
"selectedLabelIdsFilter"
:selected-label-ids=
"selectedLabelIdsFilter"
:subject-filter=
"selectedSubjectFilter"
:subject-filter=
"selectedSubjectFilter"
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_form.vue
View file @
d90ee4bb
<
script
>
<
script
>
import
{
GlButton
,
GlForm
,
GlFormInput
,
GlFormGroup
,
GlFormRadioGroup
,
GlModal
}
from
'
@gitlab/ui
'
;
import
{
GlButton
,
GlForm
,
GlFormInput
,
GlFormGroup
,
GlFormRadioGroup
,
GlLoadingIcon
,
GlModal
,
}
from
'
@gitlab/ui
'
;
import
{
cloneDeep
,
uniqueId
}
from
'
lodash
'
;
import
{
cloneDeep
,
uniqueId
}
from
'
lodash
'
;
import
Vue
from
'
vue
'
;
import
Vue
from
'
vue
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
...
@@ -43,6 +51,7 @@ export default {
...
@@ -43,6 +51,7 @@ export default {
GlFormInput
,
GlFormInput
,
GlFormGroup
,
GlFormGroup
,
GlFormRadioGroup
,
GlFormRadioGroup
,
GlLoadingIcon
,
GlModal
,
GlModal
,
DefaultStageFields
,
DefaultStageFields
,
CustomStageFields
,
CustomStageFields
,
...
@@ -101,7 +110,12 @@ export default {
...
@@ -101,7 +110,12 @@ export default {
};
};
},
},
computed
:
{
computed
:
{
...
mapState
({
isCreating
:
'
isCreatingValueStream
'
,
formEvents
:
'
formEvents
'
}),
...
mapState
({
isCreating
:
'
isCreatingValueStream
'
,
isFetchingGroupLabels
:
'
isFetchingGroupLabels
'
,
formEvents
:
'
formEvents
'
,
defaultGroupLabels
:
'
defaultGroupLabels
'
,
}),
isValueStreamNameValid
()
{
isValueStreamNameValid
()
{
return
!
this
.
nameError
?.
length
;
return
!
this
.
nameError
?.
length
;
},
},
...
@@ -149,8 +163,13 @@ export default {
...
@@ -149,8 +163,13 @@ export default {
return
this
.
defaultStageConfig
.
map
(({
name
})
=>
name
);
return
this
.
defaultStageConfig
.
map
(({
name
})
=>
name
);
},
},
},
},
created
()
{
if
(
!
this
.
defaultGroupLabels
)
{
this
.
fetchGroupLabels
();
}
},
methods
:
{
methods
:
{
...
mapActions
([
'
createValueStream
'
,
'
updateValueStream
'
]),
...
mapActions
([
'
createValueStream
'
,
'
updateValueStream
'
,
'
fetchGroupLabels
'
]),
onSubmit
()
{
onSubmit
()
{
this
.
validate
();
this
.
validate
();
if
(
this
.
hasFormErrors
)
return
false
;
if
(
this
.
hasFormErrors
)
return
false
;
...
@@ -316,7 +335,8 @@ export default {
...
@@ -316,7 +335,8 @@ export default {
@secondary.prevent="onAddStage"
@secondary.prevent="onAddStage"
@primary.prevent="onSubmit"
@primary.prevent="onSubmit"
>
>
<gl-form>
<gl-loading-icon
v-if=
"isFetchingGroupLabels"
size=
"lg"
color=
"dark"
class=
"gl-my-12"
/>
<gl-form
v-else
>
<gl-form-group
<gl-form-group
data-testid=
"create-value-stream-name"
data-testid=
"create-value-stream-name"
label-for=
"create-value-stream-name"
label-for=
"create-value-stream-name"
...
@@ -373,6 +393,7 @@ export default {
...
@@ -373,6 +393,7 @@ export default {
:index=
"activeStageIndex"
:index=
"activeStageIndex"
:total-stages=
"stages.length"
:total-stages=
"stages.length"
:errors=
"fieldErrors(activeStageIndex)"
:errors=
"fieldErrors(activeStageIndex)"
:default-group-labels=
"defaultGroupLabels"
@
move=
"handleMove"
@
move=
"handleMove"
@
remove=
"onRemove"
@
remove=
"onRemove"
@
input=
"onFieldInput(activeStageIndex, $event)"
@
input=
"onFieldInput(activeStageIndex, $event)"
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/store/actions.js
View file @
d90ee4bb
import
Api
from
'
ee/api
'
;
import
{
removeFlash
}
from
'
~/cycle_analytics/utils
'
;
import
{
removeFlash
}
from
'
~/cycle_analytics/utils
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
...
@@ -23,6 +24,13 @@ export const setPaths = ({ dispatch }, options) => {
...
@@ -23,6 +24,13 @@ export const setPaths = ({ dispatch }, options) => {
export
const
setFeatureFlags
=
({
commit
},
featureFlags
)
=>
export
const
setFeatureFlags
=
({
commit
},
featureFlags
)
=>
commit
(
types
.
SET_FEATURE_FLAGS
,
featureFlags
);
commit
(
types
.
SET_FEATURE_FLAGS
,
featureFlags
);
export
const
fetchGroupLabels
=
({
commit
,
getters
:
{
currentGroupPath
}
})
=>
{
commit
(
types
.
REQUEST_GROUP_LABELS
);
return
Api
.
cycleAnalyticsGroupLabels
(
currentGroupPath
,
{
only_group_labels
:
true
})
.
then
(({
data
=
[]
})
=>
commit
(
types
.
RECEIVE_GROUP_LABELS_SUCCESS
,
data
))
.
catch
(()
=>
commit
(
types
.
RECEIVE_GROUP_LABELS_ERROR
));
};
export
const
requestCycleAnalyticsData
=
({
commit
})
=>
commit
(
types
.
REQUEST_VALUE_STREAM_DATA
);
export
const
requestCycleAnalyticsData
=
({
commit
})
=>
commit
(
types
.
REQUEST_VALUE_STREAM_DATA
);
export
const
receiveCycleAnalyticsDataSuccess
=
({
commit
,
dispatch
})
=>
{
export
const
receiveCycleAnalyticsDataSuccess
=
({
commit
,
dispatch
})
=>
{
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/store/mutation_types.js
View file @
d90ee4bb
...
@@ -27,6 +27,10 @@ export const REQUEST_GROUP_STAGES = 'REQUEST_GROUP_STAGES';
...
@@ -27,6 +27,10 @@ export const REQUEST_GROUP_STAGES = 'REQUEST_GROUP_STAGES';
export
const
RECEIVE_GROUP_STAGES_SUCCESS
=
'
RECEIVE_GROUP_STAGES_SUCCESS
'
;
export
const
RECEIVE_GROUP_STAGES_SUCCESS
=
'
RECEIVE_GROUP_STAGES_SUCCESS
'
;
export
const
RECEIVE_GROUP_STAGES_ERROR
=
'
RECEIVE_GROUP_STAGES_ERROR
'
;
export
const
RECEIVE_GROUP_STAGES_ERROR
=
'
RECEIVE_GROUP_STAGES_ERROR
'
;
export
const
REQUEST_GROUP_LABELS
=
'
REQUEST_GROUP_LABELS
'
;
export
const
RECEIVE_GROUP_LABELS_SUCCESS
=
'
RECEIVE_GROUP_LABELS_SUCCESS
'
;
export
const
RECEIVE_GROUP_LABELS_ERROR
=
'
RECEIVE_GROUP_LABELS_ERROR
'
;
export
const
INITIALIZE_VSA
=
'
INITIALIZE_VSA
'
;
export
const
INITIALIZE_VSA
=
'
INITIALIZE_VSA
'
;
export
const
INITIALIZE_VALUE_STREAM_SUCCESS
=
'
INITIALIZE_VALUE_STREAM_SUCCESS
'
;
export
const
INITIALIZE_VALUE_STREAM_SUCCESS
=
'
INITIALIZE_VALUE_STREAM_SUCCESS
'
;
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/store/mutations.js
View file @
d90ee4bb
...
@@ -87,6 +87,18 @@ export default {
...
@@ -87,6 +87,18 @@ export default {
[
types
.
RECEIVE_GROUP_STAGES_SUCCESS
](
state
,
stages
)
{
[
types
.
RECEIVE_GROUP_STAGES_SUCCESS
](
state
,
stages
)
{
state
.
stages
=
transformRawStages
(
stages
);
state
.
stages
=
transformRawStages
(
stages
);
},
},
[
types
.
REQUEST_GROUP_LABELS
](
state
)
{
state
.
isFetchingGroupLabels
=
true
;
state
.
defaultGroupLabels
=
[];
},
[
types
.
RECEIVE_GROUP_LABELS_ERROR
](
state
)
{
state
.
isFetchingGroupLabels
=
false
;
state
.
defaultGroupLabels
=
[];
},
[
types
.
RECEIVE_GROUP_LABELS_SUCCESS
](
state
,
groupLabels
=
[])
{
state
.
isFetchingGroupLabels
=
false
;
state
.
defaultGroupLabels
=
groupLabels
.
map
(
convertObjectPropsToCamelCase
);
},
[
types
.
INITIALIZE_VSA
](
[
types
.
INITIALIZE_VSA
](
state
,
state
,
{
{
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/store/state.js
View file @
d90ee4bb
...
@@ -6,6 +6,7 @@ import {
...
@@ -6,6 +6,7 @@ import {
export
default
()
=>
({
export
default
()
=>
({
featureFlags
:
{},
featureFlags
:
{},
defaultStageConfig
:
[],
defaultStageConfig
:
[],
defaultGroupLabels
:
null
,
createdAfter
:
null
,
createdAfter
:
null
,
createdBefore
:
null
,
createdBefore
:
null
,
...
@@ -26,6 +27,7 @@ export default () => ({
...
@@ -26,6 +27,7 @@ export default () => ({
isCreatingValueStream
:
false
,
isCreatingValueStream
:
false
,
isEditingValueStream
:
false
,
isEditingValueStream
:
false
,
isDeletingValueStream
:
false
,
isDeletingValueStream
:
false
,
isFetchingGroupLabels
:
false
,
createValueStreamErrors
:
{},
createValueStreamErrors
:
{},
deleteValueStreamError
:
null
,
deleteValueStreamError
:
null
,
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/utils.js
View file @
d90ee4bb
import
dateFormat
from
'
dateformat
'
;
import
dateFormat
from
'
dateformat
'
;
import
{
isNumber
}
from
'
lodash
'
;
import
{
isNumber
,
uniqBy
}
from
'
lodash
'
;
import
{
dateFormats
}
from
'
~/analytics/shared/constants
'
;
import
{
dateFormats
}
from
'
~/analytics/shared/constants
'
;
import
{
OVERVIEW_STAGE_ID
}
from
'
~/cycle_analytics/constants
'
;
import
{
OVERVIEW_STAGE_ID
}
from
'
~/cycle_analytics/constants
'
;
import
{
medianTimeToParsedSeconds
}
from
'
~/cycle_analytics/utils
'
;
import
{
medianTimeToParsedSeconds
}
from
'
~/cycle_analytics/utils
'
;
...
@@ -371,3 +371,11 @@ export const formatMedianValuesWithOverview = (medians = []) => {
...
@@ -371,3 +371,11 @@ export const formatMedianValuesWithOverview = (medians = []) => {
[
OVERVIEW_STAGE_ID
]:
overviewMedian
?
medianTimeToParsedSeconds
(
overviewMedian
)
:
'
-
'
,
[
OVERVIEW_STAGE_ID
]:
overviewMedian
?
medianTimeToParsedSeconds
(
overviewMedian
)
:
'
-
'
,
};
};
};
};
/**
* Takes an array of objects with potential duplicates and returns the deduplicated array
*
* @param {Array} arr - The array of objects with potential duplicates
* @returns {Array} The unique objects from the original array
*/
export
const
uniqById
=
(
arr
=
[])
=>
uniqBy
(
arr
,
({
id
})
=>
id
);
ee/app/helpers/ee/groups/analytics/cycle_analytics_helper.rb
0 → 100644
View file @
d90ee4bb
# frozen_string_literal: true
module
EE::Groups::Analytics::CycleAnalyticsHelper
include
Analytics
::
CycleAnalyticsHelper
def
group_cycle_analytics_data
(
group
)
api_paths
=
group
.
present?
?
cycle_analytics_api_paths
(
group
)
:
{}
image_paths
=
cycle_analytics_image_paths
default_stages
=
{
default_stages:
cycle_analytics_default_stage_config
.
to_json
}
api_paths
.
merge
(
image_paths
,
default_stages
)
end
private
def
cycle_analytics_image_paths
{
empty_state_svg_path:
image_path
(
"illustrations/analytics/cycle-analytics-empty-chart.svg"
),
no_data_svg_path:
image_path
(
"illustrations/analytics/cycle-analytics-empty-chart.svg"
),
no_access_svg_path:
image_path
(
"illustrations/analytics/no-access.svg"
)
}
end
def
cycle_analytics_api_paths
(
group
)
{
milestones_path:
group_milestones_path
(
group
),
labels_path:
group_labels_path
(
group
)
}
end
end
ee/app/views/groups/analytics/cycle_analytics/show.html.haml
View file @
d90ee4bb
-
page_title
_
(
"Value Stream Analytics"
)
-
page_title
_
(
"Value Stream Analytics"
)
-
data_attributes
=
@request_params
.
valid?
?
@request_params
.
to_data_attributes
:
{}
-
data_attributes
=
@request_params
.
valid?
?
@request_params
.
to_data_attributes
:
{}
-
api_paths
=
@group
.
present?
?
{
milestones_path:
group_milestones_path
(
@group
),
labels_path:
group_labels_path
(
@group
)
}
:
{}
-
data_attributes
.
merge!
(
group_cycle_analytics_data
(
@group
))
-
image_paths
=
{
empty_state_svg_path:
image_path
(
"illustrations/analytics/cycle-analytics-empty-chart.svg"
),
no_data_svg_path:
image_path
(
"illustrations/analytics/cycle-analytics-empty-chart.svg"
),
no_access_svg_path:
image_path
(
"illustrations/analytics/no-access.svg"
)}
-
default_stages
=
{
default_stages:
cycle_analytics_default_stage_config
.
to_json
}
-
data_attributes
.
merge!
(
api_paths
,
image_paths
,
default_stages
)
-
add_page_specific_style
'page_bundles/cycle_analytics'
-
add_page_specific_style
'page_bundles/cycle_analytics'
#js-cycle-analytics-app
{
data:
data_attributes
}
#js-cycle-analytics-app
{
data:
data_attributes
}
ee/spec/frontend/analytics/cycle_analytics/components/__snapshots__/labels_selector_spec.js.snap
View file @
d90ee4bb
...
@@ -29,7 +29,7 @@ exports[`Value Stream Analytics LabelsSelector with no item selected will render
...
@@ -29,7 +29,7 @@ exports[`Value Stream Analytics LabelsSelector with no item selected will render
</gl-dropdown-stub>"
</gl-dropdown-stub>"
`;
`;
exports[`Value Stream Analytics LabelsSelector with selectedLabelId set will render the label selector 1`] = `
exports[`Value Stream Analytics LabelsSelector with selectedLabelId
s
set will render the label selector 1`] = `
"<gl-dropdown-stub headertext=\\"\\" hideheaderborder=\\"true\\" text=\\"\\" category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" toggleclass=\\"gl-overflow-hidden\\" class=\\"gl-w-full\\">
"<gl-dropdown-stub headertext=\\"\\" hideheaderborder=\\"true\\" text=\\"\\" category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" toggleclass=\\"gl-overflow-hidden\\" class=\\"gl-w-full\\">
<gl-dropdown-section-header-stub>Select a label </gl-dropdown-section-header-stub>
<gl-dropdown-section-header-stub>Select a label </gl-dropdown-section-header-stub>
<div class=\\"mb-3 px-3\\">
<div class=\\"mb-3 px-3\\">
...
...
ee/spec/frontend/analytics/cycle_analytics/components/labels_selector_spec.js
View file @
d90ee4bb
import
{
GlDropdownSectionHeader
}
from
'
@gitlab/ui
'
;
import
{
GlDropdownSectionHeader
}
from
'
@gitlab/ui
'
;
import
{
mount
,
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
{
mount
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
axios
from
'
axios
'
;
import
axios
from
'
axios
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
LabelsSelector
from
'
ee/analytics/cycle_analytics/components/labels_selector.vue
'
;
import
LabelsSelector
from
'
ee/analytics/cycle_analytics/components/labels_selector.vue
'
;
import
createStore
from
'
ee/analytics/cycle_analytics/store
'
;
import
createStore
from
'
ee/analytics/cycle_analytics/store
'
;
...
@@ -11,6 +12,7 @@ import createFlash from '~/flash';
...
@@ -11,6 +12,7 @@ import createFlash from '~/flash';
import
{
groupLabels
}
from
'
../mock_data
'
;
import
{
groupLabels
}
from
'
../mock_data
'
;
jest
.
mock
(
'
~/flash
'
);
jest
.
mock
(
'
~/flash
'
);
Vue
.
use
(
Vuex
);
const
selectedLabel
=
groupLabels
[
groupLabels
.
length
-
1
];
const
selectedLabel
=
groupLabels
[
groupLabels
.
length
-
1
];
const
findActiveItem
=
(
wrapper
)
=>
const
findActiveItem
=
(
wrapper
)
=>
...
@@ -24,14 +26,11 @@ const mockGroupLabelsRequest = (status = 200) =>
...
@@ -24,14 +26,11 @@ const mockGroupLabelsRequest = (status = 200) =>
describe
(
'
Value Stream Analytics LabelsSelector
'
,
()
=>
{
describe
(
'
Value Stream Analytics LabelsSelector
'
,
()
=>
{
let
store
=
null
;
let
store
=
null
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
function
createComponent
({
props
=
{
selectedLabelId
:
[]
},
shallow
=
true
}
=
{})
{
function
createComponent
({
props
=
{
selectedLabelId
s
:
[]
},
shallow
=
true
}
=
{})
{
store
=
createStore
();
store
=
createStore
();
const
func
=
shallow
?
shallowMount
:
mount
;
const
func
=
shallow
?
shallowMount
:
mount
;
return
func
(
LabelsSelector
,
{
return
func
(
LabelsSelector
,
{
localVue
,
store
:
{
store
:
{
...
store
,
...
store
,
getters
:
{
getters
:
{
...
@@ -71,6 +70,10 @@ describe('Value Stream Analytics LabelsSelector', () => {
...
@@ -71,6 +70,10 @@ describe('Value Stream Analytics LabelsSelector', () => {
expect
(
wrapper
.
text
()).
toContain
(
name
);
expect
(
wrapper
.
text
()).
toContain
(
name
);
});
});
it
(
'
will fetch the labels
'
,
()
=>
{
expect
(
mock
.
history
.
get
.
length
).
toBe
(
1
);
});
it
(
'
will render with the default option selected
'
,
()
=>
{
it
(
'
will render with the default option selected
'
,
()
=>
{
const
sectionHeader
=
wrapper
.
findComponent
(
GlDropdownSectionHeader
);
const
sectionHeader
=
wrapper
.
findComponent
(
GlDropdownSectionHeader
);
...
@@ -114,10 +117,10 @@ describe('Value Stream Analytics LabelsSelector', () => {
...
@@ -114,10 +117,10 @@ describe('Value Stream Analytics LabelsSelector', () => {
});
});
});
});
describe
(
'
with selectedLabelId set
'
,
()
=>
{
describe
(
'
with selectedLabelId
s
set
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
mock
=
mockGroupLabelsRequest
();
mock
=
mockGroupLabelsRequest
();
wrapper
=
createComponent
({
props
:
{
selectedLabelId
:
[
selectedLabel
.
id
]
}
});
wrapper
=
createComponent
({
props
:
{
selectedLabelId
s
:
[
selectedLabel
.
id
]
}
});
return
waitForPromises
();
return
waitForPromises
();
});
});
...
@@ -136,4 +139,19 @@ describe('Value Stream Analytics LabelsSelector', () => {
...
@@ -136,4 +139,19 @@ describe('Value Stream Analytics LabelsSelector', () => {
expect
(
activeItem
.
text
()).
toEqual
(
selectedLabel
.
name
);
expect
(
activeItem
.
text
()).
toEqual
(
selectedLabel
.
name
);
});
});
});
});
describe
(
'
with labels provided
'
,
()
=>
{
beforeEach
(()
=>
{
mock
=
mockGroupLabelsRequest
();
wrapper
=
createComponent
({
props
:
{
initialData
:
groupLabels
}
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
will not fetch the labels
'
,
()
=>
{
expect
(
mock
.
history
.
get
.
length
).
toBe
(
0
);
});
});
});
});
ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_spec.js
View file @
d90ee4bb
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
TasksByTypeChart
from
'
ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_chart.vue
'
;
import
TasksByTypeChart
from
'
ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_chart.vue
'
;
import
TasksByTypeFilters
from
'
ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue
'
;
import
TasksByTypeFilters
from
'
ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue
'
;
...
@@ -8,10 +9,18 @@ import {
...
@@ -8,10 +9,18 @@ import {
TASKS_BY_TYPE_FILTERS
,
TASKS_BY_TYPE_FILTERS
,
}
from
'
ee/analytics/cycle_analytics/constants
'
;
}
from
'
ee/analytics/cycle_analytics/constants
'
;
import
ChartSkeletonLoader
from
'
~/vue_shared/components/resizable_chart/skeleton_loader.vue
'
;
import
ChartSkeletonLoader
from
'
~/vue_shared/components/resizable_chart/skeleton_loader.vue
'
;
import
{
tasksByTypeData
,
taskByTypeFilters
}
from
'
../mock_data
'
;
import
{
tasksByTypeData
,
taskByTypeFilters
,
groupLabels
}
from
'
../mock_data
'
;
const
localVue
=
createLocalVue
();
const
fakeTopRankedLabels
=
[
localVue
.
use
(
Vuex
);
...
groupLabels
,
{
...
groupLabels
[
0
],
id
:
1337
,
name
:
'
fake label
'
,
},
];
Vue
.
use
(
Vuex
);
const
actionSpies
=
{
const
actionSpies
=
{
setTasksByTypeFilters
:
jest
.
fn
(),
setTasksByTypeFilters
:
jest
.
fn
(),
...
@@ -19,6 +28,9 @@ const actionSpies = {
...
@@ -19,6 +28,9 @@ const actionSpies = {
const
fakeStore
=
({
initialGetters
,
initialState
})
=>
const
fakeStore
=
({
initialGetters
,
initialState
})
=>
new
Vuex
.
Store
({
new
Vuex
.
Store
({
state
:
{
defaultGroupLabels
:
groupLabels
,
},
modules
:
{
modules
:
{
typeOfWork
:
{
typeOfWork
:
{
namespaced
:
true
,
namespaced
:
true
,
...
@@ -29,6 +41,7 @@ const fakeStore = ({ initialGetters, initialState }) =>
...
@@ -29,6 +41,7 @@ const fakeStore = ({ initialGetters, initialState }) =>
...
initialGetters
,
...
initialGetters
,
},
},
state
:
{
state
:
{
topRankedLabels
:
[],
...
initialState
,
...
initialState
,
},
},
actions
:
actionSpies
,
actions
:
actionSpies
,
...
@@ -39,7 +52,6 @@ const fakeStore = ({ initialGetters, initialState }) =>
...
@@ -39,7 +52,6 @@ const fakeStore = ({ initialGetters, initialState }) =>
describe
(
'
TypeOfWorkCharts
'
,
()
=>
{
describe
(
'
TypeOfWorkCharts
'
,
()
=>
{
function
createComponent
({
stubs
=
{},
initialGetters
,
initialState
}
=
{})
{
function
createComponent
({
stubs
=
{},
initialGetters
,
initialState
}
=
{})
{
return
shallowMount
(
TypeOfWorkCharts
,
{
return
shallowMount
(
TypeOfWorkCharts
,
{
localVue
,
store
:
fakeStore
({
initialGetters
,
initialState
}),
store
:
fakeStore
({
initialGetters
,
initialState
}),
stubs
:
{
stubs
:
{
TasksByTypeChart
:
true
,
TasksByTypeChart
:
true
,
...
@@ -51,6 +63,7 @@ describe('TypeOfWorkCharts', () => {
...
@@ -51,6 +63,7 @@ describe('TypeOfWorkCharts', () => {
let
wrapper
=
null
;
let
wrapper
=
null
;
const
labelIds
=
(
labels
)
=>
labels
.
map
(({
id
})
=>
id
);
const
findSubjectFilters
=
(
_wrapper
)
=>
_wrapper
.
findComponent
(
TasksByTypeFilters
);
const
findSubjectFilters
=
(
_wrapper
)
=>
_wrapper
.
findComponent
(
TasksByTypeFilters
);
const
findTasksByTypeChart
=
(
_wrapper
)
=>
_wrapper
.
findComponent
(
TasksByTypeChart
);
const
findTasksByTypeChart
=
(
_wrapper
)
=>
_wrapper
.
findComponent
(
TasksByTypeChart
);
const
findLoader
=
(
_wrapper
)
=>
_wrapper
.
findComponent
(
ChartSkeletonLoader
);
const
findLoader
=
(
_wrapper
)
=>
_wrapper
.
findComponent
(
ChartSkeletonLoader
);
...
@@ -77,6 +90,18 @@ describe('TypeOfWorkCharts', () => {
...
@@ -77,6 +90,18 @@ describe('TypeOfWorkCharts', () => {
it
(
'
does not render the loading icon
'
,
()
=>
{
it
(
'
does not render the loading icon
'
,
()
=>
{
expect
(
findLoader
(
wrapper
).
exists
()).
toBe
(
false
);
expect
(
findLoader
(
wrapper
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
with topRankedLabels
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
initialState
:
{
topRankedLabels
:
fakeTopRankedLabels
}
});
});
it
(
'
provides all the labels to the labels selector deduplicated
'
,
()
=>
{
const
wrapperLabelIds
=
labelIds
(
fakeTopRankedLabels
);
const
result
=
[...
labelIds
(
groupLabels
),
1337
];
expect
(
wrapperLabelIds
).
toEqual
(
result
);
});
});
});
});
describe
(
'
with no data
'
,
()
=>
{
describe
(
'
with no data
'
,
()
=>
{
...
...
ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_spec.js
View file @
d90ee4bb
...
@@ -28,6 +28,7 @@ describe('ValueStreamForm', () => {
...
@@ -28,6 +28,7 @@ describe('ValueStreamForm', () => {
const
createValueStreamMock
=
jest
.
fn
(()
=>
Promise
.
resolve
());
const
createValueStreamMock
=
jest
.
fn
(()
=>
Promise
.
resolve
());
const
updateValueStreamMock
=
jest
.
fn
(()
=>
Promise
.
resolve
());
const
updateValueStreamMock
=
jest
.
fn
(()
=>
Promise
.
resolve
());
const
fetchGroupLabelsMock
=
jest
.
fn
(()
=>
Promise
.
resolve
());
const
mockEvent
=
{
preventDefault
:
jest
.
fn
()
};
const
mockEvent
=
{
preventDefault
:
jest
.
fn
()
};
const
mockToastShow
=
jest
.
fn
();
const
mockToastShow
=
jest
.
fn
();
const
streamName
=
'
Cool stream
'
;
const
streamName
=
'
Cool stream
'
;
...
@@ -49,23 +50,26 @@ describe('ValueStreamForm', () => {
...
@@ -49,23 +50,26 @@ describe('ValueStreamForm', () => {
const
initialPreset
=
PRESET_OPTIONS_DEFAULT
;
const
initialPreset
=
PRESET_OPTIONS_DEFAULT
;
const
fakeStore
=
()
=>
const
fakeStore
=
(
{
state
}
)
=>
new
Vuex
.
Store
({
new
Vuex
.
Store
({
state
:
{
state
:
{
isCreatingValueStream
:
false
,
isCreatingValueStream
:
false
,
formEvents
,
formEvents
,
defaultGroupLabels
:
null
,
...
state
,
},
},
actions
:
{
actions
:
{
createValueStream
:
createValueStreamMock
,
createValueStream
:
createValueStreamMock
,
updateValueStream
:
updateValueStreamMock
,
updateValueStream
:
updateValueStreamMock
,
fetchGroupLabels
:
fetchGroupLabelsMock
,
},
},
});
});
const
createComponent
=
({
props
=
{},
data
=
{},
stubs
=
{}
}
=
{})
=>
const
createComponent
=
({
props
=
{},
data
=
{},
stubs
=
{}
,
state
=
{}
}
=
{})
=>
extendedWrapper
(
extendedWrapper
(
shallowMount
(
ValueStreamForm
,
{
shallowMount
(
ValueStreamForm
,
{
localVue
,
localVue
,
store
:
fakeStore
(),
store
:
fakeStore
(
{
state
}
),
data
()
{
data
()
{
return
{
return
{
...
data
,
...
data
,
...
@@ -140,6 +144,10 @@ describe('ValueStreamForm', () => {
...
@@ -140,6 +144,10 @@ describe('ValueStreamForm', () => {
expect
(
findHiddenStages
().
length
).
toBe
(
0
);
expect
(
findHiddenStages
().
length
).
toBe
(
0
);
});
});
it
(
'
will fetch group labels
'
,
()
=>
{
expect
(
fetchGroupLabelsMock
).
toHaveBeenCalled
();
});
describe
(
'
Add stage button
'
,
()
=>
{
describe
(
'
Add stage button
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
wrapper
=
createComponent
({
...
@@ -383,6 +391,18 @@ describe('ValueStreamForm', () => {
...
@@ -383,6 +391,18 @@ describe('ValueStreamForm', () => {
});
});
});
});
describe
(
'
defaultGroupLabels set
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
state
:
{
defaultGroupLabels
:
[]
},
});
});
it
(
'
does not fetch group labels
'
,
()
=>
{
expect
(
fetchGroupLabelsMock
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
form errors
'
,
()
=>
{
describe
(
'
form errors
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
wrapper
=
createComponent
({
...
...
ee/spec/frontend/analytics/cycle_analytics/store/actions_spec.js
View file @
d90ee4bb
...
@@ -7,7 +7,8 @@ import testAction from 'helpers/vuex_action_helper';
...
@@ -7,7 +7,8 @@ import testAction from 'helpers/vuex_action_helper';
import
{
createdAfter
,
createdBefore
,
currentGroup
}
from
'
jest/cycle_analytics/mock_data
'
;
import
{
createdAfter
,
createdBefore
,
currentGroup
}
from
'
jest/cycle_analytics/mock_data
'
;
import
{
I18N_VSA_ERROR_STAGES
,
I18N_VSA_ERROR_STAGE_MEDIAN
}
from
'
~/cycle_analytics/constants
'
;
import
{
I18N_VSA_ERROR_STAGES
,
I18N_VSA_ERROR_STAGE_MEDIAN
}
from
'
~/cycle_analytics/constants
'
;
import
createFlash
from
'
~/flash
'
;
import
createFlash
from
'
~/flash
'
;
import
{
allowedStages
as
stages
,
valueStreams
}
from
'
../mock_data
'
;
import
httpStatusCodes
from
'
~/lib/utils/http_status
'
;
import
{
allowedStages
as
stages
,
valueStreams
,
endpoints
,
groupLabels
}
from
'
../mock_data
'
;
const
group
=
{
fullPath
:
'
fake_group_full_path
'
};
const
group
=
{
fullPath
:
'
fake_group_full_path
'
};
const
milestonesPath
=
'
fake_milestones_path
'
;
const
milestonesPath
=
'
fake_milestones_path
'
;
...
@@ -291,6 +292,7 @@ describe('Value Stream Analytics actions', () => {
...
@@ -291,6 +292,7 @@ describe('Value Stream Analytics actions', () => {
${
'
typeOfWork/setLoading
'
}
|
${
true
}
${
'
typeOfWork/setLoading
'
}
|
${
true
}
`
(
'
dispatches $action
'
,
async
({
action
,
args
})
=>
{
`
(
'
dispatches $action
'
,
async
({
action
,
args
})
=>
{
await
actions
.
initializeCycleAnalytics
(
store
,
initialData
);
await
actions
.
initializeCycleAnalytics
(
store
,
initialData
);
expect
(
mockDispatch
).
toHaveBeenCalledWith
(
action
,
args
);
expect
(
mockDispatch
).
toHaveBeenCalledWith
(
action
,
args
);
});
});
...
@@ -351,4 +353,38 @@ describe('Value Stream Analytics actions', () => {
...
@@ -351,4 +353,38 @@ describe('Value Stream Analytics actions', () => {
[],
[],
));
));
});
});
describe
(
'
fetchGroupLabels
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
endpoints
.
groupLabels
).
reply
(
httpStatusCodes
.
OK
,
groupLabels
);
});
it
(
`will commit the "REQUEST_GROUP_LABELS" and "RECEIVE_GROUP_LABELS_SUCCESS" mutations`
,
()
=>
{
return
testAction
({
action
:
actions
.
fetchGroupLabels
,
state
,
expectedMutations
:
[
{
type
:
types
.
REQUEST_GROUP_LABELS
},
{
type
:
types
.
RECEIVE_GROUP_LABELS_SUCCESS
,
payload
:
groupLabels
},
],
});
});
describe
(
'
with a failed request
'
,
()
=>
{
beforeEach
(()
=>
{
mock
.
onGet
(
endpoints
.
groupLabels
).
reply
(
httpStatusCodes
.
BAD_REQUEST
);
});
it
(
`will commit the "RECEIVE_GROUP_LABELS_ERROR" mutation`
,
()
=>
{
return
testAction
({
action
:
actions
.
fetchGroupLabels
,
state
,
expectedMutations
:
[
{
type
:
types
.
REQUEST_GROUP_LABELS
},
{
type
:
types
.
RECEIVE_GROUP_LABELS_ERROR
},
],
});
});
});
});
});
});
ee/spec/frontend/analytics/cycle_analytics/store/mutations_spec.js
View file @
d90ee4bb
...
@@ -15,6 +15,7 @@ import {
...
@@ -15,6 +15,7 @@ import {
valueStreams
,
valueStreams
,
rawCustomStageEvents
,
rawCustomStageEvents
,
camelCasedStageEvents
,
camelCasedStageEvents
,
groupLabels
,
}
from
'
../mock_data
'
;
}
from
'
../mock_data
'
;
let
state
=
null
;
let
state
=
null
;
...
@@ -62,6 +63,8 @@ describe('Value Stream Analytics mutations', () => {
...
@@ -62,6 +63,8 @@ describe('Value Stream Analytics mutations', () => {
${
types
.
INITIALIZE_VALUE_STREAM_SUCCESS
}
|
${
'
isLoading
'
}
|
${
false
}
${
types
.
INITIALIZE_VALUE_STREAM_SUCCESS
}
|
${
'
isLoading
'
}
|
${
false
}
${
types
.
REQUEST_STAGE_COUNTS
}
|
${
'
stageCounts
'
}
|
${{}}
${
types
.
REQUEST_STAGE_COUNTS
}
|
${
'
stageCounts
'
}
|
${{}}
$
{
types
.
RECEIVE_STAGE_COUNTS_ERROR
}
|
${
'
stageCounts
'
}
|
${{}}
$
{
types
.
RECEIVE_STAGE_COUNTS_ERROR
}
|
${
'
stageCounts
'
}
|
${{}}
$
{
types
.
REQUEST_GROUP_LABELS
}
|
${
'
defaultGroupLabels
'
}
|
${[]}
${
types
.
RECEIVE_GROUP_LABELS_ERROR
}
|
${
'
defaultGroupLabels
'
}
|
${[]}
${
types
.
SET_STAGE_EVENTS
}
|
${
'
formEvents
'
}
|
${[]}
${
types
.
SET_STAGE_EVENTS
}
|
${
'
formEvents
'
}
|
${[]}
`
(
'
$mutation will set $stateKey=$value
'
,
({
mutation
,
stateKey
,
value
})
=>
{
`
(
'
$mutation will set $stateKey=$value
'
,
({
mutation
,
stateKey
,
value
})
=>
{
mutations
[
mutation
](
state
);
mutations
[
mutation
](
state
);
...
@@ -96,6 +99,7 @@ describe('Value Stream Analytics mutations', () => {
...
@@ -96,6 +99,7 @@ describe('Value Stream Analytics mutations', () => {
${
types
.
SET_SELECTED_VALUE_STREAM
}
|
${
valueStreams
[
1
].
id
}
|
${{
selectedValueStream
:
{}
}}
${
types
.
SET_SELECTED_VALUE_STREAM
}
|
${
valueStreams
[
1
].
id
}
|
${{
selectedValueStream
:
{}
}}
$
{
types
.
RECEIVE_CREATE_VALUE_STREAM_SUCCESS
}
|
${
valueStreams
[
1
]}
|
${{
selectedValueStream
:
valueStreams
[
1
]
}
}
$
{
types
.
RECEIVE_CREATE_VALUE_STREAM_SUCCESS
}
|
${
valueStreams
[
1
]}
|
${{
selectedValueStream
:
valueStreams
[
1
]
}
}
${
types
.
RECEIVE_UPDATE_VALUE_STREAM_SUCCESS
}
|
${
valueStreams
[
1
]}
|
${{
selectedValueStream
:
valueStreams
[
1
]
}
}
${
types
.
RECEIVE_UPDATE_VALUE_STREAM_SUCCESS
}
|
${
valueStreams
[
1
]}
|
${{
selectedValueStream
:
valueStreams
[
1
]
}
}
${
types
.
RECEIVE_GROUP_LABELS_SUCCESS
}
|
${
groupLabels
}
|
${{
defaultGroupLabels
:
groupLabels
}
}
${
types
.
SET_PAGINATION
}
|
${
pagination
}
|
${{
pagination
:
{
...
pagination
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
}
}}
${
types
.
SET_PAGINATION
}
|
${
pagination
}
|
${{
pagination
:
{
...
pagination
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
}
}}
${
types
.
SET_PAGINATION
}
|
${{
...
pagination
,
sort
:
'
duration
'
,
direction
:
'
asc
'
}
} |
${{
pagination
:
{
...
pagination
,
sort
:
'
duration
'
,
direction
:
'
asc
'
}
}}
${
types
.
SET_PAGINATION
}
|
${{
...
pagination
,
sort
:
'
duration
'
,
direction
:
'
asc
'
}
} |
${{
pagination
:
{
...
pagination
,
sort
:
'
duration
'
,
direction
:
'
asc
'
}
}}
${
types
.
SET_STAGE_EVENTS
}
|
${
rawCustomStageEvents
}
|
${{
formEvents
:
camelCasedStageEvents
}
}
${
types
.
SET_STAGE_EVENTS
}
|
${
rawCustomStageEvents
}
|
${{
formEvents
:
camelCasedStageEvents
}
}
...
...
ee/spec/helpers/ee/groups/analytics/cycle_analytics_helper_spec.rb
0 → 100644
View file @
d90ee4bb
# frozen_string_literal: true
require
"spec_helper"
RSpec
.
describe
EE
::
Groups
::
Analytics
::
CycleAnalyticsHelper
do
describe
'#group_cycle_analytics_data'
do
let
(
:image_path_keys
)
{
[
:empty_state_svg_path
,
:no_data_svg_path
,
:no_access_svg_path
]
}
let
(
:additional_data_keys
)
{
[
:default_stages
]
}
subject
(
:group_cycle_analytics
)
{
helper
.
group_cycle_analytics_data
(
group
)
}
context
'when a group is present'
do
let
(
:group
)
{
create
(
:group
)
}
let
(
:api_path_keys
)
{
[
:milestones_path
,
:labels_path
]
}
it
"sets the correct data keys"
do
expect
(
group_cycle_analytics
.
keys
)
.
to
match_array
(
api_path_keys
+
image_path_keys
+
additional_data_keys
)
end
end
context
'when a group is not present'
do
let
(
:group
)
{
nil
}
it
"sets the correct data keys"
do
expect
(
group_cycle_analytics
.
keys
)
.
to
match_array
(
image_path_keys
+
additional_data_keys
)
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment