Commit e4d3899d authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '268366-display-escalation-policy' into 'master'

Display Escalation Policy

See merge request gitlab-org/gitlab!63416
parents 85e509c2 c58680e2
@import 'mixins_and_variables_and_functions';
.escalation-policy-modal { .escalation-policy-modal {
width: 640px; width: 640px;
} }
...@@ -9,3 +11,27 @@ ...@@ -9,3 +11,27 @@
.rule-close-icon { .rule-close-icon {
right: 1rem; right: 1rem;
} }
$stroke-size: 1px;
.right-arrow {
@include gl-relative;
@include gl-mx-5;
@include gl-display-inline-block;
@include gl-vertical-align-middle;
height: $stroke-size;
background-color: var(--gray-900, $gray-900);
min-width: $gl-spacing-scale-7;
&-head {
@include gl-absolute;
top: -2*$stroke-size;
left: calc(100% - #{5*$stroke-size});
@include gl-display-inline-block;
@include gl-p-1;
@include gl-border-solid;
border-width: 0 $stroke-size $stroke-size 0;
border-color: var(--gray-900, $gray-900);
transform: rotate(-45deg);
}
}
...@@ -3,7 +3,7 @@ import { GlLink, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; ...@@ -3,7 +3,7 @@ import { GlLink, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { defaultEscalationRule } from '../constants'; import { DEFAULT_ESCALATION_RULE } from '../constants';
import getOncallSchedulesQuery from '../graphql/queries/get_oncall_schedules.query.graphql'; import getOncallSchedulesQuery from '../graphql/queries/get_oncall_schedules.query.graphql';
import EscalationRule from './escalation_rule.vue'; import EscalationRule from './escalation_rule.vue';
...@@ -74,11 +74,11 @@ export default { ...@@ -74,11 +74,11 @@ export default {
}, },
}, },
mounted() { mounted() {
this.rules.push({ ...cloneDeep(defaultEscalationRule), key: this.getUid() }); this.addRule();
}, },
methods: { methods: {
addRule() { addRule() {
this.rules.push({ ...cloneDeep(defaultEscalationRule), key: this.getUid() }); this.rules.push({ ...cloneDeep(DEFAULT_ESCALATION_RULE), key: this.getUid() });
}, },
updateEscalationRules(index, rule) { updateEscalationRules(index, rule) {
this.rules[index] = { ...this.rules[index], ...rule }; this.rules[index] = { ...this.rules[index], ...rule };
......
...@@ -3,7 +3,9 @@ import { GlModal, GlAlert } from '@gitlab/ui'; ...@@ -3,7 +3,9 @@ import { GlModal, GlAlert } from '@gitlab/ui';
import { set } from 'lodash'; import { set } from 'lodash';
import { s__, __ } from '~/locale'; import { s__, __ } from '~/locale';
import { addEscalationPolicyModalId } from '../constants'; import { addEscalationPolicyModalId } from '../constants';
import { updateStoreOnEscalationPolicyCreate } from '../graphql/cache_updates';
import createEscalationPolicyMutation from '../graphql/mutations/create_escalation_policy.mutation.graphql'; import createEscalationPolicyMutation from '../graphql/mutations/create_escalation_policy.mutation.graphql';
import getEscalationPoliciesQuery from '../graphql/queries/get_escalation_policies.query.graphql';
import { isNameFieldValid, getRulesValidationState } from '../utils'; import { isNameFieldValid, getRulesValidationState } from '../utils';
import AddEditEscalationPolicyForm from './add_edit_escalation_policy_form.vue'; import AddEditEscalationPolicyForm from './add_edit_escalation_policy_form.vue';
...@@ -86,6 +88,11 @@ export default { ...@@ -86,6 +88,11 @@ export default {
...this.getRequestParams(), ...this.getRequestParams(),
}, },
}, },
update(store, { data }) {
updateStoreOnEscalationPolicyCreate(store, getEscalationPoliciesQuery, data, {
projectPath,
});
},
}) })
.then( .then(
({ ({
......
<script> <script>
import { GlEmptyState, GlButton, GlModalDirective } from '@gitlab/ui'; import { GlEmptyState, GlButton, GlModalDirective, GlLoadingIcon } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { addEscalationPolicyModalId } from '../constants'; import { addEscalationPolicyModalId } from '../constants';
import getEscalationPoliciesQuery from '../graphql/queries/get_escalation_policies.query.graphql';
import AddEscalationPolicyModal from './add_edit_escalation_policy_modal.vue'; import AddEscalationPolicyModal from './add_edit_escalation_policy_modal.vue';
import EscalationPolicy from './escalation_policy.vue';
export const i18n = { export const i18n = {
title: s__('EscalationPolicies|Escalation policies'),
addPolicy: s__('EscalationPolicies|Add policy'),
emptyState: { emptyState: {
title: s__('EscalationPolicies|Create an escalation policy in GitLab'), title: s__('EscalationPolicies|Create an escalation policy in GitLab'),
description: s__( description: s__(
...@@ -20,18 +25,73 @@ export default { ...@@ -20,18 +25,73 @@ export default {
components: { components: {
GlEmptyState, GlEmptyState,
GlButton, GlButton,
GlLoadingIcon,
AddEscalationPolicyModal, AddEscalationPolicyModal,
EscalationPolicy,
}, },
directives: { directives: {
GlModal: GlModalDirective, GlModal: GlModalDirective,
}, },
inject: ['emptyEscalationPoliciesSvgPath'], inject: ['projectPath', 'emptyEscalationPoliciesSvgPath'],
data() {
return {
escalationPolicies: [],
};
},
apollo: {
escalationPolicies: {
query: getEscalationPoliciesQuery,
variables() {
return {
projectPath: this.projectPath,
};
},
update({ project }) {
return project?.incidentManagementEscalationPolicies?.nodes ?? [];
},
error(error) {
Sentry.captureException(error);
},
},
},
computed: {
isLoading() {
return this.$apollo.queries.escalationPolicies.loading;
},
hasPolicies() {
return this.escalationPolicies.length;
},
},
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3" />
<template v-else-if="hasPolicies">
<div class="gl-display-flex gl-justify-content-space-between gl-align-items-center">
<h2>{{ $options.i18n.title }}</h2>
<gl-button
v-gl-modal="$options.addEscalationPolicyModalId"
:title="$options.i18n.addPolicy"
category="secondary"
variant="confirm"
class="gl-mt-5"
>
{{ $options.i18n.addPolicy }}
</gl-button>
</div>
<escalation-policy
v-for="(policy, index) in escalationPolicies"
:key="policy.id"
:policy="policy"
:index="index"
/>
</template>
<gl-empty-state <gl-empty-state
v-else
:title="$options.i18n.emptyState.title" :title="$options.i18n.emptyState.title"
:description="$options.i18n.emptyState.description" :description="$options.i18n.emptyState.description"
:svg-path="emptyEscalationPoliciesSvgPath" :svg-path="emptyEscalationPoliciesSvgPath"
...@@ -42,6 +102,6 @@ export default { ...@@ -42,6 +102,6 @@ export default {
</gl-button> </gl-button>
</template> </template>
</gl-empty-state> </gl-empty-state>
<add-escalation-policy-modal /> <add-escalation-policy-modal :modal-id="$options.addEscalationPolicyModalId" />
</div> </div>
</template> </template>
<script>
import {
GlModalDirective,
GlTooltipDirective,
GlButton,
GlButtonGroup,
GlCard,
GlSprintf,
GlIcon,
GlCollapse,
} from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { ACTIONS, ALERT_STATUSES, DEFAULT_ACTION } from '../constants';
export const i18n = {
editPolicy: s__('EscalationPolicies|Edit escalation policy'),
deletePolicy: s__('EscalationPolicies|Delete escalation policy'),
escalationRule: s__(
'EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{schedule}',
),
minutes: s__('EscalationPolicies|mins'),
};
const isRuleValid = ({ status, elapsedTimeSeconds, oncallSchedule: { name } }) =>
Object.keys(ALERT_STATUSES).includes(status) &&
typeof elapsedTimeSeconds === 'number' &&
typeof name === 'string';
export default {
i18n,
ACTIONS,
ALERT_STATUSES,
DEFAULT_ACTION,
components: {
GlButton,
GlButtonGroup,
GlCard,
GlSprintf,
GlIcon,
GlCollapse,
},
directives: {
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
},
props: {
policy: {
type: Object,
required: true,
validator: ({ name, rules }) => {
return typeof name === 'string' && Array.isArray(rules) && rules.every(isRuleValid);
},
},
index: {
type: Number,
required: true,
},
},
data() {
return {
isPolicyVisible: this.index === 0,
};
},
computed: {
policyVisibleAngleIcon() {
return this.isPolicyVisible ? 'angle-down' : 'angle-right';
},
policyVisibleAngleIconLabel() {
return this.isPolicyVisible ? __('Collapse') : __('Expand');
},
},
};
</script>
<template>
<gl-card
class="gl-mt-5"
:class="{ 'gl-border-bottom-0': !isPolicyVisible }"
:body-class="{ 'gl-p-0': !isPolicyVisible }"
:header-class="{ 'gl-py-3': true, 'gl-rounded-base': !isPolicyVisible }"
>
<template #header>
<div class="gl-display-flex gl-align-items-center">
<gl-button
v-gl-tooltip
class="gl-mr-2 gl-p-0!"
:title="policyVisibleAngleIconLabel"
:aria-label="policyVisibleAngleIconLabel"
category="tertiary"
@click="isPolicyVisible = !isPolicyVisible"
>
<gl-icon :size="12" :name="policyVisibleAngleIcon" />
</gl-button>
<h3 class="gl-font-weight-bold gl-font-lg gl-m-0">{{ policy.name }}</h3>
<gl-button-group class="gl-ml-auto">
<gl-button
v-gl-tooltip
:title="$options.i18n.editPolicy"
icon="pencil"
:aria-label="$options.i18n.editPolicy"
disabled
/>
<gl-button
v-gl-tooltip
:title="$options.i18n.deletePolicy"
icon="remove"
:aria-label="$options.i18n.deletePolicy"
disabled
/>
</gl-button-group>
</div>
</template>
<gl-collapse :visible="isPolicyVisible">
<p v-if="policy.description" class="gl-text-gray-500 gl-mb-5">
{{ policy.description }}
</p>
<div class="gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base gl-p-5">
<div
v-for="(rule, ruleIndex) in policy.rules"
:key="rule.id"
:class="{ 'gl-mb-5': ruleIndex !== policy.rules.length - 1 }"
>
<gl-icon name="clock" class="gl-mr-3" />
<gl-sprintf :message="$options.i18n.escalationRule">
<template #alertStatus>
{{ $options.ALERT_STATUSES[rule.status].toLowerCase() }}
</template>
<template #minutes>
<span class="gl-font-weight-bold">
{{ rule.elapsedTimeSeconds }} {{ $options.i18n.minutes }}
</span>
</template>
<template #then>
<span class="right-arrow">
<i class="right-arrow-head"></i>
</span>
<gl-icon name="notifications" class="gl-mr-3" />
</template>
<template #doAction>
{{ $options.ACTIONS[$options.DEFAULT_ACTION].toLowerCase() }}
</template>
<template #schedule>
<span class="gl-font-weight-bold">
{{ rule.oncallSchedule.name }}
</span>
</template>
</gl-sprintf>
</div>
</div>
</gl-collapse>
</gl-card>
</template>
...@@ -5,11 +5,13 @@ export const ALERT_STATUSES = { ...@@ -5,11 +5,13 @@ export const ALERT_STATUSES = {
RESOLVED: s__('AlertManagement|Resolved'), RESOLVED: s__('AlertManagement|Resolved'),
}; };
export const DEFAULT_ACTION = 'EMAIL_ONCALL_SCHEDULE_USER';
export const ACTIONS = { export const ACTIONS = {
EMAIL_ONCALL_SCHEDULE_USER: s__('EscalationPolicies|Email on-call user in schedule'), [DEFAULT_ACTION]: s__('EscalationPolicies|Email on-call user in schedule'),
}; };
export const defaultEscalationRule = { export const DEFAULT_ESCALATION_RULE = {
status: 'ACKNOWLEDGED', status: 'ACKNOWLEDGED',
elapsedTimeSeconds: 0, elapsedTimeSeconds: 0,
action: 'EMAIL_ONCALL_SCHEDULE_USER', action: 'EMAIL_ONCALL_SCHEDULE_USER',
...@@ -17,3 +19,4 @@ export const defaultEscalationRule = { ...@@ -17,3 +19,4 @@ export const defaultEscalationRule = {
}; };
export const addEscalationPolicyModalId = 'addEscalationPolicyModal'; export const addEscalationPolicyModalId = 'addEscalationPolicyModal';
export const editEscalationPolicyModalId = 'editEscalationPolicyModal';
import produce from 'immer';
const addEscalationPolicyToStore = (store, query, { escalationPolicyCreate }, variables) => {
const policy = escalationPolicyCreate?.escalationPolicy;
if (!policy) {
return;
}
const sourceData = store.readQuery({
query,
variables,
});
const data = produce(sourceData, (draftData) => {
draftData.project.incidentManagementEscalationPolicies.nodes.push(policy);
});
store.writeQuery({
query,
variables,
data,
});
};
export const hasErrors = ({ errors = [] }) => errors?.length;
export const updateStoreOnEscalationPolicyCreate = (store, query, data, variables) => {
if (!hasErrors(data)) {
addEscalationPolicyToStore(store, query, data, variables);
}
};
fragment EscalationPolicy on EscalationPolicyType {
id
name
description
rules {
id
status
elapsedTimeSeconds
oncallSchedule {
iid
name
}
}
}
#import "../fragments/escalation_policy.fragment.graphql"
mutation escalationPolicyCreate($input: EscalationPolicyCreateInput!) { mutation escalationPolicyCreate($input: EscalationPolicyCreateInput!) {
escalationPolicyCreate(input: $input) { escalationPolicyCreate(input: $input) {
escalationPolicy { escalationPolicy {
id ...EscalationPolicy
name
description
rules {
status
elapsedTimeSeconds
oncallSchedule {
iid
name
}
}
} }
errors errors
} }
......
#import "../fragments/escalation_policy.fragment.graphql"
query getEscalationPolicies($projectPath: ID!) {
project(fullPath: $projectPath) {
incidentManagementEscalationPolicies {
nodes {
...EscalationPolicy
}
}
}
}
...@@ -19,6 +19,7 @@ const apolloProvider = new VueApollo({ ...@@ -19,6 +19,7 @@ const apolloProvider = new VueApollo({
return defaultDataIdFromObject(object); return defaultDataIdFromObject(object);
}, },
}, },
assumeImmutableResults: true,
}, },
), ),
}); });
......
...@@ -237,7 +237,7 @@ export default { ...@@ -237,7 +237,7 @@ export default {
class="gl-mt-5" class="gl-mt-5"
:class="{ 'gl-border-bottom-0': !scheduleVisible }" :class="{ 'gl-border-bottom-0': !scheduleVisible }"
:body-class="{ 'gl-p-0': !scheduleVisible }" :body-class="{ 'gl-p-0': !scheduleVisible }"
header-class="gl-py-3" :header-class="{ 'gl-py-3': true, 'gl-rounded-small': !scheduleVisible }"
> >
<template #header> <template #header>
<div <div
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EscalationPolicy renders policy with rules 1`] = `
<gl-card-stub
bodyclass="[object Object]"
class="gl-mt-5"
footerclass=""
headerclass="[object Object]"
>
<gl-collapse-stub
visible="true"
>
<p
class="gl-text-gray-500 gl-mb-5"
>
Description 1 lives here
</p>
<div
class="gl-border-solid gl-border-1 gl-border-gray-100 gl-rounded-base gl-p-5"
>
<div
class="gl-mb-5"
>
<gl-icon-stub
class="gl-mr-3"
name="clock"
size="16"
/>
IF alert is not
acknowledged
in
<span
class="gl-font-weight-bold"
>
10 mins
</span>
<span
class="right-arrow"
>
<i
class="right-arrow-head"
/>
</span>
<gl-icon-stub
class="gl-mr-3"
name="notifications"
size="16"
/>
THEN
email on-call user in schedule
<span
class="gl-font-weight-bold"
>
Schedule to fill in
</span>
</div>
<div
class=""
>
<gl-icon-stub
class="gl-mr-3"
name="clock"
size="16"
/>
IF alert is not
resolved
in
<span
class="gl-font-weight-bold"
>
20 mins
</span>
<span
class="right-arrow"
>
<i
class="right-arrow-head"
/>
</span>
<gl-icon-stub
class="gl-mr-3"
name="notifications"
size="16"
/>
THEN
email on-call user in schedule
<span
class="gl-font-weight-bold"
>
Monitor schedule
</span>
</div>
</div>
</gl-collapse-stub>
</gl-card-stub>
`;
import { GlLink } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AddEscalationPolicyForm, { import AddEscalationPolicyForm, {
i18n, i18n,
} from 'ee/escalation_policies/components/add_edit_escalation_policy_form.vue'; } from 'ee/escalation_policies/components/add_edit_escalation_policy_form.vue';
import EscalationRule from 'ee/escalation_policies/components/escalation_rule.vue'; import EscalationRule from 'ee/escalation_policies/components/escalation_rule.vue';
import { defaultEscalationRule } from 'ee/escalation_policies/constants'; import { DEFAULT_ESCALATION_RULE } from 'ee/escalation_policies/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import mockPolicy from './mocks/mockPolicy.json';
import mockPolicies from './mocks/mockPolicies.json';
describe('AddEscalationPolicyForm', () => { describe('AddEscalationPolicyForm', () => {
let wrapper; let wrapper;
const projectPath = 'group/project'; const projectPath = 'group/project';
const createComponent = ({ props = {} } = {}) => { const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper( wrapper = shallowMountExtended(AddEscalationPolicyForm, {
shallowMount(AddEscalationPolicyForm, { propsData: {
propsData: { form: {
form: { name: mockPolicies[1].name,
name: mockPolicy.name, description: mockPolicies[1].description,
description: mockPolicy.description,
},
validationState: {
name: true,
rules: [],
},
...props,
}, },
provide: { validationState: {
projectPath, name: true,
rules: [],
}, },
mocks: { ...props,
$apollo: { },
queries: { schedules: { loading: false } }, provide: {
}, projectPath,
},
mocks: {
$apollo: {
queries: { schedules: { loading: false } },
}, },
}), },
); });
}; };
beforeEach(() => { beforeEach(() => {
...@@ -65,7 +63,7 @@ describe('AddEscalationPolicyForm', () => { ...@@ -65,7 +63,7 @@ describe('AddEscalationPolicyForm', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
const rules = findRules(); const rules = findRules();
expect(rules.length).toBe(2); expect(rules.length).toBe(2);
expect(rules.at(1).props('rule')).toMatchObject(defaultEscalationRule); expect(rules.at(1).props('rule')).toMatchObject(DEFAULT_ESCALATION_RULE);
}); });
it('should NOT emit updates when rule is added', async () => { it('should NOT emit updates when rule is added', async () => {
......
import { GlModal, GlAlert } from '@gitlab/ui'; import { GlModal, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
import AddEscalationPolicyForm from 'ee/escalation_policies/components/add_edit_escalation_policy_form.vue'; import AddEscalationPolicyForm from 'ee/escalation_policies/components/add_edit_escalation_policy_form.vue';
import AddEscalationPolicyModal, { import AddEscalationPolicyModal, {
i18n, i18n,
} from 'ee/escalation_policies/components/add_edit_escalation_policy_modal.vue'; } from 'ee/escalation_policies/components/add_edit_escalation_policy_modal.vue';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import mockPolicy from './mocks/mockPolicy.json'; import mockPolicies from './mocks/mockPolicies.json';
describe('AddEscalationPolicyModal', () => { describe('AddEscalationPolicyModal', () => {
let wrapper; let wrapper;
const projectPath = 'group/project'; const projectPath = 'group/project';
const mockHideModal = jest.fn(); const mockHideModal = jest.fn();
const mutate = jest.fn(); const mutate = jest.fn();
const mockPolicy = cloneDeep(mockPolicies[0]);
const createComponent = ({ escalationPolicy, data } = {}) => { const createComponent = ({ escalationPolicy, data } = {}) => {
wrapper = shallowMount(AddEscalationPolicyModal, { wrapper = shallowMount(AddEscalationPolicyModal, {
...@@ -60,14 +62,23 @@ describe('AddEscalationPolicyModal', () => { ...@@ -60,14 +62,23 @@ describe('AddEscalationPolicyModal', () => {
it('makes a request with form data to create an escalation policy', () => { it('makes a request with form data to create an escalation policy', () => {
mutate.mockResolvedValueOnce({}); mutate.mockResolvedValueOnce({});
findModal().vm.$emit('primary', { preventDefault: jest.fn() }); findModal().vm.$emit('primary', { preventDefault: jest.fn() });
const rules = mockPolicy.rules.map(
({ status, elapsedTimeSeconds, oncallSchedule: { id } }) => ({
status,
elapsedTimeSeconds,
oncallScheduleIid: id,
}),
);
expect(mutate).toHaveBeenCalledWith( expect(mutate).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
variables: { variables: {
input: { input: {
projectPath, projectPath,
...mockPolicy, ...mockPolicy,
rules,
}, },
}, },
update: expect.any(Function),
}), }),
); );
}); });
......
import { GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { cloneDeep } from 'lodash';
import EscalationPolicy from 'ee/escalation_policies/components/escalation_policy.vue';
import mockPolicies from './mocks/mockPolicies.json';
describe('EscalationPolicy', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(EscalationPolicy, {
propsData: {
policy: cloneDeep(mockPolicies[0]),
index: 0,
},
stubs: {
GlSprintf,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('renders policy with rules', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
import { GlEmptyState } from '@gitlab/ui'; import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import EscalationPoliciesWrapper from 'ee/escalation_policies/components/escalation_policies_wrapper.vue';
import EscalationPoliciesWrapper, { import EscalationPolicy from 'ee/escalation_policies/components/escalation_policy.vue';
i18n, import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
} from 'ee/escalation_policies/components/escalation_policies_wrapper.vue'; import mockEscalationPolicies from './mocks/mockPolicies.json';
describe('AlertManagementEmptyState', () => { describe('Escalation Policies Wrapper', () => {
let wrapper; let wrapper;
const emptyEscalationPoliciesSvgPath = 'illustration/path.svg'; const emptyEscalationPoliciesSvgPath = 'illustration/path.svg';
const projectPath = 'group/project';
function mountComponent() { function mountComponent({ loading = false, escalationPolicies = [] } = {}) {
wrapper = shallowMount(EscalationPoliciesWrapper, { const $apollo = {
queries: {
escalationPolicies: {
loading,
},
},
};
wrapper = shallowMountExtended(EscalationPoliciesWrapper, {
provide: { provide: {
emptyEscalationPoliciesSvgPath, emptyEscalationPoliciesSvgPath,
projectPath,
},
mocks: {
$apollo,
},
data() {
return {
escalationPolicies,
};
}, },
}); });
} }
...@@ -24,15 +41,40 @@ describe('AlertManagementEmptyState', () => { ...@@ -24,15 +41,40 @@ describe('AlertManagementEmptyState', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findLoader = () => wrapper.findComponent(GlLoadingIcon);
const findEmptyState = () => wrapper.findComponent(GlEmptyState); const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findEscalationPolicies = () => wrapper.findAllComponents(EscalationPolicy);
const findAddPolicyBtn = () =>
wrapper.findByRole('button', { name: EscalationPoliciesWrapper.i18n.addPolicy });
describe.each`
state | loading | escalationPolicies | showsEmptyState | showsLoader
${'is loading'} | ${true} | ${[]} | ${false} | ${true}
${'is empty'} | ${false} | ${[]} | ${true} | ${false}
${'has policies'} | ${false} | ${mockEscalationPolicies} | ${false} | ${false}
`(``, ({ state, loading, escalationPolicies, showsEmptyState, showsLoader }) => {
describe(`When ${state}`, () => {
beforeEach(() => {
mountComponent({
loading,
escalationPolicies,
});
});
it(`does ${loading ? 'show' : 'not show'} a loader`, () => {
expect(findLoader().exists()).toBe(showsLoader);
});
it(`does ${showsEmptyState ? 'show' : 'not show'} an empty state`, () => {
expect(findEmptyState().exists()).toBe(showsEmptyState);
});
it(`does ${escalationPolicies.length ? 'show' : 'not show'} escalation policies`, () => {
expect(findEscalationPolicies()).toHaveLength(escalationPolicies.length);
});
describe('Empty state', () => { it(`does ${escalationPolicies.length ? 'show' : 'not show'} "Add policy" button`, () => {
it('shows empty state and passed correct attributes to it', () => { expect(findAddPolicyBtn().exists()).toBe(Boolean(escalationPolicies.length));
expect(findEmptyState().exists()).toBe(true);
expect(findEmptyState().attributes()).toEqual({
title: i18n.emptyState.title,
description: i18n.emptyState.description,
svgpath: emptyEscalationPoliciesSvgPath,
}); });
}); });
}); });
......
import { GlDropdownItem, GlFormGroup, GlSprintf } from '@gitlab/ui'; import { GlDropdownItem, GlFormGroup, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { cloneDeep } from 'lodash'; import { cloneDeep } from 'lodash';
import EscalationRule, { i18n } from 'ee/escalation_policies/components/escalation_rule.vue'; import EscalationRule, { i18n } from 'ee/escalation_policies/components/escalation_rule.vue';
import { defaultEscalationRule, ACTIONS, ALERT_STATUSES } from 'ee/escalation_policies/constants'; import { DEFAULT_ESCALATION_RULE, ACTIONS, ALERT_STATUSES } from 'ee/escalation_policies/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
const mockSchedules = [ const mockSchedules = [
{ id: 1, name: 'schedule1' }, { id: 1, name: 'schedule1' },
...@@ -17,22 +16,20 @@ const invalidTimeMsg = i18n.fields.rules.invalidTimeValidationMsg; ...@@ -17,22 +16,20 @@ const invalidTimeMsg = i18n.fields.rules.invalidTimeValidationMsg;
describe('EscalationRule', () => { describe('EscalationRule', () => {
let wrapper; let wrapper;
const createComponent = ({ props = {} } = {}) => { const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper( wrapper = shallowMountExtended(EscalationRule, {
shallowMount(EscalationRule, { propsData: {
propsData: { rule: cloneDeep(DEFAULT_ESCALATION_RULE),
rule: cloneDeep(defaultEscalationRule), schedules: mockSchedules,
schedules: mockSchedules, schedulesLoading: false,
schedulesLoading: false, index: 0,
index: 0, isValid: false,
isValid: false, ...props,
...props, },
}, stubs: {
stubs: { GlFormGroup,
GlFormGroup, GlSprintf,
GlSprintf, },
}, });
}),
);
}; };
beforeEach(() => { beforeEach(() => {
......
[
{
"id": "37",
"name": "Escalation policy",
"description": "Description 1 lives here",
"rules": [
{
"id": "gid://gitlab/IncidentManagement::EscalationRule/22",
"status": "ACKNOWLEDGED",
"elapsedTimeSeconds": 10,
"oncallSchedule": {
"iid": "3",
"name": "Schedule to fill in"
}
},
{
"id": "gid://gitlab/IncidentManagement::EscalationRule/23",
"status": "RESOLVED",
"elapsedTimeSeconds": 20,
"oncallSchedule": {
"iid": "4",
"name": "Monitor schedule"
}
}
]
},
{
"id": "39",
"name": "Another escalation policy",
"description": "Description 1 lives here",
"rules": [
{
"id": "gid://gitlab/IncidentManagement::EscalationRule/48",
"status": "ACKNOWLEDGED",
"elapsedTimeSeconds": 30,
"oncallSchedule": {
"iid": "3",
"name": "Schedule to fill in"
}
}
]
}
]
{
"iid": "37",
"name": "Test ecsaltion policy",
"description": "Description 1 lives here",
"rules": []
}
...@@ -13063,9 +13063,15 @@ msgstr "" ...@@ -13063,9 +13063,15 @@ msgstr ""
msgid "EscalationPolicies|Add escalation policy" msgid "EscalationPolicies|Add escalation policy"
msgstr "" msgstr ""
msgid "EscalationPolicies|Add policy"
msgstr ""
msgid "EscalationPolicies|Create an escalation policy in GitLab" msgid "EscalationPolicies|Create an escalation policy in GitLab"
msgstr "" msgstr ""
msgid "EscalationPolicies|Delete escalation policy"
msgstr ""
msgid "EscalationPolicies|Edit escalation policy" msgid "EscalationPolicies|Edit escalation policy"
msgstr "" msgstr ""
...@@ -13075,12 +13081,18 @@ msgstr "" ...@@ -13075,12 +13081,18 @@ msgstr ""
msgid "EscalationPolicies|Email on-call user in schedule" msgid "EscalationPolicies|Email on-call user in schedule"
msgstr "" msgstr ""
msgid "EscalationPolicies|Escalation policies"
msgstr ""
msgid "EscalationPolicies|Escalation rules" msgid "EscalationPolicies|Escalation rules"
msgstr "" msgstr ""
msgid "EscalationPolicies|Failed to load oncall-schedules" msgid "EscalationPolicies|Failed to load oncall-schedules"
msgstr "" msgstr ""
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} %{then} THEN %{doAction} %{schedule}"
msgstr ""
msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes" msgid "EscalationPolicies|IF alert is not %{alertStatus} in %{minutes} minutes"
msgstr "" msgstr ""
...@@ -13096,6 +13108,9 @@ msgstr "" ...@@ -13096,6 +13108,9 @@ msgstr ""
msgid "EscalationPolicies|THEN %{doAction} %{schedule}" msgid "EscalationPolicies|THEN %{doAction} %{schedule}"
msgstr "" msgstr ""
msgid "EscalationPolicies|mins"
msgstr ""
msgid "Estimate" msgid "Estimate"
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