diff options
Diffstat (limited to 'app/assets/javascripts/import_entities')
6 files changed, 146 insertions, 53 deletions
diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue index cc6a057f587..9262a4e1e95 100644 --- a/app/assets/javascripts/import_entities/components/import_status.vue +++ b/app/assets/javascripts/import_entities/components/import_status.vue @@ -1,10 +1,66 @@ <script> -import { GlIcon } from '@gitlab/ui'; -import STATUS_MAP from '../constants'; +import { GlAccordion, GlAccordionItem, GlBadge, GlIcon } from '@gitlab/ui'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { __, s__ } from '~/locale'; +import { STATUSES } from '../constants'; + +const STATISTIC_ITEMS = { + diff_note: __('Diff notes'), + issue: __('Issues'), + label: __('Labels'), + milestone: __('Milestones'), + note: __('Notes'), + pull_request: s__('GithubImporter|Pull requests'), + pull_request_merged_by: s__('GithubImporter|PR mergers'), + pull_request_review: s__('GithubImporter|PR reviews'), + release: __('Releases'), +}; + +// support both camel case and snake case versions +Object.assign(STATISTIC_ITEMS, convertObjectPropsToCamelCase(STATISTIC_ITEMS)); + +const SCHEDULED_STATUS = { + icon: 'status-scheduled', + text: __('Pending'), + variant: 'muted', +}; + +const STATUS_MAP = { + [STATUSES.NONE]: { + icon: 'status-waiting', + text: __('Not started'), + variant: 'muted', + }, + [STATUSES.SCHEDULING]: SCHEDULED_STATUS, + [STATUSES.SCHEDULED]: SCHEDULED_STATUS, + [STATUSES.CREATED]: SCHEDULED_STATUS, + [STATUSES.STARTED]: { + icon: 'status-running', + text: __('Importing...'), + variant: 'info', + }, + [STATUSES.FAILED]: { + icon: 'status-failed', + text: __('Failed'), + variant: 'danger', + }, + [STATUSES.CANCELLED]: { + icon: 'status-stopped', + text: __('Cancelled'), + variant: 'neutral', + }, +}; + +function isIncompleteImport(stats) { + return Object.keys(stats.fetched).some((key) => stats.fetched[key] !== stats.imported[key]); +} export default { name: 'ImportStatus', components: { + GlAccordion, + GlAccordionItem, + GlBadge, GlIcon, }, props: { @@ -12,19 +68,88 @@ export default { type: String, required: true, }, + stats: { + type: Object, + required: false, + default: () => ({ fetched: {}, imported: {} }), + }, }, computed: { + knownStats() { + const knownStatisticKeys = Object.keys(STATISTIC_ITEMS); + return Object.keys(this.stats.fetched).filter((key) => knownStatisticKeys.includes(key)); + }, + + hasStats() { + return this.stats && this.knownStats.length > 0; + }, + mappedStatus() { + if (this.status === STATUSES.FINISHED) { + const isIncomplete = this.stats && isIncompleteImport(this.stats); + return { + icon: 'status-success', + ...(isIncomplete + ? { + text: __('Partial import'), + variant: 'warning', + } + : { + text: __('Complete'), + variant: 'success', + }), + }; + } + return STATUS_MAP[this.status]; }, }, + + methods: { + getStatisticIconProps(key) { + const fetched = this.stats.fetched[key]; + const imported = this.stats.imported[key]; + + if (fetched === imported) { + return { name: 'status-success', class: 'gl-text-green-400' }; + } else if (imported === 0) { + return { name: 'status-scheduled', class: 'gl-text-gray-400' }; + } + + return { name: 'status-running', class: 'gl-text-blue-400' }; + }, + }, + + STATISTIC_ITEMS, }; </script> <template> <div> - <gl-icon :name="mappedStatus.icon" :class="mappedStatus.iconClass" :size="12" class="gl-mr-2" /> - <span>{{ mappedStatus.text }}</span> + <div class="gl-display-inline-block gl-w-13"> + <gl-badge :icon="mappedStatus.icon" :variant="mappedStatus.variant" size="md" class="gl-mr-2"> + {{ mappedStatus.text }} + </gl-badge> + </div> + <gl-accordion v-if="hasStats" :header-level="3"> + <gl-accordion-item :title="__('Details')"> + <ul class="gl-p-0 gl-list-style-none gl-font-sm"> + <li v-for="key in knownStats" :key="key"> + <div class="gl-display-flex gl-w-20 gl-align-items-center"> + <gl-icon + :size="12" + class="gl-mr-3 gl-flex-shrink-0" + v-bind="getStatisticIconProps(key)" + /> + <span class="">{{ $options.STATISTIC_ITEMS[key] }}</span> + <span class="gl-ml-auto"> + {{ stats.imported[key] || 0 }}/{{ stats.fetched[key] }} + </span> + </div> + </li> + </ul> + </gl-accordion-item> + </gl-accordion> </div> </template> diff --git a/app/assets/javascripts/import_entities/constants.js b/app/assets/javascripts/import_entities/constants.js index 156e92e2d00..20a4d2d84b4 100644 --- a/app/assets/javascripts/import_entities/constants.js +++ b/app/assets/javascripts/import_entities/constants.js @@ -1,5 +1,3 @@ -import { __ } from '../locale'; - // The `scheduling` status is only present on the client-side, // it is used as the status when we are requesting to start an import. @@ -13,42 +11,3 @@ export const STATUSES = { SCHEDULING: 'scheduling', CANCELLED: 'cancelled', }; - -const SCHEDULED_STATUS = { - icon: 'status-scheduled', - text: __('Pending'), - iconClass: 'gl-text-orange-400', -}; - -const STATUS_MAP = { - [STATUSES.NONE]: { - icon: 'status-waiting', - text: __('Not started'), - iconClass: 'gl-text-gray-400', - }, - [STATUSES.SCHEDULING]: SCHEDULED_STATUS, - [STATUSES.SCHEDULED]: SCHEDULED_STATUS, - [STATUSES.CREATED]: SCHEDULED_STATUS, - [STATUSES.STARTED]: { - icon: 'status-running', - text: __('Importing...'), - iconClass: 'gl-text-blue-400', - }, - [STATUSES.FINISHED]: { - icon: 'status-success', - text: __('Complete'), - iconClass: 'gl-text-green-400', - }, - [STATUSES.FAILED]: { - icon: 'status-failed', - text: __('Failed'), - iconClass: 'gl-text-red-600', - }, - [STATUSES.CANCELLED]: { - icon: 'status-stopped', - text: __('Cancelled'), - iconClass: 'gl-text-red-600', - }, -}; - -export default STATUS_MAP; diff --git a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue index bd0f4cd5dd7..e0703a77424 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/import_projects_table.vue @@ -109,7 +109,7 @@ export default { </template> <div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap gl-mb-5"> <gl-button - variant="success" + variant="confirm" :loading="isImportingAnyRepo" :disabled="!hasImportableRepos" type="button" diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue index c3d0ca4ed8c..e4090a378e1 100644 --- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue +++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue @@ -69,6 +69,10 @@ export default { return getImportStatus(this.repo); }, + stats() { + return this.repo.importedProject?.stats; + }, + importTarget() { return this.getImportTarget(this.repo.importSource.id); }, @@ -101,11 +105,11 @@ export default { <template> <tr - class="gl-h-11 gl-border-0 gl-border-solid gl-border-t-1 gl-border-gray-100 gl-h-11" + class="gl-h-11 gl-border-0 gl-border-solid gl-border-t-1 gl-border-gray-100 gl-h-11 gl-vertical-align-top" data-qa-selector="project_import_row" :data-qa-source-project="repo.importSource.fullName" > - <td class="gl-p-4"> + <td class="gl-p-4 gl-vertical-align-top"> <gl-link :href="repo.importSource.providerLink" target="_blank" data-testid="providerLink" >{{ repo.importSource.fullName }} <gl-icon v-if="repo.importSource.providerLink" name="external-link" /> @@ -156,10 +160,10 @@ export default { </template> <template v-else-if="repo.importedProject">{{ displayFullPath }}</template> </td> - <td class="gl-p-4" data-qa-selector="import_status_indicator"> - <import-status :status="importStatus" /> + <td class="gl-p-4 gl-vertical-align-top" data-qa-selector="import_status_indicator"> + <import-status :status="importStatus" :stats="stats" /> </td> - <td data-testid="actions"> + <td data-testid="actions" class="gl-vertical-align-top gl-pt-4"> <gl-button v-if="isFinished" class="btn btn-default" diff --git a/app/assets/javascripts/import_entities/import_projects/index.js b/app/assets/javascripts/import_entities/import_projects/index.js index 110cc77b20d..5146a0eb461 100644 --- a/app/assets/javascripts/import_entities/import_projects/index.js +++ b/app/assets/javascripts/import_entities/import_projects/index.js @@ -16,12 +16,13 @@ export function initStoreFromElement(element) { jobsPath, importPath, namespacesPath, + defaultTargetNamespace, paginatable, } = element.dataset; return createStore({ initialState: { - defaultTargetNamespace: gon.current_username, + defaultTargetNamespace, ciCdOnly: parseBoolean(ciCdOnly), canSelectNamespace: parseBoolean(canSelectNamespace), provider, diff --git a/app/assets/javascripts/import_entities/import_projects/store/mutations.js b/app/assets/javascripts/import_entities/import_projects/store/mutations.js index 45f7a684161..163a19976de 100644 --- a/app/assets/javascripts/import_entities/import_projects/store/mutations.js +++ b/app/assets/javascripts/import_entities/import_projects/store/mutations.js @@ -113,7 +113,11 @@ export default { updatedProjects.forEach((updatedProject) => { const repo = state.repositories.find((p) => p.importedProject?.id === updatedProject.id); if (repo?.importedProject) { - repo.importedProject.importStatus = updatedProject.importStatus; + repo.importedProject = { + ...repo.importedProject, + stats: updatedProject.stats, + importStatus: updatedProject.importStatus, + }; } }); }, |