Commit 56300cc5 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '348382-integration-form-remove-event-hub' into 'master'

Remove event hub from integrations app

See merge request gitlab-org/gitlab!78037
parents fa81c539 2a036b35
import { s__, __ } from '~/locale';
export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm';
export const integrationLevels = {
GROUP: 'group',
INSTANCE: 'instance',
......
......@@ -9,8 +9,6 @@ import {
} from '@gitlab/ui';
import { capitalize, lowerCase, isEmpty } from 'lodash';
import { mapGetters } from 'vuex';
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import eventHub from '../event_hub';
export default {
name: 'DynamicField',
......@@ -70,11 +68,15 @@ export default {
required: false,
default: null,
},
isValidated: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
model: this.value,
validated: false,
};
},
computed: {
......@@ -123,22 +125,13 @@ export default {
};
},
valid() {
return !this.required || !isEmpty(this.model) || this.isNonEmptyPassword || !this.validated;
return !this.required || !isEmpty(this.model) || this.isNonEmptyPassword || !this.isValidated;
},
},
created() {
if (this.isNonEmptyPassword) {
this.model = null;
}
eventHub.$on(VALIDATE_INTEGRATION_FORM_EVENT, this.validateForm);
},
beforeDestroy() {
eventHub.$off(VALIDATE_INTEGRATION_FORM_EVENT, this.validateForm);
},
methods: {
validateForm() {
this.validated = true;
},
},
helpHtmlConfig: {
ADD_ATTR: ['target'], // allow external links, can be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1427 is implemented
......
......@@ -5,7 +5,6 @@ import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
......@@ -14,7 +13,6 @@ import {
} from '~/integrations/constants';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import csrf from '~/lib/utils/csrf';
import eventHub from '../event_hub';
import { testIntegrationSettings } from '../api';
import ActiveCheckbox from './active_checkbox.vue';
import ConfirmationModal from './confirmation_modal.vue';
......@@ -57,6 +55,7 @@ export default {
isTesting: false,
isSaving: false,
isResetting: false,
isValidated: false,
};
},
computed: {
......@@ -107,13 +106,16 @@ export default {
: document.querySelector(INTEGRATION_FORM_SELECTOR);
},
methods: {
...mapActions(['setOverride', 'fetchResetIntegration', 'requestJiraIssueTypes']),
...mapActions(['setOverride', 'requestJiraIssueTypes']),
setIsValidated() {
this.isValidated = true;
},
onSaveClick() {
this.isSaving = true;
if (this.integrationActive && !this.form.checkValidity()) {
this.isSaving = false;
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
this.setIsValidated();
return;
}
......@@ -123,14 +125,14 @@ export default {
this.isTesting = true;
if (!this.form.checkValidity()) {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
this.setIsValidated();
return;
}
testIntegrationSettings(this.propsSource.testPath, this.getFormData())
.then(({ data: { error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } }) => {
if (error) {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
this.setIsValidated();
this.$toast.show(message);
return;
}
......@@ -227,6 +229,7 @@ export default {
v-if="isJira"
:key="`${currentKey}-jira-trigger-fields`"
v-bind="propsSource.triggerFieldsProps"
:is-validated="isValidated"
/>
<trigger-fields
v-else-if="propsSource.triggerEvents.length"
......@@ -238,11 +241,13 @@ export default {
v-for="field in propsSource.fields"
:key="`${currentKey}-${field.name}`"
v-bind="field"
:is-validated="isValidated"
/>
<jira-issues-fields
v-if="isJira && !isInstanceOrGroupLevel"
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
:is-validated="isValidated"
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
......
<script>
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import { s__, __ } from '~/locale';
import eventHub from '../event_hub';
import JiraUpgradeCta from './jira_upgrade_cta.vue';
export default {
......@@ -64,29 +62,22 @@ export default {
required: false,
default: '',
},
isValidated: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
enableJiraIssues: this.initialEnableJiraIssues,
projectKey: this.initialProjectKey,
validated: false,
};
},
computed: {
...mapGetters(['isInheriting']),
validProjectKey() {
return !this.enableJiraIssues || Boolean(this.projectKey) || !this.validated;
},
},
created() {
eventHub.$on(VALIDATE_INTEGRATION_FORM_EVENT, this.validateForm);
},
beforeDestroy() {
eventHub.$off(VALIDATE_INTEGRATION_FORM_EVENT, this.validateForm);
},
methods: {
validateForm() {
this.validated = true;
return !this.enableJiraIssues || Boolean(this.projectKey) || !this.isValidated;
},
},
i18n: {
......
......@@ -9,9 +9,7 @@ import {
} from '@gitlab/ui';
import { mapGetters } from 'vuex';
import { helpPagePath } from '~/helpers/help_page_helper';
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
const commentDetailOptions = [
{
......@@ -92,10 +90,14 @@ export default {
required: false,
default: '',
},
isValidated: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
validated: false,
triggerCommit: this.initialTriggerCommit,
triggerMergeRequest: this.initialTriggerMergeRequest,
enableComments: this.initialEnableComments,
......@@ -115,19 +117,10 @@ export default {
return this.triggerCommit || this.triggerMergeRequest;
},
validIssueTransitionId() {
return !this.validated || Boolean(this.jiraIssueTransitionId);
return !this.isValidated || Boolean(this.jiraIssueTransitionId);
},
},
created() {
eventHub.$on(VALIDATE_INTEGRATION_FORM_EVENT, this.validateForm);
},
beforeDestroy() {
eventHub.$off(VALIDATE_INTEGRATION_FORM_EVENT, this.validateForm);
},
methods: {
validateForm() {
this.validated = true;
},
showCustomIssueTransitions(currentOption) {
return (
this.jiraIssueTransitionAutomatic === ISSUE_TRANSITION_CUSTOM &&
......
import createEventHub from '~/helpers/event_hub_factory';
export default createEventHub();
import {
VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
} from '~/integrations/constants';
import { testIntegrationSettings } from '../api';
import eventHub from '../event_hub';
import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
......@@ -19,7 +17,6 @@ export const requestJiraIssueTypes = ({ commit, dispatch, getters }, formData) =
data: { issuetypes, error, message = I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE },
}) => {
if (error || !issuetypes?.length) {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
throw new Error(message);
}
......
export const SET_OVERRIDE = 'SET_OVERRIDE';
export const SET_IS_RESETTING = 'SET_IS_RESETTING';
export const SET_IS_LOADING_JIRA_ISSUE_TYPES = 'SET_IS_LOADING_JIRA_ISSUE_TYPES';
export const SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE = 'SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE';
export const SET_JIRA_ISSUE_TYPES = 'SET_JIRA_ISSUE_TYPES';
export const REQUEST_RESET_INTEGRATION = 'REQUEST_RESET_INTEGRATION';
export const RECEIVE_RESET_INTEGRATION_ERROR = 'RECEIVE_RESET_INTEGRATION_ERROR';
......@@ -2,22 +2,14 @@ import { GlFormGroup, GlFormCheckbox, GlFormInput, GlFormSelect, GlFormTextarea
import { mount } from '@vue/test-utils';
import DynamicField from '~/integrations/edit/components/dynamic_field.vue';
import { mockField } from '../mock_data';
describe('DynamicField', () => {
let wrapper;
const defaultProps = {
help: 'The URL of the project',
name: 'project_url',
placeholder: 'https://jira.example.com',
title: 'Project URL',
type: 'text',
value: '1',
};
const createComponent = (props, isInheriting = false) => {
wrapper = mount(DynamicField, {
propsData: { ...defaultProps, ...props },
propsData: { ...mockField, ...props },
computed: {
isInheriting: () => isInheriting,
},
......@@ -61,7 +53,7 @@ describe('DynamicField', () => {
});
it(`renders GlFormCheckbox with correct text content when checkboxLabel is ${checkboxLabel}`, () => {
expect(findGlFormCheckbox().text()).toContain(checkboxLabel ?? defaultProps.title);
expect(findGlFormCheckbox().text()).toContain(checkboxLabel ?? mockField.title);
});
it('does not render other types of input', () => {
......@@ -160,7 +152,7 @@ describe('DynamicField', () => {
type: 'text',
id: 'service_project_url',
name: 'service[project_url]',
placeholder: defaultProps.placeholder,
placeholder: mockField.placeholder,
required: 'required',
});
expect(findGlFormInput().attributes('readonly')).toBe(readonly);
......@@ -179,7 +171,7 @@ describe('DynamicField', () => {
it('renders description with help text', () => {
createComponent();
expect(findGlFormGroup().find('small').text()).toBe(defaultProps.help);
expect(findGlFormGroup().find('small').text()).toBe(mockField.help);
});
describe('when type is checkbox', () => {
......@@ -189,7 +181,7 @@ describe('DynamicField', () => {
});
expect(findGlFormGroup().find('small').exists()).toBe(false);
expect(findGlFormCheckbox().text()).toContain(defaultProps.help);
expect(findGlFormCheckbox().text()).toContain(mockField.help);
});
});
......@@ -221,40 +213,36 @@ describe('DynamicField', () => {
it('renders label with title', () => {
createComponent();
expect(findGlFormGroup().find('label').text()).toBe(defaultProps.title);
expect(findGlFormGroup().find('label').text()).toBe(mockField.title);
});
});
describe('validations', () => {
describe('password field', () => {
beforeEach(() => {
describe('password field validations', () => {
describe('without value', () => {
it('requires validation', () => {
createComponent({
type: 'password',
required: true,
value: null,
isValidated: true,
});
wrapper.vm.validated = true;
});
describe('without value', () => {
it('requires validation', () => {
expect(wrapper.vm.valid).toBe(false);
expect(findGlFormGroup().classes('is-invalid')).toBe(true);
expect(findGlFormInput().classes('is-invalid')).toBe(true);
});
expect(findGlFormGroup().classes('is-invalid')).toBe(true);
expect(findGlFormInput().classes('is-invalid')).toBe(true);
});
});
describe('with value', () => {
beforeEach(() => {
wrapper.setProps({ value: 'true' });
describe('with value', () => {
it('does not require validation', () => {
createComponent({
type: 'password',
required: true,
value: 'test value',
isValidated: true,
});
it('does not require validation', () => {
expect(wrapper.vm.valid).toBe(true);
expect(findGlFormGroup().classes('is-valid')).toBe(true);
expect(findGlFormInput().classes('is-valid')).toBe(true);
});
expect(findGlFormGroup().classes('is-valid')).toBe(true);
expect(findGlFormInput().classes('is-valid')).toBe(true);
});
});
});
......
......@@ -17,16 +17,13 @@ import TriggerFields from '~/integrations/edit/components/trigger_fields.vue';
import {
integrationLevels,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
VALIDATE_INTEGRATION_FORM_EVENT,
I18N_DEFAULT_ERROR_MESSAGE,
} from '~/integrations/constants';
import { createStore } from '~/integrations/edit/store';
import eventHub from '~/integrations/edit/event_hub';
import httpStatus from '~/lib/utils/http_status';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { mockIntegrationProps } from '../mock_data';
import { mockIntegrationProps, mockField } from '../mock_data';
jest.mock('~/integrations/edit/event_hub');
jest.mock('@sentry/browser');
jest.mock('~/lib/utils/url_utility');
......@@ -97,6 +94,7 @@ describe('IntegrationForm', () => {
const findGlForm = () => wrapper.findComponent(GlForm);
const findRedirectToField = () => wrapper.findByTestId('redirect-to-field');
const findFormElement = () => (vueIntegrationFormFeatureFlag ? findGlForm().element : mockForm);
const findDynamicField = () => wrapper.findComponent(DynamicField);
const mockFormFunctions = ({ checkValidityReturn }) => {
jest.spyOn(findFormElement(), 'checkValidity').mockReturnValue(checkValidityReturn);
......@@ -490,6 +488,7 @@ describe('IntegrationForm', () => {
showActive: true,
canTest: true,
initialActivated: true,
fields: [mockField],
},
mountFn: mountExtended,
});
......@@ -510,27 +509,28 @@ describe('IntegrationForm', () => {
expect(findTestButton().props('disabled')).toBe(false);
});
it('emits `VALIDATE_INTEGRATION_FORM_EVENT`', () => {
expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT);
it('sets `isValidated` props on form fields', () => {
expect(findDynamicField().props('isValidated')).toBe(true);
});
});
});
describe('when `test` button is clicked', () => {
describe('when form is invalid', () => {
it('emits `VALIDATE_INTEGRATION_FORM_EVENT` event to the event hub', () => {
it('sets `isValidated` props on form fields', async () => {
createComponent({
customStateProps: {
showActive: true,
canTest: true,
fields: [mockField],
},
mountFn: mountExtended,
});
mockFormFunctions({ checkValidityReturn: false });
findTestButton().vm.$emit('click', new Event('click'));
await findTestButton().vm.$emit('click', new Event('click'));
expect(eventHub.$emit).toHaveBeenCalledWith(VALIDATE_INTEGRATION_FORM_EVENT);
expect(findDynamicField().props('isValidated')).toBe(true);
});
});
......
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import eventHub from '~/integrations/edit/event_hub';
import { createStore } from '~/integrations/edit/store';
describe('JiraIssuesFields', () => {
......@@ -222,7 +220,7 @@ describe('JiraIssuesFields', () => {
});
describe('Project key input field', () => {
beforeEach(() => {
it('sets Project Key `state` attribute to `true` by default', () => {
createComponent({
props: {
initialProjectKey: '',
......@@ -230,29 +228,32 @@ describe('JiraIssuesFields', () => {
},
mountFn: shallowMountExtended,
});
});
it('sets Project Key `state` attribute to `true` by default', () => {
assertProjectKeyState('true');
});
describe('when event hub recieves `VALIDATE_INTEGRATION_FORM_EVENT` event', () => {
describe('when `isValidated` prop is true', () => {
beforeEach(() => {
createComponent({
props: {
initialProjectKey: '',
initialEnableJiraIssues: true,
isValidated: true,
},
mountFn: shallowMountExtended,
});
});
describe('with no project key', () => {
it('sets Project Key `state` attribute to `undefined`', async () => {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
await wrapper.vm.$nextTick();
assertProjectKeyState(undefined);
});
});
describe('when project key is set', () => {
it('sets Project Key `state` attribute to `true`', async () => {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
// set the project key
await findProjectKey().vm.$emit('input', 'AB');
await wrapper.vm.$nextTick();
assertProjectKeyState('true');
});
......
......@@ -20,3 +20,12 @@ export const mockJiraIssueTypes = [
{ id: '2', name: 'bug', description: 'bug' },
{ id: '3', name: 'epic', description: 'epic' },
];
export const mockField = {
help: 'The URL of the project',
name: 'project_url',
placeholder: 'https://jira.example.com',
title: 'Project URL',
type: 'text',
value: '1',
};
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