diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-20 16:37:47 +0300 |
commit | aee0a117a889461ce8ced6fcf73207fe017f1d99 (patch) | |
tree | 891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/import_entities | |
parent | 8d46af3258650d305f53b819eabf7ab18d22f59e (diff) |
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/import_entities')
11 files changed, 208 insertions, 216 deletions
diff --git a/app/assets/javascripts/import_entities/components/pagination_bar.vue b/app/assets/javascripts/import_entities/components/pagination_bar.vue deleted file mode 100644 index 33bd3e08bb1..00000000000 --- a/app/assets/javascripts/import_entities/components/pagination_bar.vue +++ /dev/null @@ -1,90 +0,0 @@ -<script> -import { GlDropdown, GlDropdownItem, GlIcon, GlSprintf } from '@gitlab/ui'; -import { __ } from '~/locale'; -import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; - -const DEFAULT_PAGE_SIZES = [20, 50, 100]; - -export default { - components: { - PaginationLinks, - GlDropdown, - GlDropdownItem, - GlIcon, - GlSprintf, - }, - props: { - pageInfo: { - required: true, - type: Object, - }, - pageSizes: { - required: false, - type: Array, - default: () => DEFAULT_PAGE_SIZES, - }, - itemsCount: { - required: true, - type: Number, - }, - }, - - computed: { - humanizedTotal() { - return this.pageInfo.total >= 1000 ? __('1000+') : this.pageInfo.total; - }, - - paginationInfo() { - const { page, perPage } = this.pageInfo; - const start = (page - 1) * perPage + 1; - const end = start + this.itemsCount - 1; - - return { start, end }; - }, - }, - - methods: { - setPage(page) { - this.$emit('set-page', page); - }, - }, -}; -</script> - -<template> - <div class="gl-display-flex gl-align-items-center"> - <pagination-links :change="setPage" :page-info="pageInfo" class="gl-m-0" /> - <gl-dropdown category="tertiary" class="gl-ml-auto"> - <template #button-content> - <span class="gl-font-weight-bold"> - <gl-sprintf :message="__('%{count} items per page')"> - <template #count> - {{ pageInfo.perPage }} - </template> - </gl-sprintf> - </span> - <gl-icon class="gl-button-icon dropdown-chevron" name="chevron-down" /> - </template> - <gl-dropdown-item v-for="size in pageSizes" :key="size" @click="$emit('set-page-size', size)"> - <gl-sprintf :message="__('%{count} items per page')"> - <template #count> - {{ size }} - </template> - </gl-sprintf> - </gl-dropdown-item> - </gl-dropdown> - <div class="gl-ml-2" data-testid="information"> - <gl-sprintf :message="s__('BulkImport|Showing %{start}-%{end} of %{total}')"> - <template #start> - {{ paginationInfo.start }} - </template> - <template #end> - {{ paginationInfo.end }} - </template> - <template #total> - {{ humanizedTotal }} - </template> - </gl-sprintf> - </div> - </div> -</template> diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue index e004bc35087..deaf2654424 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue @@ -44,7 +44,7 @@ export default { :size="16" name="information-o" :title=" - s__('BulkImports|Re-import creates a new group. It does not sync with the existing group.') + s__('BulkImport|Re-import creates a new group. It does not sync with the existing group.') " class="gl-ml-3" /> diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue index ec6025c84bb..028197ec9b1 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue @@ -1,9 +1,8 @@ <script> import { + GlAlert, GlButton, GlEmptyState, - GlDropdown, - GlDropdownItem, GlIcon, GlLink, GlLoadingIcon, @@ -14,8 +13,8 @@ import { } from '@gitlab/ui'; import { debounce } from 'lodash'; import createFlash from '~/flash'; -import { s__, __, n__ } from '~/locale'; -import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; +import { s__, __, n__, sprintf } from '~/locale'; +import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue'; import { getGroupPathAvailability } from '~/rest_api'; import axios from '~/lib/utils/axios_utils'; import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants'; @@ -42,10 +41,9 @@ const DEFAULT_TD_CLASSES = 'gl-vertical-align-top!'; export default { components: { + GlAlert, GlButton, GlEmptyState, - GlDropdown, - GlDropdownItem, GlIcon, GlLink, GlLoadingIcon, @@ -57,7 +55,7 @@ export default { ImportTargetCell, ImportStatusCell, ImportActionsCell, - PaginationLinks, + PaginationBar, }, props: { @@ -83,6 +81,7 @@ export default { selectedGroupsIds: [], pendingGroupsIds: [], importTargets: {}, + unavailableFeaturesAlertVisible: true, }; }, @@ -170,7 +169,7 @@ export default { }, availableGroupsForImport() { - return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && g.flags.isInvalid); + return this.groupsTableData.filter((g) => g.flags.isAvailableForImport && !g.flags.isInvalid); }, humanizedTotal() { @@ -204,6 +203,23 @@ export default { return { start, end, total }; }, + + unavailableFeatures() { + if (!this.hasGroups) { + return []; + } + + return Object.entries(this.bulkImportSourceGroups.versionValidation.features) + .filter(([, { available }]) => available === false) + .map(([k, v]) => ({ title: i18n.features[k] || k, version: v.minVersion })); + }, + + unavailableFeaturesAlertTitle() { + return sprintf(s__('BulkImport| %{host} is running outdated GitLab version (v%{version})'), { + host: this.sourceUrl, + version: this.bulkImportSourceGroups.versionValidation.features.sourceInstanceVersion, + }); + }, }, watch: { @@ -314,9 +330,8 @@ export default { variables: { importRequests }, }); } catch (error) { - const message = error?.networkError?.response?.data?.error ?? i18n.ERROR_IMPORT; createFlash({ - message, + message: i18n.ERROR_IMPORT, captureError: true, error, }); @@ -476,6 +491,38 @@ export default { <img :src="$options.gitlabLogo" class="gl-w-6 gl-h-6 gl-mb-2 gl-display-inline gl-mr-2" /> {{ s__('BulkImport|Import groups from GitLab') }} </h1> + <gl-alert + v-if="unavailableFeatures.length > 0 && unavailableFeaturesAlertVisible" + variant="warning" + :title="unavailableFeaturesAlertTitle" + @dismiss="unavailableFeaturesAlertVisible = false" + > + <gl-sprintf + :message=" + s__( + 'BulkImport|Following data will not be migrated: %{bullets} Contact system administrator of %{host} to upgrade GitLab if you need this data in your migration', + ) + " + > + <template #host> + <gl-link :href="sourceUrl" target="_blank"> + {{ sourceUrl }}<gl-icon name="external-link" class="vertical-align-middle" /> + </gl-link> + </template> + <template #bullets> + <ul> + <li v-for="feature in unavailableFeatures" :key="feature.title"> + <gl-sprintf :message="s__('BulkImport|%{feature} (require v%{version})')"> + <template #feature>{{ feature.title }}</template> + <template #version> + <strong>{{ feature.version }}</strong> + </template> + </gl-sprintf> + </li> + </ul> + </template> + </gl-sprintf> + </gl-alert> <div class="gl-py-5 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex" > @@ -495,7 +542,7 @@ export default { </template> <template #link> <gl-link :href="sourceUrl" target="_blank"> - {{ sourceUrl }} <gl-icon name="external-link" class="vertical-align-middle" /> + {{ sourceUrl }}<gl-icon name="external-link" class="vertical-align-middle" /> </gl-link> </template> </gl-sprintf> @@ -521,13 +568,15 @@ export default { /> <template v-else> <div - class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-p-4 gl-display-flex gl-align-items-center" + class="gl-bg-gray-10 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-px-4 gl-display-flex gl-align-items-center import-table-bar" > - <gl-sprintf :message="__('%{count} selected')"> - <template #count> - {{ selectedGroupsIds.length }} - </template> - </gl-sprintf> + <span data-test-id="selection-count"> + <gl-sprintf :message="__('%{count} selected')"> + <template #count> + {{ selectedGroupsIds.length }} + </template> + </gl-sprintf> + </span> <gl-button category="primary" variant="confirm" @@ -539,7 +588,7 @@ export default { </div> <gl-table ref="table" - class="gl-w-full" + class="gl-w-full import-table" data-qa-selector="import_table" :tbody-tr-class="rowClasses" :tbody-tr-attr="qaRowAttributes" @@ -599,49 +648,13 @@ export default { /> </template> </gl-table> - <div v-if="hasGroups" class="gl-display-flex gl-mt-3 gl-align-items-center"> - <pagination-links - :change="setPage" - :page-info="bulkImportSourceGroups.pageInfo" - class="gl-m-0" - /> - <gl-dropdown category="tertiary" :aria-label="__('Page size')" class="gl-ml-auto"> - <template #button-content> - <span class="font-weight-bold"> - <gl-sprintf :message="__('%{count} items per page')"> - <template #count> - {{ perPage }} - </template> - </gl-sprintf> - </span> - <gl-icon class="gl-button-icon dropdown-chevron" name="chevron-down" /> - </template> - <gl-dropdown-item - v-for="size in $options.PAGE_SIZES" - :key="size" - @click="setPageSize(size)" - > - <gl-sprintf :message="__('%{count} items per page')"> - <template #count> - {{ size }} - </template> - </gl-sprintf> - </gl-dropdown-item> - </gl-dropdown> - <div class="gl-ml-2"> - <gl-sprintf :message="s__('BulkImport|Showing %{start}-%{end} of %{total}')"> - <template #start> - {{ paginationInfo.start }} - </template> - <template #end> - {{ paginationInfo.end }} - </template> - <template #total> - {{ humanizedTotal }} - </template> - </gl-sprintf> - </div> - </div> + <pagination-bar + v-if="hasGroups" + :page-info="bulkImportSourceGroups.pageInfo" + class="gl-mt-3" + @set-page="setPage" + @set-page-size="setPageSize" + /> </template> </template> </div> diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue index ca9ae9447d0..344a6e45370 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue @@ -32,72 +32,84 @@ export default { fullPath() { return this.group.importTarget.targetNamespace.fullPath || s__('BulkImport|No parent'); }, - invalidNameValidationMessage() { - return getInvalidNameValidationMessage(this.group.importTarget); + validationMessage() { + return ( + this.group.progress?.message || getInvalidNameValidationMessage(this.group.importTarget) + ); + }, + validNameState() { + // bootstrap-vue requires null for "indifferent" state, if we return true + // this will highlight field in green like "passed validation" + return this.group.flags.isInvalid && this.group.flags.isAvailableForImport ? false : null; }, }, }; </script> <template> - <div class="gl-display-flex gl-align-items-stretch"> - <import-group-dropdown - #default="{ namespaces }" - :text="fullPath" - :disabled="!group.flags.isAvailableForImport" - :namespaces="availableNamespaces" - toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" - class="gl-h-7 gl-flex-grow-1" - data-qa-selector="target_namespace_selector_dropdown" - > - <gl-dropdown-item @click="$emit('update-target-namespace', { fullPath: '', id: null })">{{ - s__('BulkImport|No parent') - }}</gl-dropdown-item> - <template v-if="namespaces.length"> - <gl-dropdown-divider /> - <gl-dropdown-section-header> - {{ s__('BulkImport|Existing groups') }} - </gl-dropdown-section-header> - <gl-dropdown-item - v-for="ns in namespaces" - :key="ns.fullPath" - data-qa-selector="target_group_dropdown_item" - :data-qa-group-name="ns.fullPath" - @click="$emit('update-target-namespace', ns)" - > - {{ ns.fullPath }} - </gl-dropdown-item> - </template> - </import-group-dropdown> - <div - class="gl-h-7 gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1 gl-bg-gray-10" - :class="{ - 'gl-text-gray-400 gl-border-gray-100': !group.flags.isAvailableForImport, - 'gl-border-gray-200': group.flags.isAvailableForImport, - }" - > - / - </div> - <div class="gl-flex-grow-1"> - <gl-form-input - class="gl-rounded-top-left-none gl-rounded-bottom-left-none" + <div> + <div class="gl-display-flex gl-align-items-stretch"> + <import-group-dropdown + #default="{ namespaces }" + :text="fullPath" + :disabled="!group.flags.isAvailableForImport" + :namespaces="availableNamespaces" + toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" + class="gl-h-7 gl-flex-grow-1" + data-qa-selector="target_namespace_selector_dropdown" + > + <gl-dropdown-item @click="$emit('update-target-namespace', { fullPath: '', id: null })">{{ + s__('BulkImport|No parent') + }}</gl-dropdown-item> + <template v-if="namespaces.length"> + <gl-dropdown-divider /> + <gl-dropdown-section-header> + {{ s__('BulkImport|Existing groups') }} + </gl-dropdown-section-header> + <gl-dropdown-item + v-for="ns in namespaces" + :key="ns.fullPath" + data-qa-selector="target_group_dropdown_item" + :data-qa-group-name="ns.fullPath" + @click="$emit('update-target-namespace', ns)" + > + {{ ns.fullPath }} + </gl-dropdown-item> + </template> + </import-group-dropdown> + <div + class="gl-h-7 gl-px-3 gl-display-flex gl-align-items-center gl-border-solid gl-border-0 gl-border-t-1 gl-border-b-1 gl-bg-gray-10" :class="{ - 'gl-inset-border-1-gray-200!': group.flags.isAvailableForImport, - 'gl-inset-border-1-gray-100!': !group.flags.isAvailableForImport, - 'is-invalid': group.flags.isInvalid && group.flags.isAvailableForImport, + 'gl-text-gray-400 gl-border-gray-100': !group.flags.isAvailableForImport, + 'gl-border-gray-200': group.flags.isAvailableForImport, }" - debounce="500" - :disabled="!group.flags.isAvailableForImport" - :value="group.importTarget.newName" - :aria-label="__('New name')" - @input="$emit('update-new-name', $event)" - /> - <p - v-if="group.flags.isAvailableForImport && group.flags.isInvalid" - class="gl-text-red-500 gl-m-0 gl-mt-2" > - {{ invalidNameValidationMessage }} - </p> + / + </div> + <div class="gl-flex-grow-1"> + <gl-form-input + class="gl-rounded-top-left-none gl-rounded-bottom-left-none" + :class="{ + 'gl-inset-border-1-gray-200!': + group.flags.isAvailableForImport && !group.flags.isInvalid, + 'gl-inset-border-1-gray-100!': + !group.flags.isAvailableForImport && !group.flags.isInvalid, + }" + debounce="500" + :disabled="!group.flags.isAvailableForImport" + :value="group.importTarget.newName" + :aria-label="__('New name')" + :state="validNameState" + @input="$emit('update-new-name', $event)" + /> + </div> + </div> + <div + v-if="group.flags.isAvailableForImport && (group.flags.isInvalid || validationMessage)" + class="gl-text-red-500 gl-m-0 gl-mt-2" + role="alert" + > + {{ validationMessage }} </div> </div> </template> diff --git a/app/assets/javascripts/import_entities/import_groups/constants.js b/app/assets/javascripts/import_entities/import_groups/constants.js index aa9cf3897e6..ac1466238d0 100644 --- a/app/assets/javascripts/import_entities/import_groups/constants.js +++ b/app/assets/javascripts/import_entities/import_groups/constants.js @@ -11,6 +11,10 @@ export const i18n = { ), ERROR_IMPORT: s__('BulkImport|Importing the group failed.'), ERROR_IMPORT_COMPLETED: s__('BulkImport|Import is finished. Pick another name for re-import'), + + features: { + projectMigration: __('projects'), + }, }; export const NEW_NAME_FIELD = 'newName'; diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js index bce6e7bcb1f..36da996ea17 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js +++ b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js @@ -14,6 +14,9 @@ export const clientTypenames = { BulkImportPageInfo: 'ClientBulkImportPageInfo', BulkImportTarget: 'ClientBulkImportTarget', BulkImportProgress: 'ClientBulkImportProgress', + BulkImportVersionValidation: 'ClientBulkImportVersionValidation', + BulkImportVersionValidationFeature: 'ClientBulkImportVersionValidationFeature', + BulkImportVersionValidationFeatures: 'ClientBulkImportVersionValidationFeatures', }; function makeLastImportTarget(data) { @@ -92,6 +95,18 @@ export function createResolvers({ endpoints }) { __typename: clientTypenames.BulkImportPageInfo, ...pagination, }, + versionValidation: { + __typename: clientTypenames.BulkImportVersionValidation, + features: { + __typename: clientTypenames.BulkImportVersionValidationFeatures, + sourceInstanceVersion: data.version_validation.features.source_instance_version, + projectMigration: { + __typename: clientTypenames.BulkImportVersionValidationFeature, + available: data.version_validation.features.project_migration.available, + minVersion: data.version_validation.features.project_migration.min_version, + }, + }, + }, }; return response; }, @@ -142,9 +157,7 @@ export function createResolvers({ endpoints }) { }; }); - const { - data: { id: jobId }, - } = await axios.post(endpoints.createBulkImport, { + const { data: originalResponse } = await axios.post(endpoints.createBulkImport, { bulk_import: importOperations.map((op) => ({ source_type: 'group_entity', source_full_path: op.group.fullPath, @@ -153,15 +166,21 @@ export function createResolvers({ endpoints }) { })), }); - return importOperations.map((op) => { + const responses = Array.isArray(originalResponse) + ? originalResponse + : [{ success: true, id: originalResponse.id }]; + + return importOperations.map((op, idx) => { + const response = responses[idx]; const lastImportTarget = { targetNamespace: op.targetNamespace, newName: op.newName, }; const progress = { - id: jobId, - status: STATUSES.CREATED, + id: response.id || `local-${Date.now()}-${idx}`, + status: response.success ? STATUSES.CREATED : STATUSES.FAILED, + message: response.message || null, }; localStorageCache.set(op.group.webUrl, { progress, lastImportTarget }); diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_progress.fragment.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_progress.fragment.graphql index 2d60bf82d65..33c564f36a8 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_progress.fragment.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_progress.fragment.graphql @@ -1,4 +1,5 @@ fragment BulkImportSourceGroupProgress on ClientBulkImportProgress { id status + message } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql index 75215471d0f..39289887b75 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql @@ -9,6 +9,7 @@ mutation importGroups($importRequests: [ImportGroupInput!]!) { progress { id status + message } } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql index 28dfefdf8a7..ace8bffc012 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_groups.query.graphql @@ -11,5 +11,14 @@ query bulkImportSourceGroups($page: Int = 1, $perPage: Int = 20, $filter: String total totalPages } + versionValidation { + features { + sourceInstanceVersion + projectMigration { + available + minVersion + } + } + } } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/services/local_storage_cache.js b/app/assets/javascripts/import_entities/import_groups/graphql/services/local_storage_cache.js index 09bc7b33692..1aad22f0f3f 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/services/local_storage_cache.js +++ b/app/assets/javascripts/import_entities/import_groups/graphql/services/local_storage_cache.js @@ -22,7 +22,14 @@ export class LocalStorageCache { loadCacheFromStorage() { try { - return JSON.parse(this.storage.getItem(KEY)) ?? {}; + const storage = JSON.parse(this.storage.getItem(KEY)) ?? {}; + Object.values(storage).forEach((entry) => { + if (entry.progress && !('message' in entry.progress)) { + // eslint-disable-next-line no-param-reassign + entry.progress.message = ''; + } + }); + return storage; } catch { return {}; } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql index b8dd79a5000..c48e22a7717 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql @@ -11,11 +11,13 @@ type ClientBulkImportTarget { type ClientBulkImportSourceGroupConnection { nodes: [ClientBulkImportSourceGroup!]! pageInfo: ClientBulkImportPageInfo! + versionValidation: ClientBulkImportVersionValidation! } type ClientBulkImportProgress { id: ID! status: String! + message: String } type ClientBulkImportValidationError { @@ -45,6 +47,20 @@ type ClientBulkImportNamespaceSuggestion { suggestions: [String!]! } +type ClientBulkImportVersionValidation { + features: ClientBulkImportVersionValidationFeatures! +} + +type ClientBulkImportVersionValidationFeatures { + project_migration: ClientBulkImportVersionValidationFeature! + sourceInstanceVersion: String! +} + +type ClientBulkImportVersionValidationFeature { + available: Boolean! + min_version: String! +} + extend type Query { bulkImportSourceGroups( page: Int! |