summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/cycle_analytics
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 08:43:02 +0000
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/cycle_analytics
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
downloadgitlab-ce-d9ab72d6080f594d0b3cae15f14b3ef2c6c638cb.tar.gz
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/cycle_analytics')
-rw-r--r--app/assets/javascripts/cycle_analytics/components/base.vue16
-rw-r--r--app/assets/javascripts/cycle_analytics/components/filter_bar.vue1
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_table.vue3
-rw-r--r--app/assets/javascripts/cycle_analytics/constants.js2
-rw-r--r--app/assets/javascripts/cycle_analytics/index.js1
-rw-r--r--app/assets/javascripts/cycle_analytics/store/actions.js20
-rw-r--r--app/assets/javascripts/cycle_analytics/store/getters.js8
-rw-r--r--app/assets/javascripts/cycle_analytics/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/cycle_analytics/store/mutations.js21
-rw-r--r--app/assets/javascripts/cycle_analytics/store/state.js11
-rw-r--r--app/assets/javascripts/cycle_analytics/utils.js30
11 files changed, 77 insertions, 37 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue
index ae78ce33263..1d98a42ce58 100644
--- a/app/assets/javascripts/cycle_analytics/components/base.vue
+++ b/app/assets/javascripts/cycle_analytics/components/base.vue
@@ -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>
diff --git a/app/assets/javascripts/cycle_analytics/components/filter_bar.vue b/app/assets/javascripts/cycle_analytics/components/filter_bar.vue
index 5140b05e189..016fea354fe 100644
--- a/app/assets/javascripts/cycle_analytics/components/filter_bar.vue
+++ b/app/assets/javascripts/cycle_analytics/components/filter_bar.vue
@@ -79,7 +79,6 @@ export default {
title: __('Assignees'),
type: 'assignees',
token: AuthorToken,
- defaultAuthors: [],
initialAuthors: this.assigneesData,
unique: false,
operators: OPERATOR_IS_ONLY,
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
index 8a2667a4ab1..fc4dfafb809 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
@@ -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">
diff --git a/app/assets/javascripts/cycle_analytics/constants.js b/app/assets/javascripts/cycle_analytics/constants.js
index c1be2ce7096..c205aa1e831 100644
--- a/app/assets/javascripts/cycle_analytics/constants.js
+++ b/app/assets/javascripts/cycle_analytics/constants.js
@@ -44,7 +44,7 @@ export const METRICS_POPOVER_CONTENT = {
},
'cycle-time': {
description: s__(
- 'ValueStreamAnalytics|Median time from issue first merge request created to issue closed.',
+ "ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
),
},
'new-issue': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
diff --git a/app/assets/javascripts/cycle_analytics/index.js b/app/assets/javascripts/cycle_analytics/index.js
index 620da0104e0..34ef03409b8 100644
--- a/app/assets/javascripts/cycle_analytics/index.js
+++ b/app/assets/javascripts/cycle_analytics/index.js
@@ -45,6 +45,7 @@ export default () => {
new Vue({
el,
name: 'CycleAnalytics',
+ apolloProvider: {},
store,
render: (createElement) =>
createElement(CycleAnalytics, {
diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js
index e39cd224199..24b62849db7 100644
--- a/app/assets/javascripts/cycle_analytics/store/actions.js
+++ b/app/assets/javascripts/cycle_analytics/store/actions.js
@@ -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);
diff --git a/app/assets/javascripts/cycle_analytics/store/getters.js b/app/assets/javascripts/cycle_analytics/store/getters.js
index 77c285f5ce0..962e1d50d12 100644
--- a/app/assets/javascripts/cycle_analytics/store/getters.js
+++ b/app/assets/javascripts/cycle_analytics/store/getters.js
@@ -1,6 +1,7 @@
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 },
diff --git a/app/assets/javascripts/cycle_analytics/store/mutation_types.js b/app/assets/javascripts/cycle_analytics/store/mutation_types.js
index 0d94aad2ca5..0ad67d4e6bd 100644
--- a/app/assets/javascripts/cycle_analytics/store/mutation_types.js
+++ b/app/assets/javascripts/cycle_analytics/store/mutation_types.js
@@ -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';
diff --git a/app/assets/javascripts/cycle_analytics/store/mutations.js b/app/assets/javascripts/cycle_analytics/store/mutations.js
index 301e7d95f8c..64930a5b51f 100644
--- a/app/assets/javascripts/cycle_analytics/store/mutations.js
+++ b/app/assets/javascripts/cycle_analytics/store/mutations.js
@@ -1,13 +1,24 @@
+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 = [];
},
diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js
index 0882db51218..52bc01cafa4 100644
--- a/app/assets/javascripts/cycle_analytics/store/state.js
+++ b/app/assets/javascripts/cycle_analytics/store/state.js
@@ -1,3 +1,8 @@
+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,
+ },
});
diff --git a/app/assets/javascripts/cycle_analytics/utils.js b/app/assets/javascripts/cycle_analytics/utils.js
index fa02fdf914a..3c6267bac06 100644
--- a/app/assets/javascripts/cycle_analytics/utils.js
+++ b/app/assets/javascripts/cycle_analytics/utils.js
@@ -1,13 +1,10 @@
import dateFormat from 'dateformat';
-import { unescape } from 'lodash';
import { dateFormats } from '~/analytics/shared/constants';
import { hideFlash } from '~/flash';
-import { sanitize } from '~/lib/dompurify';
-import { roundToNearestHalf } from '~/lib/utils/common_utils';
import { getDateInPast } from '~/lib/utils/datetime/date_calculation_utility';
import { parseSeconds } from '~/lib/utils/datetime_utility';
+import { formatTimeAsSummary } from '~/lib/utils/datetime/date_format_utility';
import { slugify } from '~/lib/utils/text_utility';
-import { s__, sprintf } from '../locale';
export const removeFlash = (type = 'alert') => {
const flashEl = document.querySelector(`.flash-${type}`);
@@ -45,29 +42,6 @@ export const transformStagesForPathNavigation = ({
return formattedStages;
};
-export const timeSummaryForPathNavigation = ({ seconds, hours, days, minutes, weeks, months }) => {
- if (months) {
- return sprintf(s__('ValueStreamAnalytics|%{value}M'), {
- value: roundToNearestHalf(months),
- });
- } else if (weeks) {
- return sprintf(s__('ValueStreamAnalytics|%{value}w'), {
- value: roundToNearestHalf(weeks),
- });
- } else if (days) {
- return sprintf(s__('ValueStreamAnalytics|%{value}d'), {
- value: roundToNearestHalf(days),
- });
- } else if (hours) {
- return sprintf(s__('ValueStreamAnalytics|%{value}h'), { value: hours });
- } else if (minutes) {
- return sprintf(s__('ValueStreamAnalytics|%{value}m'), { value: minutes });
- } else if (seconds) {
- return unescape(sanitize(s__('ValueStreamAnalytics|&lt;1m'), { ALLOWED_TAGS: [] }));
- }
- return '-';
-};
-
/**
* Takes a raw median value in seconds and converts it to a string representation
* ie. converts 172800 => 2d (2 days)
@@ -76,7 +50,7 @@ export const timeSummaryForPathNavigation = ({ seconds, hours, days, minutes, we
* @returns {String} String representation ie 2w
*/
export const medianTimeToParsedSeconds = (value) =>
- timeSummaryForPathNavigation({
+ formatTimeAsSummary({
...parseSeconds(value, { daysPerWeek: 7, hoursPerDay: 24 }),
seconds: value,
});