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>
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { GlAlert, GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import last from 'lodash/last';
import { __ } from '~/locale';
import getJiraImportDetailsQuery from '../queries/get_jira_import_details.query.graphql';
import initiateJiraImportMutation from '../queries/initiate_jira_import.mutation.graphql';
......@@ -13,6 +14,7 @@ export default {
components: {
GlAlert,
GlLoadingIcon,
GlSprintf,
JiraImportForm,
JiraImportProgress,
JiraImportSetup,
......@@ -51,6 +53,7 @@ export default {
return {
errorMessage: '',
showAlert: false,
selectedProject: undefined,
};
},
apollo: {
......@@ -63,7 +66,7 @@ export default {
},
update: ({ project }) => ({
status: project.jiraImportStatus,
import: project.jiraImports.nodes[0],
imports: project.jiraImports.nodes,
}),
skip() {
return !this.isJiraConfigured;
......@@ -77,6 +80,24 @@ export default {
jiraProjectsOptions() {
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: {
dismissAlert() {
......@@ -97,6 +118,13 @@ export default {
return;
}
const cacheData = store.readQuery({
query: getJiraImportDetailsQuery,
variables: {
fullPath: this.projectPath,
},
});
store.writeQuery({
query: getJiraImportDetailsQuery,
variables: {
......@@ -106,7 +134,10 @@ export default {
project: {
jiraImportStatus: IMPORT_STATE.SCHEDULED,
jiraImports: {
nodes: [data.jiraImportStart.jiraImport],
nodes: [
...cacheData.project.jiraImports.nodes,
data.jiraImportStart.jiraImport,
],
__typename: 'JiraImportConnection',
},
// eslint-disable-next-line @gitlab/require-i18n-strings
......@@ -119,6 +150,8 @@ export default {
.then(({ data }) => {
if (data.jiraImportStart.errors.length) {
this.setAlertMessage(data.jiraImportStart.errors.join('. '));
} else {
this.selectedProject = undefined;
}
})
.catch(() => this.setAlertMessage(__('There was an error importing the Jira project.')));
......@@ -136,6 +169,19 @@ export default {
<gl-alert v-if="showAlert" variant="danger" @dismiss="dismissAlert">
{{ 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>
</gl-alert>
<jira-import-setup
v-if="!isJiraConfigured"
......@@ -146,13 +192,15 @@ export default {
<jira-import-progress
v-else-if="isImportInProgress"
:illustration="inProgressIllustration"
:import-initiator="jiraImportDetails.import.scheduledBy.name"
:import-project="jiraImportDetails.import.jiraProjectKey"
:import-time="jiraImportDetails.import.scheduledAt"
:import-initiator="mostRecentImport.scheduledBy.name"
:import-project="mostRecentImport.jiraProjectKey"
:import-time="mostRecentImport.scheduledAt"
:issues-path="issuesPath"
/>
<jira-import-form
v-else
v-model="selectedProject"
:import-label="importLabel"
:issues-path="issuesPath"
:jira-projects="jiraProjectsOptions"
@initiateJiraImport="initiateJiraImport"
......
......@@ -13,6 +13,10 @@ export default {
currentUserAvatarUrl: gon.current_user_avatar_url,
currentUsername: gon.current_username,
props: {
importLabel: {
type: String,
required: true,
},
issuesPath: {
type: String,
required: true,
......@@ -21,21 +25,25 @@ export default {
type: Array,
required: true,
},
value: {
type: String,
required: false,
default: undefined,
},
},
data() {
return {
selectedOption: null,
selectState: null,
};
},
methods: {
initiateJiraImport(event) {
event.preventDefault();
if (!this.selectedOption) {
this.showValidationError();
} else {
if (this.value) {
this.hideValidationError();
this.$emit('initiateJiraImport', this.selectedOption);
this.$emit('initiateJiraImport', this.value);
} else {
this.showValidationError();
}
},
hideValidationError() {
......@@ -62,10 +70,11 @@ export default {
>
<gl-form-select
id="jira-project-select"
v-model="selectedOption"
class="mb-2"
:options="jiraProjects"
:state="selectState"
:value="value"
@change="$emit('input', $event)"
/>
</gl-form-group>
......@@ -79,7 +88,7 @@ export default {
id="jira-project-label"
class="mb-2"
background-color="#428BCA"
title="jira-import::KEY-1"
:title="importLabel"
scoped
/>
</gl-form-group>
......
......@@ -3,7 +3,7 @@
query($fullPath: ID!) {
project(fullPath: $fullPath) {
jiraImportStatus
jiraImports(last: 1) {
jiraImports {
nodes {
...JiraImport
}
......
---
title: Show correct label and count on Jira import form
merge_request: 30072
author:
type: changed
......@@ -24096,6 +24096,9 @@ 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."
msgstr ""
msgid "You have no permissions"
msgstr ""
......
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mount, shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import JiraImportApp from '~/jira_import/components/jira_import_app.vue';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
......@@ -11,12 +11,16 @@ import { IMPORT_STATE } from '~/jira_import/utils';
const mountComponent = ({
isJiraConfigured = true,
errorMessage = '',
showAlert = true,
selectedProject = 'MTG',
showAlert = false,
status = IMPORT_STATE.NONE,
loading = false,
mutate = jest.fn(() => Promise.resolve()),
} = {}) =>
shallowMount(JiraImportApp, {
mountType,
} = {}) => {
const mountFunction = mountType === 'mount' ? mount : shallowMount;
return mountFunction(JiraImportApp, {
propsData: {
isJiraConfigured,
inProgressIllustration: 'in-progress-illustration.svg',
......@@ -34,15 +38,32 @@ const mountComponent = ({
return {
errorMessage,
showAlert,
selectedProject,
jiraImportDetails: {
status,
import: {
imports: [
{
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: {
name: 'Jane Doe',
},
},
],
},
};
},
......@@ -53,6 +74,7 @@ const mountComponent = ({
},
},
});
};
describe('JiraImportApp', () => {
let wrapper;
......@@ -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', () => {
it('calls the mutation with the expected arguments', () => {
const mutate = jest.fn(() => Promise.resolve());
......@@ -201,6 +281,7 @@ describe('JiraImportApp', () => {
wrapper = mountComponent({
errorMessage: 'There was an error importing the Jira project.',
showAlert: true,
selectedProject: null,
});
expect(getAlert().exists()).toBe(true);
......
......@@ -2,11 +2,15 @@ import { GlAvatar, GlButton, GlFormSelect, GlLabel } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import JiraImportForm from '~/jira_import/components/jira_import_form.vue';
const importLabel = 'jira-import::MTG-1';
const value = 'MTG';
const mountComponent = ({ mountType } = {}) => {
const mountFunction = mountType === 'mount' ? mount : shallowMount;
return mountFunction(JiraImportForm, {
propsData: {
importLabel,
issuesPath: 'gitlab-org/gitlab-test/-/issues',
jiraProjects: [
{
......@@ -22,6 +26,7 @@ const mountComponent = ({ mountType } = {}) => {
value: 'MTG',
},
],
value,
},
});
};
......@@ -29,6 +34,8 @@ const mountComponent = ({ mountType } = {}) => {
describe('JiraImportForm', () => {
let wrapper;
const getSelectDropdown = () => wrapper.find(GlFormSelect);
const getCancelButton = () => wrapper.findAll(GlButton).at(1);
afterEach(() => {
......@@ -40,7 +47,7 @@ describe('JiraImportForm', () => {
it('is shown', () => {
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', () => {
......@@ -48,8 +55,7 @@ describe('JiraImportForm', () => {
const optionItems = ['My Jira Project', 'My Second Jira Project', 'Migrate to GitLab'];
wrapper
.find(GlFormSelect)
getSelectDropdown()
.findAll('option')
.wrappers.forEach((optionEl, index) => {
expect(optionEl.text()).toBe(optionItems[index]);
......@@ -63,7 +69,7 @@ describe('JiraImportForm', () => {
});
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', () => {
......@@ -77,7 +83,7 @@ describe('JiraImportForm', () => {
});
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', () => {
......@@ -111,16 +117,19 @@ describe('JiraImportForm', () => {
});
});
it('emits an "initiateJiraImport" event with the selected dropdown value when submitted', () => {
const selectedOption = 'MTG';
it('emits an "input" event when the input select value changes', () => {
wrapper = mountComponent({ mountType: 'mount' });
wrapper = mountComponent();
wrapper.setData({
selectedOption,
getSelectDropdown().vm.$emit('change', value);
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');
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';
describe('JiraImportProgress', () => {
let wrapper;
const getGlEmptyStateAttribute = attribute => wrapper.find(GlEmptyState).attributes(attribute);
const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
const getParagraphText = () => wrapper.find('p').text();
......@@ -37,21 +37,21 @@ describe('JiraImportProgress', () => {
});
it('contains illustration', () => {
expect(getGlEmptyStateAttribute('svgpath')).toBe(illustration);
expect(getGlEmptyStateProp('svgPath')).toBe(illustration);
});
it('contains a title', () => {
const title = 'Import in progress';
expect(getGlEmptyStateAttribute('title')).toBe(title);
expect(getGlEmptyStateProp('title')).toBe(title);
});
it('contains button text', () => {
expect(getGlEmptyStateAttribute('primarybuttontext')).toBe('View issues');
expect(getGlEmptyStateProp('primaryButtonText')).toBe('View issues');
});
it('contains button url', () => {
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';
describe('JiraImportSetup', () => {
let wrapper;
const getGlEmptyStateAttribute = attribute => wrapper.find(GlEmptyState).attributes(attribute);
const getGlEmptyStateProp = attribute => wrapper.find(GlEmptyState).props(attribute);
beforeEach(() => {
wrapper = shallowMount(JiraImportSetup, {
......@@ -25,19 +25,19 @@ describe('JiraImportSetup', () => {
});
it('contains illustration', () => {
expect(getGlEmptyStateAttribute('svgpath')).toBe(illustration);
expect(getGlEmptyStateProp('svgPath')).toBe(illustration);
});
it('contains a description', () => {
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', () => {
expect(getGlEmptyStateAttribute('primarybuttontext')).toBe('Set up Jira Integration');
expect(getGlEmptyStateProp('primaryButtonText')).toBe('Set up Jira Integration');
});
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