Commit 0d7b6c81 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch 'fix-cycle-analytics-group-labels-endpoint' into 'master'

Replace cycle analytics group labels endpoint

See merge request gitlab-org/gitlab!25505
parents 7f6d5703 a0b66130
...@@ -26,6 +26,11 @@ export default { ...@@ -26,6 +26,11 @@ export default {
}, },
}, },
methods: { methods: {
labelTitle(label) {
// there are 2 possible endpoints for group labels
// one returns label.name the other label.title
return label?.name || label.title;
},
isSelectedLabel(id) { isSelectedLabel(id) {
return this.selectedLabelId && id === this.selectedLabelId; return this.selectedLabelId && id === this.selectedLabelId;
}, },
...@@ -41,7 +46,7 @@ export default { ...@@ -41,7 +46,7 @@ export default {
class="d-inline-block dropdown-label-box" class="d-inline-block dropdown-label-box"
> >
</span> </span>
{{ selectedLabel.title }} {{ labelTitle(selectedLabel) }}
</span> </span>
<span v-else>{{ __('Select a label') }}</span> <span v-else>{{ __('Select a label') }}</span>
</template> </template>
...@@ -56,7 +61,7 @@ export default { ...@@ -56,7 +61,7 @@ export default {
> >
<span :style="{ backgroundColor: label.color }" class="d-inline-block dropdown-label-box"> <span :style="{ backgroundColor: label.color }" class="d-inline-block dropdown-label-box">
</span> </span>
{{ label.title }} {{ labelTitle(label) }}
</gl-dropdown-item> </gl-dropdown-item>
</gl-dropdown> </gl-dropdown>
</template> </template>
...@@ -257,8 +257,8 @@ export const fetchGroupLabels = ({ dispatch, state }) => { ...@@ -257,8 +257,8 @@ export const fetchGroupLabels = ({ dispatch, state }) => {
selectedGroup: { fullPath }, selectedGroup: { fullPath },
} = state; } = state;
return Api.groupLabels(fullPath) return Api.cycleAnalyticsGroupLabels(fullPath)
.then(data => dispatch('receiveGroupLabelsSuccess', data)) .then(({ data }) => dispatch('receiveGroupLabelsSuccess', data))
.catch(error => .catch(error =>
handleErrorOrRethrow({ error, action: () => dispatch('receiveGroupLabelsError', error) }), handleErrorOrRethrow({ error, action: () => dispatch('receiveGroupLabelsError', error) }),
); );
......
...@@ -22,6 +22,7 @@ export default { ...@@ -22,6 +22,7 @@ export default {
cycleAnalyticsStagePath: '/-/analytics/value_stream_analytics/stages/:stage_id', cycleAnalyticsStagePath: '/-/analytics/value_stream_analytics/stages/:stage_id',
cycleAnalyticsDurationChartPath: cycleAnalyticsDurationChartPath:
'/-/analytics/value_stream_analytics/stages/:stage_id/duration_chart', '/-/analytics/value_stream_analytics/stages/:stage_id/duration_chart',
cycleAnalyticsGroupLabelsPath: '/api/:version/groups/:namespace_path/labels',
codeReviewAnalyticsPath: '/api/:version/analytics/code_review', codeReviewAnalyticsPath: '/api/:version/analytics/code_review',
countriesPath: '/-/countries', countriesPath: '/-/countries',
countryStatesPath: '/-/country_states', countryStatesPath: '/-/country_states',
...@@ -214,6 +215,19 @@ export default { ...@@ -214,6 +215,19 @@ export default {
}); });
}, },
cycleAnalyticsGroupLabels(groupId, params = {}) {
// TODO: This can be removed when we resolve the labels endpoint
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25746
const url = Api.buildUrl(this.cycleAnalyticsGroupLabelsPath).replace(
':namespace_path',
groupId,
);
return axios.get(url, {
params,
});
},
codeReviewAnalytics(params = {}) { codeReviewAnalytics(params = {}) {
const url = Api.buildUrl(this.codeReviewAnalyticsPath); const url = Api.buildUrl(this.codeReviewAnalyticsPath);
return axios.get(url, { params }); return axios.get(url, { params });
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Value Stream Analytics LabelsSelector with no item selected will render the label selector 1`] = `
"<gl-dropdown-stub text=\\"\\" toggle-class=\\"overflow-hidden\\" class=\\"w-100\\"><template></template>
<gl-dropdown-item-stub active=\\"true\\">Select a label
</gl-dropdown-item-stub>
<gl-dropdown-item-stub><span class=\\"d-inline-block dropdown-label-box\\" style=\\"background-color: rgb(255, 0, 0);\\"></span>
roses
</gl-dropdown-item-stub>
<gl-dropdown-item-stub><span class=\\"d-inline-block dropdown-label-box\\" style=\\"background-color: rgb(255, 255, 255);\\"></span>
some space
</gl-dropdown-item-stub>
<gl-dropdown-item-stub><span class=\\"d-inline-block dropdown-label-box\\" style=\\"background-color: rgb(0, 0, 255);\\"></span>
violets
</gl-dropdown-item-stub>
</gl-dropdown-stub>"
`;
exports[`Value Stream Analytics LabelsSelector with selectedLabelId set will render the label selector 1`] = `
"<gl-dropdown-stub text=\\"\\" toggle-class=\\"overflow-hidden\\" class=\\"w-100\\"><template></template>
<gl-dropdown-item-stub>Select a label
</gl-dropdown-item-stub>
<gl-dropdown-item-stub><span class=\\"d-inline-block dropdown-label-box\\" style=\\"background-color: rgb(255, 0, 0);\\"></span>
roses
</gl-dropdown-item-stub>
<gl-dropdown-item-stub><span class=\\"d-inline-block dropdown-label-box\\" style=\\"background-color: rgb(255, 255, 255);\\"></span>
some space
</gl-dropdown-item-stub>
<gl-dropdown-item-stub active=\\"true\\"><span class=\\"d-inline-block dropdown-label-box\\" style=\\"background-color: rgb(0, 0, 255);\\"></span>
violets
</gl-dropdown-item-stub>
</gl-dropdown-stub>"
`;
...@@ -28,17 +28,25 @@ exports[`TasksByTypeChart with data available filters labels with label dropdown ...@@ -28,17 +28,25 @@ exports[`TasksByTypeChart with data available filters labels with label dropdown
<ul> <ul>
<li> <li>
<a href=\\"#\\" class=\\"dropdown-menu-link is-active\\"> <a href=\\"#\\" class=\\"dropdown-menu-link is-active\\">
<span style=\\"background-color: #BADA55;\\" class=\\"d-inline-block dropdown-label-box\\"> <span style=\\"background-color: #FF0000;\\" class=\\"d-inline-block dropdown-label-box\\">
</span> </span>
Foo Label
</a> </a>
</li> </li>
<li> <li>
<a href=\\"#\\" class=\\"dropdown-menu-link is-active\\"> <a href=\\"#\\" class=\\"dropdown-menu-link is-active\\">
<span style=\\"background-color: #0033CC;\\" class=\\"d-inline-block dropdown-label-box\\"> <span style=\\"background-color: #FFFFFF;\\" class=\\"d-inline-block dropdown-label-box\\">
</span> </span>
Foo::Bar
</a>
</li>
<li>
<a href=\\"#\\" class=\\"dropdown-menu-link is-active\\">
<span style=\\"background-color: #0000FF;\\" class=\\"d-inline-block dropdown-label-box\\">
</span>
</a> </a>
</li> </li>
</ul> </ul>
...@@ -58,7 +66,7 @@ exports[`TasksByTypeChart with data available should render the loading chart 1` ...@@ -58,7 +66,7 @@ exports[`TasksByTypeChart with data available should render the loading chart 1`
<h3>Type of work</h3> <h3>Type of work</h3>
<div> <div>
<p>Showing data for group 'Gitlab Org' from Dec 11, 2019 to Jan 10, 2020</p> <p>Showing data for group 'Gitlab Org' from Dec 11, 2019 to Jan 10, 2020</p>
<tasks-by-type-filters-stub labels=\\"[object Object],[object Object]\\" selectedlabelids=\\"1,2,3\\" subjectfilter=\\"Issue\\"></tasks-by-type-filters-stub> <tasks-by-type-filters-stub labels=\\"[object Object],[object Object],[object Object]\\" selectedlabelids=\\"1,2,3\\" subjectfilter=\\"Issue\\"></tasks-by-type-filters-stub>
<gl-stacked-column-chart-stub data=\\"0,1,2,5,2,3,2,4,1\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"Group 1,Group 2,Group 3\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"Cool label,Normal label\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub> <gl-stacked-column-chart-stub data=\\"0,1,2,5,2,3,2,4,1\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"Group 1,Group 2,Group 3\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"Cool label,Normal label\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub>
</div> </div>
</div> </div>
......
...@@ -22,7 +22,6 @@ import * as mockData from '../mock_data'; ...@@ -22,7 +22,6 @@ import * as mockData from '../mock_data';
const noDataSvgPath = 'path/to/no/data'; const noDataSvgPath = 'path/to/no/data';
const noAccessSvgPath = 'path/to/no/access'; const noAccessSvgPath = 'path/to/no/access';
const emptyStateSvgPath = 'path/to/empty/state'; const emptyStateSvgPath = 'path/to/empty/state';
const baseStagesEndpoint = '/-/analytics/cycle_analytics/stages';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -50,7 +49,7 @@ function createComponent({ ...@@ -50,7 +49,7 @@ function createComponent({
emptyStateSvgPath, emptyStateSvgPath,
noDataSvgPath, noDataSvgPath,
noAccessSvgPath, noAccessSvgPath,
baseStagesEndpoint, baseStagesEndpoint: mockData.endpoints.baseStagesEndpoint,
}, },
provide: { provide: {
glFeatures: { glFeatures: {
...@@ -401,8 +400,6 @@ describe('Cycle Analytics component', () => { ...@@ -401,8 +400,6 @@ describe('Cycle Analytics component', () => {
}); });
describe('with failed requests while loading', () => { describe('with failed requests while loading', () => {
const { full_path: groupId } = mockData.group;
function mockRequestCycleAnalyticsData({ function mockRequestCycleAnalyticsData({
overrides = {}, overrides = {},
mockFetchStageData = true, mockFetchStageData = true,
...@@ -414,17 +411,17 @@ describe('Cycle Analytics component', () => { ...@@ -414,17 +411,17 @@ describe('Cycle Analytics component', () => {
const defaultRequests = { const defaultRequests = {
fetchSummaryData: { fetchSummaryData: {
status: defaultStatus, status: defaultStatus,
endpoint: `/-/analytics/value_stream_analytics/summary`, endpoint: mockData.endpoints.summaryData,
response: [...mockData.summaryData], response: [...mockData.summaryData],
}, },
fetchGroupStagesAndEvents: { fetchGroupStagesAndEvents: {
status: defaultStatus, status: defaultStatus,
endpoint: `/-/analytics/value_stream_analytics/stages`, endpoint: mockData.endpoints.baseStagesEndpoint,
response: { ...mockData.customizableStagesAndEvents }, response: { ...mockData.customizableStagesAndEvents },
}, },
fetchGroupLabels: { fetchGroupLabels: {
status: defaultStatus, status: defaultStatus,
endpoint: `/groups/${groupId}/-/labels`, endpoint: mockData.endpoints.groupLabels,
response: [...mockData.groupLabels], response: [...mockData.groupLabels],
}, },
...overrides, ...overrides,
...@@ -432,26 +429,22 @@ describe('Cycle Analytics component', () => { ...@@ -432,26 +429,22 @@ describe('Cycle Analytics component', () => {
if (mockFetchTasksByTypeData) { if (mockFetchTasksByTypeData) {
mock mock
.onGet(/analytics\/type_of_work\/tasks_by_type/) .onGet(mockData.endpoints.tasksByTypeData)
.reply(defaultStatus, { ...mockData.tasksByTypeData }); .reply(defaultStatus, { ...mockData.tasksByTypeData });
} }
if (mockFetchDurationData) { if (mockFetchDurationData) {
mock mock
.onGet(/analytics\/value_stream_analytics\/stages\/\d+\/duration_chart/) .onGet(mockData.endpoints.durationData)
.reply(defaultStatus, [...mockData.rawDurationData]); .reply(defaultStatus, [...mockData.rawDurationData]);
} }
if (mockFetchStageMedian) { if (mockFetchStageMedian) {
mock mock.onGet(mockData.endpoints.stageMedian).reply(defaultStatus, { value: null });
.onGet(/analytics\/value_stream_analytics\/stages\/\d+\/median/)
.reply(defaultStatus, { value: null });
} }
if (mockFetchStageData) { if (mockFetchStageData) {
mock mock.onGet(mockData.endpoints.stageData).reply(defaultStatus, mockData.issueEvents);
.onGet(/analytics\/value_stream_analytics\/stages\/\d+\/records/)
.reply(defaultStatus, mockData.issueEvents);
} }
Object.values(defaultRequests).forEach(({ endpoint, status, response }) => { Object.values(defaultRequests).forEach(({ endpoint, status, response }) => {
...@@ -487,7 +480,7 @@ describe('Cycle Analytics component', () => { ...@@ -487,7 +480,7 @@ describe('Cycle Analytics component', () => {
overrides: { overrides: {
fetchSummaryData: { fetchSummaryData: {
status: httpStatusCodes.NOT_FOUND, status: httpStatusCodes.NOT_FOUND,
endpoint: '/-/analytics/value_stream_analytics/summary', endpoint: mockData.endpoints.summaryData,
response: { response: { status: httpStatusCodes.NOT_FOUND } }, response: { response: { status: httpStatusCodes.NOT_FOUND } },
}, },
}, },
...@@ -504,6 +497,7 @@ describe('Cycle Analytics component', () => { ...@@ -504,6 +497,7 @@ describe('Cycle Analytics component', () => {
mockRequestCycleAnalyticsData({ mockRequestCycleAnalyticsData({
overrides: { overrides: {
fetchGroupLabels: { fetchGroupLabels: {
endpoint: mockData.endpoints.groupLabels,
status: httpStatusCodes.NOT_FOUND, status: httpStatusCodes.NOT_FOUND,
response: { response: { status: httpStatusCodes.NOT_FOUND } }, response: { response: { status: httpStatusCodes.NOT_FOUND } },
}, },
...@@ -521,7 +515,7 @@ describe('Cycle Analytics component', () => { ...@@ -521,7 +515,7 @@ describe('Cycle Analytics component', () => {
mockRequestCycleAnalyticsData({ mockRequestCycleAnalyticsData({
overrides: { overrides: {
fetchGroupStagesAndEvents: { fetchGroupStagesAndEvents: {
endPoint: '/-/analytics/value_stream_analytics/stages', endPoint: mockData.endpoints.baseStagesEndpoint,
status: httpStatusCodes.NOT_FOUND, status: httpStatusCodes.NOT_FOUND,
response: { response: { status: httpStatusCodes.NOT_FOUND } }, response: { response: { status: httpStatusCodes.NOT_FOUND } },
}, },
......
...@@ -4,6 +4,12 @@ import { groupLabels } from '../mock_data'; ...@@ -4,6 +4,12 @@ import { groupLabels } from '../mock_data';
const selectedLabel = groupLabels[groupLabels.length - 1]; const selectedLabel = groupLabels[groupLabels.length - 1];
const findActiveItem = wrapper =>
wrapper
.findAll('gl-dropdown-item-stub')
.filter(d => d.attributes('active'))
.at(0);
describe('Value Stream Analytics LabelsSelector', () => { describe('Value Stream Analytics LabelsSelector', () => {
function createComponent({ props = {}, shallow = true } = {}) { function createComponent({ props = {}, shallow = true } = {}) {
const func = shallow ? shallowMount : mount; const func = shallow ? shallowMount : mount;
...@@ -16,7 +22,7 @@ describe('Value Stream Analytics LabelsSelector', () => { ...@@ -16,7 +22,7 @@ describe('Value Stream Analytics LabelsSelector', () => {
} }
let wrapper = null; let wrapper = null;
const labelNames = groupLabels.map(({ title }) => title); const labelNames = groupLabels.map(({ name }) => name);
describe('with no item selected', () => { describe('with no item selected', () => {
beforeEach(() => { beforeEach(() => {
...@@ -25,14 +31,19 @@ describe('Value Stream Analytics LabelsSelector', () => { ...@@ -25,14 +31,19 @@ describe('Value Stream Analytics LabelsSelector', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
});
it('will render the label selector', () => {
expect(wrapper.html()).toMatchSnapshot();
}); });
it.each(labelNames)('generate a label item for the label %s', title => { it.each(labelNames)('generate a label item for the label %s', name => {
expect(wrapper.text()).toContain(title); expect(wrapper.text()).toContain(name);
}); });
it('will render with the default option selected', () => { it('will render with the default option selected', () => {
const activeItem = wrapper.find('[active="true"]'); const activeItem = findActiveItem(wrapper);
expect(activeItem.exists()).toBe(true); expect(activeItem.exists()).toBe(true);
expect(activeItem.text()).toEqual('Select a label'); expect(activeItem.text()).toEqual('Select a label');
...@@ -77,11 +88,15 @@ describe('Value Stream Analytics LabelsSelector', () => { ...@@ -77,11 +88,15 @@ describe('Value Stream Analytics LabelsSelector', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('will set the active class', () => { it('will render the label selector', () => {
const activeItem = wrapper.find('[active="true"]'); expect(wrapper.html()).toMatchSnapshot();
});
it('will set the active label', () => {
const activeItem = findActiveItem(wrapper);
expect(activeItem.exists()).toBe(true); expect(activeItem.exists()).toBe(true);
expect(activeItem.text()).toEqual(selectedLabel.title); expect(activeItem.text()).toEqual(selectedLabel.name);
}); });
}); });
}); });
...@@ -6,18 +6,30 @@ import * as types from 'ee/analytics/cycle_analytics/store/mutation_types'; ...@@ -6,18 +6,30 @@ import * as types from 'ee/analytics/cycle_analytics/store/mutation_types';
import { DEFAULT_DAYS_IN_PAST } from 'ee/analytics/cycle_analytics/constants'; import { DEFAULT_DAYS_IN_PAST } from 'ee/analytics/cycle_analytics/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { getDateInPast, getDatesInRange } from '~/lib/utils/datetime_utility'; import { getDateInPast, getDatesInRange } from '~/lib/utils/datetime_utility';
import { mockLabels } from '../../../../../spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data';
import { toYmd } from 'ee/analytics/shared/utils'; import { toYmd } from 'ee/analytics/shared/utils';
import { transformRawTasksByTypeData } from 'ee/analytics/cycle_analytics/utils'; import { transformRawTasksByTypeData } from 'ee/analytics/cycle_analytics/utils';
const endpoints = { const fixtureEndpoints = {
customizableCycleAnalyticsStagesAndEvents: 'analytics/value_stream_analytics/stages.json', // customizable stages and events endpoint customizableCycleAnalyticsStagesAndEvents: 'analytics/value_stream_analytics/stages.json', // customizable stages and events endpoint
stageEvents: stage => `analytics/value_stream_analytics/stages/${stage}/records.json`, stageEvents: stage => `analytics/value_stream_analytics/stages/${stage}/records.json`,
stageMedian: stage => `analytics/value_stream_analytics/stages/${stage}/median.json`, stageMedian: stage => `analytics/value_stream_analytics/stages/${stage}/median.json`,
summaryData: 'analytics/value_stream_analytics/summary.json', summaryData: 'analytics/value_stream_analytics/summary.json',
groupLabels: 'api/group_labels.json',
}; };
export const groupLabels = mockLabels; export const endpoints = {
groupLabels: /groups\/[A-Z|a-z|\d|\-|_]+\/labels/,
summaryData: /analytics\/value_stream_analytics\/summary/,
durationData: /analytics\/value_stream_analytics\/stages\/\d+\/duration_chart/,
stageData: /analytics\/value_stream_analytics\/stages\/\d+\/records/,
stageMedian: /analytics\/value_stream_analytics\/stages\/\d+\/median/,
baseStagesEndpoint: /analytics\/value_stream_analytics\/stages$/,
tasksByTypeData: /analytics\/type_of_work\/tasks_by_type/,
};
export const groupLabels = getJSONFixture(fixtureEndpoints.groupLabels).map(
convertObjectPropsToCamelCase,
);
export const group = { export const group = {
id: 1, id: 1,
...@@ -30,10 +42,10 @@ export const group = { ...@@ -30,10 +42,10 @@ export const group = {
const getStageByTitle = (stages, title) => const getStageByTitle = (stages, title) =>
stages.find(stage => stage.title && stage.title.toLowerCase().trim() === title) || {}; stages.find(stage => stage.title && stage.title.toLowerCase().trim() === title) || {};
export const summaryData = getJSONFixture(endpoints.summaryData); export const summaryData = getJSONFixture(fixtureEndpoints.summaryData);
export const customizableStagesAndEvents = getJSONFixture( export const customizableStagesAndEvents = getJSONFixture(
endpoints.customizableCycleAnalyticsStagesAndEvents, fixtureEndpoints.customizableCycleAnalyticsStagesAndEvents,
); );
const dummyState = {}; const dummyState = {};
...@@ -56,7 +68,7 @@ const deepCamelCase = obj => convertObjectPropsToCamelCase(obj, { deep: true }); ...@@ -56,7 +68,7 @@ const deepCamelCase = obj => convertObjectPropsToCamelCase(obj, { deep: true });
export const defaultStages = ['issue', 'plan', 'review', 'code', 'test', 'staging', 'production']; export const defaultStages = ['issue', 'plan', 'review', 'code', 'test', 'staging', 'production'];
const stageFixtures = defaultStages.reduce((acc, stage) => { const stageFixtures = defaultStages.reduce((acc, stage) => {
const events = getJSONFixture(endpoints.stageEvents(stage)); const events = getJSONFixture(fixtureEndpoints.stageEvents(stage));
return { return {
...acc, ...acc,
[stage]: events, [stage]: events,
...@@ -64,7 +76,7 @@ const stageFixtures = defaultStages.reduce((acc, stage) => { ...@@ -64,7 +76,7 @@ const stageFixtures = defaultStages.reduce((acc, stage) => {
}, {}); }, {});
export const stageMedians = defaultStages.reduce((acc, stage) => { export const stageMedians = defaultStages.reduce((acc, stage) => {
const { value } = getJSONFixture(endpoints.stageMedian(stage)); const { value } = getJSONFixture(fixtureEndpoints.stageMedian(stage));
return { return {
...acc, ...acc,
[stage]: value, [stage]: value,
......
...@@ -22,6 +22,7 @@ import { ...@@ -22,6 +22,7 @@ import {
rawDurationMedianData, rawDurationMedianData,
transformedDurationData, transformedDurationData,
transformedDurationMedianData, transformedDurationMedianData,
endpoints,
} from '../mock_data'; } from '../mock_data';
const stageData = { events: [] }; const stageData = { events: [] };
...@@ -30,14 +31,6 @@ const flashErrorMessage = 'There was an error while fetching value stream analyt ...@@ -30,14 +31,6 @@ const flashErrorMessage = 'There was an error while fetching value stream analyt
const selectedGroup = { fullPath: group.path }; const selectedGroup = { fullPath: group.path };
const [selectedStage] = stages; const [selectedStage] = stages;
const selectedStageSlug = selectedStage.slug; const selectedStageSlug = selectedStage.slug;
const endpoints = {
groupLabels: `/groups/${group.path}/-/labels`,
summaryData: '/analytics/value_stream_analytics/summary',
durationData: /analytics\/value_stream_analytics\/stages\/\d+\/duration_chart/,
stageData: /analytics\/value_stream_analytics\/stages\/\d+\/records/,
stageMedian: /analytics\/value_stream_analytics\/stages\/\d+\/median/,
baseStagesEndpoint: '/analytics/value_stream_analytics/stages',
};
const stageEndpoint = ({ stageId }) => `/-/analytics/value_stream_analytics/stages/${stageId}`; const stageEndpoint = ({ stageId }) => `/-/analytics/value_stream_analytics/stages/${stageId}`;
...@@ -253,8 +246,8 @@ describe('Cycle analytics actions', () => { ...@@ -253,8 +246,8 @@ describe('Cycle analytics actions', () => {
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="flash-container"></div>'); setFixtures('<div class="flash-container"></div>');
}); });
it(`commits the ${types.RECEIVE_STAGE_DATA_ERROR} mutation`, done => { it(`commits the ${types.RECEIVE_STAGE_DATA_ERROR} mutation`, () => {
testAction( return testAction(
actions.receiveStageDataError, actions.receiveStageDataError,
null, null,
state, state,
...@@ -264,7 +257,6 @@ describe('Cycle analytics actions', () => { ...@@ -264,7 +257,6 @@ describe('Cycle analytics actions', () => {
}, },
], ],
[], [],
done,
); );
}); });
...@@ -278,13 +270,15 @@ describe('Cycle analytics actions', () => { ...@@ -278,13 +270,15 @@ describe('Cycle analytics actions', () => {
}); });
describe('fetchGroupLabels', () => { describe('fetchGroupLabels', () => {
describe('succeeds', () => {
beforeEach(() => { beforeEach(() => {
state = { ...state, selectedGroup }; gon.api_version = 'v4';
state = { selectedGroup };
mock.onGet(endpoints.groupLabels).replyOnce(200, groupLabels); mock.onGet(endpoints.groupLabels).replyOnce(200, groupLabels);
}); });
it('dispatches receiveGroupLabels if the request succeeds', done => { it('dispatches receiveGroupLabels if the request succeeds', () => {
testAction( return testAction(
actions.fetchGroupLabels, actions.fetchGroupLabels,
null, null,
state, state,
...@@ -296,15 +290,21 @@ describe('Cycle analytics actions', () => { ...@@ -296,15 +290,21 @@ describe('Cycle analytics actions', () => {
payload: groupLabels, payload: groupLabels,
}, },
], ],
done,
); );
}); });
});
it('dispatches receiveGroupLabelsError if the request fails', done => { describe('with an error', () => {
testAction( beforeEach(() => {
state = { selectedGroup };
mock.onGet(endpoints.groupLabels).replyOnce(404);
});
it('dispatches receiveGroupLabelsError if the request fails', () => {
return testAction(
actions.fetchGroupLabels, actions.fetchGroupLabels,
null, null,
{ ...state, selectedGroup: { fullPath: null } }, state,
[], [],
[ [
{ type: 'requestGroupLabels' }, { type: 'requestGroupLabels' },
...@@ -313,14 +313,15 @@ describe('Cycle analytics actions', () => { ...@@ -313,14 +313,15 @@ describe('Cycle analytics actions', () => {
payload: error, payload: error,
}, },
], ],
done,
); );
}); });
});
describe('receiveGroupLabelsError', () => { describe('receiveGroupLabelsError', () => {
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="flash-container"></div>'); setFixtures('<div class="flash-container"></div>');
}); });
it('flashes an error message if the request fails', () => { it('flashes an error message if the request fails', () => {
actions.receiveGroupLabelsError({ actions.receiveGroupLabelsError({
commit: () => {}, commit: () => {},
......
...@@ -543,6 +543,22 @@ describe('Api', () => { ...@@ -543,6 +543,22 @@ describe('Api', () => {
.catch(done.fail); .catch(done.fail);
}); });
}); });
describe('cycleAnalyticsGroupLabels', () => {
it('fetches group level labels', done => {
const response = [];
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/labels`;
mock.onGet(expectedUrl).reply(200, response);
Api.cycleAnalyticsGroupLabels(groupId)
.then(({ data, config: { url } }) => {
expect(data).toEqual(response);
expect(url).toEqual(expectedUrl);
})
.then(done)
.catch(done.fail);
});
});
}); });
describe('GeoDesigns', () => { describe('GeoDesigns', () => {
......
...@@ -41,6 +41,23 @@ describe 'Labels (JavaScript fixtures)' do ...@@ -41,6 +41,23 @@ describe 'Labels (JavaScript fixtures)' do
end end
end end
describe API::Helpers::LabelHelpers, type: :request do
include JavaScriptFixturesHelpers
include ApiHelpers
let(:user) { create(:user) }
before do
group.add_owner(user)
end
it 'api/group_labels.json' do
get api("/groups/#{group.id}/labels", user)
expect(response).to be_successful
end
end
describe Projects::LabelsController, '(JavaScript fixtures)', type: :controller do describe Projects::LabelsController, '(JavaScript fixtures)', type: :controller do
render_views render_views
......
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