diff options
Diffstat (limited to 'app/assets/javascripts/alerts_settings')
7 files changed, 264 insertions, 49 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alerts_form.vue b/app/assets/javascripts/alerts_settings/components/alerts_form.vue new file mode 100644 index 00000000000..696e7f359d1 --- /dev/null +++ b/app/assets/javascripts/alerts_settings/components/alerts_form.vue @@ -0,0 +1,143 @@ +<script> +import { + GlButton, + GlSprintf, + GlLink, + GlFormGroup, + GlFormCheckbox, + GlDropdown, + GlDropdownItem, +} from '@gitlab/ui'; +import { + I18N_ALERT_SETTINGS_FORM, + NO_ISSUE_TEMPLATE_SELECTED, + TAKING_INCIDENT_ACTION_DOCS_LINK, + ISSUE_TEMPLATES_DOCS_LINK, +} from '../constants'; + +export default { + components: { + GlButton, + GlSprintf, + GlLink, + GlFormGroup, + GlFormCheckbox, + GlDropdown, + GlDropdownItem, + }, + inject: ['service', 'alertSettings'], + data() { + return { + templates: [NO_ISSUE_TEMPLATE_SELECTED, ...this.alertSettings.templates], + createIssueEnabled: this.alertSettings.createIssue, + issueTemplate: this.alertSettings.issueTemplateKey, + sendEmailEnabled: this.alertSettings.sendEmail, + autoCloseIncident: this.alertSettings.autoCloseIncident, + loading: false, + }; + }, + i18n: I18N_ALERT_SETTINGS_FORM, + TAKING_INCIDENT_ACTION_DOCS_LINK, + ISSUE_TEMPLATES_DOCS_LINK, + computed: { + issueTemplateHeader() { + return this.issueTemplate || NO_ISSUE_TEMPLATE_SELECTED.name; + }, + formData() { + return { + create_issue: this.createIssueEnabled, + issue_template_key: this.issueTemplate, + send_email: this.sendEmailEnabled, + auto_close_incident: this.autoCloseIncident, + }; + }, + }, + methods: { + selectIssueTemplate(templateKey) { + this.issueTemplate = templateKey; + }, + isTemplateSelected(templateKey) { + return templateKey === this.issueTemplate; + }, + updateAlertsIntegrationSettings() { + this.loading = true; + + this.service.updateSettings(this.formData).catch(() => { + this.loading = false; + }); + }, + }, +}; +</script> + +<template> + <div> + <p> + <gl-sprintf :message="$options.i18n.introText"> + <template #docsLink> + <gl-link :href="$options.TAKING_INCIDENT_ACTION_DOCS_LINK" target="_blank"> + <span>{{ $options.i18n.introLinkText }}</span> + </gl-link> + </template> + </gl-sprintf> + </p> + <form ref="settingsForm" @submit.prevent="updateAlertsIntegrationSettings"> + <gl-form-group class="gl-pl-0"> + <gl-form-checkbox v-model="createIssueEnabled" data-qa-selector="create_issue_checkbox"> + <span>{{ $options.i18n.createIncident.label }}</span> + </gl-form-checkbox> + </gl-form-group> + + <gl-form-group + label-size="sm" + label-for="alert-integration-settings-issue-template" + class="col-8 col-md-9 gl-px-6" + > + <label class="gl-display-inline-flex" for="alert-integration-settings-issue-template"> + {{ $options.i18n.incidentTemplate.label }} + <gl-link :href="$options.ISSUE_TEMPLATES_DOCS_LINK" target="_blank"> + <span class="gl-font-weight-normal gl-pl-2">{{ $options.i18n.introLinkText }}</span> + </gl-link> + </label> + <gl-dropdown + id="alert-integration-settings-issue-template" + data-qa-selector="incident_templates_dropdown" + :text="issueTemplateHeader" + :block="true" + > + <gl-dropdown-item + v-for="template in templates" + :key="template.key" + data-qa-selector="incident_templates_item" + :is-check-item="true" + :is-checked="isTemplateSelected(template.key)" + @click="selectIssueTemplate(template.key)" + > + {{ template.name }} + </gl-dropdown-item> + </gl-dropdown> + </gl-form-group> + + <gl-form-group class="gl-pl-0 gl-mb-5"> + <gl-form-checkbox v-model="sendEmailEnabled"> + <span>{{ $options.i18n.sendEmail.label }}</span> + </gl-form-checkbox> + </gl-form-group> + <gl-form-group class="gl-pl-0 gl-mb-5"> + <gl-form-checkbox v-model="autoCloseIncident"> + <span>{{ $options.i18n.autoCloseIncidents.label }}</span> + </gl-form-checkbox> + </gl-form-group> + <gl-button + ref="submitBtn" + data-qa-selector="save_changes_button" + :disabled="loading" + variant="confirm" + type="submit" + class="js-no-auto-disable" + > + {{ $options.i18n.saveBtnLabel }} + </gl-button> + </form> + </div> +</template> diff --git a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue index d9e5878b9e3..f4cc0678c38 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_integrations_list.vue @@ -23,7 +23,6 @@ import getCurrentIntegrationQuery from '../graphql/queries/get_current_integrati export const i18n = { deleteIntegration: s__('AlertSettings|Delete integration'), editIntegration: s__('AlertSettings|Edit integration'), - title: s__('AlertsIntegrations|Current integrations'), emptyState: s__('AlertsIntegrations|No integrations have been added yet.'), status: { enabled: { @@ -141,7 +140,6 @@ export default { <template> <div class="incident-management-list"> - <h5 class="gl-font-lg gl-mt-5">{{ $options.i18n.title }}</h5> <gl-table class="integration-list" :items="integrations" @@ -155,7 +153,7 @@ export default { <span v-if="item.active" data-testid="integration-activated-status"> <gl-icon v-gl-tooltip - name="check-circle-filled" + name="check" :size="16" class="gl-text-green-500 gl-hover-cursor-pointer gl-mr-3" :title="$options.i18n.status.enabled.tooltip" diff --git a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue index 3917e4c5fdd..902bad780ad 100644 --- a/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue +++ b/app/assets/javascripts/alerts_settings/components/alerts_settings_wrapper.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlAlert } from '@gitlab/ui'; +import { GlButton, GlAlert, GlTabs, GlTab } 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'; @@ -30,6 +30,7 @@ import { INTEGRATION_INACTIVE_PAYLOAD_TEST_ERROR, DEFAULT_ERROR, } from '../utils/error_messages'; +import AlertsForm from './alerts_form.vue'; import IntegrationsList from './alerts_integrations_list.vue'; import AlertSettingsForm from './alerts_settings_form.vue'; @@ -38,9 +39,12 @@ export default { i18n, components: { IntegrationsList, + AlertsForm, AlertSettingsForm, - GlButton, GlAlert, + GlButton, + GlTabs, + GlTab, }, inject: { projectPath: { @@ -167,7 +171,7 @@ export default { if (testAfterSubmit) { this.viewIntegration(integration, tabIndices.sendTestAlert); } else { - this.clearCurrentIntegration(type); + this.clearCurrentIntegration({ type }); } createFlash({ @@ -347,46 +351,51 @@ export default { </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> + <gl-tabs data-testid="alert-integration-settings"> + <gl-tab :title="$options.i18n.settingsTabs.currentIntegrations"> + <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" - :loading="loading" - @edit-integration="editIntegration" - @delete-integration="deleteIntegration" - /> - <gl-button - v-if="canAddIntegration && !formVisible" - category="secondary" - variant="confirm" - data-testid="add-integration-btn" - class="gl-mt-3" - @click="setFormVisibility(true)" - > - {{ $options.i18n.addNewIntegration }} - </gl-button> - <alert-settings-form - v-if="formVisible" - :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> + <integrations-list + :integrations="integrations" + :loading="loading" + @edit-integration="editIntegration" + @delete-integration="deleteIntegration" + /> + <gl-button + v-if="canAddIntegration && !formVisible" + category="secondary" + variant="confirm" + data-testid="add-integration-btn" + class="gl-mt-3" + @click="setFormVisibility(true)" + > + {{ $options.i18n.addNewIntegration }} + </gl-button> + <alert-settings-form + v-if="formVisible" + :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" + /> + </gl-tab> + <gl-tab :title="$options.i18n.settingsTabs.integrationSettings"> + <alerts-form class="gl-pt-3" data-testid="alert-integration-settings-tab" /> + </gl-tab> + </gl-tabs> </template> diff --git a/app/assets/javascripts/alerts_settings/constants.js b/app/assets/javascripts/alerts_settings/constants.js index 4a180ed2bc0..b93119d6e6a 100644 --- a/app/assets/javascripts/alerts_settings/constants.js +++ b/app/assets/javascripts/alerts_settings/constants.js @@ -93,6 +93,10 @@ export const i18n = { integrationRemoved: s__('AlertsIntegrations|The integration is deleted.'), alertSent: s__('AlertsIntegrations|The test alert should now be visible in your alerts list.'), addNewIntegration: s__('AlertSettings|Add new integration'), + settingsTabs: { + currentIntegrations: s__('AlertSettings|Current integrations'), + integrationSettings: s__('AlertSettings|Alert settings'), + }, }; export const integrationSteps = { @@ -156,3 +160,31 @@ export const tabIndices = { }; export const testAlertModalId = 'confirmSendTestAlert'; + +/* Alerts integration settings constants */ + +export const I18N_ALERT_SETTINGS_FORM = { + saveBtnLabel: __('Save changes'), + introText: __('Action to take when receiving an alert. %{docsLink}'), + introLinkText: __('Learn more.'), + createIncident: { + label: __('Create an incident. Incidents are created for each alert triggered.'), + }, + incidentTemplate: { + label: __('Incident template (optional).'), + }, + sendEmail: { + label: __('Send a single email notification to Owners and Maintainers for new alerts.'), + }, + autoCloseIncidents: { + label: __( + 'Automatically close associated incident when a recovery alert notification resolves an alert', + ), + }, +}; + +export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selected') }; +export const TAKING_INCIDENT_ACTION_DOCS_LINK = + '/help/operations/metrics/alerts#trigger-actions-from-alerts'; +export const ISSUE_TEMPLATES_DOCS_LINK = + '/help/user/project/description_templates#create-an-issue-template'; diff --git a/app/assets/javascripts/alerts_settings/graphql.js b/app/assets/javascripts/alerts_settings/graphql.js index 72817f636ff..15862f4034a 100644 --- a/app/assets/javascripts/alerts_settings/graphql.js +++ b/app/assets/javascripts/alerts_settings/graphql.js @@ -1,9 +1,15 @@ +import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; import produce from 'immer'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; +import introspectionQueryResultData from './graphql/fragmentTypes.json'; import getCurrentIntegrationQuery from './graphql/queries/get_current_integration.query.graphql'; +const fragmentMatcher = new IntrospectionFragmentMatcher({ + introspectionQueryResultData, +}); + Vue.use(VueApollo); const resolvers = { @@ -50,7 +56,9 @@ const resolvers = { export default new VueApollo({ defaultClient: createDefaultClient(resolvers, { - cacheConfig: {}, + cacheConfig: { + fragmentMatcher, + }, assumeImmutableResults: true, }), }); diff --git a/app/assets/javascripts/alerts_settings/graphql/fragmentTypes.json b/app/assets/javascripts/alerts_settings/graphql/fragmentTypes.json new file mode 100644 index 00000000000..07dfc43aa6c --- /dev/null +++ b/app/assets/javascripts/alerts_settings/graphql/fragmentTypes.json @@ -0,0 +1 @@ +{"__schema":{"types":[{"kind":"UNION","name":"AlertManagementIntegration","possibleTypes":[{"name":"AlertManagementHttpIntegration"},{"name":"AlertManagementPrometheusIntegration"}]}]}} diff --git a/app/assets/javascripts/alerts_settings/index.js b/app/assets/javascripts/alerts_settings/index.js index 953a867b2b7..19cdf1d7a19 100644 --- a/app/assets/javascripts/alerts_settings/index.js +++ b/app/assets/javascripts/alerts_settings/index.js @@ -1,5 +1,6 @@ import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; +import IncidentsSettingsService from '~/incidents_settings/incidents_settings_service'; import { parseBoolean } from '~/lib/utils/common_utils'; import AlertSettingsWrapper from './components/alerts_settings_wrapper.vue'; import apolloProvider from './graphql'; @@ -19,14 +20,37 @@ export default (el) => { return null; } - const { alertsUsageUrl, projectPath, multiIntegrations, alertFields } = el.dataset; + const { + alertsUsageUrl, + projectPath, + multiIntegrations, + alertFields, + templates, + createIssue, + issueTemplateKey, + sendEmail, + autoCloseIncident, + pagerdutyResetKeyPath, + operationsSettingsEndpoint, + } = el.dataset; + const service = new IncidentsSettingsService(operationsSettingsEndpoint, pagerdutyResetKeyPath); return new Vue({ el, components: { AlertSettingsWrapper, }, provide: { + service, + alertSettings: { + templates: JSON.parse(templates), + createIssue: parseBoolean(createIssue), + issueTemplateKey, + sendEmail: parseBoolean(sendEmail), + autoCloseIncident: parseBoolean(autoCloseIncident), + pagerdutyResetKeyPath, + operationsSettingsEndpoint, + }, alertsUsageUrl, projectPath, multiIntegrations: parseBoolean(multiIntegrations), |