Commit e7adf4ab authored by Justin Ho Tuan Duong's avatar Justin Ho Tuan Duong Committed by Nicolò Maria Mezzopera

Add confirmation modal on integration form

- For instance-level integration
- Replaces "Save changes" button which saved
immediately (without confirmation)
parent c215b24d
<script>
import { mapGetters } from 'vuex';
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlModal,
},
computed: {
...mapGetters(['isSavingOrTesting']),
primaryProps() {
return {
text: __('Save'),
attributes: [
{ variant: 'success' },
{ category: 'primary' },
{ disabled: this.isSavingOrTesting },
],
};
},
cancelProps() {
return {
text: __('Cancel'),
};
},
},
methods: {
onSubmit() {
this.$emit('submit');
},
},
};
</script>
<template>
<gl-modal
modal-id="confirmSaveIntegration"
size="sm"
:title="s__('Integrations|Save settings?')"
:action-primary="primaryProps"
:action-cancel="cancelProps"
@primary="onSubmit"
>
<p>
{{
s__(
'Integrations|Saving will update the default settings for all projects that are not using custom settings.',
)
}}
</p>
<p class="gl-mb-0">
{{
s__(
'Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults.',
)
}}
</p>
</gl-modal>
</template>
<script> <script>
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { GlButton } from '@gitlab/ui'; import { GlButton, GlModalDirective } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import { integrationLevels } from '../constants';
import OverrideDropdown from './override_dropdown.vue'; import OverrideDropdown from './override_dropdown.vue';
import ActiveCheckbox from './active_checkbox.vue'; import ActiveCheckbox from './active_checkbox.vue';
...@@ -10,6 +11,7 @@ import JiraTriggerFields from './jira_trigger_fields.vue'; ...@@ -10,6 +11,7 @@ import JiraTriggerFields from './jira_trigger_fields.vue';
import JiraIssuesFields from './jira_issues_fields.vue'; import JiraIssuesFields from './jira_issues_fields.vue';
import TriggerFields from './trigger_fields.vue'; import TriggerFields from './trigger_fields.vue';
import DynamicField from './dynamic_field.vue'; import DynamicField from './dynamic_field.vue';
import ConfirmationModal from './confirmation_modal.vue';
export default { export default {
name: 'IntegrationForm', name: 'IntegrationForm',
...@@ -20,8 +22,12 @@ export default { ...@@ -20,8 +22,12 @@ export default {
JiraIssuesFields, JiraIssuesFields,
TriggerFields, TriggerFields,
DynamicField, DynamicField,
ConfirmationModal,
GlButton, GlButton,
}, },
directives: {
'gl-modal': GlModalDirective,
},
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
computed: { computed: {
...mapGetters(['currentKey', 'propsSource', 'isSavingOrTesting']), ...mapGetters(['currentKey', 'propsSource', 'isSavingOrTesting']),
...@@ -32,6 +38,9 @@ export default { ...@@ -32,6 +38,9 @@ export default {
isJira() { isJira() {
return this.propsSource.type === 'jira'; return this.propsSource.type === 'jira';
}, },
isInstanceLevel() {
return this.propsSource.integrationLevel === integrationLevels.INSTANCE;
},
showJiraIssuesFields() { showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration; return this.isJira && this.glFeatures.jiraIssuesIntegration;
}, },
...@@ -82,7 +91,21 @@ export default { ...@@ -82,7 +91,21 @@ export default {
v-bind="propsSource.jiraIssuesProps" v-bind="propsSource.jiraIssuesProps"
/> />
<div v-if="isEditable" class="footer-block row-content-block"> <div v-if="isEditable" class="footer-block row-content-block">
<template v-if="isInstanceLevel">
<gl-button
v-gl-modal.confirmSaveIntegration
category="primary"
variant="success"
:loading="isSaving"
:disabled="isSavingOrTesting"
data-qa-selector="save_changes_button"
>
{{ __('Save changes') }}
</gl-button>
<confirmation-modal @submit="onSaveClick" />
</template>
<gl-button <gl-button
v-else
category="primary" category="primary"
variant="success" variant="success"
type="submit" type="submit"
...@@ -93,6 +116,7 @@ export default { ...@@ -93,6 +116,7 @@ export default {
> >
{{ __('Save changes') }} {{ __('Save changes') }}
</gl-button> </gl-button>
<gl-button <gl-button
v-if="propsSource.canTest" v-if="propsSource.canTest"
:loading="isTesting" :loading="isTesting"
......
---
title: Add confirmation modal on instance-level integration form
merge_request: 42840
author:
type: changed
...@@ -13773,6 +13773,15 @@ msgstr "" ...@@ -13773,6 +13773,15 @@ msgstr ""
msgid "Integrations|Includes commit title and branch" msgid "Integrations|Includes commit title and branch"
msgstr "" msgstr ""
msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults."
msgstr ""
msgid "Integrations|Save settings?"
msgstr ""
msgid "Integrations|Saving will update the default settings for all projects that are not using custom settings."
msgstr ""
msgid "Integrations|Standard" msgid "Integrations|Standard"
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import { GlModal } from '@gitlab/ui';
import { createStore } from '~/integrations/edit/store';
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
describe('ConfirmationModal', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(ConfirmationModal, {
store: createStore(),
});
};
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
const findGlModal = () => wrapper.find(GlModal);
describe('template', () => {
beforeEach(() => {
createComponent();
});
it('renders GlModal with correct copy', () => {
expect(findGlModal().exists()).toBe(true);
expect(findGlModal().attributes('title')).toBe('Save settings?');
expect(findGlModal().text()).toContain(
'Saving will update the default settings for all projects that are not using custom settings.',
);
expect(findGlModal().text()).toContain(
'Projects using custom settings will not be impacted unless the project owner chooses to use instance-level defaults.',
);
});
it('emits `submit` event when `primary` event is emitted on GlModal', async () => {
expect(wrapper.emitted().submit).toBeUndefined();
findGlModal().vm.$emit('primary');
await wrapper.vm.$nextTick();
expect(wrapper.emitted().submit).toHaveLength(1);
});
});
});
...@@ -4,6 +4,7 @@ import { createStore } from '~/integrations/edit/store'; ...@@ -4,6 +4,7 @@ import { createStore } from '~/integrations/edit/store';
import IntegrationForm from '~/integrations/edit/components/integration_form.vue'; import IntegrationForm from '~/integrations/edit/components/integration_form.vue';
import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue'; import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue';
import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue';
import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue';
import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue'; import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
...@@ -22,6 +23,7 @@ describe('IntegrationForm', () => { ...@@ -22,6 +23,7 @@ describe('IntegrationForm', () => {
stubs: { stubs: {
OverrideDropdown, OverrideDropdown,
ActiveCheckbox, ActiveCheckbox,
ConfirmationModal,
JiraTriggerFields, JiraTriggerFields,
TriggerFields, TriggerFields,
}, },
...@@ -40,6 +42,7 @@ describe('IntegrationForm', () => { ...@@ -40,6 +42,7 @@ describe('IntegrationForm', () => {
const findOverrideDropdown = () => wrapper.find(OverrideDropdown); const findOverrideDropdown = () => wrapper.find(OverrideDropdown);
const findActiveCheckbox = () => wrapper.find(ActiveCheckbox); const findActiveCheckbox = () => wrapper.find(ActiveCheckbox);
const findConfirmationModal = () => wrapper.find(ConfirmationModal);
const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields); const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields);
const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields); const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields);
const findTriggerFields = () => wrapper.find(TriggerFields); const findTriggerFields = () => wrapper.find(TriggerFields);
...@@ -63,6 +66,26 @@ describe('IntegrationForm', () => { ...@@ -63,6 +66,26 @@ describe('IntegrationForm', () => {
}); });
}); });
describe('integrationLevel is instance', () => {
it('renders ConfirmationModal', () => {
createComponent({
integrationLevel: 'instance',
});
expect(findConfirmationModal().exists()).toBe(true);
});
});
describe('integrationLevel is not instance', () => {
it('does not render ConfirmationModal', () => {
createComponent({
integrationLevel: 'project',
});
expect(findConfirmationModal().exists()).toBe(false);
});
});
describe('type is "slack"', () => { describe('type is "slack"', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ type: 'slack' }); createComponent({ type: 'slack' });
......
...@@ -2,6 +2,7 @@ export const mockIntegrationProps = { ...@@ -2,6 +2,7 @@ export const mockIntegrationProps = {
id: 25, id: 25,
initialActivated: true, initialActivated: true,
showActive: true, showActive: true,
editable: true,
triggerFieldsProps: { triggerFieldsProps: {
initialTriggerCommit: false, initialTriggerCommit: false,
initialTriggerMergeRequest: false, initialTriggerMergeRequest: false,
......
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