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