Commit 6ed38caf authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '217670-alerts-count' into 'master'

Add alerts count badge to alerts tabs

See merge request gitlab-org/gitlab!32582
parents 7a533749 25ad9b16
...@@ -10,14 +10,16 @@ import { ...@@ -10,14 +10,16 @@ import {
GlDropdownItem, GlDropdownItem,
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge,
} from '@gitlab/ui'; } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility'; import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { fetchPolicies } from '~/lib/graphql';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import getAlerts from '../graphql/queries/getAlerts.query.graphql'; import getAlerts from '../graphql/queries/get_alerts.query.graphql';
import getAlertsCountByStatus from '../graphql/queries/get_count_by_status.query.graphql';
import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants'; import { ALERTS_STATUS, ALERTS_STATUS_TABS, ALERTS_SEVERITY_LABELS } from '../constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql'; import updateAlertStatus from '../graphql/mutations/update_alert_status.graphql';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
...@@ -88,8 +90,8 @@ export default { ...@@ -88,8 +90,8 @@ export default {
GlIcon, GlIcon,
GlTabs, GlTabs,
GlTab, GlTab,
GlBadge,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
projectPath: { projectPath: {
type: String, type: String,
...@@ -114,6 +116,7 @@ export default { ...@@ -114,6 +116,7 @@ export default {
}, },
apollo: { apollo: {
alerts: { alerts: {
fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
query: getAlerts, query: getAlerts,
variables() { variables() {
return { return {
...@@ -122,12 +125,23 @@ export default { ...@@ -122,12 +125,23 @@ export default {
}; };
}, },
update(data) { update(data) {
return data.project.alertManagementAlerts.nodes; return data.project?.alertManagementAlerts?.nodes;
}, },
error() { error() {
this.errored = true; this.errored = true;
}, },
}, },
alertsCount: {
query: getAlertsCountByStatus,
variables() {
return {
projectPath: this.projectPath,
};
},
update(data) {
return data.project?.alertManagementAlertStatusCounts;
},
},
}, },
data() { data() {
return { return {
...@@ -139,7 +153,9 @@ export default { ...@@ -139,7 +153,9 @@ export default {
}, },
computed: { computed: {
showNoAlertsMsg() { showNoAlertsMsg() {
return !this.errored && !this.loading && !this.alerts?.length && !this.isAlertDismissed; return (
!this.errored && !this.loading && this.alertsCount?.all === 0 && !this.isAlertDismissed
);
}, },
showErrorMsg() { showErrorMsg() {
return this.errored && !this.isErrorAlertDismissed; return this.errored && !this.isErrorAlertDismissed;
...@@ -171,6 +187,7 @@ export default { ...@@ -171,6 +187,7 @@ export default {
}) })
.then(() => { .then(() => {
this.$apollo.queries.alerts.refetch(); this.$apollo.queries.alerts.refetch();
this.$apollo.queries.alertsCount.refetch();
}) })
.catch(() => { .catch(() => {
createFlash( createFlash(
...@@ -196,10 +213,13 @@ export default { ...@@ -196,10 +213,13 @@ export default {
{{ $options.i18n.errorMsg }} {{ $options.i18n.errorMsg }}
</gl-alert> </gl-alert>
<gl-tabs v-if="glFeatures.alertListStatusFilteringEnabled" @input="filterAlertsByStatus"> <gl-tabs @input="filterAlertsByStatus">
<gl-tab v-for="tab in $options.statusTabs" :key="tab.status"> <gl-tab v-for="tab in $options.statusTabs" :key="tab.status">
<template slot="title"> <template slot="title">
<span>{{ tab.title }}</span> <span>{{ tab.title }}</span>
<gl-badge v-if="alertsCount" pill size="sm" class="gl-tab-counter-badge">
{{ alertsCount[tab.status.toLowerCase()] }}
</gl-badge>
</template> </template>
</gl-tab> </gl-tab>
</gl-tabs> </gl-tabs>
......
#import "./listItem.fragment.graphql" #import "./list_item.fragment.graphql"
fragment AlertDetailItem on AlertManagementAlert { fragment AlertDetailItem on AlertManagementAlert {
...AlertListItem ...AlertListItem
......
#import "../fragments/listItem.fragment.graphql" #import "../fragments/list_item.fragment.graphql"
query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!]) { query getAlerts($projectPath: ID!, $statuses: [AlertManagementStatus!]) {
project(fullPath: $projectPath) { project(fullPath: $projectPath) {
......
query getAlertsCount($projectPath: ID!) {
project(fullPath: $projectPath) {
alertManagementAlertStatusCounts {
all
open
acknowledged
resolved
triggered
}
}
}
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
class Projects::AlertManagementController < Projects::ApplicationController class Projects::AlertManagementController < Projects::ApplicationController
before_action :authorize_read_alert_management_alert! before_action :authorize_read_alert_management_alert!
before_action do before_action do
push_frontend_feature_flag(:alert_list_status_filtering_enabled)
push_frontend_feature_flag(:alert_management_create_alert_issue) push_frontend_feature_flag(:alert_management_create_alert_issue)
push_frontend_feature_flag(:alert_assignee, project) push_frontend_feature_flag(:alert_assignee, project)
end end
......
---
title: Organize alerts by status tabs
merge_request: 32582
author:
type: added
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
GlDropdownItem, GlDropdownItem,
GlIcon, GlIcon,
GlTab, GlTab,
GlBadge,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } 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';
...@@ -33,10 +34,19 @@ describe('AlertManagementList', () => { ...@@ -33,10 +34,19 @@ describe('AlertManagementList', () => {
const findLoader = () => wrapper.find(GlLoadingIcon); const findLoader = () => wrapper.find(GlLoadingIcon);
const findStatusDropdown = () => wrapper.find(GlDropdown); const findStatusDropdown = () => wrapper.find(GlDropdown);
const findStatusFilterTabs = () => wrapper.findAll(GlTab); const findStatusFilterTabs = () => wrapper.findAll(GlTab);
const findStatusFilterBadge = () => wrapper.findAll(GlBadge);
const findDateFields = () => wrapper.findAll(TimeAgo); const findDateFields = () => wrapper.findAll(TimeAgo);
const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem); const findFirstStatusOption = () => findStatusDropdown().find(GlDropdownItem);
const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]'); const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]');
const alertsCount = {
acknowledged: 6,
all: 16,
open: 14,
resolved: 2,
triggered: 10,
};
function mountComponent({ function mountComponent({
props = { props = {
alertManagementEnabled: false, alertManagementEnabled: false,
...@@ -44,7 +54,6 @@ describe('AlertManagementList', () => { ...@@ -44,7 +54,6 @@ describe('AlertManagementList', () => {
}, },
data = {}, data = {},
loading = false, loading = false,
alertListStatusFilteringEnabled = false,
stubs = {}, stubs = {},
} = {}) { } = {}) {
wrapper = mount(AlertManagementList, { wrapper = mount(AlertManagementList, {
...@@ -54,11 +63,6 @@ describe('AlertManagementList', () => { ...@@ -54,11 +63,6 @@ describe('AlertManagementList', () => {
emptyAlertSvgPath: 'illustration/path', emptyAlertSvgPath: 'illustration/path',
...props, ...props,
}, },
provide: {
glFeatures: {
alertListStatusFilteringEnabled,
},
},
data() { data() {
return data; return data;
}, },
...@@ -93,42 +97,25 @@ describe('AlertManagementList', () => { ...@@ -93,42 +97,25 @@ describe('AlertManagementList', () => {
}); });
describe('Status Filter Tabs', () => { describe('Status Filter Tabs', () => {
describe('alertListStatusFilteringEnabled feature flag enabled', () => { beforeEach(() => {
beforeEach(() => { mountComponent({
mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, data: { alerts: mockAlerts, alertsCount },
data: { alerts: mockAlerts }, loading: false,
loading: false, stubs: {
alertListStatusFilteringEnabled: true, GlTab: true,
stubs: { },
GlTab: true,
},
});
});
it('should display filter tabs for all statuses', () => {
const tabs = findStatusFilterTabs().wrappers;
tabs.forEach((tab, i) => {
expect(tab.text()).toContain(ALERTS_STATUS_TABS[i].title);
});
}); });
}); });
describe('alertListStatusFilteringEnabled feature flag disabled', () => { it('should display filter tabs with alerts count badge for each status', () => {
beforeEach(() => { const tabs = findStatusFilterTabs().wrappers;
mountComponent({ const badges = findStatusFilterBadge();
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts },
loading: false,
alertListStatusFilteringEnabled: false,
stubs: {
GlTab: true,
},
});
});
it('should NOT display tabs', () => { tabs.forEach((tab, i) => {
expect(findStatusFilterTabs()).not.toExist(); const status = ALERTS_STATUS_TABS[i].status.toLowerCase();
expect(tab.text()).toContain(ALERTS_STATUS_TABS[i].title);
expect(badges.at(i).text()).toContain(alertsCount[status]);
}); });
}); });
}); });
...@@ -137,7 +124,7 @@ describe('AlertManagementList', () => { ...@@ -137,7 +124,7 @@ describe('AlertManagementList', () => {
it('loading state', () => { it('loading state', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: null }, data: { alerts: null, alertsCount: null },
loading: true, loading: true,
}); });
expect(findAlertsTable().exists()).toBe(true); expect(findAlertsTable().exists()).toBe(true);
...@@ -152,7 +139,7 @@ describe('AlertManagementList', () => { ...@@ -152,7 +139,7 @@ describe('AlertManagementList', () => {
it('error state', () => { it('error state', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: null, errored: true }, data: { alerts: null, alertsCount: null, errored: true },
loading: false, loading: false,
}); });
expect(findAlertsTable().exists()).toBe(true); expect(findAlertsTable().exists()).toBe(true);
...@@ -169,7 +156,7 @@ describe('AlertManagementList', () => { ...@@ -169,7 +156,7 @@ describe('AlertManagementList', () => {
it('empty state', () => { it('empty state', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: [], errored: false }, data: { alerts: [], alertsCount: { all: 0 }, errored: false },
loading: false, loading: false,
}); });
expect(findAlertsTable().exists()).toBe(true); expect(findAlertsTable().exists()).toBe(true);
...@@ -186,7 +173,7 @@ describe('AlertManagementList', () => { ...@@ -186,7 +173,7 @@ describe('AlertManagementList', () => {
it('has data state', () => { it('has data state', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false }, data: { alerts: mockAlerts, alertsCount, errored: false },
loading: false, loading: false,
}); });
expect(findLoader().exists()).toBe(false); expect(findLoader().exists()).toBe(false);
...@@ -202,7 +189,7 @@ describe('AlertManagementList', () => { ...@@ -202,7 +189,7 @@ describe('AlertManagementList', () => {
it('displays status dropdown', () => { it('displays status dropdown', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false }, data: { alerts: mockAlerts, alertsCount, errored: false },
loading: false, loading: false,
}); });
expect(findStatusDropdown().exists()).toBe(true); expect(findStatusDropdown().exists()).toBe(true);
...@@ -211,7 +198,7 @@ describe('AlertManagementList', () => { ...@@ -211,7 +198,7 @@ describe('AlertManagementList', () => {
it('shows correct severity icons', () => { it('shows correct severity icons', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false }, data: { alerts: mockAlerts, alertsCount, errored: false },
loading: false, loading: false,
}); });
...@@ -228,7 +215,7 @@ describe('AlertManagementList', () => { ...@@ -228,7 +215,7 @@ describe('AlertManagementList', () => {
it('renders severity text', () => { it('renders severity text', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false }, data: { alerts: mockAlerts, alertsCount, errored: false },
loading: false, loading: false,
}); });
...@@ -242,7 +229,7 @@ describe('AlertManagementList', () => { ...@@ -242,7 +229,7 @@ describe('AlertManagementList', () => {
it('navigates to the detail page when alert row is clicked', () => { it('navigates to the detail page when alert row is clicked', () => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false }, data: { alerts: mockAlerts, alertsCount, errored: false },
loading: false, loading: false,
}); });
...@@ -266,6 +253,7 @@ describe('AlertManagementList', () => { ...@@ -266,6 +253,7 @@ describe('AlertManagementList', () => {
severity: 'high', severity: 'high',
}, },
], ],
alertsCount,
errored: false, errored: false,
}, },
loading: false, loading: false,
...@@ -286,6 +274,7 @@ describe('AlertManagementList', () => { ...@@ -286,6 +274,7 @@ describe('AlertManagementList', () => {
severity: 'high', severity: 'high',
}, },
], ],
alertsCount,
errored: false, errored: false,
}, },
loading: false, loading: false,
...@@ -312,7 +301,7 @@ describe('AlertManagementList', () => { ...@@ -312,7 +301,7 @@ describe('AlertManagementList', () => {
beforeEach(() => { beforeEach(() => {
mountComponent({ mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: mockAlerts, errored: false }, data: { alerts: mockAlerts, alertsCount, errored: false },
loading: false, loading: false,
}); });
}); });
......
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