Commit 40b0ef7a authored by Martin Wortschack's avatar Martin Wortschack Committed by Brandon Labuschagne

Group Repository Analytics: Replace metric card with single stat

parent e2905b3a
<script>
import {
GlCard,
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlLink,
GlIcon,
GlTooltipDirective,
} from '@gitlab/ui';
export default {
name: 'MetricCard',
components: {
GlCard,
GlSkeletonLoading,
GlLink,
GlIcon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
title: {
type: String,
required: true,
},
metrics: {
type: Array,
required: true,
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
valueText(metric) {
const { value = null, unit = null } = metric;
if (!value || value === '-') return '-';
return unit && value ? `${value} ${unit}` : value;
},
},
};
</script>
<template>
<gl-card class="gl-mb-5">
<template #header>
<strong ref="title">{{ title }}</strong>
</template>
<template #default>
<gl-skeleton-loading v-if="isLoading" class="gl-h-auto gl-py-3" />
<div v-else ref="metricsWrapper" class="gl-display-flex">
<div
v-for="metric in metrics"
:key="metric.key"
ref="metricItem"
class="js-metric-card-item gl-flex-grow-1 gl-text-center"
>
<gl-link v-if="metric.link" :href="metric.link">
<h3 class="gl-my-2 gl-text-blue-700">{{ valueText(metric) }}</h3>
</gl-link>
<h3 v-else class="gl-my-2">{{ valueText(metric) }}</h3>
<p class="text-secondary gl-font-sm gl-mb-2">
{{ metric.label }}
<span v-if="metric.tooltipText">
&nbsp;
<gl-icon
v-gl-tooltip="{ title: metric.tooltipText }"
:size="14"
class="gl-vertical-align-middle"
name="question"
data-testid="tooltip"
/>
</span>
</p>
</div>
</div>
</template>
</gl-card>
</template>
<script>
import chartEmptyStateIllustration from '@gitlab/svgs/dist/illustrations/chart-empty-state.svg';
import { GlCard, GlSprintf, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { GlAreaChart } from '@gitlab/ui/dist/charts';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
import {
GlCard,
GlSprintf,
GlDeprecatedSkeletonLoading as GlSkeletonLoading,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import { GlSingleStat, GlAreaChart } from '@gitlab/ui/dist/charts';
import { formatDate } from '~/lib/utils/datetime_utility';
import { SUPPORTED_FORMATS, getFormatter } from '~/lib/utils/unit_format';
import { __, s__ } from '~/locale';
......@@ -18,7 +22,8 @@ export default {
GlAreaChart,
GlCard,
GlSprintf,
MetricCard,
GlSkeletonLoading,
GlSingleStat,
},
directives: {
SafeHtml,
......@@ -144,7 +149,6 @@ export default {
graphName: s__('RepositoriesAnalytics|Average coverage'),
graphTooltipMessage: __('Code Coverage: %{coveragePercentage}'),
metrics: {
cardTitle: s__('RepositoriesAnalytics|Overall activity'),
projectCountLabel: s__('RepositoriesAnalytics|Projects with Coverage'),
averageCoverageLabel: s__('RepositoriesAnalytics|Average Coverage by Job'),
coverageCountLabel: s__('RepositoriesAnalytics|Jobs with Coverage'),
......@@ -155,11 +159,21 @@ export default {
</script>
<template>
<div>
<metric-card
:title="$options.i18n.metrics.cardTitle"
:metrics="metrics"
:is-loading="isLoading"
<div
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-my-6 gl-align-items-flex-start"
>
<gl-skeleton-loading v-if="isLoading" />
<gl-single-stat
v-for="metric in metrics"
v-else
:key="metric.key"
class="gl-pr-9 gl-my-4 gl-md-mt-0 gl-md-mb-0"
:value="`${metric.value || '-'}`"
:unit="metric.value ? metric.unit : null"
:title="metric.label"
:should-animate="true"
/>
</div>
<gl-card>
<template #header>
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { GlSingleStat } from '@gitlab/ui/dist/charts';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import TestCoverageSummary from 'ee/analytics/repository_analytics/components/test_coverage_summary.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
const localVue = createLocalVue();
describe('Test coverage table component', () => {
let wrapper;
const findProjectsWithTests = () => wrapper.find('.js-metric-card-item:nth-child(1) h3');
const findAverageCoverage = () => wrapper.find('.js-metric-card-item:nth-child(2) h3');
const findTotalCoverages = () => wrapper.find('.js-metric-card-item:nth-child(3) h3');
const findAllSingleStats = () => wrapper.findAllComponents(GlSingleStat);
const findProjectsWithTests = () => findAllSingleStats().at(0);
const findAverageCoverage = () => findAllSingleStats().at(1);
const findTotalCoverages = () => findAllSingleStats().at(2);
const findGroupCoverageChart = () => wrapper.findByTestId('group-coverage-chart');
const findChartLoadingState = () => wrapper.findByTestId('group-coverage-chart-loading');
const findChartEmptyState = () => wrapper.findByTestId('group-coverage-chart-empty');
......@@ -41,7 +42,7 @@ describe('Test coverage table component', () => {
},
},
stubs: {
MetricCard,
GlSingleStat,
},
}),
);
......@@ -56,9 +57,9 @@ describe('Test coverage table component', () => {
it('renders empty metrics', () => {
createComponent();
expect(findProjectsWithTests().text()).toBe('-');
expect(findAverageCoverage().text()).toBe('-');
expect(findTotalCoverages().text()).toBe('-');
expect(findProjectsWithTests().text()).toContain('-');
expect(findAverageCoverage().text()).toContain('-');
expect(findTotalCoverages().text()).toContain('-');
});
it('renders empty chart state', () => {
......@@ -92,9 +93,10 @@ describe('Test coverage table component', () => {
},
});
expect(findProjectsWithTests().text()).toBe(projectCount);
expect(findAverageCoverage().text()).toBe(`${averageCoverage} %`);
expect(findTotalCoverages().text()).toBe(coverageCount);
expect(findProjectsWithTests().props('value')).toBe(projectCount);
expect(findAverageCoverage().props('value')).toBe(`${averageCoverage}`);
expect(findAverageCoverage().props('unit')).toBe('%');
expect(findTotalCoverages().props('value')).toBe(coverageCount);
});
it('renders area chart with correct data', () => {
......
......@@ -27467,9 +27467,6 @@ msgstr ""
msgid "RepositoriesAnalytics|No test coverage to display"
msgstr ""
msgid "RepositoriesAnalytics|Overall activity"
msgstr ""
msgid "RepositoriesAnalytics|Please select a project or multiple projects to display their most recent test coverage data."
msgstr ""
......
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MetricCard from '~/analytics/shared/components/metric_card.vue';
const metrics = [
{ key: 'first_metric', value: 10, label: 'First metric', unit: 'days', link: 'some_link' },
{ key: 'second_metric', value: 20, label: 'Yet another metric' },
{ key: 'third_metric', value: null, label: 'Null metric without value', unit: 'parsecs' },
{ key: 'fourth_metric', value: '-', label: 'Metric without value', unit: 'parsecs' },
];
const defaultProps = {
title: 'My fancy title',
isLoading: false,
metrics,
};
describe('MetricCard', () => {
let wrapper;
const factory = (props = defaultProps) => {
wrapper = mount(MetricCard, {
propsData: {
...defaultProps,
...props,
},
directives: {
GlTooltip: createMockDirective(),
},
});
};
afterEach(() => {
wrapper.destroy();
});
const findTitle = () => wrapper.find({ ref: 'title' });
const findLoadingIndicator = () => wrapper.find(GlSkeletonLoading);
const findMetricsWrapper = () => wrapper.find({ ref: 'metricsWrapper' });
const findMetricItem = () => wrapper.findAll({ ref: 'metricItem' });
const findTooltip = () => wrapper.find('[data-testid="tooltip"]');
describe('template', () => {
it('renders the title', () => {
factory();
expect(findTitle().text()).toContain('My fancy title');
});
describe('when isLoading is true', () => {
beforeEach(() => {
factory({ isLoading: true });
});
it('displays a loading indicator', () => {
expect(findLoadingIndicator().exists()).toBe(true);
});
it('does not display the metrics container', () => {
expect(findMetricsWrapper().exists()).toBe(false);
});
});
describe('when isLoading is false', () => {
beforeEach(() => {
factory({ isLoading: false });
});
it('does not display a loading indicator', () => {
expect(findLoadingIndicator().exists()).toBe(false);
});
it('displays the metrics container', () => {
expect(findMetricsWrapper().exists()).toBe(true);
});
it('renders two metrics', () => {
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`
columnIndex | label | value | unit | link
${0} | ${'First metric'} | ${10} | ${' days'} | ${'some_link'}
${1} | ${'Yet another metric'} | ${20} | ${''} | ${null}
${2} | ${'Null metric without value'} | ${'-'} | ${''} | ${null}
${3} | ${'Metric without value'} | ${'-'} | ${''} | ${null}
`('metric columns', ({ columnIndex, label, value, unit, link }) => {
it(`renders ${value}${unit} ${label} with URL ${link}`, () => {
const allMetricItems = findMetricItem();
const metricItem = allMetricItems.at(columnIndex);
const text = metricItem.text();
expect(text).toContain(`${value}${unit}`);
expect(text).toContain(label);
if (link) {
expect(metricItem.find('a').attributes('href')).toBe(link);
} else {
expect(metricItem.find('a').exists()).toBe(false);
}
});
});
});
});
});
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