diff options
Diffstat (limited to 'app/assets/javascripts/import_projects/components')
6 files changed, 218 insertions, 142 deletions
diff --git a/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue b/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue index f673a0e42dc..bc8aa522596 100644 --- a/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue +++ b/app/assets/javascripts/import_projects/components/bitbucket_status_table.vue @@ -9,6 +9,7 @@ export default { GlSprintf, GlLink, }, + inheritAttrs: false, props: { providerTitle: { type: String, @@ -28,7 +29,7 @@ export default { }; </script> <template> - <import-projects-table :provider-title="providerTitle"> + <import-projects-table :provider-title="providerTitle" v-bind="$attrs"> <template #actions> <slot name="actions"></slot> </template> diff --git a/app/assets/javascripts/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_projects/components/import_projects_table.vue index 6a467fb8c6a..72fdaca7e24 100644 --- a/app/assets/javascripts/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_projects/components/import_projects_table.vue @@ -3,10 +3,12 @@ import { throttle } from 'lodash'; import { mapActions, mapState, mapGetters } from 'vuex'; import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { __, sprintf } from '~/locale'; +import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; import ImportedProjectTableRow from './imported_project_table_row.vue'; import ProviderRepoTableRow from './provider_repo_table_row.vue'; import IncompatibleRepoTableRow from './incompatible_repo_table_row.vue'; -import eventHub from '../event_hub'; +import PageQueryParamSync from './page_query_param_sync.vue'; +import { isProjectImportable } from '../utils'; const reposFetchThrottleDelay = 1000; @@ -16,8 +18,10 @@ export default { ImportedProjectTableRow, ProviderRepoTableRow, IncompatibleRepoTableRow, + PageQueryParamSync, GlLoadingIcon, GlButton, + PaginationLinks, }, props: { providerTitle: { @@ -29,23 +33,37 @@ export default { required: false, default: true, }, + paginatable: { + type: Boolean, + required: false, + default: false, + }, }, computed: { - ...mapState([ - 'importedProjects', - 'providerRepos', - 'incompatibleRepos', - 'isLoadingRepos', - 'filter', - ]), + ...mapState(['filter', 'repositories', 'namespaces', 'defaultTargetNamespace', 'pageInfo']), ...mapGetters([ + 'isLoading', 'isImportingAnyRepo', - 'hasProviderRepos', - 'hasImportedProjects', + 'hasImportableRepos', 'hasIncompatibleRepos', ]), + availableNamespaces() { + const serializedNamespaces = this.namespaces.map(({ fullPath }) => ({ + id: fullPath, + text: fullPath, + })); + + return [ + { text: __('Groups'), children: serializedNamespaces }, + { + text: __('Users'), + children: [{ id: this.defaultTargetNamespace, text: this.defaultTargetNamespace }], + }, + ]; + }, + importAllButtonText() { return this.hasIncompatibleRepos ? __('Import all compatible repositories') @@ -64,7 +82,8 @@ export default { }, mounted() { - return this.fetchRepos(); + this.fetchNamespaces(); + this.fetchRepos(); }, beforeDestroy() { @@ -75,17 +94,14 @@ export default { methods: { ...mapActions([ 'fetchRepos', - 'fetchReposFiltered', - 'fetchJobs', + 'fetchNamespaces', 'stopJobsPolling', 'clearJobsEtagPoll', 'setFilter', + 'importAll', + 'setPage', ]), - importAll() { - eventHub.$emit('importAll'); - }, - handleFilterInput({ target }) { this.setFilter(target.value); }, @@ -93,79 +109,90 @@ export default { throttledFetchRepos: throttle(function fetch() { this.fetchRepos(); }, reposFetchThrottleDelay), + + isProjectImportable, }, }; </script> <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') }} </p> <template v-if="hasIncompatibleRepos"> - <slot name="incompatible-repos-warning"> </slot> + <slot name="incompatible-repos-warning"></slot> </template> - <div - v-if="!isLoadingRepos" - class="d-flex justify-content-between align-items-end flex-wrap mb-3" - > - <gl-button - variant="success" - :loading="isImportingAnyRepo" - :disabled="!hasProviderRepos" - type="button" - @click="importAll" - > - {{ importAllButtonText }} - </gl-button> - <slot name="actions"></slot> - <form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent> - <input - :value="filter" - data-qa-selector="githubish_import_filter_field" - class="form-control" - name="filter" - :placeholder="__('Filter your projects by name')" - autofocus - size="40" - @input="handleFilterInput($event)" - @keyup.enter="throttledFetchRepos" - /> - </form> - </div> <gl-loading-icon - v-if="isLoadingRepos" + v-if="isLoading" class="js-loading-button-icon import-projects-loading-icon" size="md" /> - <div - v-else-if="hasProviderRepos || hasImportedProjects || hasIncompatibleRepos" - class="table-responsive" - > - <table class="table import-table"> - <thead> - <th class="import-jobs-from-col">{{ fromHeaderText }}</th> - <th class="import-jobs-to-col">{{ __('To GitLab') }}</th> - <th class="import-jobs-status-col">{{ __('Status') }}</th> - <th class="import-jobs-cta-col"></th> - </thead> - <tbody> - <imported-project-table-row - v-for="project in importedProjects" - :key="project.id" - :project="project" - /> - <provider-repo-table-row v-for="repo in providerRepos" :key="repo.id" :repo="repo" /> - <incompatible-repo-table-row - v-for="repo in incompatibleRepos" - :key="repo.id" - :repo="repo" + <template v-if="!isLoading"> + <div class="d-flex justify-content-between align-items-end flex-wrap mb-3"> + <gl-button + variant="success" + :loading="isImportingAnyRepo" + :disabled="!hasImportableRepos" + type="button" + @click="importAll" + >{{ importAllButtonText }}</gl-button + > + <slot name="actions"></slot> + <form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent> + <input + :value="filter" + data-qa-selector="githubish_import_filter_field" + class="form-control" + name="filter" + :placeholder="__('Filter your projects by name')" + autofocus + size="40" + @input="handleFilterInput($event)" + @keyup.enter="throttledFetchRepos" /> - </tbody> - </table> - </div> - <div v-else class="text-center"> - <strong>{{ emptyStateText }}</strong> - </div> + </form> + </div> + <div v-if="repositories.length" class="table-responsive"> + <table class="table import-table"> + <thead> + <th class="import-jobs-from-col">{{ fromHeaderText }}</th> + <th class="import-jobs-to-col">{{ __('To GitLab') }}</th> + <th class="import-jobs-status-col">{{ __('Status') }}</th> + <th class="import-jobs-cta-col"></th> + </thead> + <tbody> + <template v-for="repo in repositories"> + <incompatible-repo-table-row + v-if="repo.importSource.incompatible" + :key="repo.importSource.id" + :repo="repo" + /> + <provider-repo-table-row + v-else-if="isProjectImportable(repo)" + :key="repo.importSource.id" + :repo="repo" + :available-namespaces="availableNamespaces" + /> + <imported-project-table-row v-else :key="repo.importSource.id" :project="repo" /> + </template> + </tbody> + </table> + </div> + <div v-else class="text-center"> + <strong>{{ emptyStateText }}</strong> + </div> + <pagination-links + v-if="paginatable" + align="center" + class="gl-mt-3" + :page-info="pageInfo" + :prev-page="pageInfo.page - 1" + :next-page="repositories.length && pageInfo.page + 1" + :change="setPage" + /> + </template> </div> </template> diff --git a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue index ab2bd87ee9f..50e735b4478 100644 --- a/app/assets/javascripts/import_projects/components/imported_project_table_row.vue +++ b/app/assets/javascripts/import_projects/components/imported_project_table_row.vue @@ -1,4 +1,5 @@ <script> +import { GlIcon } from '@gitlab/ui'; import ImportStatus from './import_status.vue'; import { STATUSES } from '../constants'; @@ -6,6 +7,7 @@ export default { name: 'ImportedProjectTableRow', components: { ImportStatus, + GlIcon, }, props: { project: { @@ -16,7 +18,7 @@ export default { computed: { displayFullPath() { - return this.project.fullPath.replace(/^\//, ''); + return this.project.importedProject.fullPath.replace(/^\//, ''); }, isFinished() { @@ -27,28 +29,30 @@ export default { </script> <template> - <tr class="js-imported-project import-row"> + <tr class="import-row"> <td> <a - :href="project.providerLink" + :href="project.importSource.providerLink" rel="noreferrer noopener" target="_blank" - class="js-provider-link" - > - {{ project.importSource }} + data-testid="providerLink" + >{{ project.importSource.fullName }} + <gl-icon v-if="project.importSource.providerLink" name="external-link" /> </a> </td> - <td class="js-full-path">{{ displayFullPath }}</td> - <td><import-status :status="project.importStatus" /></td> + <td data-testid="fullPath">{{ displayFullPath }}</td> + <td> + <import-status :status="project.importStatus" /> + </td> <td> <a v-if="isFinished" - class="btn btn-default js-go-to-project" - :href="project.fullPath" + class="btn btn-default" + data-testid="goToProject" + :href="project.importedProject.fullPath" rel="noreferrer noopener" target="_blank" - > - {{ __('Go to project') }} + >{{ __('Go to project') }} </a> </td> </tr> diff --git a/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue b/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue index fa2fb439eac..3140585ccd7 100644 --- a/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue +++ b/app/assets/javascripts/import_projects/components/incompatible_repo_table_row.vue @@ -1,9 +1,10 @@ <script> -import { GlBadge } from '@gitlab/ui'; +import { GlIcon, GlBadge } from '@gitlab/ui'; export default { components: { GlBadge, + GlIcon, }, props: { repo: { @@ -17,8 +18,9 @@ export default { <template> <tr class="import-row"> <td> - <a :href="repo.providerLink" rel="noreferrer noopener" target="_blank"> - {{ repo.fullName }} + <a :href="repo.importSource.providerLink" rel="noreferrer noopener" target="_blank" + >{{ repo.importSource.fullName }} + <gl-icon v-if="repo.importSource.providerLink" name="external-link" /> </a> </td> <td></td> diff --git a/app/assets/javascripts/import_projects/components/page_query_param_sync.vue b/app/assets/javascripts/import_projects/components/page_query_param_sync.vue new file mode 100644 index 00000000000..5ba3d70f5d0 --- /dev/null +++ b/app/assets/javascripts/import_projects/components/page_query_param_sync.vue @@ -0,0 +1,39 @@ +<script> +import { queryToObject, setUrlParams, updateHistory } from '~/lib/utils/url_utility'; + +export default { + props: { + page: { + type: Number, + required: true, + }, + }, + + watch: { + page(newPage) { + updateHistory({ + url: setUrlParams({ + page: newPage === 1 ? null : newPage, + }), + }); + }, + }, + + created() { + window.addEventListener('popstate', this.updatePage); + }, + + beforeDestroy() { + window.removeEventListener('popstate', this.updatePage); + }, + + methods: { + updatePage() { + const page = parseInt(queryToObject(window.location.search).page, 10) || 1; + this.$emit('popstate', page); + }, + }, + + render: () => null, +}; +</script> diff --git a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue index 63524d61146..d8cffc6a7d5 100644 --- a/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue +++ b/app/assets/javascripts/import_projects/components/provider_repo_table_row.vue @@ -1,9 +1,8 @@ <script> import { mapState, mapGetters, mapActions } from 'vuex'; +import { GlIcon } from '@gitlab/ui'; import Select2Select from '~/vue_shared/components/select2_select.vue'; import { __ } from '~/locale'; -import eventHub from '../event_hub'; -import { STATUSES } from '../constants'; import ImportStatus from './import_status.vue'; export default { @@ -11,25 +10,26 @@ export default { components: { Select2Select, ImportStatus, + GlIcon, }, props: { repo: { type: Object, required: true, }, - }, - - data() { - return { - targetNamespace: this.$store.state.defaultTargetNamespace, - newName: this.repo.sanitizedName, - }; + availableNamespaces: { + type: Array, + required: true, + }, }, computed: { - ...mapState(['namespaces', 'reposBeingImported', 'ciCdOnly']), + ...mapState(['ciCdOnly']), + ...mapGetters(['getImportTarget']), - ...mapGetters(['namespaceSelectOptions']), + importTarget() { + return this.getImportTarget(this.repo.importSource.id); + }, importButtonText() { return this.ciCdOnly ? __('Connect') : __('Import'); @@ -37,37 +37,36 @@ export default { select2Options() { return { - data: this.namespaceSelectOptions, - containerCssClass: - 'import-namespace-select js-namespace-select qa-project-namespace-select w-auto', + data: this.availableNamespaces, + containerCssClass: 'import-namespace-select qa-project-namespace-select w-auto', }; }, - isLoadingImport() { - return this.reposBeingImported.includes(this.repo.id); + targetNamespaceSelect: { + get() { + return this.importTarget.targetNamespace; + }, + set(value) { + this.updateImportTarget({ targetNamespace: value }); + }, }, - status() { - return this.isLoadingImport ? STATUSES.SCHEDULING : STATUSES.NONE; + newNameInput: { + get() { + return this.importTarget.newName; + }, + set(value) { + this.updateImportTarget({ newName: value }); + }, }, }, - created() { - eventHub.$on('importAll', this.importRepo); - }, - - beforeDestroy() { - eventHub.$off('importAll', this.importRepo); - }, - methods: { - ...mapActions(['fetchImport']), - - importRepo() { - return this.fetchImport({ - newName: this.newName, - targetNamespace: this.targetNamespace, - repo: this.repo, + ...mapActions(['fetchImport', 'setImportTarget']), + updateImportTarget(changedValues) { + this.setImportTarget({ + repoId: this.repo.importSource.id, + importTarget: { ...this.importTarget, ...changedValues }, }); }, }, @@ -75,35 +74,39 @@ export default { </script> <template> - <tr class="qa-project-import-row js-provider-repo import-row"> + <tr class="qa-project-import-row import-row"> <td> <a - :href="repo.providerLink" + :href="repo.importSource.providerLink" rel="noreferrer noopener" target="_blank" - class="js-provider-link" - > - {{ repo.fullName }} + data-testid="providerLink" + >{{ repo.importSource.fullName }} + <gl-icon v-if="repo.importSource.providerLink" name="external-link" /> </a> </td> <td class="d-flex flex-wrap flex-lg-nowrap"> - <select2-select v-model="targetNamespace" :options="select2Options" /> - <span class="px-2 import-slash-divider d-flex justify-content-center align-items-center" - >/</span - > - <input - v-model="newName" - type="text" - class="form-control import-project-name-input js-new-name qa-project-path-field" - /> + <template v-if="repo.target">{{ repo.target }}</template> + <template v-else> + <select2-select v-model="targetNamespaceSelect" :options="select2Options" /> + <span class="px-2 import-slash-divider d-flex justify-content-center align-items-center" + >/</span + > + <input + v-model="newNameInput" + type="text" + class="form-control import-project-name-input qa-project-path-field" + /> + </template> + </td> + <td> + <import-status :status="repo.importStatus" /> </td> - <td><import-status :status="status" /></td> <td> <button - v-if="!isLoadingImport" type="button" - class="qa-import-button js-import-button btn btn-default" - @click="importRepo" + class="qa-import-button btn btn-default" + @click="fetchImport(repo.importSource.id)" > {{ importButtonText }} </button> |