Commit 8ac18fbb authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch 'nfriend-update-lead-time-graphs-to-support-group-level-data' into 'master'

Update lead time graphs to support group-level data

See merge request gitlab-org/gitlab!62925
parents c2aed6e4 c3ea049e
......@@ -28,6 +28,10 @@ export default {
type: String,
default: '',
},
groupPath: {
type: String,
default: '',
},
},
data() {
return {
......@@ -49,13 +53,23 @@ export default {
},
},
async mounted() {
if (!this.checkProvidedPaths()) {
return;
}
const results = await Promise.allSettled(
allChartDefinitions.map(async ({ id, requestParams, startDate, endDate }) => {
const { data: apiData } = await DoraApi.getProjectDoraMetrics(
this.projectPath,
DoraApi.LEAD_TIME_FOR_CHANGES,
requestParams,
);
const { data: apiData } = this.projectPath
? await DoraApi.getProjectDoraMetrics(
this.projectPath,
DoraApi.LEAD_TIME_FOR_CHANGES,
requestParams,
)
: await DoraApi.getGroupDoraMetrics(
this.groupPath,
DoraApi.LEAD_TIME_FOR_CHANGES,
requestParams,
);
this.chartData[id] = buildNullSeriesForLeadTimeChart(
apiDataToChartSeries(apiData, startDate, endDate, CHART_TITLE, null),
......@@ -69,7 +83,7 @@ export default {
const allErrorMessages = requestErrors.join('\n');
createFlash({
message: s__('DORA4Metrics|Something went wrong while getting lead time data.'),
message: this.$options.i18n.flashMessage,
error: new Error(`Something went wrong while getting lead time data:\n${allErrorMessages}`),
captureError: true,
});
......@@ -82,16 +96,52 @@ export default {
this.tooltipValue = seconds != null ? humanizeTimeInterval(seconds) : null;
},
/**
* Validates that exactly one of [this.projectPath, this.groupPath] has been
* provided to this component. If not, a flash message is shown and an error
* is logged with Sentry. This is mainly intended to be a development aid.
* @returns {Boolean} Whether or not the paths are valid
*/
checkProvidedPaths() {
let errorMessage = '';
if (this.projectPath && this.groupPath) {
errorMessage = 'Both projectPath and groupPath were provided';
}
if (!this.projectPath && !this.groupPath) {
errorMessage = 'Either projectPath or groupPath must be provided';
}
if (errorMessage) {
createFlash({
message: this.$options.i18n.flashMessage,
error: new Error(`Error while rendering lead time charts: ${errorMessage}`),
captureError: true,
});
return false;
}
return true;
},
},
areaChartOptions,
chartDescriptionText,
chartDocumentationHref,
i18n: {
flashMessage: s__('DORA4Metrics|Something went wrong while getting lead time data.'),
chartHeaderText: s__('DORA4Metrics|Lead time'),
medianLeadTime: s__('DORA4Metrics|Median lead time'),
noMergeRequestsDeployed: s__('DORA4Metrics|No merge requests were deployed during this period'),
},
};
</script>
<template>
<div>
<dora-chart-header
:header-text="s__('DORA4Metrics|Lead time')"
:header-text="$options.i18n.chartHeaderText"
:chart-description-text="$options.chartDescriptionText"
:chart-documentation-href="$options.chartDocumentationHref"
/>
......@@ -108,10 +158,10 @@ export default {
<template #tooltip-title> {{ tooltipTitle }} </template>
<template #tooltip-content>
<template v-if="tooltipValue === null">
{{ s__('DORA4Metrics|No merge requests were deployed during this period') }}
{{ $options.i18n.noMergeRequestsDeployed }}
</template>
<div v-else class="gl-display-flex gl-align-items-flex-end">
<div class="gl-mr-5">{{ s__('DORA4Metrics|Median lead time') }}</div>
<div class="gl-mr-5">{{ $options.i18n.medianLeadTime }}</div>
<div class="gl-font-weight-bold" data-testid="tooltip-value">{{ tooltipValue }}</div>
</div>
</template>
......
......@@ -34,14 +34,15 @@ describe('lead_time_charts.vue', () => {
let wrapper;
let mock;
const defaultMountOptions = {
provide: {
projectPath: 'test/project',
},
stubs: { GlSprintf },
};
const createComponent = (mountFn = shallowMount) => {
wrapper = mountFn(LeadTimeCharts, {
provide: {
projectPath: 'test/project',
},
stubs: { GlSprintf },
});
const createComponent = ({ mountFn = shallowMount, mountOptions = defaultMountOptions } = {}) => {
wrapper = mountFn(LeadTimeCharts, mountOptions);
};
// Initializes the mock endpoint to return a specific set of lead time data for a given "from" date.
......@@ -104,7 +105,7 @@ describe('lead_time_charts.vue', () => {
describe('methods', () => {
describe('formatTooltipText', () => {
it('displays a humanized version of the time interval in the tooltip', async () => {
createComponent(mount);
createComponent({ mountFn: mount });
await axios.waitForAll();
......@@ -143,4 +144,94 @@ describe('lead_time_charts.vue', () => {
]);
});
});
describe('group/project behavior', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet(/projects\/test%2Fproject\/dora\/metrics/).reply(httpStatus.OK, lastWeekData);
mock.onGet(/groups\/test%2Fgroup\/dora\/metrics/).reply(httpStatus.OK, lastWeekData);
});
describe('when projectPath is provided', () => {
beforeEach(async () => {
createComponent({
mountOptions: {
provide: {
projectPath: 'test/project',
},
},
});
await axios.waitForAll();
});
it('makes a call to the project API endpoint', () => {
expect(mock.history.get.length).toBe(3);
expect(mock.history.get[0].url).toMatch('/projects/test%2Fproject/dora/metrics');
});
it('does not throw an error', () => {
expect(createFlash).not.toHaveBeenCalled();
});
});
describe('when groupPath is provided', () => {
beforeEach(async () => {
createComponent({
mountOptions: {
provide: {
groupPath: 'test/group',
},
},
});
await axios.waitForAll();
});
it('makes a call to the group API endpoint', () => {
expect(mock.history.get.length).toBe(3);
expect(mock.history.get[0].url).toMatch('/groups/test%2Fgroup/dora/metrics');
});
it('does not throw an error', () => {
expect(createFlash).not.toHaveBeenCalled();
});
});
describe('when both projectPath and groupPath are provided', () => {
beforeEach(async () => {
createComponent({
mountOptions: {
provide: {
projectPath: 'test/project',
groupPath: 'test/group',
},
},
});
await axios.waitForAll();
});
it('throws an error (which shows a flash message)', () => {
expect(createFlash).toHaveBeenCalled();
});
});
describe('when neither projectPath nor groupPath are provided', () => {
beforeEach(async () => {
createComponent({
mountOptions: {
provide: {},
},
});
await axios.waitForAll();
});
it('throws an error (which shows a flash message)', () => {
expect(createFlash).toHaveBeenCalled();
});
});
});
});
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