Commit 9fdd6aa4 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '119018-move-alert-settings-to-Vue' into 'master'

Move alert integration settings to Vue

See merge request gitlab-org/gitlab!36110
parents 631c8ff7 6c6ac293
<script>
import {
GlButton,
GlSprintf,
GlLink,
GlIcon,
GlFormGroup,
GlFormCheckbox,
GlNewDropdown,
GlNewDropdownItem,
} from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import {
I18N_ALERT_SETTINGS_FORM,
NO_ISSUE_TEMPLATE_SELECTED,
TAKING_INCIDENT_ACTION_DOCS_LINK,
ISSUE_TEMPLATES_DOCS_LINK,
ERROR_MSG,
} from '../constants';
export default {
components: {
GlButton,
GlSprintf,
GlLink,
GlFormGroup,
GlIcon,
GlFormCheckbox,
GlNewDropdown,
GlNewDropdownItem,
},
inject: ['alertSettings', 'operationsSettingsEndpoint'],
data() {
return {
templates: [NO_ISSUE_TEMPLATE_SELECTED, ...this.alertSettings.templates],
createIssueEnabled: this.alertSettings.createIssue,
issueTemplate: this.alertSettings.issueTemplateKey,
sendEmailEnabled: this.alertSettings.sendEmail,
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,
};
},
},
methods: {
selectIssueTemplate(templateKey) {
this.issueTemplate = templateKey;
},
isTemplateSelected(templateKey) {
return templateKey === this.issueTemplate;
},
updateAlertsIntegrationSettings() {
this.loading = true;
return axios
.patch(this.operationsSettingsEndpoint, {
project: {
incident_management_setting_attributes: this.formData,
},
})
.then(() => {
refreshCurrentPage();
})
.catch(({ response }) => {
const message = response?.data?.message || '';
createFlash(`${ERROR_MSG} ${message}`, 'alert');
})
.finally(() => {
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.createIssue.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.issueTemplate.label }}
<gl-link :href="$options.ISSUE_TEMPLATES_DOCS_LINK" target="_blank">
<gl-icon name="question" :size="12" />
</gl-link>
</label>
<gl-new-dropdown
id="alert-integration-settings-issue-template"
data-qa-selector="incident_templates_dropdown"
:text="issueTemplateHeader"
:block="true"
>
<gl-new-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-new-dropdown-item>
</gl-new-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-button
ref="submitBtn"
data-qa-selector="save_changes_button"
:disabled="loading"
variant="success"
type="submit"
class="js-no-auto-disable"
>
{{ $options.i18n.saveBtnLabel }}
</gl-button>
</form>
</div>
</template>
<script>
import { GlButton, GlTabs, GlTab } from '@gitlab/ui';
import AlertsSettingsForm from './alerts_form.vue';
import { INTEGRATION_TABS_CONFIG, I18N_INTEGRATION_TABS } from '../constants';
export default {
components: {
GlButton,
GlTabs,
GlTab,
AlertsSettingsForm,
},
tabs: INTEGRATION_TABS_CONFIG,
i18n: I18N_INTEGRATION_TABS,
};
</script>
<template>
<section
id="incident-management-settings"
data-qa-selector="incidents_settings_content"
class="settings no-animate qa-incident-management-settings"
>
<div class="settings-header">
<h3 ref="sectionHeader" class="h4">
{{ $options.i18n.headerText }}
</h3>
<gl-button ref="toggleBtn" class="js-settings-toggle">{{
$options.i18n.expandBtnLabel
}}</gl-button>
<p ref="sectionSubHeader">
{{ $options.i18n.subHeaderText }}
</p>
</div>
<div class="settings-content">
<gl-tabs>
<gl-tab
v-for="(tab, index) in $options.tabs"
v-if="tab.active"
:key="`${tab.title}_${index}`"
:title="tab.title"
>
<component :is="tab.component" class="gl-pt-3" :data-testid="`${tab.component}-tab`" />
</gl-tab>
</gl-tabs>
</div>
</section>
</template>
import { __, s__ } from '~/locale';
export const INTEGRATION_TABS_CONFIG = [
{
title: s__('IncidentSettings|Alert integration'),
component: 'AlertsSettingsForm',
active: true,
},
{
title: s__('IncidentSettings|PagerDuty integration'),
component: '',
active: false,
},
{
title: s__('IncidentSettings|Grafana integration'),
component: '',
active: false,
},
];
export const I18N_INTEGRATION_TABS = {
headerText: s__('IncidentSettings|Incidents'),
expandBtnLabel: __('Expand'),
saveBtnLabel: __('Save changes'),
subHeaderText: s__(
'IncidentSettings|Set up integrations with external tools to help better manage incidents.',
),
};
export const I18N_ALERT_SETTINGS_FORM = {
saveBtnLabel: __('Save changes'),
introText: __('Action to take when receiving an alert. %{docsLink}'),
introLinkText: __('More information.'),
createIssue: {
label: __('Create an issue. Issues are created for each alert triggered.'),
},
issueTemplate: {
label: __('Issue template (optional)'),
},
sendEmail: {
label: __('Send a separate email notification to Developers.'),
},
};
export const NO_ISSUE_TEMPLATE_SELECTED = { key: '', name: __('No template selected') };
export const TAKING_INCIDENT_ACTION_DOCS_LINK =
'/help/user/project/integrations/prometheus#taking-action-on-incidents-ultimate';
export const ISSUE_TEMPLATES_DOCS_LINK =
'/help/user/project/description_templates#creating-issue-templates';
export const ERROR_MSG = __('There was an error saving your changes.');
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import SettingsTabs from './components/incidents_settings_tabs.vue';
export default () => {
const el = document.querySelector('.js-incidents-settings');
if (!el) {
return null;
}
const {
dataset: { operationsSettingsEndpoint, templates, createIssue, issueTemplateKey, sendEmail },
} = el;
return new Vue({
el,
provide: {
operationsSettingsEndpoint,
alertSettings: {
templates: JSON.parse(templates),
createIssue: parseBoolean(createIssue),
issueTemplateKey,
sendEmail: parseBoolean(sendEmail),
},
},
render(createElement) {
return createElement(SettingsTabs);
},
});
};
......@@ -3,8 +3,10 @@ import mountAlertsSettings from '~/alerts_settings';
import mountOperationSettings from '~/operation_settings';
import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels';
import initIncidentsSettings from '~/incidents_settings';
document.addEventListener('DOMContentLoaded', () => {
initIncidentsSettings();
mountErrorTrackingForm();
mountOperationSettings();
mountGrafanaIntegration();
......
- templates = []
- setting = project_incident_management_setting
- templates = setting.available_issue_templates.map { |t| [t.name, t.key] }
- templates = setting.available_issue_templates.map { |t| { key: t.key, name: t.name } }
%section.settings.no-animate.qa-incident-management-settings{ data: { qa_selector: 'incidents_settings_content' } }
.settings-header
%h3{ :class => "h4" }= _('Incidents')
%button.btn.js-settings-toggle{ type: 'button' }
= _('Expand')
%p
= _('Action to take when receiving an alert.')
= link_to help_page_path('user/project/integrations/prometheus', anchor: 'taking-action-on-incidents-ultimate') do
= _('More information')
.settings-content
= form_for @project, url: project_settings_operations_path(@project), method: :patch do |f|
= form_errors(@project.incident_management_setting)
.form-group
= f.fields_for :incident_management_setting_attributes, setting do |form|
.form-group
= form.check_box :create_issue, data: { qa_selector: 'create_issue_checkbox' }
= form.label :create_issue, _('Create an issue. Issues are created for each alert triggered.'), class: 'form-check-label'
.form-group.col-sm-8
= form.label :issue_template_key, class: 'label-bold' do
= _('Issue template (optional)')
= link_to icon('question-circle'), help_page_path('user/project/description_templates', anchor: 'creating-issue-templates'), target: '_blank', rel: 'noopener noreferrer'
.select-wrapper
= form.select :issue_template_key, templates, {include_blank: 'No template selected'}, class: "form-control select-control", data: { qa_selector: 'incident_templates_dropdown' }
= icon('chevron-down')
.form-group
= form.check_box :send_email
= form.label :send_email, _('Send a separate email notification to Developers.'), class: 'form-check-label'
= f.submit _('Save changes'), class: 'btn btn-success', data: { qa_selector: 'save_changes_button' }
.js-incidents-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
templates: templates.to_json,
create_issue: setting.create_issue.to_s,
issue_template_key: setting.issue_template_key.to_s,
send_email: setting.send_email.to_s } }
---
title: Move alert integrations setting to Vue
merge_request: 36110
author:
type: changed
......@@ -1289,7 +1289,7 @@ msgstr ""
msgid "Account: %{account}"
msgstr ""
msgid "Action to take when receiving an alert."
msgid "Action to take when receiving an alert. %{docsLink}"
msgstr ""
msgid "Actions"
......@@ -12407,7 +12407,19 @@ msgstr ""
msgid "Incident Management Limits"
msgstr ""
msgid "Incidents"
msgid "IncidentSettings|Alert integration"
msgstr ""
msgid "IncidentSettings|Grafana integration"
msgstr ""
msgid "IncidentSettings|Incidents"
msgstr ""
msgid "IncidentSettings|PagerDuty integration"
msgstr ""
msgid "IncidentSettings|Set up integrations with external tools to help better manage incidents."
msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
......@@ -15065,6 +15077,9 @@ msgstr ""
msgid "More information is available|here"
msgstr ""
msgid "More information."
msgstr ""
msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr ""
......@@ -15634,6 +15649,9 @@ msgstr ""
msgid "No template"
msgstr ""
msgid "No template selected"
msgstr ""
msgid "No test coverage"
msgstr ""
......
......@@ -5,10 +5,11 @@ module QA
module Project
module Settings
class Incidents < Page::Base
view 'app/views/projects/settings/operations/_incidents.html.haml' do
view 'app/assets/javascripts/incidents_settings/components/alerts_form.vue' do
element :create_issue_checkbox
element :incident_templates_dropdown
element :save_changes_button
element :incident_templates_item
end
def enable_issues_for_incidents
......@@ -16,8 +17,9 @@ module QA
end
def select_issue_template(template)
click_element(:incident_templates_dropdown)
within_element :incident_templates_dropdown do
find(:option, template).select_option
find_element(:incident_templates_item, text: template).click
end
end
......
......@@ -7,7 +7,7 @@ module QA
class Operations < Page::Base
include QA::Page::Settings::Common
view 'app/views/projects/settings/operations/_incidents.html.haml' do
view 'app/assets/javascripts/incidents_settings/components/incidents_settings_tabs.vue' do
element :incidents_settings_content
end
......
......@@ -46,9 +46,6 @@ module QA
incident_settings.select_issue_template('incident')
incident_settings.save_incident_settings
end
settings.expand_incidents do |incident_settings|
expect(incident_settings).to have_template('incident')
end
end
end
......
......@@ -45,15 +45,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
it 'updates form values' do
check(create_issue)
template_select = find_field('Issue template')
template_select.find(:xpath, 'option[2]').select_option
uncheck(send_email)
save_form
click_expand_incident_management_button
expect(find_field(create_issue)).to be_checked
expect(page).to have_select('Issue template', selected: 'bug')
expect(find_field(send_email)).not_to be_checked
end
......@@ -64,7 +61,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
end
def save_form
page.within "#edit_project_#{project.id}" do
page.within ".qa-incident-management-settings" do
click_on 'Save changes'
end
end
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Alert integration settings form default state should match the default snapshot 1`] = `
<div>
<p>
<gl-sprintf-stub
message="Action to take when receiving an alert. %{docsLink}"
/>
</p>
<form>
<gl-form-group-stub
class="gl-pl-0"
>
<gl-form-checkbox-stub
checked="true"
data-qa-selector="create_issue_checkbox"
>
<span>
Create an issue. Issues are created for each alert triggered.
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
<gl-form-group-stub
class="col-8 col-md-9 gl-px-6"
label-for="alert-integration-settings-issue-template"
label-size="sm"
>
<label
class="gl-display-inline-flex"
for="alert-integration-settings-issue-template"
>
Issue template (optional)
<gl-link-stub
href="/help/user/project/description_templates#creating-issue-templates"
target="_blank"
>
<gl-icon-stub
name="question"
size="12"
/>
</gl-link-stub>
</label>
<gl-new-dropdown-stub
block="true"
category="tertiary"
data-qa-selector="incident_templates_dropdown"
headertext=""
id="alert-integration-settings-issue-template"
size="medium"
text="selecte_tmpl"
variant="default"
>
<gl-new-dropdown-item-stub
avatarurl=""
data-qa-selector="incident_templates_item"
iconcolor=""
iconname=""
iconrightname=""
ischeckitem="true"
secondarytext=""
>
No template selected
</gl-new-dropdown-item-stub>
</gl-new-dropdown-stub>
</gl-form-group-stub>
<gl-form-group-stub
class="gl-pl-0 gl-mb-5"
>
<gl-form-checkbox-stub>
<span>
Send a separate email notification to Developers.
</span>
</gl-form-checkbox-stub>
</gl-form-group-stub>
<gl-button-stub
category="tertiary"
class="js-no-auto-disable"
data-qa-selector="save_changes_button"
icon=""
size="medium"
type="submit"
variant="success"
>
Save changes
</gl-button-stub>
</form>
</div>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`IncidentsSettingTabs should render the component 1`] = `
<section
class="settings no-animate qa-incident-management-settings"
data-qa-selector="incidents_settings_content"
id="incident-management-settings"
>
<div
class="settings-header"
>
<h3
class="h4"
>
Incidents
</h3>
<gl-button-stub
category="tertiary"
class="js-settings-toggle"
icon=""
size="medium"
variant="default"
>
Expand
</gl-button-stub>
<p>
Set up integrations with external tools to help better manage incidents.
</p>
</div>
<div
class="settings-content"
>
<gl-tabs-stub
theme="indigo"
>
<gl-tab-stub
title="Alert integration"
>
<alertssettingsform-stub
class="gl-pt-3"
data-testid="AlertsSettingsForm-tab"
/>
</gl-tab-stub>
<!---->
<!---->
</gl-tabs-stub>
</div>
</section>
`;
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AlertsSettingsForm from '~/incidents_settings/components/alerts_form.vue';
import { ERROR_MSG } from '~/incidents_settings/constants';
import createFlash from '~/flash';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility');
describe('Alert integration settings form', () => {
let wrapper;
const findForm = () => wrapper.find({ ref: 'settingsForm' });
beforeEach(() => {
wrapper = shallowMount(AlertsSettingsForm, {
provide: {
operationsSettingsEndpoint: 'operations/endpoint',
alertSettings: {
issueTemplateKey: 'selecte_tmpl',
createIssue: true,
sendEmail: false,
templates: [],
},
},
});
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
describe('default state', () => {
it('should match the default snapshot', () => {
expect(wrapper.element).toMatchSnapshot();
});
});
describe('form', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
it('should refresh the page on successful submit', () => {
mock.onPatch().reply(200);
findForm().trigger('submit');
return waitForPromises().then(() => {
expect(refreshCurrentPage).toHaveBeenCalled();
});
});
it('should display a flah message on unsuccessful submit', () => {
mock.onPatch().reply(400);
findForm().trigger('submit');
return waitForPromises().then(() => {
expect(createFlash).toHaveBeenCalledWith(expect.stringContaining(ERROR_MSG), 'alert');
});
});
});
});
import { shallowMount } from '@vue/test-utils';
import { GlTab } from '@gitlab/ui';
import IncidentsSettingTabs from '~/incidents_settings/components/incidents_settings_tabs.vue';
describe('IncidentsSettingTabs', () => {
let wrapper;
beforeEach(() => {
wrapper = shallowMount(IncidentsSettingTabs);
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
}
});
const findToggleButton = () => wrapper.find({ ref: 'toggleBtn' });
const findSectionHeader = () => wrapper.find({ ref: 'sectionHeader' });
const findIntegrationTabs = () => wrapper.findAll(GlTab);
it('renders header text', () => {
expect(findSectionHeader().text()).toBe('Incidents');
});
describe('expand/collapse button', () => {
it('renders as an expand button by default', () => {
expect(findToggleButton().text()).toBe('Expand');
});
});
it('should render the component', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('should render the tab for each active integration', () => {
const activeTabs = wrapper.vm.$options.tabs.filter(tab => tab.active);
expect(findIntegrationTabs().length).toBe(activeTabs.length);
activeTabs.forEach((tab, index) => {
expect(
findIntegrationTabs()
.at(index)
.attributes('title'),
).toBe(tab.title);
expect(
findIntegrationTabs()
.at(index)
.find(`[data-testid="${tab.component}-tab"]`)
.exists(),
).toBe(true);
});
});
});
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