Commit c3ea7524 authored by Illya Klymov's avatar Illya Klymov Committed by Ezekiel Kigbo

Add modal warning and count when importing multiple projects

* Display confirmation modal to prevent mis-clicks
* Display proper count
parent 005b5b1c
<script>
import { throttle } from 'lodash';
import { mapActions, mapState, mapGetters } from 'vuex';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import { GlButton, GlLoadingIcon, GlModal } from '@gitlab/ui';
import { n__, __, sprintf } from '~/locale';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
import ProviderRepoTableRow from './provider_repo_table_row.vue';
import PageQueryParamSync from './page_query_param_sync.vue';
......@@ -16,6 +16,7 @@ export default {
PageQueryParamSync,
GlLoadingIcon,
GlButton,
GlModal,
PaginationLinks,
},
props: {
......@@ -42,6 +43,7 @@ export default {
'isImportingAnyRepo',
'hasImportableRepos',
'hasIncompatibleRepos',
'importAllCount',
]),
availableNamespaces() {
......@@ -61,8 +63,12 @@ export default {
importAllButtonText() {
return this.hasIncompatibleRepos
? __('Import all compatible repositories')
: __('Import all repositories');
? n__(
'Import %d compatible repository',
'Import %d compatible repositories',
this.importAllCount,
)
: n__('Import %d repository', 'Import %d repositories', this.importAllCount);
},
emptyStateText() {
......@@ -111,9 +117,8 @@ export default {
<template>
<div>
<page-query-param-sync :page="pageInfo.page" @popstate="setPage" />
<p class="light text-nowrap mt-2">
{{ s__('ImportProjects|Select the projects you want to import') }}
{{ s__('ImportProjects|Select the repositories you want to import') }}
</p>
<template v-if="hasIncompatibleRepos">
<slot name="incompatible-repos-warning"></slot>
......@@ -130,9 +135,25 @@ export default {
:loading="isImportingAnyRepo"
:disabled="!hasImportableRepos"
type="button"
@click="importAll"
@click="$refs.importAllModal.show()"
>{{ importAllButtonText }}</gl-button
>
<gl-modal
ref="importAllModal"
modal-id="import-all-modal"
:title="s__('ImportProjects|Import repositories')"
:ok-title="__('Import')"
@ok="importAll"
>
{{
n__(
'Are you sure you want to import %d repository?',
'Are you sure you want to import %d repositories?',
importAllCount,
)
}}
</gl-modal>
<slot name="actions"></slot>
<form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent>
<input
......@@ -140,7 +161,7 @@ export default {
data-qa-selector="githubish_import_filter_field"
class="form-control"
name="filter"
:placeholder="__('Filter your projects by name')"
:placeholder="__('Filter your repositories by name')"
autofocus
size="40"
@input="handleFilterInput($event)"
......
......@@ -14,6 +14,8 @@ export const hasIncompatibleRepos = state => state.repositories.some(isIncompati
export const hasImportableRepos = state => state.repositories.some(isProjectImportable);
export const importAllCount = state => state.repositories.filter(isProjectImportable).length;
export const getImportTarget = state => repoId => {
if (state.customImportTargets[repoId]) {
return state.customImportTargets[repoId];
......
---
title: Add confirmation dialog when importing multiple projects
merge_request: 41306
author:
type: changed
......@@ -3268,6 +3268,11 @@ msgstr ""
msgid "Are you sure you want to erase this build?"
msgstr ""
msgid "Are you sure you want to import %d repository?"
msgid_plural "Are you sure you want to import %d repositories?"
msgstr[0] ""
msgstr[1] ""
msgid "Are you sure you want to lose unsaved changes?"
msgstr ""
......@@ -10965,7 +10970,7 @@ msgstr ""
msgid "Filter results..."
msgstr ""
msgid "Filter your projects by name"
msgid "Filter your repositories by name"
msgstr ""
msgid "Filter..."
......@@ -13012,6 +13017,16 @@ msgstr ""
msgid "Import"
msgstr ""
msgid "Import %d compatible repository"
msgid_plural "Import %d compatible repositories"
msgstr[0] ""
msgstr[1] ""
msgid "Import %d repository"
msgid_plural "Import %d repositories"
msgstr[0] ""
msgstr[1] ""
msgid "Import CSV"
msgstr ""
......@@ -13021,15 +13036,9 @@ msgstr ""
msgid "Import all compatible projects"
msgstr ""
msgid "Import all compatible repositories"
msgstr ""
msgid "Import all projects"
msgstr ""
msgid "Import all repositories"
msgstr ""
msgid "Import an exported GitLab project"
msgstr ""
......@@ -13117,6 +13126,9 @@ msgstr ""
msgid "ImportProjects|Error importing repository %{project_safe_import_url} into %{project_full_path} - %{message}"
msgstr ""
msgid "ImportProjects|Import repositories"
msgstr ""
msgid "ImportProjects|Importing the project failed"
msgstr ""
......@@ -13129,7 +13141,7 @@ msgstr ""
msgid "ImportProjects|Requesting your %{provider} repositories failed"
msgstr ""
msgid "ImportProjects|Select the projects you want to import"
msgid "ImportProjects|Select the repositories you want to import"
msgstr ""
msgid "ImportProjects|The remote data could not be imported."
......
......@@ -20,7 +20,7 @@ RSpec.describe 'Import multiple repositories by uploading a manifest file', :js
attach_file('manifest', Rails.root.join('spec/fixtures/aosp_manifest.xml'))
click_on 'List available repositories'
expect(page).to have_button('Import all repositories')
expect(page).to have_button('Import 660 repositories')
expect(page).to have_content('https://android-review.googlesource.com/platform/build/blueprint')
end
......
......@@ -16,15 +16,24 @@ describe('ImportProjectsTable', () => {
wrapper.find('input[data-qa-selector="githubish_import_filter_field"]');
const providerTitle = 'THE PROVIDER';
const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
const providerRepo = {
importSource: {
id: 10,
sanitizedName: 'sanitizedName',
fullName: 'fullName',
},
importedProject: null,
};
const findImportAllButton = () =>
wrapper
.findAll(GlButton)
.filter(w => w.props().variant === 'success')
.at(0);
const findImportAllModal = () => wrapper.find({ ref: 'importAllModal' });
const importAllFn = jest.fn();
const importAllModalShowFn = jest.fn();
const setPageFn = jest.fn();
function createComponent({
......@@ -64,6 +73,9 @@ describe('ImportProjectsTable', () => {
paginatable,
},
slots,
stubs: {
GlModal: { template: '<div>Modal!</div>', methods: { show: importAllModalShowFn } },
},
});
}
......@@ -110,18 +122,21 @@ describe('ImportProjectsTable', () => {
});
it.each`
hasIncompatibleRepos | buttonText
${false} | ${'Import all repositories'}
${true} | ${'Import all compatible repositories'}
hasIncompatibleRepos | count | buttonText
${false} | ${1} | ${'Import 1 repository'}
${true} | ${1} | ${'Import 1 compatible repository'}
${false} | ${5} | ${'Import 5 repositories'}
${true} | ${5} | ${'Import 5 compatible repositories'}
`(
'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos',
({ hasIncompatibleRepos, buttonText }) => {
'import all button has "$buttonText" text when hasIncompatibleRepos is $hasIncompatibleRepos and repos count is $count',
({ hasIncompatibleRepos, buttonText, count }) => {
createComponent({
state: {
providerRepos: [providerRepo],
},
getters: {
hasIncompatibleRepos: () => hasIncompatibleRepos,
importAllCount: () => count,
},
});
......@@ -129,19 +144,28 @@ describe('ImportProjectsTable', () => {
},
);
it('renders an empty state if there are no projects available', () => {
it('renders an empty state if there are no repositories available', () => {
createComponent({ state: { repositories: [] } });
expect(wrapper.find(ProviderRepoTableRow).exists()).toBe(false);
expect(wrapper.text()).toContain(`No ${providerTitle} repositories found`);
});
it('sends importAll event when import button is clicked', async () => {
createComponent({ state: { providerRepos: [providerRepo] } });
it('opens confirmation modal when import all button is clicked', async () => {
createComponent({ state: { repositories: [providerRepo] } });
findImportAllButton().vm.$emit('click');
await nextTick();
expect(importAllModalShowFn).toHaveBeenCalled();
});
it('triggers importAll action when modal is confirmed', async () => {
createComponent({ state: { providerRepos: [providerRepo] } });
findImportAllModal().vm.$emit('ok');
await nextTick();
expect(importAllFn).toHaveBeenCalled();
});
......
......@@ -3,6 +3,7 @@ import {
isImportingAnyRepo,
hasIncompatibleRepos,
hasImportableRepos,
importAllCount,
getImportTarget,
} from '~/import_projects/store/getters';
import { STATUSES } from '~/import_projects/constants';
......@@ -97,6 +98,19 @@ describe('import_projects store getters', () => {
});
});
describe('importAllCount', () => {
it('returns count of available importable projects ', () => {
localState.repositories = [
IMPORTABLE_REPO,
IMPORTABLE_REPO,
IMPORTED_REPO,
INCOMPATIBLE_REPO,
];
expect(importAllCount(localState)).toBe(2);
});
});
describe('getImportTarget', () => {
it('returns default value if no custom target available', () => {
localState.defaultTargetNamespace = 'default';
......
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