diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-16 12:09:05 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-16 12:09:05 +0300 |
commit | 78e911431fc575ff4f6c9b7e0f95c02b57a5e926 (patch) | |
tree | 1f3ff9709f56bfb1b6834eef1275a7c9606c5aaa /app/assets/javascripts/import_entities | |
parent | 80f5d0d15f8d7ced767651978fb016072003f376 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/import_entities')
7 files changed, 284 insertions, 216 deletions
diff --git a/app/assets/javascripts/import_entities/components/group_dropdown.vue b/app/assets/javascripts/import_entities/components/group_dropdown.vue index 44d6d17232f..5ba910746ca 100644 --- a/app/assets/javascripts/import_entities/components/group_dropdown.vue +++ b/app/assets/javascripts/import_entities/components/group_dropdown.vue @@ -28,7 +28,7 @@ export default { <template> <gl-dropdown toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" - class="import-entities-namespace-dropdown gl-h-7 gl-flex-fill-1" + class="gl-h-7 gl-flex-fill-1" data-qa-selector="target_namespace_selector_dropdown" v-bind="$attrs" > 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 bcacf9a3bbc..d94c5a8ae07 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 @@ -10,19 +10,25 @@ import { GlSearchBoxByClick, GlSprintf, GlSafeHtmlDirective as SafeHtml, + GlTable, GlTooltip, } 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 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 ImportTableRow from './import_table_row.vue'; +import { isInvalid } from '../utils'; +import ImportTargetCell from './import_target_cell.vue'; const PAGE_SIZES = [20, 50, 100]; const DEFAULT_PAGE_SIZE = PAGE_SIZES[0]; +const DEFAULT_TH_CLASSES = + 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-200! gl-border-b-1! gl-p-5!'; +const DEFAULT_TD_CLASSES = 'gl-vertical-align-top!'; export default { components: { @@ -36,7 +42,9 @@ export default { GlSearchBoxByClick, GlSprintf, GlTooltip, - ImportTableRow, + GlTable, + ImportStatus, + ImportTargetCell, PaginationLinks, }, directives: { @@ -76,6 +84,34 @@ export default { availableNamespaces: availableNamespacesQuery, }, + fields: [ + { + key: 'web_url', + label: s__('BulkImport|From source group'), + thClass: `${DEFAULT_TH_CLASSES} import-jobs-from-col`, + tdClass: DEFAULT_TD_CLASSES, + }, + { + key: 'import_target', + label: s__('BulkImport|To new group'), + thClass: `${DEFAULT_TH_CLASSES} import-jobs-to-col`, + tdClass: DEFAULT_TD_CLASSES, + }, + { + key: 'progress', + label: __('Status'), + thClass: `${DEFAULT_TH_CLASSES} import-jobs-status-col`, + tdClass: DEFAULT_TD_CLASSES, + tdAttr: { 'data-qa-selector': 'import_status_indicator' }, + }, + { + key: 'actions', + label: '', + thClass: `${DEFAULT_TH_CLASSES} import-jobs-cta-col`, + tdClass: DEFAULT_TD_CLASSES, + }, + ], + computed: { groups() { return this.bulkImportSourceGroups?.nodes ?? []; @@ -133,6 +169,25 @@ export default { }, methods: { + qaRowAttributes(group, type) { + if (type === 'row') { + return { + 'data-qa-selector': 'import_item', + 'data-qa-source-group': group.full_path, + }; + } + + return {}; + }, + + isAlreadyImported(group) { + return group.progress.status !== STATUSES.NONE; + }, + + isInvalid(group) { + return isInvalid(group, this.groupPathRegex); + }, + groupsCount(count) { return n__('%d group', '%d groups', count); }, @@ -243,32 +298,53 @@ export default { :description="s__('Check your source instance permissions.')" /> <template v-else> - <table class="gl-w-full" data-qa-selector="import_table"> - <thead class="gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1"> - <th class="gl-py-4 import-jobs-from-col">{{ s__('BulkImport|From source group') }}</th> - <th class="gl-py-4 import-jobs-to-col">{{ s__('BulkImport|To new group') }}</th> - <th class="gl-py-4 import-jobs-status-col">{{ __('Status') }}</th> - <th class="gl-py-4 import-jobs-cta-col"></th> - </thead> - <tbody class="gl-vertical-align-top"> - <template v-for="group in bulkImportSourceGroups.nodes"> - <import-table-row - :key="group.id" - :group="group" - :available-namespaces="availableNamespaces" - :group-path-regex="groupPathRegex" - :group-url-error-message="groupUrlErrorMessage" - @update-target-namespace=" - updateImportTarget(group.id, $event, group.import_target.new_name) - " - @update-new-name=" - updateImportTarget(group.id, group.import_target.target_namespace, $event) - " - @import-group="importGroups([group.id])" - /> - </template> - </tbody> - </table> + <gl-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-attr="qaRowAttributes" + :items="bulkImportSourceGroups.nodes" + :fields="$options.fields" + > + <template #cell(web_url)="{ value: web_url, item: { full_path } }"> + <gl-link + :href="web_url" + target="_blank" + class="gl-display-flex gl-align-items-center gl-h-7" + > + {{ full_path }} <gl-icon name="external-link" /> + </gl-link> + </template> + <template #cell(import_target)="{ item: group }"> + <import-target-cell + :group="group" + :available-namespaces="availableNamespaces" + :group-path-regex="groupPathRegex" + :group-url-error-message="groupUrlErrorMessage" + @update-target-namespace=" + updateImportTarget(group.id, $event, group.import_target.new_name) + " + @update-new-name=" + updateImportTarget(group.id, group.import_target.target_namespace, $event) + " + /> + </template> + <template #cell(progress)="{ value: { status } }"> + <import-status :status="status" class="gl-mt-2" /> + </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> + </template> + </gl-table> <div v-if="hasGroups" class="gl-display-flex gl-mt-3 gl-align-items-center"> <pagination-links :change="setPage" 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 deleted file mode 100644 index 3dd780895a2..00000000000 --- a/app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue +++ /dev/null @@ -1,185 +0,0 @@ -<script> -import { - GlButton, - GlDropdownDivider, - GlDropdownItem, - GlDropdownSectionHeader, - GlIcon, - GlLink, - GlFormInput, -} from '@gitlab/ui'; -import { joinPaths } from '~/lib/utils/url_utility'; -import ImportGroupDropdown from '../../components/group_dropdown.vue'; -import ImportStatus from '../../components/import_status.vue'; -import { STATUSES } from '../../constants'; - -export default { - components: { - ImportStatus, - ImportGroupDropdown, - GlButton, - GlDropdownDivider, - GlDropdownItem, - GlDropdownSectionHeader, - GlLink, - GlIcon, - GlFormInput, - }, - props: { - group: { - type: Object, - required: true, - }, - availableNamespaces: { - type: Array, - required: true, - }, - groupPathRegex: { - type: RegExp, - required: true, - }, - groupUrlErrorMessage: { - type: String, - required: true, - }, - }, - - computed: { - availableNamespaceNames() { - return this.availableNamespaces.map((ns) => ns.full_path); - }, - - importTarget() { - return this.group.import_target; - }, - - invalidNameValidationMessage() { - return this.group.validation_errors.find(({ field }) => field === 'new_name')?.message; - }, - - isInvalid() { - return Boolean(!this.isNameValid || this.invalidNameValidationMessage); - }, - - isNameValid() { - return this.groupPathRegex.test(this.importTarget.new_name); - }, - - 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); - }, - }, -}; -</script> - -<template> - <tr - class="gl-border-gray-200 gl-border-0 gl-border-b-1 gl-border-solid" - data-qa-selector="import_item" - :data-qa-source-group="group.full_path" - > - <td class="gl-p-4"> - <gl-link - :href="group.web_url" - target="_blank" - class="gl-display-flex gl-align-items-center gl-h-7" - > - {{ group.full_path }} <gl-icon name="external-link" /> - </gl-link> - </td> - <td class="gl-p-4"> - <gl-link - v-if="isFinished" - class="gl-display-flex gl-align-items-center gl-h-7" - :href="absolutePath" - > - {{ fullPath }} - </gl-link> - - <div - v-else - class="import-entities-target-select gl-display-flex gl-align-items-stretch" - :class="{ - disabled: isAlreadyImported, - }" - > - <import-group-dropdown - #default="{ namespaces }" - :text="importTarget.target_namespace" - :disabled="isAlreadyImported" - :namespaces="availableNamespaceNames" - toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!" - class="import-entities-namespace-dropdown gl-h-7 gl-flex-grow-1" - data-qa-selector="target_namespace_selector_dropdown" - > - <gl-dropdown-item @click="$emit('update-target-namespace', '')">{{ - 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" - data-qa-selector="target_group_dropdown_item" - :data-qa-group-name="ns" - @click="$emit('update-target-namespace', ns)" - > - {{ ns }} - </gl-dropdown-item> - </template> - </import-group-dropdown> - <div - class="import-entities-target-select-separator 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" - > - / - </div> - <div class="gl-flex-grow-1"> - <gl-form-input - class="gl-rounded-top-left-none gl-rounded-bottom-left-none" - :class="{ 'is-invalid': isInvalid && !isAlreadyImported }" - :disabled="isAlreadyImported" - :value="importTarget.new_name" - @input="$emit('update-new-name', $event)" - /> - <p v-if="isInvalid" class="gl-text-red-500 gl-m-0 gl-mt-2"> - <template v-if="!isNameValid"> - {{ groupUrlErrorMessage }} - </template> - <template v-else-if="invalidNameValidationMessage"> - {{ invalidNameValidationMessage }} - </template> - </p> - </div> - </div> - </td> - <td class="gl-p-4 gl-white-space-nowrap" data-qa-selector="import_status_indicator"> - <import-status :status="group.progress.status" class="gl-mt-2" /> - </td> - <td class="gl-p-4"> - <gl-button - v-if="!isAlreadyImported" - :disabled="isInvalid" - variant="confirm" - category="secondary" - data-qa-selector="import_group_button" - @click="$emit('import-group')" - >{{ __('Import') }}</gl-button - > - </td> - </tr> -</template> 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 new file mode 100644 index 00000000000..0b2276f8823 --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue @@ -0,0 +1,162 @@ +<script> +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'; + +export default { + components: { + ImportGroupDropdown, + GlDropdownDivider, + GlDropdownItem, + GlDropdownSectionHeader, + GlLink, + GlFormInput, + }, + props: { + group: { + type: Object, + required: true, + }, + availableNamespaces: { + type: Array, + required: true, + }, + groupPathRegex: { + type: RegExp, + required: true, + }, + groupUrlErrorMessage: { + type: String, + required: true, + }, + }, + + computed: { + availableNamespaceNames() { + return this.availableNamespaces.map((ns) => ns.full_path); + }, + + importTarget() { + return this.group.import_target; + }, + + invalidNameValidationMessage() { + return getInvalidNameValidationMessage(this.group); + }, + + isInvalid() { + return isInvalid(this.group, this.groupPathRegex); + }, + + isNameValid() { + 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); + }, + }, + + i18n: { + NAME_ALREADY_EXISTS: s__('BulkImport|Name already exists.'), + }, +}; +</script> + +<template> + <gl-link + v-if="isFinished" + class="gl-display-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, + }" + > + <import-group-dropdown + #default="{ namespaces }" + :text="importTarget.target_namespace" + :disabled="isAlreadyImported" + :namespaces="availableNamespaceNames" + 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', '')">{{ + 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" + data-qa-selector="target_group_dropdown_item" + :data-qa-group-name="ns" + @click="$emit('update-target-namespace', ns)" + > + {{ ns }} + </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': isAlreadyImported, + 'gl-border-gray-200': !isAlreadyImported, + }" + > + / + </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!': !isAlreadyImported, + 'gl-inset-border-1-gray-100!': isAlreadyImported, + 'is-invalid': isInvalid && !isAlreadyImported, + }" + :disabled="isAlreadyImported" + :value="importTarget.new_name" + @input="$emit('update-new-name', $event)" + /> + <p v-if="isInvalid" class="gl-text-red-500 gl-m-0 gl-mt-2"> + <template v-if="!isNameValid"> + {{ groupUrlErrorMessage }} + </template> + <template v-else-if="invalidNameValidationMessage"> + {{ invalidNameValidationMessage }} + </template> + </p> + </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 d920e87aac8..b2c3d85e280 100644 --- a/app/assets/javascripts/import_entities/import_groups/constants.js +++ b/app/assets/javascripts/import_entities/import_groups/constants.js @@ -3,3 +3,5 @@ import { s__ } from '~/locale'; export const i18n = { NAME_ALREADY_EXISTS: s__('BulkImport|Name already exists.'), }; + +export const NEW_NAME_FIELD = 'new_name'; 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 c608aa164d1..57188441158 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,7 +4,7 @@ import axios from '~/lib/utils/axios_utils'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; import { STATUSES } from '../../constants'; -import { i18n } from '../constants'; +import { i18n, NEW_NAME_FIELD } from '../constants'; import bulkImportSourceGroupItemFragment from './fragments/bulk_import_source_group_item.fragment.graphql'; import addValidationErrorMutation from './mutations/add_validation_error.mutation.graphql'; import removeValidationErrorMutation from './mutations/remove_validation_error.mutation.graphql'; @@ -61,7 +61,7 @@ async function checkImportTargetIsValid({ client, newName, targetNamespace, sour }); const variables = { - field: 'new_name', + field: NEW_NAME_FIELD, sourceGroupId, }; diff --git a/app/assets/javascripts/import_entities/import_groups/utils.js b/app/assets/javascripts/import_entities/import_groups/utils.js new file mode 100644 index 00000000000..b451008b6f9 --- /dev/null +++ b/app/assets/javascripts/import_entities/import_groups/utils.js @@ -0,0 +1,13 @@ +import { NEW_NAME_FIELD } from './constants'; + +export function isNameValid(group, validationRegex) { + return validationRegex.test(group.import_target[NEW_NAME_FIELD]); +} + +export function getInvalidNameValidationMessage(group) { + return group.validation_errors.find(({ field }) => field === NEW_NAME_FIELD)?.message; +} + +export function isInvalid(group, validationRegex) { + return Boolean(!isNameValid(group, validationRegex) || getInvalidNameValidationMessage(group)); +} |