diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-10 18:10:12 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-10 18:10:12 +0300 |
commit | 8f143a46faf2e7b594301512757edf372c294a0c (patch) | |
tree | 8bd5957ffa44d028905ab51a7252cce6783d2e25 /app/assets/javascripts/registry | |
parent | 3e06afc4cd1b75b3e957e8debf5e4f1963ba18e0 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/registry')
8 files changed, 185 insertions, 77 deletions
diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue index 2844b4ffde3..ad39a898e7b 100644 --- a/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue +++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue @@ -34,7 +34,7 @@ export default { return this.tags.some(tag => this.selectedItems[tag.name]); }, showMultiDeleteButton() { - return this.tags.some(tag => tag.destroy_path) && !this.isMobile; + return this.tags.some(tag => tag.canDelete) && !this.isMobile; }, }, methods: { diff --git a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue index 2edeac1144f..5aeafd318aa 100644 --- a/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue +++ b/app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue @@ -63,7 +63,7 @@ export default { }, computed: { formattedSize() { - return this.tag.total_size ? numberToHumanSize(this.tag.total_size) : NOT_AVAILABLE_SIZE; + return this.tag.totalSize ? numberToHumanSize(this.tag.totalSize) : NOT_AVAILABLE_SIZE; }, layers() { return this.tag.layers ? n__('%d layer', '%d layers', this.tag.layers) : ''; @@ -76,10 +76,10 @@ export default { return this.tag.digest?.substring(7, 14) ?? NOT_AVAILABLE_TEXT; }, publishedDate() { - return formatDate(this.tag.created_at, 'isoDate'); + return formatDate(this.tag.createdAt, 'isoDate'); }, publishedTime() { - return formatDate(this.tag.created_at, 'hh:MM Z'); + return formatDate(this.tag.createdAt, 'hh:MM Z'); }, formattedRevision() { // to be removed when API response is adjusted @@ -101,7 +101,7 @@ export default { <list-item v-bind="$attrs" :selected="selected"> <template #left-action> <gl-form-checkbox - v-if="Boolean(tag.destroy_path)" + v-if="tag.canDelete" :disabled="invalidTag" class="gl-m-0" :checked="selected" @@ -148,7 +148,7 @@ export default { <span data-testid="time"> <gl-sprintf :message="$options.i18n.CREATED_AT_LABEL"> <template #timeInfo> - <time-ago-tooltip :time="tag.created_at" /> + <time-ago-tooltip :time="tag.createdAt" /> </template> </gl-sprintf> </span> @@ -162,10 +162,10 @@ export default { </template> <template #right-action> <delete-button - :disabled="!tag.destroy_path || invalidTag" + :disabled="!tag.canDelete || invalidTag" :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE" :tooltip-title="$options.i18n.REMOVE_TAG_BUTTON_DISABLE_TOOLTIP" - :tooltip-disabled="Boolean(tag.destroy_path)" + :tooltip-disabled="tag.canDelete" data-testid="single-delete-button" @delete="$emit('delete')" /> diff --git a/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue b/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue index 47a1e3da608..1cedcc41b2b 100644 --- a/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue +++ b/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue @@ -1,6 +1,6 @@ <script> /* eslint-disable vue/no-v-html */ -// We are forced to use `v-html` untill this gitlab-ui MR is merged: https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/1869 +// We are forced to use `v-html` untill this gitlab-ui issue is resolved: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1079 // then we can re-write this to use gl-breadcrumb import { initial, first, last } from 'lodash'; import { sanitize } from '~/lib/dompurify'; @@ -35,7 +35,7 @@ export default { return { tagName, className, - text: this.$route.meta.nameGenerator(this.$store.state), + text: this.$route.meta.nameGenerator(), path: { to: this.$route.name }, }; }, @@ -53,7 +53,7 @@ export default { ></li> <li v-if="!isRootRoute"> <router-link ref="rootRouteLink" :to="rootRoute.path"> - {{ rootRoute.meta.nameGenerator($store.state) }} + {{ rootRoute.meta.nameGenerator() }} </router-link> <component :is="divider.tagName" :class="divider.classList" v-html="divider.innerHTML" /> </li> diff --git a/app/assets/javascripts/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql b/app/assets/javascripts/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql new file mode 100644 index 00000000000..a31f2829e13 --- /dev/null +++ b/app/assets/javascripts/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql @@ -0,0 +1,5 @@ +mutation destroyContainerRepositoryTags($id: ContainerRepositoryID!, $tagNames: [String!]!) { + destroyContainerRepositoryTags(input: { id: $id, tagNames: $tagNames }) { + errors + } +} diff --git a/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql new file mode 100644 index 00000000000..a54934fa681 --- /dev/null +++ b/app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql @@ -0,0 +1,37 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" + +query getContainerRepositoryDetails( + $id: ID! + $first: Int + $last: Int + $after: String + $before: String +) { + containerRepository(id: $id) { + id + name + path + status + location + canDelete + createdAt + tagsCount + expirationPolicyStartedAt + tags(after: $after, before: $before, first: $first, last: $last) { + nodes { + digest + location + path + name + revision + shortRevision + createdAt + totalSize + canDelete + } + pageInfo { + ...PageInfo + } + } + } +} diff --git a/app/assets/javascripts/registry/explorer/index.js b/app/assets/javascripts/registry/explorer/index.js index 5fafd861a06..b85f28e2300 100644 --- a/app/assets/javascripts/registry/explorer/index.js +++ b/app/assets/javascripts/registry/explorer/index.js @@ -19,8 +19,16 @@ export default () => { const { endpoint } = el.dataset; + // 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 store = createStore(); - const router = createRouter(endpoint); + const router = createRouter(endpoint, breadCrumbState); store.dispatch('setInitialState', el.dataset); const attachMainComponent = () => @@ -32,6 +40,9 @@ export default () => { components: { RegistryExplorer, }, + provide() { + return { breadCrumbState }; + }, render(createElement) { return createElement('registry-explorer'); }, @@ -42,8 +53,8 @@ export default () => { const crumbs = [...document.querySelectorAll('.js-breadcrumbs-list li')]; return new Vue({ el: breadCrumbEl, - store, router, + apolloProvider, components: { RegistryBreadcrumb, }, diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue index a60ef5c4982..e47f1d73856 100644 --- a/app/assets/javascripts/registry/explorer/pages/details.vue +++ b/app/assets/javascripts/registry/explorer/pages/details.vue @@ -1,8 +1,10 @@ <script> -import { mapState, mapActions } from 'vuex'; -import { GlPagination, GlResizeObserverDirective } from '@gitlab/ui'; +import { mapState } from 'vuex'; +import { GlKeysetPagination, GlResizeObserverDirective } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; +import createFlash from '~/flash'; import Tracking from '~/tracking'; +import { joinPaths } from '~/lib/utils/url_utility'; import DeleteAlert from '../components/details_page/delete_alert.vue'; import PartialCleanupAlert from '../components/details_page/partial_cleanup_alert.vue'; import DeleteModal from '../components/details_page/delete_modal.vue'; @@ -11,11 +13,16 @@ import TagsList from '../components/details_page/tags_list.vue'; import TagsLoader from '../components/details_page/tags_loader.vue'; import EmptyTagsState from '../components/details_page/empty_tags_state.vue'; +import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.graphql'; +import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.graphql'; + import { ALERT_SUCCESS_TAG, ALERT_DANGER_TAG, ALERT_SUCCESS_TAGS, ALERT_DANGER_TAGS, + GRAPHQL_PAGE_SIZE, + FETCH_IMAGES_LIST_ERROR_MESSAGE, } from '../constants/index'; export default { @@ -23,28 +30,62 @@ export default { DeleteAlert, PartialCleanupAlert, DetailsHeader, - GlPagination, + GlKeysetPagination, DeleteModal, TagsList, TagsLoader, EmptyTagsState, }, + inject: ['breadCrumbState'], directives: { GlResizeObserver: GlResizeObserverDirective, }, mixins: [Tracking.mixin()], + apollo: { + image: { + query: getContainerRepositoryDetailsQuery, + variables() { + return this.queryVariables; + }, + update(data) { + return data.containerRepository; + }, + result({ data }) { + this.tagsPageInfo = data.containerRepository?.tags?.pageInfo; + this.breadCrumbState.updateName(data.containerRepository?.name); + }, + error() { + createFlash({ message: FETCH_IMAGES_LIST_ERROR_MESSAGE }); + }, + }, + }, data() { return { + image: {}, + tagsPageInfo: {}, itemsToBeDeleted: [], isMobile: false, + mutationLoading: false, deleteAlertType: null, dismissPartialCleanupWarning: false, }; }, computed: { - ...mapState(['tagsPagination', 'isLoading', 'config', 'tags', 'imageDetails']), + ...mapState(['config']), + queryVariables() { + return { + id: joinPaths(this.config.gidPrefix, `${this.$route.params.id}`), + first: GRAPHQL_PAGE_SIZE, + }; + }, + isLoading() { + return this.$apollo.queries.image.loading || this.mutationLoading; + }, + tags() { + return this.image?.tags?.nodes || []; + }, showPartialCleanupWarning() { - return this.imageDetails?.cleanup_policy_started_at && !this.dismissPartialCleanupWarning; + return this.image?.expirationPolicyStartedAt && !this.dismissPartialCleanupWarning; }, tracking() { return { @@ -52,66 +93,78 @@ export default { this.itemsToBeDeleted?.length > 1 ? 'bulk_registry_tag_delete' : 'registry_tag_delete', }; }, - currentPage: { - get() { - return this.tagsPagination.page; - }, - set(page) { - this.requestTagsList({ page }); - }, + showPagination() { + return this.tagsPageInfo.hasPreviousPage || this.tagsPageInfo.hasNextPage; }, }, - mounted() { - this.requestImageDetailsAndTagsList(this.$route.params.id); - }, methods: { - ...mapActions([ - 'requestTagsList', - 'requestDeleteTag', - 'requestDeleteTags', - 'requestImageDetailsAndTagsList', - ]), deleteTags(toBeDeleted) { this.itemsToBeDeleted = this.tags.filter(tag => toBeDeleted[tag.name]); this.track('click_button'); this.$refs.deleteModal.show(); }, - handleSingleDelete() { - const [itemToDelete] = this.itemsToBeDeleted; - this.itemsToBeDeleted = []; - return this.requestDeleteTag({ tag: itemToDelete }) - .then(() => { - this.deleteAlertType = ALERT_SUCCESS_TAG; - }) - .catch(() => { - this.deleteAlertType = ALERT_DANGER_TAG; - }); - }, - handleMultipleDelete() { + async handleDelete() { + this.track('confirm_delete'); const { itemsToBeDeleted } = this; this.itemsToBeDeleted = []; - - return this.requestDeleteTags({ - ids: itemsToBeDeleted.map(x => x.name), - }) - .then(() => { - this.deleteAlertType = ALERT_SUCCESS_TAGS; - }) - .catch(() => { - this.deleteAlertType = ALERT_DANGER_TAGS; + this.mutationLoading = true; + try { + const { data } = await this.$apollo.mutate({ + mutation: deleteContainerRepositoryTagsMutation, + variables: { + id: this.queryVariables.id, + tagNames: itemsToBeDeleted.map(i => i.name), + }, + awaitRefetchQueries: true, + refetchQueries: [ + { + query: getContainerRepositoryDetailsQuery, + variables: this.queryVariables, + }, + ], }); - }, - onDeletionConfirmed() { - this.track('confirm_delete'); - if (this.itemsToBeDeleted.length > 1) { - this.handleMultipleDelete(); - } else { - this.handleSingleDelete(); + + if (data?.destroyContainerRepositoryTags?.errors[0]) { + throw new Error(); + } + this.deleteAlertType = + itemsToBeDeleted.length === 0 ? ALERT_SUCCESS_TAG : ALERT_SUCCESS_TAGS; + } catch (e) { + this.deleteAlertType = itemsToBeDeleted.length === 0 ? ALERT_DANGER_TAG : ALERT_DANGER_TAGS; } + + this.mutationLoading = false; }, handleResize() { this.isMobile = GlBreakpointInstance.getBreakpointSize() === 'xs'; }, + fetchNextPage() { + if (this.tagsPageInfo?.hasNextPage) { + this.$apollo.queries.image.fetchMore({ + variables: { + after: this.tagsPageInfo?.endCursor, + first: GRAPHQL_PAGE_SIZE, + }, + updateQuery(previousResult, { fetchMoreResult }) { + return fetchMoreResult; + }, + }); + } + }, + fetchPreviousPage() { + if (this.tagsPageInfo?.hasPreviousPage) { + this.$apollo.queries.image.fetchMore({ + variables: { + first: null, + before: this.tagsPageInfo?.startCursor, + last: GRAPHQL_PAGE_SIZE, + }, + updateQuery(previousResult, { fetchMoreResult }) { + return fetchMoreResult; + }, + }); + } + }, }, }; </script> @@ -132,28 +185,30 @@ export default { @dismiss="dismissPartialCleanupWarning = true" /> - <details-header :image-name="imageDetails.name" /> + <details-header :image-name="image.name" /> <tags-loader v-if="isLoading" /> <template v-else> <empty-tags-state v-if="tags.length === 0" :no-containers-image="config.noContainersImage" /> - <tags-list v-else :tags="tags" :is-mobile="isMobile" @delete="deleteTags" /> + <template v-else> + <tags-list :tags="tags" :is-mobile="isMobile" @delete="deleteTags" /> + <div class="gl-display-flex gl-justify-content-center"> + <gl-keyset-pagination + v-if="showPagination" + :has-next-page="tagsPageInfo.hasNextPage" + :has-previous-page="tagsPageInfo.hasPreviousPage" + class="gl-mt-3" + @prev="fetchPreviousPage" + @next="fetchNextPage" + /> + </div> + </template> </template> - <gl-pagination - v-if="!isLoading" - ref="pagination" - v-model="currentPage" - :per-page="tagsPagination.perPage" - :total-items="tagsPagination.total" - align="center" - class="gl-w-full gl-mt-3" - /> - <delete-modal ref="deleteModal" :items-to-be-deleted="itemsToBeDeleted" - @confirmDelete="onDeletionConfirmed" + @confirmDelete="handleDelete" @cancel="track('cancel_delete')" /> </div> diff --git a/app/assets/javascripts/registry/explorer/router.js b/app/assets/javascripts/registry/explorer/router.js index dcf1c77329d..d8903cf0931 100644 --- a/app/assets/javascripts/registry/explorer/router.js +++ b/app/assets/javascripts/registry/explorer/router.js @@ -6,7 +6,7 @@ import { CONTAINER_REGISTRY_TITLE } from './constants/index'; Vue.use(VueRouter); -export default function createRouter(base) { +export default function createRouter(base, breadCrumbState) { const router = new VueRouter({ base, mode: 'history', @@ -25,7 +25,7 @@ export default function createRouter(base) { path: '/:id', component: Details, meta: { - nameGenerator: ({ imageDetails }) => imageDetails?.name, + nameGenerator: () => breadCrumbState.name, }, }, ], |