Commit 9e46d9e3 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo

Minor refactor metrics requests

Refactors the metrics requests into a single
request and updates specs
parent ba6a0972
...@@ -7,6 +7,9 @@ const PROJECT_VSA_PATH_BASE = '/:request_path/-/analytics/value_stream_analytics ...@@ -7,6 +7,9 @@ const PROJECT_VSA_PATH_BASE = '/:request_path/-/analytics/value_stream_analytics
const PROJECT_VSA_STAGES_PATH = `${PROJECT_VSA_PATH_BASE}/:value_stream_id/stages`; const PROJECT_VSA_STAGES_PATH = `${PROJECT_VSA_PATH_BASE}/:value_stream_id/stages`;
const PROJECT_VSA_STAGE_DATA_PATH = `${PROJECT_VSA_STAGES_PATH}/:stage_id`; const PROJECT_VSA_STAGE_DATA_PATH = `${PROJECT_VSA_STAGES_PATH}/:stage_id`;
export const METRIC_TYPE_SUMMARY = 'summary';
export const METRIC_TYPE_TIME_SUMMARY = 'time_summary';
const buildProjectMetricsPath = (requestPath) => const buildProjectMetricsPath = (requestPath) =>
buildApiUrl(PROJECT_VSA_METRICS_BASE).replace(':request_path', requestPath); buildApiUrl(PROJECT_VSA_METRICS_BASE).replace(':request_path', requestPath);
...@@ -65,9 +68,13 @@ export const getValueStreamStageCounts = ({ requestPath, valueStreamId, stageId ...@@ -65,9 +68,13 @@ export const getValueStreamStageCounts = ({ requestPath, valueStreamId, stageId
return axios.get(joinPaths(stageBase, 'count'), { params }); return axios.get(joinPaths(stageBase, 'count'), { params });
}; };
export const getValueStreamTimeSummaryMetrics = (requestPath, params = {}) => { export const getValueStreamMetrics = ({
endpoint = METRIC_TYPE_SUMMARY,
requestPath,
params = {},
}) => {
const metricBase = buildProjectMetricsPath(requestPath); const metricBase = buildProjectMetricsPath(requestPath);
return axios.get(joinPaths(metricBase, 'time_summary'), { params }); return axios.get(joinPaths(metricBase, endpoint), { params });
}; };
export const getValueStreamSummaryMetrics = (requestPath, params = {}) => { export const getValueStreamSummaryMetrics = (requestPath, params = {}) => {
......
...@@ -7,8 +7,8 @@ import { sprintf, s__ } from '~/locale'; ...@@ -7,8 +7,8 @@ import { sprintf, s__ } from '~/locale';
import { METRICS_POPOVER_CONTENT } from '../constants'; import { METRICS_POPOVER_CONTENT } from '../constants';
import { removeFlash, prepareTimeMetricsData } from '../utils'; import { removeFlash, prepareTimeMetricsData } from '../utils';
const requestData = ({ request, path, params, name }) => { const requestData = ({ request, endpoint, path, params, name }) => {
return request(path, params) return request({ endpoint, params, requestPath: path })
.then(({ data }) => data) .then(({ data }) => data)
.catch(() => { .catch(() => {
const message = sprintf( const message = sprintf(
......
import { import {
getValueStreamTimeSummaryMetrics, getValueStreamMetrics,
getValueStreamSummaryMetrics, METRIC_TYPE_SUMMARY,
METRIC_TYPE_TIME_SUMMARY,
} from '~/api/analytics_api'; } from '~/api/analytics_api';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
...@@ -61,10 +62,10 @@ export const METRICS_POPOVER_CONTENT = { ...@@ -61,10 +62,10 @@ export const METRICS_POPOVER_CONTENT = {
}; };
export const SUMMARY_METRICS_REQUEST = [ export const SUMMARY_METRICS_REQUEST = [
{ request: getValueStreamSummaryMetrics, name: __('recent activity') }, { endpoint: METRIC_TYPE_SUMMARY, name: __('recent activity'), request: getValueStreamMetrics },
]; ];
export const METRICS_REQUESTS = [ export const METRICS_REQUESTS = [
{ request: getValueStreamTimeSummaryMetrics, name: __('time summary') }, { endpoint: METRIC_TYPE_TIME_SUMMARY, name: __('time summary'), request: getValueStreamMetrics },
...SUMMARY_METRICS_REQUEST, ...SUMMARY_METRICS_REQUEST,
]; ];
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { DEFAULT_DAYS_TO_DISPLAY } from '../constants'; import { DEFAULT_DAYS_TO_DISPLAY } from '../constants';
import { decorateData, formatMedianValues, calculateFormattedDayInPast } from '../utils'; import { formatMedianValues, calculateFormattedDayInPast } from '../utils';
import * as types from './mutation_types'; import * as types from './mutation_types';
export default { export default {
...@@ -49,9 +49,7 @@ export default { ...@@ -49,9 +49,7 @@ export default {
state.hasError = false; state.hasError = false;
}, },
[types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, data) { [types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS](state, data) {
const { summary } = decorateData(data);
state.permissions = data?.permissions || {}; state.permissions = data?.permissions || {};
state.summary = summary;
state.hasError = false; state.hasError = false;
}, },
[types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR](state) { [types.RECEIVE_CYCLE_ANALYTICS_DATA_ERROR](state) {
......
...@@ -16,15 +16,6 @@ export const removeFlash = (type = 'alert') => { ...@@ -16,15 +16,6 @@ export const removeFlash = (type = 'alert') => {
} }
}; };
const mapToSummary = ({ value, ...rest }) => ({ ...rest, value: value || '-' });
export const decorateData = (data = {}) => {
const { summary } = data;
return {
summary: summary?.map((item) => mapToSummary(item)) || [],
};
};
/** /**
* Takes the stages and median data, combined with the selected stage, to build an * Takes the stages and median data, combined with the selected stage, to build an
* array which is formatted to proivde the data required for the path navigation. * array which is formatted to proivde the data required for the path navigation.
......
import { import { getGroupValueStreamMetrics } from 'ee/api/analytics_api';
getGroupValueStreamSummaryData, import { METRIC_TYPE_SUMMARY, METRIC_TYPE_TIME_SUMMARY } from '~/api/analytics_api';
getGroupValueStreamTimeSummaryData,
} from 'ee/api/analytics_api';
import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants'; import { OVERVIEW_STAGE_ID } from '~/cycle_analytics/constants';
import { __ } from '~/locale'; import { __ } from '~/locale';
...@@ -34,11 +32,13 @@ export const OVERVIEW_STAGE_CONFIG = { ...@@ -34,11 +32,13 @@ export const OVERVIEW_STAGE_CONFIG = {
export const METRICS_REQUESTS = [ export const METRICS_REQUESTS = [
{ {
request: getGroupValueStreamTimeSummaryData, endpoint: METRIC_TYPE_TIME_SUMMARY,
request: getGroupValueStreamMetrics,
name: __('time summary'), name: __('time summary'),
}, },
{ {
request: getGroupValueStreamSummaryData, endpoint: METRIC_TYPE_SUMMARY,
request: getGroupValueStreamMetrics,
name: __('recent activity'), name: __('recent activity'),
}, },
]; ];
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import { buildApiUrl } from '~/api/api_utils'; import { buildApiUrl } from '~/api/api_utils';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { joinPaths } from '~/lib/utils/url_utility'; import { joinPaths } from '~/lib/utils/url_utility';
...@@ -22,8 +23,8 @@ export const getGroupValueStreamStageMedian = ( ...@@ -22,8 +23,8 @@ export const getGroupValueStreamStageMedian = (
return axios.get(`${stageBase}/median`, { params }); return axios.get(`${stageBase}/median`, { params });
}; };
export const getGroupValueStreamSummaryData = (groupId, params = {}) => export const getGroupValueStreamMetrics = ({
axios.get(joinPaths(buildGroupValueStreamPath({ groupId }), 'summary'), { params }); endpoint = METRIC_TYPE_SUMMARY,
requestPath: groupId,
export const getGroupValueStreamTimeSummaryData = (groupId, params = {}) => params = {},
axios.get(joinPaths(buildGroupValueStreamPath({ groupId }), 'time_summary'), { params }); }) => axios.get(joinPaths(buildGroupValueStreamPath({ groupId }), endpoint), { params });
...@@ -6,8 +6,6 @@ import { ...@@ -6,8 +6,6 @@ import {
selectedStage, selectedStage,
rawIssueEvents, rawIssueEvents,
issueEvents, issueEvents,
rawData,
convertedData,
selectedValueStream, selectedValueStream,
rawValueStreamStages, rawValueStreamStages,
valueStreamStages, valueStreamStages,
...@@ -90,18 +88,17 @@ describe('Project Value Stream Analytics mutations', () => { ...@@ -90,18 +88,17 @@ describe('Project Value Stream Analytics mutations', () => {
}); });
it.each` it.each`
mutation | payload | stateKey | value mutation | payload | stateKey | value
${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'daysInPast'} | ${DEFAULT_DAYS_TO_DISPLAY} ${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'daysInPast'} | ${DEFAULT_DAYS_TO_DISPLAY}
${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'createdAfter'} | ${mockCreatedAfter} ${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'createdAfter'} | ${mockCreatedAfter}
${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'createdBefore'} | ${mockCreatedBefore} ${types.SET_DATE_RANGE} | ${DEFAULT_DAYS_TO_DISPLAY} | ${'createdBefore'} | ${mockCreatedBefore}
${types.SET_LOADING} | ${true} | ${'isLoading'} | ${true} ${types.SET_LOADING} | ${true} | ${'isLoading'} | ${true}
${types.SET_LOADING} | ${false} | ${'isLoading'} | ${false} ${types.SET_LOADING} | ${false} | ${'isLoading'} | ${false}
${types.SET_SELECTED_VALUE_STREAM} | ${selectedValueStream} | ${'selectedValueStream'} | ${selectedValueStream} ${types.SET_SELECTED_VALUE_STREAM} | ${selectedValueStream} | ${'selectedValueStream'} | ${selectedValueStream}
${types.RECEIVE_CYCLE_ANALYTICS_DATA_SUCCESS} | ${rawData} | ${'summary'} | ${convertedData.summary} ${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${[selectedValueStream]} | ${'valueStreams'} | ${[selectedValueStream]}
${types.RECEIVE_VALUE_STREAMS_SUCCESS} | ${[selectedValueStream]} | ${'valueStreams'} | ${[selectedValueStream]} ${types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS} | ${{ stages: rawValueStreamStages }} | ${'stages'} | ${valueStreamStages}
${types.RECEIVE_VALUE_STREAM_STAGES_SUCCESS} | ${{ stages: rawValueStreamStages }} | ${'stages'} | ${valueStreamStages} ${types.RECEIVE_STAGE_MEDIANS_SUCCESS} | ${rawStageMedians} | ${'medians'} | ${formattedStageMedians}
${types.RECEIVE_STAGE_MEDIANS_SUCCESS} | ${rawStageMedians} | ${'medians'} | ${formattedStageMedians} ${types.RECEIVE_STAGE_COUNTS_SUCCESS} | ${rawStageCounts} | ${'stageCounts'} | ${stageCounts}
${types.RECEIVE_STAGE_COUNTS_SUCCESS} | ${rawStageCounts} | ${'stageCounts'} | ${stageCounts}
`( `(
'$mutation with $payload will set $stateKey to $value', '$mutation with $payload will set $stateKey to $value',
({ mutation, payload, stateKey, value }) => { ({ mutation, payload, stateKey, value }) => {
......
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import { import {
decorateData,
transformStagesForPathNavigation, transformStagesForPathNavigation,
timeSummaryForPathNavigation, timeSummaryForPathNavigation,
medianTimeToParsedSeconds, medianTimeToParsedSeconds,
...@@ -12,8 +11,6 @@ import { ...@@ -12,8 +11,6 @@ import {
import { slugify } from '~/lib/utils/text_utility'; import { slugify } from '~/lib/utils/text_utility';
import { import {
selectedStage, selectedStage,
rawData,
convertedData,
allowedStages, allowedStages,
stageMedians, stageMedians,
pathNavIssueMetric, pathNavIssueMetric,
...@@ -22,22 +19,6 @@ import { ...@@ -22,22 +19,6 @@ import {
} from './mock_data'; } from './mock_data';
describe('Value stream analytics utils', () => { describe('Value stream analytics utils', () => {
describe('decorateData', () => {
const result = decorateData(rawData);
it('returns the summary data', () => {
expect(result.summary).toEqual(convertedData.summary);
});
it('returns `-` for summary data that has no value', () => {
const singleSummaryResult = decorateData({
stats: [],
permissions: { issue: true },
summary: [{ value: null, title: 'Commits' }],
});
expect(singleSummaryResult.summary).toEqual([{ value: '-', title: 'Commits' }]);
});
});
describe('transformStagesForPathNavigation', () => { describe('transformStagesForPathNavigation', () => {
const stages = allowedStages; const stages = allowedStages;
const response = transformStagesForPathNavigation({ const response = transformStagesForPathNavigation({
......
...@@ -2,6 +2,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; ...@@ -2,6 +2,7 @@ import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts'; import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { METRIC_TYPE_SUMMARY } from '~/api/analytics_api';
import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue'; import ValueStreamMetrics from '~/cycle_analytics/components/value_stream_metrics.vue';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { group, metricsData } from './mock_data'; import { group, metricsData } from './mock_data';
...@@ -14,84 +15,97 @@ describe('ValueStreamMetrics', () => { ...@@ -14,84 +15,97 @@ describe('ValueStreamMetrics', () => {
const { full_path: requestPath } = group; const { full_path: requestPath } = group;
const fakeReqName = 'Mock metrics'; const fakeReqName = 'Mock metrics';
const metricsRequestFactory = () => ({
request: mockGetValueStreamSummaryMetrics,
endpoint: METRIC_TYPE_SUMMARY,
name: fakeReqName,
});
const createComponent = ({ requestParams = {} } = {}) => { const createComponent = ({ requestParams = {} } = {}) => {
return shallowMount(ValueStreamMetrics, { return shallowMount(ValueStreamMetrics, {
propsData: { propsData: {
requestPath, requestPath,
requestParams, requestParams,
requests: [{ request: mockGetValueStreamSummaryMetrics, name: fakeReqName }], requests: [metricsRequestFactory()],
}, },
}); });
}; };
const findMetrics = () => wrapper.findAllComponents(GlSingleStat); const findMetrics = () => wrapper.findAllComponents(GlSingleStat);
const expectToHaveRequest = (fields) => {
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith({
endpoint: METRIC_TYPE_SUMMARY,
requestPath,
...fields,
});
};
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
}); });
it('will display a loader with pending requests', async () => {
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
wrapper = createComponent();
await wrapper.vm.$nextTick();
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
});
describe('with successful requests', () => { describe('with successful requests', () => {
beforeEach(async () => { beforeEach(() => {
mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData }); mockGetValueStreamSummaryMetrics = jest.fn().mockResolvedValue({ data: metricsData });
wrapper = createComponent(); wrapper = createComponent();
await waitForPromises();
}); });
it('fetches data for the `getValueStreamSummaryMetrics` request', () => { it('will display a loader with pending requests', async () => {
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith(requestPath, {}); await wrapper.vm.$nextTick();
});
it.each` expect(wrapper.find(GlSkeletonLoading).exists()).toBe(true);
index | value | title | unit
${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit}
${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit}
${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit}
${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit}
`(
'renders a single stat component for the $title with value and unit',
({ index, value, title, unit }) => {
const metric = findMetrics().at(index);
const expectedUnit = unit ?? '';
expect(metric.props('value')).toBe(value);
expect(metric.props('title')).toBe(title);
expect(metric.props('unit')).toBe(expectedUnit);
},
);
it('will not display a loading icon', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
}); });
describe('with additional params', () => { describe('with data loaded', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper = createComponent({
requestParams: {
'project_ids[]': [1],
created_after: '2020-01-01',
created_before: '2020-02-01',
},
});
await waitForPromises(); await waitForPromises();
}); });
it('fetches data for the `getValueStreamSummaryMetrics` request', () => { it('fetches data from the value stream analytics endpoint', () => {
expect(mockGetValueStreamSummaryMetrics).toHaveBeenCalledWith(requestPath, { expectToHaveRequest({ params: {} });
'project_ids[]': [1], });
created_after: '2020-01-01',
created_before: '2020-02-01', it.each`
index | value | title | unit
${0} | ${metricsData[0].value} | ${metricsData[0].title} | ${metricsData[0].unit}
${1} | ${metricsData[1].value} | ${metricsData[1].title} | ${metricsData[1].unit}
${2} | ${metricsData[2].value} | ${metricsData[2].title} | ${metricsData[2].unit}
${3} | ${metricsData[3].value} | ${metricsData[3].title} | ${metricsData[3].unit}
`(
'renders a single stat component for the $title with value and unit',
({ index, value, title, unit }) => {
const metric = findMetrics().at(index);
expect(metric.props()).toMatchObject({ value, title, unit: unit ?? '' });
},
);
it('will not display a loading icon', () => {
expect(wrapper.find(GlSkeletonLoading).exists()).toBe(false);
});
describe('with additional params', () => {
beforeEach(async () => {
wrapper = createComponent({
requestParams: {
'project_ids[]': [1],
created_after: '2020-01-01',
created_before: '2020-02-01',
},
});
await waitForPromises();
});
it('fetches data for the `getValueStreamSummaryMetrics` request', () => {
expectToHaveRequest({
params: {
'project_ids[]': [1],
created_after: '2020-01-01',
created_before: '2020-02-01',
},
});
}); });
}); });
}); });
......
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