Commit d431f594 authored by Alexander Turinske's avatar Alexander Turinske Committed by Olena Horal-Koretska

Add filter to hide dismissed alerts

- add alert_filter component
- allow for hiding of dismissed alerts
- add tests
parent d36cd67d
<script>
import { GlFormCheckbox, GlFormGroup } from '@gitlab/ui';
import { s__ } from '~/locale';
import { DEFAULT_FILTERS } from './constants';
export default {
DEFAULT_DISMISSED_FILTER: true,
components: { GlFormCheckbox, GlFormGroup },
i18n: {
HIDE_DISMISSED_TITLE: s__('ThreatMonitoring|Hide dismissed alerts'),
},
methods: {
changeDismissedFilter(filtered) {
const newFilters = filtered ? DEFAULT_FILTERS : {};
this.$emit('filter-change', newFilters);
},
},
};
</script>
<template>
<div
class="gl-pt-3 gl-px-3 gl-bg-gray-10 gl-display-flex gl-justify-content-end gl-align-items-center"
>
<gl-form-group label-size="sm">
<gl-form-checkbox
class="gl-mt-3"
:checked="$options.DEFAULT_DISMISSED_FILTER"
@change="changeDismissedFilter"
>
{{ $options.i18n.HIDE_DISMISSED_TITLE }}
</gl-form-checkbox>
</gl-form-group>
</div>
</template>
...@@ -14,7 +14,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; ...@@ -14,7 +14,8 @@ import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { convertToSnakeCase } from '~/lib/utils/text_utility'; import { convertToSnakeCase } from '~/lib/utils/text_utility';
// TODO once backend is settled, update by either abstracting this out to app/assets/javascripts/graphql_shared or create new, modified query in #287757 // TODO once backend is settled, update by either abstracting this out to app/assets/javascripts/graphql_shared or create new, modified query in #287757
import getAlerts from '~/alert_management/graphql/queries/get_alerts.query.graphql'; import getAlerts from '~/alert_management/graphql/queries/get_alerts.query.graphql';
import { FIELDS, MESSAGES, PAGE_SIZE, STATUSES } from './constants'; import { DEFAULT_FILTERS, FIELDS, MESSAGES, PAGE_SIZE, STATUSES } from './constants';
import AlertFilters from './alert_filters.vue';
import AlertStatus from './alert_status.vue'; import AlertStatus from './alert_status.vue';
export default { export default {
...@@ -26,6 +27,7 @@ export default { ...@@ -26,6 +27,7 @@ export default {
}, },
components: { components: {
AlertStatus, AlertStatus,
AlertFilters,
GlAlert, GlAlert,
GlIntersectionObserver, GlIntersectionObserver,
GlLink, GlLink,
...@@ -47,6 +49,7 @@ export default { ...@@ -47,6 +49,7 @@ export default {
firstPageSize: this.$options.PAGE_SIZE, firstPageSize: this.$options.PAGE_SIZE,
projectPath: this.projectPath, projectPath: this.projectPath,
sort: this.sort, sort: this.sort,
...this.filters,
}; };
}, },
update: ({ project }) => project?.alertManagementAlerts.nodes || [], update: ({ project }) => project?.alertManagementAlerts.nodes || [],
...@@ -63,6 +66,7 @@ export default { ...@@ -63,6 +66,7 @@ export default {
alerts: [], alerts: [],
errored: false, errored: false,
errorMsg: '', errorMsg: '',
filters: DEFAULT_FILTERS,
isErrorAlertDismissed: false, isErrorAlertDismissed: false,
pageInfo: {}, pageInfo: {},
sort: 'STARTED_AT_DESC', sort: 'STARTED_AT_DESC',
...@@ -118,6 +122,9 @@ export default { ...@@ -118,6 +122,9 @@ export default {
this.errored = true; this.errored = true;
this.errorMsg = msg; this.errorMsg = msg;
}, },
handleFilterChange(newFilters) {
this.filters = newFilters;
},
handleStatusUpdate() { handleStatusUpdate() {
this.$apollo.queries.alerts.refetch(); this.$apollo.queries.alerts.refetch();
}, },
...@@ -126,6 +133,7 @@ export default { ...@@ -126,6 +133,7 @@ export default {
</script> </script>
<template> <template>
<div> <div>
<alert-filters @filter-change="handleFilterChange" />
<gl-alert v-if="showNoAlertsMsg" data-testid="threat-alerts-unconfigured" :dismissible="false"> <gl-alert v-if="showNoAlertsMsg" data-testid="threat-alerts-unconfigured" :dismissible="false">
<gl-sprintf :message="$options.i18n.MESSAGES.CONFIGURE"> <gl-sprintf :message="$options.i18n.MESSAGES.CONFIGURE">
<template #link="{ content }"> <template #link="{ content }">
......
...@@ -45,3 +45,5 @@ export const FIELDS = [ ...@@ -45,3 +45,5 @@ export const FIELDS = [
]; ];
export const PAGE_SIZE = 20; export const PAGE_SIZE = 20;
export const DEFAULT_FILTERS = { statuses: ['TRIGGERED', 'ACKNOWLEDGED'] };
import AlertFilters from 'ee/threat_monitoring/components/alerts/alert_filters.vue';
import { DEFAULT_FILTERS } from 'ee/threat_monitoring/components/alerts/constants';
import { GlFormCheckbox } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
describe('AlertFilters component', () => {
let wrapper;
const findGlFormCheckbox = () => wrapper.find(GlFormCheckbox);
const createWrapper = () => {
wrapper = shallowMount(AlertFilters);
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('default state', () => {
it('"hide dismissed checkbox" is checked', () => {
createWrapper();
const checkbox = findGlFormCheckbox();
expect(checkbox.exists()).toBe(true);
expect(checkbox.attributes('checked')).toBeTruthy();
});
});
describe('dismissed alerts filter', () => {
beforeEach(() => {
createWrapper();
});
it('emits an event with no filters on filter deselect', async () => {
const checkbox = findGlFormCheckbox();
checkbox.vm.$emit('change', false);
await wrapper.vm.$nextTick();
expect(wrapper.emitted('filter-change')).toEqual([[{}]]);
});
it('emits an event with the default filters on filter select', async () => {
const checkbox = findGlFormCheckbox();
checkbox.vm.$emit('change', true);
await wrapper.vm.$nextTick();
expect(wrapper.emitted('filter-change')).toEqual([[DEFAULT_FILTERS]]);
});
});
});
import { GlIntersectionObserver, GlSkeletonLoading } from '@gitlab/ui'; import { GlIntersectionObserver, GlSkeletonLoading } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import AlertsList from 'ee/threat_monitoring/components/alerts/alerts_list.vue'; import AlertsList from 'ee/threat_monitoring/components/alerts/alerts_list.vue';
import AlertFilters from 'ee/threat_monitoring/components/alerts/alert_filters.vue';
import AlertStatus from 'ee/threat_monitoring/components/alerts/alert_status.vue'; import AlertStatus from 'ee/threat_monitoring/components/alerts/alert_status.vue';
import { DEFAULT_FILTERS } from 'ee/threat_monitoring/components/alerts/constants';
import { mockAlerts } from '../../mock_data'; import { mockAlerts } from '../../mock_data';
const alerts = mockAlerts; const alerts = mockAlerts;
...@@ -25,7 +27,9 @@ describe('AlertsList component', () => { ...@@ -25,7 +27,9 @@ describe('AlertsList component', () => {
}, },
}, },
}; };
const defaultProps = { filters: DEFAULT_FILTERS };
const findAlertFilters = () => wrapper.find(AlertFilters);
const findUnconfiguredAlert = () => wrapper.find("[data-testid='threat-alerts-unconfigured']"); const findUnconfiguredAlert = () => wrapper.find("[data-testid='threat-alerts-unconfigured']");
const findErrorAlert = () => wrapper.find("[data-testid='threat-alerts-error']"); const findErrorAlert = () => wrapper.find("[data-testid='threat-alerts-error']");
const findStartedAtColumn = () => wrapper.find("[data-testid='threat-alerts-started-at']"); const findStartedAtColumn = () => wrapper.find("[data-testid='threat-alerts-started-at']");
...@@ -43,12 +47,14 @@ describe('AlertsList component', () => { ...@@ -43,12 +47,14 @@ describe('AlertsList component', () => {
mocks: { mocks: {
$apollo, $apollo,
}, },
propsData: defaultProps,
provide: { provide: {
documentationPath: '#', documentationPath: '#',
projectPath: '#', projectPath: '#',
}, },
stubs: { stubs: {
AlertStatus: true, AlertStatus: true,
AlertFilters: true,
GlAlert: true, GlAlert: true,
GlLoadingIcon: true, GlLoadingIcon: true,
GlIntersectionObserver: true, GlIntersectionObserver: true,
...@@ -70,6 +76,22 @@ describe('AlertsList component', () => { ...@@ -70,6 +76,22 @@ describe('AlertsList component', () => {
createWrapper({ data: { alerts, pageInfo } }); createWrapper({ data: { alerts, pageInfo } });
}); });
it('shows threat monitoring alert filters', () => {
expect(findAlertFilters().exists()).toBe(true);
});
it('does have the default filters initially', () => {
expect(wrapper.vm.filters).toEqual(DEFAULT_FILTERS);
});
it('does update its filters on filter event emitted', async () => {
const newFilters = { statuses: [] };
expect(wrapper.vm.filters).toEqual(DEFAULT_FILTERS);
findAlertFilters().vm.$emit('filter-change', newFilters);
await wrapper.vm.$nextTick();
expect(wrapper.vm.filters).toEqual(newFilters);
});
it('does show all columns', () => { it('does show all columns', () => {
expect(findStartedAtColumn().exists()).toBe(true); expect(findStartedAtColumn().exists()).toBe(true);
expect(findIdColumn().exists()).toBe(true); expect(findIdColumn().exists()).toBe(true);
......
...@@ -28698,6 +28698,9 @@ msgstr "" ...@@ -28698,6 +28698,9 @@ msgstr ""
msgid "ThreatMonitoring|Environment" msgid "ThreatMonitoring|Environment"
msgstr "" msgstr ""
msgid "ThreatMonitoring|Hide dismissed alerts"
msgstr ""
msgid "ThreatMonitoring|In review" msgid "ThreatMonitoring|In review"
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