diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-03-16 21:18:33 +0300 |
commit | f64a639bcfa1fc2bc89ca7db268f594306edfd7c (patch) | |
tree | a2c3c2ebcc3b45e596949db485d6ed18ffaacfa1 /spec/frontend/alerts_settings | |
parent | bfbc3e0d6583ea1a91f627528bedc3d65ba4b10f (diff) |
Add latest changes from gitlab-org/gitlab@13-10-stable-eev13.10.0-rc40
Diffstat (limited to 'spec/frontend/alerts_settings')
-rw-r--r-- | spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap | 700 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js | 28 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/components/alerts_settings_form_spec.js | 227 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js | 231 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/components/mocks/apollo_mock.js | 17 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/mocks/alert_fields.json (renamed from spec/frontend/alerts_settings/mocks/alertFields.json) | 0 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/mocks/parsed_mapping.json | 122 | ||||
-rw-r--r-- | spec/frontend/alerts_settings/utils/mapping_transformations_spec.js | 34 |
8 files changed, 855 insertions, 504 deletions
diff --git a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap index eb2b82a0211..1f8429af7dd 100644 --- a/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap +++ b/spec/frontend/alerts_settings/components/__snapshots__/alerts_settings_form_spec.js.snap @@ -4,125 +4,151 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`] <form class="gl-mt-6" > - <h5 - class="gl-font-lg gl-my-5" - > - Add new integrations - </h5> - <div - class="form-group gl-form-group" - id="integration-type" - role="group" + class="tabs gl-tabs" + id="__BVID__6" > - <label - class="d-block col-form-label" - for="integration-type" - id="integration-type__BV_label_" - > - 1. Select integration type - </label> + <!----> <div - class="bv-no-focus-ring" + class="" > - <select - class="gl-form-select mw-100 custom-select" - id="__BVID__8" + <ul + class="nav gl-tabs-nav" + id="__BVID__6__BV_tab_controls_" + role="tablist" > - <option - value="" + <!----> + <li + class="nav-item" + role="presentation" > - Select integration type - </option> - <option - value="HTTP" + <a + aria-controls="__BVID__8" + aria-posinset="1" + aria-selected="true" + aria-setsize="3" + class="nav-link active gl-tab-nav-item gl-tab-nav-item-active gl-tab-nav-item-active-indigo" + href="#" + id="__BVID__8___BV_tab_button__" + role="tab" + target="_self" + > + Configure details + </a> + </li> + <li + class="nav-item" + role="presentation" > - HTTP Endpoint - </option> - <option - value="PROMETHEUS" + <a + aria-controls="__BVID__19" + aria-disabled="true" + aria-posinset="2" + aria-selected="false" + aria-setsize="3" + class="nav-link disabled disabled gl-tab-nav-item" + href="#" + id="__BVID__19___BV_tab_button__" + role="tab" + tabindex="-1" + target="_self" + > + View credentials + </a> + </li> + <li + class="nav-item" + role="presentation" > - External Prometheus - </option> - </select> - - <!----> - <!----> - <!----> - <!----> + <a + aria-controls="__BVID__41" + aria-disabled="true" + aria-posinset="3" + aria-selected="false" + aria-setsize="3" + class="nav-link disabled disabled gl-tab-nav-item" + href="#" + id="__BVID__41___BV_tab_button__" + role="tab" + tabindex="-1" + target="_self" + > + Send test alert + </a> + </li> + <!----> + </ul> </div> - </div> - - <transition-stub - class="gl-mt-3" - css="true" - enteractiveclass="collapsing" - enterclass="" - entertoclass="collapse show" - leaveactiveclass="collapsing" - leaveclass="collapse show" - leavetoclass="collapse" - > <div - class="collapse" - id="__BVID__10" - style="display: none;" + class="tab-content gl-tab-content" + id="__BVID__6__BV_tab_container_" > - <div> + <transition-stub + css="true" + enteractiveclass="" + enterclass="" + entertoclass="show" + leaveactiveclass="" + leaveclass="show" + leavetoclass="" + mode="out-in" + name="" + > <div - class="form-group gl-form-group" - id="name-integration" - role="group" + aria-hidden="false" + aria-labelledby="__BVID__8___BV_tab_button__" + class="tab-pane active" + id="__BVID__8" + role="tabpanel" + style="" > - <label - class="d-block col-form-label" - for="name-integration" - id="name-integration__BV_label_" - > - 2. Name integration - </label> <div - class="bv-no-focus-ring" + class="form-group gl-form-group" + id="integration-type" + role="group" > - <input - class="gl-form-input form-control" - id="__BVID__15" - placeholder="Enter integration name" - type="text" - /> - <!----> - <!----> - <!----> + <label + class="d-block col-form-label" + for="integration-type" + id="integration-type__BV_label_" + > + 1.Select integration type + </label> + <div + class="bv-no-focus-ring" + > + <select + class="gl-form-select gl-max-w-full custom-select" + id="__BVID__13" + > + <option + value="" + > + Select integration type + </option> + <option + value="HTTP" + > + HTTP Endpoint + </option> + <option + value="PROMETHEUS" + > + External Prometheus + </option> + </select> + + <!----> + <!----> + <!----> + <!----> + </div> </div> - </div> - - <div - class="form-group gl-form-group" - id="integration-webhook" - role="group" - > - <label - class="d-block col-form-label" - for="integration-webhook" - id="integration-webhook__BV_label_" - > - 3. Set up webhook - </label> + <div - class="bv-no-focus-ring" + class="gl-mt-3" > - <span> - Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the - <a - class="gl-link gl-display-inline-block" - href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html" - rel="noopener noreferrer" - target="_blank" - > - GitLab documentation - </a> - to learn more about configuring your endpoint. - </span> + <!----> <label class="gl-display-flex gl-flex-direction-column gl-mb-0 gl-w-max-content gl-my-4 gl-font-weight-normal" @@ -166,241 +192,333 @@ exports[`AlertsSettingsForm with default values renders the initial template 1`] <!----> - <div - class="gl-my-4" + <!----> + </div> + + <div + class="gl-display-flex gl-justify-content-start gl-py-3" + > + <button + class="btn js-no-auto-disable btn-confirm btn-md gl-button" + data-testid="integration-form-submit" + type="submit" > + <!----> + + <!----> + <span - class="gl-font-weight-bold" + class="gl-button-text" > - Webhook URL - + Save integration + </span> + </button> + + <button + class="btn gl-ml-3 js-no-auto-disable btn-default btn-md gl-button" + type="reset" + > + <!----> + <!----> + + <span + class="gl-button-text" + > + Cancel and close + </span> + </button> + </div> + </div> + </transition-stub> + + <transition-stub + css="true" + enteractiveclass="" + enterclass="" + entertoclass="show" + leaveactiveclass="" + leaveclass="show" + leavetoclass="" + mode="out-in" + name="" + > + <div + aria-hidden="true" + aria-labelledby="__BVID__19___BV_tab_button__" + class="tab-pane disabled" + id="__BVID__19" + role="tabpanel" + style="display: none;" + > + <span> + Utilize the URL and authorization key below to authorize an external service to send alerts to GitLab. Review your external service's documentation to learn where to add these details, and the + <a + class="gl-link gl-display-inline-block" + href="https://docs.gitlab.com/ee/operations/incident_management/alert_integrations.html" + rel="noopener noreferrer" + target="_blank" + > + GitLab documentation + </a> + to learn more about configuring your endpoint. + </span> + + <fieldset + class="form-group gl-form-group" + id="integration-webhook" + > + <!----> + <div + class="bv-no-focus-ring" + role="group" + tabindex="-1" + > <div - id="url" - readonly="readonly" + class="gl-my-4" > + <span + class="gl-font-weight-bold" + > + + Webhook URL + + </span> + <div - class="input-group" - role="group" + id="url" + readonly="readonly" > - <!----> - <!----> - - <input - class="gl-form-input form-control" - id="url" - readonly="readonly" - type="text" - /> - <div - class="input-group-append" + class="input-group" + role="group" > - <button - aria-label="Copy this value" - class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon" - data-clipboard-text="" - title="Copy" - type="button" + <!----> + <!----> + + <input + class="gl-form-input form-control" + id="url" + readonly="readonly" + type="text" + /> + + <div + class="input-group-append" > - <!----> - - <svg - aria-hidden="true" - class="gl-button-icon gl-icon s16" - data-testid="copy-to-clipboard-icon" + <button + aria-label="Copy this value" + class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon" + data-clipboard-text="" + title="Copy" + type="button" > - <use - href="#copy-to-clipboard" - /> - </svg> - - <!----> - </button> + <!----> + + <svg + aria-hidden="true" + class="gl-button-icon gl-icon s16" + data-testid="copy-to-clipboard-icon" + > + <use + href="#copy-to-clipboard" + /> + </svg> + + <!----> + </button> + </div> + <!----> </div> - <!----> </div> </div> - </div> - - <div - class="gl-my-4" - > - <span - class="gl-font-weight-bold" - > - - Authorization key - - </span> <div - class="gl-mb-3" - id="authorization-key" - readonly="readonly" + class="gl-my-4" > + <span + class="gl-font-weight-bold" + > + + Authorization key + + </span> + <div - class="input-group" - role="group" + class="gl-mb-3" + id="authorization-key" + readonly="readonly" > - <!----> - <!----> - - <input - class="gl-form-input form-control" - id="authorization-key" - readonly="readonly" - type="text" - /> - <div - class="input-group-append" + class="input-group" + role="group" > - <button - aria-label="Copy this value" - class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon" - data-clipboard-text="" - title="Copy" - type="button" + <!----> + <!----> + + <input + class="gl-form-input form-control" + id="authorization-key" + readonly="readonly" + type="text" + /> + + <div + class="input-group-append" > - <!----> - - <svg - aria-hidden="true" - class="gl-button-icon gl-icon s16" - data-testid="copy-to-clipboard-icon" + <button + aria-label="Copy this value" + class="btn gl-m-0! btn-default btn-md gl-button btn-default-secondary btn-icon" + data-clipboard-text="" + title="Copy" + type="button" > - <use - href="#copy-to-clipboard" - /> - </svg> - - <!----> - </button> + <!----> + + <svg + aria-hidden="true" + class="gl-button-icon gl-icon s16" + data-testid="copy-to-clipboard-icon" + > + <use + href="#copy-to-clipboard" + /> + </svg> + + <!----> + </button> + </div> + <!----> </div> - <!----> </div> </div> - - <button - class="btn btn-default btn-md disabled gl-button" - disabled="disabled" - type="button" - > - <!----> - - <!----> - - <span - class="gl-button-text" - > - - Reset Key - - </span> - </button> - + <!----> + <!----> <!----> </div> - <!----> - <!----> - <!----> - </div> - </div> - - <div - class="form-group gl-form-group" - id="test-integration" - role="group" - > - <label - class="d-block col-form-label" - for="test-integration" - id="test-integration__BV_label_" - > - 4. Sample alert payload (optional) - </label> - <div - class="bv-no-focus-ring" + </fieldset> + + <button + class="btn btn-danger btn-md disabled gl-button" + disabled="disabled" + type="button" > - <span> - Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to test the integration (optional). - </span> + <!----> - <textarea - class="gl-form-input gl-form-textarea gl-my-3 form-control is-valid" - disabled="disabled" - id="test-payload" - placeholder="{ \\"events\\": [{ \\"application\\": \\"Name of application\\" }] }" - style="resize: none; overflow-y: scroll;" - wrap="soft" - /> <!----> + + <span + class="gl-button-text" + > + + Reset Key + + </span> + </button> + + <button + class="btn gl-ml-3 js-no-auto-disable btn-default btn-md gl-button" + type="reset" + > <!----> + <!----> - </div> + + <span + class="gl-button-text" + > + Cancel and close + </span> + </button> + + <!----> </div> - - <!----> - - <!----> - </div> + </transition-stub> - <div - class="gl-display-flex gl-justify-content-start gl-py-3" + <transition-stub + css="true" + enteractiveclass="" + enterclass="" + entertoclass="show" + leaveactiveclass="" + leaveclass="show" + leavetoclass="" + mode="out-in" + name="" > - <button - class="btn js-no-auto-disable btn-success btn-md gl-button" - data-testid="integration-form-submit" - type="submit" + <div + aria-hidden="true" + aria-labelledby="__BVID__41___BV_tab_button__" + class="tab-pane disabled" + id="__BVID__41" + role="tabpanel" + style="display: none;" > - <!----> - - <!----> - - <span - class="gl-button-text" + <fieldset + class="form-group gl-form-group" + id="test-integration" > - Save integration - - </span> - </button> - - <button - class="btn gl-mx-3 js-no-auto-disable btn-success btn-md disabled gl-button btn-success-secondary" - data-testid="integration-test-and-submit" - disabled="disabled" - type="button" - > - <!----> + <!----> + <div + class="bv-no-focus-ring" + role="group" + tabindex="-1" + > + <span> + Provide an example payload from the monitoring tool you intend to integrate with. This will allow you to send an alert to an active GitLab alerting point. + </span> + + <textarea + class="gl-form-input gl-form-textarea gl-my-3 form-control is-valid" + id="test-payload" + placeholder="{ \\"events\\": [{ \\"application\\": \\"Name of application\\" }] }" + style="resize: none; overflow-y: scroll;" + wrap="soft" + /> + <!----> + <!----> + <!----> + </div> + </fieldset> - <!----> - - <span - class="gl-button-text" + <button + class="btn js-no-auto-disable btn-confirm btn-md gl-button" + data-testid="send-test-alert" + type="button" > - Save and test payload - </span> - </button> - - <button - class="btn js-no-auto-disable btn-default btn-md gl-button" - type="reset" - > - <!----> + <!----> + + <!----> + + <span + class="gl-button-text" + > + + Send + + </span> + </button> - <!----> - - <span - class="gl-button-text" + <button + class="btn gl-ml-3 js-no-auto-disable btn-default btn-md gl-button" + type="reset" > - Cancel - </span> - </button> - </div> + <!----> + + <!----> + + <span + class="gl-button-text" + > + Cancel and close + </span> + </button> + </div> + </transition-stub> + <!----> </div> - </transition-stub> + </div> </form> `; diff --git a/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js b/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js index 7e1d1acb62c..dba9c8be669 100644 --- a/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js +++ b/spec/frontend/alerts_settings/components/alert_mapping_builder_spec.js @@ -1,10 +1,10 @@ import { GlIcon, GlFormInput, GlDropdown, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import AlertMappingBuilder, { i18n } from '~/alerts_settings/components/alert_mapping_builder.vue'; -import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json'; import * as transformationUtils from '~/alerts_settings/utils/mapping_transformations'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; -import alertFields from '../mocks/alertFields.json'; +import alertFields from '../mocks/alert_fields.json'; +import parsedMapping from '../mocks/parsed_mapping.json'; describe('AlertMappingBuilder', () => { let wrapper; @@ -12,8 +12,8 @@ describe('AlertMappingBuilder', () => { function mountComponent() { wrapper = shallowMount(AlertMappingBuilder, { propsData: { - parsedPayload: parsedMapping.samplePayload.payloadAlerFields.nodes, - savedMapping: parsedMapping.storedMapping.nodes, + parsedPayload: parsedMapping.payloadAlerFields, + savedMapping: parsedMapping.payloadAttributeMappings, alertFields, }, }); @@ -33,6 +33,15 @@ describe('AlertMappingBuilder', () => { const findColumnInRow = (row, column) => wrapper.findAll('.gl-display-table-row').at(row).findAll('.gl-display-table-cell ').at(column); + const getDropdownContent = (dropdown, types) => { + const searchBox = dropdown.findComponent(GlSearchBoxByType); + const dropdownItems = dropdown.findAllComponents(GlDropdownItem); + const mappingOptions = parsedMapping.payloadAlerFields.filter(({ type }) => + types.includes(type), + ); + return { searchBox, dropdownItems, mappingOptions }; + }; + it('renders column captions', () => { expect(findColumnInRow(0, 0).text()).toContain(i18n.columns.gitlabKeyTitle); expect(findColumnInRow(0, 2).text()).toContain(i18n.columns.payloadKeyTitle); @@ -63,10 +72,7 @@ describe('AlertMappingBuilder', () => { it('renders mapping dropdown for each field', () => { alertFields.forEach(({ types }, index) => { const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown); - const searchBox = dropdown.findComponent(GlSearchBoxByType); - const dropdownItems = dropdown.findAllComponents(GlDropdownItem); - const { nodes } = parsedMapping.samplePayload.payloadAlerFields; - const mappingOptions = nodes.filter(({ type }) => types.includes(type)); + const { searchBox, dropdownItems, mappingOptions } = getDropdownContent(dropdown, types); expect(dropdown.exists()).toBe(true); expect(searchBox.exists()).toBe(true); @@ -80,11 +86,7 @@ describe('AlertMappingBuilder', () => { expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks)); if (numberOfFallbacks) { - const searchBox = dropdown.findComponent(GlSearchBoxByType); - const dropdownItems = dropdown.findAllComponents(GlDropdownItem); - const { nodes } = parsedMapping.samplePayload.payloadAlerFields; - const mappingOptions = nodes.filter(({ type }) => types.includes(type)); - + const { searchBox, dropdownItems, mappingOptions } = getDropdownContent(dropdown, types); expect(searchBox.exists()).toBe(Boolean(numberOfFallbacks)); expect(dropdownItems).toHaveLength(mappingOptions.length); } diff --git a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js index 02229b3d3da..d2dcff14432 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_form_spec.js @@ -1,29 +1,18 @@ -import { - GlForm, - GlFormSelect, - GlCollapse, - GlFormInput, - GlToggle, - GlFormTextarea, -} from '@gitlab/ui'; +import { GlForm, GlFormSelect, GlFormInput, GlToggle, GlFormTextarea, GlTab } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import waitForPromises from 'helpers/wait_for_promises'; import MappingBuilder from '~/alerts_settings/components/alert_mapping_builder.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue'; import { typeSet } from '~/alerts_settings/constants'; -import alertFields from '../mocks/alertFields.json'; +import alertFields from '../mocks/alert_fields.json'; +import parsedMapping from '../mocks/parsed_mapping.json'; import { defaultAlertSettingsConfig } from './util'; describe('AlertsSettingsForm', () => { let wrapper; const mockToastShow = jest.fn(); - const createComponent = ({ - data = {}, - props = {}, - multipleHttpIntegrationsCustomMapping = false, - multiIntegrations = true, - } = {}) => { + const createComponent = ({ data = {}, props = {}, multiIntegrations = true } = {}) => { wrapper = mount(AlertsSettingsForm, { data() { return { ...data }; @@ -35,10 +24,12 @@ describe('AlertsSettingsForm', () => { }, provide: { ...defaultAlertSettingsConfig, - glFeatures: { multipleHttpIntegrationsCustomMapping }, multiIntegrations, }, mocks: { + $apollo: { + query: jest.fn(), + }, $toast: { show: mockToastShow, }, @@ -46,20 +37,20 @@ describe('AlertsSettingsForm', () => { }); }; - const findForm = () => wrapper.find(GlForm); - const findSelect = () => wrapper.find(GlFormSelect); - const findFormSteps = () => wrapper.find(GlCollapse); - const findFormFields = () => wrapper.findAll(GlFormInput); - const findFormToggle = () => wrapper.find(GlToggle); - const findTestPayloadSection = () => wrapper.find(`[id = "test-integration"]`); + const findForm = () => wrapper.findComponent(GlForm); + const findSelect = () => wrapper.findComponent(GlFormSelect); + const findFormFields = () => wrapper.findAllComponents(GlFormInput); + const findFormToggle = () => wrapper.findComponent(GlToggle); + const findSamplePayloadSection = () => wrapper.find('[data-testid="sample-payload-section"]'); const findMappingBuilderSection = () => wrapper.find(`[id = "mapping-builder"]`); const findMappingBuilder = () => wrapper.findComponent(MappingBuilder); const findSubmitButton = () => wrapper.find(`[type = "submit"]`); const findMultiSupportText = () => wrapper.find(`[data-testid="multi-integrations-not-supported"]`); - const findJsonTestSubmit = () => wrapper.find(`[data-testid="integration-test-and-submit"]`); + const findJsonTestSubmit = () => wrapper.find(`[data-testid="send-test-alert"]`); const findJsonTextArea = () => wrapper.find(`[id = "test-payload"]`); const findActionBtn = () => wrapper.find(`[data-testid="payload-action-btn"]`); + const findTabs = () => wrapper.findAllComponents(GlTab); afterEach(() => { if (wrapper) { @@ -91,7 +82,7 @@ describe('AlertsSettingsForm', () => { expect(findForm().exists()).toBe(true); expect(findSelect().exists()).toBe(true); expect(findMultiSupportText().exists()).toBe(false); - expect(findFormSteps().attributes('visible')).toBeUndefined(); + expect(findFormFields()).toHaveLength(0); }); it('shows the rest of the form when the dropdown is used', async () => { @@ -106,37 +97,47 @@ describe('AlertsSettingsForm', () => { expect(findMultiSupportText().exists()).toBe(true); }); - it('disabled the name input when the selected value is prometheus', async () => { + it('hides the name input when the selected value is prometheus', async () => { createComponent(); await selectOptionAtIndex(2); - - expect(findFormFields().at(0).attributes('disabled')).toBe('disabled'); + expect(findFormFields().at(0).attributes('id')).not.toBe('name-integration'); }); - }); - - describe('submitting integration form', () => { - describe('HTTP', () => { - it('create', async () => { - createComponent(); - const integrationName = 'Test integration'; - await selectOptionAtIndex(1); - enableIntegration(0, integrationName); - - const submitBtn = findSubmitButton(); - expect(submitBtn.exists()).toBe(true); - expect(submitBtn.text()).toBe('Save integration'); + describe('form tabs', () => { + it('renders 3 tabs', () => { + expect(findTabs()).toHaveLength(3); + }); - findForm().trigger('submit'); + it('only first tab is enabled on integration create', () => { + createComponent({ + data: { + currentIntegration: null, + }, + }); + const tabs = findTabs(); + expect(tabs.at(0).find('[role="tabpanel"]').classes('disabled')).toBe(false); + expect(tabs.at(1).find('[role="tabpanel"]').classes('disabled')).toBe(true); + expect(tabs.at(2).find('[role="tabpanel"]').classes('disabled')).toBe(true); + }); - expect(wrapper.emitted('create-new-integration')[0]).toEqual([ - { type: typeSet.http, variables: { name: integrationName, active: true } }, - ]); + it('all tabs are enabled on integration edit', () => { + createComponent({ + data: { + currentIntegration: { id: 1 }, + }, + }); + const tabs = findTabs(); + expect(tabs.at(0).find('[role="tabpanel"]').classes('disabled')).toBe(false); + expect(tabs.at(1).find('[role="tabpanel"]').classes('disabled')).toBe(false); + expect(tabs.at(2).find('[role="tabpanel"]').classes('disabled')).toBe(false); }); + }); + }); + describe('submitting integration form', () => { + describe('HTTP', () => { it('create with custom mapping', async () => { createComponent({ - multipleHttpIntegrationsCustomMapping: true, multiIntegrations: true, props: { alertFields }, }); @@ -146,7 +147,7 @@ describe('AlertsSettingsForm', () => { enableIntegration(0, integrationName); - const sampleMapping = { field: 'test' }; + const sampleMapping = parsedMapping.payloadAttributeMappings; findMappingBuilder().vm.$emit('onMappingUpdate', sampleMapping); findForm().trigger('submit'); @@ -157,7 +158,7 @@ describe('AlertsSettingsForm', () => { name: integrationName, active: true, payloadAttributeMappings: sampleMapping, - payloadExample: null, + payloadExample: '{}', }, }, ]); @@ -182,23 +183,28 @@ describe('AlertsSettingsForm', () => { findForm().trigger('submit'); - expect(wrapper.emitted('update-integration')[0]).toEqual([ - { type: typeSet.http, variables: { name: updatedIntegrationName, active: true } }, - ]); + expect(wrapper.emitted('update-integration')[0]).toEqual( + expect.arrayContaining([ + { + type: typeSet.http, + variables: { + name: updatedIntegrationName, + active: true, + payloadAttributeMappings: [], + payloadExample: '{}', + }, + }, + ]), + ); }); }); describe('PROMETHEUS', () => { it('create', async () => { createComponent(); - await selectOptionAtIndex(2); - const apiUrl = 'https://test.com'; - enableIntegration(1, apiUrl); - - findFormToggle().trigger('click'); - + enableIntegration(0, apiUrl); const submitBtn = findSubmitButton(); expect(submitBtn.exists()).toBe(true); expect(submitBtn.text()).toBe('Save integration'); @@ -222,7 +228,7 @@ describe('AlertsSettingsForm', () => { }); const apiUrl = 'https://test-post.com'; - enableIntegration(1, apiUrl); + enableIntegration(0, apiUrl); const submitBtn = findSubmitButton(); expect(submitBtn.exists()).toBe(true); @@ -260,7 +266,7 @@ describe('AlertsSettingsForm', () => { const jsonTestSubmit = findJsonTestSubmit(); expect(jsonTestSubmit.exists()).toBe(true); - expect(jsonTestSubmit.text()).toBe('Save and test payload'); + expect(jsonTestSubmit.text()).toBe('Send'); expect(jsonTestSubmit.props('disabled')).toBe(true); }); @@ -275,56 +281,73 @@ describe('AlertsSettingsForm', () => { }); describe('Test payload section for HTTP integration', () => { + const validSamplePayload = JSON.stringify(alertFields); + const emptySamplePayload = '{}'; + beforeEach(() => { createComponent({ - multipleHttpIntegrationsCustomMapping: true, - props: { + data: { currentIntegration: { type: typeSet.http, + payloadExample: validSamplePayload, + payloadAttributeMappings: [], }, - alertFields, + active: false, + resetPayloadAndMappingConfirmed: false, }, + props: { alertFields }, }); }); describe.each` - active | resetSamplePayloadConfirmed | disabled - ${true} | ${true} | ${undefined} - ${false} | ${true} | ${'disabled'} - ${true} | ${false} | ${'disabled'} - ${false} | ${false} | ${'disabled'} - `('', ({ active, resetSamplePayloadConfirmed, disabled }) => { - const payloadResetMsg = resetSamplePayloadConfirmed ? 'was confirmed' : 'was not confirmed'; + active | resetPayloadAndMappingConfirmed | disabled + ${true} | ${true} | ${undefined} + ${false} | ${true} | ${'disabled'} + ${true} | ${false} | ${'disabled'} + ${false} | ${false} | ${'disabled'} + `('', ({ active, resetPayloadAndMappingConfirmed, disabled }) => { + const payloadResetMsg = resetPayloadAndMappingConfirmed + ? 'was confirmed' + : 'was not confirmed'; const enabledState = disabled === 'disabled' ? 'disabled' : 'enabled'; const activeState = active ? 'active' : 'not active'; it(`textarea should be ${enabledState} when payload reset ${payloadResetMsg} and current integration is ${activeState}`, async () => { wrapper.setData({ - customMapping: { samplePayload: true }, + selectedIntegration: typeSet.http, active, - resetSamplePayloadConfirmed, + resetPayloadAndMappingConfirmed, }); await wrapper.vm.$nextTick(); - expect(findTestPayloadSection().find(GlFormTextarea).attributes('disabled')).toBe(disabled); + expect(findSamplePayloadSection().find(GlFormTextarea).attributes('disabled')).toBe( + disabled, + ); }); }); describe('action buttons for sample payload', () => { describe.each` - resetSamplePayloadConfirmed | samplePayload | caption - ${false} | ${true} | ${'Edit payload'} - ${true} | ${false} | ${'Submit payload'} - ${true} | ${true} | ${'Submit payload'} - ${false} | ${false} | ${'Submit payload'} - `('', ({ resetSamplePayloadConfirmed, samplePayload, caption }) => { - const samplePayloadMsg = samplePayload ? 'was provided' : 'was not provided'; - const payloadResetMsg = resetSamplePayloadConfirmed ? 'was confirmed' : 'was not confirmed'; + resetPayloadAndMappingConfirmed | payloadExample | caption + ${false} | ${validSamplePayload} | ${'Edit payload'} + ${true} | ${emptySamplePayload} | ${'Parse payload for custom mapping'} + ${true} | ${validSamplePayload} | ${'Parse payload for custom mapping'} + ${false} | ${emptySamplePayload} | ${'Parse payload for custom mapping'} + `('', ({ resetPayloadAndMappingConfirmed, payloadExample, caption }) => { + const samplePayloadMsg = payloadExample ? 'was provided' : 'was not provided'; + const payloadResetMsg = resetPayloadAndMappingConfirmed + ? 'was confirmed' + : 'was not confirmed'; it(`shows ${caption} button when sample payload ${samplePayloadMsg} and payload reset ${payloadResetMsg}`, async () => { wrapper.setData({ selectedIntegration: typeSet.http, - customMapping: { samplePayload }, - resetSamplePayloadConfirmed, + currentIntegration: { + payloadExample, + type: typeSet.http, + active: true, + payloadAttributeMappings: [], + }, + resetPayloadAndMappingConfirmed, }); await wrapper.vm.$nextTick(); expect(findActionBtn().text()).toBe(caption); @@ -333,16 +356,20 @@ describe('AlertsSettingsForm', () => { }); describe('Parsing payload', () => { - it('displays a toast message on successful parse', async () => { - jest.useFakeTimers(); + beforeEach(() => { wrapper.setData({ selectedIntegration: typeSet.http, - customMapping: { samplePayload: false }, + resetPayloadAndMappingConfirmed: true, }); - await wrapper.vm.$nextTick(); + }); + it('displays a toast message on successful parse', async () => { + jest.spyOn(wrapper.vm.$apollo, 'query').mockResolvedValue({ + data: { + project: { alertManagementPayloadFields: [] }, + }, + }); findActionBtn().vm.$emit('click'); - jest.advanceTimersByTime(1000); await waitForPromises(); @@ -350,27 +377,33 @@ describe('AlertsSettingsForm', () => { 'Sample payload has been parsed. You can now map the fields.', ); }); + + it('displays an error message under payload field on unsuccessful parse', async () => { + const errorMessage = 'Error parsing paylod'; + jest.spyOn(wrapper.vm.$apollo, 'query').mockRejectedValue({ message: errorMessage }); + findActionBtn().vm.$emit('click'); + + await waitForPromises(); + + expect(findSamplePayloadSection().find('.invalid-feedback').text()).toBe(errorMessage); + }); }); }); describe('Mapping builder section', () => { describe.each` - alertFieldsProvided | multiIntegrations | featureFlag | integrationOption | visible - ${true} | ${true} | ${true} | ${1} | ${true} - ${true} | ${true} | ${true} | ${2} | ${false} - ${true} | ${true} | ${false} | ${1} | ${false} - ${true} | ${true} | ${false} | ${2} | ${false} - ${true} | ${false} | ${true} | ${1} | ${false} - ${false} | ${true} | ${true} | ${1} | ${false} - `('', ({ alertFieldsProvided, multiIntegrations, featureFlag, integrationOption, visible }) => { + alertFieldsProvided | multiIntegrations | integrationOption | visible + ${true} | ${true} | ${1} | ${true} + ${true} | ${true} | ${2} | ${false} + ${true} | ${false} | ${1} | ${false} + ${false} | ${true} | ${1} | ${false} + `('', ({ alertFieldsProvided, multiIntegrations, integrationOption, visible }) => { const visibleMsg = visible ? 'is rendered' : 'is not rendered'; - const featureFlagMsg = featureFlag ? 'is enabled' : 'is disabled'; const alertFieldsMsg = alertFieldsProvided ? 'are provided' : 'are not provided'; const integrationType = integrationOption === 1 ? typeSet.http : typeSet.prometheus; - it(`${visibleMsg} when multipleHttpIntegrationsCustomMapping feature flag ${featureFlagMsg} and integration type is ${integrationType} and alert fields ${alertFieldsMsg}`, async () => { + it(`${visibleMsg} when integration type is ${integrationType} and alert fields ${alertFieldsMsg}`, async () => { createComponent({ - multipleHttpIntegrationsCustomMapping: featureFlag, multiIntegrations, props: { alertFields: alertFieldsProvided ? alertFields : [], diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js index 80293597ab6..77fac6dd022 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js @@ -2,21 +2,26 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { mount, createLocalVue } from '@vue/test-utils'; import AxiosMockAdapter from 'axios-mock-adapter'; import VueApollo from 'vue-apollo'; +import createHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; +import updateHttpIntegrationMutation from 'ee_else_ce/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; import createMockApollo from 'helpers/mock_apollo_helper'; import { useMockIntersectionObserver } from 'helpers/mock_dom_observer'; import waitForPromises from 'helpers/wait_for_promises'; import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue'; import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue'; -import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue'; +import AlertsSettingsWrapper, { + i18n, +} from '~/alerts_settings/components/alerts_settings_wrapper.vue'; import { typeSet } from '~/alerts_settings/constants'; -import createHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/create_http_integration.mutation.graphql'; import createPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/create_prometheus_integration.mutation.graphql'; import destroyHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/destroy_http_integration.mutation.graphql'; import resetHttpTokenMutation from '~/alerts_settings/graphql/mutations/reset_http_token.mutation.graphql'; import resetPrometheusTokenMutation from '~/alerts_settings/graphql/mutations/reset_prometheus_token.mutation.graphql'; -import updateHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_http_integration.mutation.graphql'; +import updateCurrentHttpIntegrationMutation from '~/alerts_settings/graphql/mutations/update_current_http_integration.mutation.graphql'; +import updateCurrentPrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_current_prometheus_integration.mutation.graphql'; import updatePrometheusIntegrationMutation from '~/alerts_settings/graphql/mutations/update_prometheus_integration.mutation.graphql'; import getIntegrationsQuery from '~/alerts_settings/graphql/queries/get_integrations.query.graphql'; +import alertsUpdateService from '~/alerts_settings/services'; import { ADD_INTEGRATION_ERROR, RESET_INTEGRATION_TOKEN_ERROR, @@ -24,14 +29,15 @@ import { INTEGRATION_PAYLOAD_TEST_ERROR, DELETE_INTEGRATION_ERROR, } from '~/alerts_settings/utils/error_messages'; -import createFlash from '~/flash'; +import createFlash, { FLASH_TYPES } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { createHttpVariables, updateHttpVariables, createPrometheusVariables, updatePrometheusVariables, - ID, + HTTP_ID, + PROMETHEUS_ID, errorMsg, getIntegrationsQueryResponse, destroyIntegrationResponse, @@ -50,9 +56,33 @@ describe('AlertsSettingsWrapper', () => { let fakeApollo; let destroyIntegrationHandler; useMockIntersectionObserver(); + const httpMappingData = { + payloadExample: '{"test: : "field"}', + payloadAttributeMappings: [], + payloadAlertFields: [], + }; + const httpIntegrations = { + list: [ + { + id: mockIntegrations[0].id, + ...httpMappingData, + }, + { + id: mockIntegrations[1].id, + ...httpMappingData, + }, + { + id: mockIntegrations[2].id, + httpMappingData, + }, + ], + }; - const findLoader = () => wrapper.find(IntegrationsList).find(GlLoadingIcon); + const findLoader = () => wrapper.findComponent(IntegrationsList).findComponent(GlLoadingIcon); + const findIntegrationsList = () => wrapper.findComponent(IntegrationsList); const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr'); + const findAddIntegrationBtn = () => wrapper.find('[data-testid="add-integration-btn"]'); + const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm); async function destroyHttpIntegration(localWrapper) { await jest.runOnlyPendingTimers(); @@ -119,14 +149,37 @@ describe('AlertsSettingsWrapper', () => { wrapper = null; }); - describe('rendered via default permissions', () => { - it('renders the GraphQL alerts integrations list and new form', () => { - createComponent(); - expect(wrapper.find(IntegrationsList).exists()).toBe(true); - expect(wrapper.find(AlertsSettingsForm).exists()).toBe(true); + describe('template', () => { + beforeEach(() => { + createComponent({ + data: { + integrations: { list: mockIntegrations }, + httpIntegrations: { list: [] }, + currentIntegration: mockIntegrations[0], + }, + loading: false, + }); }); - it('uses a loading state inside the IntegrationsList table', () => { + it('renders alerts integrations list and add new integration button by default', () => { + expect(findLoader().exists()).toBe(false); + expect(findIntegrations()).toHaveLength(mockIntegrations.length); + expect(findAddIntegrationBtn().exists()).toBe(true); + }); + + it('does NOT render settings form by default', () => { + expect(findAlertsSettingsForm().exists()).toBe(false); + }); + + it('hides `add new integration` button and displays setting form on btn click', async () => { + const addNewIntegrationBtn = findAddIntegrationBtn(); + expect(addNewIntegrationBtn.exists()).toBe(true); + await addNewIntegrationBtn.trigger('click'); + expect(findAlertsSettingsForm().exists()).toBe(true); + expect(addNewIntegrationBtn.exists()).toBe(false); + }); + + it('shows loading indicator inside the IntegrationsList table', () => { createComponent({ data: { integrations: {} }, loading: true, @@ -134,26 +187,24 @@ describe('AlertsSettingsWrapper', () => { expect(wrapper.find(IntegrationsList).exists()).toBe(true); expect(findLoader().exists()).toBe(true); }); + }); - it('renders the IntegrationsList table using the API data', () => { + describe('Integration updates', () => { + beforeEach(() => { createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, + data: { + integrations: { list: mockIntegrations }, + currentIntegration: mockIntegrations[0], + formVisible: true, + }, loading: false, }); - expect(findLoader().exists()).toBe(false); - expect(findIntegrations()).toHaveLength(mockIntegrations.length); }); - it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: { createHttpIntegrationMutation: { integration: { id: '1' } } }, }); - wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', { + findAlertsSettingsForm().vm.$emit('create-new-integration', { type: typeSet.http, variables: createHttpVariables, }); @@ -167,15 +218,10 @@ describe('AlertsSettingsWrapper', () => { }); it('calls `$apollo.mutate` with `updateHttpIntegrationMutation`', () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: { updateHttpIntegrationMutation: { integration: { id: '1' } } }, }); - wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', { + findAlertsSettingsForm().vm.$emit('update-integration', { type: typeSet.http, variables: updateHttpVariables, }); @@ -187,37 +233,27 @@ describe('AlertsSettingsWrapper', () => { }); it('calls `$apollo.mutate` with `resetHttpTokenMutation`', () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: { resetHttpTokenMutation: { integration: { id: '1' } } }, }); - wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', { + findAlertsSettingsForm().vm.$emit('reset-token', { type: typeSet.http, - variables: { id: ID }, + variables: { id: HTTP_ID }, }); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ mutation: resetHttpTokenMutation, variables: { - id: ID, + id: HTTP_ID, }, }); }); it('calls `$apollo.mutate` with `createPrometheusIntegrationMutation`', () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: { createPrometheusIntegrationMutation: { integration: { id: '2' } } }, }); - wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', { + findAlertsSettingsForm().vm.$emit('create-new-integration', { type: typeSet.prometheus, variables: createPrometheusVariables, }); @@ -232,14 +268,18 @@ describe('AlertsSettingsWrapper', () => { it('calls `$apollo.mutate` with `updatePrometheusIntegrationMutation`', () => { createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, + data: { + integrations: { list: mockIntegrations }, + currentIntegration: mockIntegrations[3], + formVisible: true, + }, loading: false, }); jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: { updatePrometheusIntegrationMutation: { integration: { id: '2' } } }, }); - wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', { + findAlertsSettingsForm().vm.$emit('update-integration', { type: typeSet.prometheus, variables: updatePrometheusVariables, }); @@ -251,35 +291,25 @@ describe('AlertsSettingsWrapper', () => { }); it('calls `$apollo.mutate` with `resetPrometheusTokenMutation`', () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: { resetPrometheusTokenMutation: { integration: { id: '1' } } }, }); - wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', { + findAlertsSettingsForm().vm.$emit('reset-token', { type: typeSet.prometheus, - variables: { id: ID }, + variables: { id: PROMETHEUS_ID }, }); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ mutation: resetPrometheusTokenMutation, variables: { - id: ID, + id: PROMETHEUS_ID, }, }); }); it('shows an error alert when integration creation fails ', async () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(ADD_INTEGRATION_ERROR); - wrapper.find(AlertsSettingsForm).vm.$emit('create-new-integration', {}); + findAlertsSettingsForm().vm.$emit('create-new-integration', {}); await waitForPromises(); @@ -287,28 +317,18 @@ describe('AlertsSettingsWrapper', () => { }); it('shows an error alert when integration token reset fails ', async () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(RESET_INTEGRATION_TOKEN_ERROR); - wrapper.find(AlertsSettingsForm).vm.$emit('reset-token', {}); + findAlertsSettingsForm().vm.$emit('reset-token', {}); await waitForPromises(); expect(createFlash).toHaveBeenCalledWith({ message: RESET_INTEGRATION_TOKEN_ERROR }); }); it('shows an error alert when integration update fails ', async () => { - createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, - loading: false, - }); - jest.spyOn(wrapper.vm.$apollo, 'mutate').mockRejectedValue(errorMsg); - wrapper.find(AlertsSettingsForm).vm.$emit('update-integration', {}); + findAlertsSettingsForm().vm.$emit('update-integration', {}); await waitForPromises(); expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR }); @@ -317,15 +337,74 @@ describe('AlertsSettingsWrapper', () => { it('shows an error alert when integration test payload fails ', async () => { const mock = new AxiosMockAdapter(axios); mock.onPost(/(.*)/).replyOnce(403); + return wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' }).then(() => { + expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR }); + expect(createFlash).toHaveBeenCalledTimes(1); + mock.restore(); + }); + }); + + it('calls `$apollo.mutate` with `updateCurrentHttpIntegrationMutation` on HTTP integration edit', () => { createComponent({ - data: { integrations: { list: mockIntegrations }, currentIntegration: mockIntegrations[0] }, + data: { + integrations: { list: mockIntegrations }, + currentIntegration: mockIntegrations[0], + httpIntegrations, + }, loading: false, }); - return wrapper.vm.validateAlertPayload({ endpoint: '', data: '', token: '' }).then(() => { - expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR }); - expect(createFlash).toHaveBeenCalledTimes(1); - mock.restore(); + jest.spyOn(wrapper.vm.$apollo, 'mutate'); + findIntegrationsList().vm.$emit('edit-integration', updateHttpVariables); + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: updateCurrentHttpIntegrationMutation, + variables: { ...mockIntegrations[0], ...httpMappingData }, + }); + }); + + it('calls `$apollo.mutate` with `updateCurrentPrometheusIntegrationMutation` on PROMETHEUS integration edit', () => { + createComponent({ + data: { + integrations: { list: mockIntegrations }, + currentIntegration: mockIntegrations[3], + httpIntegrations, + }, + loading: false, + }); + + jest.spyOn(wrapper.vm.$apollo, 'mutate'); + findIntegrationsList().vm.$emit('edit-integration', updatePrometheusVariables); + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: updateCurrentPrometheusIntegrationMutation, + variables: mockIntegrations[3], + }); + }); + + describe('Test alert', () => { + it('makes `updateTestAlert` service call', async () => { + jest.spyOn(alertsUpdateService, 'updateTestAlert').mockResolvedValueOnce(); + const testPayload = '{"title":"test"}'; + findAlertsSettingsForm().vm.$emit('test-alert-payload', testPayload); + expect(alertsUpdateService.updateTestAlert).toHaveBeenCalledWith(testPayload); + }); + + it('shows success message on successful test', async () => { + jest.spyOn(alertsUpdateService, 'updateTestAlert').mockResolvedValueOnce({}); + findAlertsSettingsForm().vm.$emit('test-alert-payload', ''); + await waitForPromises(); + expect(createFlash).toHaveBeenCalledWith({ + message: i18n.alertSent, + type: FLASH_TYPES.SUCCESS, + }); + }); + + it('shows error message when test alert fails', async () => { + jest.spyOn(alertsUpdateService, 'updateTestAlert').mockRejectedValueOnce({}); + findAlertsSettingsForm().vm.$emit('test-alert-payload', ''); + await waitForPromises(); + expect(createFlash).toHaveBeenCalledWith({ + message: INTEGRATION_PAYLOAD_TEST_ERROR, + }); }); }); }); diff --git a/spec/frontend/alerts_settings/components/mocks/apollo_mock.js b/spec/frontend/alerts_settings/components/mocks/apollo_mock.js index e0eba1e8421..828580a436b 100644 --- a/spec/frontend/alerts_settings/components/mocks/apollo_mock.js +++ b/spec/frontend/alerts_settings/components/mocks/apollo_mock.js @@ -1,29 +1,34 @@ const projectPath = ''; -export const ID = 'gid://gitlab/AlertManagement::HttpIntegration/7'; +export const HTTP_ID = 'gid://gitlab/AlertManagement::HttpIntegration/7'; +export const PROMETHEUS_ID = 'gid://gitlab/PrometheusService/12'; export const errorMsg = 'Something went wrong'; export const createHttpVariables = { name: 'Test Pre', active: true, projectPath, + type: 'HTTP', }; export const updateHttpVariables = { name: 'Test Pre', active: true, - id: ID, + id: HTTP_ID, + type: 'HTTP', }; export const createPrometheusVariables = { apiUrl: 'https://test-pre.com', active: true, projectPath, + type: 'PROMETHEUS', }; export const updatePrometheusVariables = { apiUrl: 'https://test-pre.com', active: true, - id: ID, + id: PROMETHEUS_ID, + type: 'PROMETHEUS', }; export const getIntegrationsQueryResponse = { @@ -99,6 +104,9 @@ export const destroyIntegrationResponse = { 'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-5/d4875758e67334f3.json', token: '89eb01df471d990ff5162a1c640408cf', apiUrl: null, + payloadExample: '{"field": "value"}', + payloadAttributeMappings: [], + payloadAlertFields: [], }, }, }, @@ -117,6 +125,9 @@ export const destroyIntegrationResponseWithErrors = { 'http://127.0.0.1:3000/h5bp/html5-boilerplate/alerts/notify/test-5/d4875758e67334f3.json', token: '89eb01df471d990ff5162a1c640408cf', apiUrl: null, + payloadExample: '{"field": "value"}', + payloadAttributeMappings: [], + payloadAlertFields: [], }, }, }, diff --git a/spec/frontend/alerts_settings/mocks/alertFields.json b/spec/frontend/alerts_settings/mocks/alert_fields.json index ffe59dd0c05..ffe59dd0c05 100644 --- a/spec/frontend/alerts_settings/mocks/alertFields.json +++ b/spec/frontend/alerts_settings/mocks/alert_fields.json diff --git a/spec/frontend/alerts_settings/mocks/parsed_mapping.json b/spec/frontend/alerts_settings/mocks/parsed_mapping.json new file mode 100644 index 00000000000..e985671a923 --- /dev/null +++ b/spec/frontend/alerts_settings/mocks/parsed_mapping.json @@ -0,0 +1,122 @@ +{ + "payloadAlerFields": [ + { + "path": [ + "dashboardId" + ], + "label": "Dashboard Id", + "type": "string" + }, + { + "path": [ + "evalMatches" + ], + "label": "Eval Matches", + "type": "array" + }, + { + "path": [ + "createdAt" + ], + "label": "Created At", + "type": "datetime" + }, + { + "path": [ + "imageUrl" + ], + "label": "Image Url", + "type": "string" + }, + { + "path": [ + "message" + ], + "label": "Message", + "type": "string" + }, + { + "path": [ + "orgId" + ], + "label": "Org Id", + "type": "string" + }, + { + "path": [ + "panelId" + ], + "label": "Panel Id", + "type": "string" + }, + { + "path": [ + "ruleId" + ], + "label": "Rule Id", + "type": "string" + }, + { + "path": [ + "ruleName" + ], + "label": "Rule Name", + "type": "string" + }, + { + "path": [ + "ruleUrl" + ], + "label": "Rule Url", + "type": "string" + }, + { + "path": [ + "state" + ], + "label": "State", + "type": "string" + }, + { + "path": [ + "title" + ], + "label": "Title", + "type": "string" + }, + { + "path": [ + "tags", + "tag" + ], + "label": "Tags", + "type": "string" + } + ], + "payloadAttributeMappings": [ + { + "fieldName": "title", + "label": "Title", + "type": "STRING", + "path": ["title"] + }, + { + "fieldName": "description", + "label": "description", + "type": "STRING", + "path": ["description"] + }, + { + "fieldName": "hosts", + "label": "Host", + "type": "ARRAY", + "path": ["hosts", "host"] + }, + { + "fieldName": "startTime", + "label": "Created Atd", + "type": "STRING", + "path": ["time", "createdAt"] + } + ] +} diff --git a/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js b/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js index 8c1977ffebe..62b95c6078b 100644 --- a/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js +++ b/spec/frontend/alerts_settings/utils/mapping_transformations_spec.js @@ -1,29 +1,25 @@ -import parsedMapping from '~/alerts_settings/components/mocks/parsedMapping.json'; -import { - getMappingData, - getPayloadFields, - transformForSave, -} from '~/alerts_settings/utils/mapping_transformations'; -import alertFields from '../mocks/alertFields.json'; +import { getMappingData, transformForSave } from '~/alerts_settings/utils/mapping_transformations'; +import alertFields from '../mocks/alert_fields.json'; +import parsedMapping from '../mocks/parsed_mapping.json'; describe('Mapping Transformation Utilities', () => { const nameField = { label: 'Name', path: ['alert', 'name'], - type: 'string', + type: 'STRING', }; const dashboardField = { label: 'Dashboard Id', path: ['alert', 'dashboardId'], - type: 'string', + type: 'STRING', }; describe('getMappingData', () => { it('should return mapping data', () => { const result = getMappingData( alertFields, - getPayloadFields(parsedMapping.samplePayload.payloadAlerFields.nodes.slice(0, 3)), - parsedMapping.storedMapping.nodes.slice(0, 3), + parsedMapping.payloadAlerFields.slice(0, 3), + parsedMapping.payloadAttributeMappings.slice(0, 3), ); result.forEach((data, index) => { @@ -44,8 +40,8 @@ describe('Mapping Transformation Utilities', () => { const mockMappingData = [ { name: fieldName, - mapping: 'alert_name', - mappingFields: getPayloadFields([dashboardField, nameField]), + mapping: ['alert', 'name'], + mappingFields: [dashboardField, nameField], }, ]; const result = transformForSave(mockMappingData); @@ -61,21 +57,11 @@ describe('Mapping Transformation Utilities', () => { { name: fieldName, mapping: null, - mappingFields: getPayloadFields([nameField, dashboardField]), + mappingFields: [nameField, dashboardField], }, ]; const result = transformForSave(mockMappingData); expect(result).toEqual([]); }); }); - - describe('getPayloadFields', () => { - it('should add name field to each payload field', () => { - const result = getPayloadFields([nameField, dashboardField]); - expect(result).toEqual([ - { ...nameField, name: 'alert_name' }, - { ...dashboardField, name: 'alert_dashboardId' }, - ]); - }); - }); }); |