diff options
Diffstat (limited to 'spec/frontend/alert_management')
12 files changed, 594 insertions, 170 deletions
diff --git a/spec/frontend/alert_management/components/alert_management_detail_spec.js b/spec/frontend/alert_management/components/alert_management_detail_spec.js index 14e45a4f563..daa730d3b9f 100644 --- a/spec/frontend/alert_management/components/alert_management_detail_spec.js +++ b/spec/frontend/alert_management/components/alert_management_detail_spec.js @@ -3,7 +3,7 @@ import { GlAlert, GlLoadingIcon, GlTable } from '@gitlab/ui'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import AlertDetails from '~/alert_management/components/alert_details.vue'; -import createIssueQuery from '~/alert_management/graphql/mutations/create_issue_from_alert.graphql'; +import createIssueMutation from '~/alert_management/graphql/mutations/create_issue_from_alert.mutation.graphql'; import { joinPaths } from '~/lib/utils/url_utility'; import { trackAlertsDetailsViewsOptions, @@ -19,18 +19,20 @@ describe('AlertDetails', () => { let mock; const projectPath = 'root/alerts'; const projectIssuesPath = 'root/alerts/-/issues'; + const projectId = '1'; const findDetailsTable = () => wrapper.find(GlTable); function mountComponent({ data, loading = false, mountMethod = shallowMount, stubs = {} } = {}) { wrapper = mountMethod(AlertDetails, { - propsData: { + provide: { alertId: 'alertId', projectPath, projectIssuesPath, + projectId, }, data() { - return { alert: { ...mockAlert }, ...data }; + return { alert: { ...mockAlert }, sidebarStatus: false, ...data }; }, mocks: { $apollo: { @@ -39,6 +41,7 @@ describe('AlertDetails', () => { alert: { loading, }, + sidebarStatus: {}, }, }, }, @@ -52,9 +55,7 @@ describe('AlertDetails', () => { afterEach(() => { if (wrapper) { - if (wrapper) { - wrapper.destroy(); - } + wrapper.destroy(); } mock.restore(); }); @@ -133,7 +134,7 @@ describe('AlertDetails', () => { it('should display "View issue" button that links the issue page when issue exists', () => { const issueIid = '3'; mountComponent({ - data: { alert: { ...mockAlert, issueIid } }, + data: { alert: { ...mockAlert, issueIid }, sidebarStatus: false }, }); expect(findViewIssueBtn().exists()).toBe(true); expect(findViewIssueBtn().attributes('href')).toBe(joinPaths(projectIssuesPath, issueIid)); @@ -146,8 +147,11 @@ describe('AlertDetails', () => { mountMethod: mount, data: { alert: { ...mockAlert, issueIid } }, }); - expect(findViewIssueBtn().exists()).toBe(false); - expect(findCreateIssueBtn().exists()).toBe(true); + + return wrapper.vm.$nextTick().then(() => { + expect(findViewIssueBtn().exists()).toBe(false); + expect(findCreateIssueBtn().exists()).toBe(true); + }); }); it('calls `$apollo.mutate` with `createIssueQuery`', () => { @@ -158,7 +162,7 @@ describe('AlertDetails', () => { findCreateIssueBtn().trigger('click'); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: createIssueQuery, + mutation: createIssueMutation, variables: { iid: mockAlert.iid, projectPath, @@ -208,6 +212,13 @@ describe('AlertDetails', () => { expect(wrapper.find(GlAlert).exists()).toBe(true); }); + it('renders html-errors correctly', () => { + mountComponent({ + data: { errored: true, sidebarErrorMessage: '<span data-testid="htmlError" />' }, + }); + expect(wrapper.find('[data-testid="htmlError"]').exists()).toBe(true); + }); + it('does not display an error when dismissed', () => { mountComponent({ data: { errored: true, isErrorDismissed: true } }); expect(wrapper.find(GlAlert).exists()).toBe(false); diff --git a/spec/frontend/alert_management/components/alert_management_empty_state_spec.js b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js new file mode 100644 index 00000000000..0d1214211d3 --- /dev/null +++ b/spec/frontend/alert_management/components/alert_management_empty_state_spec.js @@ -0,0 +1,54 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlEmptyState } from '@gitlab/ui'; +import AlertManagementEmptyState from '~/alert_management/components/alert_management_empty_state.vue'; + +describe('AlertManagementEmptyState', () => { + let wrapper; + + function mountComponent({ + props = { + alertManagementEnabled: false, + userCanEnableAlertManagement: false, + }, + stubs = {}, + } = {}) { + wrapper = shallowMount(AlertManagementEmptyState, { + propsData: { + enableAlertManagementPath: '/link', + emptyAlertSvgPath: 'illustration/path', + ...props, + }, + stubs, + }); + } + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + const EmptyState = () => wrapper.find(GlEmptyState); + + describe('Empty state', () => { + it('shows empty state', () => { + expect(EmptyState().exists()).toBe(true); + }); + + it('show OpsGenie integration state when OpsGenie mcv is true', () => { + mountComponent({ + props: { + alertManagementEnabled: false, + userCanEnableAlertManagement: false, + opsgenieMvcEnabled: true, + opsgenieMvcTargetUrl: 'https://opsgenie-url.com', + }, + }); + expect(EmptyState().props('title')).toBe('Opsgenie is enabled'); + }); + }); +}); diff --git a/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js new file mode 100644 index 00000000000..4644406c037 --- /dev/null +++ b/spec/frontend/alert_management/components/alert_management_list_wrapper_spec.js @@ -0,0 +1,57 @@ +import { shallowMount } from '@vue/test-utils'; +import AlertManagementList from '~/alert_management/components/alert_management_list_wrapper.vue'; +import { trackAlertListViewsOptions } from '~/alert_management/constants'; +import mockAlerts from '../mocks/alerts.json'; +import Tracking from '~/tracking'; + +describe('AlertManagementList', () => { + let wrapper; + + function mountComponent({ + props = { + alertManagementEnabled: false, + userCanEnableAlertManagement: false, + }, + data = {}, + stubs = {}, + } = {}) { + wrapper = shallowMount(AlertManagementList, { + propsData: { + projectPath: 'gitlab-org/gitlab', + enableAlertManagementPath: '/link', + populatingAlertsHelpUrl: '/help/help-page.md#populating-alert-data', + emptyAlertSvgPath: 'illustration/path', + ...props, + }, + data() { + return data; + }, + stubs, + }); + } + + beforeEach(() => { + mountComponent(); + }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('Snowplow tracking', () => { + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: { list: mockAlerts } }, + }); + }); + + it('should track alert list page views', () => { + const { category, action } = trackAlertListViewsOptions; + expect(Tracking.event).toHaveBeenCalledWith(category, action); + }); + }); +}); diff --git a/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js b/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js new file mode 100644 index 00000000000..fe08cf2c10a --- /dev/null +++ b/spec/frontend/alert_management/components/alert_management_sidebar_todo_spec.js @@ -0,0 +1,76 @@ +import { mount } from '@vue/test-utils'; +import SidebarTodo from '~/alert_management/components/sidebar/sidebar_todo.vue'; +import AlertMarkTodo from '~/alert_management/graphql/mutations/alert_todo_create.graphql'; +import mockAlerts from '../mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('Alert Details Sidebar To Do', () => { + let wrapper; + + function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) { + wrapper = mount(SidebarTodo, { + propsData: { + alert: { ...mockAlert }, + ...data, + sidebarCollapsed, + projectPath: 'projectPath', + }, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + }, + }, + }, + stubs, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + describe('updating the alert to do', () => { + const mockUpdatedMutationResult = { + data: { + updateAlertTodo: { + errors: [], + alert: {}, + }, + }, + }; + + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('renders a button for adding a To Do', () => { + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.find('[data-testid="alert-todo-button"]').text()).toBe('Add a To Do'); + }); + }); + + it('calls `$apollo.mutate` with `AlertMarkTodo` mutation and variables containing `iid`, `todoEvent`, & `projectPath`', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + + return wrapper.vm.$nextTick().then(() => { + wrapper.find('button').trigger('click'); + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: AlertMarkTodo, + variables: { + iid: '1527542', + projectPath: 'projectPath', + }, + }); + }); + }); + }); +}); diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_table_spec.js index 0154e5fa112..f316126432e 100644 --- a/spec/frontend/alert_management/components/alert_management_list_spec.js +++ b/spec/frontend/alert_management/components/alert_management_table_spec.js @@ -1,6 +1,5 @@ import { mount } from '@vue/test-utils'; import { - GlEmptyState, GlTable, GlAlert, GlLoadingIcon, @@ -11,28 +10,22 @@ import { GlTab, GlBadge, GlPagination, + GlSearchBoxByType, } from '@gitlab/ui'; import { visitUrl } from '~/lib/utils/url_utility'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; -import createFlash from '~/flash'; -import AlertManagementList from '~/alert_management/components/alert_management_list.vue'; -import { - ALERTS_STATUS_TABS, - trackAlertListViewsOptions, - trackAlertStatusUpdateOptions, -} from '~/alert_management/constants'; -import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql'; +import AlertManagementTable from '~/alert_management/components/alert_management_table.vue'; +import { ALERTS_STATUS_TABS, trackAlertStatusUpdateOptions } from '~/alert_management/constants'; +import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql'; import mockAlerts from '../mocks/alerts.json'; import Tracking from '~/tracking'; -jest.mock('~/flash'); - jest.mock('~/lib/utils/url_utility', () => ({ visitUrl: jest.fn().mockName('visitUrlMock'), joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths, })); -describe('AlertManagementList', () => { +describe('AlertManagementTable', () => { let wrapper; const findAlertsTable = () => wrapper.find(GlTable); @@ -49,6 +42,8 @@ describe('AlertManagementList', () => { const findSeverityFields = () => wrapper.findAll('[data-testid="severityField"]'); const findSeverityColumnHeader = () => wrapper.findAll('th').at(0); const findPagination = () => wrapper.find(GlPagination); + const findSearch = () => wrapper.find(GlSearchBoxByType); + const findIssueFields = () => wrapper.findAll('[data-testid="issueField"]'); const alertsCount = { open: 14, triggered: 10, @@ -66,11 +61,10 @@ describe('AlertManagementList', () => { loading = false, stubs = {}, } = {}) { - wrapper = mount(AlertManagementList, { + wrapper = mount(AlertManagementTable, { propsData: { projectPath: 'gitlab-org/gitlab', - enableAlertManagementPath: '/link', - emptyAlertSvgPath: 'illustration/path', + populatingAlertsHelpUrl: '/help/help-page.md#populating-alert-data', ...props, }, data() { @@ -92,7 +86,7 @@ describe('AlertManagementList', () => { } beforeEach(() => { - mountComponent(); + mountComponent({ data: { alerts: mockAlerts, alertsCount } }); }); afterEach(() => { @@ -101,12 +95,6 @@ describe('AlertManagementList', () => { } }); - describe('Empty state', () => { - it('shows empty state', () => { - expect(wrapper.find(GlEmptyState).exists()).toBe(true); - }); - }); - describe('Status Filter Tabs', () => { beforeEach(() => { mountComponent({ @@ -206,6 +194,15 @@ describe('AlertManagementList', () => { expect(findStatusDropdown().exists()).toBe(true); }); + it('does not display a dropdown status header', () => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + loading: false, + }); + expect(findStatusDropdown().contains('.dropdown-title')).toBe(false); + }); + it('shows correct severity icons', () => { mountComponent({ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, @@ -278,6 +275,37 @@ describe('AlertManagementList', () => { expect(visitUrl).toHaveBeenCalledWith('/1527542/details'); }); + describe('alert issue links', () => { + beforeEach(() => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + loading: false, + }); + }); + + it('shows "None" when no link exists', () => { + expect( + findIssueFields() + .at(0) + .text(), + ).toBe('None'); + }); + + it('renders a link when one exists', () => { + expect( + findIssueFields() + .at(1) + .text(), + ).toBe('#1'); + expect( + findIssueFields() + .at(1) + .attributes('href'), + ).toBe('/gitlab-org/gitlab/-/issues/1'); + }); + }); + describe('handle date fields', () => { it('should display time ago dates when values provided', () => { mountComponent({ @@ -289,7 +317,6 @@ describe('AlertManagementList', () => { iid: 1, status: 'acknowledged', startedAt: '2020-03-17T23:18:14.996Z', - endedAt: '2020-04-17T23:18:14.996Z', severity: 'high', assignees: { nodes: [] }, }, @@ -300,7 +327,7 @@ describe('AlertManagementList', () => { }, loading: false, }); - expect(findDateFields().length).toBe(2); + expect(findDateFields().length).toBe(1); }); it('should not display time ago dates when values not provided', () => { @@ -312,7 +339,6 @@ describe('AlertManagementList', () => { iid: 1, status: 'acknowledged', startedAt: null, - endedAt: null, severity: 'high', }, ], @@ -323,6 +349,40 @@ describe('AlertManagementList', () => { }); expect(findDateFields().exists()).toBe(false); }); + + describe('New Alert indicator', () => { + const oldAlert = mockAlerts[0]; + + const newAlert = { ...oldAlert, isNew: true }; + + it('should highlight the row when alert is new', () => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: { list: [newAlert] }, alertsCount, errored: false }, + loading: false, + }); + + expect( + findAlerts() + .at(0) + .classes(), + ).toContain('new-alert'); + }); + + it('should not highlight the row when alert is not new', () => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: { list: [oldAlert] }, alertsCount, errored: false }, + loading: false, + }); + + expect( + findAlerts() + .at(0) + .classes(), + ).not.toContain('new-alert'); + }); + }); }); }); @@ -388,14 +448,38 @@ describe('AlertManagementList', () => { }); }); - it('calls `createFlash` when request fails', () => { + it('shows an error when request fails', () => { jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); findFirstStatusOption().vm.$emit('click'); + wrapper.setData({ + errored: true, + }); - setImmediate(() => { - expect(createFlash).toHaveBeenCalledWith( - 'There was an error while updating the status of the alert. Please try again.', - ); + return wrapper.vm.$nextTick(() => { + expect(wrapper.find('[data-testid="alert-error"]').exists()).toBe(true); + }); + }); + + it('shows an error when response includes HTML errors', () => { + const mockUpdatedMutationErrorResult = { + data: { + updateAlertStatus: { + errors: ['<span data-testid="htmlError" />'], + alert: { + iid, + status: 'acknowledged', + }, + }, + }, + }; + + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationErrorResult); + findFirstStatusOption().vm.$emit('click'); + wrapper.setData({ errored: true }); + + return wrapper.vm.$nextTick(() => { + expect(wrapper.contains('[data-testid="alert-error"]')).toBe(true); + expect(wrapper.contains('[data-testid="htmlError"]')).toBe(true); }); }); }); @@ -410,11 +494,6 @@ describe('AlertManagementList', () => { }); }); - it('should track alert list page views', () => { - const { category, action } = trackAlertListViewsOptions; - expect(Tracking.event).toHaveBeenCalledWith(category, action); - }); - it('should track alert status updates', () => { Tracking.event.mockClear(); jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); @@ -438,14 +517,14 @@ describe('AlertManagementList', () => { it('does NOT show pagination control when list is smaller than default page size', () => { findStatusTabs().vm.$emit('input', 3); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findPagination().exists()).toBe(false); }); }); it('shows pagination control when list is larger than default page size', () => { findStatusTabs().vm.$emit('input', 0); - wrapper.vm.$nextTick(() => { + return wrapper.vm.$nextTick(() => { expect(findPagination().exists()).toBe(true); }); }); @@ -486,4 +565,26 @@ describe('AlertManagementList', () => { }); }); }); + + describe('Search', () => { + beforeEach(() => { + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alerts: { list: mockAlerts }, alertsCount, errored: false }, + loading: false, + }); + }); + + it('renders the search component', () => { + expect(findSearch().exists()).toBe(true); + }); + + it('sets the `searchTerm` graphql variable', () => { + const SEARCH_TERM = 'Simple Alert'; + + findSearch().vm.$emit('input', SEARCH_TERM); + + expect(wrapper.vm.$data.searchTerm).toBe(SEARCH_TERM); + }); + }); }); diff --git a/spec/frontend/alert_management/components/alert_metrics_spec.js b/spec/frontend/alert_management/components/alert_metrics_spec.js new file mode 100644 index 00000000000..c188363ddc2 --- /dev/null +++ b/spec/frontend/alert_management/components/alert_metrics_spec.js @@ -0,0 +1,67 @@ +import { shallowMount } from '@vue/test-utils'; +import waitForPromises from 'helpers/wait_for_promises'; +import AlertMetrics from '~/alert_management/components/alert_metrics.vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; + +jest.mock('~/monitoring/stores', () => ({ + monitoringDashboard: {}, +})); + +const mockEmbedName = 'MetricsEmbedStub'; + +jest.mock('~/monitoring/components/embeds/metric_embed.vue', () => ({ + name: mockEmbedName, + render(h) { + return h('div'); + }, +})); + +describe('Alert Metrics', () => { + let wrapper; + const mock = new MockAdapter(axios); + + function mountComponent({ props } = {}) { + wrapper = shallowMount(AlertMetrics, { + propsData: { + ...props, + }, + stubs: { + MetricEmbed: true, + }, + }); + } + + const findChart = () => wrapper.find({ name: mockEmbedName }); + const findEmptyState = () => wrapper.find({ ref: 'emptyState' }); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + afterAll(() => { + mock.restore(); + }); + + describe('Empty state', () => { + it('should display a message when metrics dashboard url is not provided ', () => { + mountComponent(); + expect(findChart().exists()).toBe(false); + expect(findEmptyState().text()).toBe("Metrics weren't available in the alerts payload."); + }); + }); + + describe('Chart', () => { + it('should be rendered when dashboard url is provided', async () => { + mountComponent({ props: { dashboardUrl: 'metrics.url' } }); + + await waitForPromises(); + await wrapper.vm.$nextTick(); + + expect(findEmptyState().exists()).toBe(false); + expect(findChart().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/alert_sidebar_status_spec.js deleted file mode 100644 index 94643966a43..00000000000 --- a/spec/frontend/alert_management/components/alert_sidebar_status_spec.js +++ /dev/null @@ -1,107 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; -import { trackAlertStatusUpdateOptions } from '~/alert_management/constants'; -import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue'; -import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.graphql'; -import Tracking from '~/tracking'; -import mockAlerts from '../mocks/alerts.json'; - -const mockAlert = mockAlerts[0]; - -describe('Alert Details Sidebar Status', () => { - let wrapper; - const findStatusDropdownItem = () => wrapper.find(GlDropdownItem); - const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon); - - function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) { - wrapper = shallowMount(AlertSidebarStatus, { - propsData: { - alert: { ...mockAlert }, - ...data, - sidebarCollapsed, - projectPath: 'projectPath', - }, - mocks: { - $apollo: { - mutate: jest.fn(), - queries: { - alert: { - loading, - }, - }, - }, - }, - stubs, - }); - } - - afterEach(() => { - if (wrapper) { - wrapper.destroy(); - } - }); - - describe('updating the alert status', () => { - const mockUpdatedMutationResult = { - data: { - updateAlertStatus: { - errors: [], - alert: { - status: 'acknowledged', - }, - }, - }, - }; - - beforeEach(() => { - mountComponent({ - data: { alert: mockAlert }, - sidebarCollapsed: false, - loading: false, - }); - }); - - it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => { - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); - findStatusDropdownItem().vm.$emit('click'); - - expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ - mutation: updateAlertStatus, - variables: { - iid: '1527542', - status: 'TRIGGERED', - projectPath: 'projectPath', - }, - }); - }); - - it('stops updating when the request fails', () => { - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); - findStatusDropdownItem().vm.$emit('click'); - expect(findStatusLoadingIcon().exists()).toBe(false); - expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered'); - }); - }); - - describe('Snowplow tracking', () => { - beforeEach(() => { - jest.spyOn(Tracking, 'event'); - mountComponent({ - props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, - data: { alert: mockAlert }, - loading: false, - }); - }); - - it('should track alert status updates', () => { - Tracking.event.mockClear(); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); - findStatusDropdownItem().vm.$emit('click'); - const status = findStatusDropdownItem().text(); - setImmediate(() => { - const { category, action, label } = trackAlertStatusUpdateOptions; - expect(Tracking.event).toHaveBeenCalledWith(category, action, { label, property: status }); - }); - }); - }); -}); diff --git a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js index 5dbd83dbdac..db086782424 100644 --- a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js +++ b/spec/frontend/alert_management/components/sidebar/alert_managment_sidebar_assignees_spec.js @@ -4,8 +4,8 @@ import MockAdapter from 'axios-mock-adapter'; import { GlDropdownItem } from '@gitlab/ui'; import SidebarAssignee from '~/alert_management/components/sidebar/sidebar_assignee.vue'; import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue'; -import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.graphql'; -import mockAlerts from '../mocks/alerts.json'; +import AlertSetAssignees from '~/alert_management/graphql/mutations/alert_set_assignees.mutation.graphql'; +import mockAlerts from '../../mocks/alerts.json'; const mockAlert = mockAlerts[0]; @@ -33,6 +33,7 @@ describe('Alert Details Sidebar Assignees', () => { ...data, sidebarCollapsed, projectPath: 'projectPath', + projectId: '1', }, mocks: { $apollo: { @@ -58,7 +59,7 @@ describe('Alert Details Sidebar Assignees', () => { describe('updating the alert status', () => { const mockUpdatedMutationResult = { data: { - updateAlertStatus: { + alertSetAssignees: { errors: [], alert: { assigneeUsernames: ['root'], @@ -69,7 +70,7 @@ describe('Alert Details Sidebar Assignees', () => { beforeEach(() => { mock = new MockAdapter(axios); - const path = '/autocomplete/users.json'; + const path = '/-/autocomplete/users.json'; const users = [ { avatar_url: @@ -124,6 +125,26 @@ describe('Alert Details Sidebar Assignees', () => { }); }); + it('shows an error when request contains error messages', () => { + wrapper.setData({ isDropdownSearching: false }); + const errorMutationResult = { + data: { + alertSetAssignees: { + errors: ['There was a problem for sure.'], + alert: {}, + }, + }, + }; + + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult); + + return wrapper.vm.$nextTick().then(() => { + const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0); + SideBarAssigneeItem.vm.$emit('click'); + expect(wrapper.emitted('alert-refresh')).toBeUndefined(); + }); + }); + it('stops updating and cancels loading when the request fails', () => { jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); wrapper.vm.updateAlertAssignees('root'); diff --git a/spec/frontend/alert_management/components/alert_sidebar_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js index 80c4d9e0650..5235ae63fee 100644 --- a/spec/frontend/alert_management/components/alert_sidebar_spec.js +++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_spec.js @@ -3,7 +3,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import AlertSidebar from '~/alert_management/components/alert_sidebar.vue'; import SidebarAssignees from '~/alert_management/components/sidebar/sidebar_assignees.vue'; -import mockAlerts from '../mocks/alerts.json'; +import mockAlerts from '../../mocks/alerts.json'; const mockAlert = mockAlerts[0]; @@ -11,19 +11,28 @@ describe('Alert Details Sidebar', () => { let wrapper; let mock; - function mountComponent({ - sidebarCollapsed = true, - mountMethod = shallowMount, - stubs = {}, - alert = {}, - } = {}) { + function mountComponent({ mountMethod = shallowMount, stubs = {}, alert = {} } = {}) { wrapper = mountMethod(AlertSidebar, { + data() { + return { + sidebarStatus: false, + }; + }, propsData: { alert, - sidebarCollapsed, + }, + provide: { projectPath: 'projectPath', + projectId: '1', }, stubs, + mocks: { + $apollo: { + queries: { + sidebarStatus: {}, + }, + }, + }, }); } @@ -41,7 +50,7 @@ describe('Alert Details Sidebar', () => { }); it('open as default', () => { - expect(wrapper.props('sidebarCollapsed')).toBe(true); + expect(wrapper.classes('right-sidebar-expanded')).toBe(true); }); it('should render side bar assignee dropdown', () => { diff --git a/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js new file mode 100644 index 00000000000..c2eaf540e9c --- /dev/null +++ b/spec/frontend/alert_management/components/sidebar/alert_sidebar_status_spec.js @@ -0,0 +1,129 @@ +import { mount } from '@vue/test-utils'; +import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui'; +import { trackAlertStatusUpdateOptions } from '~/alert_management/constants'; +import AlertSidebarStatus from '~/alert_management/components/sidebar/sidebar_status.vue'; +import updateAlertStatus from '~/alert_management/graphql/mutations/update_alert_status.mutation.graphql'; +import Tracking from '~/tracking'; +import mockAlerts from '../../mocks/alerts.json'; + +const mockAlert = mockAlerts[0]; + +describe('Alert Details Sidebar Status', () => { + let wrapper; + const findStatusDropdown = () => wrapper.find(GlDropdown); + const findStatusDropdownItem = () => wrapper.find(GlDropdownItem); + const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon); + + function mountComponent({ data, sidebarCollapsed = true, loading = false, stubs = {} } = {}) { + wrapper = mount(AlertSidebarStatus, { + propsData: { + alert: { ...mockAlert }, + ...data, + sidebarCollapsed, + projectPath: 'projectPath', + }, + mocks: { + $apollo: { + mutate: jest.fn(), + queries: { + alert: { + loading, + }, + }, + }, + }, + stubs, + }); + } + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('Alert Sidebar Dropdown Status', () => { + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('displays status dropdown', () => { + expect(findStatusDropdown().exists()).toBe(true); + }); + + it('displays the dropdown status header', () => { + expect(findStatusDropdown().contains('.dropdown-title')).toBe(true); + }); + + describe('updating the alert status', () => { + const mockUpdatedMutationResult = { + data: { + updateAlertStatus: { + errors: [], + alert: { + status: 'acknowledged', + }, + }, + }, + }; + + beforeEach(() => { + mountComponent({ + data: { alert: mockAlert }, + sidebarCollapsed: false, + loading: false, + }); + }); + + it('calls `$apollo.mutate` with `updateAlertStatus` mutation and variables containing `iid`, `status`, & `projectPath`', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(mockUpdatedMutationResult); + findStatusDropdownItem().vm.$emit('click'); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: updateAlertStatus, + variables: { + iid: '1527542', + status: 'TRIGGERED', + projectPath: 'projectPath', + }, + }); + }); + + it('stops updating when the request fails', () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); + findStatusDropdownItem().vm.$emit('click'); + expect(findStatusLoadingIcon().exists()).toBe(false); + expect(wrapper.find('[data-testid="status"]').text()).toBe('Triggered'); + }); + }); + + describe('Snowplow tracking', () => { + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + mountComponent({ + props: { alertManagementEnabled: true, userCanEnableAlertManagement: true }, + data: { alert: mockAlert }, + loading: false, + }); + }); + + it('should track alert status updates', () => { + Tracking.event.mockClear(); + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({}); + findStatusDropdownItem().vm.$emit('click'); + const status = findStatusDropdownItem().text(); + setImmediate(() => { + const { category, action, label } = trackAlertStatusUpdateOptions; + expect(Tracking.event).toHaveBeenCalledWith(category, action, { + label, + property: status, + }); + }); + }); + }); + }); +}); diff --git a/spec/frontend/alert_management/components/alert_management_system_note_spec.js b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js index 87dc36cc7cb..8dd663e55d9 100644 --- a/spec/frontend/alert_management/components/alert_management_system_note_spec.js +++ b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js @@ -1,6 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import SystemNote from '~/alert_management/components/system_notes/system_note.vue'; -import mockAlerts from '../mocks/alerts.json'; +import mockAlerts from '../../mocks/alerts.json'; const mockAlert = mockAlerts[1]; @@ -28,7 +28,11 @@ describe('Alert Details System Note', () => { }); it('renders the correct system note', () => { - expect(wrapper.find('.note-wrapper').attributes('id')).toBe('note_1628'); + const noteId = wrapper.find('.note-wrapper').attributes('id'); + const iconRoute = wrapper.find('use').attributes('href'); + + expect(noteId).toBe('note_1628'); + expect(iconRoute.includes('user')).toBe(true); }); }); }); diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/alert_management/mocks/alerts.json index 312d1756790..f63019d1e5c 100644 --- a/spec/frontend/alert_management/mocks/alerts.json +++ b/spec/frontend/alert_management/mocks/alerts.json @@ -20,6 +20,7 @@ "endedAt": "2020-04-17T23:18:14.996Z", "status": "ACKNOWLEDGED", "assignees": { "nodes": [{ "username": "root" }] }, + "issueIid": "1", "notes": { "nodes": [ { @@ -32,7 +33,8 @@ "name": "Administrator", "username": "root", "webUrl": "http://192.168.1.4:3000/root" - } + }, + "systemNoteIconName": "user" } ] } |