Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-07-15 21:09:37 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-07-15 21:09:37 +0300
commitd811b6d8f61b45cd12f94251abff9102b8cefc19 (patch)
tree064f5bdcf19c1480959ff02b752ab8fafa46c0fc /app/assets/javascripts/cycle_analytics
parent1581767ea15fcfa63919d39c0d3579da0a4de96e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/cycle_analytics')
-rw-r--r--app/assets/javascripts/cycle_analytics/index.js15
-rw-r--r--app/assets/javascripts/cycle_analytics/store/actions.js60
-rw-r--r--app/assets/javascripts/cycle_analytics/store/getters.js29
-rw-r--r--app/assets/javascripts/cycle_analytics/store/mutation_types.js4
-rw-r--r--app/assets/javascripts/cycle_analytics/store/mutations.js38
-rw-r--r--app/assets/javascripts/cycle_analytics/store/state.js5
-rw-r--r--app/assets/javascripts/cycle_analytics/utils.js20
7 files changed, 161 insertions, 10 deletions
diff --git a/app/assets/javascripts/cycle_analytics/index.js b/app/assets/javascripts/cycle_analytics/index.js
index 57cb220d9c9..615f96c3860 100644
--- a/app/assets/javascripts/cycle_analytics/index.js
+++ b/app/assets/javascripts/cycle_analytics/index.js
@@ -8,11 +8,24 @@ Vue.use(Translate);
export default () => {
const store = createStore();
const el = document.querySelector('#js-cycle-analytics');
- const { noAccessSvgPath, noDataSvgPath, requestPath, fullPath } = el.dataset;
+ const {
+ noAccessSvgPath,
+ noDataSvgPath,
+ requestPath,
+ fullPath,
+ projectId,
+ groupPath,
+ } = el.dataset;
store.dispatch('initializeVsa', {
+ projectId: parseInt(projectId, 10),
+ groupPath,
requestPath,
fullPath,
+ features: {
+ cycleAnalyticsForGroups:
+ (groupPath && gon?.licensed_features?.cycleAnalyticsForGroups) || false,
+ },
});
// eslint-disable-next-line no-new
diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js
index ab852cbbb2d..955f0c7271e 100644
--- a/app/assets/javascripts/cycle_analytics/store/actions.js
+++ b/app/assets/javascripts/cycle_analytics/store/actions.js
@@ -3,6 +3,7 @@ import {
getProjectValueStreams,
getProjectValueStreamStageData,
getProjectValueStreamMetrics,
+ getValueStreamStageMedian,
} from '~/api/analytics_api';
import createFlash from '~/flash';
import { __ } from '~/locale';
@@ -35,21 +36,33 @@ export const receiveValueStreamsSuccess = ({ commit, dispatch }, data = []) => {
};
export const fetchValueStreams = ({ commit, dispatch, state }) => {
- const { fullPath } = state;
+ const {
+ fullPath,
+ features: { cycleAnalyticsForGroups },
+ } = state;
commit(types.REQUEST_VALUE_STREAMS);
+ const stageRequests = ['setSelectedStage'];
+ if (cycleAnalyticsForGroups) {
+ stageRequests.push('fetchStageMedians');
+ }
+
return getProjectValueStreams(fullPath)
.then(({ data }) => dispatch('receiveValueStreamsSuccess', data))
- .then(() => dispatch('setSelectedStage'))
+ .then(() => Promise.all(stageRequests.map((r) => dispatch(r))))
.catch(({ response: { status } }) => {
commit(types.RECEIVE_VALUE_STREAMS_ERROR, status);
});
};
-export const fetchCycleAnalyticsData = ({ state: { requestPath, startDate }, commit }) => {
+export const fetchCycleAnalyticsData = ({
+ state: { requestPath },
+ getters: { legacyFilterParams },
+ commit,
+}) => {
commit(types.REQUEST_CYCLE_ANALYTICS_DATA);
- return getProjectValueStreamMetrics(requestPath, { 'cycle_analytics[start_date]': startDate })
+ return getProjectValueStreamMetrics(requestPath, legacyFilterParams)
.then(({ data }) => commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS, data))
.catch(() => {
commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR);
@@ -59,13 +72,17 @@ export const fetchCycleAnalyticsData = ({ state: { requestPath, startDate }, com
});
};
-export const fetchStageData = ({ state: { requestPath, selectedStage, startDate }, commit }) => {
+export const fetchStageData = ({
+ state: { requestPath, selectedStage },
+ getters: { legacyFilterParams },
+ commit,
+}) => {
commit(types.REQUEST_STAGE_DATA);
return getProjectValueStreamStageData({
requestPath,
stageId: selectedStage.id,
- params: { 'cycle_analytics[start_date]': startDate },
+ params: legacyFilterParams,
})
.then(({ data }) => {
// when there's a query timeout, the request succeeds but the error is encoded in the response data
@@ -78,6 +95,37 @@ export const fetchStageData = ({ state: { requestPath, selectedStage, startDate
.catch(() => commit(types.RECEIVE_STAGE_DATA_ERROR));
};
+const getStageMedians = ({ stageId, vsaParams, filterParams = {} }) => {
+ return getValueStreamStageMedian({ ...vsaParams, stageId }, filterParams).then(({ data }) => ({
+ id: stageId,
+ value: data?.value || null,
+ }));
+};
+
+export const fetchStageMedians = ({
+ state: { stages },
+ getters: { requestParams: vsaParams, filterParams },
+ commit,
+}) => {
+ commit(types.REQUEST_STAGE_MEDIANS);
+ return Promise.all(
+ stages.map(({ id: stageId }) =>
+ getStageMedians({
+ vsaParams,
+ stageId,
+ filterParams,
+ }),
+ ),
+ )
+ .then((data) => commit(types.RECEIVE_STAGE_MEDIANS_SUCCESS, data))
+ .catch((error) => {
+ commit(types.RECEIVE_STAGE_MEDIANS_ERROR, error);
+ createFlash({
+ message: __('There was an error fetching median data for stages'),
+ });
+ });
+};
+
export const setSelectedStage = ({ dispatch, commit, state: { stages } }, selectedStage = null) => {
const stage = selectedStage || stages[0];
commit(types.SET_SELECTED_STAGE, stage);
diff --git a/app/assets/javascripts/cycle_analytics/store/getters.js b/app/assets/javascripts/cycle_analytics/store/getters.js
index c60a70ef147..66971ea8a2e 100644
--- a/app/assets/javascripts/cycle_analytics/store/getters.js
+++ b/app/assets/javascripts/cycle_analytics/store/getters.js
@@ -1,3 +1,5 @@
+import dateFormat from 'dateformat';
+import { dateFormats } from '~/analytics/shared/constants';
import { transformStagesForPathNavigation, filterStagesByHiddenStatus } from '../utils';
export const pathNavigationData = ({ stages, medians, stageCounts, selectedStage }) => {
@@ -8,3 +10,30 @@ export const pathNavigationData = ({ stages, medians, stageCounts, selectedStage
selectedStage,
});
};
+
+export const requestParams = (state) => {
+ const {
+ selectedStage: { id: stageId = null },
+ groupPath: groupId,
+ selectedValueStream: { id: valueStreamId },
+ } = state;
+ return { valueStreamId, groupId, stageId };
+};
+
+const dateRangeParams = ({ createdAfter, createdBefore }) => ({
+ created_after: createdAfter ? dateFormat(createdAfter, dateFormats.isoDate) : null,
+ created_before: createdBefore ? dateFormat(createdBefore, dateFormats.isoDate) : null,
+});
+
+export const legacyFilterParams = ({ startDate }) => {
+ return {
+ 'cycle_analytics[start_date]': startDate,
+ };
+};
+
+export const filterParams = ({ id, ...rest }) => {
+ return {
+ project_ids: [id],
+ ...dateRangeParams(rest),
+ };
+};
diff --git a/app/assets/javascripts/cycle_analytics/store/mutation_types.js b/app/assets/javascripts/cycle_analytics/store/mutation_types.js
index 4f3d430ec9f..11ed62a4081 100644
--- a/app/assets/javascripts/cycle_analytics/store/mutation_types.js
+++ b/app/assets/javascripts/cycle_analytics/store/mutation_types.js
@@ -20,3 +20,7 @@ export const RECEIVE_CYCLE_ANALYTICS_DATA_ERROR = 'RECEIVE_CYCLE_ANALYTICS_DATA_
export const REQUEST_STAGE_DATA = 'REQUEST_STAGE_DATA';
export const RECEIVE_STAGE_DATA_SUCCESS = 'RECEIVE_STAGE_DATA_SUCCESS';
export const RECEIVE_STAGE_DATA_ERROR = 'RECEIVE_STAGE_DATA_ERROR';
+
+export const REQUEST_STAGE_MEDIANS = 'REQUEST_STAGE_MEDIANS';
+export const RECEIVE_STAGE_MEDIANS_SUCCESS = 'RECEIVE_STAGE_MEDIANS_SUCCESS';
+export const RECEIVE_STAGE_MEDIANS_ERROR = 'RECEIVE_STAGE_MEDIANS_ERROR';
diff --git a/app/assets/javascripts/cycle_analytics/store/mutations.js b/app/assets/javascripts/cycle_analytics/store/mutations.js
index 0ae80116cd2..a8b7a607b66 100644
--- a/app/assets/javascripts/cycle_analytics/store/mutations.js
+++ b/app/assets/javascripts/cycle_analytics/store/mutations.js
@@ -1,11 +1,23 @@
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
-import { decorateData, decorateEvents, formatMedianValues } from '../utils';
+import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
+import {
+ decorateData,
+ decorateEvents,
+ formatMedianValues,
+ calculateFormattedDayInPast,
+} from '../utils';
import * as types from './mutation_types';
export default {
- [types.INITIALIZE_VSA](state, { requestPath, fullPath }) {
+ [types.INITIALIZE_VSA](state, { requestPath, fullPath, groupPath, projectId, features }) {
state.requestPath = requestPath;
state.fullPath = fullPath;
+ state.groupPath = groupPath;
+ state.id = projectId;
+ const { now, past } = calculateFormattedDayInPast(DEFAULT_DAYS_TO_DISPLAY);
+ state.createdBefore = now;
+ state.createdAfter = past;
+ state.features = features;
},
[types.SET_LOADING](state, loadingState) {
state.isLoading = loadingState;
@@ -18,6 +30,9 @@ export default {
},
[types.SET_DATE_RANGE](state, { startDate }) {
state.startDate = startDate;
+ const { now, past } = calculateFormattedDayInPast(startDate);
+ state.createdBefore = now;
+ state.createdAfter = past;
},
[types.REQUEST_VALUE_STREAMS](state) {
state.valueStreams = [];
@@ -46,17 +61,25 @@ export default {
[types.REQUEST_CYCLE_ANALYTICS_DATA](state) {
state.isLoading = true;
state.hasError = false;
+ if (!state.features.cycleAnalyticsForGroups) {
+ state.medians = {};
+ }
},
[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, data) {
const { summary, medians } = decorateData(data);
+ if (!state.features.cycleAnalyticsForGroups) {
+ state.medians = formatMedianValues(medians);
+ }
state.permissions = data.permissions;
state.summary = summary;
- state.medians = formatMedianValues(medians);
state.hasError = false;
},
[types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR](state) {
state.isLoading = false;
state.hasError = true;
+ if (!state.features.cycleAnalyticsForGroups) {
+ state.medians = {};
+ }
},
[types.REQUEST_STAGE_DATA](state) {
state.isLoadingStage = true;
@@ -78,4 +101,13 @@ export default {
state.hasError = true;
state.selectedStageError = error;
},
+ [types.REQUEST_STAGE_MEDIANS](state) {
+ state.medians = {};
+ },
+ [types.RECEIVE_STAGE_MEDIANS_SUCCESS](state, medians) {
+ state.medians = formatMedianValues(medians);
+ },
+ [types.RECEIVE_STAGE_MEDIANS_ERROR](state) {
+ state.medians = {};
+ },
};
diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js
index 02f953d9517..4d61077fb99 100644
--- a/app/assets/javascripts/cycle_analytics/store/state.js
+++ b/app/assets/javascripts/cycle_analytics/store/state.js
@@ -1,9 +1,13 @@
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
export default () => ({
+ features: {},
+ id: null,
requestPath: '',
fullPath: '',
startDate: DEFAULT_DAYS_TO_DISPLAY,
+ createdAfter: null,
+ createdBefore: null,
stages: [],
summary: [],
analytics: [],
@@ -19,4 +23,5 @@ export default () => ({
isLoadingStage: false,
isEmptyStage: false,
permissions: {},
+ parentPath: null,
});
diff --git a/app/assets/javascripts/cycle_analytics/utils.js b/app/assets/javascripts/cycle_analytics/utils.js
index 40ad7d8b2fc..a1690dd1513 100644
--- a/app/assets/javascripts/cycle_analytics/utils.js
+++ b/app/assets/javascripts/cycle_analytics/utils.js
@@ -1,6 +1,9 @@
+import dateFormat from 'dateformat';
import { unescape } from 'lodash';
+import { dateFormats } from '~/analytics/shared/constants';
import { sanitize } from '~/lib/dompurify';
import { roundToNearestHalf, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+import { getDateInPast } from '~/lib/utils/datetime/date_calculation_utility';
import { parseSeconds } from '~/lib/utils/datetime_utility';
import { s__, sprintf } from '../locale';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
@@ -115,3 +118,20 @@ export const formatMedianValues = (medians = []) =>
export const filterStagesByHiddenStatus = (stages = [], isHidden = true) =>
stages.filter(({ hidden = false }) => hidden === isHidden);
+
+const toIsoFormat = (d) => dateFormat(d, dateFormats.isoDate);
+
+/**
+ * Takes an integer specifying the number of days to subtract
+ * from the date specified will return the 2 dates, formatted as ISO dates
+ *
+ * @param {Number} daysInPast - Number of days in the past to subtract
+ * @param {Date} [today=new Date] - Date to subtract days from, defaults to today
+ * @returns {Object} Returns 'now' and the 'past' date formatted as ISO dates
+ */
+export const calculateFormattedDayInPast = (daysInPast, today = new Date()) => {
+ return {
+ now: toIsoFormat(today),
+ past: toIsoFormat(getDateInPast(today, daysInPast)),
+ };
+};