diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-05 18:13:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-05 18:13:51 +0300 |
commit | 67d7efba05353044f927a30634c63b921b2efb73 (patch) | |
tree | 9bc6fefcae4141dd80434b4a72e8eae57ab14466 /app/assets/javascripts/packages_and_registries | |
parent | 6d29831123c8c806dc75d64e29b7691576cbea7f (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
8 files changed, 172 insertions, 54 deletions
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/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/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/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> |