Commit 79a8cb9c authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Phil Hughes

Fetch stages from value stream

Fetch the data for each value stream and
make sure we include the value stream
in the create stage request

Create a stage in the value stream
parent 85aeffd2
...@@ -14,6 +14,7 @@ import { mapState, mapActions } from 'vuex'; ...@@ -14,6 +14,7 @@ import { mapState, mapActions } from 'vuex';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { DATA_REFETCH_DELAY } from '../../shared/constants'; import { DATA_REFETCH_DELAY } from '../../shared/constants';
import { DEFAULT_VALUE_STREAM_ID } from '../constants';
const ERRORS = { const ERRORS = {
MIN_LENGTH: __('Name is required'), MIN_LENGTH: __('Name is required'),
...@@ -31,6 +32,10 @@ const validate = ({ name }) => { ...@@ -31,6 +32,10 @@ const validate = ({ name }) => {
return errors; return errors;
}; };
const hasCustomValueStream = vs => {
return Boolean(vs.length > 1 || vs[0].name.toLowerCase().trim() !== DEFAULT_VALUE_STREAM_ID);
};
export default { export default {
components: { components: {
GlButton, GlButton,
...@@ -48,7 +53,7 @@ export default { ...@@ -48,7 +53,7 @@ export default {
data() { data() {
return { return {
name: '', name: '',
errors: { name: [] }, errors: {},
}; };
}, },
computed: { computed: {
...@@ -59,13 +64,13 @@ export default { ...@@ -59,13 +64,13 @@ export default {
selectedValueStream: 'selectedValueStream', selectedValueStream: 'selectedValueStream',
}), }),
isValid() { isValid() {
return !this.errors?.name.length; return !this.errors.name?.length;
}, },
invalidFeedback() { invalidFeedback() {
return this.errors?.name.join('\n'); return this.errors.name?.join('\n');
}, },
hasValueStreams() { hasValueStreams() {
return Boolean(this.data.length); return Boolean(this.data.length && hasCustomValueStream(this.data));
}, },
selectedValueStreamName() { selectedValueStreamName() {
return this.selectedValueStream?.name || ''; return this.selectedValueStream?.name || '';
...@@ -73,10 +78,19 @@ export default { ...@@ -73,10 +78,19 @@ export default {
selectedValueStreamId() { selectedValueStreamId() {
return this.selectedValueStream?.id || null; return this.selectedValueStream?.id || null;
}, },
hasFormErrors() {
const { initialFormErrors } = this;
return Boolean(Object.keys(initialFormErrors).length);
},
},
watch: {
initialFormErrors(newErrors = {}) {
this.errors = newErrors;
},
}, },
mounted() { mounted() {
const { initialFormErrors } = this; const { initialFormErrors } = this;
if (Object.keys(initialFormErrors).length) { if (this.hasFormErrors) {
this.errors = initialFormErrors; this.errors = initialFormErrors;
} else { } else {
this.onHandleInput(); this.onHandleInput();
...@@ -87,10 +101,12 @@ export default { ...@@ -87,10 +101,12 @@ export default {
onSubmit() { onSubmit() {
const { name } = this; const { name } = this;
return this.createValueStream({ name }).then(() => { return this.createValueStream({ name }).then(() => {
if (!this.hasFormErrors) {
this.$toast.show(sprintf(__("'%{name}' Value Stream created"), { name }), { this.$toast.show(sprintf(__("'%{name}' Value Stream created"), { name }), {
position: 'top-center', position: 'top-center',
}); });
this.name = ''; this.name = '';
}
}); });
}, },
onHandleInput: debounce(function debouncedValidation() { onHandleInput: debounce(function debouncedValidation() {
......
...@@ -76,3 +76,5 @@ export const CAPITALIZED_STAGE_NAME = Object.keys(STAGE_NAME).reduce((acc, stage ...@@ -76,3 +76,5 @@ export const CAPITALIZED_STAGE_NAME = Object.keys(STAGE_NAME).reduce((acc, stage
}, {}); }, {});
export const PATH_HOME_ICON = 'home'; export const PATH_HOME_ICON = 'home';
export const DEFAULT_VALUE_STREAM_ID = 'default';
...@@ -112,7 +112,6 @@ export const fetchCycleAnalyticsData = ({ dispatch }) => { ...@@ -112,7 +112,6 @@ export const fetchCycleAnalyticsData = ({ dispatch }) => {
return Promise.resolve() return Promise.resolve()
.then(() => dispatch('fetchValueStreams')) .then(() => dispatch('fetchValueStreams'))
.then(() => dispatch('fetchGroupStagesAndEvents'))
.then(() => dispatch('fetchStageMedianValues')) .then(() => dispatch('fetchStageMedianValues'))
.then(() => dispatch('receiveCycleAnalyticsDataSuccess')) .then(() => dispatch('receiveCycleAnalyticsDataSuccess'))
.catch(error => dispatch('receiveCycleAnalyticsDataError', error)); .catch(error => dispatch('receiveCycleAnalyticsDataError', error));
...@@ -144,20 +143,35 @@ export const receiveGroupStagesSuccess = ({ commit, dispatch }, stages) => { ...@@ -144,20 +143,35 @@ export const receiveGroupStagesSuccess = ({ commit, dispatch }, stages) => {
return dispatch('setDefaultSelectedStage'); return dispatch('setDefaultSelectedStage');
}; };
export const fetchValueStreamStages = ({
hasCreateMultipleValueStreams,
valueStreamId,
groupId,
params,
}) => {
return hasCreateMultipleValueStreams
? Api.cycleAnalyticsValueStreamGroupStagesAndEvents(groupId, valueStreamId, params)
: Api.cycleAnalyticsGroupStagesAndEvents(groupId, params);
};
export const fetchGroupStagesAndEvents = ({ state, dispatch, getters }) => { export const fetchGroupStagesAndEvents = ({ state, dispatch, getters }) => {
const { const {
selectedGroup: { fullPath }, featureFlags: { hasCreateMultipleValueStreams = false },
} = state; } = state;
const { const {
currentValueStreamId: valueStreamId,
currentGroupPath: groupId,
cycleAnalyticsRequestParams: { created_after, project_ids }, cycleAnalyticsRequestParams: { created_after, project_ids },
} = getters; } = getters;
dispatch('requestGroupStages'); dispatch('requestGroupStages');
dispatch('customStages/setStageEvents', []); dispatch('customStages/setStageEvents', []);
return Api.cycleAnalyticsGroupStagesAndEvents(fullPath, { return fetchValueStreamStages({
start_date: created_after, hasCreateMultipleValueStreams,
project_ids, groupId,
valueStreamId,
params: { start_date: created_after, project_ids },
}) })
.then(({ data: { stages = [], events = [] } }) => { .then(({ data: { stages = [], events = [] } }) => {
dispatch('receiveGroupStagesSuccess', stages); dispatch('receiveGroupStagesSuccess', stages);
...@@ -307,13 +321,15 @@ export const createValueStream = ({ commit, dispatch, rootState }, data) => { ...@@ -307,13 +321,15 @@ export const createValueStream = ({ commit, dispatch, rootState }, data) => {
return Api.cycleAnalyticsCreateValueStream(fullPath, data) return Api.cycleAnalyticsCreateValueStream(fullPath, data)
.then(() => dispatch('receiveCreateValueStreamSuccess')) .then(() => dispatch('receiveCreateValueStreamSuccess'))
.catch(({ response } = {}) => { .catch(({ response } = {}) => {
const { data: { message, errors } = null } = response; const { data: { message, payload: { errors } } = null } = response;
commit(types.RECEIVE_CREATE_VALUE_STREAM_ERROR, { data, message, errors }); commit(types.RECEIVE_CREATE_VALUE_STREAM_ERROR, { message, errors });
}); });
}; };
export const setSelectedValueStream = ({ commit }, streamId) => export const setSelectedValueStream = ({ commit, dispatch }, streamId) => {
commit(types.SET_SELECTED_VALUE_STREAM, streamId); commit(types.SET_SELECTED_VALUE_STREAM, streamId);
return dispatch('fetchGroupStagesAndEvents');
};
export const receiveValueStreamsSuccess = ({ commit, dispatch }, data = []) => { export const receiveValueStreamsSuccess = ({ commit, dispatch }, data = []) => {
commit(types.RECEIVE_VALUE_STREAMS_SUCCESS, data); commit(types.RECEIVE_VALUE_STREAMS_SUCCESS, data);
...@@ -340,5 +356,5 @@ export const fetchValueStreams = ({ commit, dispatch, getters, state }) => { ...@@ -340,5 +356,5 @@ export const fetchValueStreams = ({ commit, dispatch, getters, state }) => {
commit(types.RECEIVE_VALUE_STREAMS_ERROR, data); commit(types.RECEIVE_VALUE_STREAMS_ERROR, data);
}); });
} }
return Promise.resolve(); return dispatch('fetchGroupStagesAndEvents');
}; };
...@@ -65,15 +65,17 @@ export const receiveCreateStageError = ( ...@@ -65,15 +65,17 @@ export const receiveCreateStageError = (
return dispatch('setStageFormErrors', errors); return dispatch('setStageFormErrors', errors);
}; };
export const createStage = ({ dispatch, rootState }, data) => { export const createStage = ({ dispatch, rootState, rootGetters }, data) => {
const { const {
selectedGroup: { fullPath }, selectedGroup: { fullPath },
} = rootState; } = rootState;
const { currentValueStreamId } = rootGetters;
dispatch('clearFormErrors'); dispatch('clearFormErrors');
dispatch('setSavingCustomStage'); dispatch('setSavingCustomStage');
return Api.cycleAnalyticsCreateStage(fullPath, data) return Api.cycleAnalyticsCreateStage(fullPath, currentValueStreamId, data)
.then(response => { .then(response => {
const { status, data: responseData } = response; const { status, data: responseData } = response;
return dispatch('receiveCreateStageSuccess', { status, data: responseData }); return dispatch('receiveCreateStageSuccess', { status, data: responseData });
......
...@@ -126,7 +126,7 @@ export default { ...@@ -126,7 +126,7 @@ export default {
state.isCreatingValueStream = true; state.isCreatingValueStream = true;
state.createValueStreamErrors = {}; state.createValueStreamErrors = {};
}, },
[types.RECEIVE_CREATE_VALUE_STREAM_ERROR](state, errors = {}) { [types.RECEIVE_CREATE_VALUE_STREAM_ERROR](state, { errors } = {}) {
state.isCreatingValueStream = false; state.isCreatingValueStream = false;
state.createValueStreamErrors = errors; state.createValueStreamErrors = errors;
}, },
......
...@@ -144,6 +144,14 @@ export default { ...@@ -144,6 +144,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)
...@@ -160,8 +168,10 @@ export default { ...@@ -160,8 +168,10 @@ export default {
return axios.get(url, { params: { ...params } }); return axios.get(url, { params: { ...params } });
}, },
cycleAnalyticsCreateStage(groupId, data) { cycleAnalyticsCreateStage(groupId, valueStreamId, data) {
const url = Api.buildUrl(this.cycleAnalyticsGroupStagesAndEventsPath).replace(':id', groupId); const url = Api.buildUrl(this.cycleAnalyticsValueStreamGroupStagesAndEventsPath)
.replace(':id', groupId)
.replace(':value_stream_id', valueStreamId);
return axios.post(url, data); return axios.post(url, data);
}, },
......
...@@ -126,7 +126,7 @@ module Analytics ...@@ -126,7 +126,7 @@ module Analytics
end end
def load_value_stream def load_value_stream
if params[:value_stream_id] if params[:value_stream_id] && params[:value_stream_id] != Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
@value_stream = @group.value_streams.find(params[:value_stream_id]) @value_stream = @group.value_streams.find(params[:value_stream_id])
end end
end end
......
...@@ -11,7 +11,7 @@ class Groups::Analytics::CycleAnalytics::ValueStreamsController < Analytics::App ...@@ -11,7 +11,7 @@ class Groups::Analytics::CycleAnalytics::ValueStreamsController < Analytics::App
end end
def index def index
render json: Analytics::GroupValueStreamSerializer.new.represent(@group.value_streams) render json: Analytics::GroupValueStreamSerializer.new.represent(value_streams)
end end
def create def create
...@@ -29,4 +29,12 @@ class Groups::Analytics::CycleAnalytics::ValueStreamsController < Analytics::App ...@@ -29,4 +29,12 @@ class Groups::Analytics::CycleAnalytics::ValueStreamsController < Analytics::App
def value_stream_params def value_stream_params
params.require(:value_stream).permit(:name) params.require(:value_stream).permit(:name)
end end
def value_streams
@group.value_streams.presence || [in_memory_default_value_stream]
end
def in_memory_default_value_stream
@group.value_streams.new(name: Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME)
end
end end
...@@ -4,5 +4,11 @@ module Analytics ...@@ -4,5 +4,11 @@ module Analytics
class GroupValueStreamEntity < Grape::Entity class GroupValueStreamEntity < Grape::Entity
expose :name expose :name
expose :id expose :id
private
def id
object.id || object.name # use the name `default` if the record is not persisted
end
end end
end end
...@@ -54,6 +54,19 @@ RSpec.describe Analytics::CycleAnalytics::StagesController do ...@@ -54,6 +54,19 @@ RSpec.describe Analytics::CycleAnalytics::StagesController do
expect(response).to be_successful expect(response).to be_successful
end end
context 'when `default` value_stream_id is given' do
before do
params[:value_stream_id] = Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
end
it 'returns only the default value stream stages' do
subject
expect(response).to be_successful
expect(json_response['stages'].size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size)
end
end
it 'renders `forbidden` based on the response of the service object' do it 'renders `forbidden` based on the response of the service object' do
expect_any_instance_of(Analytics::CycleAnalytics::Stages::ListService).to receive(:can?).and_return(false) expect_any_instance_of(Analytics::CycleAnalytics::Stages::ListService).to receive(:can?).and_return(false)
......
...@@ -6,7 +6,6 @@ RSpec.describe Groups::Analytics::CycleAnalytics::ValueStreamsController do ...@@ -6,7 +6,6 @@ RSpec.describe Groups::Analytics::CycleAnalytics::ValueStreamsController do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:group, refind: true) { create(:group) } let_it_be(:group, refind: true) { create(:group) }
let(:params) { { group_id: group } } let(:params) { { group_id: group } }
let!(:value_stream) { create(:cycle_analytics_group_value_stream, group: group) }
before do before do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true) stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
...@@ -17,11 +16,27 @@ RSpec.describe Groups::Analytics::CycleAnalytics::ValueStreamsController do ...@@ -17,11 +16,27 @@ RSpec.describe Groups::Analytics::CycleAnalytics::ValueStreamsController do
end end
describe 'GET #index' do describe 'GET #index' do
it 'returns an in-memory default value stream' do
get :index, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.size).to eq(1)
expect(json_response.first['id']).to eq(Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME)
expect(json_response.first['name']).to eq(Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME)
end
context 'when persisted value streams present' do
let!(:value_stream) { create(:cycle_analytics_group_value_stream, group: group) }
it 'succeeds' do it 'succeeds' do
get :index, params: params get :index, params: params
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('analytics/cycle_analytics/value_streams', dir: 'ee') expect(response).to match_response_schema('analytics/cycle_analytics/value_streams', dir: 'ee')
expect(json_response.first['id']).to eq(value_stream.id)
expect(json_response.first['name']).to eq(value_stream.name)
end
end end
end end
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"required": ["name", "id"], "required": ["name", "id"],
"properties": { "properties": {
"id": { "id": {
"type": "integer" "type": ["integer", "string"]
}, },
"name": { "name": {
"type": "string" "type": "string"
......
...@@ -179,7 +179,7 @@ describe('Cycle Analytics component', () => { ...@@ -179,7 +179,7 @@ describe('Cycle Analytics component', () => {
expect(wrapper.find(FilterBar).exists()).toBe(flag); expect(wrapper.find(FilterBar).exists()).toBe(flag);
}; };
const displaysCreateValueStream = flag => { const displaysValueStreamSelect = flag => {
expect(wrapper.find(ValueStreamSelect).exists()).toBe(flag); expect(wrapper.find(ValueStreamSelect).exists()).toBe(flag);
}; };
...@@ -247,8 +247,8 @@ describe('Cycle Analytics component', () => { ...@@ -247,8 +247,8 @@ describe('Cycle Analytics component', () => {
displaysPathNavigation(false); displaysPathNavigation(false);
}); });
it('does not display the create multiple value streams button', () => { it('does not display the value stream select component', () => {
displaysCreateValueStream(false); displaysValueStreamSelect(false);
}); });
describe('hideGroupDropDown = true', () => { describe('hideGroupDropDown = true', () => {
...@@ -276,8 +276,8 @@ describe('Cycle Analytics component', () => { ...@@ -276,8 +276,8 @@ describe('Cycle Analytics component', () => {
}); });
}); });
it('displays the create multiple value streams button', () => { it('displays the value stream select component', () => {
displaysCreateValueStream(true); displaysValueStreamSelect(true);
}); });
}); });
}); });
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlButton, GlModal, GlNewDropdown as GlDropdown } from '@gitlab/ui'; import { GlButton, GlModal, GlNewDropdown as GlDropdown, GlFormGroup } from '@gitlab/ui';
import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue'; import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
import { valueStreams } from '../mock_data'; import { valueStreams } from '../mock_data';
import { findDropdownItemText } from '../helpers'; import { findDropdownItemText } from '../helpers';
...@@ -14,6 +14,7 @@ describe('ValueStreamSelect', () => { ...@@ -14,6 +14,7 @@ describe('ValueStreamSelect', () => {
const createValueStreamMock = jest.fn(() => Promise.resolve()); const createValueStreamMock = jest.fn(() => Promise.resolve());
const mockEvent = { preventDefault: jest.fn() }; const mockEvent = { preventDefault: jest.fn() };
const mockToastShow = jest.fn(); const mockToastShow = jest.fn();
const streamName = 'Cool stream';
const fakeStore = ({ initialState = {} }) => const fakeStore = ({ initialState = {} }) =>
new Vuex.Store({ new Vuex.Store({
...@@ -52,6 +53,7 @@ describe('ValueStreamSelect', () => { ...@@ -52,6 +53,7 @@ describe('ValueStreamSelect', () => {
const findSelectValueStreamDropdown = () => wrapper.find(GlDropdown); const findSelectValueStreamDropdown = () => wrapper.find(GlDropdown);
const findSelectValueStreamDropdownOptions = _wrapper => findDropdownItemText(_wrapper); const findSelectValueStreamDropdownOptions = _wrapper => findDropdownItemText(_wrapper);
const findCreateValueStreamButton = () => wrapper.find(GlButton); const findCreateValueStreamButton = () => wrapper.find(GlButton);
const findFormGroup = () => wrapper.find(GlFormGroup);
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ wrapper = createComponent({
...@@ -105,9 +107,30 @@ describe('ValueStreamSelect', () => { ...@@ -105,9 +107,30 @@ describe('ValueStreamSelect', () => {
expect(submitButtonDisabledState()).toBe(true); expect(submitButtonDisabledState()).toBe(true);
}); });
describe('with valid fields', () => { describe('form errors', () => {
const streamName = 'Cool stream'; const fieldErrors = ['already exists', 'is required'];
beforeEach(() => {
wrapper = createComponent({
data: { name: streamName },
initialState: {
createValueStreamErrors: {
name: fieldErrors,
},
},
});
});
it('renders the error', () => {
expect(findFormGroup().attributes('invalid-feedback')).toEqual(fieldErrors.join('\n'));
});
it('submit button is disabled', () => {
expect(submitButtonDisabledState()).toBe(true);
});
});
describe('with valid fields', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ data: { name: streamName } }); wrapper = createComponent({ data: { name: streamName } });
}); });
......
...@@ -66,7 +66,6 @@ describe('Cycle analytics actions', () => { ...@@ -66,7 +66,6 @@ describe('Cycle analytics actions', () => {
${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ hasDurationChart: true }} ${'setFeatureFlags'} | ${'SET_FEATURE_FLAGS'} | ${'featureFlags'} | ${{ hasDurationChart: true }}
${'setSelectedProjects'} | ${'SET_SELECTED_PROJECTS'} | ${'selectedProjectIds'} | ${[10, 20, 30, 40]} ${'setSelectedProjects'} | ${'SET_SELECTED_PROJECTS'} | ${'selectedProjectIds'} | ${[10, 20, 30, 40]}
${'setSelectedStage'} | ${'SET_SELECTED_STAGE'} | ${'selectedStage'} | ${{ id: 'someStageId' }} ${'setSelectedStage'} | ${'SET_SELECTED_STAGE'} | ${'selectedStage'} | ${{ id: 'someStageId' }}
${'setSelectedValueStream'} | ${'SET_SELECTED_VALUE_STREAM'} | ${'selectedValueStream'} | ${{ id: 'vs-1', name: 'Value stream 1' }}
`('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => { `('$action should set $stateKey with $payload and type $type', ({ action, type, payload }) => {
return testAction( return testAction(
actions[action], actions[action],
...@@ -82,6 +81,20 @@ describe('Cycle analytics actions', () => { ...@@ -82,6 +81,20 @@ describe('Cycle analytics actions', () => {
); );
}); });
describe('setSelectedValueStream', () => {
const vs = { id: 'vs-1', name: 'Value stream 1' };
it('dispatches the fetchCycleAnalyticsData action', () => {
return testAction(
actions.setSelectedValueStream,
vs,
{ ...state, selectedValueStream: {} },
[{ type: types.SET_SELECTED_VALUE_STREAM, payload: vs }],
[{ type: 'fetchGroupStagesAndEvents' }],
);
});
});
describe('setDateRange', () => { describe('setDateRange', () => {
const payload = { startDate, endDate }; const payload = { startDate, endDate };
...@@ -256,7 +269,6 @@ describe('Cycle analytics actions', () => { ...@@ -256,7 +269,6 @@ describe('Cycle analytics actions', () => {
[ [
{ type: 'requestCycleAnalyticsData' }, { type: 'requestCycleAnalyticsData' },
{ type: 'fetchValueStreams' }, { type: 'fetchValueStreams' },
{ type: 'fetchGroupStagesAndEvents' },
{ type: 'fetchStageMedianValues' }, { type: 'fetchStageMedianValues' },
{ type: 'receiveCycleAnalyticsDataSuccess' }, { type: 'receiveCycleAnalyticsDataSuccess' },
], ],
...@@ -910,7 +922,9 @@ describe('Cycle analytics actions', () => { ...@@ -910,7 +922,9 @@ describe('Cycle analytics actions', () => {
}); });
describe('with errors', () => { describe('with errors', () => {
const resp = { message: 'error', errors: {} }; const errors = { name: ['is taken'] };
const message = { message: 'error' };
const resp = { message, payload: { errors } };
beforeEach(() => { beforeEach(() => {
mock.onPost(endpoints.valueStreamData).replyOnce(httpStatusCodes.NOT_FOUND, resp); mock.onPost(endpoints.valueStreamData).replyOnce(httpStatusCodes.NOT_FOUND, resp);
}); });
...@@ -924,7 +938,7 @@ describe('Cycle analytics actions', () => { ...@@ -924,7 +938,7 @@ describe('Cycle analytics actions', () => {
{ type: types.REQUEST_CREATE_VALUE_STREAM }, { type: types.REQUEST_CREATE_VALUE_STREAM },
{ {
type: types.RECEIVE_CREATE_VALUE_STREAM_ERROR, type: types.RECEIVE_CREATE_VALUE_STREAM_ERROR,
payload: { data: { ...payload }, ...resp }, payload: { message, errors },
}, },
], ],
[], [],
...@@ -1016,8 +1030,14 @@ describe('Cycle analytics actions', () => { ...@@ -1016,8 +1030,14 @@ describe('Cycle analytics actions', () => {
}; };
}); });
it(`will skip making a request`, () => it(`will dispatch the 'fetchGroupStagesAndEvents' request`, () =>
testAction(actions.fetchValueStreams, null, state, [], [])); testAction(
actions.fetchValueStreams,
null,
state,
[],
[{ type: 'fetchGroupStagesAndEvents' }],
));
}); });
}); });
}); });
...@@ -63,7 +63,7 @@ describe('Cycle analytics mutations', () => { ...@@ -63,7 +63,7 @@ describe('Cycle analytics mutations', () => {
${types.SET_SELECTED_PROJECTS} | ${selectedProjects} | ${{ selectedProjects }} ${types.SET_SELECTED_PROJECTS} | ${selectedProjects} | ${{ selectedProjects }}
${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }} ${types.SET_DATE_RANGE} | ${{ startDate, endDate }} | ${{ startDate, endDate }}
${types.SET_SELECTED_STAGE} | ${{ id: 'first-stage' }} | ${{ selectedStage: { id: 'first-stage' } }} ${types.SET_SELECTED_STAGE} | ${{ id: 'first-stage' }} | ${{ selectedStage: { id: 'first-stage' } }}
${types.RECEIVE_CREATE_VALUE_STREAM_ERROR} | ${{ name: ['is required'] }} | ${{ createValueStreamErrors: { name: ['is required'] }, isCreatingValueStream: false }} ${types.RECEIVE_CREATE_VALUE_STREAM_ERROR} | ${{ errors: { name: ['is required'] } }} | ${{ createValueStreamErrors: { name: ['is required'] }, isCreatingValueStream: false }}
${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${valueStreams} | ${{ valueStreams, isLoadingValueStreams: false }} ${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${valueStreams} | ${{ valueStreams, isLoadingValueStreams: false }}
${types.SET_SELECTED_VALUE_STREAM} | ${valueStreams[1].id} | ${{ selectedValueStream: {} }} ${types.SET_SELECTED_VALUE_STREAM} | ${valueStreams[1].id} | ${{ selectedValueStream: {} }}
`( `(
......
...@@ -442,6 +442,8 @@ describe('Api', () => { ...@@ -442,6 +442,8 @@ describe('Api', () => {
}); });
describe('cycleAnalyticsCreateStage', () => { describe('cycleAnalyticsCreateStage', () => {
const valueStreamId = 'fake-value-stream';
it('submit the custom stage data', done => { it('submit the custom stage data', done => {
const response = {}; const response = {};
const customStage = { const customStage = {
...@@ -451,10 +453,10 @@ describe('Api', () => { ...@@ -451,10 +453,10 @@ describe('Api', () => {
end_event_identifier: 'issue_closed', end_event_identifier: 'issue_closed',
end_event_label_id: null, end_event_label_id: null,
}; };
const expectedUrl = `${dummyCycleAnalyticsUrlRoot}/-/analytics/value_stream_analytics/stages`; const expectedUrl = `${dummyCycleAnalyticsUrlRoot}/-/analytics/value_stream_analytics/value_streams/${valueStreamId}/stages`;
mock.onPost(expectedUrl).reply(httpStatus.OK, response); mock.onPost(expectedUrl).reply(httpStatus.OK, response);
Api.cycleAnalyticsCreateStage(groupId, customStage) Api.cycleAnalyticsCreateStage(groupId, valueStreamId, customStage)
.then(({ data, config: { data: reqData, url } }) => { .then(({ data, config: { data: reqData, url } }) => {
expect(data).toEqual(response); expect(data).toEqual(response);
expect(JSON.parse(reqData)).toMatchObject(customStage); expect(JSON.parse(reqData)).toMatchObject(customStage);
......
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