Commit 7878cee8 authored by Tom Quirk's avatar Tom Quirk

Move getJiraIssueTypes to Vuex action

We move this functionality from integration_settings_form.js
to the existing, related, Vuex action.

This commit also changes a Vue event name and
adds a DOM lookup to `integration_form.js`.

It also includes tests.
parent fec794ba
......@@ -2,7 +2,6 @@ import { s__, __ } from '~/locale';
export const TEST_INTEGRATION_EVENT = 'testIntegration';
export const SAVE_INTEGRATION_EVENT = 'saveIntegration';
export const GET_JIRA_ISSUE_TYPES_EVENT = 'getJiraIssueTypes';
export const TOGGLE_INTEGRATION_EVENT = 'toggleIntegration';
export const VALIDATE_INTEGRATION_FORM_EVENT = 'validateIntegrationForm';
......
import axios from '~/lib/utils/axios_utils';
/**
* Test the validity of [integrationFormData].
* @return Promise<{ issuetypes: []String }> - issuetypes contains valid Jira issue types.
*/
export const testIntegrationSettings = (testPath, integrationFormData) => {
return axios.put(testPath, integrationFormData);
};
......@@ -69,6 +69,10 @@ export default {
return this.isInstanceOrGroupLevel && this.propsSource.resetPath;
},
},
mounted() {
// this form element is defined in Haml
this.form = document.querySelector('.js-integration-settings-form');
},
methods: {
...mapActions([
'setOverride',
......@@ -76,6 +80,7 @@ export default {
'setIsTesting',
'setIsResetting',
'fetchResetIntegration',
'requestJiraIssueTypes',
]),
onSaveClick() {
this.setIsSaving(true);
......@@ -88,6 +93,10 @@ export default {
onResetClick() {
this.fetchResetIntegration();
},
onRequestJiraIssueTypes() {
const formData = new FormData(this.form);
this.requestJiraIssueTypes(formData);
},
},
helpHtmlConfig: {
ADD_ATTR: ['target'], // allow external links, can be removed after https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1427 is implemented
......@@ -135,6 +144,7 @@ export default {
v-if="isJira && !isInstanceOrGroupLevel"
:key="`${currentKey}-jira-issues-fields`"
v-bind="propsSource.jiraIssuesProps"
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
<div v-if="isEditable" class="footer-block row-content-block">
<template v-if="isInstanceOrGroupLevel">
......
<script>
import { GlFormGroup, GlFormCheckbox, GlFormInput, GlSprintf, GlLink } from '@gitlab/ui';
import { mapGetters } from 'vuex';
import {
VALIDATE_INTEGRATION_FORM_EVENT,
GET_JIRA_ISSUE_TYPES_EVENT,
} from '~/integrations/constants';
import { VALIDATE_INTEGRATION_FORM_EVENT } from '~/integrations/constants';
import { s__, __ } from '~/locale';
import eventHub from '../event_hub';
import JiraUpgradeCta from './jira_upgrade_cta.vue';
......@@ -91,9 +88,6 @@ export default {
validateForm() {
this.validated = true;
},
getJiraIssueTypes() {
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
},
},
i18n: {
sectionTitle: s__('JiraService|View Jira issues in GitLab'),
......@@ -136,7 +130,7 @@ export default {
:initial-issue-type-id="initialVulnerabilitiesIssuetype"
:show-full-feature="showJiraVulnerabilitiesIntegration"
data-testid="jira-for-vulnerabilities"
@request-get-issue-types="getJiraIssueTypes"
@request-jira-issue-types="$emit('request-jira-issue-types')"
/>
<jira-upgrade-cta
v-if="!showJiraVulnerabilitiesIntegration"
......
import axios from 'axios';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
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);
......@@ -27,10 +34,28 @@ export const fetchResetIntegration = ({ dispatch, getters }) => {
.catch(() => dispatch('receiveResetIntegrationError'));
};
export const requestJiraIssueTypes = ({ commit }) => {
export const requestJiraIssueTypes = ({ commit, dispatch, getters }, formData) => {
commit(types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, '');
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, true);
return testIntegrationSettings(getters.propsSource.testPath, formData)
.then(
({
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);
}
dispatch('receiveJiraIssueTypesSuccess', issuetypes);
},
)
.catch(({ message = I18N_DEFAULT_ERROR_MESSAGE }) => {
dispatch('receiveJiraIssueTypesError', message);
});
};
export const receiveJiraIssueTypesSuccess = ({ commit }, issueTypes = []) => {
commit(types.SET_IS_LOADING_JIRA_ISSUE_TYPES, false);
commit(types.SET_JIRA_ISSUE_TYPES, issueTypes);
......
import { delay } from 'lodash';
import toast from '~/vue_shared/plugins/global_toast';
import axios from '../lib/utils/axios_utils';
import initForm from './edit';
import eventHub from './edit/event_hub';
import {
TEST_INTEGRATION_EVENT,
SAVE_INTEGRATION_EVENT,
GET_JIRA_ISSUE_TYPES_EVENT,
TOGGLE_INTEGRATION_EVENT,
VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
} from './constants';
import { testIntegrationSettings } from './edit/api';
export default class IntegrationSettingsForm {
constructor(formSelector) {
......@@ -41,9 +39,6 @@ export default class IntegrationSettingsForm {
eventHub.$on(SAVE_INTEGRATION_EVENT, () => {
this.saveIntegration();
});
eventHub.$on(GET_JIRA_ISSUE_TYPES_EVENT, () => {
this.getJiraIssueTypes(new FormData(this.$form));
});
}
saveIntegration() {
......@@ -96,43 +91,12 @@ export default class IntegrationSettingsForm {
*
* @return {Promise}
*/
getJiraIssueTypes(formData) {
const {
$store: { dispatch },
} = this.vue;
dispatch('requestJiraIssueTypes');
return this.fetchTestSettings(formData)
.then(
({
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);
}
dispatch('receiveJiraIssueTypesSuccess', issuetypes);
},
)
.catch(({ message = I18N_DEFAULT_ERROR_MESSAGE }) => {
dispatch('receiveJiraIssueTypesError', message);
});
}
/**
* Send request to the test endpoint which checks if the current config is valid
*/
fetchTestSettings(formData) {
return axios.put(this.testEndPoint, formData);
}
/**
* Test Integration config
*/
testSettings(formData) {
return this.fetchTestSettings(formData)
return testIntegrationSettings(this.testEndPoint, formData)
.then(({ data }) => {
if (data.error) {
toast(`${data.message} ${data.service_response}`);
......
......@@ -120,12 +120,15 @@ export default {
},
created() {
if (this.initialIsEnabled) {
this.$emit('request-get-issue-types');
this.requestJiraIssueTypes();
}
},
methods: {
requestJiraIssueTypes() {
this.$emit('request-jira-issue-types');
},
handleLoadJiraIssueTypesClick() {
this.$emit('request-get-issue-types');
this.requestJiraIssueTypes();
this.projectKeyForCurrentIssues = this.projectKey;
this.isLoadingErrorAlertDimissed = false;
},
......
......@@ -205,9 +205,9 @@ describe('JiraIssuesFields', () => {
});
it('emits "fetch-issues-clicked" when clicked', async () => {
expect(wrapper.emitted('request-get-issue-types')).toBe(undefined);
expect(wrapper.emitted('request-jira-issue-types')).toBe(undefined);
await findFetchIssueTypeButton().vm.$emit('click');
expect(wrapper.emitted('request-get-issue-types')).toHaveLength(1);
expect(wrapper.emitted('request-jira-issue-types')).toHaveLength(1);
});
});
......
......@@ -16,6 +16,7 @@ import { createStore } from '~/integrations/edit/store';
describe('IntegrationForm', () => {
let wrapper;
let dispatch;
const createComponent = ({
customStateProps = {},
......@@ -23,12 +24,15 @@ describe('IntegrationForm', () => {
initialState = {},
props = {},
} = {}) => {
const store = createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
});
dispatch = jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMountExtended(IntegrationForm, {
propsData: { ...props },
store: createStore({
customState: { ...mockIntegrationProps, ...customStateProps },
...initialState,
}),
store,
stubs: {
OverrideDropdown,
ActiveCheckbox,
......@@ -195,13 +199,29 @@ describe('IntegrationForm', () => {
});
describe('type is "jira"', () => {
it('renders JiraTriggerFields', () => {
beforeEach(() => {
jest.spyOn(document, 'querySelector').mockReturnValue(document.createElement('form'));
createComponent({
customStateProps: { type: 'jira' },
customStateProps: { type: 'jira', testPath: '/test' },
});
});
it('renders JiraTriggerFields', () => {
expect(findJiraTriggerFields().exists()).toBe(true);
});
it('renders JiraIssuesFields', () => {
expect(findJiraIssuesFields().exists()).toBe(true);
});
describe('when JiraIssueFields emits `request-jira-issue-types` event', () => {
it('dispatches `requestJiraIssueTypes` action', () => {
findJiraIssuesFields().vm.$emit('request-jira-issue-types');
expect(dispatch).toHaveBeenCalledWith('requestJiraIssueTypes', expect.any(FormData));
});
});
});
describe('triggerEvents is present', () => {
......
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
GET_JIRA_ISSUE_TYPES_EVENT,
VALIDATE_INTEGRATION_FORM_EVENT,
} from '~/integrations/constants';
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';
......@@ -216,13 +213,11 @@ describe('JiraIssuesFields', () => {
);
});
it('emits "getJiraIssueTypes" to the eventHub when the jira-vulnerabilities component requests to fetch issue types', async () => {
const eventHubEmitSpy = jest.spyOn(eventHub, '$emit');
it('emits "request-jira-issue-types` when the jira-vulnerabilities component requests to fetch issue types', async () => {
await setEnableCheckbox(true);
await findJiraForVulnerabilities().vm.$emit('request-get-issue-types');
await findJiraForVulnerabilities().vm.$emit('request-jira-issue-types');
expect(eventHubEmitSpy).toHaveBeenCalledWith(GET_JIRA_ISSUE_TYPES_EVENT);
expect(wrapper.emitted('request-jira-issue-types')).toHaveLength(1);
});
});
......
......@@ -14,3 +14,9 @@ export const mockIntegrationProps = {
type: '',
inheritFromId: 25,
};
export const mockJiraIssueTypes = [
{ id: '1', name: 'issue', description: 'issue' },
{ id: '2', name: 'bug', description: 'bug' },
{ id: '3', name: 'epic', description: 'epic' },
];
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants';
import {
setOverride,
setIsSaving,
......@@ -14,14 +17,21 @@ import {
import * as types from '~/integrations/edit/store/mutation_types';
import createState from '~/integrations/edit/store/state';
import { refreshCurrentPage } from '~/lib/utils/url_utility';
import { mockJiraIssueTypes } from '../mock_data';
jest.mock('~/lib/utils/url_utility');
describe('Integration form store actions', () => {
let state;
let mockAxios;
beforeEach(() => {
state = createState();
mockAxios = new MockAdapter(axios);
});
afterEach(() => {
mockAxios.restore();
});
describe('setOverride', () => {
......@@ -75,11 +85,28 @@ describe('Integration form store actions', () => {
});
describe('requestJiraIssueTypes', () => {
it('should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations', () => {
return testAction(requestJiraIssueTypes, null, state, [
{ type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' },
{ type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true },
]);
describe.each`
scenario | responseCode | response | action
${'when successful'} | ${200} | ${{ issuetypes: mockJiraIssueTypes }} | ${{ type: 'receiveJiraIssueTypesSuccess', payload: mockJiraIssueTypes }}
${'when response has no issue types'} | ${200} | ${{ issuetypes: [] }} | ${{ type: 'receiveJiraIssueTypesError', payload: I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE }}
${'when response includes error'} | ${200} | ${{ error: new Error() }} | ${{ type: 'receiveJiraIssueTypesError', payload: I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE }}
${'when error occurs'} | ${500} | ${{}} | ${{ type: 'receiveJiraIssueTypesError', payload: expect.any(String) }}
`('$scenario', ({ responseCode, response, action }) => {
it(`should commit SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE and SET_IS_LOADING_JIRA_ISSUE_TYPES mutations, and dispatch ${action.type}`, () => {
mockAxios.onPut('/test').replyOnce(responseCode, response);
return testAction(
requestJiraIssueTypes,
new FormData(),
{ propsSource: { testPath: '/test' } },
[
// should clear the error messages and set the loading state
{ type: types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, payload: '' },
{ type: types.SET_IS_LOADING_JIRA_ISSUE_TYPES, payload: true },
],
[action],
);
});
});
});
......
......@@ -4,10 +4,8 @@ import eventHub from '~/integrations/edit/event_hub';
import axios from '~/lib/utils/axios_utils';
import toast from '~/vue_shared/plugins/global_toast';
import {
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
GET_JIRA_ISSUE_TYPES_EVENT,
TOGGLE_INTEGRATION_EVENT,
TEST_INTEGRATION_EVENT,
SAVE_INTEGRATION_EVENT,
......@@ -154,62 +152,6 @@ describe('IntegrationSettingsForm', () => {
});
});
describe('when event hub receives `GET_JIRA_ISSUE_TYPES_EVENT`', () => {
it('should always dispatch `requestJiraIssueTypes`', () => {
const dispatchSpy = mockStoreDispatch();
mockAxios.onPut(integrationSettingsForm.testEndPoint).networkError();
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
expect(dispatchSpy).toHaveBeenCalledWith('requestJiraIssueTypes');
});
it('should make an ajax request with provided `formData`', () => {
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
expect(axios.put).toHaveBeenCalledWith(
integrationSettingsForm.testEndPoint,
new FormData(integrationSettingsForm.$form),
);
});
it('should dispatch `receiveJiraIssueTypesSuccess` with the correct payload if ajax request is successful', async () => {
const dispatchSpy = mockStoreDispatch();
const mockData = ['ISSUE', 'EPIC'];
mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
error: false,
issuetypes: mockData,
});
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
await waitForPromises();
expect(dispatchSpy).toHaveBeenCalledWith('receiveJiraIssueTypesSuccess', mockData);
});
it.each(['Custom error message here', undefined])(
'should dispatch "receiveJiraIssueTypesError" with a message if the backend responds with error',
async (responseErrorMessage) => {
const dispatchSpy = mockStoreDispatch();
const expectedErrorMessage =
responseErrorMessage || I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE;
mockAxios.onPut(integrationSettingsForm.testEndPoint).reply(200, {
error: true,
message: responseErrorMessage,
});
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
await waitForPromises();
expect(dispatchSpy).toHaveBeenCalledWith(
'receiveJiraIssueTypesError',
expectedErrorMessage,
);
},
);
});
describe('when event hub receives `SAVE_INTEGRATION_EVENT`', () => {
describe('when form is valid', () => {
beforeEach(() => {
......
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