Commit 24ded9bb authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '296715-add-incident-column' into 'master'

Create incident column for threat alerts

See merge request gitlab-org/gitlab!59821
parents 01ba92f9 a7d06d42
...@@ -221,7 +221,7 @@ to set the status for each alert: ...@@ -221,7 +221,7 @@ to set the status for each alert:
By default, the list doesn't display resolved or dismissed alerts. To show these alerts, clear the By default, the list doesn't display resolved or dismissed alerts. To show these alerts, clear the
checkbox **Hide dismissed alerts**. checkbox **Hide dismissed alerts**.
![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_11.png) ![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_12.png)
Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page). Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page).
......
...@@ -16,7 +16,15 @@ import { joinPaths } from '~/lib/utils/url_utility'; ...@@ -16,7 +16,15 @@ import { joinPaths } from '~/lib/utils/url_utility';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import AlertFilters from './alert_filters.vue'; import AlertFilters from './alert_filters.vue';
import AlertStatus from './alert_status.vue'; import AlertStatus from './alert_status.vue';
import { DEFAULT_FILTERS, FIELDS, MESSAGES, PAGE_SIZE, STATUSES, DOMAIN } from './constants'; import {
DEFAULT_FILTERS,
FIELDS,
MESSAGES,
PAGE_SIZE,
STATUSES,
DOMAIN,
CLOSED,
} from './constants';
export default { export default {
PAGE_SIZE, PAGE_SIZE,
...@@ -25,6 +33,7 @@ export default { ...@@ -25,6 +33,7 @@ export default {
FIELDS, FIELDS,
MESSAGES, MESSAGES,
STATUSES, STATUSES,
CLOSED,
}, },
components: { components: {
AlertStatus, AlertStatus,
...@@ -119,6 +128,18 @@ export default { ...@@ -119,6 +128,18 @@ export default {
this.sort = `${sortingColumn}_${sortingDirection}`; this.sort = `${sortingColumn}_${sortingDirection}`;
}, },
getIssueMeta({ issue: { iid, state } }) {
return {
state: state === 'closed' ? `(${this.$options.i18n.CLOSED})` : '',
link: joinPaths(
gon.relative_url_root || '/',
this.projectPath,
'-',
'issues/incident',
iid,
),
};
},
handleAlertError(msg) { handleAlertError(msg) {
this.errored = true; this.errored = true;
this.errorMsg = msg; this.errorMsg = msg;
...@@ -198,6 +219,18 @@ export default { ...@@ -198,6 +219,18 @@ export default {
</div> </div>
</template> </template>
<template #cell(issue)="{ item }">
<gl-link
v-if="item.issue"
v-gl-tooltip
:title="item.issue.title"
data-testid="threat-alerts-issue"
:href="getIssueMeta(item).link"
>
#{{ item.issue.iid }} {{ getIssueMeta(item).state }}
</gl-link>
</template>
<template #cell(status)="{ item }"> <template #cell(status)="{ item }">
<alert-status <alert-status
:alert="item" :alert="item"
......
...@@ -41,6 +41,11 @@ export const FIELDS = [ ...@@ -41,6 +41,11 @@ export const FIELDS = [
tdClass: `gl-pl-6! gl-text-right`, tdClass: `gl-pl-6! gl-text-right`,
sortable: true, sortable: true,
}, },
{
key: 'issue',
label: s__('ThreatMonitoring|Incident'),
thClass: 'gl-bg-white! gl-w-15p',
},
{ {
key: 'status', key: 'status',
label: s__('ThreatMonitoring|Status'), label: s__('ThreatMonitoring|Status'),
...@@ -60,3 +65,5 @@ export const DOMAIN = 'threat_monitoring'; ...@@ -60,3 +65,5 @@ export const DOMAIN = 'threat_monitoring';
export const DEBOUNCE = 250; export const DEBOUNCE = 250;
export const ALL = { key: 'ALL', value: __('All') }; export const ALL = { key: 'ALL', value: __('All') };
export const CLOSED = __('closed');
---
title: Create incident column for threat alerts
merge_request: 59821
author:
type: added
...@@ -44,6 +44,8 @@ describe('AlertsList component', () => { ...@@ -44,6 +44,8 @@ describe('AlertsList component', () => {
const findStartedAtColumnHeader = () => wrapper.findByTestId('threat-alerts-started-at-header'); const findStartedAtColumnHeader = () => wrapper.findByTestId('threat-alerts-started-at-header');
const findIdColumn = () => wrapper.findByTestId('threat-alerts-id'); const findIdColumn = () => wrapper.findByTestId('threat-alerts-id');
const findEventCountColumn = () => wrapper.findByTestId('threat-alerts-event-count'); const findEventCountColumn = () => wrapper.findByTestId('threat-alerts-event-count');
const findIssueColumn = () => wrapper.findByTestId('threat-alerts-issue');
const findIssueColumnAt = (id) => wrapper.findAllByTestId('threat-alerts-issue').at(id);
const findStatusColumn = () => wrapper.findComponent(AlertStatus); const findStatusColumn = () => wrapper.findComponent(AlertStatus);
const findStatusColumnHeader = () => wrapper.findByTestId('threat-alerts-status-header'); const findStatusColumnHeader = () => wrapper.findByTestId('threat-alerts-status-header');
const findEmptyState = () => wrapper.findByTestId('threat-alerts-empty-state'); const findEmptyState = () => wrapper.findByTestId('threat-alerts-empty-state');
...@@ -128,6 +130,7 @@ describe('AlertsList component', () => { ...@@ -128,6 +130,7 @@ describe('AlertsList component', () => {
expect(findStartedAtColumn().exists()).toBe(true); expect(findStartedAtColumn().exists()).toBe(true);
expect(findIdColumn().exists()).toBe(true); expect(findIdColumn().exists()).toBe(true);
expect(findEventCountColumn().exists()).toBe(true); expect(findEventCountColumn().exists()).toBe(true);
expect(findIssueColumn().exists()).toBe(true);
expect(findStatusColumn().exists()).toBe(true); expect(findStatusColumn().exists()).toBe(true);
}); });
...@@ -171,6 +174,35 @@ describe('AlertsList component', () => { ...@@ -171,6 +174,35 @@ describe('AlertsList component', () => {
it('navigates to the alert details page on title click', () => { it('navigates to the alert details page on title click', () => {
expect(findIdColumn().attributes('href')).toBe('/alerts/01'); expect(findIdColumn().attributes('href')).toBe('/alerts/01');
}); });
describe('issue column', () => {
it('only displays text when an issue is created', () => {
expect(wrapper.findAllByTestId('threat-alerts-issue').length).toBe(2);
});
it.each`
description | id | text | link
${'when an issue is created and is open'} | ${0} | ${'#5'} | ${'/#/-/issues/incident/5'}
${'when an issue is created and is closed'} | ${1} | ${'#6 (closed)'} | ${'/#/-/issues/incident/6'}
`('displays the correct text $description', ({ id, text, link }) => {
expect(findIssueColumnAt(id).text()).toBe(text);
expect(findIssueColumnAt(id).attributes('href')).toBe(link);
});
describe('gon.relative_url_root', () => {
beforeAll(() => {
gon.relative_url_root = '/test';
});
afterEach(() => {
gon.relative_url_root = '';
});
it('creates the correct href when the gon.relative_url_root is set', () => {
expect(findIssueColumnAt(0).attributes('href')).toBe('/test/#/-/issues/incident/5');
});
});
});
}); });
describe('empty state', () => { describe('empty state', () => {
......
...@@ -97,7 +97,7 @@ export const mockAlerts = [ ...@@ -97,7 +97,7 @@ export const mockAlerts = [
assignees: { nodes: [] }, assignees: { nodes: [] },
eventCount: '1', eventCount: '1',
issueIid: null, issueIid: null,
issue: { iid: '1', state: '', title: '' }, issue: { iid: '5', state: 'opened', title: 'Issue 01' },
title: 'Issue 01', title: 'Issue 01',
severity: 'HIGH', severity: 'HIGH',
status: 'TRIGGERED', status: 'TRIGGERED',
...@@ -108,7 +108,7 @@ export const mockAlerts = [ ...@@ -108,7 +108,7 @@ export const mockAlerts = [
eventCount: '2', eventCount: '2',
assignees: { nodes: [] }, assignees: { nodes: [] },
issueIid: null, issueIid: null,
issue: { iid: '2', state: '', title: '' }, issue: { iid: '6', state: 'closed', title: 'Issue 02' },
severity: 'CRITICAL', severity: 'CRITICAL',
title: 'Issue 02', title: 'Issue 02',
status: 'ACKNOWLEDGED', status: 'ACKNOWLEDGED',
...@@ -119,7 +119,7 @@ export const mockAlerts = [ ...@@ -119,7 +119,7 @@ export const mockAlerts = [
eventCount: '3', eventCount: '3',
assignees: { nodes: [] }, assignees: { nodes: [] },
issueIid: null, issueIid: null,
issue: { iid: '3', state: '', title: '' }, issue: null,
severity: 'MEDIUM', severity: 'MEDIUM',
title: 'Issue 03', title: 'Issue 03',
status: 'RESOLVED', status: 'RESOLVED',
...@@ -129,7 +129,7 @@ export const mockAlerts = [ ...@@ -129,7 +129,7 @@ export const mockAlerts = [
iid: '04', iid: '04',
assignees: { nodes: [] }, assignees: { nodes: [] },
issueIid: null, issueIid: null,
issue: { iid: '4', state: '', title: '' }, issue: null,
severity: 'LOW', severity: 'LOW',
eventCount: '4', eventCount: '4',
title: 'Issue 04', title: 'Issue 04',
......
...@@ -32730,6 +32730,9 @@ msgstr "" ...@@ -32730,6 +32730,9 @@ msgstr ""
msgid "ThreatMonitoring|In review" msgid "ThreatMonitoring|In review"
msgstr "" msgstr ""
msgid "ThreatMonitoring|Incident"
msgstr ""
msgid "ThreatMonitoring|Name" msgid "ThreatMonitoring|Name"
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