Commit f9043c65 authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Nicolò Maria Mezzopera

Add tooltip helper text to cycle metrics

Adds a hoverable tooltip icon to the time
metrics for VSA, adding an explanation of
how each metric is calculated.

Minor refactor of the metric_card

Minor cleanup some utility css
parent 09b48cbc
<script> <script>
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective, GlIcon } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
name: 'StageTableHeader', name: 'StageTableHeader',
components: { components: {
Icon, GlIcon,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -31,10 +30,10 @@ export default { ...@@ -31,10 +30,10 @@ export default {
<template> <template>
<li :class="headerClasses"> <li :class="headerClasses">
<span class="stage-name align-middle font-weight-bold">{{ title }}</span> <span class="stage-name gl-font-weight-bold">{{ title }}</span>
<icon <gl-icon
v-gl-tooltip v-gl-tooltip
class="align-middle" class="gl-vertical-align-middle"
:size="14" :size="14"
name="question" name="question"
container=".stage-name" container=".stage-name"
......
<script> <script>
import Api from 'ee/api'; import Api from 'ee/api';
import { __ } from '~/locale'; import { __, s__ } from '~/locale';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { slugify } from '~/lib/utils/text_utility'; import { slugify } from '~/lib/utils/text_utility';
import MetricCard from '../../shared/components/metric_card.vue'; import MetricCard from '../../shared/components/metric_card.vue';
import { removeFlash } from '../utils'; import { removeFlash } from '../utils';
const I18N_TEXT = {
'lead-time': s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
'cycle-time': s__('ValueStreamAnalytics|Median time from first commit to issue closed.'),
};
export default { export default {
name: 'TimeMetricsCard', name: 'TimeMetricsCard',
components: { components: {
...@@ -42,11 +47,15 @@ export default { ...@@ -42,11 +47,15 @@ export default {
this.loading = true; this.loading = true;
return Api.cycleAnalyticsTimeSummaryData(this.groupPath, this.additionalParams) return Api.cycleAnalyticsTimeSummaryData(this.groupPath, this.additionalParams)
.then(({ data }) => { .then(({ data }) => {
this.data = data.map(({ title: label, ...rest }) => ({ this.data = data.map(({ title: label, ...rest }) => {
...rest, const key = slugify(label);
label, return {
key: slugify(label), ...rest,
})); label,
key,
tooltipText: I18N_TEXT[key] || '',
};
});
}) })
.catch(() => { .catch(() => {
createFlash( createFlash(
......
<script> <script>
import { GlCard, GlSkeletonLoading, GlLink } from '@gitlab/ui'; import { GlCard, GlSkeletonLoading, GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui';
export default { export default {
name: 'MetricCard', name: 'MetricCard',
...@@ -7,6 +7,10 @@ export default { ...@@ -7,6 +7,10 @@ export default {
GlCard, GlCard,
GlSkeletonLoading, GlSkeletonLoading,
GlLink, GlLink,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
}, },
props: { props: {
title: { title: {
...@@ -38,19 +42,29 @@ export default { ...@@ -38,19 +42,29 @@ export default {
<strong ref="title">{{ title }}</strong> <strong ref="title">{{ title }}</strong>
</template> </template>
<template #default> <template #default>
<gl-skeleton-loading v-if="isLoading" class="h-auto py-3" /> <gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3" />
<div v-else ref="metricsWrapper" class="d-flex"> <div v-else ref="metricsWrapper" class="gl-display-flex">
<div <div
v-for="metric in metrics" v-for="{ tooltipText = '', ...metric } in metrics"
:key="metric.key" :key="metric.key"
ref="metricItem" ref="metricItem"
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<gl-link v-if="metric.link" :href="metric.link"> <gl-link v-if="metric.link" :href="metric.link">
<h3 class="gl-my-2 gl-text-blue-700">{{ valueText(metric) }}</h3> <h3 class="gl-my-2 gl-text-blue-700">{{ valueText(metric) }}</h3>
</gl-link> </gl-link>
<h3 v-else class="gl-my-2">{{ valueText(metric) }}</h3> <h3 v-else class="gl-my-2">{{ valueText(metric) }}</h3>
<p class="text-secondary gl-font-sm mb-2">{{ metric.label }}</p> <p class="text-secondary gl-font-sm gl-mb-2">
{{ metric.label }}
<span v-if="tooltipText.length"
>&nbsp;<gl-icon
v-gl-tooltip="{ title: tooltipText }"
:size="14"
class="gl-vertical-align-middle"
name="question"
data-testid="tooltip"
/></span>
</p>
</div> </div>
</div> </div>
</template> </template>
......
---
title: Add info icons to explain lead / cycle time calculations
merge_request: 37903
author:
type: added
...@@ -19,10 +19,10 @@ exports[`RecentActivityCard matches the snapshot 1`] = ` ...@@ -19,10 +19,10 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
<!----> <!---->
<div <div
class="d-flex" class="gl-display-flex"
> >
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<h3 <h3
class="gl-my-2" class="gl-my-2"
...@@ -31,13 +31,16 @@ exports[`RecentActivityCard matches the snapshot 1`] = ` ...@@ -31,13 +31,16 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
</h3> </h3>
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm gl-mb-2"
> >
New Issues New Issues
<!---->
</p> </p>
</div> </div>
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<h3 <h3
class="gl-my-2" class="gl-my-2"
...@@ -46,13 +49,16 @@ exports[`RecentActivityCard matches the snapshot 1`] = ` ...@@ -46,13 +49,16 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
</h3> </h3>
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm gl-mb-2"
> >
Deploys Deploys
<!---->
</p> </p>
</div> </div>
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<h3 <h3
class="gl-my-2" class="gl-my-2"
...@@ -61,9 +67,12 @@ exports[`RecentActivityCard matches the snapshot 1`] = ` ...@@ -61,9 +67,12 @@ exports[`RecentActivityCard matches the snapshot 1`] = `
</h3> </h3>
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm gl-mb-2"
> >
Deployment Frequency Deployment Frequency
<!---->
</p> </p>
</div> </div>
</div> </div>
......
...@@ -19,10 +19,10 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -19,10 +19,10 @@ exports[`GroupActivity component matches the snapshot 1`] = `
<!----> <!---->
<div <div
class="d-flex" class="gl-display-flex"
> >
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<h3 <h3
class="gl-my-2" class="gl-my-2"
...@@ -31,13 +31,16 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -31,13 +31,16 @@ exports[`GroupActivity component matches the snapshot 1`] = `
</h3> </h3>
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm gl-mb-2"
> >
Merge Requests opened Merge Requests opened
<!---->
</p> </p>
</div> </div>
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<h3 <h3
class="gl-my-2" class="gl-my-2"
...@@ -46,13 +49,16 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -46,13 +49,16 @@ exports[`GroupActivity component matches the snapshot 1`] = `
</h3> </h3>
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm gl-mb-2"
> >
Issues opened Issues opened
<!---->
</p> </p>
</div> </div>
<div <div
class="js-metric-card-item flex-grow text-center" class="js-metric-card-item gl-flex-grow-1 gl-text-center"
> >
<h3 <h3
class="gl-my-2" class="gl-my-2"
...@@ -61,9 +67,12 @@ exports[`GroupActivity component matches the snapshot 1`] = ` ...@@ -61,9 +67,12 @@ exports[`GroupActivity component matches the snapshot 1`] = `
</h3> </h3>
<p <p
class="text-secondary gl-font-sm mb-2" class="text-secondary gl-font-sm gl-mb-2"
> >
Members added Members added
<!---->
</p> </p>
</div> </div>
</div> </div>
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { GlSkeletonLoading } from '@gitlab/ui'; import { GlSkeletonLoading } from '@gitlab/ui';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MetricCard from 'ee/analytics/shared/components/metric_card.vue'; import MetricCard from 'ee/analytics/shared/components/metric_card.vue';
const metrics = [ const metrics = [
...@@ -24,6 +25,9 @@ describe('MetricCard', () => { ...@@ -24,6 +25,9 @@ describe('MetricCard', () => {
...defaultProps, ...defaultProps,
...props, ...props,
}, },
directives: {
GlTooltip: createMockDirective(),
},
}); });
}; };
...@@ -35,6 +39,7 @@ describe('MetricCard', () => { ...@@ -35,6 +39,7 @@ describe('MetricCard', () => {
const findLoadingIndicator = () => wrapper.find(GlSkeletonLoading); const findLoadingIndicator = () => wrapper.find(GlSkeletonLoading);
const findMetricsWrapper = () => wrapper.find({ ref: 'metricsWrapper' }); const findMetricsWrapper = () => wrapper.find({ ref: 'metricsWrapper' });
const findMetricItem = () => wrapper.findAll({ ref: 'metricItem' }); const findMetricItem = () => wrapper.findAll({ ref: 'metricItem' });
const findTooltip = () => wrapper.find('[data-testid="tooltip"]');
describe('template', () => { describe('template', () => {
it('renders the title', () => { it('renders the title', () => {
...@@ -74,6 +79,29 @@ describe('MetricCard', () => { ...@@ -74,6 +79,29 @@ describe('MetricCard', () => {
expect(findMetricItem()).toHaveLength(metrics.length); expect(findMetricItem()).toHaveLength(metrics.length);
}); });
describe('with tooltip text', () => {
const tooltipText = 'This is a tooltip';
const tooltipMetric = {
key: 'fifth_metric',
value: '-',
label: 'Metric with tooltip',
unit: 'parsecs',
tooltipText,
};
beforeEach(() => {
factory({
isLoading: false,
metrics: [tooltipMetric],
});
});
it('will render a tooltip', () => {
const tt = getBinding(findTooltip().element, 'gl-tooltip');
expect(tt.value.title).toEqual(tooltipText);
});
});
describe.each` describe.each`
columnIndex | label | value | unit | link columnIndex | label | value | unit | link
${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'} ${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'}
...@@ -84,8 +112,10 @@ describe('MetricCard', () => { ...@@ -84,8 +112,10 @@ describe('MetricCard', () => {
it(`renders ${value}${unit} ${label} with URL ${link}`, () => { it(`renders ${value}${unit} ${label} with URL ${link}`, () => {
const allMetricItems = findMetricItem(); const allMetricItems = findMetricItem();
const metricItem = allMetricItems.at(columnIndex); const metricItem = allMetricItems.at(columnIndex);
const text = metricItem.text();
expect(metricItem.text()).toBe(`${value}${unit} ${label}`); expect(text).toContain(`${value}${unit}`);
expect(text).toContain(label);
if (link) { if (link) {
expect(metricItem.find('a').attributes('href')).toBe(link); expect(metricItem.find('a').attributes('href')).toBe(link);
......
...@@ -26373,6 +26373,12 @@ msgstr "" ...@@ -26373,6 +26373,12 @@ msgstr ""
msgid "ValueStreamAnalytics|%{days}d" msgid "ValueStreamAnalytics|%{days}d"
msgstr "" msgstr ""
msgid "ValueStreamAnalytics|Median time from first commit to issue closed."
msgstr ""
msgid "ValueStreamAnalytics|Median time from issue created to issue closed."
msgstr ""
msgid "Variable" msgid "Variable"
msgstr "" msgstr ""
......
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