Commit b38de0a0 authored by David O'Regan's avatar David O'Regan

Merge branch '300827-integration-form-cleanup-part-3' into 'master'

Alert integration UX cleanup - 3rd part

See merge request gitlab-org/gitlab!55892
parents ed8c0960 50f9fc4e
......@@ -118,17 +118,17 @@ export default {
<template>
<div class="gl-display-table gl-w-full gl-mt-5">
<div class="gl-display-table-row">
<h5 id="gitlabFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
<h5 id="gitlabFieldsHeader" class="gl-display-table-cell gl-pb-3 gl-pr-3">
{{ $options.i18n.columns.gitlabKeyTitle }}
</h5>
<h5 class="gl-display-table-cell gl-py-3 gl-pr-3">&nbsp;</h5>
<h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-py-3 gl-pr-3">
<h5 class="gl-display-table-cell gl-pb-3 gl-pr-3">&nbsp;</h5>
<h5 id="parsedFieldsHeader" class="gl-display-table-cell gl-pb-3 gl-pr-3">
{{ $options.i18n.columns.payloadKeyTitle }}
</h5>
<h5
v-if="hasFallbackColumn"
id="fallbackFieldsHeader"
class="gl-display-table-cell gl-py-3 gl-pr-3"
class="gl-display-table-cell gl-pb-3 gl-pr-3"
>
{{ $options.i18n.columns.fallbackKeyTitle }}
<gl-icon
......@@ -140,11 +140,7 @@ export default {
</h5>
</div>
<div
v-for="(gitlabField, index) in mappingData"
:key="gitlabField.name"
class="gl-display-table-row"
>
<div v-for="gitlabField in mappingData" :key="gitlabField.name" class="gl-display-table-row">
<div class="gl-display-table-cell gl-py-3 gl-pr-3 gl-w-30p gl-vertical-align-middle">
<gl-form-input
aria-labelledby="gitlabFieldsHeader"
......@@ -153,8 +149,8 @@ export default {
/>
</div>
<div class="gl-display-table-cell gl-py-3 gl-pr-3">
<div class="right-arrow" :class="{ 'gl-vertical-align-middle': index === 0 }">
<div class="gl-display-table-cell gl-pr-3 gl-vertical-align-middle">
<div class="right-arrow">
<i class="right-arrow-head"></i>
</div>
</div>
......
......@@ -139,7 +139,7 @@ export default {
<template>
<div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5>
<h5 class="gl-font-lg gl-mt-5">{{ $options.i18n.title }}</h5>
<gl-table
class="integration-list"
:items="integrations"
......
<script>
import { GlButton } from '@gitlab/ui';
import { GlButton, GlAlert } from '@gitlab/ui';
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 createFlash, { FLASH_TYPES } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { s__ } from '~/locale';
import { typeSet } from '../constants';
import httpStatusCodes from '~/lib/utils/http_status';
import { typeSet, i18n, tabIndices } from '../constants';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import destroyHttpIntegrationMutation from '../graphql/mutations/destroy_http_integration.mutation.graphql';
import resetHttpTokenMutation from '../graphql/mutations/reset_http_token.mutation.graphql';
......@@ -28,21 +28,12 @@ import {
RESET_INTEGRATION_TOKEN_ERROR,
UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR,
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DEFAULT_ERROR,
} from '../utils/error_messages';
import IntegrationsList from './alerts_integrations_list.vue';
import AlertSettingsForm from './alerts_settings_form.vue';
export const i18n = {
changesSaved: s__(
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
),
integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'),
alertSent: s__(
'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
),
addNewIntegration: s__('AlertSettings|Add new integration'),
};
export default {
typeSet,
i18n,
......@@ -50,14 +41,9 @@ export default {
IntegrationsList,
AlertSettingsForm,
GlButton,
GlAlert,
},
inject: {
generic: {
default: {},
},
prometheus: {
default: {},
},
projectPath: {
default: '',
},
......@@ -124,7 +110,10 @@ export default {
integrations: {},
httpIntegrations: {},
currentIntegration: null,
newIntegration: null,
formVisible: false,
showSuccessfulCreateAlert: false,
tabIndex: tabIndices.configureDetails,
};
},
computed: {
......@@ -139,10 +128,10 @@ export default {
isHttp(type) {
return type === typeSet.http;
},
createNewIntegration({ type, variables }) {
createNewIntegration({ type, variables }, testAfterSubmit) {
const { projectPath } = this;
const isHttp = this.isHttp(type);
this.isUpdating = true;
this.$apollo
.mutate({
......@@ -163,16 +152,19 @@ export default {
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
if (error) {
return createFlash({ message: error });
createFlash({ message: error });
return;
}
const { integration } = httpIntegrationCreate || prometheusIntegrationCreate;
this.editIntegration(integration);
const { integration } = httpIntegrationCreate || prometheusIntegrationCreate;
this.newIntegration = integration;
this.showSuccessfulCreateAlert = true;
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
if (testAfterSubmit) {
this.viewIntegration(this.newIntegration, tabIndices.sendTestAlert);
} else {
this.setFormVisibility(false);
}
})
.catch(() => {
createFlash({ message: ADD_INTEGRATION_ERROR });
......@@ -181,9 +173,9 @@ export default {
this.isUpdating = false;
});
},
updateIntegration({ type, variables }) {
updateIntegration({ type, variables }, testAfterSubmit) {
this.isUpdating = true;
this.$apollo
return this.$apollo
.mutate({
mutation: this.isHttp(type)
? updateHttpIntegrationMutation
......@@ -196,12 +188,20 @@ export default {
.then(({ data: { httpIntegrationUpdate, prometheusIntegrationUpdate } = {} } = {}) => {
const error = httpIntegrationUpdate?.errors[0] || prometheusIntegrationUpdate?.errors[0];
if (error) {
return createFlash({ message: error });
createFlash({ message: error });
return;
}
this.clearCurrentIntegration({ type });
const integration =
httpIntegrationUpdate?.integration || prometheusIntegrationUpdate?.integration;
return createFlash({
if (testAfterSubmit) {
this.viewIntegration(integration, tabIndices.sendTestAlert);
} else {
this.clearCurrentIntegration(type);
}
createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
......@@ -261,13 +261,23 @@ export default {
currentIntegration = { ...currentIntegration, ...httpIntegrationMappingData };
}
this.$apollo.mutate({
mutation: this.isHttp(type)
? updateCurrentHttpIntegrationMutation
: updateCurrentPrometheusIntegrationMutation,
variables: currentIntegration,
});
this.setFormVisibility(true);
this.viewIntegration(currentIntegration, tabIndices.viewCredentials);
},
viewIntegration(integration, tabIndex) {
this.$apollo
.mutate({
mutation: this.isHttp(integration.type)
? updateCurrentHttpIntegrationMutation
: updateCurrentPrometheusIntegrationMutation,
variables: integration,
})
.then(() => {
this.setFormVisibility(true);
this.tabIndex = tabIndex;
})
.catch(() => {
createFlash({ message: DEFAULT_ERROR });
});
},
deleteIntegration({ id, type }) {
const { projectPath } = this;
......@@ -319,19 +329,44 @@ export default {
type: FLASH_TYPES.SUCCESS,
});
})
.catch(() => {
createFlash({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
.catch((error) => {
let message = INTEGRATION_PAYLOAD_TEST_ERROR;
if (error.response?.status === httpStatusCodes.FORBIDDEN) {
message = INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR;
}
createFlash({ message });
});
},
saveAndTestAlertPayload(integration, payload) {
return this.updateIntegration(integration, false).then(() => {
this.testAlertPayload(payload);
});
},
setFormVisibility(visible) {
this.formVisible = visible;
},
viewCreatedIntegration() {
this.viewIntegration(this.newIntegration, tabIndices.viewCredentials);
this.showSuccessfulCreateAlert = false;
this.newIntegration = null;
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="showSuccessfulCreateAlert"
class="gl-mt-n2"
:primary-button-text="$options.i18n.integrationCreated.btnCaption"
:title="$options.i18n.integrationCreated.title"
@primaryAction="viewCreatedIntegration"
@dismiss="showSuccessfulCreateAlert = false"
>
{{ $options.i18n.integrationCreated.successMsg }}
</gl-alert>
<integrations-list
:integrations="integrations.list"
:loading="loading"
......@@ -353,11 +388,13 @@ export default {
:loading="isUpdating"
:can-add-integration="canAddIntegration"
:alert-fields="alertFields"
:tab-index="tabIndex"
@create-new-integration="createNewIntegration"
@update-integration="updateIntegration"
@reset-token="resetToken"
@clear-current-integration="clearCurrentIntegration"
@test-alert-payload="testAlertPayload"
@save-and-test-alert-payload="saveAndTestAlertPayload"
/>
</div>
</template>
......@@ -17,6 +17,13 @@ export const i18n = {
label: s__('AlertSettings|Name integration'),
placeholder: s__('AlertSettings|Enter integration name'),
activeToggle: __('Active'),
error: __("Name can't be blank"),
},
enableIntegration: {
label: s__('AlertSettings|Enable integration'),
help: s__(
'AlertSettings|A webhook URL and authorization key will be generated for the integration. Both will be visible after saving the integration in the “View credentials” tab.',
),
},
setupCredentials: {
help: s__(
......@@ -29,35 +36,41 @@ export const i18n = {
authorizationKey: s__('AlertSettings|Authorization key'),
reset: s__('AlertSettings|Reset Key'),
},
setSamplePayload: {
label: s__('AlertSettings|Sample alert payload (optional)'),
testPayloadHelpHttp: s__(
'AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional).',
),
testPayloadHelp: s__(
'AlertSettings|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.',
mapFields: {
label: s__('AlertSettings|Customize alert payload mapping (optional)'),
help: s__(
'AlertSettings|If you intend to create a custom mapping, provide an example payload from your monitoring tool and click the "parse payload fields" button to continue. The sample payload is required for completing the custom mapping; if you want to skip the mapping step, progress straight to saving your integration.',
),
placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
editPayload: s__('AlertSettings|Edit payload'),
parsePayload: s__('AlertSettings|Parse payload fields'),
payloadParsedSucessMsg: s__(
'AlertSettings|Sample payload has been parsed. You can now map the fields.',
),
resetHeader: s__('AlertSettings|Reset the mapping'),
resetBody: s__(
"AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields.",
),
resetOk: s__('AlertSettings|Proceed with editing'),
editPayload: s__('AlertSettings|Edit payload'),
parsePayload: s__('AlertSettings|Parse payload for custom mapping'),
payloadParsedSucessMsg: s__(
'AlertSettings|Sample payload has been parsed. You can now map the fields.',
mapIntro: s__(
"AlertSettings|The default GitLab alert fields are listed below. If you choose to map your payload keys to GitLab's, please make a selection in the dropdowns below. You may also opt to leave the fields unmapped and move straight to saving your integration.",
),
},
mapFields: {
label: s__('AlertSettings|Customize alert payload mapping (optional)'),
intro: s__(
'AlertSettings|If you intend to create a custom mapping, provide an example payload from your monitoring tool and click "parse payload fields" button to continue. The sample payload is required for completing the custom mapping; if you want to skip the mapping step, progress straight to saving your integration.',
testPayload: {
help: s__(
'AlertSettings|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.',
),
placeholder: s__('AlertSettings|{ "events": [{ "application": "Name of application" }] }'),
modalTitle: s__('AlertSettings|The form has unsaved changes'),
modalBody: s__('AlertSettings|The form has unsaved changes. How would you like to proceed?'),
savedAndTest: s__('AlertSettings|Save integration & send'),
proceedWithoutSave: s__('AlertSettings|Send without saving'),
cancel: __('Cancel'),
},
prometheusFormUrl: {
label: s__('AlertSettings|Prometheus API base URL'),
help: s__('AlertSettings|URL cannot be blank and must start with http or https'),
error: s__('AlertSettings|URL is invalid.'),
},
restKeyInfo: {
label: s__(
......@@ -66,40 +79,52 @@ export const i18n = {
},
},
saveIntegration: s__('AlertSettings|Save integration'),
changesSaved: s__('AlertSettings|Your integration was successfully updated.'),
saveAndTestIntegration: s__('AlertSettings|Save & create test alert'),
cancelAndClose: __('Cancel and close'),
send: s__('AlertSettings|Send'),
send: __('Send'),
copy: __('Copy'),
integrationCreated: {
title: s__('AlertSettings|Integration successfully saved'),
successMsg: s__(
'AlertSettings|A URL and authorization key have been created for your integration. You will need them to setup a webhook and authorize your endpoint to send alerts to GitLab.',
),
btnCaption: s__('AlertSettings|View URL and authorization key'),
},
changesSaved: s__('AlertsIntegrations|The integration has been successfully saved.'),
integrationRemoved: s__('AlertsIntegrations|The integration has been successfully removed.'),
alertSent: s__(
'AlertsIntegrations|The test alert has been successfully sent, and should now be visible on your alerts list.',
),
addNewIntegration: s__('AlertSettings|Add new integration'),
};
export const integrationSteps = {
selectType: 'SELECT_TYPE',
nameIntegration: 'NAME_INTEGRATION',
setPrometheusApiUrl: 'SET_PROMETHEUS_API_URL',
setSamplePayload: 'SET_SAMPLE_PAYLOAD',
enableHttpIntegration: 'ENABLE_HTTP_INTEGRATION',
enablePrometheusIntegration: 'ENABLE_PROMETHEUS_INTEGRATION',
customizeMapping: 'CUSTOMIZE_MAPPING',
};
export const createStepNumbers = {
[integrationSteps.selectType]: 1,
[integrationSteps.nameIntegration]: 2,
[integrationSteps.setPrometheusApiUrl]: 2,
[integrationSteps.setSamplePayload]: 3,
[integrationSteps.enableHttpIntegration]: 3,
[integrationSteps.enablePrometheusIntegration]: 2,
[integrationSteps.customizeMapping]: 4,
};
export const editStepNumbers = {
[integrationSteps.selectType]: 1,
[integrationSteps.nameIntegration]: 1,
[integrationSteps.setPrometheusApiUrl]: null,
[integrationSteps.setSamplePayload]: 2,
[integrationSteps.enableHttpIntegration]: 2,
[integrationSteps.enablePrometheusIntegration]: null,
[integrationSteps.customizeMapping]: 3,
};
export const integrationTypes = {
none: { value: '', text: s__('AlertSettings|Select integration type') },
http: { value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
prometheus: { value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
prometheus: { value: 'PROMETHEUS', text: s__('AlertSettings|Prometheus') },
};
export const typeSet = {
......@@ -127,4 +152,10 @@ export const mappingFields = {
fallback: 'fallback',
};
export const viewCredentialsTabIndex = 1;
export const tabIndices = {
configureDetails: 0,
viewCredentials: 1,
sendTestAlert: 2,
};
export const testAlertModalId = 'confirmSendTestAlert';
......@@ -19,23 +19,7 @@ export default (el) => {
return null;
}
const {
prometheusActivated,
prometheusUrl,
prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
activated: activatedStr,
alertsSetupUrl,
alertsUsageUrl,
formPath,
authorizationKey,
url,
projectPath,
multiIntegrations,
alertFields,
} = el.dataset;
const { alertsUsageUrl, projectPath, multiIntegrations, alertFields } = el.dataset;
return new Vue({
el,
......@@ -43,22 +27,7 @@ export default (el) => {
AlertSettingsWrapper,
},
provide: {
prometheus: {
active: parseBoolean(prometheusActivated),
url: prometheusUrl,
token: prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
},
generic: {
alertsSetupUrl,
alertsUsageUrl,
active: parseBoolean(activatedStr),
formPath,
token: authorizationKey,
url,
},
alertsUsageUrl,
projectPath,
multiIntegrations: parseBoolean(multiIntegrations),
},
......
import { s__ } from '~/locale';
import { s__, __ } from '~/locale';
export const DELETE_INTEGRATION_ERROR = s__(
'AlertsIntegrations|The integration could not be deleted. Please try again.',
......@@ -19,3 +19,9 @@ export const RESET_INTEGRATION_TOKEN_ERROR = s__(
export const INTEGRATION_PAYLOAD_TEST_ERROR = s__(
'AlertsIntegrations|Integration payload is invalid.',
);
export const INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR = s__(
'AlertsIntegrations|The integration is currently inactive. Enable the integration to send the test alert.',
);
export const DEFAULT_ERROR = __('Something went wrong on our end.');
......@@ -6,7 +6,6 @@ $stroke-size: 1px;
@include gl-relative;
@include gl-w-full;
height: $stroke-size;
@include gl-display-inline-block;
background-color: var(--gray-400, $gray-400);
min-width: $gl-spacing-scale-5;
......
......@@ -6,11 +6,11 @@
%section.settings.no-animate#js-alert-management-settings{ class: ('expanded' if expanded) }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
= _('Alerts')
= _('Alert integrations')
%button.btn.js-settings-toggle{ type: 'button' }
= _('Expand')
%p
= _('Display alerts from all your monitoring tools directly within GitLab.')
= link_to _('More information'), help_page_path('operations/incident_management/index.md'), target: '_blank', rel: 'noopener noreferrer'
= link_to _('More information'), help_page_path('operations/incident_management/integrations.md'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
.js-alerts-settings{ data: alerts_settings_data }
---
title: Alerts integration form UX cleanup
merge_request: 55892
author:
type: changed
......@@ -2731,6 +2731,9 @@ msgid_plural "Alerts"
msgstr[0] ""
msgstr[1] ""
msgid "Alert integrations"
msgstr ""
msgid "AlertManagement|Acknowledged"
msgstr ""
......@@ -2893,6 +2896,12 @@ msgstr ""
msgid "AlertMappingBuilder|Title is a required field for alerts in GitLab. Should the payload field you specified not be available, specifiy which field we should use instead. "
msgstr ""
msgid "AlertSettings|A URL and authorization key have been created for your integration. You will need them to setup a webhook and authorize your endpoint to send alerts to GitLab."
msgstr ""
msgid "AlertSettings|A webhook URL and authorization key will be generated for the integration. Both will be visible after saving the integration in the “View credentials” tab."
msgstr ""
msgid "AlertSettings|Add new integration"
msgstr ""
......@@ -2911,10 +2920,10 @@ msgstr ""
msgid "AlertSettings|Edit payload"
msgstr ""
msgid "AlertSettings|Enter integration name"
msgid "AlertSettings|Enable integration"
msgstr ""
msgid "AlertSettings|External Prometheus"
msgid "AlertSettings|Enter integration name"
msgstr ""
msgid "AlertSettings|HTTP Endpoint"
......@@ -2923,25 +2932,28 @@ msgstr ""
msgid "AlertSettings|If you edit the payload, the stored mapping will be reset, and you'll need to re-map the fields."
msgstr ""
msgid "AlertSettings|If you intend to create a custom mapping, provide an example payload from your monitoring tool and click \"parse payload fields\" button to continue. The sample payload is required for completing the custom mapping; if you want to skip the mapping step, progress straight to saving your integration."
msgid "AlertSettings|If you intend to create a custom mapping, provide an example payload from your monitoring tool and click the \"parse payload fields\" button to continue. The sample payload is required for completing the custom mapping; if you want to skip the mapping step, progress straight to saving your integration."
msgstr ""
msgid "AlertSettings|In free versions of GitLab, only one integration for each type can be added. %{linkStart}Upgrade your subscription%{linkEnd} to add additional integrations."
msgstr ""
msgid "AlertSettings|Integration successfully saved"
msgstr ""
msgid "AlertSettings|Name integration"
msgstr ""
msgid "AlertSettings|Parse payload for custom mapping"
msgid "AlertSettings|Parse payload fields"
msgstr ""
msgid "AlertSettings|Proceed with editing"
msgstr ""
msgid "AlertSettings|Prometheus API base URL"
msgid "AlertSettings|Prometheus"
msgstr ""
msgid "AlertSettings|Provide an example payload from the monitoring tool you intend to integrate with. This payload can be used to create a custom mapping (optional)."
msgid "AlertSettings|Prometheus API base URL"
msgstr ""
msgid "AlertSettings|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."
......@@ -2956,33 +2968,51 @@ msgstr ""
msgid "AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in."
msgstr ""
msgid "AlertSettings|Sample alert payload (optional)"
msgid "AlertSettings|Sample payload has been parsed. You can now map the fields."
msgstr ""
msgid "AlertSettings|Sample payload has been parsed. You can now map the fields."
msgid "AlertSettings|Save & create test alert"
msgstr ""
msgid "AlertSettings|Save integration"
msgstr ""
msgid "AlertSettings|Select integration type"
msgid "AlertSettings|Save integration & send"
msgstr ""
msgid "AlertSettings|Send"
msgid "AlertSettings|Select integration type"
msgstr ""
msgid "AlertSettings|Send test alert"
msgstr ""
msgid "AlertSettings|Send without saving"
msgstr ""
msgid "AlertSettings|The default GitLab alert fields are listed below. If you choose to map your payload keys to GitLab's, please make a selection in the dropdowns below. You may also opt to leave the fields unmapped and move straight to saving your integration."
msgstr ""
msgid "AlertSettings|The form has unsaved changes"
msgstr ""
msgid "AlertSettings|The form has unsaved changes. How would you like to proceed?"
msgstr ""
msgid "AlertSettings|URL cannot be blank and must start with http or https"
msgstr ""
msgid "AlertSettings|URL is invalid."
msgstr ""
msgid "AlertSettings|Utilize the URL and authorization key below to authorize Prometheus to send alerts to GitLab. Review the Prometheus documentation to learn where to add these details, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
msgid "AlertSettings|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 %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint."
msgstr ""
msgid "AlertSettings|View URL and authorization key"
msgstr ""
msgid "AlertSettings|View credentials"
msgstr ""
......@@ -2992,9 +3022,6 @@ msgstr ""
msgid "AlertSettings|You can now set up alert endpoints for manually configured Prometheus instances in the Alerts section on the Operations settings page. Alert endpoint fields on this page have been deprecated."
msgstr ""
msgid "AlertSettings|Your integration was successfully updated."
msgstr ""
msgid "AlertSettings|{ \"events\": [{ \"application\": \"Name of application\" }] }"
msgstr ""
......@@ -3031,7 +3058,10 @@ msgstr ""
msgid "AlertsIntegrations|The integration has been successfully removed."
msgstr ""
msgid "AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list."
msgid "AlertsIntegrations|The integration has been successfully saved."
msgstr ""
msgid "AlertsIntegrations|The integration is currently inactive. Enable the integration to send the test alert."
msgstr ""
msgid "AlertsIntegrations|The integration token could not be reset. Please try again."
......@@ -20127,6 +20157,9 @@ msgstr ""
msgid "Name"
msgstr ""
msgid "Name can't be blank"
msgstr ""
msgid "Name has already been taken"
msgstr ""
......@@ -27529,6 +27562,9 @@ msgstr ""
msgid "SelfMonitoring|Self monitoring project has been successfully deleted."
msgstr ""
msgid "Send"
msgstr ""
msgid "Send a single email notification to Owners and Maintainers for new alerts."
msgstr ""
......
......@@ -25,7 +25,7 @@ RSpec.describe 'Alert integrations settings form', :js do
it 'shows the alerts setting form title' do
page.within('#js-alert-management-settings') do
expect(find('h4')).to have_content('Alerts')
expect(find('h4')).to have_content('Alert integrations')
end
end
......
......@@ -63,7 +63,7 @@ RSpec.describe 'User searches project settings', :js do
visit project_settings_operations_path(project)
end
it_behaves_like 'can search settings', 'Alerts', 'Error tracking'
it_behaves_like 'can search settings', 'Alert integrations', 'Error tracking'
end
context 'in Pages page' do
......
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { mount, createLocalVue } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
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 { extendedWrapper } from 'helpers/vue_test_utils_helper';
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, {
i18n,
} from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import { typeSet } from '~/alerts_settings/constants';
import AlertsSettingsWrapper from '~/alerts_settings/components/alerts_settings_wrapper.vue';
import { typeSet, i18n } from '~/alerts_settings/constants';
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';
......@@ -27,10 +27,12 @@ import {
RESET_INTEGRATION_TOKEN_ERROR,
UPDATE_INTEGRATION_ERROR,
INTEGRATION_PAYLOAD_TEST_ERROR,
INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
DELETE_INTEGRATION_ERROR,
} from '~/alerts_settings/utils/error_messages';
import createFlash, { FLASH_TYPES } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import {
createHttpVariables,
updateHttpVariables,
......@@ -81,8 +83,9 @@ describe('AlertsSettingsWrapper', () => {
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 findAddIntegrationBtn = () => wrapper.findByTestId('add-integration-btn');
const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm);
const findAlert = () => wrapper.findComponent(GlAlert);
async function destroyHttpIntegration(localWrapper) {
await jest.runOnlyPendingTimers();
......@@ -94,32 +97,34 @@ describe('AlertsSettingsWrapper', () => {
}
async function awaitApolloDomMock() {
await wrapper.vm.$nextTick(); // kick off the DOM update
await nextTick(); // kick off the DOM update
await jest.runOnlyPendingTimers(); // kick off the mocked GQL stuff (promises)
await wrapper.vm.$nextTick(); // kick off the DOM update for flash
await nextTick(); // kick off the DOM update for flash
}
const createComponent = ({ data = {}, provide = {}, loading = false } = {}) => {
wrapper = mount(AlertsSettingsWrapper, {
data() {
return { ...data };
},
provide: {
...defaultAlertSettingsConfig,
...provide,
},
mocks: {
$apollo: {
mutate: jest.fn(),
query: jest.fn(),
queries: {
integrations: {
loading,
wrapper = extendedWrapper(
mount(AlertsSettingsWrapper, {
data() {
return { ...data };
},
provide: {
...defaultAlertSettingsConfig,
...provide,
},
mocks: {
$apollo: {
mutate: jest.fn(),
query: jest.fn(),
queries: {
integrations: {
loading,
},
},
},
},
},
});
}),
);
};
function createComponentWithApollo({
......@@ -200,20 +205,29 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
});
it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { createHttpIntegrationMutation: { integration: { id: '1' } } },
describe('Create', () => {
beforeEach(() => {
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({
data: { httpIntegrationCreate: { integration: { id: '1' }, errors: [] } },
});
findAlertsSettingsForm().vm.$emit('create-new-integration', {
type: typeSet.http,
variables: createHttpVariables,
});
});
findAlertsSettingsForm().vm.$emit('create-new-integration', {
type: typeSet.http,
variables: createHttpVariables,
it('calls `$apollo.mutate` with `createHttpIntegrationMutation`', () => {
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createHttpIntegrationMutation,
update: expect.anything(),
variables: createHttpVariables,
});
});
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: createHttpIntegrationMutation,
update: expect.anything(),
variables: createHttpVariables,
it('shows success alert', () => {
expect(findAlert().exists()).toBe(true);
});
});
......@@ -334,13 +348,29 @@ describe('AlertsSettingsWrapper', () => {
expect(createFlash).toHaveBeenCalledWith({ message: UPDATE_INTEGRATION_ERROR });
});
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(() => {
describe('Test alert failure', () => {
let mock;
beforeEach(() => {
mock = new AxiosMockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('shows an error alert when integration test payload is invalid ', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR });
expect(createFlash).toHaveBeenCalledTimes(1);
mock.restore();
});
it('shows an error alert when integration is not activated ', async () => {
mock.onPost(/(.*)/).replyOnce(httpStatusCodes.FORBIDDEN);
await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' });
expect(createFlash).toHaveBeenCalledWith({
message: INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR,
});
expect(createFlash).toHaveBeenCalledTimes(1);
});
});
......@@ -354,7 +384,7 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate');
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValueOnce({});
findIntegrationsList().vm.$emit('edit-integration', updateHttpVariables);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateCurrentHttpIntegrationMutation,
......@@ -372,7 +402,7 @@ describe('AlertsSettingsWrapper', () => {
loading: false,
});
jest.spyOn(wrapper.vm.$apollo, 'mutate');
jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue();
findIntegrationsList().vm.$emit('edit-integration', updatePrometheusVariables);
expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({
mutation: updateCurrentPrometheusIntegrationMutation,
......@@ -414,7 +444,7 @@ describe('AlertsSettingsWrapper', () => {
createComponentWithApollo();
await jest.runOnlyPendingTimers();
await wrapper.vm.$nextTick();
await nextTick();
expect(findIntegrations()).toHaveLength(4);
});
......@@ -426,7 +456,7 @@ describe('AlertsSettingsWrapper', () => {
expect(destroyIntegrationHandler).toHaveBeenCalled();
await wrapper.vm.$nextTick();
await nextTick();
expect(findIntegrations()).toHaveLength(3);
});
......
......@@ -36,7 +36,7 @@ RSpec.describe 'projects/settings/operations/show' do
it 'renders the Operations Settings page' do
render
expect(rendered).to have_content _('Alerts')
expect(rendered).to have_content _('Alert integrations')
expect(rendered).to have_content _('Display alerts from all your monitoring tools directly within GitLab.')
end
end
......
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