Commit 6c6ac293 authored by Olena Horal-Koretska's avatar Olena Horal-Koretska

Move incidents setting to Vue

parent 4da75867
<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'; ...@@ -3,8 +3,10 @@ import mountAlertsSettings from '~/alerts_settings';
import mountOperationSettings from '~/operation_settings'; import mountOperationSettings from '~/operation_settings';
import mountGrafanaIntegration from '~/grafana_integration'; import mountGrafanaIntegration from '~/grafana_integration';
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import initIncidentsSettings from '~/incidents_settings';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initIncidentsSettings();
mountErrorTrackingForm(); mountErrorTrackingForm();
mountOperationSettings(); mountOperationSettings();
mountGrafanaIntegration(); mountGrafanaIntegration();
......
- templates = []
- setting = project_incident_management_setting - 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' } } .js-incidents-settings{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
.settings-header templates: templates.to_json,
%h3{ :class => "h4" }= _('Incidents') create_issue: setting.create_issue.to_s,
%button.btn.js-settings-toggle{ type: 'button' } issue_template_key: setting.issue_template_key.to_s,
= _('Expand') send_email: setting.send_email.to_s } }
%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' }
---
title: Move alert integrations setting to Vue
merge_request: 36110
author:
type: changed
...@@ -1289,7 +1289,7 @@ msgstr "" ...@@ -1289,7 +1289,7 @@ msgstr ""
msgid "Account: %{account}" msgid "Account: %{account}"
msgstr "" msgstr ""
msgid "Action to take when receiving an alert." msgid "Action to take when receiving an alert. %{docsLink}"
msgstr "" msgstr ""
msgid "Actions" msgid "Actions"
...@@ -12380,7 +12380,19 @@ msgstr "" ...@@ -12380,7 +12380,19 @@ msgstr ""
msgid "Incident Management Limits" msgid "Incident Management Limits"
msgstr "" 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 "" msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept." msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
...@@ -15038,6 +15050,9 @@ msgstr "" ...@@ -15038,6 +15050,9 @@ msgstr ""
msgid "More information is available|here" msgid "More information is available|here"
msgstr "" msgstr ""
msgid "More information."
msgstr ""
msgid "More than %{number_commits_distance} commits different with %{default_branch}" msgid "More than %{number_commits_distance} commits different with %{default_branch}"
msgstr "" msgstr ""
...@@ -15607,6 +15622,9 @@ msgstr "" ...@@ -15607,6 +15622,9 @@ msgstr ""
msgid "No template" msgid "No template"
msgstr "" msgstr ""
msgid "No template selected"
msgstr ""
msgid "No test coverage" msgid "No test coverage"
msgstr "" msgstr ""
......
...@@ -5,10 +5,11 @@ module QA ...@@ -5,10 +5,11 @@ module QA
module Project module Project
module Settings module Settings
class Incidents < Page::Base 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 :create_issue_checkbox
element :incident_templates_dropdown element :incident_templates_dropdown
element :save_changes_button element :save_changes_button
element :incident_templates_item
end end
def enable_issues_for_incidents def enable_issues_for_incidents
...@@ -16,8 +17,9 @@ module QA ...@@ -16,8 +17,9 @@ module QA
end end
def select_issue_template(template) def select_issue_template(template)
click_element(:incident_templates_dropdown)
within_element :incident_templates_dropdown do within_element :incident_templates_dropdown do
find(:option, template).select_option find_element(:incident_templates_item, text: template).click
end end
end end
......
...@@ -7,7 +7,7 @@ module QA ...@@ -7,7 +7,7 @@ module QA
class Operations < Page::Base class Operations < Page::Base
include QA::Page::Settings::Common 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 element :incidents_settings_content
end end
......
...@@ -46,9 +46,6 @@ module QA ...@@ -46,9 +46,6 @@ module QA
incident_settings.select_issue_template('incident') incident_settings.select_issue_template('incident')
incident_settings.save_incident_settings incident_settings.save_incident_settings
end end
settings.expand_incidents do |incident_settings|
expect(incident_settings).to have_template('incident')
end
end end
end end
......
...@@ -45,15 +45,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do ...@@ -45,15 +45,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
it 'updates form values' do it 'updates form values' do
check(create_issue) check(create_issue)
template_select = find_field('Issue template')
template_select.find(:xpath, 'option[2]').select_option
uncheck(send_email) uncheck(send_email)
save_form save_form
click_expand_incident_management_button click_expand_incident_management_button
expect(find_field(create_issue)).to be_checked 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 expect(find_field(send_email)).not_to be_checked
end end
...@@ -64,7 +61,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do ...@@ -64,7 +61,7 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
end end
def save_form def save_form
page.within "#edit_project_#{project.id}" do page.within ".qa-incident-management-settings" do
click_on 'Save changes' click_on 'Save changes'
end end
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