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
76e23a12
Commit
76e23a12
authored
Oct 12, 2021
by
Ezekiel Kigbo
Committed by
Savas Vedova
Oct 12, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[VSA][FE] Add sorting and pagination to the project level stage table
parent
f21f76b8
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
275 additions
and
88 deletions
+275
-88
app/assets/javascripts/cycle_analytics/components/base.vue
app/assets/javascripts/cycle_analytics/components/base.vue
+13
-3
app/assets/javascripts/cycle_analytics/components/stage_table.vue
...ts/javascripts/cycle_analytics/components/stage_table.vue
+3
-0
app/assets/javascripts/cycle_analytics/store/actions.js
app/assets/javascripts/cycle_analytics/store/actions.js
+17
-3
app/assets/javascripts/cycle_analytics/store/getters.js
app/assets/javascripts/cycle_analytics/store/getters.js
+8
-0
app/assets/javascripts/cycle_analytics/store/mutation_types.js
...ssets/javascripts/cycle_analytics/store/mutation_types.js
+1
-0
app/assets/javascripts/cycle_analytics/store/mutations.js
app/assets/javascripts/cycle_analytics/store/mutations.js
+20
-1
app/assets/javascripts/cycle_analytics/store/state.js
app/assets/javascripts/cycle_analytics/store/state.js
+11
-0
doc/user/analytics/img/project_vsa_stage_table_v14_4.png
doc/user/analytics/img/project_vsa_stage_table_v14_4.png
+0
-0
doc/user/analytics/value_stream_analytics.md
doc/user/analytics/value_stream_analytics.md
+28
-0
doc/user/group/value_stream_analytics/index.md
doc/user/group/value_stream_analytics/index.md
+2
-2
ee/app/assets/javascripts/analytics/cycle_analytics/store/getters.js
...ts/javascripts/analytics/cycle_analytics/store/getters.js
+6
-8
ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js
...rontend/analytics/cycle_analytics/components/base_spec.js
+1
-1
ee/spec/frontend/analytics/cycle_analytics/mock_data.js
ee/spec/frontend/analytics/cycle_analytics/mock_data.js
+1
-25
ee/spec/frontend/analytics/cycle_analytics/store/getters_spec.js
.../frontend/analytics/cycle_analytics/store/getters_spec.js
+0
-22
spec/features/cycle_analytics_spec.rb
spec/features/cycle_analytics_spec.rb
+53
-6
spec/frontend/cycle_analytics/base_spec.js
spec/frontend/cycle_analytics/base_spec.js
+7
-1
spec/frontend/cycle_analytics/mock_data.js
spec/frontend/cycle_analytics/mock_data.js
+26
-1
spec/frontend/cycle_analytics/store/actions_spec.js
spec/frontend/cycle_analytics/store/actions_spec.js
+34
-3
spec/frontend/cycle_analytics/store/getters_spec.js
spec/frontend/cycle_analytics/store/getters_spec.js
+26
-1
spec/frontend/cycle_analytics/store/mutations_spec.js
spec/frontend/cycle_analytics/store/mutations_spec.js
+18
-11
No files found.
app/assets/javascripts/cycle_analytics/components/base.vue
View file @
76e23a12
...
...
@@ -51,6 +51,7 @@ export default {
'
features
'
,
'
createdBefore
'
,
'
createdAfter
'
,
'
pagination
'
,
]),
...
mapGetters
([
'
pathNavigationData
'
,
'
filterParams
'
]),
displayStageEvents
()
{
...
...
@@ -99,7 +100,12 @@ export default {
},
},
methods
:
{
...
mapActions
([
'
fetchStageData
'
,
'
setSelectedStage
'
,
'
setDateRange
'
]),
...
mapActions
([
'
fetchStageData
'
,
'
setSelectedStage
'
,
'
setDateRange
'
,
'
updateStageTablePagination
'
,
]),
onSetDateRange
({
startDate
,
endDate
})
{
this
.
setDateRange
({
createdAfter
:
new
Date
(
startDate
),
...
...
@@ -108,6 +114,7 @@ export default {
},
onSelectStage
(
stage
)
{
this
.
setSelectedStage
(
stage
);
this
.
updateStageTablePagination
({
...
this
.
pagination
,
page
:
1
});
},
dismissOverviewDialog
()
{
this
.
isOverviewDialogDismissed
=
true
;
...
...
@@ -117,6 +124,9 @@ export default {
const
{
permissions
}
=
this
;
return
Boolean
(
permissions
?.[
id
]);
},
onHandleUpdatePagination
(
data
)
{
this
.
updateStageTablePagination
(
data
);
},
},
dayRangeOptions
:
[
7
,
30
,
90
],
i18n
:
{
...
...
@@ -163,8 +173,8 @@ export default {
:empty-state-title=
"emptyStageTitle"
:empty-state-message=
"emptyStageText"
:no-data-svg-path=
"noDataSvgPath"
:pagination=
"
null
"
:sortable=
"false
"
:pagination=
"
pagination
"
@
handleUpdatePagination=
"onHandleUpdatePagination
"
/>
</div>
</
template
>
app/assets/javascripts/cycle_analytics/components/stage_table.vue
View file @
76e23a12
...
...
@@ -194,6 +194,9 @@ export default {
><formatted-stage-count
:stage-count=
"stageCount"
/></gl-badge>
</
template
>
<
template
#head(duration)=
"data"
>
<span
data-testid=
"vsa-stage-header-duration"
>
{{
data
.
label
}}
</span>
</
template
>
<
template
#cell(end_event)=
"{ item }"
>
<div
data-testid=
"vsa-stage-event"
>
<div
v-if=
"item.id"
data-testid=
"vsa-stage-content"
>
...
...
app/assets/javascripts/cycle_analytics/store/actions.js
View file @
76e23a12
...
...
@@ -6,6 +6,7 @@ import {
getValueStreamStageRecords
,
getValueStreamStageCounts
,
}
from
'
~/api/analytics_api
'
;
import
{
normalizeHeaders
,
parseIntPagination
}
from
'
~/lib/utils/common_utils
'
;
import
createFlash
from
'
~/flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
DEFAULT_VALUE_STREAM
,
I18N_VSA_ERROR_STAGE_MEDIAN
}
from
'
../constants
'
;
...
...
@@ -72,16 +73,21 @@ export const fetchCycleAnalyticsData = ({
});
};
export
const
fetchStageData
=
({
getters
:
{
requestParams
,
filterParams
},
commit
})
=>
{
export
const
fetchStageData
=
({
getters
:
{
requestParams
,
filterParams
,
paginationParams
},
commit
,
})
=>
{
commit
(
types
.
REQUEST_STAGE_DATA
);
return
getValueStreamStageRecords
(
requestParams
,
filterParams
)
.
then
(({
data
})
=>
{
return
getValueStreamStageRecords
(
requestParams
,
{
...
filterParams
,
...
paginationParams
}
)
.
then
(({
data
,
headers
})
=>
{
// when there's a query timeout, the request succeeds but the error is encoded in the response data
if
(
data
?.
error
)
{
commit
(
types
.
RECEIVE_STAGE_DATA_ERROR
,
data
.
error
);
}
else
{
commit
(
types
.
RECEIVE_STAGE_DATA_SUCCESS
,
data
);
const
{
page
=
null
,
nextPage
=
null
}
=
parseIntPagination
(
normalizeHeaders
(
headers
));
commit
(
types
.
SET_PAGINATION
,
{
...
paginationParams
,
page
,
hasNextPage
:
Boolean
(
nextPage
)
});
}
})
.
catch
(()
=>
commit
(
types
.
RECEIVE_STAGE_DATA_ERROR
));
...
...
@@ -176,6 +182,14 @@ export const setDateRange = ({ dispatch, commit }, { createdAfter, createdBefore
return
refetchStageData
(
dispatch
);
};
export
const
updateStageTablePagination
=
(
{
commit
,
dispatch
,
state
:
{
selectedStage
}
},
paginationParams
,
)
=>
{
commit
(
types
.
SET_PAGINATION
,
paginationParams
);
return
dispatch
(
'
fetchStageData
'
,
selectedStage
.
id
);
};
export
const
initializeVsa
=
({
commit
,
dispatch
},
initialData
=
{})
=>
{
commit
(
types
.
INITIALIZE_VSA
,
initialData
);
...
...
app/assets/javascripts/cycle_analytics/store/getters.js
View file @
76e23a12
import
dateFormat
from
'
dateformat
'
;
import
{
dateFormats
}
from
'
~/analytics/shared/constants
'
;
import
{
filterToQueryObject
}
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_utils
'
;
import
{
PAGINATION_TYPE
}
from
'
../constants
'
;
import
{
transformStagesForPathNavigation
,
filterStagesByHiddenStatus
}
from
'
../utils
'
;
export
const
pathNavigationData
=
({
stages
,
medians
,
stageCounts
,
selectedStage
})
=>
{
...
...
@@ -21,6 +22,13 @@ export const requestParams = (state) => {
return
{
requestPath
:
fullPath
,
valueStreamId
,
stageId
};
};
export
const
paginationParams
=
({
pagination
:
{
page
,
sort
,
direction
}
})
=>
({
pagination
:
PAGINATION_TYPE
,
sort
,
direction
,
page
,
});
const
filterBarParams
=
({
filters
})
=>
{
const
{
authors
:
{
selected
:
selectedAuthor
},
...
...
app/assets/javascripts/cycle_analytics/store/mutation_types.js
View file @
76e23a12
...
...
@@ -4,6 +4,7 @@ export const SET_LOADING = 'SET_LOADING';
export
const
SET_SELECTED_VALUE_STREAM
=
'
SET_SELECTED_VALUE_STREAM
'
;
export
const
SET_SELECTED_STAGE
=
'
SET_SELECTED_STAGE
'
;
export
const
SET_DATE_RANGE
=
'
SET_DATE_RANGE
'
;
export
const
SET_PAGINATION
=
'
SET_PAGINATION
'
;
export
const
REQUEST_VALUE_STREAMS
=
'
REQUEST_VALUE_STREAMS
'
;
export
const
RECEIVE_VALUE_STREAMS_SUCCESS
=
'
RECEIVE_VALUE_STREAMS_SUCCESS
'
;
...
...
app/assets/javascripts/cycle_analytics/store/mutations.js
View file @
76e23a12
import
Vue
from
'
vue
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
PAGINATION_SORT_FIELD_END_EVENT
,
PAGINATION_SORT_DIRECTION_DESC
}
from
'
../constants
'
;
import
{
formatMedianValues
}
from
'
../utils
'
;
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
INITIALIZE_VSA
](
state
,
{
endpoints
,
features
,
createdBefore
,
createdAfter
})
{
[
types
.
INITIALIZE_VSA
](
state
,
{
endpoints
,
features
,
createdBefore
,
createdAfter
,
pagination
=
{}
},
)
{
state
.
endpoints
=
endpoints
;
state
.
createdBefore
=
createdBefore
;
state
.
createdAfter
=
createdAfter
;
state
.
features
=
features
;
Vue
.
set
(
state
,
'
pagination
'
,
{
page
:
pagination
.
page
??
state
.
pagination
.
page
,
sort
:
pagination
.
sort
??
state
.
pagination
.
sort
,
direction
:
pagination
.
direction
??
state
.
pagination
.
direction
,
});
},
[
types
.
SET_LOADING
](
state
,
loadingState
)
{
state
.
isLoading
=
loadingState
;
...
...
@@ -22,6 +33,14 @@ export default {
state
.
createdBefore
=
createdBefore
;
state
.
createdAfter
=
createdAfter
;
},
[
types
.
SET_PAGINATION
](
state
,
{
page
,
hasNextPage
,
sort
,
direction
})
{
Vue
.
set
(
state
,
'
pagination
'
,
{
page
,
hasNextPage
,
sort
:
sort
||
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
direction
||
PAGINATION_SORT_DIRECTION_DESC
,
});
},
[
types
.
REQUEST_VALUE_STREAMS
](
state
)
{
state
.
valueStreams
=
[];
},
...
...
app/assets/javascripts/cycle_analytics/store/state.js
View file @
76e23a12
import
{
PAGINATION_SORT_FIELD_END_EVENT
,
PAGINATION_SORT_DIRECTION_DESC
,
}
from
'
~/cycle_analytics/constants
'
;
export
default
()
=>
({
id
:
null
,
features
:
{},
...
...
@@ -20,4 +25,10 @@ export default () => ({
isLoadingStage
:
false
,
isEmptyStage
:
false
,
permissions
:
{},
pagination
:
{
page
:
null
,
hasNextPage
:
false
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
,
},
});
doc/user/analytics/img/project_vsa_stage_table_v14_4.png
0 → 100644
View file @
76e23a12
108 KB
doc/user/analytics/value_stream_analytics.md
View file @
76e23a12
...
...
@@ -49,6 +49,34 @@ To filter analytics results based on a date range,
select different
**From**
and
**To**
days
from the date picker (default: last 30 days).
### Stage table
> Sorting the stage table [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335974) in GitLab 14.4.
![
Value Stream Analytics Stage table
](
img/project_vsa_stage_table_v14_4.png
"Project VSA stage table"
)
The stage table shows a list of related workflow items for the selected stage. This can include:
-
CI/CD jobs
-
Issues
-
Merge requests
-
Pipelines
A little badge next to the workflow items table header shows the number of workflow items that
completed the selected stage.
The stage table also includes the
**Time**
column, which shows how long it takes each item to pass
through the selected value stream stage.
To sort the stage table by a table column, select the table header.
You can sort in ascending or descending order. To find items that spent the most time in a stage,
potentially causing bottlenecks in your value stream, sort the table by the
**Time**
column.
From there, select individual items to drill in and investigate how delays are happening.
To see which items most recently exited the stage, sort by the work item column on the left.
The table displays 20 items per page. If there are more than 20 items, you can use the
**Prev**
and
**Next**
buttons to navigate through the pages.
## How Time metrics are measured
The
**Time**
metrics near the top of the page are measured as follows:
...
...
doc/user/group/value_stream_analytics/index.md
View file @
76e23a12
...
...
@@ -284,9 +284,9 @@ To sort the stage table by a table column, select the table header.
You can sort in ascending or descending order. To find items that spent the most time in a stage,
potentially causing bottlenecks in your value stream, sort the table by the
**Time**
column.
From there, select individual items to drill in and investigate how delays are happening.
To see which items
the stage most recently
, sort by the work item column on the left.
To see which items
most recently exited the stage
, sort by the work item column on the left.
The table displays
up to 20 items at a tim
e. If there are more than 20 items, you can use the
The table displays
20 items per pag
e. If there are more than 20 items, you can use the
**Prev**
and
**Next**
buttons to navigate through the pages.
### Creating a value stream
...
...
ee/app/assets/javascripts/analytics/cycle_analytics/store/getters.js
View file @
76e23a12
import
dateFormat
from
'
dateformat
'
;
import
{
isNumber
}
from
'
lodash
'
;
import
{
dateFormats
}
from
'
~/analytics/shared/constants
'
;
import
{
OVERVIEW_STAGE_ID
,
PAGINATION_TYPE
}
from
'
~/cycle_analytics/constants
'
;
import
{
pathNavigationData
as
basePathNavigationData
}
from
'
~/cycle_analytics/store/getters
'
;
import
{
OVERVIEW_STAGE_ID
}
from
'
~/cycle_analytics/constants
'
;
import
{
pathNavigationData
as
basePathNavigationData
,
paginationParams
as
basePaginationParams
,
}
from
'
~/cycle_analytics/store/getters
'
;
import
{
filterStagesByHiddenStatus
}
from
'
~/cycle_analytics/utils
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
httpStatus
from
'
~/lib/utils/http_status
'
;
...
...
@@ -46,12 +49,7 @@ export const cycleAnalyticsRequestParams = (state, getters) => {
};
};
export
const
paginationParams
=
({
pagination
:
{
page
,
sort
,
direction
}
})
=>
({
pagination
:
PAGINATION_TYPE
,
sort
,
direction
,
page
,
});
export
const
paginationParams
=
basePaginationParams
;
export
const
hiddenStages
=
({
stages
})
=>
filterStagesByHiddenStatus
(
stages
);
export
const
activeStages
=
({
stages
})
=>
filterStagesByHiddenStatus
(
stages
,
false
);
...
...
ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js
View file @
76e23a12
...
...
@@ -14,6 +14,7 @@ import {
createdBefore
,
createdAfter
,
selectedProjects
,
initialPaginationQuery
,
}
from
'
jest/cycle_analytics/mock_data
'
;
import
{
toYmd
}
from
'
~/analytics/shared/utils
'
;
import
PathNavigation
from
'
~/cycle_analytics/components/path_navigation.vue
'
;
...
...
@@ -33,7 +34,6 @@ import httpStatusCodes from '~/lib/utils/http_status';
import
*
as
urlUtils
from
'
~/lib/utils/url_utility
'
;
import
UrlSync
from
'
~/vue_shared/components/url_sync.vue
'
;
import
{
initialPaginationQuery
,
valueStreams
,
endpoints
,
customizableStagesAndEvents
,
...
...
ee/spec/frontend/analytics/cycle_analytics/mock_data.js
View file @
76e23a12
import
tasksByType
from
'
test_fixtures/analytics/charts/type_of_work/tasks_by_type.json
'
;
import
{
uniq
}
from
'
lodash
'
;
import
tasksByType
from
'
test_fixtures/analytics/charts/type_of_work/tasks_by_type.json
'
;
import
{
TASKS_BY_TYPE_SUBJECT_ISSUE
,
OVERVIEW_STAGE_CONFIG
,
...
...
@@ -20,11 +20,6 @@ import {
deepCamelCase
,
}
from
'
jest/cycle_analytics/mock_data
'
;
import
{
toYmd
}
from
'
~/analytics/shared/utils
'
;
import
{
PAGINATION_TYPE
,
PAGINATION_SORT_DIRECTION_DESC
,
PAGINATION_SORT_FIELD_END_EVENT
,
}
from
'
~/cycle_analytics/constants
'
;
import
{
transformStagesForPathNavigation
,
formatMedianValues
}
from
'
~/cycle_analytics/utils
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
getDatesInRange
}
from
'
~/lib/utils/datetime_utility
'
;
...
...
@@ -270,22 +265,3 @@ export const rawDurationMedianData = [
];
export
const
pathNavIssueMetric
=
172800
;
export
const
initialPaginationQuery
=
{
page
:
15
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
,
};
export
const
initialPaginationState
=
{
...
initialPaginationQuery
,
page
:
null
,
hasNextPage
:
false
,
};
export
const
basePaginationResult
=
{
pagination
:
PAGINATION_TYPE
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
,
page
:
null
,
};
ee/spec/frontend/analytics/cycle_analytics/store/getters_spec.js
View file @
76e23a12
...
...
@@ -14,8 +14,6 @@ import {
issueStage
,
stageMediansWithNumericIds
,
stageCounts
,
basePaginationResult
,
initialPaginationState
,
transformedStagePathData
,
}
from
'
../mock_data
'
;
...
...
@@ -221,26 +219,6 @@ describe('Value Stream Analytics getters', () => {
});
});
describe
(
'
paginationParams
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{
pagination
:
initialPaginationState
};
});
it
(
'
returns the `pagination` type
'
,
()
=>
{
expect
(
getters
.
paginationParams
(
state
)).
toEqual
(
basePaginationResult
);
});
it
(
'
returns the `sort` type
'
,
()
=>
{
expect
(
getters
.
paginationParams
(
state
)).
toEqual
(
basePaginationResult
);
});
it
(
'
with page=10, sets the `page` property
'
,
()
=>
{
const
page
=
10
;
state
=
{
pagination
:
{
...
initialPaginationState
,
page
}
};
expect
(
getters
.
paginationParams
(
state
)).
toEqual
({
...
basePaginationResult
,
page
});
});
});
describe
(
'
selectedStageCount
'
,
()
=>
{
it
(
'
returns the count when a value exist for the given stage
'
,
()
=>
{
state
=
{
selectedStage
:
{
id
:
1
},
stageCounts
:
{
1
:
10
,
2
:
20
}
};
...
...
spec/features/cycle_analytics_spec.rb
View file @
76e23a12
...
...
@@ -7,10 +7,13 @@ RSpec.describe 'Value Stream Analytics', :js do
let_it_be
(
:guest
)
{
create
(
:user
)
}
let_it_be
(
:stage_table_selector
)
{
'[data-testid="vsa-stage-table"]'
}
let_it_be
(
:stage_table_event_selector
)
{
'[data-testid="vsa-stage-event"]'
}
let_it_be
(
:stage_table_event_title_selector
)
{
'[data-testid="vsa-stage-event-title"]'
}
let_it_be
(
:stage_table_pagination_selector
)
{
'[data-testid="vsa-stage-pagination"]'
}
let_it_be
(
:stage_table_duration_column_header_selector
)
{
'[data-testid="vsa-stage-header-duration"]'
}
let_it_be
(
:metrics_selector
)
{
"[data-testid='vsa-time-metrics']"
}
let_it_be
(
:metric_value_selector
)
{
"[data-testid='displayValue']"
}
let
(
:stage_table
)
{
page
.
find
(
stage_table_selector
)
}
let
(
:stage_table
)
{
find
(
stage_table_selector
)
}
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
created_at:
2
.
days
.
ago
)
}
let
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
...
...
@@ -53,6 +56,7 @@ RSpec.describe 'Value Stream Analytics', :js do
# So setting the date range to be the last 2 days should skip past the existing data
from
=
2
.
days
.
ago
.
strftime
(
"%Y-%m-%d"
)
to
=
1
.
day
.
ago
.
strftime
(
"%Y-%m-%d"
)
max_items_per_page
=
20
around
do
|
example
|
travel_to
(
5
.
days
.
ago
)
{
example
.
run
}
...
...
@@ -60,9 +64,8 @@ RSpec.describe 'Value Stream Analytics', :js do
before
do
project
.
add_maintainer
(
user
)
create_list
(
:issue
,
2
,
project:
project
,
created_at:
2
.
weeks
.
ago
,
milestone:
milestone
)
create_cycle
(
user
,
project
,
issue
,
mr
,
milestone
,
pipeline
)
create_list
(
:issue
,
max_items_per_page
,
project:
project
,
created_at:
2
.
weeks
.
ago
,
milestone:
milestone
)
deploy_master
(
user
,
project
)
issue
.
metrics
.
update!
(
first_mentioned_in_commit_at:
issue
.
metrics
.
first_associated_with_milestone_at
+
1
.
hour
)
...
...
@@ -81,6 +84,8 @@ RSpec.describe 'Value Stream Analytics', :js do
wait_for_requests
end
let
(
:stage_table_events
)
{
stage_table
.
all
(
stage_table_event_selector
)
}
it
'displays metrics'
do
metrics_tiles
=
page
.
find
(
metrics_selector
)
...
...
@@ -112,20 +117,62 @@ RSpec.describe 'Value Stream Analytics', :js do
end
it
'can filter the issues by date'
do
expect
(
stage_table
.
all
(
stage_table_event_selector
).
length
).
to
eq
(
3
)
expect
(
page
).
to
have_selector
(
stage_table_event_selector
)
set_daterange
(
from
,
to
)
expect
(
stage_table
.
all
(
stage_table_event_selector
).
length
).
to
eq
(
0
)
expect
(
page
).
not_to
have_selector
(
stage_table_event_selector
)
expect
(
page
).
not_to
have_selector
(
stage_table_pagination_selector
)
end
it
'can filter the metrics by date'
do
expect
(
metrics_values
).
to
eq
([
"3
.0"
,
"2.0"
,
"1.0"
,
"0.0"
])
expect
(
metrics_values
).
to
match_array
([
"21
.0"
,
"2.0"
,
"1.0"
,
"0.0"
])
set_daterange
(
from
,
to
)
expect
(
metrics_values
).
to
eq
([
'-'
]
*
4
)
end
it
'can sort records'
do
# NOTE: checking that the string changes should suffice
# depending on the order the tests are run we might run into problems with hard coded strings
original_first_title
=
first_stage_title
stage_time_column
.
click
expect_to_be_sorted
"descending"
expect
(
first_stage_title
).
not_to
have_text
(
original_first_title
,
exact:
true
)
stage_time_column
.
click
expect_to_be_sorted
"ascending"
expect
(
first_stage_title
).
to
have_text
(
original_first_title
,
exact:
true
)
end
it
'paginates the results'
do
original_first_title
=
first_stage_title
expect
(
page
).
to
have_selector
(
stage_table_pagination_selector
)
go_to_next_page
expect
(
page
).
not_to
have_text
(
original_first_title
,
exact:
true
)
end
def
stage_time_column
stage_table
.
find
(
stage_table_duration_column_header_selector
).
ancestor
(
"th"
)
end
def
first_stage_title
stage_table
.
all
(
stage_table_event_title_selector
).
first
.
text
end
def
expect_to_be_sorted
(
direction
)
expect
(
stage_time_column
[
'aria-sort'
]).
to
eq
(
direction
)
end
def
go_to_next_page
page
.
find
(
stage_table_pagination_selector
).
find_link
(
"Next"
).
click
end
end
end
...
...
spec/frontend/cycle_analytics/base_spec.js
View file @
76e23a12
...
...
@@ -19,6 +19,7 @@ import {
createdAfter
,
currentGroup
,
stageCounts
,
initialPaginationState
as
pagination
,
}
from
'
./mock_data
'
;
const
selectedStageEvents
=
issueEvents
.
events
;
...
...
@@ -81,6 +82,7 @@ const findOverviewMetrics = () => wrapper.findComponent(ValueStreamMetrics);
const
findStageTable
=
()
=>
wrapper
.
findComponent
(
StageTable
);
const
findStageEvents
=
()
=>
findStageTable
().
props
(
'
stageEvents
'
);
const
findEmptyStageTitle
=
()
=>
wrapper
.
findComponent
(
GlEmptyState
).
props
(
'
title
'
);
const
findPagination
=
()
=>
wrapper
.
findByTestId
(
'
vsa-stage-pagination
'
);
const
hasMetricsRequests
=
(
reqs
)
=>
{
const
foundReqs
=
findOverviewMetrics
().
props
(
'
requests
'
);
...
...
@@ -90,7 +92,7 @@ const hasMetricsRequests = (reqs) => {
describe
(
'
Value stream analytics component
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
initialState
:
{
selectedStage
,
selectedStageEvents
}
});
wrapper
=
createComponent
({
initialState
:
{
selectedStage
,
selectedStageEvents
,
pagination
}
});
});
afterEach
(()
=>
{
...
...
@@ -153,6 +155,10 @@ describe('Value stream analytics component', () => {
expect
(
findLoadingIcon
().
exists
()).
toBe
(
false
);
});
it
(
'
renders pagination
'
,
()
=>
{
expect
(
findPagination
().
exists
()).
toBe
(
true
);
});
describe
(
'
with `cycleAnalyticsForGroups=true` license
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
({
initialState
:
{
features
:
{
cycleAnalyticsForGroups
:
true
}
}
});
...
...
spec/frontend/cycle_analytics/mock_data.js
View file @
76e23a12
import
{
getJSONFixture
}
from
'
helpers/fixtures
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
import
{
DEFAULT_VALUE_STREAM
,
DEFAULT_DAYS_IN_PAST
}
from
'
~/cycle_analytics/constants
'
;
import
{
DEFAULT_VALUE_STREAM
,
DEFAULT_DAYS_IN_PAST
,
PAGINATION_TYPE
,
PAGINATION_SORT_DIRECTION_DESC
,
PAGINATION_SORT_FIELD_END_EVENT
,
}
from
'
~/cycle_analytics/constants
'
;
import
{
convertObjectPropsToCamelCase
}
from
'
~/lib/utils/common_utils
'
;
import
{
getDateInPast
}
from
'
~/lib/utils/datetime_utility
'
;
...
...
@@ -256,3 +262,22 @@ export const rawValueStreamStages = customizableStagesAndEvents.stages;
export
const
valueStreamStages
=
rawValueStreamStages
.
map
((
s
)
=>
convertObjectPropsToCamelCase
(
s
,
{
deep
:
true
}),
);
export
const
initialPaginationQuery
=
{
page
:
15
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
,
};
export
const
initialPaginationState
=
{
...
initialPaginationQuery
,
page
:
null
,
hasNextPage
:
false
,
};
export
const
basePaginationResult
=
{
pagination
:
PAGINATION_TYPE
,
sort
:
PAGINATION_SORT_FIELD_END_EVENT
,
direction
:
PAGINATION_SORT_DIRECTION_DESC
,
page
:
null
,
};
spec/frontend/cycle_analytics/store/actions_spec.js
View file @
76e23a12
...
...
@@ -11,6 +11,8 @@ import {
currentGroup
,
createdAfter
,
createdBefore
,
initialPaginationState
,
reviewEvents
,
}
from
'
../mock_data
'
;
const
{
id
:
groupId
,
path
:
groupPath
}
=
currentGroup
;
...
...
@@ -31,7 +33,13 @@ const mockSetDateActionCommit = {
type
:
'
SET_DATE_RANGE
'
,
};
const
defaultState
=
{
...
getters
,
selectedValueStream
,
createdAfter
,
createdBefore
};
const
defaultState
=
{
...
getters
,
selectedValueStream
,
createdAfter
,
createdBefore
,
pagination
:
initialPaginationState
,
};
describe
(
'
Project Value Stream Analytics actions
'
,
()
=>
{
let
state
;
...
...
@@ -112,6 +120,21 @@ describe('Project Value Stream Analytics actions', () => {
});
});
describe
(
'
updateStageTablePagination
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{
...
state
,
selectedStage
};
});
it
(
`will dispatch the "fetchStageData" action and commit the 'SET_PAGINATION' mutation`
,
()
=>
{
return
testAction
({
action
:
actions
.
updateStageTablePagination
,
state
,
expectedMutations
:
[{
type
:
'
SET_PAGINATION
'
}],
expectedActions
:
[{
type
:
'
fetchStageData
'
,
payload
:
selectedStage
.
id
}],
});
});
});
describe
(
'
fetchCycleAnalyticsData
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{
...
defaultState
,
endpoints
:
mockEndpoints
};
...
...
@@ -154,6 +177,10 @@ describe('Project Value Stream Analytics actions', () => {
describe
(
'
fetchStageData
'
,
()
=>
{
const
mockStagePath
=
/value_streams
\/\w
+
\/
stages
\/\w
+
\/
records/
;
const
headers
=
{
'
X-Next-Page
'
:
2
,
'
X-Page
'
:
1
,
};
beforeEach
(()
=>
{
state
=
{
...
...
@@ -162,7 +189,7 @@ describe('Project Value Stream Analytics actions', () => {
selectedStage
,
};
mock
=
new
MockAdapter
(
axios
);
mock
.
onGet
(
mockStagePath
).
reply
(
httpStatusCodes
.
OK
);
mock
.
onGet
(
mockStagePath
).
reply
(
httpStatusCodes
.
OK
,
reviewEvents
,
headers
);
});
it
(
`commits the 'RECEIVE_STAGE_DATA_SUCCESS' mutation`
,
()
=>
...
...
@@ -170,7 +197,11 @@ describe('Project Value Stream Analytics actions', () => {
action
:
actions
.
fetchStageData
,
state
,
payload
:
{},
expectedMutations
:
[{
type
:
'
REQUEST_STAGE_DATA
'
},
{
type
:
'
RECEIVE_STAGE_DATA_SUCCESS
'
}],
expectedMutations
:
[
{
type
:
'
REQUEST_STAGE_DATA
'
},
{
type
:
'
RECEIVE_STAGE_DATA_SUCCESS
'
,
payload
:
reviewEvents
},
{
type
:
'
SET_PAGINATION
'
,
payload
:
{
hasNextPage
:
true
,
page
:
1
}
},
],
expectedActions
:
[],
}));
...
...
spec/frontend/cycle_analytics/store/getters_spec.js
View file @
76e23a12
import
*
as
getters
from
'
~/cycle_analytics/store/getters
'
;
import
{
allowedStages
,
stageMedians
,
transformedProjectStagePathData
,
selectedStage
,
stageCounts
,
basePaginationResult
,
initialPaginationState
,
}
from
'
../mock_data
'
;
describe
(
'
Value stream analytics getters
'
,
()
=>
{
let
state
=
{};
describe
(
'
pathNavigationData
'
,
()
=>
{
it
(
'
returns the transformed data
'
,
()
=>
{
const
state
=
{
stages
:
allowedStages
,
medians
:
stageMedians
,
selectedStage
,
stageCounts
};
state
=
{
stages
:
allowedStages
,
medians
:
stageMedians
,
selectedStage
,
stageCounts
};
expect
(
getters
.
pathNavigationData
(
state
)).
toEqual
(
transformedProjectStagePathData
);
});
});
describe
(
'
paginationParams
'
,
()
=>
{
beforeEach
(()
=>
{
state
=
{
pagination
:
initialPaginationState
};
});
it
(
'
returns the `pagination` type
'
,
()
=>
{
expect
(
getters
.
paginationParams
(
state
)).
toEqual
(
basePaginationResult
);
});
it
(
'
returns the `sort` type
'
,
()
=>
{
expect
(
getters
.
paginationParams
(
state
)).
toEqual
(
basePaginationResult
);
});
it
(
'
with page=10, sets the `page` property
'
,
()
=>
{
const
page
=
10
;
state
=
{
pagination
:
{
...
initialPaginationState
,
page
}
};
expect
(
getters
.
paginationParams
(
state
)).
toEqual
({
...
basePaginationResult
,
page
});
});
});
});
spec/frontend/cycle_analytics/store/mutations_spec.js
View file @
76e23a12
import
{
useFakeDate
}
from
'
helpers/fake_date
'
;
import
*
as
types
from
'
~/cycle_analytics/store/mutation_types
'
;
import
mutations
from
'
~/cycle_analytics/store/mutations
'
;
import
{
PAGINATION_SORT_FIELD_END_EVENT
,
PAGINATION_SORT_DIRECTION_DESC
,
}
from
'
~/cycle_analytics/constants
'
;
import
{
selectedStage
,
rawIssueEvents
,
...
...
@@ -12,6 +16,7 @@ import {
formattedStageMedians
,
rawStageCounts
,
stageCounts
,
initialPaginationState
as
pagination
,
}
from
'
../mock_data
'
;
let
state
;
...
...
@@ -25,7 +30,7 @@ describe('Project Value Stream Analytics mutations', () => {
useFakeDate
(
2020
,
6
,
18
);
beforeEach
(()
=>
{
state
=
{};
state
=
{
pagination
};
});
afterEach
(()
=>
{
...
...
@@ -88,16 +93,18 @@ describe('Project Value Stream Analytics mutations', () => {
});
it
.
each
`
mutation | payload | stateKey | value
${
types
.
SET_DATE_RANGE
}
|
${
mockSetDatePayload
}
|
${
'
createdAfter
'
}
|
${
mockCreatedAfter
}
${
types
.
SET_DATE_RANGE
}
|
${
mockSetDatePayload
}
|
${
'
createdBefore
'
}
|
${
mockCreatedBefore
}
${
types
.
SET_LOADING
}
|
${
true
}
|
${
'
isLoading
'
}
|
${
true
}
${
types
.
SET_LOADING
}
|
${
false
}
|
${
'
isLoading
'
}
|
${
false
}
${
types
.
SET_SELECTED_VALUE_STREAM
}
|
${
selectedValueStream
}
|
${
'
selectedValueStream
'
}
|
${
selectedValueStream
}
${
types
.
RECEIVE_VALUE_STREAMS_SUCCESS
}
|
${[
selectedValueStream
]}
|
${
'
valueStreams
'
}
|
${[
selectedValueStream
]}
${
types
.
RECEIVE_VALUE_STREAM_STAGES_SUCCESS
}
|
${{
stages
:
rawValueStreamStages
}
} |
${
'
stages
'
}
|
${
valueStreamStages
}
${
types
.
RECEIVE_STAGE_MEDIANS_SUCCESS
}
|
${
rawStageMedians
}
|
${
'
medians
'
}
|
${
formattedStageMedians
}
${
types
.
RECEIVE_STAGE_COUNTS_SUCCESS
}
|
${
rawStageCounts
}
|
${
'
stageCounts
'
}
|
${
stageCounts
}
mutation | payload | stateKey | value
${
types
.
SET_DATE_RANGE
}
|
${
mockSetDatePayload
}
|
${
'
createdAfter
'
}
|
${
mockCreatedAfter
}
${
types
.
SET_DATE_RANGE
}
|
${
mockSetDatePayload
}
|
${
'
createdBefore
'
}
|
${
mockCreatedBefore
}
${
types
.
SET_LOADING
}
|
${
true
}
|
${
'
isLoading
'
}
|
${
true
}
${
types
.
SET_LOADING
}
|
${
false
}
|
${
'
isLoading
'
}
|
${
false
}
${
types
.
SET_SELECTED_VALUE_STREAM
}
|
${
selectedValueStream
}
|
${
'
selectedValueStream
'
}
|
${
selectedValueStream
}
${
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
.
RECEIVE_VALUE_STREAMS_SUCCESS
}
|
${[
selectedValueStream
]}
|
${
'
valueStreams
'
}
|
${[
selectedValueStream
]}
${
types
.
RECEIVE_VALUE_STREAM_STAGES_SUCCESS
}
|
${{
stages
:
rawValueStreamStages
}
} |
${
'
stages
'
}
|
${
valueStreamStages
}
${
types
.
RECEIVE_STAGE_MEDIANS_SUCCESS
}
|
${
rawStageMedians
}
|
${
'
medians
'
}
|
${
formattedStageMedians
}
${
types
.
RECEIVE_STAGE_COUNTS_SUCCESS
}
|
${
rawStageCounts
}
|
${
'
stageCounts
'
}
|
${
stageCounts
}
`
(
'
$mutation with $payload will set $stateKey to $value
'
,
({
mutation
,
payload
,
stateKey
,
value
})
=>
{
...
...
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