Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-07 15:10:27 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-07 15:10:27 +0300
commit53f456b167f19877d663ee6ed510673cebee0f91 (patch)
treefcc0bb52b79c195bf0eda100cc5d7e7a16dc0c0b /app/assets/javascripts/import_entities
parente8a31d8dc2afd673ca50d74d26edab0a0fec83ca (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/import_entities')
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table.vue78
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue38
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js96
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql4
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql9
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql9
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql9
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql9
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js46
-rw-r--r--app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql18
10 files changed, 254 insertions, 62 deletions
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 f337520b0db..79b3e689442 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,5 +1,6 @@
<script>
import {
+ GlButton,
GlEmptyState,
GlDropdown,
GlDropdownItem,
@@ -8,10 +9,13 @@ import {
GlLoadingIcon,
GlSearchBoxByClick,
GlSprintf,
+ GlSafeHtmlDirective as SafeHtml,
+ GlTooltip,
} from '@gitlab/ui';
-import { s__, __ } from '~/locale';
+import { s__, __, n__ } from '~/locale';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
-import importGroupMutation from '../graphql/mutations/import_group.mutation.graphql';
+import { STATUSES } from '../../constants';
+import importGroupsMutation from '../graphql/mutations/import_groups.mutation.graphql';
import setNewNameMutation from '../graphql/mutations/set_new_name.mutation.graphql';
import setTargetNamespaceMutation from '../graphql/mutations/set_target_namespace.mutation.graphql';
import availableNamespacesQuery from '../graphql/queries/available_namespaces.query.graphql';
@@ -23,6 +27,7 @@ const DEFAULT_PAGE_SIZE = PAGE_SIZES[0];
export default {
components: {
+ GlButton,
GlEmptyState,
GlDropdown,
GlDropdownItem,
@@ -31,9 +36,13 @@ export default {
GlLoadingIcon,
GlSearchBoxByClick,
GlSprintf,
+ GlTooltip,
ImportTableRow,
PaginationLinks,
},
+ directives: {
+ SafeHtml,
+ },
props: {
sourceUrl: {
@@ -65,12 +74,28 @@ export default {
},
computed: {
+ groups() {
+ return this.bulkImportSourceGroups?.nodes ?? [];
+ },
+
+ hasGroupsWithValidationError() {
+ return this.groups.some((g) => g.validation_errors.length);
+ },
+
+ availableGroupsForImport() {
+ return this.groups.filter((g) => g.progress.status === STATUSES.NONE);
+ },
+
+ isImportAllButtonDisabled() {
+ return this.hasGroupsWithValidationError || this.availableGroupsForImport.length === 0;
+ },
+
humanizedTotal() {
return this.paginationInfo.total >= 1000 ? __('1000+') : this.paginationInfo.total;
},
hasGroups() {
- return this.bulkImportSourceGroups?.nodes?.length > 0;
+ return this.groups.length > 0;
},
hasEmptyFilter() {
@@ -105,6 +130,10 @@ export default {
},
methods: {
+ groupsCount(count) {
+ return n__('%d group', '%d groups', count);
+ },
+
setPage(page) {
this.page = page;
},
@@ -123,24 +152,57 @@ export default {
});
},
- importGroup(sourceGroupId) {
+ importGroups(sourceGroupIds) {
this.$apollo.mutate({
- mutation: importGroupMutation,
- variables: { sourceGroupId },
+ mutation: importGroupsMutation,
+ variables: { sourceGroupIds },
});
},
+ importAllGroups() {
+ this.importGroups(this.availableGroupsForImport.map((g) => g.id));
+ },
+
setPageSize(size) {
this.perPage = size;
},
},
+ gitlabLogo: window.gon.gitlab_logo,
PAGE_SIZES,
};
</script>
<template>
<div>
+ <h1
+ class="gl-my-0 gl-py-4 gl-font-size-h1 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex"
+ >
+ <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') }}
+ <div ref="importAllButtonWrapper" class="gl-ml-auto">
+ <gl-button
+ v-if="!$apollo.loading && hasGroups"
+ :disabled="isImportAllButtonDisabled"
+ variant="confirm"
+ @click="importAllGroups"
+ >
+ <gl-sprintf :message="s__('BulkImport|Import %{groups}')">
+ <template #groups>
+ {{ groupsCount(availableGroupsForImport.length) }}
+ </template>
+ </gl-sprintf>
+ </gl-button>
+ </div>
+ <gl-tooltip v-if="isImportAllButtonDisabled" :target="() => $refs.importAllButtonWrapper">
+ <template v-if="hasGroupsWithValidationError">
+ {{ s__('BulkImport|One or more groups has validation errors') }}
+ </template>
+ <template v-else>
+ {{ s__('BulkImport|No groups on this page are available for import') }}
+ </template>
+ </gl-tooltip>
+ </h1>
<div
class="gl-py-5 gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1 gl-display-flex"
>
@@ -153,7 +215,7 @@ export default {
<strong>{{ paginationInfo.end }}</strong>
</template>
<template #total>
- <strong>{{ n__('%d group', '%d groups', paginationInfo.total) }}</strong>
+ <strong>{{ groupsCount(paginationInfo.total) }}</strong>
</template>
<template #filter>
<strong>{{ filter }}</strong>
@@ -196,7 +258,7 @@ export default {
:group-path-regex="groupPathRegex"
@update-target-namespace="updateTargetNamespace(group.id, $event)"
@update-new-name="updateNewName(group.id, $event)"
- @import-group="importGroup(group.id)"
+ @import-group="importGroups([group.id])"
/>
</template>
</tbody>
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
index 1d36b370457..51333f810a6 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue
@@ -10,8 +10,11 @@ import {
GlFormInput,
} from '@gitlab/ui';
import { joinPaths } from '~/lib/utils/url_utility';
+import { s__ } from '~/locale';
import ImportStatus from '../../components/import_status.vue';
import { STATUSES } from '../../constants';
+import addValidationErrorMutation from '../graphql/mutations/add_validation_error.mutation.graphql';
+import removeValidationErrorMutation from '../graphql/mutations/remove_validation_error.mutation.graphql';
import groupQuery from '../graphql/queries/group.query.graphql';
const DEBOUNCE_INTERVAL = 300;
@@ -52,6 +55,27 @@ export default {
fullPath: this.fullPath,
};
},
+ update({ existingGroup }) {
+ const variables = {
+ field: 'new_name',
+ sourceGroupId: this.group.id,
+ };
+
+ if (!existingGroup) {
+ this.$apollo.mutate({
+ mutation: removeValidationErrorMutation,
+ variables,
+ });
+ } else {
+ this.$apollo.mutate({
+ mutation: addValidationErrorMutation,
+ variables: {
+ ...variables,
+ message: s__('BulkImport|Name already exists.'),
+ },
+ });
+ }
+ },
skip() {
return !this.isNameValid || this.isAlreadyImported;
},
@@ -63,8 +87,12 @@ export default {
return this.group.import_target;
},
+ invalidNameValidationMessage() {
+ return this.group.validation_errors.find(({ field }) => field === 'new_name')?.message;
+ },
+
isInvalid() {
- return Boolean(!this.isNameValid || this.existingGroup);
+ return Boolean(!this.isNameValid || this.invalidNameValidationMessage);
},
isNameValid() {
@@ -157,21 +185,21 @@ export default {
<template v-if="!isNameValid">
{{ __('Please choose a group URL with no special characters.') }}
</template>
- <template v-else-if="existingGroup">
- {{ s__('BulkImport|Name already exists.') }}
+ <template v-else-if="invalidNameValidationMessage">
+ {{ invalidNameValidationMessage }}
</template>
</p>
</div>
</div>
</td>
<td class="gl-p-4 gl-white-space-nowrap">
- <import-status :status="group.progress.status" />
+ <import-status :status="group.progress.status" class="gl-mt-2" />
</td>
<td class="gl-p-4">
<gl-button
v-if="!isAlreadyImported"
:disabled="isInvalid"
- variant="success"
+ variant="confirm"
category="secondary"
@click="$emit('import-group')"
>{{ __('Import') }}</gl-button
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 facefe316eb..2cde3781a6a 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
@@ -20,6 +20,7 @@ export const clientTypenames = {
BulkImportPageInfo: 'ClientBulkImportPageInfo',
BulkImportTarget: 'ClientBulkImportTarget',
BulkImportProgress: 'ClientBulkImportProgress',
+ BulkImportValidationError: 'ClientBulkImportValidationError',
};
function makeGroup(data) {
@@ -106,6 +107,7 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr
return makeGroup({
...group,
+ validation_errors: [],
progress: {
id: jobId ?? localProgressId(group.id),
status: cachedImportState?.status ?? STATUSES.NONE,
@@ -152,7 +154,7 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr
async setImportProgress(_, { sourceGroupId, status, jobId }) {
if (jobId) {
- groupsManager.saveImportState(jobId, { status });
+ groupsManager.updateImportProgress(jobId, status);
}
return makeGroup({
@@ -165,7 +167,7 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr
},
async updateImportStatus(_, { id, status }) {
- groupsManager.saveImportState(id, { status });
+ groupsManager.updateImportProgress(id, status);
return {
__typename: clientTypenames.BulkImportProgress,
@@ -174,39 +176,81 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr
};
},
- async importGroup(_, { sourceGroupId }, { client }) {
+ async addValidationError(_, { sourceGroupId, field, message }, { client }) {
const {
- data: { bulkImportSourceGroup: group },
+ data: {
+ bulkImportSourceGroup: { validation_errors: validationErrors, ...group },
+ },
} = await client.query({
query: bulkImportSourceGroupQuery,
variables: { id: sourceGroupId },
});
- const GROUP_BEING_SCHEDULED = makeGroup({
- id: sourceGroupId,
- progress: {
- id: localProgressId(sourceGroupId),
- status: STATUSES.SCHEDULING,
+ return {
+ ...group,
+ validation_errors: [
+ ...validationErrors.filter(({ field: f }) => f !== field),
+ {
+ __typename: clientTypenames.BulkImportValidationError,
+ field,
+ message,
+ },
+ ],
+ };
+ },
+
+ async removeValidationError(_, { sourceGroupId, field }, { client }) {
+ const {
+ data: {
+ bulkImportSourceGroup: { validation_errors: validationErrors, ...group },
},
+ } = await client.query({
+ query: bulkImportSourceGroupQuery,
+ variables: { id: sourceGroupId },
});
+ return {
+ ...group,
+ validation_errors: validationErrors.filter(({ field: f }) => f !== field),
+ };
+ },
+
+ async importGroups(_, { sourceGroupIds }, { client }) {
+ const groups = await Promise.all(
+ sourceGroupIds.map((id) =>
+ client
+ .query({
+ query: bulkImportSourceGroupQuery,
+ variables: { id },
+ })
+ .then(({ data }) => data.bulkImportSourceGroup),
+ ),
+ );
+
+ const GROUPS_BEING_SCHEDULED = sourceGroupIds.map((sourceGroupId) =>
+ makeGroup({
+ id: sourceGroupId,
+ progress: {
+ id: localProgressId(sourceGroupId),
+ status: STATUSES.SCHEDULING,
+ },
+ }),
+ );
+
const defaultErrorMessage = s__('BulkImport|Importing the group failed');
axios
.post(endpoints.createBulkImport, {
- bulk_import: [
- {
- source_type: 'group_entity',
- source_full_path: group.full_path,
- destination_namespace: group.import_target.target_namespace,
- destination_name: group.import_target.new_name,
- },
- ],
+ bulk_import: groups.map((group) => ({
+ source_type: 'group_entity',
+ source_full_path: group.full_path,
+ destination_namespace: group.import_target.target_namespace,
+ destination_name: group.import_target.new_name,
+ })),
})
.then(({ data: { id: jobId } }) => {
- groupsManager.saveImportState(jobId, {
- id: group.id,
- importTarget: group.import_target,
+ groupsManager.createImportState(jobId, {
status: STATUSES.CREATED,
+ groups,
});
return { status: STATUSES.CREATED, jobId };
@@ -217,14 +261,16 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr
return { status: STATUSES.NONE };
})
.then((newStatus) =>
- client.mutate({
- mutation: setImportProgressMutation,
- variables: { sourceGroupId, ...newStatus },
- }),
+ sourceGroupIds.forEach((sourceGroupId) =>
+ client.mutate({
+ mutation: setImportProgressMutation,
+ variables: { sourceGroupId, ...newStatus },
+ }),
+ ),
)
.catch(() => createFlash({ message: defaultErrorMessage }));
- return GROUP_BEING_SCHEDULED;
+ return GROUPS_BEING_SCHEDULED;
},
},
};
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql
index ee3add7966c..47675cd1bd0 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_item.fragment.graphql
@@ -12,4 +12,8 @@ fragment BulkImportSourceGroupItem on ClientBulkImportSourceGroup {
target_namespace
new_name
}
+ validation_errors {
+ field
+ message
+ }
}
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql
new file mode 100644
index 00000000000..d95c460c046
--- /dev/null
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/add_validation_error.mutation.graphql
@@ -0,0 +1,9 @@
+mutation addValidationError($sourceGroupId: String!, $field: String!, $message: String!) {
+ addValidationError(sourceGroupId: $sourceGroupId, field: $field, message: $message) @client {
+ id
+ validation_errors {
+ field
+ message
+ }
+ }
+}
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql
deleted file mode 100644
index 41d32a1d639..00000000000
--- a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_group.mutation.graphql
+++ /dev/null
@@ -1,9 +0,0 @@
-mutation importGroup($sourceGroupId: String!) {
- importGroup(sourceGroupId: $sourceGroupId) @client {
- id
- progress {
- id
- status
- }
- }
-}
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
new file mode 100644
index 00000000000..d8e46329e38
--- /dev/null
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql
@@ -0,0 +1,9 @@
+mutation importGroups($sourceGroupIds: [String!]!) {
+ importGroups(sourceGroupIds: $sourceGroupIds) @client {
+ id
+ progress {
+ id
+ status
+ }
+ }
+}
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql
new file mode 100644
index 00000000000..940bf4dfaac
--- /dev/null
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/remove_validation_error.mutation.graphql
@@ -0,0 +1,9 @@
+mutation removeValidationError($sourceGroupId: String!, $field: String!) {
+ removeValidationError(sourceGroupId: $sourceGroupId, field: $field) @client {
+ id
+ validation_errors {
+ field
+ message
+ }
+ }
+}
diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js b/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
index 536f96529d7..97dbdbf518a 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/services/source_groups_manager.js
@@ -13,25 +13,42 @@ export class SourceGroupsManager {
loadImportStatesFromStorage() {
try {
- return JSON.parse(this.storage.getItem(KEY)) ?? {};
+ return Object.fromEntries(
+ Object.entries(JSON.parse(this.storage.getItem(KEY)) ?? {}).map(([jobId, config]) => {
+ // new format of storage
+ if (config.groups) {
+ return [jobId, config];
+ }
+
+ return [
+ jobId,
+ {
+ status: config.status,
+ groups: [{ id: config.id, importTarget: config.importTarget }],
+ },
+ ];
+ }),
+ );
} catch {
return {};
}
}
- saveImportState(importId, group) {
- const key = this.getStorageKey(importId);
- const oldState = this.importStates[key] ?? {};
+ createImportState(importId, jobConfig) {
+ this.importStates[this.getStorageKey(importId)] = {
+ status: jobConfig.status,
+ groups: jobConfig.groups.map((g) => ({ importTarget: g.import_target, id: g.id })),
+ };
+ this.saveImportStatesToStorage();
+ }
- if (!oldState.id && !group.id) {
+ updateImportProgress(importId, status) {
+ const currentState = this.importStates[this.getStorageKey(importId)];
+ if (!currentState) {
return;
}
- this.importStates[key] = {
- ...oldState,
- ...group,
- status: group.status,
- };
+ currentState.status = status;
this.saveImportStatesToStorage();
}
@@ -39,10 +56,15 @@ export class SourceGroupsManager {
const PREFIX = this.getStorageKey('');
const [jobId, importState] =
Object.entries(this.importStates).find(
- ([key, group]) => key.startsWith(PREFIX) && group.id === groupId,
+ ([key, state]) => key.startsWith(PREFIX) && state.groups.some((g) => g.id === groupId),
) ?? [];
- return { jobId, importState };
+ if (!jobId) {
+ return null;
+ }
+
+ const group = importState.groups.find((g) => g.id === groupId);
+ return { jobId, importState: { ...group, status: importState.status } };
}
getStorageKey(importId) {
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 ba042d33893..c830aaa75e6 100644
--- a/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql
+++ b/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql
@@ -18,6 +18,11 @@ type ClientBulkImportProgress {
status: String!
}
+type ClientBulkImportValidationError {
+ field: String!
+ message: String!
+}
+
type ClientBulkImportSourceGroup {
id: ID!
web_url: String!
@@ -25,6 +30,7 @@ type ClientBulkImportSourceGroup {
full_name: String!
progress: ClientBulkImportProgress!
import_target: ClientBulkImportTarget!
+ validation_errors: [ClientBulkImportValidationError!]!
}
type ClientBulkImportPageInfo {
@@ -45,9 +51,15 @@ extend type Query {
}
extend type Mutation {
- setNewName(newName: String, sourceGroupId: ID!): ClientTargetNamespace!
- setTargetNamespace(targetNamespace: String, sourceGroupId: ID!): ClientTargetNamespace!
- importGroup(id: ID!): ClientBulkImportSourceGroup!
+ setNewName(newName: String, sourceGroupId: ID!): ClientBulkImportSourceGroup!
+ setTargetNamespace(targetNamespace: String, sourceGroupId: ID!): ClientBulkImportSourceGroup!
+ importGroups(sourceGroupIds: [ID!]!): [ClientBulkImportSourceGroup!]!
setImportProgress(id: ID, status: String!): ClientBulkImportSourceGroup!
updateImportProgress(id: ID, status: String!): ClientBulkImportProgress
+ addValidationError(
+ sourceGroupId: ID!
+ field: String!
+ message: String!
+ ): ClientBulkImportSourceGroup!
+ removeValidationError(sourceGroupId: ID!, field: String!): ClientBulkImportSourceGroup!
}