Commit b07b1d99 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera

Merge branch...

Merge branch '273778-integrations-should-have-a-way-to-be-reset-unlinked-from-inheriting-projects' into 'master'

Add reset button and modal to mass integrations

See merge request gitlab-org/gitlab!47188
parents c221a8e4 026baf50
...@@ -8,14 +8,14 @@ export default { ...@@ -8,14 +8,14 @@ export default {
GlModal, GlModal,
}, },
computed: { computed: {
...mapGetters(['isSavingOrTesting']), ...mapGetters(['isDisabled']),
primaryProps() { primaryProps() {
return { return {
text: __('Save'), text: __('Save'),
attributes: [ attributes: [
{ variant: 'success' }, { variant: 'success' },
{ category: 'primary' }, { category: 'primary' },
{ disabled: this.isSavingOrTesting }, { disabled: this.isDisabled },
], ],
}; };
}, },
......
...@@ -12,6 +12,7 @@ import JiraIssuesFields from './jira_issues_fields.vue'; ...@@ -12,6 +12,7 @@ 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'; import ConfirmationModal from './confirmation_modal.vue';
import ResetConfirmationModal from './reset_confirmation_modal.vue';
export default { export default {
name: 'IntegrationForm', name: 'IntegrationForm',
...@@ -23,6 +24,7 @@ export default { ...@@ -23,6 +24,7 @@ export default {
TriggerFields, TriggerFields,
DynamicField, DynamicField,
ConfirmationModal, ConfirmationModal,
ResetConfirmationModal,
GlButton, GlButton,
}, },
directives: { directives: {
...@@ -30,8 +32,8 @@ export default { ...@@ -30,8 +32,8 @@ export default {
}, },
mixins: [glFeatureFlagsMixin()], mixins: [glFeatureFlagsMixin()],
computed: { computed: {
...mapGetters(['currentKey', 'propsSource', 'isSavingOrTesting']), ...mapGetters(['currentKey', 'propsSource', 'isDisabled']),
...mapState(['defaultState', 'override', 'isSaving', 'isTesting']), ...mapState(['defaultState', 'override', 'isSaving', 'isTesting', 'isResetting']),
isEditable() { isEditable() {
return this.propsSource.editable; return this.propsSource.editable;
}, },
...@@ -47,9 +49,12 @@ export default { ...@@ -47,9 +49,12 @@ export default {
showJiraIssuesFields() { showJiraIssuesFields() {
return this.isJira && this.glFeatures.jiraIssuesIntegration; return this.isJira && this.glFeatures.jiraIssuesIntegration;
}, },
showReset() {
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
},
}, },
methods: { methods: {
...mapActions(['setOverride', 'setIsSaving', 'setIsTesting']), ...mapActions(['setOverride', 'setIsSaving', 'setIsTesting', 'setIsResetting']),
onSaveClick() { onSaveClick() {
this.setIsSaving(true); this.setIsSaving(true);
eventHub.$emit('saveIntegration'); eventHub.$emit('saveIntegration');
...@@ -58,6 +63,7 @@ export default { ...@@ -58,6 +63,7 @@ export default {
this.setIsTesting(true); this.setIsTesting(true);
eventHub.$emit('testIntegration'); eventHub.$emit('testIntegration');
}, },
onResetClick() {},
}, },
}; };
</script> </script>
...@@ -100,7 +106,7 @@ export default { ...@@ -100,7 +106,7 @@ export default {
category="primary" category="primary"
variant="success" variant="success"
:loading="isSaving" :loading="isSaving"
:disabled="isSavingOrTesting" :disabled="isDisabled"
data-qa-selector="save_changes_button" data-qa-selector="save_changes_button"
> >
{{ __('Save changes') }} {{ __('Save changes') }}
...@@ -113,7 +119,7 @@ export default { ...@@ -113,7 +119,7 @@ export default {
variant="success" variant="success"
type="submit" type="submit"
:loading="isSaving" :loading="isSaving"
:disabled="isSavingOrTesting" :disabled="isDisabled"
data-qa-selector="save_changes_button" data-qa-selector="save_changes_button"
@click.prevent="onSaveClick" @click.prevent="onSaveClick"
> >
...@@ -123,13 +129,27 @@ export default { ...@@ -123,13 +129,27 @@ export default {
<gl-button <gl-button
v-if="propsSource.canTest" v-if="propsSource.canTest"
:loading="isTesting" :loading="isTesting"
:disabled="isSavingOrTesting" :disabled="isDisabled"
:href="propsSource.testPath" :href="propsSource.testPath"
@click.prevent="onTestClick" @click.prevent="onTestClick"
> >
{{ __('Test settings') }} {{ __('Test settings') }}
</gl-button> </gl-button>
<template v-if="showReset">
<gl-button
v-gl-modal.confirmResetIntegration
category="secondary"
variant="default"
:loading="isResetting"
:disabled="isDisabled"
data-testid="reset-button"
>
{{ __('Reset') }}
</gl-button>
<reset-confirmation-modal @reset="onResetClick" />
</template>
<gl-button class="btn-cancel" :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button> <gl-button class="btn-cancel" :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button>
</div> </div>
</div> </div>
......
<script>
import { mapGetters } from 'vuex';
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlModal,
},
computed: {
...mapGetters(['isDisabled']),
primaryProps() {
return {
text: __('Reset'),
attributes: [
{ variant: 'warning' },
{ category: 'primary' },
{ disabled: this.isDisabled },
],
};
},
cancelProps() {
return {
text: __('Cancel'),
};
},
},
methods: {
onReset() {
this.$emit('reset');
},
},
};
</script>
<template>
<gl-modal
modal-id="confirmResetIntegration"
size="sm"
:title="s__('Integrations|Reset integration?')"
:action-primary="primaryProps"
:action-cancel="cancelProps"
@primary="onReset"
>
<p>
{{
s__(
'Integrations|Resetting this integration will clear the settings and deactivate this integration.',
)
}}
</p>
<p>
{{ s__('Integrations|All projects inheriting these settings will also be reset.') }}
</p>
<p class="gl-mb-0">
{{ s__('Integrations|Projects using custom settings will not be affected.') }}
</p>
</gl-modal>
</template>
...@@ -26,6 +26,7 @@ function parseDatasetToProps(data) { ...@@ -26,6 +26,7 @@ function parseDatasetToProps(data) {
integrationLevel, integrationLevel,
cancelPath, cancelPath,
testPath, testPath,
resetPath,
...booleanAttributes ...booleanAttributes
} = data; } = data;
const { const {
...@@ -49,6 +50,7 @@ function parseDatasetToProps(data) { ...@@ -49,6 +50,7 @@ function parseDatasetToProps(data) {
editable, editable,
canTest, canTest,
testPath, testPath,
resetPath,
triggerFieldsProps: { triggerFieldsProps: {
initialTriggerCommit: commitEvents, initialTriggerCommit: commitEvents,
initialTriggerMergeRequest: mergeRequestEvents, initialTriggerMergeRequest: mergeRequestEvents,
......
...@@ -3,3 +3,5 @@ import * as types from './mutation_types'; ...@@ -3,3 +3,5 @@ import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override); export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving); export const setIsSaving = ({ commit }, isSaving) => commit(types.SET_IS_SAVING, isSaving);
export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting); export const setIsTesting = ({ commit }, isTesting) => commit(types.SET_IS_TESTING, isTesting);
export const setIsResetting = ({ commit }, isResetting) =>
commit(types.SET_IS_RESETTING, isResetting);
export const isInheriting = state => (state.defaultState === null ? false : !state.override); 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) => export const propsSource = (state, getters) =>
getters.isInheriting ? state.defaultState : state.customState; getters.isInheriting ? state.defaultState : state.customState;
......
export const SET_OVERRIDE = 'SET_OVERRIDE'; export const SET_OVERRIDE = 'SET_OVERRIDE';
export const SET_IS_SAVING = 'SET_IS_SAVING'; export const SET_IS_SAVING = 'SET_IS_SAVING';
export const SET_IS_TESTING = 'SET_IS_TESTING'; export const SET_IS_TESTING = 'SET_IS_TESTING';
export const SET_IS_RESETTING = 'SET_IS_RESETTING';
...@@ -10,4 +10,7 @@ export default { ...@@ -10,4 +10,7 @@ export default {
[types.SET_IS_TESTING](state, isTesting) { [types.SET_IS_TESTING](state, isTesting) {
state.isTesting = isTesting; state.isTesting = isTesting;
}, },
[types.SET_IS_RESETTING](state, isResetting) {
state.isResetting = isResetting;
},
}; };
...@@ -7,5 +7,6 @@ export default ({ defaultState = null, customState = {} } = {}) => { ...@@ -7,5 +7,6 @@ export default ({ defaultState = null, customState = {} } = {}) => {
customState, customState,
isSaving: false, isSaving: false,
isTesting: false, isTesting: false,
isResetting: false,
}; };
}; };
...@@ -93,7 +93,8 @@ module ServicesHelper ...@@ -93,7 +93,8 @@ module ServicesHelper
editable: integration.editable?.to_s, editable: integration.editable?.to_s,
cancel_path: scoped_integrations_path, cancel_path: scoped_integrations_path,
can_test: integration.can_test?.to_s, can_test: integration.can_test?.to_s,
test_path: scoped_test_integration_path(integration) test_path: scoped_test_integration_path(integration),
reset_path: ''
} }
end end
......
...@@ -14621,6 +14621,9 @@ msgstr "" ...@@ -14621,6 +14621,9 @@ msgstr ""
msgid "Integrations|All details" msgid "Integrations|All details"
msgstr "" msgstr ""
msgid "Integrations|All projects inheriting these settings will also be reset."
msgstr ""
msgid "Integrations|Comment detail:" msgid "Integrations|Comment detail:"
msgstr "" msgstr ""
...@@ -14654,9 +14657,18 @@ msgstr "" ...@@ -14654,9 +14657,18 @@ msgstr ""
msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira." msgid "Integrations|Issues created in Jira are shown here once you have created the issues in project setup in Jira."
msgstr "" 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." msgid "Integrations|Projects using custom settings will not be impacted unless the project owner chooses to use parent level defaults."
msgstr "" 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" msgid "Integrations|Return to GitLab for Jira"
msgstr "" msgstr ""
...@@ -23062,6 +23074,9 @@ msgstr "" ...@@ -23062,6 +23074,9 @@ msgstr ""
msgid "Resend it" msgid "Resend it"
msgstr "" msgstr ""
msgid "Reset"
msgstr ""
msgid "Reset authorization key" msgid "Reset authorization key"
msgstr "" msgstr ""
......
...@@ -5,6 +5,7 @@ import IntegrationForm from '~/integrations/edit/components/integration_form.vue ...@@ -5,6 +5,7 @@ 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 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 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';
...@@ -44,6 +45,8 @@ describe('IntegrationForm', () => { ...@@ -44,6 +45,8 @@ 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 findConfirmationModal = () => wrapper.find(ConfirmationModal);
const findResetConfirmationModal = () => wrapper.find(ResetConfirmationModal);
const findResetButton = () => wrapper.find('[data-testid="reset-button"]');
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);
...@@ -75,6 +78,29 @@ describe('IntegrationForm', () => { ...@@ -75,6 +78,29 @@ describe('IntegrationForm', () => {
expect(findConfirmationModal().exists()).toBe(true); 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', () => { describe('integrationLevel is group', () => {
...@@ -85,6 +111,29 @@ describe('IntegrationForm', () => { ...@@ -85,6 +111,29 @@ describe('IntegrationForm', () => {
expect(findConfirmationModal().exists()).toBe(true); 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', () => { describe('integrationLevel is project', () => {
...@@ -95,6 +144,16 @@ describe('IntegrationForm', () => { ...@@ -95,6 +144,16 @@ describe('IntegrationForm', () => {
expect(findConfirmationModal().exists()).toBe(false); 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"', () => { describe('type is "slack"', () => {
......
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import createState from '~/integrations/edit/store/state'; 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'; import * as types from '~/integrations/edit/store/mutation_types';
describe('Integration form store actions', () => { describe('Integration form store actions', () => {
...@@ -15,4 +20,24 @@ 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 }]); 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 },
]);
});
});
}); });
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 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'; import { mockIntegrationProps } from '../mock_data';
describe('Integration form store getters', () => { describe('Integration form store getters', () => {
...@@ -45,6 +52,29 @@ 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', () => { describe('propsSource', () => {
beforeEach(() => { beforeEach(() => {
state.defaultState = defaultState; state.defaultState = defaultState;
......
...@@ -16,4 +16,28 @@ describe('Integration form store mutations', () => { ...@@ -16,4 +16,28 @@ describe('Integration form store mutations', () => {
expect(state.override).toBe(true); 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);
});
});
}); });
...@@ -7,6 +7,7 @@ describe('Integration form state factory', () => { ...@@ -7,6 +7,7 @@ describe('Integration form state factory', () => {
customState: {}, customState: {},
isSaving: false, isSaving: false,
isTesting: false, isTesting: false,
isResetting: false,
override: false, override: 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