Commit a76ed459 authored by Martin Wortschack's avatar Martin Wortschack Committed by Kushal Pandya

Add metric column component

- This ensures that we render numeric values only
The component renders a dash for non-numeric and null values
parent f5d6f4f3
<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>
&ndash;
</template>
</span>
<span v-if="label" class="d-flex d-md-none text-secondary metric-label">{{ label }}</span>
</div>
</template>
<script> <script>
import { sprintf, __, n__ } from '~/locale'; import { sprintf, __, n__ } from '~/locale';
import { GlLink, GlAvatar } from '@gitlab/ui'; import { GlLink, GlAvatar } from '@gitlab/ui';
import MetricColumn from './metric_column.vue';
export default { export default {
components: { components: {
GlLink, GlLink,
GlAvatar, GlAvatar,
MetricColumn,
}, },
props: { props: {
mergeRequest: { mergeRequest: {
...@@ -68,20 +70,12 @@ export default { ...@@ -68,20 +70,12 @@ export default {
</div> </div>
</div> </div>
<div class="table-section section-50 d-flex flex-row align-items-start qa-mr-metrics"> <div class="table-section section-50 d-flex flex-row align-items-start qa-mr-metrics">
<div class="metric-col"> <metric-column
<span class="time"> type="time_to_merge"
{{ mergeRequest.time_to_merge }} :value="mergeRequest.time_to_merge"
<span> {{ n__('Time|hr', 'Time|hrs', mergeRequest.time_to_merge) }} </span> :label="__('Time to merge')"
</span> />
<span class="d-flex d-md-none text-secondary metric-label">{{ __('Time to merge') }}</span> <metric-column :type="metricType" :value="selectedMetric" :label="metricLabel" />
</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>
</div> </div>
</div> </div>
</template> </template>
// 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>
`;
...@@ -58,47 +58,17 @@ exports[`MergeRequestTableRow component on creation matches the snapshot 1`] = ` ...@@ -58,47 +58,17 @@ exports[`MergeRequestTableRow component on creation matches the snapshot 1`] = `
<div <div
class="table-section section-50 d-flex flex-row align-items-start qa-mr-metrics" class="table-section section-50 d-flex flex-row align-items-start qa-mr-metrics"
> >
<div <metriccolumn-stub
class="metric-col" label="Time to merge"
> type="time_to_merge"
<span value="0"
class="time" />
>
0
<span>
hrs
</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"
>
0
<span>
hrs
</span>
</span>
<span <metriccolumn-stub
class="d-flex d-md-none text-secondary metric-label" label="Time from first comment to last commit"
> type="time_to_last_commit"
Time from first comment to last commit value="0"
</span> />
</div>
</div> </div>
</div> </div>
`; `;
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');
});
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import MergeRequestTableRow from 'ee/analytics/productivity_analytics/components/mr_table_row.vue'; 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 { GlAvatar } from '@gitlab/ui';
import { mockMergeRequests } from '../mock_data'; import { mockMergeRequests } from '../mock_data';
...@@ -24,7 +25,7 @@ describe('MergeRequestTableRow component', () => { ...@@ -24,7 +25,7 @@ describe('MergeRequestTableRow component', () => {
const findMrDetails = () => wrapper.find('.qa-mr-details'); const findMrDetails = () => wrapper.find('.qa-mr-details');
const findMrMetrics = () => wrapper.find('.qa-mr-metrics'); const findMrMetrics = () => wrapper.find('.qa-mr-metrics');
const findMetricColumns = () => findMrMetrics().findAll('.metric-col'); const findMetricColumns = () => findMrMetrics().findAll(MetricColumn);
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -68,8 +69,8 @@ describe('MergeRequestTableRow component', () => { ...@@ -68,8 +69,8 @@ describe('MergeRequestTableRow component', () => {
expect( expect(
findMetricColumns() findMetricColumns()
.at(0) .at(0)
.text(), .props('value'),
).toContain(defaultProps.mergeRequest.time_to_merge); ).toBe(defaultProps.mergeRequest.time_to_merge);
}); });
}); });
}); });
......
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