diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-30 15:12:30 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-04-30 15:12:30 +0300 |
commit | 6d19e491d1257b6fbc74f4cf3a30ddb28deaeaf4 (patch) | |
tree | e14c505a5e880c85161d8c5b17b23341b6c37a21 /app/assets/javascripts/import_entities | |
parent | ea0085de54590ffde24fee2ced286961a419410d (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/import_entities')
13 files changed, 268 insertions, 131 deletions
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 aed879e75fb..1d36b370457 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 @@ -72,11 +72,11 @@ export default { }, isAlreadyImported() { - return this.group.status !== STATUSES.NONE; + return this.group.progress.status !== STATUSES.NONE; }, isFinished() { - return this.group.status === STATUSES.FINISHED; + return this.group.progress.status === STATUSES.FINISHED; }, fullPath() { @@ -165,7 +165,7 @@ export default { </div> </td> <td class="gl-p-4 gl-white-space-nowrap"> - <import-status :status="group.status" /> + <import-status :status="group.progress.status" /> </td> <td class="gl-p-4"> <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 d444cc77aa7..facefe316eb 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 @@ -4,40 +4,82 @@ import axios from '~/lib/utils/axios_utils'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import { STATUSES } from '../../constants'; +import bulkImportSourceGroupItemFragment from './fragments/bulk_import_source_group_item.fragment.graphql'; +import setImportProgressMutation from './mutations/set_import_progress.mutation.graphql'; +import updateImportStatusMutation from './mutations/update_import_status.mutation.graphql'; import availableNamespacesQuery from './queries/available_namespaces.query.graphql'; +import bulkImportSourceGroupQuery from './queries/bulk_import_source_group.query.graphql'; import { SourceGroupsManager } from './services/source_groups_manager'; import { StatusPoller } from './services/status_poller'; +import typeDefs from './typedefs.graphql'; export const clientTypenames = { BulkImportSourceGroupConnection: 'ClientBulkImportSourceGroupConnection', BulkImportSourceGroup: 'ClientBulkImportSourceGroup', AvailableNamespace: 'ClientAvailableNamespace', BulkImportPageInfo: 'ClientBulkImportPageInfo', + BulkImportTarget: 'ClientBulkImportTarget', + BulkImportProgress: 'ClientBulkImportProgress', }; -export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGroupsManager }) { - let statusPoller; +function makeGroup(data) { + const result = { + __typename: clientTypenames.BulkImportSourceGroup, + ...data, + }; + const NESTED_OBJECT_FIELDS = { + import_target: clientTypenames.BulkImportTarget, + progress: clientTypenames.BulkImportProgress, + }; - let sourceGroupManager; - const getGroupsManager = (client) => { - if (!sourceGroupManager) { - sourceGroupManager = new GroupsManager({ client, sourceUrl }); + Object.entries(NESTED_OBJECT_FIELDS).forEach(([field, type]) => { + if (!data[field]) { + return; } - return sourceGroupManager; - }; + result[field] = { + __typename: type, + ...data[field], + }; + }); + + return result; +} + +const localProgressId = (id) => `not-started-${id}`; + +export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGroupsManager }) { + const groupsManager = new GroupsManager({ + sourceUrl, + }); + + let statusPoller; return { Query: { + async bulkImportSourceGroup(_, { id }, { client, getCacheKey }) { + return client.readFragment({ + fragment: bulkImportSourceGroupItemFragment, + fragmentName: 'BulkImportSourceGroupItem', + id: getCacheKey({ + __typename: clientTypenames.BulkImportSourceGroup, + id, + }), + }); + }, + async bulkImportSourceGroups(_, vars, { client }) { if (!statusPoller) { statusPoller = new StatusPoller({ - groupManager: getGroupsManager(client), + updateImportStatus: ({ id, status_name: status }) => + client.mutate({ + mutation: updateImportStatusMutation, + variables: { id, status }, + }), pollPath: endpoints.jobs, }); statusPoller.startPolling(); } - const groupsManager = getGroupsManager(client); return Promise.all([ axios.get(endpoints.status, { params: { @@ -59,19 +101,20 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr return { __typename: clientTypenames.BulkImportSourceGroupConnection, nodes: data.importable_data.map((group) => { - const cachedImportState = groupsManager.getImportStateFromStorageByGroupId( - group.id, - ); + const { jobId, importState: cachedImportState } = + groupsManager.getImportStateFromStorageByGroupId(group.id) ?? {}; - return { - __typename: clientTypenames.BulkImportSourceGroup, + return makeGroup({ ...group, - status: cachedImportState?.status ?? STATUSES.NONE, + progress: { + id: jobId ?? localProgressId(group.id), + status: cachedImportState?.status ?? STATUSES.NONE, + }, import_target: cachedImportState?.importTarget ?? { new_name: group.full_path, target_namespace: availableNamespaces[0]?.full_path ?? '', }, - }; + }); }), pageInfo: { __typename: clientTypenames.BulkImportPageInfo, @@ -91,26 +134,65 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr ), }, Mutation: { - setTargetNamespace(_, { targetNamespace, sourceGroupId }, { client }) { - getGroupsManager(client).updateById(sourceGroupId, (sourceGroup) => { - // eslint-disable-next-line no-param-reassign - sourceGroup.import_target.target_namespace = targetNamespace; + setTargetNamespace: (_, { targetNamespace, sourceGroupId }) => + makeGroup({ + id: sourceGroupId, + import_target: { + target_namespace: targetNamespace, + }, + }), + + setNewName: (_, { newName, sourceGroupId }) => + makeGroup({ + id: sourceGroupId, + import_target: { + new_name: newName, + }, + }), + + async setImportProgress(_, { sourceGroupId, status, jobId }) { + if (jobId) { + groupsManager.saveImportState(jobId, { status }); + } + + return makeGroup({ + id: sourceGroupId, + progress: { + id: jobId ?? localProgressId(sourceGroupId), + status, + }, }); }, - setNewName(_, { newName, sourceGroupId }, { client }) { - getGroupsManager(client).updateById(sourceGroupId, (sourceGroup) => { - // eslint-disable-next-line no-param-reassign - sourceGroup.import_target.new_name = newName; - }); + async updateImportStatus(_, { id, status }) { + groupsManager.saveImportState(id, { status }); + + return { + __typename: clientTypenames.BulkImportProgress, + id, + status, + }; }, async importGroup(_, { sourceGroupId }, { client }) { - const groupManager = getGroupsManager(client); - const group = groupManager.findById(sourceGroupId); - groupManager.setImportStatus(group, STATUSES.SCHEDULING); - try { - const response = await axios.post(endpoints.createBulkImport, { + const { + data: { bulkImportSourceGroup: group }, + } = await client.query({ + query: bulkImportSourceGroupQuery, + variables: { id: sourceGroupId }, + }); + + const GROUP_BEING_SCHEDULED = 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', @@ -119,18 +201,38 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr destination_name: group.import_target.new_name, }, ], - }); - groupManager.startImport({ group, importId: response.data.id }); - } catch (e) { - const message = e?.response?.data?.error ?? s__('BulkImport|Importing the group failed'); - createFlash({ message }); - groupManager.setImportStatus(group, STATUSES.NONE); - throw e; - } + }) + .then(({ data: { id: jobId } }) => { + groupsManager.saveImportState(jobId, { + id: group.id, + importTarget: group.import_target, + status: STATUSES.CREATED, + }); + + return { status: STATUSES.CREATED, jobId }; + }) + .catch((e) => { + const message = e?.response?.data?.error ?? defaultErrorMessage; + createFlash({ message }); + return { status: STATUSES.NONE }; + }) + .then((newStatus) => + client.mutate({ + mutation: setImportProgressMutation, + variables: { sourceGroupId, ...newStatus }, + }), + ) + .catch(() => createFlash({ message: defaultErrorMessage })); + + return GROUP_BEING_SCHEDULED; }, }, }; } export const createApolloClient = ({ sourceUrl, endpoints }) => - createDefaultClient(createResolvers({ sourceUrl, endpoints }), { assumeImmutableResults: true }); + createDefaultClient( + createResolvers({ sourceUrl, endpoints }), + { assumeImmutableResults: true }, + typeDefs, + ); 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 50774e36599..ee3add7966c 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 @@ -1,8 +1,15 @@ +#import "./bulk_import_source_group_progress.fragment.graphql" + fragment BulkImportSourceGroupItem on ClientBulkImportSourceGroup { id web_url full_path full_name - status - import_target + progress { + ...BulkImportSourceGroupProgress + } + import_target { + target_namespace + new_name + } } 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 new file mode 100644 index 00000000000..2d60bf82d65 --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/graphql/fragments/bulk_import_source_group_progress.fragment.graphql @@ -0,0 +1,4 @@ +fragment BulkImportSourceGroupProgress on ClientBulkImportProgress { + id + status +} 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 index 412608d3faf..41d32a1d639 100644 --- 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 @@ -1,3 +1,9 @@ mutation importGroup($sourceGroupId: String!) { - importGroup(sourceGroupId: $sourceGroupId) @client + importGroup(sourceGroupId: $sourceGroupId) @client { + id + progress { + id + status + } + } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_import_progress.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_import_progress.mutation.graphql new file mode 100644 index 00000000000..2ec1269932a --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_import_progress.mutation.graphql @@ -0,0 +1,9 @@ +mutation setImportProgress($status: String!, $sourceGroupId: String!, $jobId: String) { + setImportProgress(status: $status, sourceGroupId: $sourceGroupId, jobId: $jobId) @client { + id + progress { + id + status + } + } +} diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql index 2bc19891401..354bf2a5815 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_new_name.mutation.graphql @@ -1,3 +1,8 @@ mutation setNewName($newName: String!, $sourceGroupId: String!) { - setNewName(newName: $newName, sourceGroupId: $sourceGroupId) @client + setNewName(newName: $newName, sourceGroupId: $sourceGroupId) @client { + id + import_target { + new_name + } + } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql index fc98a1652c1..a0ef407f135 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/set_target_namespace.mutation.graphql @@ -1,3 +1,8 @@ mutation setTargetNamespace($targetNamespace: String!, $sourceGroupId: String!) { - setTargetNamespace(targetNamespace: $targetNamespace, sourceGroupId: $sourceGroupId) @client + setTargetNamespace(targetNamespace: $targetNamespace, sourceGroupId: $sourceGroupId) @client { + id + import_target { + target_namespace + } + } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/mutations/update_import_status.mutation.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/update_import_status.mutation.graphql new file mode 100644 index 00000000000..8c0233b2939 --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/graphql/mutations/update_import_status.mutation.graphql @@ -0,0 +1,6 @@ +mutation updateImportStatus($status: String!, $id: String!) { + updateImportStatus(status: $status, id: $id) @client { + id + status + } +} diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_group.query.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_group.query.graphql new file mode 100644 index 00000000000..0aff23af96d --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/graphql/queries/bulk_import_source_group.query.graphql @@ -0,0 +1,7 @@ +#import "../fragments/bulk_import_source_group_item.fragment.graphql" + +query bulkImportSourceGroup($id: ID!) { + bulkImportSourceGroup(id: $id) @client { + ...BulkImportSourceGroupItem + } +} 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 2c88d25358f..536f96529d7 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 @@ -1,26 +1,10 @@ -import { defaultDataIdFromObject } from 'apollo-cache-inmemory'; -import produce from 'immer'; import { debounce, merge } from 'lodash'; -import { STATUSES } from '../../../constants'; -import ImportSourceGroupFragment from '../fragments/bulk_import_source_group_item.fragment.graphql'; - -function extractTypeConditionFromFragment(fragment) { - return fragment.definitions[0]?.typeCondition.name.value; -} - -function generateGroupId(id) { - return defaultDataIdFromObject({ - __typename: extractTypeConditionFromFragment(ImportSourceGroupFragment), - id, - }); -} export const KEY = 'gl-bulk-imports-import-state'; export const DEBOUNCE_INTERVAL = 200; export class SourceGroupsManager { - constructor({ client, sourceUrl, storage = window.localStorage }) { - this.client = client; + constructor({ sourceUrl, storage = window.localStorage }) { this.sourceUrl = sourceUrl; this.storage = storage; @@ -35,45 +19,30 @@ export class SourceGroupsManager { } } - findById(id) { - const cacheId = generateGroupId(id); - return this.client.readFragment({ fragment: ImportSourceGroupFragment, id: cacheId }); - } - - update(group, fn) { - this.client.writeFragment({ - fragment: ImportSourceGroupFragment, - id: generateGroupId(group.id), - data: produce(group, fn), - }); - } + saveImportState(importId, group) { + const key = this.getStorageKey(importId); + const oldState = this.importStates[key] ?? {}; - updateById(id, fn) { - const group = this.findById(id); - this.update(group, fn); - } + if (!oldState.id && !group.id) { + return; + } - saveImportState(importId, group) { - this.importStates[this.getStorageKey(importId)] = { - id: group.id, - importTarget: group.import_target, + this.importStates[key] = { + ...oldState, + ...group, status: group.status, }; this.saveImportStatesToStorage(); } - getImportStateFromStorage(importId) { - return this.importStates[this.getStorageKey(importId)]; - } - getImportStateFromStorageByGroupId(groupId) { const PREFIX = this.getStorageKey(''); - const [, importState] = + const [jobId, importState] = Object.entries(this.importStates).find( ([key, group]) => key.startsWith(PREFIX) && group.id === groupId, ) ?? []; - return importState; + return { jobId, importState }; } getStorageKey(importId) { @@ -91,34 +60,4 @@ export class SourceGroupsManager { // empty catch intentional: storage might be unavailable or full } }, DEBOUNCE_INTERVAL); - - startImport({ group, importId }) { - this.setImportStatus(group, STATUSES.CREATED); - this.saveImportState(importId, group); - } - - setImportStatus(group, status) { - this.update(group, (sourceGroup) => { - // eslint-disable-next-line no-param-reassign - sourceGroup.status = status; - }); - } - - setImportStatusByImportId(importId, status) { - const importState = this.getImportStateFromStorage(importId); - if (!importState) { - return; - } - - if (importState.status !== status) { - importState.status = status; - } - - const group = this.findById(importState.id); - if (group?.id) { - this.setImportStatus(group, status); - } - - this.saveImportStatesToStorage(); - } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js b/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js index b80a575afce..0297b3d3428 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js +++ b/app/assets/javascripts/import_entities/import_groups/graphql/services/status_poller.js @@ -5,13 +5,15 @@ import Poll from '~/lib/utils/poll'; import { s__ } from '~/locale'; export class StatusPoller { - constructor({ groupManager, pollPath }) { + constructor({ updateImportStatus, pollPath }) { this.eTagPoll = new Poll({ resource: { fetchJobs: () => axios.get(pollPath), }, method: 'fetchJobs', - successCallback: ({ data }) => this.updateImportsStatuses(data), + successCallback: ({ data: statuses }) => { + statuses.forEach((status) => updateImportStatus(status)); + }, errorCallback: () => createFlash({ message: s__('BulkImport|Update of import statuses with realtime changes failed'), @@ -25,17 +27,9 @@ export class StatusPoller { this.eTagPoll.stop(); } }); - - this.groupManager = groupManager; } startPolling() { this.eTagPoll.makeRequest(); } - - async updateImportsStatuses(importStatuses) { - importStatuses.forEach(({ id, status_name: statusName }) => { - this.groupManager.setImportStatusByImportId(id, statusName); - }); - } } diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql b/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql new file mode 100644 index 00000000000..ba042d33893 --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql @@ -0,0 +1,53 @@ +type ClientBulkImportAvailableNamespace { + id: ID! + full_path: String! +} + +type ClientBulkImportTarget { + target_namespace: String! + new_name: String! +} + +type ClientBulkImportSourceGroupConnection { + nodes: [ClientBulkImportSourceGroup!]! + pageInfo: ClientBulkImportPageInfo! +} + +type ClientBulkImportProgress { + id: ID + status: String! +} + +type ClientBulkImportSourceGroup { + id: ID! + web_url: String! + full_path: String! + full_name: String! + progress: ClientBulkImportProgress! + import_target: ClientBulkImportTarget! +} + +type ClientBulkImportPageInfo { + page: Int! + perPage: Int! + total: Int! + totalPages: Int! +} + +extend type Query { + bulkImportSourceGroup(id: ID!): ClientBulkImportSourceGroup + bulkImportSourceGroups( + page: Int! + perPage: Int! + filter: String! + ): ClientBulkImportSourceGroupConnection! + availableNamespaces: [ClientBulkImportAvailableNamespace!]! +} + +extend type Mutation { + setNewName(newName: String, sourceGroupId: ID!): ClientTargetNamespace! + setTargetNamespace(targetNamespace: String, sourceGroupId: ID!): ClientTargetNamespace! + importGroup(id: ID!): ClientBulkImportSourceGroup! + setImportProgress(id: ID, status: String!): ClientBulkImportSourceGroup! + updateImportProgress(id: ID, status: String!): ClientBulkImportProgress +} |