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>
import { GlAlert, GlLabel } from '@gitlab/ui';
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 {
name: 'IssuableListRoot',
......
......@@ -4,7 +4,8 @@ import { last } from 'lodash';
import { __ } from '~/locale';
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.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 JiraImportProgress from './jira_import_progress.vue';
import JiraImportSetup from './jira_import_setup.vue';
......@@ -20,14 +21,14 @@ export default {
JiraImportSetup,
},
props: {
isJiraConfigured: {
type: Boolean,
required: true,
},
inProgressIllustration: {
type: String,
required: true,
},
isJiraConfigured: {
type: Boolean,
required: true,
},
issuesPath: {
type: String,
required: true,
......@@ -62,9 +63,10 @@ export default {
};
},
update: ({ project }) => ({
projects: extractJiraProjectsOptions(project.services.nodes[0].projects.nodes),
status: project.jiraImportStatus,
imports: project.jiraImports.nodes,
isInProgress: isInProgress(project.jiraImportStatus),
mostRecentImport: last(project.jiraImports.nodes),
projects: extractJiraProjectsOptions(project.services.nodes[0].projects.nodes),
}),
skip() {
return !this.isJiraConfigured;
......@@ -72,32 +74,22 @@ export default {
},
},
computed: {
isImportInProgress() {
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() {
numberOfPreviousImports() {
return this.jiraImportDetails.imports?.reduce?.(
(acc, jiraProject) => (jiraProject.jiraProjectKey === this.selectedProject ? acc + 1 : acc),
0,
);
},
hasPreviousImports() {
return this.numberOfPreviousImports > 0;
},
importLabel() {
return this.selectedProject
? `jira-import::${this.selectedProject}-${this.numberOfPreviousImportsForProject + 1}`
? `jira-import::${this.selectedProject}-${this.numberOfPreviousImports + 1}`
: 'jira-import::KEY-1';
},
hasPreviousImports() {
return this.numberOfPreviousImportsForProject > 0;
},
},
methods: {
dismissAlert() {
this.showAlert = false;
},
initiateJiraImport(project) {
this.$apollo
.mutate({
......@@ -108,39 +100,8 @@ export default {
jiraProjectKey: project,
},
},
update: (store, { data }) => {
if (data.jiraImportStart.errors.length) {
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',
},
},
});
},
update: (store, { data }) =>
addInProgressImportToStore(store, data.jiraImportStart, this.projectPath),
})
.then(({ data }) => {
if (data.jiraImportStart.errors.length) {
......@@ -155,7 +116,13 @@ export default {
this.errorMessage = message;
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>
......@@ -165,16 +132,8 @@ export default {
{{ errorMessage }}
</gl-alert>
<gl-alert v-if="hasPreviousImports" variant="warning" :dismissible="false">
<gl-sprintf
:message="
__(
'You have imported from this project %{numberOfPreviousImportsForProject} times before. Each new import will create duplicate issues.',
)
"
>
<template #numberOfPreviousImportsForProject>{{
numberOfPreviousImportsForProject
}}</template>
<gl-sprintf :message="$options.previousImportsMessage">
<template #numberOfPreviousImports>{{ numberOfPreviousImports }}</template>
</gl-sprintf>
</gl-alert>
......@@ -185,11 +144,11 @@ export default {
/>
<gl-loading-icon v-else-if="$apollo.loading" size="md" class="mt-3" />
<jira-import-progress
v-else-if="isImportInProgress"
v-else-if="jiraImportDetails.isInProgress"
:illustration="inProgressIllustration"
:import-initiator="mostRecentImport.scheduledBy.name"
:import-project="mostRecentImport.jiraProjectKey"
:import-time="mostRecentImport.scheduledAt"
:import-initiator="jiraImportDetails.mostRecentImport.scheduledBy.name"
:import-project="jiraImportDetails.mostRecentImport.jiraProjectKey"
:import-time="jiraImportDetails.mostRecentImport.scheduledAt"
:issues-path="issuesPath"
/>
<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 ""
msgid "You have declined the invitation to join %{label}."
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 ""
msgid "You have no permissions"
......
......@@ -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 JiraImportSetup from '~/jira_import/components/jira_import_setup.vue';
import initiateJiraImportMutation from '~/jira_import/queries/initiate_jira_import.mutation.graphql';
import { IMPORT_STATE } from '~/jira_import/utils';
const mountComponent = ({
isJiraConfigured = true,
errorMessage = '',
selectedProject = 'MTG',
showAlert = false,
status = IMPORT_STATE.NONE,
isInProgress = false,
loading = false,
mutate = jest.fn(() => Promise.resolve()),
mountType,
......@@ -22,8 +21,8 @@ const mountComponent = ({
return mountFunction(JiraImportApp, {
propsData: {
isJiraConfigured,
inProgressIllustration: 'in-progress-illustration.svg',
isJiraConfigured,
issuesPath: 'gitlab-org/gitlab-test/-/issues',
jiraIntegrationPath: 'gitlab-org/gitlab-test/-/services/jira/edit',
projectPath: 'gitlab-org/gitlab-test',
......@@ -35,12 +34,7 @@ const mountComponent = ({
showAlert,
selectedProject,
jiraImportDetails: {
projects: [
{ text: 'My Jira Project (MJP)', value: 'MJP' },
{ text: 'My Second Jira Project (MSJP)', value: 'MSJP' },
{ text: 'Migrate to GitLab (MTG)', value: 'MTG' },
],
status,
isInProgress,
imports: [
{
jiraProjectKey: 'MTG',
......@@ -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', () => {
describe('when Jira integration is configured but import is in progress', () => {
beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED });
wrapper = mountComponent({ isInProgress: true });
});
it('does not show the "Set up Jira integration" screen', () => {
......@@ -184,7 +190,7 @@ describe('JiraImportApp', () => {
describe('import in progress screen', () => {
beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED });
wrapper = mountComponent({ isInProgress: true });
});
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 {
IMPORT_STATE,
isFinished,
isInProgress,
} from '~/jira_import/utils';
} from '~/jira_import/utils/jira_import_utils';
describe('isInProgress', () => {
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