Commit ba49beb8 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Merge branch 'tr-consolidate-incident-sla-component' into 'master'

Consolidate incident SLA component

See merge request gitlab-org/gitlab!45621
parents a5f6d19b 0d858c23
...@@ -102,7 +102,7 @@ export default { ...@@ -102,7 +102,7 @@ export default {
GlIcon, GlIcon,
PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'), PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'),
ServiceLevelAgreementCell: () => ServiceLevelAgreementCell: () =>
import('ee_component/incidents/components/service_level_agreement_cell.vue'), import('ee_component/vue_shared/components/incidents/service_level_agreement.vue'),
GlEmptyState, GlEmptyState,
SeverityToken, SeverityToken,
PaginatedTableWithSearchAndTabs, PaginatedTableWithSearchAndTabs,
......
<script> <script>
import { GlIcon } from '@gitlab/ui'; import { GlIcon } from '@gitlab/ui';
import ServiceLevelAgreement from 'ee_component/vue_shared/components/incidents/service_level_agreement.vue';
import { isValidSlaDueAt } from 'ee/vue_shared/components/incidents/utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { formatTime, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility'; import { formatTime, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import getSlaDueAt from './graphql/queries/get_sla_due_at.graphql'; import getSlaDueAt from './graphql/queries/get_sla_due_at.graphql';
export default { export default {
components: { GlIcon, TimeAgoTooltip }, components: { GlIcon, ServiceLevelAgreement },
inject: ['fullPath', 'iid', 'slaFeatureAvailable'], inject: ['fullPath', 'iid', 'slaFeatureAvailable'],
apollo: { apollo: {
slaDueAt: { slaDueAt: {
...@@ -21,9 +22,14 @@ export default { ...@@ -21,9 +22,14 @@ export default {
update(data) { update(data) {
return data?.project?.issue?.slaDueAt; return data?.project?.issue?.slaDueAt;
}, },
result({ data } = {}) { result({ data }) {
const slaDueAt = data?.project?.issue?.slaDueAt; const isValidSla = isValidSlaDueAt(data?.project?.issue?.slaDueAt);
this.$emit('update', Boolean(slaDueAt));
// Render component
this.hasData = isValidSla;
// Render parent component
this.$emit('update', isValidSla);
}, },
error() { error() {
createFlash({ createFlash({
...@@ -35,6 +41,7 @@ export default { ...@@ -35,6 +41,7 @@ export default {
data() { data() {
return { return {
slaDueAt: null, slaDueAt: null,
hasData: false,
}; };
}, },
computed: { computed: {
...@@ -49,13 +56,11 @@ export default { ...@@ -49,13 +56,11 @@ export default {
</script> </script>
<template> <template>
<div v-if="slaFeatureAvailable && slaDueAt"> <div v-if="slaFeatureAvailable && hasData">
<span class="gl-font-weight-bold">{{ s__('HighlightBar|Time to SLA:') }}</span> <span class="gl-font-weight-bold">{{ s__('HighlightBar|Time to SLA:') }}</span>
<span class="gl-white-space-nowrap"> <span class="gl-white-space-nowrap">
<gl-icon name="timer" /> <gl-icon name="timer" />
<time-ago-tooltip :time="slaDueAt"> <service-level-agreement :sla-due-at="slaDueAt" />
{{ displayValue }}
</time-ago-tooltip>
</span> </span>
</div> </div>
</template> </template>
...@@ -2,26 +2,26 @@ ...@@ -2,26 +2,26 @@
import { GlTooltipDirective } from '@gitlab/ui'; import { GlTooltipDirective } from '@gitlab/ui';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import { formatTime, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility'; import { formatTime, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility';
import { isValidSlaDueAt } from './utils';
export default { export default {
i18n: { i18n: {
longText: s__('IncidentManagement|%{hours} hours, %{minutes} minutes remaining'), longTitle: s__('IncidentManagement|%{hours} hours, %{minutes} minutes remaining'),
shortText: s__('IncidentManagement|%{minutes} minutes remaining'), shortTitle: s__('IncidentManagement|%{minutes} minutes remaining'),
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
props: { props: {
slaDueAt: { slaDueAt: {
type: String, type: String, // ISODateString
required: false, required: false,
default: null, default: null,
}, },
}, },
computed: { computed: {
shouldShow() { shouldShow() {
// Checks for a valid date string return isValidSlaDueAt(this.slaDueAt);
return this.slaDueAt && !Number.isNaN(Date.parse(this.slaDueAt));
}, },
remainingTime() { remainingTime() {
return calculateRemainingMilliseconds(this.slaDueAt); return calculateRemainingMilliseconds(this.slaDueAt);
...@@ -37,9 +37,9 @@ export default { ...@@ -37,9 +37,9 @@ export default {
const hours = Math.floor(this.remainingTime / 1000 / 60 / 60); const hours = Math.floor(this.remainingTime / 1000 / 60 / 60);
if (hours > 0) { if (hours > 0) {
return sprintf(this.$options.i18n.longText, { hours, minutes }); return sprintf(this.$options.i18n.longTitle, { hours, minutes });
} }
return sprintf(this.$options.i18n.shortText, { hours, minutes }); return sprintf(this.$options.i18n.shortTitle, { minutes });
}, },
}, },
}; };
......
// Checks for a valid date string
export const isValidSlaDueAt = (slaDueAt) =>
Boolean(slaDueAt) && !Number.isNaN(Date.parse(slaDueAt));
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { merge } from 'lodash'; import { merge } from 'lodash';
import IncidentSla from 'ee/issue_show/components/incidents/incident_sla.vue'; import IncidentSla from 'ee/issue_show/components/incidents/incident_sla.vue';
import { formatTime } from '~/lib/utils/datetime_utility'; import ServiceLevelAgreement from 'ee_component/vue_shared/components/incidents/service_level_agreement.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
jest.mock('~/lib/utils/datetime_utility'); jest.mock('~/lib/utils/datetime_utility');
const defaultProvide = { fullPath: 'test', iid: 1, slaFeatureAvailable: true }; const defaultProvide = { fullPath: 'test', iid: 1, slaFeatureAvailable: true };
const mockSlaDueAt = '2020-01-01T00:00:00.000Z';
describe('Incident SLA', () => { describe('Incident SLA', () => {
let wrapper; let wrapper;
...@@ -17,7 +17,7 @@ describe('Incident SLA', () => { ...@@ -17,7 +17,7 @@ describe('Incident SLA', () => {
merge( merge(
{ {
data() { data() {
return { slaDueAt: '2020-01-01T00:00:00.000Z' }; return { slaDueAt: mockSlaDueAt, hasData: true };
}, },
provide: { ...defaultProvide }, provide: { ...defaultProvide },
}, },
...@@ -26,10 +26,6 @@ describe('Incident SLA', () => { ...@@ -26,10 +26,6 @@ describe('Incident SLA', () => {
); );
}; };
beforeEach(() => {
formatTime.mockImplementation(() => '12:34:56');
});
afterEach(() => { afterEach(() => {
if (wrapper) { if (wrapper) {
wrapper.destroy(); wrapper.destroy();
...@@ -37,28 +33,16 @@ describe('Incident SLA', () => { ...@@ -37,28 +33,16 @@ describe('Incident SLA', () => {
} }
}); });
const findTimer = () => wrapper.find(TimeAgoTooltip); const findSLA = () => wrapper.find(ServiceLevelAgreement);
it('does not render an SLA when no sla is present', () => { it('renders a blank component when there is no data', () => {
mountComponent({ mountComponent({
data() { data() {
return { slaDueAt: null }; return { hasData: false };
}, },
}); });
expect(findTimer().exists()).toBe(false); expect(wrapper.isVisible()).toBe(false);
});
it('renders an incident SLA when sla is present', () => {
mountComponent();
expect(findTimer().text()).toBe('12:34');
});
it('renders a component when feature is available', () => {
mountComponent();
expect(wrapper.exists()).toBe(true);
}); });
it('renders a blank component when feature is not available', () => { it('renders a blank component when feature is not available', () => {
...@@ -69,6 +53,13 @@ describe('Incident SLA', () => { ...@@ -69,6 +53,13 @@ describe('Incident SLA', () => {
}, },
}); });
expect(wrapper.html()).toBe(''); expect(wrapper.isVisible()).toBe(false);
});
it('renders an incident SLA when sla is present and feature is available', () => {
mountComponent();
expect(wrapper.isVisible()).toBe(true);
expect(findSLA().attributes('sladueat')).toBe(mockSlaDueAt);
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ServiceLevelAgreementCell from 'ee/incidents/components/service_level_agreement_cell.vue'; import ServiceLevelAgreementCell from 'ee/vue_shared/components/incidents/service_level_agreement.vue';
import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility'; import { calculateRemainingMilliseconds, formatTime } from '~/lib/utils/datetime_utility';
jest.mock('~/lib/utils/datetime_utility', () => ({ jest.mock('~/lib/utils/datetime_utility', () => ({
......
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