diff --git a/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue b/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue index d4489bbf693a3252a5247f50b6132f9643425e41..93ea1f4f636055f8b30161c7e3e4dedf72153aec 100644 --- a/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue +++ b/app/assets/javascripts/integrations/edit/components/confirmation_modal.vue @@ -8,14 +8,14 @@ export default { GlModal, }, computed: { - ...mapGetters(['isSavingOrTesting']), + ...mapGetters(['isDisabled']), primaryProps() { return { text: __('Save'), attributes: [ { variant: 'success' }, { category: 'primary' }, - { disabled: this.isSavingOrTesting }, + { disabled: this.isDisabled }, ], }; }, diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue index e42a8b7de7c73af13fbc1144c557c9c4bbbd01a3..bbfa865905aec1fec6b949dd5be278424c112bff 100644 --- a/app/assets/javascripts/integrations/edit/components/integration_form.vue +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -12,6 +12,7 @@ import JiraIssuesFields from './jira_issues_fields.vue'; import TriggerFields from './trigger_fields.vue'; import DynamicField from './dynamic_field.vue'; import ConfirmationModal from './confirmation_modal.vue'; +import ResetConfirmationModal from './reset_confirmation_modal.vue'; export default { name: 'IntegrationForm', @@ -23,6 +24,7 @@ export default { TriggerFields, DynamicField, ConfirmationModal, + ResetConfirmationModal, GlButton, }, directives: { @@ -30,8 +32,8 @@ export default { }, mixins: [glFeatureFlagsMixin()], computed: { - ...mapGetters(['currentKey', 'propsSource', 'isSavingOrTesting']), - ...mapState(['defaultState', 'override', 'isSaving', 'isTesting']), + ...mapGetters(['currentKey', 'propsSource', 'isDisabled']), + ...mapState(['defaultState', 'override', 'isSaving', 'isTesting', 'isResetting']), isEditable() { return this.propsSource.editable; }, @@ -47,9 +49,12 @@ export default { showJiraIssuesFields() { return this.isJira && this.glFeatures.jiraIssuesIntegration; }, + showReset() { + return this.isInstanceOrGroupLevel && this.propsSource.resetPath; + }, }, methods: { - ...mapActions(['setOverride', 'setIsSaving', 'setIsTesting']), + ...mapActions(['setOverride', 'setIsSaving', 'setIsTesting', 'setIsResetting']), onSaveClick() { this.setIsSaving(true); eventHub.$emit('saveIntegration'); @@ -58,6 +63,7 @@ export default { this.setIsTesting(true); eventHub.$emit('testIntegration'); }, + onResetClick() {}, }, }; @@ -100,7 +106,7 @@ export default { category="primary" variant="success" :loading="isSaving" - :disabled="isSavingOrTesting" + :disabled="isDisabled" data-qa-selector="save_changes_button" > {{ __('Save changes') }} @@ -113,7 +119,7 @@ export default { variant="success" type="submit" :loading="isSaving" - :disabled="isSavingOrTesting" + :disabled="isDisabled" data-qa-selector="save_changes_button" @click.prevent="onSaveClick" > @@ -123,13 +129,27 @@ export default { {{ __('Test settings') }} + + {{ __('Cancel') }} diff --git a/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue b/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue new file mode 100644 index 0000000000000000000000000000000000000000..d850391056697dc66fa4c68d0931f213b7ca0b05 --- /dev/null +++ b/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue @@ -0,0 +1,61 @@ + + + diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index 248ee62d43a2f788f4e2ed41d631ce64e60f0186..95a53f1beab1633458d627c3d707c40d311cabc1 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -26,6 +26,7 @@ function parseDatasetToProps(data) { integrationLevel, cancelPath, testPath, + resetPath, ...booleanAttributes } = data; const { @@ -49,6 +50,7 @@ function parseDatasetToProps(data) { editable, canTest, testPath, + resetPath, triggerFieldsProps: { initialTriggerCommit: commitEvents, initialTriggerMergeRequest: mergeRequestEvents, diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js index 199c9074ead1290939339b3e407793223870a7b3..097304be242886a66f37c3f8853074c3cdc343ea 100644 --- a/app/assets/javascripts/integrations/edit/store/actions.js +++ b/app/assets/javascripts/integrations/edit/store/actions.js @@ -3,3 +3,5 @@ import * as types from './mutation_types'; export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override); export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving); export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting); +export const setIsResetting = ({ commit }, isResetting) => + commit(types.SET_IS_RESETTING, isResetting); diff --git a/app/assets/javascripts/integrations/edit/store/getters.js b/app/assets/javascripts/integrations/edit/store/getters.js index 4ee5f11855cca0adaccbd37be5c2a787117ebd16..310d970c73ebaf1dcc5f47eae392ca2c52ba5c64 100644 --- a/app/assets/javascripts/integrations/edit/store/getters.js +++ b/app/assets/javascripts/integrations/edit/store/getters.js @@ -1,6 +1,6 @@ export const isInheriting = state => (state.defaultState === null ? false : !state.override); -export const isSavingOrTesting = state => state.isSaving || state.isTesting; +export const isDisabled = state => state.isSaving || state.isTesting || state.isResetting; export const propsSource = (state, getters) => getters.isInheriting ? state.defaultState : state.customState; diff --git a/app/assets/javascripts/integrations/edit/store/mutation_types.js b/app/assets/javascripts/integrations/edit/store/mutation_types.js index 0dae8ea079ee2cd55ffd097f436d3aa9bc97eabe..2a84408f6585344aaaa067044eca26c6a4808f45 100644 --- a/app/assets/javascripts/integrations/edit/store/mutation_types.js +++ b/app/assets/javascripts/integrations/edit/store/mutation_types.js @@ -1,3 +1,4 @@ export const SET_OVERRIDE = 'SET_OVERRIDE'; export const SET_IS_SAVING = 'SET_IS_SAVING'; export const SET_IS_TESTING = 'SET_IS_TESTING'; +export const SET_IS_RESETTING = 'SET_IS_RESETTING'; diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js index 8ac3c476f9e545cfafbd769fba8de3980f66dfaa..07e3e25ccf0149c622f42373f203d962816008fa 100644 --- a/app/assets/javascripts/integrations/edit/store/mutations.js +++ b/app/assets/javascripts/integrations/edit/store/mutations.js @@ -10,4 +10,7 @@ export default { [types.SET_IS_TESTING](state, isTesting) { state.isTesting = isTesting; }, + [types.SET_IS_RESETTING](state, isResetting) { + state.isResetting = isResetting; + }, }; diff --git a/app/assets/javascripts/integrations/edit/store/state.js b/app/assets/javascripts/integrations/edit/store/state.js index a9ecee6c5396383170bb732c2edd86d5071abc26..aae3db1583fbaecc8536c97df5685a0bb5759937 100644 --- a/app/assets/javascripts/integrations/edit/store/state.js +++ b/app/assets/javascripts/integrations/edit/store/state.js @@ -7,5 +7,6 @@ export default ({ defaultState = null, customState = {} } = {}) => { customState, isSaving: false, isTesting: false, + isResetting: false, }; }; diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 1ca2c9a5cd3a1cb5408bc29d017abe44b5f5f2d1..9eba9cf89fd22ddfa5b16ddaeeb2cda5d41a5aed 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -93,7 +93,8 @@ def integration_form_data(integration) editable: integration.editable?.to_s, cancel_path: scoped_integrations_path, can_test: integration.can_test?.to_s, - test_path: scoped_test_integration_path(integration) + test_path: scoped_test_integration_path(integration), + reset_path: '' } end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 60a987778d5837a6393f43944faa2fffd515c40d..697e92b7c689ab1df87ca88937fe9e414dafa189 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14569,6 +14569,9 @@ msgstr "" msgid "Integrations|All details" msgstr "" +msgid "Integrations|All projects inheriting these settings will also be reset." +msgstr "" + msgid "Integrations|Comment detail:" msgstr "" @@ -14602,9 +14605,18 @@ msgstr "" msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira." msgstr "" +msgid "Integrations|Projects using custom settings will not be affected." +msgstr "" + msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use parent level defaults." msgstr "" +msgid "Integrations|Reset integration?" +msgstr "" + +msgid "Integrations|Resetting this integration will clear the settings and deactivate this integration." +msgstr "" + msgid "Integrations|Return to GitLab for Jira" msgstr "" @@ -22977,6 +22989,9 @@ msgstr "" msgid "Resend it" msgstr "" +msgid "Reset" +msgstr "" + msgid "Reset authorization key" msgstr "" diff --git a/spec/frontend/integrations/edit/components/integration_form_spec.js b/spec/frontend/integrations/edit/components/integration_form_spec.js index 4dd38c959f8ce5c02b67b75bf4382196375eb87d..97e77ac87abcb50adb01d47c01de2f6b25ca49f4 100644 --- a/spec/frontend/integrations/edit/components/integration_form_spec.js +++ b/spec/frontend/integrations/edit/components/integration_form_spec.js @@ -5,6 +5,7 @@ import IntegrationForm from '~/integrations/edit/components/integration_form.vue import OverrideDropdown from '~/integrations/edit/components/override_dropdown.vue'; import ActiveCheckbox from '~/integrations/edit/components/active_checkbox.vue'; import ConfirmationModal from '~/integrations/edit/components/confirmation_modal.vue'; +import ResetConfirmationModal from '~/integrations/edit/components/reset_confirmation_modal.vue'; import JiraTriggerFields from '~/integrations/edit/components/jira_trigger_fields.vue'; import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue'; import TriggerFields from '~/integrations/edit/components/trigger_fields.vue'; @@ -44,6 +45,8 @@ describe('IntegrationForm', () => { const findOverrideDropdown = () => wrapper.find(OverrideDropdown); const findActiveCheckbox = () => wrapper.find(ActiveCheckbox); const findConfirmationModal = () => wrapper.find(ConfirmationModal); + const findResetConfirmationModal = () => wrapper.find(ResetConfirmationModal); + const findResetButton = () => wrapper.find('[data-testid="reset-button"]'); const findJiraTriggerFields = () => wrapper.find(JiraTriggerFields); const findJiraIssuesFields = () => wrapper.find(JiraIssuesFields); const findTriggerFields = () => wrapper.find(TriggerFields); @@ -75,6 +78,29 @@ describe('IntegrationForm', () => { expect(findConfirmationModal().exists()).toBe(true); }); + + describe('resetPath is empty', () => { + it('does not render ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.INSTANCE, + }); + + expect(findResetButton().exists()).toBe(false); + expect(findResetConfirmationModal().exists()).toBe(false); + }); + }); + + describe('resetPath is present', () => { + it('renders ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.INSTANCE, + resetPath: 'resetPath', + }); + + expect(findResetButton().exists()).toBe(true); + expect(findResetConfirmationModal().exists()).toBe(true); + }); + }); }); describe('integrationLevel is group', () => { @@ -85,6 +111,29 @@ describe('IntegrationForm', () => { expect(findConfirmationModal().exists()).toBe(true); }); + + describe('resetPath is empty', () => { + it('does not render ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.GROUP, + }); + + expect(findResetButton().exists()).toBe(false); + expect(findResetConfirmationModal().exists()).toBe(false); + }); + }); + + describe('resetPath is present', () => { + it('renders ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: integrationLevels.GROUP, + resetPath: 'resetPath', + }); + + expect(findResetButton().exists()).toBe(true); + expect(findResetConfirmationModal().exists()).toBe(true); + }); + }); }); describe('integrationLevel is project', () => { @@ -95,6 +144,16 @@ describe('IntegrationForm', () => { expect(findConfirmationModal().exists()).toBe(false); }); + + it('does not render ResetConfirmationModal and button', () => { + createComponent({ + integrationLevel: 'project', + resetPath: 'resetPath', + }); + + expect(findResetButton().exists()).toBe(false); + expect(findResetConfirmationModal().exists()).toBe(false); + }); }); describe('type is "slack"', () => { diff --git a/spec/frontend/integrations/edit/store/actions_spec.js b/spec/frontend/integrations/edit/store/actions_spec.js index 5356c0a411b0ca1939e1ae7d69c8a80e0e30afbe..5b5c8d6f76ee4460a2db0ebbff9fa79998b9c99d 100644 --- a/spec/frontend/integrations/edit/store/actions_spec.js +++ b/spec/frontend/integrations/edit/store/actions_spec.js @@ -1,6 +1,11 @@ import testAction from 'helpers/vuex_action_helper'; import createState from '~/integrations/edit/store/state'; -import { setOverride } from '~/integrations/edit/store/actions'; +import { + setOverride, + setIsSaving, + setIsTesting, + setIsResetting, +} from '~/integrations/edit/store/actions'; import * as types from '~/integrations/edit/store/mutation_types'; describe('Integration form store actions', () => { @@ -15,4 +20,24 @@ describe('Integration form store actions', () => { return testAction(setOverride, true, state, [{ type: types.SET_OVERRIDE, payload: true }]); }); }); + + describe('setIsSaving', () => { + it('should commit isSaving mutation', () => { + return testAction(setIsSaving, true, state, [{ type: types.SET_IS_SAVING, payload: true }]); + }); + }); + + describe('setIsTesting', () => { + it('should commit isTesting mutation', () => { + return testAction(setIsTesting, true, state, [{ type: types.SET_IS_TESTING, payload: true }]); + }); + }); + + describe('setIsResetting', () => { + it('should commit isResetting mutation', () => { + return testAction(setIsResetting, true, state, [ + { type: types.SET_IS_RESETTING, payload: true }, + ]); + }); + }); }); diff --git a/spec/frontend/integrations/edit/store/getters_spec.js b/spec/frontend/integrations/edit/store/getters_spec.js index 3353e0c84cc19c48019539abc84dd3fcaea1145e..7d4532a10598d7e6322eb6ba15c95038775265fb 100644 --- a/spec/frontend/integrations/edit/store/getters_spec.js +++ b/spec/frontend/integrations/edit/store/getters_spec.js @@ -1,5 +1,12 @@ -import { currentKey, isInheriting, propsSource } from '~/integrations/edit/store/getters'; +import { + currentKey, + isInheriting, + isDisabled, + propsSource, +} from '~/integrations/edit/store/getters'; import createState from '~/integrations/edit/store/state'; +import mutations from '~/integrations/edit/store/mutations'; +import * as types from '~/integrations/edit/store/mutation_types'; import { mockIntegrationProps } from '../mock_data'; describe('Integration form store getters', () => { @@ -45,6 +52,29 @@ describe('Integration form store getters', () => { }); }); + describe('isDisabled', () => { + it.each` + isSaving | isTesting | isResetting | expected + ${false} | ${false} | ${false} | ${false} + ${true} | ${false} | ${false} | ${true} + ${false} | ${true} | ${false} | ${true} + ${false} | ${false} | ${true} | ${true} + ${false} | ${true} | ${true} | ${true} + ${true} | ${false} | ${true} | ${true} + ${true} | ${true} | ${false} | ${true} + ${true} | ${true} | ${true} | ${true} + `( + 'when isSaving = $isSaving, isTesting = $isTesting, isResetting = $isResetting then isDisabled = $expected', + ({ isSaving, isTesting, isResetting, expected }) => { + mutations[types.SET_IS_SAVING](state, isSaving); + mutations[types.SET_IS_TESTING](state, isTesting); + mutations[types.SET_IS_RESETTING](state, isResetting); + + expect(isDisabled(state)).toBe(expected); + }, + ); + }); + describe('propsSource', () => { beforeEach(() => { state.defaultState = defaultState; diff --git a/spec/frontend/integrations/edit/store/mutations_spec.js b/spec/frontend/integrations/edit/store/mutations_spec.js index 4b733726d44a5df5a759d3da143412b70008b5d8..4707b4b37144f256a23c298c295ab695e05e3301 100644 --- a/spec/frontend/integrations/edit/store/mutations_spec.js +++ b/spec/frontend/integrations/edit/store/mutations_spec.js @@ -16,4 +16,28 @@ describe('Integration form store mutations', () => { expect(state.override).toBe(true); }); }); + + describe(`${types.SET_IS_SAVING}`, () => { + it('sets isSaving', () => { + mutations[types.SET_IS_SAVING](state, true); + + expect(state.isSaving).toBe(true); + }); + }); + + describe(`${types.SET_IS_TESTING}`, () => { + it('sets isTesting', () => { + mutations[types.SET_IS_TESTING](state, true); + + expect(state.isTesting).toBe(true); + }); + }); + + describe(`${types.SET_IS_RESETTING}`, () => { + it('sets isResetting', () => { + mutations[types.SET_IS_RESETTING](state, true); + + expect(state.isResetting).toBe(true); + }); + }); }); diff --git a/spec/frontend/integrations/edit/store/state_spec.js b/spec/frontend/integrations/edit/store/state_spec.js index fc193850a94cd17db11ca81dabffa18bcbc13de9..4d0f4a1da717d4288182a1a843f344072eb3347c 100644 --- a/spec/frontend/integrations/edit/store/state_spec.js +++ b/spec/frontend/integrations/edit/store/state_spec.js @@ -7,6 +7,7 @@ describe('Integration form state factory', () => { customState: {}, isSaving: false, isTesting: false, + isResetting: false, override: false, }); });