diff options
Diffstat (limited to 'app/assets/javascripts/cycle_analytics/store')
6 files changed, 139 insertions, 31 deletions
diff --git a/app/assets/javascripts/cycle_analytics/store/actions.js b/app/assets/javascripts/cycle_analytics/store/actions.js index fe3c6d6b3ba..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,23 +62,42 @@ 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) { + commit(types.RECEIVE_STAGE_DATA_ERROR, data.error); + } else { + commit(types.RECEIVE_STAGE_DATA_SUCCESS, data); + } }) - .then(({ data }) => commit(types.RECEIVE_STAGE_DATA_SUCCESS, data)) .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/getters.js b/app/assets/javascripts/cycle_analytics/store/getters.js new file mode 100644 index 00000000000..c60a70ef147 --- /dev/null +++ b/app/assets/javascripts/cycle_analytics/store/getters.js @@ -0,0 +1,10 @@ +import { transformStagesForPathNavigation, filterStagesByHiddenStatus } from '../utils'; + +export const pathNavigationData = ({ stages, medians, stageCounts, selectedStage }) => { + return transformStagesForPathNavigation({ + stages: filterStagesByHiddenStatus(stages, false), + medians, + stageCounts, + selectedStage, + }); +}; diff --git a/app/assets/javascripts/cycle_analytics/store/index.js b/app/assets/javascripts/cycle_analytics/store/index.js index ab47538dcf5..c6ca88ea492 100644 --- a/app/assets/javascripts/cycle_analytics/store/index.js +++ b/app/assets/javascripts/cycle_analytics/store/index.js @@ -8,6 +8,7 @@ import Vue from 'vue'; import Vuex from 'vuex'; import * as actions from './actions'; +import * as getters from './getters'; import mutations from './mutations'; import state from './state'; @@ -16,6 +17,7 @@ Vue.use(Vuex); export default () => new Vuex.Store({ actions, + getters, mutations, state, }); 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 8fd5c78339a..0ae80116cd2 100644 --- a/app/assets/javascripts/cycle_analytics/store/mutations.js +++ b/app/assets/javascripts/cycle_analytics/store/mutations.js @@ -1,33 +1,61 @@ -import { decorateData, decorateEvents } from '../utils'; +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 } = 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) { @@ -43,10 +71,11 @@ export default { state.selectedStageEvents = decorateEvents(events, selectedStage); state.hasError = false; }, - [types.RECEIVE_STAGE_DATA_ERROR](state) { + [types.RECEIVE_STAGE_DATA_ERROR](state, error) { state.isLoadingStage = false; state.isEmptyStage = true; state.selectedStageEvents = []; state.hasError = true; + state.selectedStageError = error; }, }; diff --git a/app/assets/javascripts/cycle_analytics/store/state.js b/app/assets/javascripts/cycle_analytics/store/state.js index 5db4e1878a9..02f953d9517 100644 --- a/app/assets/javascripts/cycle_analytics/store/state.js +++ b/app/assets/javascripts/cycle_analytics/store/state.js @@ -2,16 +2,21 @@ 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: '', medians: {}, hasError: false, isLoading: false, isLoadingStage: false, isEmptyStage: false, + permissions: {}, }); |