Commit 6cae55a9 authored by Mike Greiling's avatar Mike Greiling

Merge branch '32112-follow-up-migrate-mock-data-to-fixture-spec' into 'master'

Resolve "Follow up - Migrate mock data to fixture spec"

Closes #32112

See merge request gitlab-org/gitlab!17727
parents a296b9b4 85e2d719
import Vue from 'vue'; import Vue from 'vue';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import CustomStageForm from 'ee/analytics/cycle_analytics/components/custom_stage_form.vue'; import CustomStageForm from 'ee/analytics/cycle_analytics/components/custom_stage_form.vue';
import { apiResponse, groupLabels } from '../mock_data'; import {
groupLabels,
const { events } = apiResponse; customStageEvents as events,
labelStartEvent,
const startEvents = events.filter(ev => ev.canBeStartEvent); labelStopEvent,
const stopEvents = events.filter(ev => !ev.canBeStartEvent); customStageStartEvents as startEvents,
customStageStopEvents as stopEvents,
} from '../mock_data';
const initData = { const initData = {
name: 'Cool stage pre', name: 'Cool stage pre',
startEvent: 'issue_label_added', startEvent: labelStartEvent.identifier,
startEventLabel: groupLabels[0].id, startEventLabel: groupLabels[0].id,
stopEvent: 'issue_label_removed', stopEvent: labelStopEvent.identifier,
stopEventLabel: groupLabels[1].id, stopEventLabel: groupLabels[1].id,
}; };
...@@ -129,7 +131,7 @@ describe('CustomStageForm', () => { ...@@ -129,7 +131,7 @@ describe('CustomStageForm', () => {
it('will display the start event label field if a label event is selected', done => { it('will display the start event label field if a label event is selected', done => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
startEvent: 'issue_label_added', startEvent: labelStartEvent.identifier,
}, },
}); });
...@@ -143,7 +145,7 @@ describe('CustomStageForm', () => { ...@@ -143,7 +145,7 @@ describe('CustomStageForm', () => {
const selectedLabelId = groupLabels[0].id; const selectedLabelId = groupLabels[0].id;
expect(wrapper.vm.fields.startEventLabel).toEqual(null); expect(wrapper.vm.fields.startEventLabel).toEqual(null);
wrapper.find(sel.startEvent).setValue('issue_label_added'); wrapper.find(sel.startEvent).setValue(labelStartEvent.identifier);
Vue.nextTick(() => { Vue.nextTick(() => {
wrapper wrapper
.find(sel.startEventLabel) .find(sel.startEventLabel)
...@@ -162,12 +164,7 @@ describe('CustomStageForm', () => { ...@@ -162,12 +164,7 @@ describe('CustomStageForm', () => {
describe('Stop event', () => { describe('Stop event', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent( wrapper = createComponent({}, false);
{
events,
},
false,
);
}); });
it('notifies that a start event needs to be selected first', () => { it('notifies that a start event needs to be selected first', () => {
...@@ -208,17 +205,24 @@ describe('CustomStageForm', () => { ...@@ -208,17 +205,24 @@ describe('CustomStageForm', () => {
it('will only display valid stop events allowed for the selected start event', done => { it('will only display valid stop events allowed for the selected start event', done => {
let stopOptions = wrapper.find(sel.stopEvent).findAll('option'); let stopOptions = wrapper.find(sel.stopEvent).findAll('option');
const index = 2;
const selectedStopEventIdentifer = startEvents[index].allowedEndEvents[0];
const selectedStopEvent = stopEvents.find(
ev => ev.identifier === selectedStopEventIdentifer,
);
expect(stopOptions.at(0).html()).toEqual('<option value="">Select stop event</option>'); expect(stopOptions.at(0).html()).toEqual('<option value="">Select stop event</option>');
selectDropdownOption(wrapper, sel.startEvent, 1); selectDropdownOption(wrapper, sel.startEvent, index);
Vue.nextTick(() => { Vue.nextTick(() => {
stopOptions = wrapper.find(sel.stopEvent).findAll('option'); stopOptions = wrapper.find(sel.stopEvent).findAll('option');
[ [
{ name: 'Select stop event', identifier: '' }, { name: 'Select stop event', identifier: '' },
{ {
name: 'Issue first associated with a milestone or issue first added to a board', name: selectedStopEvent.name,
identifier: 'issue_stage_end', identifier: selectedStopEvent.identifier,
}, },
].forEach(({ name, identifier }, i) => { ].forEach(({ name, identifier }, i) => {
expect(stopOptions.at(i).html()).toEqual( expect(stopOptions.at(i).html()).toEqual(
...@@ -307,8 +311,8 @@ describe('CustomStageForm', () => { ...@@ -307,8 +311,8 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
startEvent: 'issue_label_added', stopEvent: labelStopEvent.identifier,
stopEvent: 'issue_label_removed', startEvent: labelStartEvent.identifier,
}, },
}); });
...@@ -324,8 +328,8 @@ describe('CustomStageForm', () => { ...@@ -324,8 +328,8 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
startEvent: 'issue_label_added', startEvent: labelStartEvent.identifier,
stopEvent: 'issue_label_removed', stopEvent: labelStopEvent.identifier,
}, },
}); });
...@@ -373,10 +377,16 @@ describe('CustomStageForm', () => { ...@@ -373,10 +377,16 @@ describe('CustomStageForm', () => {
}); });
describe('with all fields set', () => { describe('with all fields set', () => {
const startEventIndex = 2;
const firstStopEventIdentifier = startEvents[startEventIndex].allowedEndEvents[0];
const stopEventIndex = stopEvents.findIndex(
ev => ev.identifier === firstStopEventIdentifier,
);
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({}, false); wrapper = createComponent({}, false);
selectDropdownOption(wrapper, sel.startEvent, 1); selectDropdownOption(wrapper, sel.startEvent, startEventIndex);
return Vue.nextTick(() => { return Vue.nextTick(() => {
selectDropdownOption(wrapper, sel.stopEvent, 1); selectDropdownOption(wrapper, sel.stopEvent, 1);
...@@ -402,9 +412,9 @@ describe('CustomStageForm', () => { ...@@ -402,9 +412,9 @@ describe('CustomStageForm', () => {
const res = [ const res = [
{ {
name: 'Cool stage', name: 'Cool stage',
startEvent: 'issue_created', startEvent: startEvents[startEventIndex].identifier,
startEventLabel: null, startEventLabel: null,
stopEvent: 'issue_stage_end', stopEvent: stopEvents[stopEventIndex].identifier,
stopEventLabel: null, stopEventLabel: null,
}, },
]; ];
...@@ -440,8 +450,8 @@ describe('CustomStageForm', () => { ...@@ -440,8 +450,8 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
name: 'Cool stage pre', name: 'Cool stage pre',
startEvent: 'issue_label_added', startEvent: labelStartEvent.identifier,
stopEvent: 'issue_label_removed', stopEvent: labelStopEvent.identifier,
}, },
}); });
...@@ -512,8 +522,8 @@ describe('CustomStageForm', () => { ...@@ -512,8 +522,8 @@ describe('CustomStageForm', () => {
wrapper.setData({ wrapper.setData({
fields: { fields: {
name: 'Cool stage pre', name: 'Cool stage pre',
startEvent: 'issue_label_added', startEvent: labelStartEvent.identifier,
stopEvent: 'issue_label_removed', stopEvent: labelStopEvent.identifier,
}, },
}); });
......
...@@ -55,199 +55,24 @@ export const testEvents = stageFixtures.test; ...@@ -55,199 +55,24 @@ export const testEvents = stageFixtures.test;
export const stagingEvents = stageFixtures.staging; export const stagingEvents = stageFixtures.staging;
export const productionEvents = stageFixtures.production; export const productionEvents = stageFixtures.production;
// NOTE: once the backend is complete, we can generate this as a JSON fixture const { events: rawCustomStageEvents } = getJSONFixture('analytics/cycle_analytics/stages.json');
// https://gitlab.com/gitlab-org/gitlab/issues/32112 const camelCasedStageEvents = rawCustomStageEvents.map(deepCamelCase);
export const apiResponse = {
events: [ export const customStageStartEvents = camelCasedStageEvents.filter(ev => ev.canBeStartEvent);
{ export const customStageStopEvents = camelCasedStageEvents.filter(ev => !ev.canBeStartEvent);
name: 'Issue created',
identifier: 'issue_created',
type: 'simple',
canBeStartEvent: true,
allowedEndEvents: ['issue_stage_end'],
},
{
name: 'Issue first mentioned in a commit',
identifier: 'issue_first_mentioned_in_commit',
type: 'simple',
canBeStartEvent: false,
allowedEndEvents: [],
},
{
name: 'Merge request created',
identifier: 'merge_request_created',
type: 'simple',
canBeStartEvent: true,
allowedEndEvents: ['merge_request_merged'],
},
{
name: 'Merge request first deployed to production',
identifier: 'merge_request_first_deployed_to_production',
type: 'simple',
canBeStartEvent: false,
allowedEndEvents: [],
},
{
name: 'Merge request last build finish time',
identifier: 'merge_request_last_build_finished',
type: 'simple',
canBeStartEvent: false,
allowedEndEvents: [],
},
{
name: 'Merge request last build start time',
identifier: 'merge_request_last_build_started',
type: 'simple',
canBeStartEvent: true,
allowedEndEvents: ['merge_request_last_build_finished'],
},
{
name: 'Merge request merged',
identifier: 'merge_request_merged',
type: 'simple',
canBeStartEvent: true,
allowedEndEvents: ['merge_request_first_deployed_to_production'],
},
{
name: 'Issue first mentioned in a commit',
identifier: 'code_stage_start',
type: 'simple',
canBeStartEvent: true,
allowedEndEvents: ['merge_request_created'],
},
{
name: 'Issue first associated with a milestone or issue first added to a board',
identifier: 'issue_stage_end',
type: 'simple',
canBeStartEvent: false,
allowedEndEvents: [],
},
{
name: 'Issue first associated with a milestone or issue first added to a board',
identifier: 'plan_stage_start',
type: 'simple',
canBeStartEvent: true,
allowedEndEvents: ['issue_first_mentioned_in_commit'],
},
{
identifier: 'issue_label_added',
name: 'Issue Label Added',
type: 'label',
canBeStartEvent: true,
allowedEndEvents: ['issue_closed', 'issue_label_removed'],
},
{
identifier: 'issue_label_removed',
name: 'Issue Label Removed',
type: 'label',
canBeStartEvent: false,
allowedEndEvents: [],
},
],
stages: [
{
name: 'issue',
legend: 'Related Issues',
description: 'Time before an issue gets scheduled',
id: 'issue',
position: 1,
hidden: false,
custom: false,
startEventIdentifier: 'issue_created',
endEventIdentifier: 'issue_stage_end',
},
{
name: 'plan',
legend: 'Related Issues',
description: 'Time before an issue starts implementation',
id: 'plan',
position: 2,
hidden: false,
custom: false,
startEventIdentifier: 'plan_stage_start',
endEventIdentifier: 'issue_first_mentioned_in_commit',
},
{
name: 'code',
legend: 'Related Merged Requests',
description: 'Time until first merge request',
id: 'code',
position: 3,
hidden: false,
custom: false,
startEventIdentifier: 'code_stage_start',
endEventIdentifier: 'merge_request_created',
},
{
name: 'test',
legend: 'Related Merged Requests',
description: 'Total test time for all commits/merges',
id: 'test',
position: 4,
hidden: false,
custom: false,
startEventIdentifier: 'merge_request_last_build_started',
endEventIdentifier: 'merge_request_last_build_finished',
},
{
name: 'review',
legend: 'Related Merged Requests',
description: 'Time between merge request creation and merge/close',
id: 'review',
position: 5,
hidden: false,
custom: false,
startEventIdentifier: 'merge_request_created',
endEventIdentifier: 'merge_request_merged',
},
{
name: 'staging',
legend: 'Related Merged Requests',
description: 'From merge request merge until deploy to production',
id: 'staging',
position: 6,
hidden: false,
custom: false,
startEventIdentifier: 'merge_request_merged',
endEventIdentifier: 'merge_request_first_deployed_to_production',
},
{
name: 'production',
legend: 'Related Merged Requests',
description: 'From issue creation until deploy to production',
id: 'production',
position: 7,
hidden: false,
custom: false,
startEventIdentifier: 'merge_request_merged',
endEventIdentifier: 'merge_request_first_deployed_to_production',
},
],
summary: [
{
value: 2,
title: 'New Issues',
},
{
value: 0,
title: 'Commits',
},
{
value: 0,
title: 'Deploys',
},
],
permissions: {
issue: true,
plan: true,
code: true,
test: true,
review: true,
staging: true,
production: true,
},
};
export default { // TODO: the shim below should be removed once we have label events seeding
apiResponse, export const labelStartEvent = { ...customStageStartEvents[0], type: 'label' };
const firstAllowedStopEvent = labelStartEvent.allowedEndEvents[0];
// We need to enusre that the stop event can be applied to the start event
export const labelStopEvent = {
...customStageStopEvents.find(ev => ev.identifier === firstAllowedStopEvent),
type: 'label',
}; };
export const customStageEvents = [
...customStageStartEvents.filter(ev => ev.identifier !== labelStartEvent.identifier),
...customStageStopEvents.filter(ev => ev.identifier !== labelStopEvent.identifier),
labelStartEvent,
labelStopEvent,
];
...@@ -6,23 +6,24 @@ import { ...@@ -6,23 +6,24 @@ import {
eventsByIdentifier, eventsByIdentifier,
getLabelEventsIdentifiers, getLabelEventsIdentifiers,
} from 'ee/analytics/cycle_analytics/utils'; } from 'ee/analytics/cycle_analytics/utils';
import { apiResponse } from './mock_data'; import {
customStageEvents as events,
const { events } = apiResponse; labelStartEvent,
labelStopEvent,
customStageStartEvents as startEvents,
customStageStopEvents as stopEvents,
} from './mock_data';
const startEvent = events[0]; const labelEvents = [labelStartEvent, labelStopEvent].map(i => i.identifier);
const endEvent = events[1];
const labelEvent = events[11];
const labelEvents = [events[10], events[11]].map(i => i.identifier);
describe('Cycle analytics utils', () => { describe('Cycle analytics utils', () => {
describe('isStartEvent', () => { describe('isStartEvent', () => {
it('will return true for a valid start event', () => { it('will return true for a valid start event', () => {
expect(isStartEvent(startEvent)).toEqual(true); expect(isStartEvent(startEvents[0])).toEqual(true);
}); });
it('will return false for input that is not a start event', () => { it('will return false for input that is not a start event', () => {
[endEvent, {}, [], null, undefined].forEach(ev => { [stopEvents[0], {}, [], null, undefined].forEach(ev => {
expect(isStartEvent(ev)).toEqual(false); expect(isStartEvent(ev)).toEqual(false);
}); });
}); });
...@@ -30,10 +31,10 @@ describe('Cycle analytics utils', () => { ...@@ -30,10 +31,10 @@ describe('Cycle analytics utils', () => {
describe('isLabelEvent', () => { describe('isLabelEvent', () => {
it('will return true if the given event identifier is in the labelEvents array', () => { it('will return true if the given event identifier is in the labelEvents array', () => {
expect(isLabelEvent(labelEvents, labelEvent.identifier)).toEqual(true); expect(isLabelEvent(labelEvents, labelStartEvent.identifier)).toEqual(true);
}); });
it('will return false if the given event identifier is not in the labelEvents array', () => { it('will return false if the given event identifier is not in the labelEvents array', () => {
[startEvent.identifier, null, undefined, ''].forEach(ev => { [startEvents[1].identifier, null, undefined, ''].forEach(ev => {
expect(isLabelEvent(labelEvents, ev)).toEqual(false); expect(isLabelEvent(labelEvents, ev)).toEqual(false);
}); });
expect(isLabelEvent(labelEvents)).toEqual(false); expect(isLabelEvent(labelEvents)).toEqual(false);
...@@ -68,6 +69,7 @@ describe('Cycle analytics utils', () => { ...@@ -68,6 +69,7 @@ describe('Cycle analytics utils', () => {
expect(res.length).toEqual(labelEvents.length); expect(res.length).toEqual(labelEvents.length);
expect(res).toEqual(labelEvents); expect(res).toEqual(labelEvents);
}); });
it('will return an empty array when there are no matches', () => { it('will return an empty array when there are no matches', () => {
const ev = [{ _type: 'simple' }, { type: 'simple' }, { t: 'simple' }]; const ev = [{ _type: 'simple' }, { type: 'simple' }, { t: 'simple' }];
expect(getLabelEventsIdentifiers(ev)).toEqual([]); expect(getLabelEventsIdentifiers(ev)).toEqual([]);
...@@ -77,8 +79,8 @@ describe('Cycle analytics utils', () => { ...@@ -77,8 +79,8 @@ describe('Cycle analytics utils', () => {
describe('getAllowedEndEvents', () => { describe('getAllowedEndEvents', () => {
it('will return the relevant end events for a given start event identifier', () => { it('will return the relevant end events for a given start event identifier', () => {
const se = events[10].allowedEndEvents; const se = events[0];
expect(getAllowedEndEvents(events, 'issue_label_added')).toEqual(se); expect(getAllowedEndEvents(events, se.identifier)).toEqual(se.allowedEndEvents);
}); });
it('will return an empty array if there are no end events available', () => { it('will return an empty array if there are no end events available', () => {
...@@ -90,7 +92,7 @@ describe('Cycle analytics utils', () => { ...@@ -90,7 +92,7 @@ describe('Cycle analytics utils', () => {
describe('eventsByIdentifier', () => { describe('eventsByIdentifier', () => {
it('will return the events with an identifier in the provided array', () => { it('will return the events with an identifier in the provided array', () => {
expect(eventsByIdentifier(events, labelEvents)).toEqual([events[10], events[11]]); expect(eventsByIdentifier(events, labelEvents)).toEqual([labelStartEvent, labelStopEvent]);
}); });
it('will return an empty array if there are no matching events', () => { it('will return an empty array if there are no matching events', () => {
......
...@@ -45,7 +45,6 @@ describe 'Analytics (JavaScript fixtures)' do ...@@ -45,7 +45,6 @@ describe 'Analytics (JavaScript fixtures)' do
end end
describe Groups::CycleAnalytics::EventsController, type: :controller do describe Groups::CycleAnalytics::EventsController, type: :controller do
using RSpec::Parameterized::TableSyntax
render_views render_views
before do before do
...@@ -87,4 +86,21 @@ describe 'Analytics (JavaScript fixtures)' do ...@@ -87,4 +86,21 @@ describe 'Analytics (JavaScript fixtures)' do
expect(response).to be_successful expect(response).to be_successful
end end
end end
describe Analytics::CycleAnalytics::StagesController, type: :controller do
render_views
before do
stub_feature_flags(Gitlab::Analytics::CYCLE_ANALYTICS_FEATURE_FLAG => true)
stub_licensed_features(cycle_analytics_for_groups: true)
sign_in(user)
end
it 'analytics/cycle_analytics/stages.json' do
get(:index, params: { group_id: group.name }, format: :json)
expect(response).to be_successful
end
end
end end
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