diff options
Diffstat (limited to 'app/assets/javascripts/import_entities/import_groups')
10 files changed, 293 insertions, 119 deletions
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 new file mode 100644 index 00000000000..104c84173fc --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/components/import_actions_cell.vue @@ -0,0 +1,69 @@ +<script> +import { GlButton, GlIcon, GlTooltipDirective as GlTooltip } from '@gitlab/ui'; +import { joinPaths } from '~/lib/utils/url_utility'; +import { isFinished, isInvalid, isAvailableForImport } from '../utils'; + +export default { + components: { + GlIcon, + GlButton, + }, + directives: { + GlTooltip, + }, + props: { + group: { + type: Object, + required: true, + }, + groupPathRegex: { + type: RegExp, + required: true, + }, + }, + computed: { + fullLastImportPath() { + return this.group.last_import_target + ? `${this.group.last_import_target.target_namespace}/${this.group.last_import_target.new_name}` + : null; + }, + absoluteLastImportPath() { + return joinPaths(gon.relative_url_root || '/', this.fullLastImportPath); + }, + isAvailableForImport() { + return isAvailableForImport(this.group); + }, + isFinished() { + return isFinished(this.group); + }, + isInvalid() { + return isInvalid(this.group, this.groupPathRegex); + }, + }, +}; +</script> + +<template> + <span class="gl-white-space-nowrap gl-inline-flex gl-align-items-center"> + <gl-button + v-if="isAvailableForImport" + :disabled="isInvalid" + variant="confirm" + category="secondary" + data-qa-selector="import_group_button" + @click="$emit('import-group')" + > + {{ isFinished ? __('Re-import') : __('Import') }} + </gl-button> + <gl-icon + v-if="isFinished" + v-gl-tooltip + :size="16" + name="information-o" + :title=" + s__('BulkImports|Re-import creates a new group. It does not sync with the existing group.') + " + class="gl-ml-3" + /> + </span> +</template> diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_source_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_source_cell.vue new file mode 100644 index 00000000000..2de9bd4f868 --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/components/import_source_cell.vue @@ -0,0 +1,53 @@ +<script> +import { GlLink, GlSprintf, GlIcon } from '@gitlab/ui'; +import { joinPaths } from '~/lib/utils/url_utility'; +import { isFinished } from '../utils'; + +export default { + components: { + GlLink, + GlSprintf, + GlIcon, + }, + props: { + group: { + type: Object, + required: true, + }, + }, + computed: { + fullLastImportPath() { + return this.group.last_import_target + ? `${this.group.last_import_target.target_namespace}/${this.group.last_import_target.new_name}` + : null; + }, + absoluteLastImportPath() { + return joinPaths(gon.relative_url_root || '/', this.fullLastImportPath); + }, + isFinished() { + return isFinished(this.group); + }, + }, +}; +</script> + +<template> + <div> + <gl-link + :href="group.web_url" + target="_blank" + class="gl-display-inline-flex gl-align-items-center gl-h-7" + > + {{ group.full_path }} <gl-icon name="external-link" /> + </gl-link> + <div v-if="isFinished && fullLastImportPath" class="gl-font-sm"> + <gl-sprintf :message="s__('BulkImport|Last imported to %{link}')"> + <template #link> + <gl-link :href="absoluteLastImportPath" class="gl-font-sm" target="_blank">{{ + fullLastImportPath + }}</gl-link> + </template> + </gl-sprintf> + </div> + </div> +</template> 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 db44be2bcd7..04b037ecc2b 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 @@ -9,19 +9,19 @@ import { GlLoadingIcon, GlSearchBoxByClick, GlSprintf, - GlSafeHtmlDirective as SafeHtml, GlTable, GlFormCheckbox, } from '@gitlab/ui'; import { s__, __, n__ } from '~/locale'; import PaginationLinks from '~/vue_shared/components/pagination_links.vue'; -import ImportStatus from '../../components/import_status.vue'; -import { STATUSES } from '../../constants'; +import ImportStatusCell from '../../components/import_status.vue'; import importGroupsMutation from '../graphql/mutations/import_groups.mutation.graphql'; import setImportTargetMutation from '../graphql/mutations/set_import_target.mutation.graphql'; import availableNamespacesQuery from '../graphql/queries/available_namespaces.query.graphql'; import bulkImportSourceGroupsQuery from '../graphql/queries/bulk_import_source_groups.query.graphql'; -import { isInvalid } from '../utils'; +import { isInvalid, isFinished, isAvailableForImport } from '../utils'; +import ImportActionsCell from './import_actions_cell.vue'; +import ImportSourceCell from './import_source_cell.vue'; import ImportTargetCell from './import_target_cell.vue'; const PAGE_SIZES = [20, 50, 100]; @@ -43,13 +43,12 @@ export default { GlFormCheckbox, GlSprintf, GlTable, - ImportStatus, + ImportSourceCell, ImportTargetCell, + ImportStatusCell, + ImportActionsCell, PaginationLinks, }, - directives: { - SafeHtml, - }, props: { sourceUrl: { @@ -136,7 +135,7 @@ export default { }, availableGroupsForImport() { - return this.groups.filter((g) => g.progress.status === STATUSES.NONE && !this.isInvalid(g)); + return this.groups.filter((g) => isAvailableForImport(g) && !this.isInvalid(g)); }, humanizedTotal() { @@ -190,6 +189,24 @@ export default { }, methods: { + isUnselectable(group) { + return !this.isAvailableForImport(group) || this.isInvalid(group); + }, + + rowClasses(group) { + const DEFAULT_CLASSES = [ + 'gl-border-gray-200', + 'gl-border-0', + 'gl-border-b-1', + 'gl-border-solid', + ]; + const result = [...DEFAULT_CLASSES]; + if (this.isUnselectable(group)) { + result.push('gl-cursor-default!'); + } + return result; + }, + qaRowAttributes(group, type) { if (type === 'row') { return { @@ -201,10 +218,8 @@ export default { return {}; }, - isAlreadyImported(group) { - return group.progress.status !== STATUSES.NONE; - }, - + isAvailableForImport, + isFinished, isInvalid(group) { return isInvalid(group, this.groupPathRegex); }, @@ -253,7 +268,7 @@ export default { const table = this.getTableRef(); this.groups.forEach((group, idx) => { - if (table.isRowSelected(idx) && (this.isAlreadyImported(group) || this.isInvalid(group))) { + if (table.isRowSelected(idx) && this.isUnselectable(group)) { table.unselectRow(idx); } }); @@ -291,7 +306,7 @@ export default { <strong>{{ filter }}</strong> </template> <template #link> - <gl-link class="gl-display-inline-block" :href="sourceUrl" target="_blank"> + <gl-link :href="sourceUrl" target="_blank"> {{ sourceUrl }} <gl-icon name="external-link" class="vertical-align-middle" /> </gl-link> </template> @@ -338,7 +353,7 @@ export default { ref="table" class="gl-w-full" data-qa-selector="import_table" - tbody-tr-class="gl-border-gray-200 gl-border-0 gl-border-b-1 gl-border-solid" + :tbody-tr-class="rowClasses" :tbody-tr-attr="qaRowAttributes" :items="groups" :fields="$options.fields" @@ -360,18 +375,12 @@ export default { <gl-form-checkbox class="gl-h-7 gl-pt-3" :checked="rowSelected" - :disabled="isAlreadyImported(group) || isInvalid(group)" + :disabled="!isAvailableForImport(group) || isInvalid(group)" @change="rowSelected ? unselectRow() : selectRow()" /> </template> - <template #cell(web_url)="{ value: web_url, item: { full_path } }"> - <gl-link - :href="web_url" - target="_blank" - class="gl-display-inline-flex gl-align-items-center gl-h-7" - > - {{ full_path }} <gl-icon name="external-link" /> - </gl-link> + <template #cell(web_url)="{ item: group }"> + <import-source-cell :group="group" /> </template> <template #cell(import_target)="{ item: group }"> <import-target-cell @@ -388,19 +397,14 @@ export default { /> </template> <template #cell(progress)="{ value: { status } }"> - <import-status :status="status" class="gl-line-height-32" /> + <import-status-cell :status="status" class="gl-line-height-32" /> </template> <template #cell(actions)="{ item: group }"> - <gl-button - v-if="!isAlreadyImported(group)" - :disabled="isInvalid(group)" - variant="confirm" - category="secondary" - data-qa-selector="import_group_button" - @click="importGroups([group.id])" - > - {{ __('Import') }} - </gl-button> + <import-actions-cell + :group="group" + :group-path-regex="groupPathRegex" + @import-group="importGroups([group.id])" + /> </template> </gl-table> <div v-if="hasGroups" class="gl-display-flex gl-mt-3 gl-align-items-center"> 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 7359d4f239e..daced740c94 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 @@ -3,14 +3,16 @@ import { GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader, - GlLink, GlFormInput, } from '@gitlab/ui'; -import { joinPaths } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import ImportGroupDropdown from '../../components/group_dropdown.vue'; -import { STATUSES } from '../../constants'; -import { isInvalid, getInvalidNameValidationMessage, isNameValid } from '../utils'; +import { + isInvalid, + getInvalidNameValidationMessage, + isNameValid, + isAvailableForImport, +} from '../utils'; export default { components: { @@ -18,7 +20,6 @@ export default { GlDropdownDivider, GlDropdownItem, GlDropdownSectionHeader, - GlLink, GlFormInput, }, props: { @@ -61,20 +62,8 @@ export default { return isNameValid(this.group, this.groupPathRegex); }, - isAlreadyImported() { - return this.group.progress.status !== STATUSES.NONE; - }, - - isFinished() { - return this.group.progress.status === STATUSES.FINISHED; - }, - - fullPath() { - return `${this.importTarget.target_namespace}/${this.importTarget.new_name}`; - }, - - absolutePath() { - return joinPaths(gon.relative_url_root || '/', this.fullPath); + isAvailableForImport() { + return isAvailableForImport(this.group); }, }, @@ -85,25 +74,11 @@ export default { </script> <template> - <gl-link - v-if="isFinished" - class="gl-display-inline-flex gl-align-items-center gl-h-7" - :href="absolutePath" - > - {{ fullPath }} - </gl-link> - - <div - v-else - class="gl-display-flex gl-align-items-stretch" - :class="{ - disabled: isAlreadyImported, - }" - > + <div class="gl-display-flex gl-align-items-stretch"> <import-group-dropdown #default="{ namespaces }" :text="importTarget.target_namespace" - :disabled="isAlreadyImported" + :disabled="!isAvailableForImport" :namespaces="availableNamespaceNames" toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" class="gl-h-7 gl-flex-grow-1" @@ -131,8 +106,8 @@ export default { <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': isAlreadyImported, - 'gl-border-gray-200': !isAlreadyImported, + 'gl-text-gray-400 gl-border-gray-100': !isAvailableForImport, + 'gl-border-gray-200': isAvailableForImport, }" > / @@ -141,11 +116,11 @@ export default { <gl-form-input class="gl-rounded-top-left-none gl-rounded-bottom-left-none" :class="{ - 'gl-inset-border-1-gray-200!': !isAlreadyImported, - 'gl-inset-border-1-gray-100!': isAlreadyImported, - 'is-invalid': isInvalid && !isAlreadyImported, + 'gl-inset-border-1-gray-200!': isAvailableForImport, + 'gl-inset-border-1-gray-100!': !isAvailableForImport, + 'is-invalid': isInvalid && isAvailableForImport, }" - :disabled="isAlreadyImported" + :disabled="!isAvailableForImport" :value="importTarget.new_name" @input="$emit('update-new-name', $event)" /> 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 57188441158..c08cf909a00 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 @@ -5,10 +5,13 @@ import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import { STATUSES } from '../../constants'; import { i18n, NEW_NAME_FIELD } from '../constants'; +import { isAvailableForImport } from '../utils'; import bulkImportSourceGroupItemFragment from './fragments/bulk_import_source_group_item.fragment.graphql'; +import bulkImportSourceGroupProgressFragment from './fragments/bulk_import_source_group_progress.fragment.graphql'; import addValidationErrorMutation from './mutations/add_validation_error.mutation.graphql'; import removeValidationErrorMutation from './mutations/remove_validation_error.mutation.graphql'; import setImportProgressMutation from './mutations/set_import_progress.mutation.graphql'; +import setImportTargetMutation from './mutations/set_import_target.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'; @@ -34,6 +37,7 @@ function makeGroup(data) { }; const NESTED_OBJECT_FIELDS = { import_target: clientTypenames.BulkImportTarget, + last_import_target: clientTypenames.BulkImportTarget, progress: clientTypenames.BulkImportProgress, }; @@ -55,6 +59,7 @@ async function checkImportTargetIsValid({ client, newName, targetNamespace, sour data: { existingGroup, existingProject }, } = await client.query({ query: groupAndProjectQuery, + fetchPolicy: 'no-cache', variables: { fullPath: `${targetNamespace}/${newName}`, }, @@ -82,6 +87,7 @@ async function checkImportTargetIsValid({ client, newName, targetNamespace, sour } const localProgressId = (id) => `not-started-${id}`; +const nextName = (name) => `${name}-1`; export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGroupsManager }) { const groupsManager = new GroupsManager({ @@ -140,17 +146,28 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr const { jobId, importState: cachedImportState } = groupsManager.getImportStateFromStorageByGroupId(group.id) ?? {}; + const status = cachedImportState?.status ?? STATUSES.NONE; + + const importTarget = + status === STATUSES.FINISHED && cachedImportState.importTarget + ? { + target_namespace: cachedImportState.importTarget.target_namespace, + new_name: nextName(cachedImportState.importTarget.new_name), + } + : cachedImportState?.importTarget ?? { + new_name: group.full_path, + target_namespace: availableNamespaces[0]?.full_path ?? '', + }; + return makeGroup({ ...group, validation_errors: [], 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 ?? '', + status, }, + import_target: importTarget, + last_import_target: cachedImportState?.importTarget ?? null, }); }), pageInfo: { @@ -161,7 +178,7 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr setTimeout(() => { response.nodes.forEach((group) => { - if (group.progress.status === STATUSES.NONE) { + if (isAvailableForImport(group)) { checkImportTargetIsValid({ client, newName: group.import_target.new_name, @@ -193,32 +210,18 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr targetNamespace, newName, }); + return makeGroup({ id: sourceGroupId, import_target: { target_namespace: targetNamespace, new_name: newName, + id: sourceGroupId, }, }); }, - 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 }) { + async setImportProgress(_, { sourceGroupId, status, jobId, importTarget }) { if (jobId) { groupsManager.updateImportProgress(jobId, status); } @@ -229,16 +232,46 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr id: jobId ?? localProgressId(sourceGroupId), status, }, + last_import_target: { + __typename: clientTypenames.BulkImportTarget, + ...importTarget, + }, }); }, - async updateImportStatus(_, { id, status }) { - groupsManager.updateImportProgress(id, status); + async updateImportStatus(_, { id, status: newStatus }, { client, getCacheKey }) { + groupsManager.updateImportProgress(id, newStatus); + + const progressItem = client.readFragment({ + fragment: bulkImportSourceGroupProgressFragment, + fragmentName: 'BulkImportSourceGroupProgress', + id: getCacheKey({ + __typename: clientTypenames.BulkImportProgress, + id, + }), + }); + + const isInProgress = Boolean(progressItem); + const { status: currentStatus } = progressItem ?? {}; + if (newStatus === STATUSES.FINISHED && isInProgress && currentStatus !== newStatus) { + const groups = groupsManager.getImportedGroupsByJobId(id); + + groups.forEach(async ({ id: groupId, importTarget }) => { + client.mutate({ + mutation: setImportTargetMutation, + variables: { + sourceGroupId: groupId, + targetNamespace: importTarget.target_namespace, + newName: nextName(importTarget.new_name), + }, + }); + }); + } return { __typename: clientTypenames.BulkImportProgress, id, - status, + status: newStatus, }; }, @@ -327,10 +360,10 @@ export function createResolvers({ endpoints, sourceUrl, GroupsManager = SourceGr return { status: STATUSES.NONE }; }) .then((newStatus) => - sourceGroupIds.forEach((sourceGroupId) => + sourceGroupIds.forEach((sourceGroupId, idx) => client.mutate({ mutation: setImportProgressMutation, - variables: { sourceGroupId, ...newStatus }, + variables: { sourceGroupId, ...newStatus, importTarget: groups[idx].import_target }, }), ), ) 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 47675cd1bd0..089340b3c48 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,6 +12,10 @@ fragment BulkImportSourceGroupItem on ClientBulkImportSourceGroup { target_namespace new_name } + last_import_target { + target_namespace + new_name + } validation_errors { field message 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 index 2ec1269932a..43301554de3 100644 --- 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 @@ -1,9 +1,23 @@ -mutation setImportProgress($status: String!, $sourceGroupId: String!, $jobId: String) { - setImportProgress(status: $status, sourceGroupId: $sourceGroupId, jobId: $jobId) @client { +mutation setImportProgress( + $status: String! + $sourceGroupId: String! + $jobId: String + $importTarget: ImportTargetInput! +) { + setImportProgress( + status: $status + sourceGroupId: $sourceGroupId + jobId: $jobId + importTarget: $importTarget + ) @client { id progress { id status } + last_import_target { + target_namespace + new_name + } } } 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 97dbdbf518a..7caa37d9ad4 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 @@ -35,15 +35,18 @@ export class SourceGroupsManager { } createImportState(importId, jobConfig) { - this.importStates[this.getStorageKey(importId)] = { + this.importStates[importId] = { status: jobConfig.status, - groups: jobConfig.groups.map((g) => ({ importTarget: g.import_target, id: g.id })), + groups: jobConfig.groups.map((g) => ({ + importTarget: { ...g.import_target }, + id: g.id, + })), }; this.saveImportStatesToStorage(); } updateImportProgress(importId, status) { - const currentState = this.importStates[this.getStorageKey(importId)]; + const currentState = this.importStates[importId]; if (!currentState) { return; } @@ -52,12 +55,15 @@ export class SourceGroupsManager { this.saveImportStatesToStorage(); } + getImportedGroupsByJobId(jobId) { + return this.importStates[jobId]?.groups ?? []; + } + getImportStateFromStorageByGroupId(groupId) { - const PREFIX = this.getStorageKey(''); const [jobId, importState] = - Object.entries(this.importStates).find( - ([key, state]) => key.startsWith(PREFIX) && state.groups.some((g) => g.id === groupId), - ) ?? []; + Object.entries(this.importStates) + .reverse() + .find(([, state]) => state.groups.some((g) => g.id === groupId)) ?? []; if (!jobId) { return null; @@ -67,10 +73,6 @@ export class SourceGroupsManager { return { jobId, importState: { ...group, status: importState.status } }; } - getStorageKey(importId) { - return `${this.sourceUrl}|${importId}`; - } - saveImportStatesToStorage = debounce(() => { try { // storage might be changed in other tab so fetch first 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 c830aaa75e6..6ef4bbafec0 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql +++ b/app/assets/javascripts/import_entities/import_groups/graphql/typedefs.graphql @@ -30,6 +30,7 @@ type ClientBulkImportSourceGroup { full_name: String! progress: ClientBulkImportProgress! import_target: ClientBulkImportTarget! + last_import_target: ClientBulkImportTarget validation_errors: [ClientBulkImportValidationError!]! } @@ -50,11 +51,21 @@ extend type Query { availableNamespaces: [ClientBulkImportAvailableNamespace!]! } +input InputTargetInput { + target_namespace: String! + new_name: String! +} + extend type Mutation { setNewName(newName: String, sourceGroupId: ID!): ClientBulkImportSourceGroup! setTargetNamespace(targetNamespace: String, sourceGroupId: ID!): ClientBulkImportSourceGroup! importGroups(sourceGroupIds: [ID!]!): [ClientBulkImportSourceGroup!]! - setImportProgress(id: ID, status: String!): ClientBulkImportSourceGroup! + setImportProgress( + id: ID + status: String! + jobId: String + importTarget: ImportTargetInput! + ): ClientBulkImportSourceGroup! updateImportProgress(id: ID, status: String!): ClientBulkImportProgress addValidationError( sourceGroupId: ID! diff --git a/app/assets/javascripts/import_entities/import_groups/utils.js b/app/assets/javascripts/import_entities/import_groups/utils.js index b451008b6f9..a1baeaf39dd 100644 --- a/app/assets/javascripts/import_entities/import_groups/utils.js +++ b/app/assets/javascripts/import_entities/import_groups/utils.js @@ -1,3 +1,4 @@ +import { STATUSES } from '../constants'; import { NEW_NAME_FIELD } from './constants'; export function isNameValid(group, validationRegex) { @@ -11,3 +12,11 @@ export function getInvalidNameValidationMessage(group) { export function isInvalid(group, validationRegex) { return Boolean(!isNameValid(group, validationRegex) || getInvalidNameValidationMessage(group)); } + +export function isFinished(group) { + return group.progress.status === STATUSES.FINISHED; +} + +export function isAvailableForImport(group) { + return [STATUSES.NONE, STATUSES.FINISHED].some((status) => group.progress.status === status); +} |