diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 09:16:11 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 09:16:11 +0000 |
commit | edaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch) | |
tree | 11f143effbfeba52329fb7afbd05e6e2a3790241 /app/assets/javascripts/packages_and_registries | |
parent | d8a5691316400a0f7ec4f83832698f1988eb27c1 (diff) | |
download | gitlab-ce-edaa33dee2ff2f7ea3fac488d41558eb5f86d68c.tar.gz |
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
27 files changed, 377 insertions, 194 deletions
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue index d988ad8d8ca..29c181f04fb 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue @@ -143,6 +143,7 @@ export default { </template> <template #right-actions> <gl-dropdown + v-if="!deleteButtonDisabled" icon="ellipsis_v" text="More actions" :text-sr-only="true" @@ -150,11 +151,7 @@ export default { no-caret right > - <gl-dropdown-item - variant="danger" - :disabled="deleteButtonDisabled" - @click="$emit('delete')" - > + <gl-dropdown-item variant="danger" @click="$emit('delete')"> {{ __('Delete image repository') }} </gl-dropdown-item> </gl-dropdown> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue deleted file mode 100644 index a16d95a6b30..00000000000 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue +++ /dev/null @@ -1,44 +0,0 @@ -<script> -import { GlEmptyState } from '@gitlab/ui'; -import { - NO_TAGS_TITLE, - NO_TAGS_MESSAGE, - MISSING_OR_DELETED_IMAGE_TITLE, - MISSING_OR_DELETED_IMAGE_MESSAGE, -} from '../../constants/index'; - -export default { - components: { - GlEmptyState, - }, - props: { - noContainersImage: { - type: String, - required: false, - default: '', - }, - isEmptyImage: { - type: Boolean, - default: false, - required: false, - }, - }, - computed: { - title() { - return this.isEmptyImage ? MISSING_OR_DELETED_IMAGE_TITLE : NO_TAGS_TITLE; - }, - description() { - return this.isEmptyImage ? MISSING_OR_DELETED_IMAGE_MESSAGE : NO_TAGS_MESSAGE; - }, - }, -}; -</script> - -<template> - <gl-empty-state - :title="title" - :svg-path="noContainersImage" - :description="description" - class="gl-mx-auto gl-my-0" - /> -</template> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue index 2d32295b537..4fda4058711 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue @@ -1,28 +1,38 @@ <script> +import { GlEmptyState } from '@gitlab/ui'; import createFlash from '~/flash'; import { n__ } from '~/locale'; import { joinPaths } from '~/lib/utils/url_utility'; import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue'; + +import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue'; +import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants'; import { REMOVE_TAGS_BUTTON_TITLE, TAGS_LIST_TITLE, GRAPHQL_PAGE_SIZE, FETCH_IMAGES_LIST_ERROR_MESSAGE, + NAME_SORT_FIELD, + NO_TAGS_TITLE, + NO_TAGS_MESSAGE, + NO_TAGS_MATCHING_FILTERS_TITLE, + NO_TAGS_MATCHING_FILTERS_DESCRIPTION, } from '../../constants/index'; import getContainerRepositoryTagsQuery from '../../graphql/queries/get_container_repository_tags.query.graphql'; -import EmptyState from './empty_state.vue'; import TagsListRow from './tags_list_row.vue'; import TagsLoader from './tags_loader.vue'; export default { name: 'TagsList', components: { + GlEmptyState, TagsListRow, - EmptyState, TagsLoader, RegistryList, + PersistedSearch, }, inject: ['config'], + props: { id: { type: [Number, String], @@ -44,6 +54,7 @@ export default { required: false, }, }, + searchConfig: { NAME_SORT_FIELD }, i18n: { REMOVE_TAGS_BUTTON_TITLE, TAGS_LIST_TITLE, @@ -51,6 +62,9 @@ export default { apollo: { containerRepository: { query: getContainerRepositoryTagsQuery, + skip() { + return !this.sort; + }, variables() { return this.queryVariables; }, @@ -62,6 +76,8 @@ export default { data() { return { containerRepository: {}, + filters: {}, + sort: null, }; }, computed: { @@ -78,6 +94,8 @@ export default { return { id: joinPaths(this.config.gidPrefix, `${this.id}`), first: GRAPHQL_PAGE_SIZE, + name: this.filters?.name, + sort: this.sort, }; }, showMultiDeleteButton() { @@ -87,7 +105,16 @@ export default { return this.tags.length === 0; }, isLoading() { - return this.isImageLoading || this.$apollo.queries.containerRepository.loading; + return this.isImageLoading || this.$apollo.queries.containerRepository.loading || !this.sort; + }, + hasFilters() { + return this.filters?.name; + }, + emptyStateTitle() { + return this.hasFilters ? NO_TAGS_MATCHING_FILTERS_TITLE : NO_TAGS_TITLE; + }, + emptyStateDescription() { + return this.hasFilters ? NO_TAGS_MATCHING_FILTERS_DESCRIPTION : NO_TAGS_MESSAGE; }, }, methods: { @@ -114,15 +141,47 @@ export default { }, }); }, + handleSearchUpdate({ sort, filters }) { + this.sort = sort; + + const parsed = { + name: '', + }; + + // This takes in account the fact that we will be adding more filters types + // this is why is an object and not an array or a simple string + this.filters = filters.reduce((acc, filter) => { + if (filter.type === FILTERED_SEARCH_TERM) { + return { + ...acc, + name: `${acc.name} ${filter.value.data}`.trim(), + }; + } + return acc; + }, parsed); + }, }, }; </script> <template> <div> + <persisted-search + class="gl-mb-5" + :sortable-fields="[$options.searchConfig.NAME_SORT_FIELD]" + :default-order="$options.searchConfig.NAME_SORT_FIELD.orderBy" + default-sort="asc" + @update="handleSearchUpdate" + /> <tags-loader v-if="isLoading" /> <template v-else> - <empty-state v-if="hasNoTags" :no-containers-image="config.noContainersImage" /> + <gl-empty-state + v-if="hasNoTags" + :title="emptyStateTitle" + :svg-path="config.noContainersImage" + :description="emptyStateDescription" + class="gl-mx-auto gl-my-0" + /> <template v-else> <registry-list :title="listTitle" diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue index 0556fd298aa..15d92ab0ef7 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue @@ -107,11 +107,8 @@ export default { isInvalidTag() { return !this.tag.digest; }, - isCheckboxDisabled() { - return this.isInvalidTag || this.disabled; - }, isDeleteDisabled() { - return this.isInvalidTag || this.disabled || !this.tag.canDelete; + return this.disabled || !this.tag.canDelete; }, }, }; @@ -122,7 +119,7 @@ export default { <template #left-action> <gl-form-checkbox v-if="tag.canDelete" - :disabled="isCheckboxDisabled" + :disabled="disabled" class="gl-m-0" :checked="selected" @change="$emit('select')" diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js index f7beec2c935..17adaec7a7d 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js @@ -2,3 +2,5 @@ import { s__, __ } from '~/locale'; export const ROOT_IMAGE_TEXT = s__('ContainerRegistry|Root image'); export const MORE_ACTIONS_TEXT = __('More actions'); + +export const NAME_SORT_FIELD = { orderBy: 'NAME', label: __('Name') }; diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js index 19e1a75fb2f..8b8769a884d 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js @@ -116,6 +116,13 @@ export const ROOT_IMAGE_TOOLTIP = s__( 'ContainerRegistry|Image repository with no name located at the project URL.', ); +export const NO_TAGS_MATCHING_FILTERS_TITLE = s__( + 'ContainerRegistry|The filter returned no results', +); +export const NO_TAGS_MATCHING_FILTERS_DESCRIPTION = s__( + 'ContainerRegistry|Please try different search criteria', +); + // Parameters export const DEFAULT_PAGE = 1; diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js index d21a154d1b8..7fa950ccfd0 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js @@ -1,4 +1,5 @@ import { s__, __ } from '~/locale'; +import { NAME_SORT_FIELD } from './common'; // Translations strings @@ -49,5 +50,5 @@ export const GRAPHQL_PAGE_SIZE = 10; export const SORT_FIELDS = [ { orderBy: 'UPDATED', label: __('Updated') }, { orderBy: 'CREATED', label: __('Created') }, - { orderBy: 'NAME', label: __('Name') }, + NAME_SORT_FIELD, ]; diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql index 502382010f9..d753d33a02c 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql @@ -6,11 +6,13 @@ query getContainerRepositoryTags( $last: Int $after: String $before: String + $name: String + $sort: ContainerRepositoryTagSort ) { containerRepository(id: $id) { id tagsCount - tags(after: $after, before: $before, first: $first, last: $last) { + tags(after: $after, before: $before, first: $first, last: $last, name: $name, sort: $sort) { nodes { digest location diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js index 246a6768593..ca5bd8d6964 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js @@ -3,7 +3,8 @@ import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; import PerformancePlugin from '~/performance/vue_performance_plugin'; import Translate from '~/vue_shared/translate'; -import RegistryBreadcrumb from './components/registry_breadcrumb.vue'; +import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue'; +import { renderBreadcrumb } from '~/packages_and_registries/shared/utils'; import { apolloProvider } from './graphql/index'; import RegistryExplorer from './pages/index.vue'; import createRouter from './router'; @@ -84,38 +85,8 @@ export default () => { }, }); - const attachBreadcrumb = () => { - const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li'); - const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1]; - const crumbs = [breadCrumbEl.querySelector('h2')]; - const nestedBreadcrumbEl = document.createElement('div'); - breadCrumbEl.replaceChild(nestedBreadcrumbEl, breadCrumbEl.querySelector('h2')); - return new Vue({ - el: nestedBreadcrumbEl, - router, - apolloProvider, - components: { - RegistryBreadcrumb, - }, - render(createElement) { - // FIXME(@tnir): this is a workaround until the MR gets merged: - // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115 - const parentEl = breadCrumbEl.parentElement.parentElement; - if (parentEl) { - parentEl.classList.remove('breadcrumbs-container'); - parentEl.classList.add('gl-display-flex'); - parentEl.classList.add('w-100'); - } - // End of FIXME(@tnir) - return createElement('registry-breadcrumb', { - class: breadCrumbEl.className, - props: { - crumbs, - }, - }); - }, - }); + return { + attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb), + attachMainComponent, }; - - return { attachBreadcrumb, attachMainComponent }; }; diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue index bc6e3091f0e..bb687ffdb89 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue @@ -1,5 +1,5 @@ <script> -import { GlResizeObserverDirective } from '@gitlab/ui'; +import { GlResizeObserverDirective, GlEmptyState } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; @@ -9,7 +9,6 @@ import DeleteImage from '../components/delete_image.vue'; import DeleteAlert from '../components/details_page/delete_alert.vue'; import DeleteModal from '../components/details_page/delete_modal.vue'; import DetailsHeader from '../components/details_page/details_header.vue'; -import EmptyState from '../components/details_page/empty_state.vue'; import PartialCleanupAlert from '../components/details_page/partial_cleanup_alert.vue'; import StatusAlert from '../components/details_page/status_alert.vue'; import TagsList from '../components/details_page/tags_list.vue'; @@ -26,6 +25,8 @@ import { MISSING_OR_DELETED_IMAGE_BREADCRUMB, ROOT_IMAGE_TEXT, GRAPHQL_PAGE_SIZE, + MISSING_OR_DELETED_IMAGE_TITLE, + MISSING_OR_DELETED_IMAGE_MESSAGE, } from '../constants/index'; import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql'; import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql'; @@ -34,13 +35,13 @@ import getContainerRepositoryTagsQuery from '../graphql/queries/get_container_re export default { name: 'RegistryDetailsPage', components: { + GlEmptyState, DeleteAlert, PartialCleanupAlert, DetailsHeader, DeleteModal, TagsList, TagsLoader, - EmptyState, StatusAlert, DeleteImage, }, @@ -49,6 +50,10 @@ export default { }, mixins: [Tracking.mixin()], inject: ['breadCrumbState', 'config'], + i18n: { + MISSING_OR_DELETED_IMAGE_TITLE, + MISSING_OR_DELETED_IMAGE_MESSAGE, + }, apollo: { containerRepository: { query: getContainerRepositoryDetailsQuery, @@ -230,6 +235,12 @@ export default { @cancel="track('cancel_delete')" /> </template> - <empty-state v-else is-empty-image :no-containers-image="config.noContainersImage" /> + <gl-empty-state + v-else + :title="$options.i18n.MISSING_OR_DELETED_IMAGE_TITLE" + :description="$options.i18n.MISSING_OR_DELETED_IMAGE_MESSAGE" + :svg-path="config.noContainersImage" + class="gl-mx-auto gl-my-0" + /> </div> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue index cc629ae394c..a482c29bf50 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue @@ -6,6 +6,7 @@ import { TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + COMPOSER_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -17,7 +18,7 @@ export default { GlLink, GlSprintf, }, - inject: ['composerHelpPath', 'composerConfigRepositoryName', 'composerPath', 'groupListUrl'], + inject: ['groupListUrl'], props: { packageEntity: { type: Object, @@ -27,7 +28,7 @@ export default { computed: { composerRegistryInclude() { // eslint-disable-next-line @gitlab/require-i18n-strings - return `composer config repositories.${this.composerConfigRepositoryName} '{"type": "composer", "url": "${this.composerPath}"}'`; + return `composer config repositories.${this.packageEntity.composerConfigRepositoryUrl} '{"type": "composer", "url": "${this.packageEntity.composerUrl}"}'`; }, composerPackageInclude() { // eslint-disable-next-line @gitlab/require-i18n-strings @@ -51,6 +52,9 @@ export default { TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, }, + links: { + COMPOSER_HELP_PATH, + }, installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }], }; </script> @@ -79,7 +83,7 @@ export default { <span data-testid="help-text"> <gl-sprintf :message="$options.i18n.infoLine"> <template #link="{ content }"> - <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.COMPOSER_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </span> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue index 99e27c9d44a..ba0a3fcf5a1 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue @@ -6,6 +6,7 @@ import { TRACKING_ACTION_COPY_CONAN_COMMAND, TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + CONAN_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -17,7 +18,6 @@ export default { GlLink, GlSprintf, }, - inject: ['conanHelpPath', 'conanPath'], props: { packageEntity: { type: Object, @@ -31,7 +31,7 @@ export default { }, conanSetupCommand() { // eslint-disable-next-line @gitlab/require-i18n-strings - return `conan remote add gitlab ${this.conanPath}`; + return `conan remote add gitlab ${this.packageEntity.conanUrl}`; }, }, i18n: { @@ -44,7 +44,7 @@ export default { TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, }, - + links: { CONAN_HELP_PATH }, installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }], }; </script> @@ -72,7 +72,7 @@ export default { /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="conanHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.CONAN_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue index 2070f0bbca0..4510c7a7322 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue @@ -12,6 +12,7 @@ import { TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, TRACKING_LABEL_MAVEN_INSTALLATION, + MAVEN_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -23,7 +24,6 @@ export default { GlLink, GlSprintf, }, - inject: ['mavenHelpPath', 'mavenPath'], props: { packageEntity: { type: Object, @@ -36,6 +36,9 @@ export default { }; }, computed: { + mavenUrl() { + return this.packageEntity.mavenUrl; + }, appGroup() { return this.packageEntity.metadata.appGroup; }, @@ -61,19 +64,19 @@ export default { return `<repositories> <repository> <id>gitlab-maven</id> - <url>${this.mavenPath}</url> + <url>${this.mavenUrl}</url> </repository> </repositories> <distributionManagement> <repository> <id>gitlab-maven</id> - <url>${this.mavenPath}</url> + <url>${this.mavenUrl}</url> </repository> <snapshotRepository> <id>gitlab-maven</id> - <url>${this.mavenPath}</url> + <url>${this.mavenUrl}</url> </snapshotRepository> </distributionManagement>`; }, @@ -86,7 +89,7 @@ export default { gradleGroovyAddSourceCommand() { // eslint-disable-next-line @gitlab/require-i18n-strings return `maven { - url '${this.mavenPath}' + url '${this.mavenUrl}' }`; }, @@ -95,7 +98,7 @@ export default { }, gradleKotlinAddSourceCommand() { - return `maven("${this.mavenPath}")`; + return `maven("${this.mavenUrl}")`; }, showMaven() { return this.instructionType === 'maven'; @@ -126,7 +129,7 @@ export default { TRACKING_LABEL_CODE_INSTRUCTION, TRACKING_LABEL_MAVEN_INSTALLATION, }, - + links: { MAVEN_HELP_PATH }, installOptions: [ { value: 'maven', label: s__('PackageRegistry|Maven XML') }, { value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') }, @@ -185,7 +188,7 @@ export default { /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.MAVEN_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue index 2448324549e..7479f748a56 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue @@ -13,6 +13,7 @@ import { YARN_PACKAGE_MANAGER, PROJECT_PACKAGE_ENDPOINT_TYPE, INSTANCE_PACKAGE_ENDPOINT_TYPE, + NPM_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -25,7 +26,7 @@ export default { GlSprintf, GlFormRadioGroup, }, - inject: ['npmHelpPath', 'npmPath', 'npmProjectPath'], + inject: ['npmInstanceUrl'], props: { packageEntity: { type: Object, @@ -65,7 +66,9 @@ export default { npmSetupCommand(type, endpointType) { const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/')); const npmPathForEndpoint = - endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE ? this.npmPath : this.npmProjectPath; + endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE + ? this.npmInstanceUrl + : this.packageEntity.npmUrl; if (type === NPM_PACKAGE_MANAGER) { return `echo ${scope}:registry=${npmPathForEndpoint}/ >> .npmrc`; @@ -89,6 +92,7 @@ export default { 'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.', ), }, + links: { NPM_HELP_PATH }, installOptions: [ { value: NPM_PACKAGE_MANAGER, label: s__('PackageRegistry|Show NPM commands') }, { value: YARN_PACKAGE_MANAGER, label: s__('PackageRegistry|Show Yarn commands') }, @@ -150,7 +154,7 @@ export default { <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="npmHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.NPM_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue index 2e9991b7be5..b2007df142c 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue @@ -6,6 +6,7 @@ import { TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND, TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + NUGET_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -17,7 +18,6 @@ export default { GlLink, GlSprintf, }, - inject: ['nugetHelpPath', 'nugetPath'], props: { packageEntity: { type: Object, @@ -29,7 +29,7 @@ export default { return `nuget install ${this.packageEntity.name} -Source "GitLab"`; }, nugetSetupCommand() { - return `nuget source Add -Name "GitLab" -Source "${this.nugetPath}" -UserName <your_username> -Password <your_token>`; + return `nuget source Add -Name "GitLab" -Source "${this.packageEntity.nugetUrl}" -UserName <your_username> -Password <your_token>`; }, }, tracking: { @@ -42,6 +42,7 @@ export default { 'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.', ), }, + links: { NUGET_HELP_PATH }, installOptions: [{ value: 'nuget', label: s__('PackageRegistry|Show Nuget commands') }], }; </script> @@ -68,7 +69,7 @@ export default { /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="nugetHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.NUGET_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue index bf7fe6fb91b..3724e371e01 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue @@ -22,8 +22,12 @@ export default { FileSha, }, mixins: [Tracking.mixin()], - inject: ['canDelete'], props: { + canDelete: { + type: Boolean, + required: false, + default: false, + }, packageFiles: { type: Array, required: false, diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue index 669adab9df6..a126d30f1ec 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue @@ -7,6 +7,7 @@ import { TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND, TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND, TRACKING_LABEL_CODE_INSTRUCTION, + PYPI_HELP_PATH, } from '~/packages_and_registries/package_registry/constants'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; @@ -18,7 +19,6 @@ export default { GlLink, GlSprintf, }, - inject: ['pypiHelpPath', 'pypiPath', 'pypiSetupPath'], props: { packageEntity: { type: Object, @@ -28,11 +28,11 @@ export default { computed: { pypiPipCommand() { // eslint-disable-next-line @gitlab/require-i18n-strings - return `pip install ${this.packageEntity.name} --extra-index-url ${this.pypiPath}`; + return `pip install ${this.packageEntity.name} --extra-index-url ${this.packageEntity.pypiUrl}`; }, pypiSetupCommand() { return `[gitlab] -repository = ${this.pypiSetupPath} +repository = ${this.packageEntity.pypiSetupUrl} username = __token__ password = <your personal access token>`; }, @@ -50,6 +50,7 @@ password = <your personal access token>`; 'PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}.', ), }, + links: { PYPI_HELP_PATH }, installOptions: [{ value: 'pypi', label: s__('PackageRegistry|Show PyPi commands') }], }; </script> @@ -86,7 +87,7 @@ password = <your personal access token>`; /> <gl-sprintf :message="$options.i18n.helpText"> <template #link="{ content }"> - <gl-link :href="pypiHelpPath" target="_blank">{{ content }}</gl-link> + <gl-link :href="$options.links.PYPI_HELP_PATH" target="_blank">{{ content }}</gl-link> </template> </gl-sprintf> </div> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue index 6fd96c0654f..6222c2e73d7 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; +import { GlButton, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import { @@ -18,7 +18,6 @@ export default { name: 'PackageListRow', components: { GlButton, - GlLink, GlSprintf, GlTruncate, PackageTags, @@ -42,9 +41,8 @@ export default { packageType() { return getPackageTypeLabel(this.packageEntity.packageType); }, - packageLink() { - const { project, id } = this.packageEntity; - return `${project?.webUrl}/-/packages/${getIdFromGraphQLId(id)}`; + packageId() { + return getIdFromGraphQLId(this.packageEntity.id); }, pipeline() { return this.packageEntity?.pipelines?.nodes[0]; @@ -61,6 +59,9 @@ export default { disabledRow() { return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS; }, + routerLinkEvent() { + return this.disabledRow ? '' : 'click'; + }, }, i18n: { erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'), @@ -73,14 +74,15 @@ export default { <list-item data-qa-selector="package_row" :disabled="disabledRow"> <template #left-primary> <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"> - <gl-link - :href="packageLink" + <router-link class="gl-text-body gl-min-w-0" + data-testid="details-link" data-qa-selector="package_link" - :disabled="disabledRow" + :event="routerLinkEvent" + :to="{ name: 'details', params: { id: packageId } }" > <gl-truncate :text="packageEntity.name" /> - </gl-link> + </router-link> <gl-button v-if="showWarningIcon" diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js index ab6541e4264..c4d331fa384 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js @@ -74,6 +74,7 @@ export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__( ); export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully'); +export const PACKAGE_REGISTRY_TITLE = __('Package Registry'); export const PACKAGE_ERROR_STATUS = 'ERROR'; export const PACKAGE_DEFAULT_STATUS = 'DEFAULT'; @@ -142,3 +143,9 @@ export const PACKAGE_TYPES = [ export const EMPTY_LIST_HELP_URL = helpPagePath('user/packages/package_registry/index'); export const PACKAGE_HELP_URL = helpPagePath('user/packages/index'); +export const NPM_HELP_PATH = helpPagePath('user/packages/npm_registry/index'); +export const MAVEN_HELP_PATH = helpPagePath('user/packages/maven_repository/index'); +export const CONAN_HELP_PATH = helpPagePath('user/packages/conan_repository/index'); +export const NUGET_HELP_PATH = helpPagePath('user/packages/nuget_repository/index'); +export const PYPI_HELP_PATH = helpPagePath('user/packages/pypi_repository/index'); +export const COMPOSER_HELP_PATH = helpPagePath('user/packages/composer_repository/index'); diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql index 08ea0938a59..c45cbe56e00 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql @@ -7,9 +7,19 @@ query getPackageDetails($id: ID!) { createdAt updatedAt status + canDestroy + npmUrl + mavenUrl + conanUrl + nugetUrl + pypiUrl + pypiSetupUrl + composerUrl + composerConfigRepositoryUrl project { id path + name } tags(first: 10) { nodes { diff --git a/app/assets/javascripts/packages_and_registries/package_registry/index.js b/app/assets/javascripts/packages_and_registries/package_registry/index.js index 7ec931ff9a0..6680e612985 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/index.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/index.js @@ -2,29 +2,59 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index'; import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue'; +import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue'; +import { renderBreadcrumb } from '~/packages_and_registries/shared/utils'; import createRouter from './router'; Vue.use(Translate); export default () => { const el = document.getElementById('js-vue-packages-list'); - const { endpoint, resourceId, fullPath, pageType, emptyListIllustration } = el.dataset; - const router = createRouter(endpoint); + const { + endpoint, + resourceId, + fullPath, + pageType, + emptyListIllustration, + npmInstanceUrl, + projectListUrl, + groupListUrl, + } = el.dataset; const isGroupPage = pageType === 'groups'; - return new Vue({ - el, - router, - apolloProvider, - provide: { - resourceId, - fullPath, - emptyListIllustration, - isGroupPage, - }, - render(createElement) { - return createElement(PackageRegistry); + // This is a mini state to help the breadcrumb have the correct name in the details page + const breadCrumbState = Vue.observable({ + name: '', + updateName(value) { + this.name = value; }, }); + + const router = createRouter(endpoint, breadCrumbState); + + const attachMainComponent = () => + new Vue({ + el, + router, + apolloProvider, + provide: { + resourceId, + fullPath, + emptyListIllustration, + isGroupPage, + npmInstanceUrl, + projectListUrl, + groupListUrl, + breadCrumbState, + }, + render(createElement) { + return createElement(PackageRegistry); + }, + }); + + return { + attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb), + attachMainComponent, + }; }; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js deleted file mode 100644 index d94bbd21035..00000000000 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js +++ /dev/null @@ -1,27 +0,0 @@ -import Vue from 'vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; -import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue'; -import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index'; -import Translate from '~/vue_shared/translate'; - -Vue.use(Translate); - -export default () => { - const el = document.getElementById('js-vue-packages-detail-new'); - if (!el) { - return null; - } - - const { canDelete, ...datasetOptions } = el.dataset; - return new Vue({ - el, - apolloProvider, - provide: { - canDelete: parseBoolean(canDelete), - ...datasetOptions, - }, - render(createElement) { - return createElement(PackagesApp); - }, - }); -}; diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue index d49c1be5202..162b420a784 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue @@ -68,16 +68,7 @@ export default { GlModal: GlModalDirective, }, mixins: [Tracking.mixin()], - inject: [ - 'packageId', - 'projectName', - 'canDelete', - 'svgPath', - 'npmPath', - 'npmHelpPath', - 'projectListUrl', - 'groupListUrl', - ], + inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl', 'breadCrumbState'], trackingActions: { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, @@ -100,7 +91,7 @@ export default { return this.queryVariables; }, update(data) { - return data.package; + return data.package || {}; }, error(error) { createFlash({ @@ -109,22 +100,33 @@ export default { error, }); }, + result() { + this.breadCrumbState.updateName( + `${this.packageEntity?.name} v ${this.packageEntity?.version}`, + ); + }, }, }, computed: { + projectName() { + return this.packageEntity.project?.name; + }, + packageId() { + return this.$route.params.id; + }, queryVariables() { return { id: convertToGraphQLId('Packages::Package', this.packageId), }; }, packageFiles() { - return this.packageEntity?.packageFiles?.nodes; + return this.packageEntity.packageFiles?.nodes; }, isLoading() { return this.$apollo.queries.packageEntity.loading; }, isValidPackage() { - return this.isLoading || Boolean(this.packageEntity?.name); + return this.isLoading || Boolean(this.packageEntity.name); }, tracking() { return { @@ -141,7 +143,7 @@ export default { return this.packageEntity.packageType === PACKAGE_TYPE_NUGET; }, showFiles() { - return this.packageEntity?.packageType !== PACKAGE_TYPE_COMPOSER; + return this.packageEntity.packageType !== PACKAGE_TYPE_COMPOSER; }, }, methods: { @@ -235,13 +237,13 @@ export default { v-if="!isValidPackage" :title="s__('PackageRegistry|Unable to load package')" :description="s__('PackageRegistry|There was a problem fetching the details for this package.')" - :svg-path="svgPath" + :svg-path="emptyListIllustration" /> <div v-else-if="!isLoading" class="packages-app"> <package-title :package-entity="packageEntity"> <template #delete-button> <gl-button - v-if="canDelete" + v-if="packageEntity.canDestroy" v-gl-modal="'delete-modal'" variant="danger" category="primary" @@ -265,6 +267,7 @@ export default { <package-files v-if="showFiles" + :can-delete="packageEntity.canDestroy" :package-files="packageFiles" @download-file="track($options.trackingActions.PULL_PACKAGE)" @delete-file="handleFileDelete" diff --git a/app/assets/javascripts/packages_and_registries/package_registry/router.js b/app/assets/javascripts/packages_and_registries/package_registry/router.js index ea5b740e879..c5ef4f70dd9 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/router.js +++ b/app/assets/javascripts/packages_and_registries/package_registry/router.js @@ -1,10 +1,12 @@ import Vue from 'vue'; import VueRouter from 'vue-router'; import List from '~/packages_and_registries/package_registry/pages/list.vue'; +import Details from '~/packages_and_registries/package_registry/pages/details.vue'; +import { PACKAGE_REGISTRY_TITLE } from '~/packages_and_registries/package_registry/constants'; Vue.use(VueRouter); -export default function createRouter(base) { +export default function createRouter(base, breadCrumbState) { const router = new VueRouter({ base, mode: 'history', @@ -13,9 +15,25 @@ export default function createRouter(base) { name: 'list', path: '/', component: List, + meta: { + nameGenerator: () => PACKAGE_REGISTRY_TITLE, + root: true, + }, + }, + { + name: 'details', + path: '/:id', + component: Details, + meta: { + nameGenerator: () => breadCrumbState.name, + }, }, ], }); + router.afterEach(() => { + breadCrumbState.updateName(''); + }); + return router; } diff --git a/app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue b/app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue new file mode 100644 index 00000000000..9b2de1a1b84 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue @@ -0,0 +1,80 @@ +<script> +import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue'; +import UrlSync from '~/vue_shared/components/url_sync.vue'; +import { extractFilterAndSorting, getQueryParams } from '~/packages_and_registries/shared/utils'; + +export default { + components: { RegistrySearch, UrlSync }, + props: { + sortableFields: { + type: Array, + required: true, + }, + defaultOrder: { + type: String, + required: true, + }, + defaultSort: { + type: String, + required: true, + }, + }, + data() { + return { + filters: [], + sorting: { + orderBy: this.defaultOrder, + sort: this.defaultSort, + }, + mountRegistrySearch: false, + }; + }, + computed: { + parsedSorting() { + const cleanOrderBy = this.sorting?.orderBy.replace('_at', ''); + return `${cleanOrderBy}_${this.sorting?.sort}`.toUpperCase(); + }, + }, + mounted() { + const queryParams = getQueryParams(window.document.location.search); + const { sorting, filters } = extractFilterAndSorting(queryParams); + this.updateSorting(sorting); + this.updateFilters(filters); + this.mountRegistrySearch = true; + this.emitUpdate(); + }, + methods: { + updateFilters(newValue) { + this.filters = newValue; + }, + updateSorting(newValue) { + this.sorting = { ...this.sorting, ...newValue }; + }, + updateSortingAndEmitUpdate(newValue) { + this.updateSorting(newValue); + this.emitUpdate(); + }, + emitUpdate() { + this.$emit('update', { sort: this.parsedSorting, filters: this.filters }); + }, + }, +}; +</script> + +<template> + <url-sync> + <template #default="{ updateQuery }"> + <registry-search + v-if="mountRegistrySearch" + :filter="filters" + :sorting="sorting" + :tokens="$options.tokens" + :sortable-fields="sortableFields" + @sorting:changed="updateSortingAndEmitUpdate" + @filter:changed="updateFilters" + @filter:submit="emitUpdate" + @query:changed="updateQuery" + /> + </template> + </url-sync> +</template> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/registry_breadcrumb.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue index e77eda31596..a1e3c06812c 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/registry_breadcrumb.vue +++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue @@ -20,8 +20,11 @@ export default { isRootRoute() { return this.$route.name === this.rootRoute.name; }, + detailsRouteName() { + return this.detailsRoute.meta.nameGenerator(); + }, isLoaded() { - return this.isRootRoute || this.$store?.state.imageDetails?.name; + return this.isRootRoute || this.detailsRouteName; }, allCrumbs() { const crumbs = [ @@ -32,7 +35,7 @@ export default { ]; if (!this.isRootRoute) { crumbs.push({ - text: this.detailsRoute.meta.nameGenerator(), + text: this.detailsRouteName, href: this.detailsRoute.meta.path, }); } @@ -45,7 +48,9 @@ export default { <template> <gl-breadcrumb :key="isLoaded" :items="allCrumbs"> <template #separator> - <gl-icon name="angle-right" :size="8" /> + <span class="gl-mx-n5"> + <gl-icon name="angle-right" :size="8" /> + </span> </template> </gl-breadcrumb> </template> diff --git a/app/assets/javascripts/packages_and_registries/shared/utils.js b/app/assets/javascripts/packages_and_registries/shared/utils.js index cf18f655e79..7e963cd0b08 100644 --- a/app/assets/javascripts/packages_and_registries/shared/utils.js +++ b/app/assets/javascripts/packages_and_registries/shared/utils.js @@ -1,3 +1,4 @@ +import Vue from 'vue'; import { queryToObject } from '~/lib/utils/url_utility'; import { FILTERED_SEARCH_TERM } from './constants'; @@ -38,3 +39,37 @@ export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGr return `../commit/${pipeline.sha}`; }; + +export const renderBreadcrumb = (router, apolloProvider, RegistryBreadcrumb) => () => { + const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li'); + const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1]; + const lastCrumb = breadCrumbEl.children[0]; + const crumbs = [lastCrumb]; + const nestedBreadcrumbEl = document.createElement('div'); + breadCrumbEl.replaceChild(nestedBreadcrumbEl, lastCrumb); + return new Vue({ + el: nestedBreadcrumbEl, + router, + apolloProvider, + components: { + RegistryBreadcrumb, + }, + render(createElement) { + // FIXME(@tnir): this is a workaround until the MR gets merged: + // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115 + const parentEl = breadCrumbEl.parentElement.parentElement; + if (parentEl) { + parentEl.classList.remove('breadcrumbs-container'); + parentEl.classList.add('gl-display-flex'); + parentEl.classList.add('w-100'); + } + // End of FIXME(@tnir) + return createElement('registry-breadcrumb', { + class: breadCrumbEl.className, + props: { + crumbs, + }, + }); + }, + }); +}; |