diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-22 18:10:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-22 18:10:48 +0300 |
commit | 232e7582b0e81e95bd721ad8094fcb50ecbc8a04 (patch) | |
tree | a68e00760d0a3446c2030addd679b33acfa39d2d /app/assets/javascripts/import_entities | |
parent | 980d813e90c70f09861e39b9269e73b2c9a4e936 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/import_entities')
7 files changed, 141 insertions, 65 deletions
diff --git a/app/assets/javascripts/import_entities/constants.js b/app/assets/javascripts/import_entities/constants.js index ad33ca158d2..c2f398cb8a8 100644 --- a/app/assets/javascripts/import_entities/constants.js +++ b/app/assets/javascripts/import_entities/constants.js @@ -7,6 +7,7 @@ export const STATUSES = { FINISHED: 'finished', FAILED: 'failed', SCHEDULED: 'scheduled', + CREATED: 'created', STARTED: 'started', NONE: 'none', SCHEDULING: 'scheduling', @@ -23,6 +24,11 @@ const STATUS_MAP = { text: __('Failed'), textClass: 'text-danger', }, + [STATUSES.CREATED]: { + icon: 'pending', + text: __('Scheduled'), + textClass: 'text-warning', + }, [STATUSES.SCHEDULED]: { icon: 'pending', text: __('Scheduled'), 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 a0a44ee74dc..c0fd076ee6b 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 @@ -33,11 +33,6 @@ export default { type: String, required: true, }, - canCreateGroup: { - type: Boolean, - required: false, - default: false, - }, }, data() { @@ -176,7 +171,6 @@ export default { :key="group.id" :group="group" :available-namespaces="availableNamespaces" - :can-create-group="canCreateGroup" @update-target-namespace="updateTargetNamespace(group.id, $event)" @update-new-name="updateNewName(group.id, $event)" @import-group="importGroup(group.id)" 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 1f3eee0c141..3b8abeae122 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 @@ -24,11 +24,6 @@ export default { type: Array, required: true, }, - canCreateGroup: { - type: Boolean, - required: false, - default: false, - }, }, computed: { isDisabled() { @@ -45,19 +40,18 @@ export default { text: namespace.full_path, })); - if (!this.canCreateGroup) { - return { data: availableNamespacesData }; + const select2Config = { + data: [{ id: '', text: s__('BulkImport|No parent') }], + }; + + if (availableNamespacesData.length) { + select2Config.data.push({ + text: s__('BulkImport|Existing groups'), + children: availableNamespacesData, + }); } - return { - data: [ - { id: '', text: s__('BulkImport|No parent') }, - { - text: s__('BulkImport|Existing groups'), - children: availableNamespacesData, - }, - ], - }; + return select2Config; }, }, methods: { 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 8110934efc4..8aeab59cb65 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 @@ -15,52 +15,71 @@ export const clientTypenames = { BulkImportPageInfo: 'ClientBulkImportPageInfo', }; -export function createResolvers({ endpoints }) { +export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGroupsManager }) { let statusPoller; + let sourceGroupManager; + const getGroupsManager = (client) => { + if (!sourceGroupManager) { + sourceGroupManager = new GroupsManager({ client, sourceUrl }); + } + return sourceGroupManager; + }; + return { Query: { async bulkImportSourceGroups(_, vars, { client }) { - const { - data: { availableNamespaces }, - } = await client.query({ query: availableNamespacesQuery }); - if (!statusPoller) { statusPoller = new StatusPoller({ - client, + groupManager: getGroupsManager(client), pollPath: endpoints.jobs, }); statusPoller.startPolling(); } - return axios - .get(endpoints.status, { + const groupsManager = getGroupsManager(client); + return Promise.all([ + axios.get(endpoints.status, { params: { page: vars.page, per_page: vars.perPage, filter: vars.filter, }, - }) - .then(({ headers, data }) => { + }), + client.query({ query: availableNamespacesQuery }), + ]).then( + ([ + { headers, data }, + { + data: { availableNamespaces }, + }, + ]) => { const pagination = parseIntPagination(normalizeHeaders(headers)); return { __typename: clientTypenames.BulkImportSourceGroupConnection, - nodes: data.importable_data.map((group) => ({ - __typename: clientTypenames.BulkImportSourceGroup, - ...group, - status: STATUSES.NONE, - import_target: { - new_name: group.full_path, - target_namespace: availableNamespaces[0].full_path, - }, - })), + nodes: data.importable_data.map((group) => { + const cachedImportState = groupsManager.getImportStateFromStorageByGroupId( + group.id, + ); + + return { + __typename: clientTypenames.BulkImportSourceGroup, + ...group, + status: cachedImportState?.status ?? STATUSES.NONE, + import_target: cachedImportState?.importTarget ?? { + new_name: group.full_path, + target_namespace: availableNamespaces[0]?.full_path ?? '', + }, + }; + }), pageInfo: { __typename: clientTypenames.BulkImportPageInfo, ...pagination, }, }; - }); + }, + ); }, availableNamespaces: () => @@ -73,21 +92,21 @@ export function createResolvers({ endpoints }) { }, Mutation: { setTargetNamespace(_, { targetNamespace, sourceGroupId }, { client }) { - new SourceGroupsManager({ client }).updateById(sourceGroupId, (sourceGroup) => { + getGroupsManager(client).updateById(sourceGroupId, (sourceGroup) => { // eslint-disable-next-line no-param-reassign sourceGroup.import_target.target_namespace = targetNamespace; }); }, setNewName(_, { newName, sourceGroupId }, { client }) { - new SourceGroupsManager({ client }).updateById(sourceGroupId, (sourceGroup) => { + getGroupsManager(client).updateById(sourceGroupId, (sourceGroup) => { // eslint-disable-next-line no-param-reassign sourceGroup.import_target.new_name = newName; }); }, async importGroup(_, { sourceGroupId }, { client }) { - const groupManager = new SourceGroupsManager({ client }); + const groupManager = getGroupsManager(client); const group = groupManager.findById(sourceGroupId); groupManager.setImportStatus(group, STATUSES.SCHEDULING); try { @@ -101,8 +120,7 @@ export function createResolvers({ endpoints }) { }, ], }); - groupManager.setImportStatus(group, STATUSES.STARTED); - SourceGroupsManager.attachImportId(group, response.data.id); + groupManager.startImport({ group, importId: response.data.id }); } catch (e) { createFlash({ message: s__('BulkImport|Importing the group failed'), @@ -116,5 +134,5 @@ export function createResolvers({ endpoints }) { }; } -export const createApolloClient = ({ endpoints }) => - createDefaultClient(createResolvers({ endpoints }), { assumeImmutableResults: true }); +export const createApolloClient = ({ sourceUrl, endpoints }) => + createDefaultClient(createResolvers({ sourceUrl, endpoints }), { assumeImmutableResults: true }); 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 261e30edbbb..2c88d25358f 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,5 +1,7 @@ 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) { @@ -13,15 +15,24 @@ function generateGroupId(id) { }); } +export const KEY = 'gl-bulk-imports-import-state'; +export const DEBOUNCE_INTERVAL = 200; + export class SourceGroupsManager { - static importMap = new Map(); + constructor({ client, sourceUrl, storage = window.localStorage }) { + this.client = client; + this.sourceUrl = sourceUrl; - static attachImportId(group, importId) { - SourceGroupsManager.importMap.set(importId, group.id); + this.storage = storage; + this.importStates = this.loadImportStatesFromStorage(); } - constructor({ client }) { - this.client = client; + loadImportStatesFromStorage() { + try { + return JSON.parse(this.storage.getItem(KEY)) ?? {}; + } catch { + return {}; + } } findById(id) { @@ -42,8 +53,48 @@ export class SourceGroupsManager { this.update(group, fn); } - findByImportId(importId) { - return this.findById(SourceGroupsManager.importMap.get(importId)); + saveImportState(importId, group) { + this.importStates[this.getStorageKey(importId)] = { + id: group.id, + importTarget: group.import_target, + status: group.status, + }; + this.saveImportStatesToStorage(); + } + + getImportStateFromStorage(importId) { + return this.importStates[this.getStorageKey(importId)]; + } + + getImportStateFromStorageByGroupId(groupId) { + const PREFIX = this.getStorageKey(''); + const [, importState] = + Object.entries(this.importStates).find( + ([key, group]) => key.startsWith(PREFIX) && group.id === groupId, + ) ?? []; + + return importState; + } + + getStorageKey(importId) { + return `${this.sourceUrl}|${importId}`; + } + + saveImportStatesToStorage = debounce(() => { + try { + // storage might be changed in other tab so fetch first + this.storage.setItem( + KEY, + JSON.stringify(merge({}, this.loadImportStatesFromStorage(), this.importStates)), + ); + } catch { + // 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) { @@ -52,4 +103,22 @@ export class SourceGroupsManager { 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 63cd6b48fc4..b80a575afce 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 @@ -3,12 +3,9 @@ import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import Poll from '~/lib/utils/poll'; import { s__ } from '~/locale'; -import { SourceGroupsManager } from './source_groups_manager'; export class StatusPoller { - constructor({ client, pollPath }) { - this.client = client; - + constructor({ groupManager, pollPath }) { this.eTagPoll = new Poll({ resource: { fetchJobs: () => axios.get(pollPath), @@ -29,7 +26,7 @@ export class StatusPoller { } }); - this.groupManager = new SourceGroupsManager({ client }); + this.groupManager = groupManager; } startPolling() { @@ -38,10 +35,7 @@ export class StatusPoller { async updateImportsStatuses(importStatuses) { importStatuses.forEach(({ id, status_name: statusName }) => { - const group = this.groupManager.findByImportId(id); - if (group.id) { - this.groupManager.setImportStatus(group, statusName); - } + this.groupManager.setImportStatusByImportId(id, statusName); }); } } diff --git a/app/assets/javascripts/import_entities/import_groups/index.js b/app/assets/javascripts/import_entities/import_groups/index.js index 0700358f6ce..cf56bb804d4 100644 --- a/app/assets/javascripts/import_entities/import_groups/index.js +++ b/app/assets/javascripts/import_entities/import_groups/index.js @@ -21,6 +21,7 @@ export function mountImportGroupsApp(mountElement) { } = mountElement.dataset; const apolloProvider = new VueApollo({ defaultClient: createApolloClient({ + sourceUrl, endpoints: { status: statusPath, availableNamespaces: availableNamespacesPath, |