Commit 76e21326 authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '216145-frontend-refactor-jira-importer-code' into 'master'

Refactor Jira Importer Vue app code

See merge request gitlab-org/gitlab!33636
parents 4655a15a 9c184ec8
<script> <script>
import { GlAlert, GlLabel } from '@gitlab/ui'; import { GlAlert, GlLabel } from '@gitlab/ui';
import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql'; import getIssuesListDetailsQuery from '../queries/get_issues_list_details.query.graphql';
import { calculateJiraImportLabel, isFinished, isInProgress } from '~/jira_import/utils'; import {
calculateJiraImportLabel,
isFinished,
isInProgress,
} from '~/jira_import/utils/jira_import_utils';
export default { export default {
name: 'IssuableListRoot', name: 'IssuableListRoot',
......
...@@ -4,7 +4,8 @@ import { last } from 'lodash'; ...@@ -4,7 +4,8 @@ import { last } from 'lodash';
import { __ } from '~/locale'; import { __ } from '~/locale';
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql'; import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql'; import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
import { IMPORT_STATE, isInProgress, extractJiraProjectsOptions } from '../utils'; import { addInProgressImportToStore } from '../utils/cache_update';
import { isInProgress, extractJiraProjectsOptions } from '../utils/jira_import_utils';
import JiraImportForm from './jira_import_form.vue'; import JiraImportForm from './jira_import_form.vue';
import JiraImportProgress from './jira_import_progress.vue'; import JiraImportProgress from './jira_import_progress.vue';
import JiraImportSetup from './jira_import_setup.vue'; import JiraImportSetup from './jira_import_setup.vue';
...@@ -20,14 +21,14 @@ export default { ...@@ -20,14 +21,14 @@ export default {
JiraImportSetup, JiraImportSetup,
}, },
props: { props: {
isJiraConfigured: {
type: Boolean,
required: true,
},
inProgressIllustration: { inProgressIllustration: {
type: String, type: String,
required: true, required: true,
}, },
isJiraConfigured: {
type: Boolean,
required: true,
},
issuesPath: { issuesPath: {
type: String, type: String,
required: true, required: true,
...@@ -62,9 +63,10 @@ export default { ...@@ -62,9 +63,10 @@ export default {
}; };
}, },
update: ({ project }) => ({ update: ({ project }) => ({
projects: extractJiraProjectsOptions(project.services.nodes[0].projects.nodes),
status: project.jiraImportStatus,
imports: project.jiraImports.nodes, imports: project.jiraImports.nodes,
isInProgress: isInProgress(project.jiraImportStatus),
mostRecentImport: last(project.jiraImports.nodes),
projects: extractJiraProjectsOptions(project.services.nodes[0].projects.nodes),
}), }),
skip() { skip() {
return !this.isJiraConfigured; return !this.isJiraConfigured;
...@@ -72,32 +74,22 @@ export default { ...@@ -72,32 +74,22 @@ export default {
}, },
}, },
computed: { computed: {
isImportInProgress() { numberOfPreviousImports() {
return isInProgress(this.jiraImportDetails.status);
},
mostRecentImport() {
// The backend returns JiraImports ordered by created_at asc in app/models/project.rb
return last(this.jiraImportDetails.imports);
},
numberOfPreviousImportsForProject() {
return this.jiraImportDetails.imports?.reduce?.( return this.jiraImportDetails.imports?.reduce?.(
(acc, jiraProject) => (jiraProject.jiraProjectKey === this.selectedProject ? acc + 1 : acc), (acc, jiraProject) => (jiraProject.jiraProjectKey === this.selectedProject ? acc + 1 : acc),
0, 0,
); );
}, },
hasPreviousImports() {
return this.numberOfPreviousImports > 0;
},
importLabel() { importLabel() {
return this.selectedProject return this.selectedProject
? `jira-import::${this.selectedProject}-${this.numberOfPreviousImportsForProject + 1}` ? `jira-import::${this.selectedProject}-${this.numberOfPreviousImports + 1}`
: 'jira-import::KEY-1'; : 'jira-import::KEY-1';
}, },
hasPreviousImports() {
return this.numberOfPreviousImportsForProject > 0;
},
}, },
methods: { methods: {
dismissAlert() {
this.showAlert = false;
},
initiateJiraImport(project) { initiateJiraImport(project) {
this.$apollo this.$apollo
.mutate({ .mutate({
...@@ -108,39 +100,8 @@ export default { ...@@ -108,39 +100,8 @@ export default {
jiraProjectKey: project, jiraProjectKey: project,
}, },
}, },
update: (store, { data }) => { update: (store, { data }) =>
if (data.jiraImportStart.errors.length) { addInProgressImportToStore(store, data.jiraImportStart, this.projectPath),
return;
}
const cacheData = store.readQuery({
query: getJiraImportDetailsQuery,
variables: {
fullPath: this.projectPath,
},
});
store.writeQuery({
query: getJiraImportDetailsQuery,
variables: {
fullPath: this.projectPath,
},
data: {
project: {
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
nodes: [
...cacheData.project.jiraImports.nodes,
data.jiraImportStart.jiraImport,
],
__typename: 'JiraImportConnection',
},
// eslint-disable-next-line @gitlab/require-i18n-strings
__typename: 'Project',
},
},
});
},
}) })
.then(({ data }) => { .then(({ data }) => {
if (data.jiraImportStart.errors.length) { if (data.jiraImportStart.errors.length) {
...@@ -155,7 +116,13 @@ export default { ...@@ -155,7 +116,13 @@ export default {
this.errorMessage = message; this.errorMessage = message;
this.showAlert = true; this.showAlert = true;
}, },
dismissAlert() {
this.showAlert = false;
},
}, },
previousImportsMessage: __(
'You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues.',
),
}; };
</script> </script>
...@@ -165,16 +132,8 @@ export default { ...@@ -165,16 +132,8 @@ export default {
{{ errorMessage }} {{ errorMessage }}
</gl-alert> </gl-alert>
<gl-alert v-if="hasPreviousImports" variant="warning" :dismissible="false"> <gl-alert v-if="hasPreviousImports" variant="warning" :dismissible="false">
<gl-sprintf <gl-sprintf :message="$options.previousImportsMessage">
:message=" <template #numberOfPreviousImports>{{ numberOfPreviousImports }}</template>
__(
'You have imported from this project %{numberOfPreviousImportsForProject} times before. Each new import will create duplicate issues.',
)
"
>
<template #numberOfPreviousImportsForProject>{{
numberOfPreviousImportsForProject
}}</template>
</gl-sprintf> </gl-sprintf>
</gl-alert> </gl-alert>
...@@ -185,11 +144,11 @@ export default { ...@@ -185,11 +144,11 @@ export default {
/> />
<gl-loading-icon v-else-if="$apollo.loading" size="md" class="mt-3" /> <gl-loading-icon v-else-if="$apollo.loading" size="md" class="mt-3" />
<jira-import-progress <jira-import-progress
v-else-if="isImportInProgress" v-else-if="jiraImportDetails.isInProgress"
:illustration="inProgressIllustration" :illustration="inProgressIllustration"
:import-initiator="mostRecentImport.scheduledBy.name" :import-initiator="jiraImportDetails.mostRecentImport.scheduledBy.name"
:import-project="mostRecentImport.jiraProjectKey" :import-project="jiraImportDetails.mostRecentImport.jiraProjectKey"
:import-time="mostRecentImport.scheduledAt" :import-time="jiraImportDetails.mostRecentImport.scheduledAt"
:issues-path="issuesPath" :issues-path="issuesPath"
/> />
<jira-import-form <jira-import-form
......
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
import { IMPORT_STATE } from './jira_import_utils';
export const addInProgressImportToStore = (store, jiraImportStart, fullPath) => {
if (jiraImportStart.errors.length) {
return;
}
const queryDetails = {
query: getJiraImportDetailsQuery,
variables: {
fullPath,
},
};
const cacheData = store.readQuery({
...queryDetails,
});
store.writeQuery({
...queryDetails,
data: {
project: {
...cacheData.project,
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
...cacheData.project.jiraImports,
nodes: cacheData.project.jiraImports.nodes.concat(jiraImportStart.jiraImport),
},
},
},
});
};
export default {
addInProgressImportToStore,
};
...@@ -25495,7 +25495,7 @@ msgstr "" ...@@ -25495,7 +25495,7 @@ msgstr ""
msgid "You have declined the invitation to join %{label}." msgid "You have declined the invitation to join %{label}."
msgstr "" msgstr ""
msgid "You have imported from this project %{numberOfPreviousImportsForProject} times before. Each new import will create duplicate issues." msgid "You have imported from this project %{numberOfPreviousImports} times before. Each new import will create duplicate issues."
msgstr "" msgstr ""
msgid "You have no permissions" msgid "You have no permissions"
......
...@@ -6,14 +6,13 @@ import JiraImportForm from '~/jira_import/components/jira_import_form.vue'; ...@@ -6,14 +6,13 @@ import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue'; import JiraImportProgress from '~/jira_import/components/jira_import_progress.vue';
import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue'; import JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql'; import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
import { IMPORT_STATE } from '~/jira_import/utils';
const mountComponent = ({ const mountComponent = ({
isJiraConfigured = true, isJiraConfigured = true,
errorMessage = '', errorMessage = '',
selectedProject = 'MTG', selectedProject = 'MTG',
showAlert = false, showAlert = false,
status = IMPORT_STATE.NONE, isInProgress = false,
loading = false, loading = false,
mutate = jest.fn(() => Promise.resolve()), mutate = jest.fn(() => Promise.resolve()),
mountType, mountType,
...@@ -22,8 +21,8 @@ const mountComponent = ({ ...@@ -22,8 +21,8 @@ const mountComponent = ({
return mountFunction(JiraImportApp, { return mountFunction(JiraImportApp, {
propsData: { propsData: {
isJiraConfigured,
inProgressIllustration: 'in-progress-illustration.svg', inProgressIllustration: 'in-progress-illustration.svg',
isJiraConfigured,
issuesPath: 'gitlab-org/gitlab-test/-/issues', issuesPath: 'gitlab-org/gitlab-test/-/issues',
jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit', jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit',
projectPath: 'gitlab-org/gitlab-test', projectPath: 'gitlab-org/gitlab-test',
...@@ -35,12 +34,7 @@ const mountComponent = ({ ...@@ -35,12 +34,7 @@ const mountComponent = ({
showAlert, showAlert,
selectedProject, selectedProject,
jiraImportDetails: { jiraImportDetails: {
projects: [ isInProgress,
{ text: 'My Jira Project (MJP)', value: 'MJP' },
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
],
status,
imports: [ imports: [
{ {
jiraProjectKey: 'MTG', jiraProjectKey: 'MTG',
...@@ -64,6 +58,18 @@ const mountComponent = ({ ...@@ -64,6 +58,18 @@ const mountComponent = ({
}, },
}, },
], ],
mostRecentImport: {
jiraProjectKey: 'MTG',
scheduledAt: '2020-04-09T16:17:18+00:00',
scheduledBy: {
name: 'Jane Doe',
},
},
projects: [
{ text: 'My Jira Project (MJP)', value: 'MJP' },
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
],
}, },
}; };
}, },
...@@ -140,7 +146,7 @@ describe('JiraImportApp', () => { ...@@ -140,7 +146,7 @@ describe('JiraImportApp', () => {
describe('when Jira integration is configured but import is in progress', () => { describe('when Jira integration is configured but import is in progress', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED }); wrapper = mountComponent({ isInProgress: true });
}); });
it('does not show the "Set up Jira integration" screen', () => { it('does not show the "Set up Jira integration" screen', () => {
...@@ -184,7 +190,7 @@ describe('JiraImportApp', () => { ...@@ -184,7 +190,7 @@ describe('JiraImportApp', () => {
describe('import in progress screen', () => { describe('import in progress screen', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED }); wrapper = mountComponent({ isInProgress: true });
}); });
it('shows the illustration', () => { it('shows the illustration', () => {
......
import getJiraImportDetailsQuery from '~/jira_import/queries/get_jira_import_details.query.graphql';
import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
export const fullPath = 'gitlab-org/gitlab-test';
export const queryDetails = {
query: getJiraImportDetailsQuery,
variables: {
fullPath,
},
};
export const jiraImportDetailsQueryResponse = {
project: {
jiraImportStatus: IMPORT_STATE.NONE,
jiraImports: {
nodes: [
{
jiraProjectKey: 'MJP',
scheduledAt: '2020-01-01T12:34:56Z',
scheduledBy: {
name: 'Jane Doe',
__typename: 'User',
},
__typename: 'JiraImport',
},
],
__typename: 'JiraImportConnection',
},
services: {
nodes: [
{
projects: {
nodes: [
{
key: 'MJP',
name: 'My Jira Project',
__typename: 'JiraProject',
},
{
key: 'MTG',
name: 'Migrate To GitLab',
__typename: 'JiraProject',
},
],
__typename: 'JiraProjectConnection',
},
__typename: 'JiraService',
},
],
__typename: 'ServiceConnection',
},
__typename: 'Project',
},
};
export const jiraImportMutationResponse = {
jiraImportStart: {
clientMutationId: null,
jiraImport: {
jiraProjectKey: 'MTG',
scheduledAt: '2020-02-02T20:20:20Z',
scheduledBy: {
name: 'John Doe',
__typename: 'User',
},
__typename: 'JiraImport',
},
errors: [],
__typename: 'JiraImportStartPayload',
},
};
import { addInProgressImportToStore } from '~/jira_import/utils/cache_update';
import { IMPORT_STATE } from '~/jira_import/utils/jira_import_utils';
import {
fullPath,
queryDetails,
jiraImportDetailsQueryResponse,
jiraImportMutationResponse,
} from '../mock_data';
describe('addInProgressImportToStore', () => {
const store = {
readQuery: jest.fn(() => jiraImportDetailsQueryResponse),
writeQuery: jest.fn(),
};
describe('when updating the cache', () => {
beforeEach(() => {
addInProgressImportToStore(store, jiraImportMutationResponse.jiraImportStart, fullPath);
});
it('reads the cache with the correct query', () => {
expect(store.readQuery).toHaveBeenCalledWith(queryDetails);
});
it('writes to the cache with the expected arguments', () => {
const expected = {
...queryDetails,
data: {
project: {
...jiraImportDetailsQueryResponse.project,
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
...jiraImportDetailsQueryResponse.project.jiraImports,
nodes: jiraImportDetailsQueryResponse.project.jiraImports.nodes.concat(
jiraImportMutationResponse.jiraImportStart.jiraImport,
),
},
},
},
};
expect(store.writeQuery).toHaveBeenCalledWith(expected);
});
});
describe('when there are errors', () => {
beforeEach(() => {
const jiraImportStart = {
...jiraImportMutationResponse.jiraImportStart,
errors: ['There was an error'],
};
addInProgressImportToStore(store, jiraImportStart, fullPath);
});
it('does not read from the store', () => {
expect(store.readQuery).not.toHaveBeenCalled();
});
it('does not write to the store', () => {
expect(store.writeQuery).not.toHaveBeenCalled();
});
});
});
...@@ -4,7 +4,7 @@ import { ...@@ -4,7 +4,7 @@ import {
IMPORT_STATE, IMPORT_STATE,
isFinished, isFinished,
isInProgress, isInProgress,
} from '~/jira_import/utils'; } from '~/jira_import/utils/jira_import_utils';
describe('isInProgress', () => { describe('isInProgress', () => {
it.each` it.each`
......
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