Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-10 18:10:12 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-10 18:10:12 +0300
commit8f143a46faf2e7b594301512757edf372c294a0c (patch)
tree8bd5957ffa44d028905ab51a7252cce6783d2e25 /app/assets/javascripts/registry
parent3e06afc4cd1b75b3e957e8debf5e4f1963ba18e0 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/registry')
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list.vue2
-rw-r--r--app/assets/javascripts/registry/explorer/components/details_page/tags_list_row.vue14
-rw-r--r--app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue6
-rw-r--r--app/assets/javascripts/registry/explorer/graphql/mutations/delete_container_repository_tags.graphql5
-rw-r--r--app/assets/javascripts/registry/explorer/graphql/queries/get_container_repository_details.graphql37
-rw-r--r--app/assets/javascripts/registry/explorer/index.js15
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue179
-rw-r--r--app/assets/javascripts/registry/explorer/router.js4
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,
},
},
],