diff --git a/ee/app/assets/javascripts/analytics/productivity_analytics/components/metric_column.vue b/ee/app/assets/javascripts/analytics/productivity_analytics/components/metric_column.vue new file mode 100644 index 0000000000000000000000000000000000000000..a4da2dc7d8147416d65ced22955d295b0a6376c7 --- /dev/null +++ b/ee/app/assets/javascripts/analytics/productivity_analytics/components/metric_column.vue @@ -0,0 +1,49 @@ +<script> +import { n__ } from '~/locale'; + +export default { + props: { + type: { + type: String, + required: true, + }, + /** + * With default null we will render a "-" in the last column as opposed to a numeric value + */ + value: { + type: Number, + required: false, + default: null, + }, + label: { + type: String, + required: false, + default: '', + }, + }, + computed: { + isNumericValue() { + return this.value !== null && !Number.isNaN(Number(this.value)); + }, + unit() { + return this.type === 'days_to_merge' + ? n__('day', 'days', this.value) + : n__('Time|hr', 'Time|hrs', this.value); + }, + }, +}; +</script> +<template> + <div class="metric-col"> + <span class="time"> + <template v-if="isNumericValue"> + {{ value }} + <span> {{ unit }} </span> + </template> + <template v-else> + – + </template> + </span> + <span v-if="label" class="d-flex d-md-none text-secondary metric-label">{{ label }}</span> + </div> +</template> diff --git a/ee/app/assets/javascripts/analytics/productivity_analytics/components/mr_table_row.vue b/ee/app/assets/javascripts/analytics/productivity_analytics/components/mr_table_row.vue index b5e85e363fba1f0a7e800c3cf9e219793556971d..8dcbbd50b8168f8fceffe6752e751d9086fb66cd 100644 --- a/ee/app/assets/javascripts/analytics/productivity_analytics/components/mr_table_row.vue +++ b/ee/app/assets/javascripts/analytics/productivity_analytics/components/mr_table_row.vue @@ -1,11 +1,13 @@ <script> import { sprintf, __, n__ } from '~/locale'; import { GlLink, GlAvatar } from '@gitlab/ui'; +import MetricColumn from './metric_column.vue'; export default { components: { GlLink, GlAvatar, + MetricColumn, }, props: { mergeRequest: { @@ -68,20 +70,12 @@ export default { </div> </div> <div class="table-section section-50 d-flex flex-row align-items-start qa-mr-metrics"> - <div class="metric-col"> - <span class="time"> - {{ mergeRequest.time_to_merge }} - <span> {{ n__('Time|hr', 'Time|hrs', mergeRequest.time_to_merge) }} </span> - </span> - <span class="d-flex d-md-none text-secondary metric-label">{{ __('Time to merge') }}</span> - </div> - <div class="metric-col"> - <span class="time"> - {{ selectedMetric }} - <span> {{ metricTimeUnit }} </span> - </span> - <span class="d-flex d-md-none text-secondary metric-label">{{ metricLabel }}</span> - </div> + <metric-column + type="time_to_merge" + :value="mergeRequest.time_to_merge" + :label="__('Time to merge')" + /> + <metric-column :type="metricType" :value="selectedMetric" :label="metricLabel" /> </div> </div> </template> diff --git a/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/metric_column_spec.js.snap b/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/metric_column_spec.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..275864512c6e15a21ab7d5054911cdda120c26c9 --- /dev/null +++ b/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/metric_column_spec.js.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MetricColumn component on creation matches the snapshot 1`] = ` +<div + class="metric-col" +> + <span + class="time" + > + + 10 + + <span> + hrs + </span> + </span> + + <span + class="d-flex d-md-none text-secondary metric-label" + > + Time from first comment to last commit + </span> +</div> +`; diff --git a/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/mr_table_row_spec.js.snap b/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/mr_table_row_spec.js.snap index bf774b3bd815e1258a107b82c3708dd3da70d82d..f1f73f73f4a58b9a9c7539fc455580c68026c63c 100644 --- a/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/mr_table_row_spec.js.snap +++ b/ee/spec/frontend/analytics/productivity_analytics/components/__snapshots__/mr_table_row_spec.js.snap @@ -58,47 +58,17 @@ exports[`MergeRequestTableRow component on creation matches the snapshot 1`] = ` <div class="table-section section-50 d-flex flex-row align-items-start qa-mr-metrics" > - <div - class="metric-col" - > - <span - class="time" - > - - 0 - - <span> - hrs - </span> - </span> - - <span - class="d-flex d-md-none text-secondary metric-label" - > - Time to merge - </span> - </div> + <metriccolumn-stub + label="Time to merge" + type="time_to_merge" + value="0" + /> - <div - class="metric-col" - > - <span - class="time" - > - - 0 - - <span> - hrs - </span> - </span> - - <span - class="d-flex d-md-none text-secondary metric-label" - > - Time from first comment to last commit - </span> - </div> + <metriccolumn-stub + label="Time from first comment to last commit" + type="time_to_last_commit" + value="0" + /> </div> </div> `; diff --git a/ee/spec/frontend/analytics/productivity_analytics/components/metric_column_spec.js b/ee/spec/frontend/analytics/productivity_analytics/components/metric_column_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..e4a962891e432fe197213f6e7aa402a0b6b0b602 --- /dev/null +++ b/ee/spec/frontend/analytics/productivity_analytics/components/metric_column_spec.js @@ -0,0 +1,101 @@ +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import MetricColumn from 'ee/analytics/productivity_analytics/components/metric_column.vue'; + +describe('MetricColumn component', () => { + let wrapper; + + const defaultProps = { + type: 'time_to_last_commit', + value: 10, + label: 'Time from first comment to last commit', + }; + + const factory = (props = defaultProps) => { + const localVue = createLocalVue(); + + wrapper = shallowMount(localVue.extend(MetricColumn), { + localVue, + sync: false, + propsData: { ...props }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findTimeContainer = () => wrapper.find('.time'); + + describe('on creation', () => { + beforeEach(() => { + factory(); + }); + + it('matches the snapshot', () => { + expect(wrapper.element).toMatchSnapshot(); + }); + }); + + describe('template', () => { + describe('when metric has value', () => { + beforeEach(() => { + factory(); + }); + + it('renders the value and unit', () => { + const unit = 'hrs'; + expect(findTimeContainer().text()).toContain(`${defaultProps.value}`); + expect(findTimeContainer().text()).toContain(unit); + }); + }); + + describe('when metric has no value', () => { + beforeEach(() => { + factory({ + ...defaultProps, + value: null, + }); + }); + + it('renders a dash', () => { + expect(findTimeContainer().text()).toContain('–'); + }); + }); + }); + + describe('computed', () => { + describe('isNumericValue', () => { + it('returns true when the value is neither null nor undefined', () => { + factory(); + expect(wrapper.vm.isNumericValue).toBe(true); + }); + + it('returns false when the value is null', () => { + factory({ + ...defaultProps, + value: null, + }); + expect(wrapper.vm.isNumericValue).toBe(false); + }); + }); + + describe('unit', () => { + it('returns "days" for the "days_to_merge" metric', () => { + factory({ + ...defaultProps, + type: 'days_to_merge', + }); + + expect(wrapper.vm.unit).toBe('days'); + }); + + it('returns "hrs" for the any other metric', () => { + factory({ + ...defaultProps, + }); + + expect(wrapper.vm.unit).toBe('hrs'); + }); + }); + }); +}); diff --git a/ee/spec/frontend/analytics/productivity_analytics/components/mr_table_row_spec.js b/ee/spec/frontend/analytics/productivity_analytics/components/mr_table_row_spec.js index b8dd73911dac7fe782b75b1b39e419dc9e4d1090..0daff7aaa54449cf1d244878101a660f039b5be9 100644 --- a/ee/spec/frontend/analytics/productivity_analytics/components/mr_table_row_spec.js +++ b/ee/spec/frontend/analytics/productivity_analytics/components/mr_table_row_spec.js @@ -1,5 +1,6 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; import MergeRequestTableRow from 'ee/analytics/productivity_analytics/components/mr_table_row.vue'; +import MetricColumn from 'ee/analytics/productivity_analytics/components/metric_column.vue'; import { GlAvatar } from '@gitlab/ui'; import { mockMergeRequests } from '../mock_data'; @@ -24,7 +25,7 @@ describe('MergeRequestTableRow component', () => { const findMrDetails = () => wrapper.find('.qa-mr-details'); const findMrMetrics = () => wrapper.find('.qa-mr-metrics'); - const findMetricColumns = () => findMrMetrics().findAll('.metric-col'); + const findMetricColumns = () => findMrMetrics().findAll(MetricColumn); afterEach(() => { wrapper.destroy(); @@ -68,8 +69,8 @@ describe('MergeRequestTableRow component', () => { expect( findMetricColumns() .at(0) - .text(), - ).toContain(defaultProps.mergeRequest.time_to_merge); + .props('value'), + ).toBe(defaultProps.mergeRequest.time_to_merge); }); }); });