diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-11 18:09:58 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-11 18:09:58 +0300 |
commit | 62cd7010ef91dcaa5a5a36790985053db0b38671 (patch) | |
tree | 935b69e9d6ad2fe13c5ea0cb0d1bbd0b3413b3ab /app/assets/javascripts/cycle_analytics | |
parent | dcf94a76413ddb50148bdac7b189afb7bffa7580 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/cycle_analytics')
8 files changed, 133 insertions, 88 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue index 8c1fecac3fc..8492f0b73e1 100644 --- a/app/assets/javascripts/cycle_analytics/components/base.vue +++ b/app/assets/javascripts/cycle_analytics/components/base.vue @@ -58,6 +58,7 @@ export default { 'stages', 'summary', 'startDate', + 'permissions', ]), ...mapGetters(['pathNavigationData']), displayStageEvents() { @@ -68,7 +69,7 @@ export default { return this.selectedStageReady && this.isEmptyStage; }, displayNoAccess() { - return this.selectedStageReady && !this.selectedStage.isUserAllowed; + return this.selectedStageReady && !this.isUserAllowed(this.selectedStage.id); }, selectedStageReady() { return !this.isLoadingStage && this.selectedStage; @@ -91,25 +92,18 @@ export default { ]), handleDateSelect(startDate) { this.setDateRange({ startDate }); - this.fetchCycleAnalyticsData(); - }, - isActiveStage(stage) { - return stage.slug === this.selectedStage.slug; }, onSelectStage(stage) { - if (this.isLoadingStage || this.selectedStage?.slug === stage?.slug) return; - this.setSelectedStage(stage); - if (!stage.isUserAllowed) { - return; - } - - this.fetchStageData(); }, dismissOverviewDialog() { this.isOverviewDialogDismissed = true; Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 }); }, + isUserAllowed(id) { + const { permissions } = this; + return Boolean(permissions?.[id]); + }, }, dayRangeOptions: [7, 30, 90], i18n: { diff --git a/app/assets/javascripts/cycle_analytics/constants.js b/app/assets/javascripts/cycle_analytics/constants.js index 50b5ebba583..96c89049e90 100644 --- a/app/assets/javascripts/cycle_analytics/constants.js +++ b/app/assets/javascripts/cycle_analytics/constants.js @@ -1,2 +1,8 @@ export const DEFAULT_DAYS_TO_DISPLAY = 30; export const OVERVIEW_STAGE_ID = 'overview'; + +export const DEFAULT_VALUE_STREAM = { + id: 'default', + slug: 'default', + name: 'default', +}; diff --git a/app/assets/javascripts/cycle_analytics/index.js b/app/assets/javascripts/cycle_analytics/index.js index 00192cc61f8..57cb220d9c9 100644 --- a/app/assets/javascripts/cycle_analytics/index.js +++ b/app/assets/javascripts/cycle_analytics/index.js @@ -8,10 +8,11 @@ Vue.use(Translate); export default () => { const store = createStore(); const el = document.querySelector('#js-cycle-analytics'); - const { noAccessSvgPath, noDataSvgPath, requestPath } = el.dataset; + const { noAccessSvgPath, noDataSvgPath, requestPath, fullPath } = el.dataset; store.dispatch('initializeVsa', { requestPath, + fullPath, }); // eslint-disable-next-line no-new @@ -24,6 +25,7 @@ export default () => { props: { noDataSvgPath, noAccessSvgPath, + fullPath, }, }), }); diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js index 40e1c01f78b..faf1c37d86a 100644 --- a/app/assets/javascripts/cycle_analytics/store/actions.js +++ b/app/assets/javascripts/cycle_analytics/store/actions.js @@ -1,27 +1,60 @@ +import { + getProjectValueStreamStages, + getProjectValueStreams, + getProjectValueStreamStageData, + getProjectValueStreamMetrics, +} from '~/api/analytics_api'; import createFlash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; -import { DEFAULT_DAYS_TO_DISPLAY } from '../constants'; +import { DEFAULT_DAYS_TO_DISPLAY, DEFAULT_VALUE_STREAM } from '../constants'; import * as types from './mutation_types'; -export const fetchCycleAnalyticsData = ({ - state: { requestPath, startDate }, - dispatch, - commit, -}) => { +export const setSelectedValueStream = ({ commit, dispatch }, valueStream) => { + commit(types.SET_SELECTED_VALUE_STREAM, valueStream); + return dispatch('fetchValueStreamStages'); +}; + +export const fetchValueStreamStages = ({ commit, state }) => { + const { fullPath, selectedValueStream } = state; + commit(types.REQUEST_VALUE_STREAM_STAGES); + + return getProjectValueStreamStages(fullPath, selectedValueStream.id) + .then(({ data }) => commit(types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS, data)) + .catch(({ response: { status } }) => { + commit(types.RECEIVE_VALUE_STREAM_STAGES_ERROR, status); + }); +}; + +export const receiveValueStreamsSuccess = ({ commit, dispatch }, data = []) => { + commit(types.RECEIVE_VALUE_STREAMS_SUCCESS, data); + if (data.length) { + const [firstStream] = data; + return dispatch('setSelectedValueStream', firstStream); + } + return dispatch('setSelectedValueStream', DEFAULT_VALUE_STREAM); +}; + +export const fetchValueStreams = ({ commit, dispatch, state }) => { + const { fullPath } = state; + commit(types.REQUEST_VALUE_STREAMS); + + return getProjectValueStreams(fullPath) + .then(({ data }) => dispatch('receiveValueStreamsSuccess', data)) + .then(() => dispatch('setSelectedStage')) + .catch(({ response: { status } }) => { + commit(types.RECEIVE_VALUE_STREAMS_ERROR, status); + }); +}; + +export const fetchCycleAnalyticsData = ({ state: { requestPath, startDate }, commit }) => { commit(types.REQUEST_CYCLE_ANALYTICS_DATA); - return axios - .get(requestPath, { - params: { 'cycle_analytics[start_date]': startDate }, - }) + return getProjectValueStreamMetrics(requestPath, { 'cycle_analytics[start_date]': startDate }) .then(({ data }) => commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS, data)) - .then(() => dispatch('setSelectedStage')) - .then(() => dispatch('fetchStageData')) .catch(() => { commit(types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR); createFlash({ - message: __('There was an error while fetching value stream analytics data.'), + message: __('There was an error while fetching value stream summary data.'), }); }); }; @@ -29,10 +62,11 @@ export const fetchCycleAnalyticsData = ({ export const fetchStageData = ({ state: { requestPath, selectedStage, startDate }, commit }) => { commit(types.REQUEST_STAGE_DATA); - return axios - .get(`${requestPath}/events/${selectedStage.name}.json`, { - params: { 'cycle_analytics[start_date]': startDate }, - }) + return getProjectValueStreamStageData({ + requestPath, + stageId: selectedStage.id, + params: { 'cycle_analytics[start_date]': startDate }, + }) .then(({ data }) => { // when there's a query timeout, the request succeeds but the error is encoded in the response data if (data?.error) { @@ -44,15 +78,26 @@ export const fetchStageData = ({ state: { requestPath, selectedStage, startDate .catch(() => commit(types.RECEIVE_STAGE_DATA_ERROR)); }; -export const setSelectedStage = ({ commit, state: { stages } }, selectedStage = null) => { +export const setSelectedStage = ({ dispatch, commit, state: { stages } }, selectedStage = null) => { const stage = selectedStage || stages[0]; commit(types.SET_SELECTED_STAGE, stage); + return dispatch('fetchStageData'); +}; + +const refetchData = (dispatch, commit) => { + commit(types.SET_LOADING, true); + return Promise.resolve() + .then(() => dispatch('fetchValueStreams')) + .then(() => dispatch('fetchCycleAnalyticsData')) + .finally(() => commit(types.SET_LOADING, false)); }; -export const setDateRange = ({ commit }, { startDate = DEFAULT_DAYS_TO_DISPLAY }) => +export const setDateRange = ({ dispatch, commit }, { startDate = DEFAULT_DAYS_TO_DISPLAY }) => { commit(types.SET_DATE_RANGE, { startDate }); + return refetchData(dispatch, commit); +}; export const initializeVsa = ({ commit, dispatch }, initialData = {}) => { commit(types.INITIALIZE_VSA, initialData); - return dispatch('fetchCycleAnalyticsData'); + return refetchData(dispatch, commit); }; diff --git a/app/assets/javascripts/cycle_analytics/store/mutation_types.js b/app/assets/javascripts/cycle_analytics/store/mutation_types.js index 00aae49ae9f..4f3d430ec9f 100644 --- a/app/assets/javascripts/cycle_analytics/store/mutation_types.js +++ b/app/assets/javascripts/cycle_analytics/store/mutation_types.js @@ -1,8 +1,18 @@ export const INITIALIZE_VSA = 'INITIALIZE_VSA'; +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 REQUEST_VALUE_STREAMS = 'REQUEST_VALUE_STREAMS'; +export const RECEIVE_VALUE_STREAMS_SUCCESS = 'RECEIVE_VALUE_STREAMS_SUCCESS'; +export const RECEIVE_VALUE_STREAMS_ERROR = 'RECEIVE_VALUE_STREAMS_ERROR'; + +export const REQUEST_VALUE_STREAM_STAGES = 'REQUEST_VALUE_STREAM_STAGES'; +export const RECEIVE_VALUE_STREAM_STAGES_SUCCESS = 'RECEIVE_VALUE_STREAM_STAGES_SUCCESS'; +export const RECEIVE_VALUE_STREAM_STAGES_ERROR = 'RECEIVE_VALUE_STREAM_STAGES_ERROR'; + export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA'; export const RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS = 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS'; export const RECEIVE_CYCLE_ANALYTICS_DATA_ERROR = 'RECEIVE_CYCLE_ANALYTICS_DATA_ERROR'; diff --git a/app/assets/javascripts/cycle_analytics/store/mutations.js b/app/assets/javascripts/cycle_analytics/store/mutations.js index 4d999b056b7..0ae80116cd2 100644 --- a/app/assets/javascripts/cycle_analytics/store/mutations.js +++ b/app/assets/javascripts/cycle_analytics/store/mutations.js @@ -1,34 +1,61 @@ +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { decorateData, decorateEvents, formatMedianValues } from '../utils'; import * as types from './mutation_types'; export default { - [types.INITIALIZE_VSA](state, { requestPath }) { + [types.INITIALIZE_VSA](state, { requestPath, fullPath }) { state.requestPath = requestPath; + state.fullPath = fullPath; + }, + [types.SET_LOADING](state, loadingState) { + state.isLoading = loadingState; + }, + [types.SET_SELECTED_VALUE_STREAM](state, selectedValueStream = {}) { + state.selectedValueStream = convertObjectPropsToCamelCase(selectedValueStream, { deep: true }); }, [types.SET_SELECTED_STAGE](state, stage) { - state.isLoadingStage = true; state.selectedStage = stage; - state.isLoadingStage = false; }, [types.SET_DATE_RANGE](state, { startDate }) { state.startDate = startDate; }, + [types.REQUEST_VALUE_STREAMS](state) { + state.valueStreams = []; + }, + [types.RECEIVE_VALUE_STREAMS_SUCCESS](state, valueStreams = []) { + state.valueStreams = valueStreams; + }, + [types.RECEIVE_VALUE_STREAMS_ERROR](state) { + state.valueStreams = []; + }, + [types.REQUEST_VALUE_STREAM_STAGES](state) { + state.stages = []; + }, + [types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS](state, { stages = [] }) { + state.stages = stages.map((s) => ({ + ...convertObjectPropsToCamelCase(s, { deep: true }), + // NOTE: we set the component type here to match the current behaviour + // this can be removed when we migrate to the update stage table + // https://gitlab.com/gitlab-org/gitlab/-/issues/326704 + component: `stage-${s.id}-component`, + })); + }, + [types.RECEIVE_VALUE_STREAM_STAGES_ERROR](state) { + state.stages = []; + }, [types.REQUEST_CYCLE_ANALYTICS_DATA](state) { state.isLoading = true; - state.stages = []; state.hasError = false; }, [types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, data) { - state.isLoading = false; - const { stages, summary, medians } = decorateData(data); - state.stages = stages; + const { summary, medians } = decorateData(data); + 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.stages = []; state.hasError = true; }, [types.REQUEST_STAGE_DATA](state) { diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js index b488340943a..02f953d9517 100644 --- a/app/assets/javascripts/cycle_analytics/store/state.js +++ b/app/assets/javascripts/cycle_analytics/store/state.js @@ -2,11 +2,14 @@ import { DEFAULT_DAYS_TO_DISPLAY } from '../constants'; export default () => ({ requestPath: '', + fullPath: '', startDate: DEFAULT_DAYS_TO_DISPLAY, stages: [], summary: [], analytics: [], stats: [], + valueStreams: [], + selectedValueStream: {}, selectedStage: {}, selectedStageEvents: [], selectedStageError: '', @@ -15,4 +18,5 @@ export default () => ({ isLoading: false, isLoadingStage: false, isEmptyStage: false, + permissions: {}, }); diff --git a/app/assets/javascripts/cycle_analytics/utils.js b/app/assets/javascripts/cycle_analytics/utils.js index 8aa4b88a374..40ad7d8b2fc 100644 --- a/app/assets/javascripts/cycle_analytics/utils.js +++ b/app/assets/javascripts/cycle_analytics/utils.js @@ -2,31 +2,9 @@ import { unescape } from 'lodash'; import { sanitize } from '~/lib/dompurify'; import { roundToNearestHalf, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { parseSeconds } from '~/lib/utils/datetime_utility'; -import { dasherize } from '~/lib/utils/text_utility'; -import { __, s__, sprintf } from '../locale'; +import { s__, sprintf } from '../locale'; import DEFAULT_EVENT_OBJECTS from './default_event_objects'; -const EMPTY_STAGE_TEXTS = { - issue: __( - 'The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage.', - ), - plan: __( - 'The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit.', - ), - code: __( - 'The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request.', - ), - test: __( - 'The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running.', - ), - review: __( - 'The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request.', - ), - staging: __( - 'The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time.', - ), -}; - /** * These `decorate` methods will be removed when me migrate to the * new table layout https://gitlab.com/gitlab-org/gitlab/-/issues/326704 @@ -43,33 +21,12 @@ const mapToEvent = (event, stage) => { export const decorateEvents = (events, stage) => events.map((event) => mapToEvent(event, stage)); -/* - * NOTE: We currently use the `name` field since the project level stages are in memory - * once we migrate to a default value stream https://gitlab.com/gitlab-org/gitlab/-/issues/326705 - * we can use the `id` to identify which median we are using - */ -const mapToStage = (permissions, { name, ...rest }) => { - const slug = dasherize(name.toLowerCase()); - return { - ...rest, - name, - id: name, - slug, - active: false, - isUserAllowed: permissions[slug], - emptyStageText: EMPTY_STAGE_TEXTS[slug], - component: `stage-${slug}-component`, - }; -}; - const mapToSummary = ({ value, ...rest }) => ({ ...rest, value: value || '-' }); -const mapToMedians = ({ id, value }) => ({ id, value }); +const mapToMedians = ({ name: id, value }) => ({ id, value }); export const decorateData = (data = {}) => { - const { permissions, stats, summary } = data; - const stages = stats?.map((item) => mapToStage(permissions, item)) || []; + const { stats: stages, summary } = data; return { - stages, summary: summary?.map((item) => mapToSummary(item)) || [], medians: stages?.map((item) => mapToMedians(item)) || [], }; |