Commit 625d2a1d authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Fetch value streams and display dropdown

Adds a dropdown to switch the
selected value stream

Fetch stages from value stream
parent 1683ea48
<script> <script>
import { GlButton, GlForm, GlFormInput, GlFormGroup, GlModal, GlModalDirective } from '@gitlab/ui'; import {
GlButton,
GlNewDropdown as GlDropdown,
GlNewDropdownItem as GlDropdownItem,
GlNewDropdownDivider as GlDropdownDivider,
GlForm,
GlFormInput,
GlFormGroup,
GlModal,
GlModalDirective,
} from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
...@@ -23,6 +33,9 @@ const validate = ({ name }) => { ...@@ -23,6 +33,9 @@ const validate = ({ name }) => {
export default { export default {
components: { components: {
GlButton, GlButton,
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
GlForm, GlForm,
GlFormInput, GlFormInput,
GlFormGroup, GlFormGroup,
...@@ -41,6 +54,8 @@ export default { ...@@ -41,6 +54,8 @@ export default {
...mapState({ ...mapState({
isLoading: 'isCreatingValueStream', isLoading: 'isCreatingValueStream',
initialFormErrors: 'createValueStreamErrors', initialFormErrors: 'createValueStreamErrors',
data: 'valueStreams',
selectedValueStream: 'selectedValueStream',
}), }),
isValid() { isValid() {
return !this.errors?.name.length; return !this.errors?.name.length;
...@@ -48,6 +63,15 @@ export default { ...@@ -48,6 +63,15 @@ export default {
invalidFeedback() { invalidFeedback() {
return this.errors?.name.join('\n'); return this.errors?.name.join('\n');
}, },
hasValueStreams() {
return Boolean(this.data.length);
},
selectedValueStreamName() {
return this?.selectedValueStream?.name || '';
},
selectedValueStreamId() {
return this?.selectedValueStream?.id || null;
},
}, },
mounted() { mounted() {
const { initialFormErrors } = this; const { initialFormErrors } = this;
...@@ -58,7 +82,7 @@ export default { ...@@ -58,7 +82,7 @@ export default {
} }
}, },
methods: { methods: {
...mapActions(['createValueStream']), ...mapActions(['createValueStream', 'setSelectedValueStream']),
onSubmit() { onSubmit() {
const { name } = this; const { name } = this;
return this.createValueStream({ name }).then(() => { return this.createValueStream({ name }).then(() => {
...@@ -73,12 +97,32 @@ export default { ...@@ -73,12 +97,32 @@ export default {
const { name } = this; const { name } = this;
this.errors = validate({ name }); this.errors = validate({ name });
}, 250), }, 250),
isSelected(id) {
return this.selectedValueStreamId && this.selectedValueStreamId === id;
},
onSelect(id) {
this.setSelectedValueStream(id);
},
}, },
}; };
</script> </script>
<template> <template>
<gl-form> <gl-form>
<gl-button v-gl-modal-directive="'create-value-stream-modal'" @click="onHandleInput">{{ <gl-dropdown v-if="hasValueStreams" :text="selectedValueStreamName" right>
<gl-dropdown-item
v-for="{ id, name: streamName } in data"
:key="id"
:is-check-item="true"
:is-checked="isSelected(id)"
@click="onSelect(id)"
>{{ streamName }}</gl-dropdown-item
>
<gl-dropdown-divider />
<gl-dropdown-item v-gl-modal-directive="'create-value-stream-modal'" @click="onHandleInput">{{
__('Create new value stream')
}}</gl-dropdown-item>
</gl-dropdown>
<gl-button v-else v-gl-modal-directive="'create-value-stream-modal'" @click="onHandleInput">{{
__('Create new value stream') __('Create new value stream')
}}</gl-button> }}</gl-button>
<gl-modal <gl-modal
......
...@@ -109,7 +109,13 @@ export const fetchCycleAnalyticsData = ({ dispatch }) => { ...@@ -109,7 +109,13 @@ export const fetchCycleAnalyticsData = ({ dispatch }) => {
removeFlash(); removeFlash();
dispatch('requestCycleAnalyticsData'); dispatch('requestCycleAnalyticsData');
// TODO: we will need to do some extra refactoring here
// We need the selected value stream to be selected first
// once we have that, we can then fetch the groups stages and events,
// this is because they will now live under the /value_streams entity
return Promise.resolve() return Promise.resolve()
.then(() => dispatch('fetchValueStreams'))
.then(() => dispatch('fetchGroupStagesAndEvents')) .then(() => dispatch('fetchGroupStagesAndEvents'))
.then(() => dispatch('fetchStageMedianValues')) .then(() => dispatch('fetchStageMedianValues'))
.then(() => dispatch('receiveCycleAnalyticsDataSuccess')) .then(() => dispatch('receiveCycleAnalyticsDataSuccess'))
...@@ -313,3 +319,34 @@ export const createValueStream = ({ commit, rootState }, data) => { ...@@ -313,3 +319,34 @@ export const createValueStream = ({ commit, rootState }, data) => {
commit(types.RECEIVE_CREATE_VALUE_STREAM_ERROR, { data, message, errors }); commit(types.RECEIVE_CREATE_VALUE_STREAM_ERROR, { data, message, errors });
}); });
}; };
export const setSelectedValueStream = ({ commit }, streamId) =>
commit(types.SET_SELECTED_VALUE_STREAM, streamId);
export const receiveValueStreams = ({ commit, dispatch }, { data }) => {
commit(types.RECEIVE_VALUE_STREAMS_SUCCESS, data);
const [firstStream] = data;
return dispatch('setSelectedValueStream', firstStream.id);
};
export const fetchValueStreams = ({ commit, dispatch, getters, state }, data) => {
const {
featureFlags: { hasCreateMultipleValueStreams = false },
} = state;
const { currentGroupPath } = getters;
if (hasCreateMultipleValueStreams) {
commit(types.REQUEST_VALUE_STREAMS);
return Api.cycleAnalyticsValueStreams(currentGroupPath)
.then(response => {
const { status, data: responseData } = response;
return dispatch('receiveValueStreams', { status, data: responseData });
})
.catch(({ response } = {}) => {
const { data: { message, errors } = null } = response;
commit(types.RECEIVE_VALUE_STREAMS_ERROR, { data, message, errors });
});
}
return Promise.resolve();
};
...@@ -6,6 +6,8 @@ import { transformStagesForPathNavigation } from '../utils'; ...@@ -6,6 +6,8 @@ import { transformStagesForPathNavigation } from '../utils';
export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDEN; export const hasNoAccessError = state => state.errorCode === httpStatus.FORBIDDEN;
export const currentValueStreamId = ({ selectedValueStream }) => selectedValueStream?.id || null;
export const currentGroupPath = ({ selectedGroup }) => export const currentGroupPath = ({ selectedGroup }) =>
selectedGroup && selectedGroup.fullPath ? selectedGroup.fullPath : null; selectedGroup && selectedGroup.fullPath ? selectedGroup.fullPath : null;
......
...@@ -5,6 +5,7 @@ export const SET_SELECTED_PROJECTS = 'SET_SELECTED_PROJECTS'; ...@@ -5,6 +5,7 @@ export const SET_SELECTED_PROJECTS = 'SET_SELECTED_PROJECTS';
export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE'; export const SET_SELECTED_STAGE = 'SET_SELECTED_STAGE';
export const SET_DATE_RANGE = 'SET_DATE_RANGE'; export const SET_DATE_RANGE = 'SET_DATE_RANGE';
export const SET_SELECTED_FILTERS = 'SET_SELECTED_FILTERS'; export const SET_SELECTED_FILTERS = 'SET_SELECTED_FILTERS';
export const SET_SELECTED_VALUE_STREAM = 'SET_SELECTED_VALUE_STREAM';
export const REQUEST_CYCLE_ANALYTICS_DATA = 'REQUEST_CYCLE_ANALYTICS_DATA'; 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_SUCCESS = 'RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS';
...@@ -39,3 +40,7 @@ export const RECEIVE_REORDER_STAGE_ERROR = 'RECEIVE_REORDER_STAGE_ERROR'; ...@@ -39,3 +40,7 @@ export const RECEIVE_REORDER_STAGE_ERROR = 'RECEIVE_REORDER_STAGE_ERROR';
export const REQUEST_CREATE_VALUE_STREAM = 'REQUEST_CREATE_VALUE_STREAM'; export const REQUEST_CREATE_VALUE_STREAM = 'REQUEST_CREATE_VALUE_STREAM';
export const RECEIVE_CREATE_VALUE_STREAM_SUCCESS = 'RECEIVE_CREATE_VALUE_STREAM_SUCCESS'; export const RECEIVE_CREATE_VALUE_STREAM_SUCCESS = 'RECEIVE_CREATE_VALUE_STREAM_SUCCESS';
export const RECEIVE_CREATE_VALUE_STREAM_ERROR = 'RECEIVE_CREATE_VALUE_STREAM_ERROR'; export const RECEIVE_CREATE_VALUE_STREAM_ERROR = 'RECEIVE_CREATE_VALUE_STREAM_ERROR';
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';
...@@ -134,4 +134,16 @@ export default { ...@@ -134,4 +134,16 @@ export default {
state.isCreatingValueStream = false; state.isCreatingValueStream = false;
state.createValueStreamErrors = {}; state.createValueStreamErrors = {};
}, },
[types.SET_SELECTED_VALUE_STREAM](state, streamId) {
state.selectedValueStream = state.valueStreams.find(({ id }) => id === streamId);
},
[types.REQUEST_VALUE_STREAMS](state) {
state.valueStreams = [];
},
[types.RECEIVE_VALUE_STREAMS_ERROR](state) {
state.valueStreams = [];
},
[types.RECEIVE_VALUE_STREAMS_SUCCESS](state, data) {
state.valueStreams = data;
},
}; };
...@@ -20,6 +20,7 @@ export default () => ({ ...@@ -20,6 +20,7 @@ export default () => ({
selectedMilestone: null, selectedMilestone: null,
selectedAssignees: [], selectedAssignees: [],
selectedLabels: [], selectedLabels: [],
selectedValueStream: null,
currentStageEvents: [], currentStageEvents: [],
...@@ -29,4 +30,5 @@ export default () => ({ ...@@ -29,4 +30,5 @@ export default () => ({
stages: [], stages: [],
summary: [], summary: [],
medians: {}, medians: {},
valueStreams: [],
}); });
...@@ -15,6 +15,8 @@ export default { ...@@ -15,6 +15,8 @@ export default {
cycleAnalyticsSummaryDataPath: '/groups/:id/-/analytics/value_stream_analytics/summary', cycleAnalyticsSummaryDataPath: '/groups/:id/-/analytics/value_stream_analytics/summary',
cycleAnalyticsTimeSummaryDataPath: '/groups/:id/-/analytics/value_stream_analytics/time_summary', cycleAnalyticsTimeSummaryDataPath: '/groups/:id/-/analytics/value_stream_analytics/time_summary',
cycleAnalyticsGroupStagesAndEventsPath: '/groups/:id/-/analytics/value_stream_analytics/stages', cycleAnalyticsGroupStagesAndEventsPath: '/groups/:id/-/analytics/value_stream_analytics/stages',
cycleAnalyticsValueStreamGroupStagesAndEventsPath:
'/groups/:id/-/analytics/value_stream_analytics/value_streams/:value_stream_id/stages',
cycleAnalyticsValueStreamsPath: '/groups/:id/-/analytics/value_stream_analytics/value_streams', cycleAnalyticsValueStreamsPath: '/groups/:id/-/analytics/value_stream_analytics/value_streams',
cycleAnalyticsStageEventsPath: cycleAnalyticsStageEventsPath:
'/groups/:id/-/analytics/value_stream_analytics/stages/:stage_id/records', '/groups/:id/-/analytics/value_stream_analytics/stages/:stage_id/records',
...@@ -141,6 +143,14 @@ export default { ...@@ -141,6 +143,14 @@ export default {
return axios.get(url, { params }); return axios.get(url, { params });
}, },
cycleAnalyticsValueStreamGroupStagesAndEvents(groupId, valueStreamId, params = {}) {
const url = Api.buildUrl(this.cycleAnalyticsValueStreamGroupStagesAndEventsPath)
.replace(':id', groupId)
.replace(':value_stream_id', valueStreamId);
return axios.get(url, { params });
},
cycleAnalyticsStageEvents(groupId, stageId, params = {}) { cycleAnalyticsStageEvents(groupId, stageId, params = {}) {
const url = Api.buildUrl(this.cycleAnalyticsStageEventsPath) const url = Api.buildUrl(this.cycleAnalyticsStageEventsPath)
.replace(':id', groupId) .replace(':id', groupId)
...@@ -168,6 +178,11 @@ export default { ...@@ -168,6 +178,11 @@ export default {
return axios.post(url, data); return axios.post(url, data);
}, },
cycleAnalyticsValueStreams(groupId, data) {
const url = Api.buildUrl(this.cycleAnalyticsValueStreamsPath).replace(':id', groupId);
return axios.get(url, data);
},
cycleAnalyticsStageUrl(stageId, groupId) { cycleAnalyticsStageUrl(stageId, groupId) {
return Api.buildUrl(this.cycleAnalyticsStagePath) return Api.buildUrl(this.cycleAnalyticsStagePath)
.replace(':id', groupId) .replace(':id', groupId)
......
...@@ -246,6 +246,7 @@ describe('Cycle analytics actions', () => { ...@@ -246,6 +246,7 @@ describe('Cycle analytics actions', () => {
[], [],
[ [
{ type: 'requestCycleAnalyticsData' }, { type: 'requestCycleAnalyticsData' },
{ type: 'fetchValueStreams' },
{ type: 'fetchGroupStagesAndEvents' }, { type: 'fetchGroupStagesAndEvents' },
{ type: 'fetchStageMedianValues' }, { type: 'fetchStageMedianValues' },
{ type: 'receiveCycleAnalyticsDataSuccess' }, { type: 'receiveCycleAnalyticsDataSuccess' },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment