Commit 3196dd37 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '215164-show-correct-import-label-and-import-count-on-jira-import-form' into 'master'

Show correct import label and import count on Jira import form

See merge request gitlab-org/gitlab!30072
parents 00605603 d4975755
<script> <script>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { GlAlert, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import last from 'lodash/last';
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';
...@@ -13,6 +14,7 @@ export default { ...@@ -13,6 +14,7 @@ export default {
components: { components: {
GlAlert, GlAlert,
GlLoadingIcon, GlLoadingIcon,
GlSprintf,
JiraImportForm, JiraImportForm,
JiraImportProgress, JiraImportProgress,
JiraImportSetup, JiraImportSetup,
...@@ -51,6 +53,7 @@ export default { ...@@ -51,6 +53,7 @@ export default {
return { return {
errorMessage: '', errorMessage: '',
showAlert: false, showAlert: false,
selectedProject: undefined,
}; };
}, },
apollo: { apollo: {
...@@ -63,7 +66,7 @@ export default { ...@@ -63,7 +66,7 @@ export default {
}, },
update: ({ project }) => ({ update: ({ project }) => ({
status: project.jiraImportStatus, status: project.jiraImportStatus,
import: project.jiraImports.nodes[0], imports: project.jiraImports.nodes,
}), }),
skip() { skip() {
return !this.isJiraConfigured; return !this.isJiraConfigured;
...@@ -77,6 +80,24 @@ export default { ...@@ -77,6 +80,24 @@ export default {
jiraProjectsOptions() { jiraProjectsOptions() {
return this.jiraProjects.map(([text, value]) => ({ text, value })); return this.jiraProjects.map(([text, value]) => ({ text, value }));
}, },
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?.(
(acc, jiraProject) => (jiraProject.jiraProjectKey === this.selectedProject ? acc + 1 : acc),
0,
);
},
importLabel() {
return this.selectedProject
? `jira-import::${this.selectedProject}-${this.numberOfPreviousImportsForProject + 1}`
: 'jira-import::KEY-1';
},
hasPreviousImports() {
return this.numberOfPreviousImportsForProject > 0;
},
}, },
methods: { methods: {
dismissAlert() { dismissAlert() {
...@@ -97,6 +118,13 @@ export default { ...@@ -97,6 +118,13 @@ export default {
return; return;
} }
const cacheData = store.readQuery({
query: getJiraImportDetailsQuery,
variables: {
fullPath: this.projectPath,
},
});
store.writeQuery({ store.writeQuery({
query: getJiraImportDetailsQuery, query: getJiraImportDetailsQuery,
variables: { variables: {
...@@ -106,7 +134,10 @@ export default { ...@@ -106,7 +134,10 @@ export default {
project: { project: {
jiraImportStatus: IMPORT_STATE.SCHEDULED, jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: { jiraImports: {
nodes: [data.jiraImportStart.jiraImport], nodes: [
...cacheData.project.jiraImports.nodes,
data.jiraImportStart.jiraImport,
],
__typename: 'JiraImportConnection', __typename: 'JiraImportConnection',
}, },
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
...@@ -119,6 +150,8 @@ export default { ...@@ -119,6 +150,8 @@ export default {
.then(({ data }) => { .then(({ data }) => {
if (data.jiraImportStart.errors.length) { if (data.jiraImportStart.errors.length) {
this.setAlertMessage(data.jiraImportStart.errors.join('. ')); this.setAlertMessage(data.jiraImportStart.errors.join('. '));
} else {
this.selectedProject = undefined;
} }
}) })
.catch(() => this.setAlertMessage(__('There was an error importing the Jira project.'))); .catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')));
...@@ -136,6 +169,19 @@ export default { ...@@ -136,6 +169,19 @@ export default {
<gl-alert v-if="showAlert" variant="danger" @dismiss="dismissAlert"> <gl-alert v-if="showAlert" variant="danger" @dismiss="dismissAlert">
{{ errorMessage }} {{ errorMessage }}
</gl-alert> </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>
</gl-alert>
<jira-import-setup <jira-import-setup
v-if="!isJiraConfigured" v-if="!isJiraConfigured"
...@@ -146,13 +192,15 @@ export default { ...@@ -146,13 +192,15 @@ export default {
<jira-import-progress <jira-import-progress
v-else-if="isImportInProgress" v-else-if="isImportInProgress"
:illustration="inProgressIllustration" :illustration="inProgressIllustration"
:import-initiator="jiraImportDetails.import.scheduledBy.name" :import-initiator="mostRecentImport.scheduledBy.name"
:import-project="jiraImportDetails.import.jiraProjectKey" :import-project="mostRecentImport.jiraProjectKey"
:import-time="jiraImportDetails.import.scheduledAt" :import-time="mostRecentImport.scheduledAt"
:issues-path="issuesPath" :issues-path="issuesPath"
/> />
<jira-import-form <jira-import-form
v-else v-else
v-model="selectedProject"
:import-label="importLabel"
:issues-path="issuesPath" :issues-path="issuesPath"
:jira-projects="jiraProjectsOptions" :jira-projects="jiraProjectsOptions"
@initiateJiraImport="initiateJiraImport" @initiateJiraImport="initiateJiraImport"
......
...@@ -13,6 +13,10 @@ export default { ...@@ -13,6 +13,10 @@ export default {
currentUserAvatarUrl: gon.current_user_avatar_url, currentUserAvatarUrl: gon.current_user_avatar_url,
currentUsername: gon.current_username, currentUsername: gon.current_username,
props: { props: {
importLabel: {
type: String,
required: true,
},
issuesPath: { issuesPath: {
type: String, type: String,
required: true, required: true,
...@@ -21,21 +25,25 @@ export default { ...@@ -21,21 +25,25 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
value: {
type: String,
required: false,
default: undefined,
},
}, },
data() { data() {
return { return {
selectedOption: null,
selectState: null, selectState: null,
}; };
}, },
methods: { methods: {
initiateJiraImport(event) { initiateJiraImport(event) {
event.preventDefault(); event.preventDefault();
if (!this.selectedOption) { if (this.value) {
this.showValidationError();
} else {
this.hideValidationError(); this.hideValidationError();
this.$emit('initiateJiraImport', this.selectedOption); this.$emit('initiateJiraImport', this.value);
} else {
this.showValidationError();
} }
}, },
hideValidationError() { hideValidationError() {
...@@ -62,10 +70,11 @@ export default { ...@@ -62,10 +70,11 @@ export default {
> >
<gl-form-select <gl-form-select
id="jira-project-select" id="jira-project-select"
v-model="selectedOption"
class="mb-2" class="mb-2"
:options="jiraProjects" :options="jiraProjects"
:state="selectState" :state="selectState"
:value="value"
@change="$emit('input', $event)"
/> />
</gl-form-group> </gl-form-group>
...@@ -79,7 +88,7 @@ export default { ...@@ -79,7 +88,7 @@ export default {
id="jira-project-label" id="jira-project-label"
class="mb-2" class="mb-2"
background-color="#428BCA" background-color="#428BCA"
title="jira-import::KEY-1" :title="importLabel"
scoped scoped
/> />
</gl-form-group> </gl-form-group>
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
query($fullPath: ID!) { query($fullPath: ID!) {
project(fullPath: $fullPath) { project(fullPath: $fullPath) {
jiraImportStatus jiraImportStatus
jiraImports(last: 1) { jiraImports {
nodes { nodes {
...JiraImport ...JiraImport
} }
......
---
title: Show correct label and count on Jira import form
merge_request: 30072
author:
type: changed
...@@ -24096,6 +24096,9 @@ msgstr "" ...@@ -24096,6 +24096,9 @@ 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."
msgstr ""
msgid "You have no permissions" msgid "You have no permissions"
msgstr "" msgstr ""
......
import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue'; import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue'; import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
...@@ -11,12 +11,16 @@ import { IMPORT_STATE } from '~/jira_import/utils'; ...@@ -11,12 +11,16 @@ import { IMPORT_STATE } from '~/jira_import/utils';
const mountComponent = ({ const mountComponent = ({
isJiraConfigured = true, isJiraConfigured = true,
errorMessage = '', errorMessage = '',
showAlert = true, selectedProject = 'MTG',
showAlert = false,
status = IMPORT_STATE.NONE, status = IMPORT_STATE.NONE,
loading = false, loading = false,
mutate = jest.fn(() => Promise.resolve()), mutate = jest.fn(() => Promise.resolve()),
} = {}) => mountType,
shallowMount(JiraImportApp, { } = {}) => {
const mountFunction = mountType === 'mount' ? mount : shallowMount;
return mountFunction(JiraImportApp, {
propsData: { propsData: {
isJiraConfigured, isJiraConfigured,
inProgressIllustration: 'in-progress-illustration.svg', inProgressIllustration: 'in-progress-illustration.svg',
...@@ -34,15 +38,32 @@ const mountComponent = ({ ...@@ -34,15 +38,32 @@ const mountComponent = ({
return { return {
errorMessage, errorMessage,
showAlert, showAlert,
selectedProject,
jiraImportDetails: { jiraImportDetails: {
status, status,
import: { imports: [
{
jiraProjectKey: 'MTG', jiraProjectKey: 'MTG',
scheduledAt: '2020-04-08T12:17:25+00:00', scheduledAt: '2020-04-08T10:11:12+00:00',
scheduledBy: {
name: 'John Doe',
},
},
{
jiraProjectKey: 'MSJP',
scheduledAt: '2020-04-09T13:14:15+00:00',
scheduledBy: {
name: 'Jimmy Doe',
},
},
{
jiraProjectKey: 'MTG',
scheduledAt: '2020-04-09T16:17:18+00:00',
scheduledBy: { scheduledBy: {
name: 'Jane Doe', name: 'Jane Doe',
}, },
}, },
],
}, },
}; };
}, },
...@@ -53,6 +74,7 @@ const mountComponent = ({ ...@@ -53,6 +74,7 @@ const mountComponent = ({
}, },
}, },
}); });
};
describe('JiraImportApp', () => { describe('JiraImportApp', () => {
let wrapper; let wrapper;
...@@ -160,6 +182,64 @@ describe('JiraImportApp', () => { ...@@ -160,6 +182,64 @@ describe('JiraImportApp', () => {
}); });
}); });
describe('import in progress screen', () => {
beforeEach(() => {
wrapper = mountComponent({ status: IMPORT_STATE.SCHEDULED });
});
it('shows the illustration', () => {
expect(getProgressComponent().props('illustration')).toBe('in-progress-illustration.svg');
});
it('shows the name of the most recent import initiator', () => {
expect(getProgressComponent().props('importInitiator')).toBe('Jane Doe');
});
it('shows the name of the most recent imported project', () => {
expect(getProgressComponent().props('importProject')).toBe('MTG');
});
it('shows the time of the most recent import', () => {
expect(getProgressComponent().props('importTime')).toBe('2020-04-09T16:17:18+00:00');
});
it('has the path to the issues page', () => {
expect(getProgressComponent().props('issuesPath')).toBe('gitlab-org/gitlab-test/-/issues');
});
});
describe('jira import form screen', () => {
describe('when selected project has been imported before', () => {
it('shows jira-import::MTG-3 label since project MTG has been imported 2 time before', () => {
wrapper = mountComponent();
expect(getFormComponent().props('importLabel')).toBe('jira-import::MTG-3');
});
it('shows warning alert to explain project MTG has been imported 2 times before', () => {
wrapper = mountComponent({ mountType: 'mount' });
expect(getAlert().text()).toBe(
'You have imported from this project 2 times before. Each new import will create duplicate issues.',
);
});
});
describe('when selected project has not been imported before', () => {
beforeEach(() => {
wrapper = mountComponent({ selectedProject: 'MJP' });
});
it('shows jira-import::MJP-1 label since project MJP has not been imported before', () => {
expect(getFormComponent().props('importLabel')).toBe('jira-import::MJP-1');
});
it('does not show warning alert since project MJP has not been imported before', () => {
expect(getAlert().exists()).toBe(false);
});
});
});
describe('initiating a Jira import', () => { describe('initiating a Jira import', () => {
it('calls the mutation with the expected arguments', () => { it('calls the mutation with the expected arguments', () => {
const mutate = jest.fn(() => Promise.resolve()); const mutate = jest.fn(() => Promise.resolve());
...@@ -201,6 +281,7 @@ describe('JiraImportApp', () => { ...@@ -201,6 +281,7 @@ describe('JiraImportApp', () => {
wrapper = mountComponent({ wrapper = mountComponent({
errorMessage: 'There was an error importing the Jira project.', errorMessage: 'There was an error importing the Jira project.',
showAlert: true, showAlert: true,
selectedProject: null,
}); });
expect(getAlert().exists()).toBe(true); expect(getAlert().exists()).toBe(true);
......
...@@ -2,11 +2,15 @@ import { GlAvatar, GlButton, GlFormSelect, GlLabel } from '@gitlab/ui'; ...@@ -2,11 +2,15 @@ import { GlAvatar, GlButton, GlFormSelect, GlLabel } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue'; import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
const importLabel = 'jira-import::MTG-1';
const value = 'MTG';
const mountComponent = ({ mountType } = {}) => { const mountComponent = ({ mountType } = {}) => {
const mountFunction = mountType === 'mount' ? mount : shallowMount; const mountFunction = mountType === 'mount' ? mount : shallowMount;
return mountFunction(JiraImportForm, { return mountFunction(JiraImportForm, {
propsData: { propsData: {
importLabel,
issuesPath: 'gitlab-org/gitlab-test/-/issues', issuesPath: 'gitlab-org/gitlab-test/-/issues',
jiraProjects: [ jiraProjects: [
{ {
...@@ -22,6 +26,7 @@ const mountComponent = ({ mountType } = {}) => { ...@@ -22,6 +26,7 @@ const mountComponent = ({ mountType } = {}) => {
value: 'MTG', value: 'MTG',
}, },
], ],
value,
}, },
}); });
}; };
...@@ -29,6 +34,8 @@ const mountComponent = ({ mountType } = {}) => { ...@@ -29,6 +34,8 @@ const mountComponent = ({ mountType } = {}) => {
describe('JiraImportForm', () => { describe('JiraImportForm', () => {
let wrapper; let wrapper;
const getSelectDropdown = () => wrapper.find(GlFormSelect);
const getCancelButton = () => wrapper.findAll(GlButton).at(1); const getCancelButton = () => wrapper.findAll(GlButton).at(1);
afterEach(() => { afterEach(() => {
...@@ -40,7 +47,7 @@ describe('JiraImportForm', () => { ...@@ -40,7 +47,7 @@ describe('JiraImportForm', () => {
it('is shown', () => { it('is shown', () => {
wrapper = mountComponent(); wrapper = mountComponent();
expect(wrapper.find(GlFormSelect).exists()).toBe(true); expect(wrapper.contains(GlFormSelect)).toBe(true);
}); });
it('contains a list of Jira projects to select from', () => { it('contains a list of Jira projects to select from', () => {
...@@ -48,8 +55,7 @@ describe('JiraImportForm', () => { ...@@ -48,8 +55,7 @@ describe('JiraImportForm', () => {
const optionItems = ['My Jira Project', 'My Second Jira Project', 'Migrate to GitLab']; const optionItems = ['My Jira Project', 'My Second Jira Project', 'Migrate to GitLab'];
wrapper getSelectDropdown()
.find(GlFormSelect)
.findAll('option') .findAll('option')
.wrappers.forEach((optionEl, index) => { .wrappers.forEach((optionEl, index) => {
expect(optionEl.text()).toBe(optionItems[index]); expect(optionEl.text()).toBe(optionItems[index]);
...@@ -63,7 +69,7 @@ describe('JiraImportForm', () => { ...@@ -63,7 +69,7 @@ describe('JiraImportForm', () => {
}); });
it('shows a label which will be applied to imported Jira projects', () => { it('shows a label which will be applied to imported Jira projects', () => {
expect(wrapper.find(GlLabel).attributes('title')).toBe('jira-import::KEY-1'); expect(wrapper.find(GlLabel).props('title')).toBe(importLabel);
}); });
it('shows information to the user', () => { it('shows information to the user', () => {
...@@ -77,7 +83,7 @@ describe('JiraImportForm', () => { ...@@ -77,7 +83,7 @@ describe('JiraImportForm', () => {
}); });
it('shows an avatar for the Reporter', () => { it('shows an avatar for the Reporter', () => {
expect(wrapper.find(GlAvatar).exists()).toBe(true); expect(wrapper.contains(GlAvatar)).toBe(true);
}); });
it('shows jira.issue.description.content for the Description', () => { it('shows jira.issue.description.content for the Description', () => {
...@@ -111,16 +117,19 @@ describe('JiraImportForm', () => { ...@@ -111,16 +117,19 @@ describe('JiraImportForm', () => {
}); });
}); });
it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => { it('emits an "input" event when the input select value changes', () => {
const selectedOption = 'MTG'; wrapper = mountComponent({ mountType: 'mount' });
wrapper = mountComponent(); getSelectDropdown().vm.$emit('change', value);
wrapper.setData({
selectedOption, expect(wrapper.emitted('input')[0]).toEqual([value]);
}); });
it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
wrapper = mountComponent();
wrapper.find('form').trigger('submit'); wrapper.find('form').trigger('submit');
expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([selectedOption]); expect(wrapper.emitted('initiateJiraImport')[0]).toEqual([value]);
}); });
}); });
...@@ -9,7 +9,7 @@ const issuesPath = 'gitlab-org/gitlab-test/-/issues'; ...@@ -9,7 +9,7 @@ const issuesPath = 'gitlab-org/gitlab-test/-/issues';
describe('JiraImportProgress', () => { describe('JiraImportProgress', () => {
let wrapper; let wrapper;
const getGlEmptyStateAttribute = attribute => wrapper.find(GlEmptyState).attributes(attribute); const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
const getParagraphText = () => wrapper.find('p').text(); const getParagraphText = () => wrapper.find('p').text();
...@@ -37,21 +37,21 @@ describe('JiraImportProgress', () => { ...@@ -37,21 +37,21 @@ describe('JiraImportProgress', () => {
}); });
it('contains illustration', () => { it('contains illustration', () => {
expect(getGlEmptyStateAttribute('svgpath')).toBe(illustration); expect(getGlEmptyStateProp('svgPath')).toBe(illustration);
}); });
it('contains a title', () => { it('contains a title', () => {
const title = 'Import in progress'; const title = 'Import in progress';
expect(getGlEmptyStateAttribute('title')).toBe(title); expect(getGlEmptyStateProp('title')).toBe(title);
}); });
it('contains button text', () => { it('contains button text', () => {
expect(getGlEmptyStateAttribute('primarybuttontext')).toBe('View issues'); expect(getGlEmptyStateProp('primaryButtonText')).toBe('View issues');
}); });
it('contains button url', () => { it('contains button url', () => {
const expected = `${issuesPath}?search=${importProject}`; const expected = `${issuesPath}?search=${importProject}`;
expect(getGlEmptyStateAttribute('primarybuttonlink')).toBe(expected); expect(getGlEmptyStateProp('primaryButtonLink')).toBe(expected);
}); });
}); });
......
...@@ -8,7 +8,7 @@ const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit'; ...@@ -8,7 +8,7 @@ const jiraIntegrationPath = 'gitlab-org/gitlab-test/-/services/jira/edit';
describe('JiraImportSetup', () => { describe('JiraImportSetup', () => {
let wrapper; let wrapper;
const getGlEmptyStateAttribute = attribute => wrapper.find(GlEmptyState).attributes(attribute); const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
beforeEach(() => { beforeEach(() => {
wrapper = shallowMount(JiraImportSetup, { wrapper = shallowMount(JiraImportSetup, {
...@@ -25,19 +25,19 @@ describe('JiraImportSetup', () => { ...@@ -25,19 +25,19 @@ describe('JiraImportSetup', () => {
}); });
it('contains illustration', () => { it('contains illustration', () => {
expect(getGlEmptyStateAttribute('svgpath')).toBe(illustration); expect(getGlEmptyStateProp('svgPath')).toBe(illustration);
}); });
it('contains a description', () => { it('contains a description', () => {
const description = 'You will first need to set up Jira Integration to use this feature.'; const description = 'You will first need to set up Jira Integration to use this feature.';
expect(getGlEmptyStateAttribute('description')).toBe(description); expect(getGlEmptyStateProp('description')).toBe(description);
}); });
it('contains button text', () => { it('contains button text', () => {
expect(getGlEmptyStateAttribute('primarybuttontext')).toBe('Set up Jira Integration'); expect(getGlEmptyStateProp('primaryButtonText')).toBe('Set up Jira Integration');
}); });
it('contains button link', () => { it('contains button link', () => {
expect(getGlEmptyStateAttribute('primarybuttonlink')).toBe(jiraIntegrationPath); expect(getGlEmptyStateProp('primaryButtonLink')).toBe(jiraIntegrationPath);
}); });
}); });
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