diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-15 21:09:37 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-07-15 21:09:37 +0300 |
commit | d811b6d8f61b45cd12f94251abff9102b8cefc19 (patch) | |
tree | 064f5bdcf19c1480959ff02b752ab8fafa46c0fc /app/assets/javascripts/cycle_analytics | |
parent | 1581767ea15fcfa63919d39c0d3579da0a4de96e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/cycle_analytics')
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)), + }; +}; |