Commit 1273b6b5 authored by Tristan Read's avatar Tristan Read Committed by Olena Horal-Koretska

Surface alert details in a tab on incidents

parent 1922007e
......@@ -468,7 +468,6 @@ export default {
<component
:is="descriptionComponent"
v-if="state.descriptionHtml"
:can-update="canUpdate"
:description-html="state.descriptionHtml"
:description-text="state.descriptionText"
......
query getHighlightBarInfo($iid: String!, $fullPath: ID!) {
query getAlert($iid: String!, $fullPath: ID!) {
project(fullPath: $fullPath) {
issue(iid: $iid) {
alertManagementAlert {
iid
title
detailsUrl
createdAt
severity
status
startedAt
eventCount
monitoringTool
service
description
endedAt
details
}
}
}
......
<script>
import { GlLink } from '@gitlab/ui';
import { formatDate } from '~/lib/utils/datetime_utility';
import getHighlightBarInfo from './graphql/queries/get_highlight_bar_info.graphql';
export default {
components: {
GlLink,
},
inject: ['fullPath', 'iid'],
apollo: {
props: {
alert: {
query: getHighlightBarInfo,
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update: data => data.project?.issue?.alertManagementAlert,
type: Object,
required: true,
},
},
computed: {
startTime() {
return formatDate(this.alert.createdAt, 'yyyy-mm-dd Z');
return formatDate(this.alert.startedAt, 'yyyy-mm-dd Z');
},
},
};
......@@ -30,7 +22,6 @@ export default {
<template>
<div
v-if="alert"
class="gl-border-solid gl-border-1 gl-border-gray-100 gl-p-5 gl-mb-3 gl-rounded-base gl-display-flex gl-justify-content-space-between"
>
<div class="text-truncate gl-pr-3">
......
<script>
import { GlTab, GlTabs } from '@gitlab/ui';
import DescriptionComponent from '../description.vue';
import HighlightBar from './highlight_bar/higlight_bar.vue';
import HighlightBar from './highlight_bar.vue';
import createFlash from '~/flash';
import { s__ } from '~/locale';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
import getAlert from './graphql/queries/get_alert.graphql';
export default {
components: {
AlertDetailsTable,
DescriptionComponent,
GlTab,
GlTabs,
DescriptionComponent,
HighlightBar,
},
inject: ['fullPath', 'iid'],
apollo: {
alert: {
query: getAlert,
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update(data) {
return data?.project?.issue?.alertManagementAlert;
},
error() {
createFlash({
message: s__('Incident|There was an issue loading alert data. Please try again.'),
});
},
},
},
data() {
return {
alert: null,
};
},
computed: {
loading() {
return this.$apollo.queries.alert.loading;
},
alertTableFields() {
if (this.alert) {
const { detailsUrl, __typename, ...restDetails } = this.alert;
return restDetails;
}
return null;
},
},
};
</script>
<template>
<div>
<gl-tabs content-class="gl-reset-line-height" class="gl-mt-n3" data-testid="incident-tabs">
<gl-tab :title="__('Summary')">
<highlight-bar />
<gl-tab :title="s__('Incident|Summary')">
<highlight-bar v-if="alert" :alert="alert" />
<description-component v-bind="$attrs" />
</gl-tab>
<gl-tab v-if="alert" class="alert-management-details" :title="s__('Incident|Alert details')">
<alert-details-table :alert="alertTableFields" :loading="loading" />
</gl-tab>
</gl-tabs>
</div>
</template>
......@@ -19,7 +19,7 @@ export default {
},
},
tableHeader: {
[s__('AlertManagement|Full Alert Payload')]: s__('AlertManagement|Value'),
[s__('AlertManagement|Key')]: s__('AlertManagement|Value'),
},
computed: {
items() {
......@@ -33,7 +33,7 @@ export default {
</script>
<template>
<gl-table
class="alert-management-details-table"
class="alert-management-details-table gl-mb-0!"
:busy="loading"
:empty-text="s__('AlertManagement|No alert data to display.')"
:items="items"
......
......@@ -6,7 +6,10 @@
@include gl-border-0;
@include gl-p-5;
border-color: transparent;
&:not(:last-child) {
border-bottom: 1px solid $table-border-color;
}
&:first-child {
div {
......@@ -31,6 +34,12 @@
}
}
}
&:last-child {
&::after {
content: none !important;
}
}
}
}
......
---
title: Surface alert details in a tab on incidents
merge_request: 41850
author:
type: added
......@@ -2214,9 +2214,6 @@ msgstr ""
msgid "AlertManagement|Events"
msgstr ""
msgid "AlertManagement|Full Alert Payload"
msgstr ""
msgid "AlertManagement|High"
msgstr ""
......@@ -2226,6 +2223,9 @@ msgstr ""
msgid "AlertManagement|Issue"
msgstr ""
msgid "AlertManagement|Key"
msgstr ""
msgid "AlertManagement|Low"
msgstr ""
......@@ -13415,6 +13415,15 @@ msgstr ""
msgid "Incidents"
msgstr ""
msgid "Incident|Alert details"
msgstr ""
msgid "Incident|Summary"
msgstr ""
msgid "Incident|There was an issue loading alert data. Please try again."
msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
......
......@@ -36,7 +36,7 @@ describe('Issuable output', () => {
const findStickyHeader = () => wrapper.find('[data-testid="issue-sticky-header"]');
const mountComponent = (props = {}) => {
const mountComponent = (props = {}, options = {}) => {
wrapper = mount(IssuableApp, {
propsData: { ...appProps, ...props },
provide: {
......@@ -45,7 +45,9 @@ describe('Issuable output', () => {
},
stubs: {
HighlightBar: true,
IncidentTabs: true,
},
...options,
});
};
......@@ -582,10 +584,23 @@ describe('Issuable output', () => {
describe('when using incident tabs description wrapper', () => {
beforeEach(() => {
mountComponent({
mountComponent(
{
descriptionComponent: IncidentTabs,
showTitleBorder: false,
});
},
{
mocks: {
$apollo: {
queries: {
alert: {
loading: false,
},
},
},
},
},
);
});
it('renders the description component', () => {
......
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar/higlight_bar.vue';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar.vue';
import { formatDate } from '~/lib/utils/datetime_utility';
jest.mock('~/lib/utils/datetime_utility');
......@@ -9,7 +9,7 @@ describe('Highlight Bar', () => {
let wrapper;
const alert = {
createdAt: '2020-05-29T10:39:22Z',
startedAt: '2020-05-29T10:39:22Z',
detailsUrl: 'http://127.0.0.1:3000/root/unique-alerts/-/alert_management/1/details',
eventCount: 1,
title: 'Alert 1',
......@@ -17,12 +17,8 @@ describe('Highlight Bar', () => {
const mountComponent = () => {
wrapper = shallowMount(HighlightBar, {
provide: {
fullPath: 'project/id',
iid: '1',
},
data() {
return { alert };
propsData: {
alert,
},
});
};
......@@ -50,7 +46,7 @@ describe('Highlight Bar', () => {
const formattedDate = '2020-05-29 UTC';
formatDate.mockReturnValueOnce(formattedDate);
mountComponent();
expect(formatDate).toHaveBeenCalledWith(alert.createdAt, 'yyyy-mm-dd Z');
expect(formatDate).toHaveBeenCalledWith(alert.startedAt, 'yyyy-mm-dd Z');
expect(wrapper.text()).toContain(formattedDate);
});
......
import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
import INVALID_URL from '~/lib/utils/invalid_url';
import IncidentTabs from '~/issue_show/components/incidents/incident_tabs.vue';
import { descriptionProps } from '../mock_data';
import { descriptionProps } from '../../mock_data';
import DescriptionComponent from '~/issue_show/components/description.vue';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar/higlight_bar.vue';
import HighlightBar from '~/issue_show/components/incidents/highlight_bar.vue';
import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue';
const mockAlert = {
__typename: 'AlertManagementAlert',
detailsUrl: INVALID_URL,
iid: '1',
};
describe('Incident Tabs component', () => {
let wrapper;
const mountComponent = () => {
const mountComponent = (data = {}) => {
wrapper = shallowMount(IncidentTabs, {
propsData: {
...descriptionProps,
......@@ -16,30 +24,76 @@ describe('Incident Tabs component', () => {
stubs: {
DescriptionComponent: true,
},
provide: {
fullPath: '',
iid: '',
},
data() {
return { alert: mockAlert, ...data };
},
mocks: {
$apollo: {
queries: {
alert: {
loading: true,
},
},
},
},
});
};
beforeEach(() => {
mountComponent();
});
const findTabs = () => wrapper.findAll(GlTab);
const findSummaryTab = () => findTabs().at(0);
const findAlertDetailsTab = () => findTabs().at(1);
const findAlertDetailsComponent = () => wrapper.find(AlertDetailsTable);
const findDescriptionComponent = () => wrapper.find(DescriptionComponent);
const findHighlightBarComponent = () => wrapper.find(HighlightBar);
describe('default state', () => {
it('renders the summary tab', async () => {
expect(findTabs()).toHaveLength(1);
describe('empty state', () => {
beforeEach(() => {
mountComponent({ alert: null });
});
it('does not show the alert details tab', () => {
expect(findAlertDetailsComponent().exists()).toBe(false);
expect(findHighlightBarComponent().exists()).toBe(false);
});
});
describe('with an alert present', () => {
beforeEach(() => {
mountComponent();
});
it('renders the summary tab', () => {
expect(findSummaryTab().exists()).toBe(true);
expect(findSummaryTab().attributes('title')).toBe('Summary');
});
it('renders the alert details tab', () => {
expect(findAlertDetailsTab().exists()).toBe(true);
expect(findAlertDetailsTab().attributes('title')).toBe('Alert details');
});
it('renders the alert details table with the correct props', () => {
const alert = { iid: mockAlert.iid };
expect(findAlertDetailsComponent().props('alert')).toEqual(alert);
expect(findAlertDetailsComponent().props('loading')).toBe(true);
});
it('renders the description component with highlight bar', () => {
expect(findDescriptionComponent().exists()).toBe(true);
expect(findHighlightBarComponent().exists()).toBe(true);
});
it('renders the highlight bar component with the correct props', () => {
const alert = { detailsUrl: mockAlert.detailsUrl };
expect(findHighlightBarComponent().props('alert')).toMatchObject(alert);
});
it('passes all props to the description component', () => {
expect(findDescriptionComponent().props()).toMatchObject(descriptionProps);
});
......
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