Commit ac9eb212 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska Committed by Nicolò Maria Mezzopera

Alert integrations table

parent 1372ea0a
<script>
import { GlTable } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export const i18n = {
title: s__('AlertsIntegrations|Current integrations'),
emptyState: s__('AlertsIntegrations|No integrations have been added yet'),
status: {
enabled: __('Enabled'),
disabled: __('Disabled'),
},
};
const bodyTrClass =
'gl-border-1 gl-border-t-solid gl-border-b-solid gl-border-gray-100 gl-hover-cursor-pointer gl-hover-bg-blue-50 gl-hover-border-blue-200';
export default {
i18n,
components: {
GlTable,
},
props: {
integrations: {
type: Array,
required: false,
default: () => [],
},
},
fields: [
{
key: 'status',
label: __('Status'),
formatter(enabled) {
return enabled ? i18n.status.enabled : i18n.status.disabled;
},
},
{
key: 'name',
label: s__('AlertsIntegrations|Integration Name'),
},
{
key: 'type',
label: __('Type'),
},
],
computed: {
tbodyTrClass() {
return {
[bodyTrClass]: this.integrations.length,
};
},
},
};
</script>
<template>
<div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5>
<gl-table
:empty-text="$options.i18n.emptyState"
:items="integrations"
:fields="$options.fields"
stacked="md"
:tbody-tr-class="tbodyTrClass"
show-empty
/>
</div>
</template>
......@@ -14,9 +14,12 @@ import {
GlFormSelect,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import { s__ } from '~/locale';
import { doesHashExistInUrl } from '~/lib/utils/url_utility';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
import IntegrationsList from './alerts_integrations_list.vue';
import csrf from '~/lib/utils/csrf';
import service from '../services';
import {
......@@ -25,7 +28,9 @@ import {
JSON_VALIDATE_DELAY,
targetPrometheusUrlPlaceholder,
targetOpsgenieUrlPlaceholder,
sectionHash,
} from '../constants';
import createFlash, { FLASH_TYPES } from '~/flash';
export default {
i18n,
......@@ -46,6 +51,7 @@ export default {
GlSprintf,
ClipboardButton,
ToggleButton,
IntegrationsList,
},
directives: {
'gl-modal': GlModalDirective,
......@@ -148,6 +154,20 @@ export default {
? this.$options.targetOpsgenieUrlPlaceholder
: this.$options.targetPrometheusUrlPlaceholder;
},
integrations() {
return [
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
status: this.generic.activated,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
status: this.prometheus.activated,
},
];
},
},
watch: {
'testAlert.json': debounce(function debouncedJsonValidate() {
......@@ -245,25 +265,7 @@ export default {
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } },
})
.then(() => {
this.active = value;
this.toggleSuccess(value);
if (!this.isOpsgenie && value) {
if (!this.selectedService.authKey) {
return window.location.reload();
}
return this.removeOpsGenieOption();
}
if (this.isOpsgenie && value) {
return this.setOpsgenieAsDefault();
}
// eslint-disable-next-line no-return-assign
return (this.options = serviceOptions);
})
.then(() => this.notifySuccessAndReload())
.catch(({ response: { data: { errors } = {} } = {} }) => {
this.createUserErrorMessage(errors);
this.setFeedback({
......@@ -276,6 +278,12 @@ export default {
this.canSaveForm = false;
});
},
reload() {
if (!doesHashExistInUrl(sectionHash)) {
window.location.hash = sectionHash;
}
window.location.reload();
},
togglePrometheusActive(value) {
this.loading = true;
return service
......@@ -288,11 +296,7 @@ export default {
redirect: window.location,
},
})
.then(() => {
this.active = value;
this.toggleSuccess(value);
this.removeOpsGenieOption();
})
.then(() => this.notifySuccessAndReload())
.catch(({ response: { data: { errors } = {} } = {} }) => {
this.createUserErrorMessage(errors);
this.setFeedback({
......@@ -305,18 +309,9 @@ export default {
this.canSaveForm = false;
});
},
toggleSuccess(value) {
if (value) {
this.setFeedback({
feedbackMessage: this.$options.i18n.endPointActivated,
variant: 'info',
});
} else {
this.setFeedback({
feedbackMessage: this.$options.i18n.changesSaved,
variant: 'info',
});
}
notifySuccessAndReload() {
createFlash({ message: this.$options.i18n.changesSaved, type: FLASH_TYPES.NOTICE });
setTimeout(() => this.reload(), 1000);
},
setFeedback({ feedbackMessage, variant }) {
this.feedback = { feedbackMessage, variant };
......@@ -389,21 +384,22 @@ export default {
{{ __('Save anyway') }}
</gl-button>
</gl-alert>
<div data-testid="alert-settings-description" class="gl-mt-5">
<p v-for="section in sections" :key="section.text">
<gl-sprintf :message="section.text">
<template #link="{ content }">
<gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<integrations-list :integrations="integrations" />
<gl-form @submit.prevent="onSubmit" @reset.prevent="onReset">
<gl-form-group
:label="$options.i18n.integrationsLabel"
label-for="integrations"
label-class="label-bold"
>
<h5 class="gl-font-lg">{{ $options.i18n.integrationsLabel }}</h5>
<gl-form-group label-for="integrations" label-class="gl-font-weight-bold">
<div data-testid="alert-settings-description" class="gl-mt-5">
<p v-for="section in sections" :key="section.text">
<gl-sprintf :message="section.text">
<template #link="{ content }">
<gl-link :href="section.url" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<gl-form-select
v-model="selectedEndpoint"
:options="options"
......@@ -426,7 +422,7 @@ export default {
<gl-form-group
:label="$options.i18n.activeLabel"
label-for="activated"
label-class="label-bold"
label-class="gl-font-weight-bold"
>
<toggle-button
id="activated"
......@@ -440,7 +436,7 @@ export default {
v-if="isOpsgenie || isPrometheus"
:label="$options.i18n.apiBaseUrlLabel"
label-for="api-url"
label-class="label-bold"
label-class="gl-font-weight-bold"
>
<gl-form-input
id="api-url"
......@@ -454,7 +450,11 @@ export default {
</span>
</gl-form-group>
<template v-if="!isOpsgenie">
<gl-form-group :label="$options.i18n.urlLabel" label-for="url" label-class="label-bold">
<gl-form-group
:label="$options.i18n.urlLabel"
label-for="url"
label-class="gl-font-weight-bold"
>
<gl-form-input-group id="url" readonly :value="selectedService.url">
<template #append>
<clipboard-button
......@@ -471,7 +471,7 @@ export default {
<gl-form-group
:label="$options.i18n.authKeyLabel"
label-for="authorization-key"
label-class="label-bold"
label-class="gl-font-weight-bold"
>
<gl-form-input-group id="authorization-key" class="gl-mb-2" readonly :value="authKey">
<template #append>
......@@ -498,7 +498,7 @@ export default {
<gl-form-group
:label="$options.i18n.alertJson"
label-for="alert-json"
label-class="label-bold"
label-class="gl-font-weight-bold"
:invalid-feedback="testAlert.error"
>
<gl-form-textarea
......
......@@ -14,15 +14,14 @@ export const i18n = {
restKeyInfo: s__(
'AlertSettings|Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.',
),
endPointActivated: s__('AlertSettings|Alerts endpoint successfully activated.'),
changesSaved: s__('AlertSettings|Your changes were successfully updated.'),
changesSaved: s__('AlertSettings|Your integration was successfully updated.'),
prometheusInfo: s__('AlertSettings|Add URL and auth key to your Prometheus config file'),
integrationsInfo: s__(
'AlertSettings|Learn more about our %{linkStart}upcoming integrations%{linkEnd}',
),
resetKey: s__('AlertSettings|Reset key'),
copyToClipboard: s__('AlertSettings|Copy'),
integrationsLabel: s__('AlertSettings|Integrations'),
integrationsLabel: s__('AlertSettings|Add new integrations'),
apiBaseUrlLabel: s__('AlertSettings|API URL'),
authKeyLabel: s__('AlertSettings|Authorization key'),
urlLabel: s__('AlertSettings|Webhook URL'),
......@@ -41,7 +40,7 @@ export const i18n = {
};
export const serviceOptions = [
{ value: 'generic', text: s__('AlertSettings|Generic') },
{ value: 'generic', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'prometheus', text: s__('AlertSettings|External Prometheus') },
{ value: 'opsgenie', text: s__('AlertSettings|Opsgenie') },
];
......@@ -50,3 +49,5 @@ export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
export const targetOpsgenieUrlPlaceholder = 'https://app.opsgenie.com/alert/list/';
export const sectionHash = 'js-alert-management-settings';
---
title: Add the Alerts integrations table to Alert integrations settings in the Operations section
merge_request: 44181
author:
type: added
......@@ -2410,10 +2410,10 @@ msgstr ""
msgid "AlertSettings|Add URL and auth key to your Prometheus config file"
msgstr ""
msgid "AlertSettings|Alert test payload"
msgid "AlertSettings|Add new integrations"
msgstr ""
msgid "AlertSettings|Alerts endpoint successfully activated."
msgid "AlertSettings|Alert test payload"
msgstr ""
msgid "AlertSettings|Authorization key"
......@@ -2431,10 +2431,10 @@ msgstr ""
msgid "AlertSettings|External Prometheus"
msgstr ""
msgid "AlertSettings|Generic"
msgid "AlertSettings|HTTP Endpoint"
msgstr ""
msgid "AlertSettings|Integrations"
msgid "AlertSettings|HTTP endpoint"
msgstr ""
msgid "AlertSettings|Learn more about our %{linkStart}upcoming integrations%{linkEnd}"
......@@ -2479,7 +2479,7 @@ msgstr ""
msgid "AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page."
msgstr ""
msgid "AlertSettings|Your changes were successfully updated."
msgid "AlertSettings|Your integration was successfully updated."
msgstr ""
msgid "Alerts"
......@@ -2488,6 +2488,21 @@ msgstr ""
msgid "Alerts endpoint"
msgstr ""
msgid "AlertsIntegrations|Current integrations"
msgstr ""
msgid "AlertsIntegrations|HTTP endpoint"
msgstr ""
msgid "AlertsIntegrations|Integration Name"
msgstr ""
msgid "AlertsIntegrations|No integrations have been added yet"
msgstr ""
msgid "AlertsIntegrations|Prometheus"
msgstr ""
msgid "Algorithm"
msgstr ""
......
......@@ -3,35 +3,37 @@
exports[`AlertsSettingsForm with default values renders the initial template 1`] = `
"<div>
<!---->
<div data-testid=\\"alert-settings-description\\" class=\\"gl-mt-5\\">
<p>
<gl-sprintf-stub message=\\"You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.\\"></gl-sprintf-stub>
</p>
<p>
<gl-sprintf-stub message=\\"Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.\\"></gl-sprintf-stub>
</p>
</div>
<integrations-list-stub integrations=\\"[object Object],[object Object]\\"></integrations-list-stub>
<gl-form-stub>
<gl-form-group-stub label=\\"Integrations\\" label-for=\\"integrations\\" label-class=\\"label-bold\\">
<h5 class=\\"gl-font-lg\\">Add new integrations</h5>
<gl-form-group-stub label-for=\\"integrations\\" label-class=\\"gl-font-weight-bold\\">
<div data-testid=\\"alert-settings-description\\" class=\\"gl-mt-5\\">
<p>
<gl-sprintf-stub message=\\"You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.\\"></gl-sprintf-stub>
</p>
<p>
<gl-sprintf-stub message=\\"Review your external service's documentation to learn where to provide this information to your external service, and the %{linkStart}GitLab documentation%{linkEnd} to learn more about configuring your endpoint.\\"></gl-sprintf-stub>
</p>
</div>
<gl-form-select-stub options=\\"[object Object],[object Object],[object Object]\\" data-testid=\\"alert-settings-select\\" value=\\"generic\\"></gl-form-select-stub> <span class=\\"gl-text-gray-200\\"><gl-sprintf-stub message=\\"Learn more about our %{linkStart}upcoming integrations%{linkEnd}\\"></gl-sprintf-stub></span>
</gl-form-group-stub>
<gl-form-group-stub label=\\"Active\\" label-for=\\"activated\\" label-class=\\"label-bold\\">
<gl-form-group-stub label=\\"Active\\" label-for=\\"activated\\" label-class=\\"gl-font-weight-bold\\">
<toggle-button-stub id=\\"activated\\"></toggle-button-stub>
</gl-form-group-stub>
<!---->
<gl-form-group-stub label=\\"Webhook URL\\" label-for=\\"url\\" label-class=\\"label-bold\\">
<gl-form-group-stub label=\\"Webhook URL\\" label-for=\\"url\\" label-class=\\"gl-font-weight-bold\\">
<gl-form-input-group-stub value=\\"/alerts/notify.json\\" predefinedoptions=\\"[object Object]\\" id=\\"url\\" readonly=\\"\\"></gl-form-input-group-stub> <span class=\\"gl-text-gray-200\\">
</span>
</gl-form-group-stub>
<gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"label-bold\\">
<gl-form-group-stub label=\\"Authorization key\\" label-for=\\"authorization-key\\" label-class=\\"gl-font-weight-bold\\">
<gl-form-input-group-stub value=\\"abcedfg123\\" predefinedoptions=\\"[object Object]\\" id=\\"authorization-key\\" readonly=\\"\\" class=\\"gl-mb-2\\"></gl-form-input-group-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\" class=\\"gl-mt-3\\" role=\\"button\\" tabindex=\\"0\\">Reset key</gl-button-stub>
<gl-modal-stub modalid=\\"authKeyModal\\" titletag=\\"h4\\" modalclass=\\"\\" size=\\"md\\" title=\\"Reset key\\" ok-title=\\"Reset key\\" ok-variant=\\"danger\\">
Resetting the authorization key for this project will require updating the authorization key in every alert source it is enabled in.
</gl-modal-stub>
</gl-form-group-stub>
<gl-form-group-stub label=\\"Alert test payload\\" label-for=\\"alert-json\\" label-class=\\"label-bold\\">
<gl-form-group-stub label=\\"Alert test payload\\" label-for=\\"alert-json\\" label-class=\\"gl-font-weight-bold\\">
<gl-form-textarea-stub noresize=\\"true\\" id=\\"alert-json\\" disabled=\\"true\\" state=\\"true\\" placeholder=\\"Enter test alert JSON....\\" rows=\\"6\\" max-rows=\\"10\\"></gl-form-textarea-stub>
</gl-form-group-stub>
<gl-button-stub category=\\"primary\\" variant=\\"default\\" size=\\"medium\\" icon=\\"\\" buttontextclasses=\\"\\" disabled=\\"true\\">Test alert payload</gl-button-stub>
......
......@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import { GlModal, GlAlert } from '@gitlab/ui';
import AlertsSettingsForm from '~/alerts_settings/components/alerts_settings_form.vue';
import IntegrationsList from '~/alerts_settings/components/alerts_integrations_list.vue';
import ToggleButton from '~/vue_shared/components/toggle_button.vue';
const PROMETHEUS_URL = '/prometheus/alerts/notify.json';
......@@ -76,6 +77,11 @@ describe('AlertsSettingsForm', () => {
});
});
it('renders alerts integrations list', () => {
createComponent();
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
});
describe('reset key', () => {
it('triggers resetKey method', () => {
const resetKey = jest.fn();
......
import { GlTable } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AlertIntegrationsList, {
i18n,
} from '~/alerts_settings/components/alerts_integrations_list.vue';
const mockIntegrations = [
{
status: true,
name: 'Integration 1',
type: 'HTTP endpoint',
},
{
status: false,
name: 'Integration 2',
type: 'HTTP endpoint',
},
];
describe('AlertIntegrationsList', () => {
let wrapper;
function mountComponent(propsData = {}) {
wrapper = shallowMount(AlertIntegrationsList, {
propsData: {
integrations: mockIntegrations,
...propsData,
},
});
}
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
beforeEach(() => {
mountComponent();
});
const findTableComponent = () => wrapper.find(GlTable);
it('renders a table', () => {
expect(findTableComponent().exists()).toBe(true);
expect(findTableComponent().attributes('empty-text')).toBe(i18n.emptyState);
});
});
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