diff options
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
31 files changed, 251 insertions, 175 deletions
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue index 7a8a1bbcf09..2da8ca2d8a8 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue @@ -83,11 +83,13 @@ export default { modal-id="delete-tag-modal" ok-variant="danger" size="sm" - :action-primary="{ + :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { text: __('Delete'), attributes: [{ variant: 'danger' }, { disabled: disablePrimaryButton }], - }" - :action-cancel="{ text: __('Cancel') }" + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" + :action-cancel="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + text: __('Cancel'), + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" @primary="$emit('confirmDelete')" @cancel="$emit('cancelDelete')" @change="projectPath = ''" 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 7659ba5f9ea..9e8eb92d87a 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 @@ -168,7 +168,9 @@ export default { <div> <persisted-search class="gl-mb-5" - :sortable-fields="[$options.searchConfig.NAME_SORT_FIELD]" + :sortable-fields="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ + $options.searchConfig.NAME_SORT_FIELD, + ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :default-order="$options.searchConfig.NAME_SORT_FIELD.orderBy" default-sort="asc" @update="handleSearchUpdate" diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue index 1f52e319ad0..3ae69731537 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue @@ -1,7 +1,9 @@ <script> -import { GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { GlIcon, GlPopover, GlLink, GlSprintf } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { timeTilRun } from '../../utils'; import { - CLEANUP_TIMED_OUT_ERROR_MESSAGE, + PARTIAL_CLEANUP_CONTINUE_MESSAGE, CLEANUP_STATUS_SCHEDULED, CLEANUP_STATUS_ONGOING, CLEANUP_STATUS_UNFINISHED, @@ -15,9 +17,9 @@ export default { name: 'CleanupStatus', components: { GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, + GlPopover, + GlLink, + GlSprintf, }, props: { status: { @@ -29,12 +31,17 @@ export default { ); }, }, + expirationPolicy: { + type: Object, + default: () => ({}), + required: false, + }, }, i18n: { CLEANUP_STATUS_SCHEDULED, CLEANUP_STATUS_ONGOING, CLEANUP_STATUS_UNFINISHED, - CLEANUP_TIMED_OUT_ERROR_MESSAGE, + PARTIAL_CLEANUP_CONTINUE_MESSAGE, }, computed: { showStatus() { @@ -46,26 +53,57 @@ export default { statusText() { return this.$options.i18n[`CLEANUP_STATUS_${this.status}`]; }, - expireIconClass() { - return this.failedDelete ? 'gl-text-orange-500' : ''; + calculatedTimeTilNextRun() { + return timeTilRun(this.expirationPolicy?.next_run); }, }, + statusPopoverOptions: { + triggers: 'hover', + placement: 'top', + }, + cleanupPolicyHelpPage: helpPagePath( + 'user/packages/container_registry/reduce_container_registry_storage.html', + { anchor: 'how-the-cleanup-policy-works' }, + ), }; </script> <template> - <div v-if="showStatus" class="gl-display-inline-flex gl-align-items-center"> - <gl-icon name="expire" data-testid="main-icon" :class="expireIconClass" /> + <div + v-if="showStatus" + id="status-popover-container" + class="gl-display-inline-flex gl-align-items-center" + > + <div class="gl-display-inline-flex gl-align-items-center"> + <gl-icon name="expire" data-testid="main-icon" /> + </div> <span class="gl-mx-2"> {{ statusText }} </span> <gl-icon v-if="failedDelete" - v-gl-tooltip="{ title: $options.i18n.CLEANUP_TIMED_OUT_ERROR_MESSAGE }" + id="status-info" :size="14" - class="gl-text-black-normal" + class="gl-text-gray-500" data-testid="extra-info" - name="information" + name="information-o" /> + <gl-popover + target="status-info" + container="status-popover-container" + v-bind="$options.statusPopoverOptions" + > + <template #title> + {{ $options.i18n.CLEANUP_STATUS_UNFINISHED }} + </template> + <gl-sprintf :message="$options.i18n.PARTIAL_CLEANUP_CONTINUE_MESSAGE"> + <template #time>{{ calculatedTimeTilNextRun }}</template + ><template #link="{ content }" + ><gl-link :href="$options.cleanupPolicyHelpPage" class="gl-font-sm" target="_blank">{{ + content + }}</gl-link></template + > + </gl-sprintf> + </gl-popover> </div> </template> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue index 5bd13322ebb..6f1f67e251f 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list.vue @@ -22,6 +22,11 @@ export default { type: Object, required: true, }, + expirationPolicy: { + type: Object, + default: () => ({}), + required: false, + }, }, computed: { showPagination() { @@ -38,6 +43,7 @@ export default { :key="index" :item="listItem" :metadata-loading="metadataLoading" + :expiration-policy="expirationPolicy" @delete="$emit('delete', $event)" /> <div class="gl-display-flex gl-justify-content-center"> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue index 484903354e8..d76a8245b63 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue @@ -6,12 +6,10 @@ import { n__ } from '~/locale'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import { - ASYNC_DELETE_IMAGE_ERROR_MESSAGE, LIST_DELETE_BUTTON_DISABLED, LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION, REMOVE_REPOSITORY_LABEL, ROW_SCHEDULED_FOR_DELETION, - CLEANUP_TIMED_OUT_ERROR_MESSAGE, IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_FAILED_DELETED_STATUS, IMAGE_MIGRATING_STATE, @@ -45,6 +43,11 @@ export default { default: false, required: false, }, + expirationPolicy: { + type: Object, + default: () => ({}), + required: false, + }, }, i18n: { REMOVE_REPOSITORY_LABEL, @@ -73,15 +76,6 @@ export default { this.item.tagsCount, ); }, - warningIconText() { - if (this.failedDelete) { - return ASYNC_DELETE_IMAGE_ERROR_MESSAGE; - } - if (this.item.expirationPolicyStartedAt) { - return CLEANUP_TIMED_OUT_ERROR_MESSAGE; - } - return null; - }, imageName() { return this.item.name ? this.item.path : `${this.item.path}/ ${ROOT_IMAGE_TEXT}`; }, @@ -140,6 +134,7 @@ export default { v-if="item.expirationPolicyCleanupStatus" class="ml-2" :status="item.expirationPolicyCleanupStatus" + :expiration-policy="expirationPolicy" /> </template> diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue index 154e176dc6e..4ffd8390e4d 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/registry_header.vue @@ -7,7 +7,6 @@ import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import { CONTAINER_REGISTRY_TITLE, - LIST_INTRO_TEXT, EXPIRATION_POLICY_WILL_RUN_IN, EXPIRATION_POLICY_DISABLED_TEXT, SET_UP_CLEANUP, @@ -87,19 +86,12 @@ export default { ? sprintf(EXPIRATION_POLICY_WILL_RUN_IN, { time: this.timeTillRun }) : EXPIRATION_POLICY_DISABLED_TEXT; }, - infoMessages() { - return [{ text: LIST_INTRO_TEXT, link: this.helpPagePath }]; - }, }, }; </script> <template> - <title-area - :title="$options.i18n.CONTAINER_REGISTRY_TITLE" - :info-messages="infoMessages" - :metadata-loading="metadataLoading" - > + <title-area :title="$options.i18n.CONTAINER_REGISTRY_TITLE" :metadata-loading="metadataLoading"> <template #right-actions> <slot name="commands"></slot> </template> 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 3c7f7ca9aa8..2a58933cd64 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 @@ -87,7 +87,7 @@ export const CLEANUP_DISABLED_TOOLTIP = s__( export const CLEANUP_STATUS_SCHEDULED = s__('ContainerRegistry|Cleanup will run soon'); export const CLEANUP_STATUS_ONGOING = s__('ContainerRegistry|Cleanup is ongoing'); -export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Cleanup timed out'); +export const CLEANUP_STATUS_UNFINISHED = s__('ContainerRegistry|Partial cleanup complete'); export const DETAILS_DELETE_IMAGE_ERROR_MESSAGE = s__( 'ContainerRegistry|Something went wrong while scheduling the image for deletion.', diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js index e584da23edb..9d0ecfd2dcb 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/expiration_policies.js @@ -10,7 +10,7 @@ export const DELETE_ALERT_TITLE = s__('ContainerRegistry|Some tags were not dele export const DELETE_ALERT_LINK_TEXT = s__( 'ContainerRegistry|The cleanup policy timed out before it could delete all tags. An administrator can %{adminLinkStart}manually run cleanup now%{adminLinkEnd} or you can wait for the cleanup policy to automatically run again. %{docLinkStart}More information%{docLinkEnd}', ); -export const CLEANUP_TIMED_OUT_ERROR_MESSAGE = s__( - 'ContainerRegistry|Cleanup timed out before it could delete all tags', +export const PARTIAL_CLEANUP_CONTINUE_MESSAGE = s__( + 'ContainerRegistry|The cleanup will continue within %{time}. %{linkStart}Learn more%{linkEnd}', ); export const SET_UP_CLEANUP = s__('ContainerRegistry|Set up cleanup'); 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 c7022d6070f..ceaf8a65a10 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 @@ -8,9 +8,6 @@ export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection e export const CONNECTION_ERROR_MESSAGE = s__( `ContainerRegistry|We are having trouble connecting to the Container Registry. Please try refreshing the page. If this error persists, please review %{docLinkStart}the troubleshooting documentation%{docLinkEnd}.`, ); -export const LIST_INTRO_TEXT = s__( - `ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`, -); export const LIST_DELETE_BUTTON_DISABLED = s__( 'ContainerRegistry|Missing or insufficient permission, delete button disabled', ); diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql index 916740f41b8..e2036d9e63d 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql @@ -1,4 +1,4 @@ -query getContainerRepositoryDetails($id: ID!) { +query getContainerRepositoryDetails($id: ContainerRepositoryID!) { containerRepository(id: $id) { id name diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql index f1f67b98407..1faa9dec795 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_metadata.query.graphql @@ -1,4 +1,4 @@ -query getContainerRepositoryMetadata($id: ID!) { +query getContainerRepositoryMetadata($id: ContainerRepositoryID!) { containerRepository(id: $id) { id tagsCount 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 8c577cc7b17..e57ac2a9efe 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 @@ -1,7 +1,7 @@ #import "~/graphql_shared/fragments/page_info.fragment.graphql" query getContainerRepositoryTags( - $id: ID! + $id: ContainerRepositoryID! $first: Int $last: Int $after: String diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue index d1cab406984..c1bd71de646 100644 --- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue @@ -336,6 +336,7 @@ export default { :images="images" :metadata-loading="$apollo.queries.additionalDetails.loading" :page-info="pageInfo" + :expiration-policy="config.expirationPolicy" @delete="deleteImage" @prev-page="fetchPreviousPage" @next-page="fetchNextPage" @@ -370,7 +371,10 @@ export default { ref="deleteModal" size="sm" modal-id="delete-image-modal" - :action-primary="{ text: __('Remove'), attributes: { variant: 'danger' } }" + :action-primary="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + text: __('Remove'), + attributes: { variant: 'danger' }, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" @primary="doDelete" @cancel="track('cancel_delete')" > diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js new file mode 100644 index 00000000000..ffdaf9f2f17 --- /dev/null +++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/utils.js @@ -0,0 +1,8 @@ +import { approximateDuration, calculateRemainingMilliseconds } from '~/lib/utils/datetime_utility'; + +export const timeTilRun = (time) => { + if (!time) return ''; + + const difference = calculateRemainingMilliseconds(time); + return approximateDuration(difference / 1000); +}; diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue index 67c2ca02d20..1faff1ff4de 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue @@ -16,10 +16,7 @@ import Api from '~/api'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import TitleArea from '~/vue_shared/components/registry/title_area.vue'; import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue'; -import { - DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, - DEPENDENCY_PROXY_DOCS_PATH, -} from '~/packages_and_registries/settings/group/constants'; +import { DEPENDENCY_PROXY_DOCS_PATH } from '~/packages_and_registries/settings/group/constants'; import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants'; import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql'; @@ -42,11 +39,8 @@ export default { directives: { GlModalDirective, }, - inject: ['groupPath', 'groupId', 'dependencyProxyAvailable', 'noManifestsIllustration'], + inject: ['groupPath', 'groupId', 'noManifestsIllustration'], i18n: { - proxyNotAvailableText: s__( - 'DependencyProxy|Dependency Proxy feature is limited to public groups for now.', - ), proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'), copyImagePrefixText: s__('DependencyProxy|Copy prefix'), blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'), @@ -80,32 +74,20 @@ export default { apollo: { group: { query: getDependencyProxyDetailsQuery, - skip() { - return !this.dependencyProxyAvailable; - }, variables() { return this.queryVariables; }, }, }, computed: { - infoMessages() { - return [ - { - text: DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, - link: DEPENDENCY_PROXY_DOCS_PATH, - }, - ]; - }, - queryVariables() { return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE }; }, pageInfo() { - return this.group.dependencyProxyManifests.pageInfo; + return this.group.dependencyProxyManifests?.pageInfo; }, manifests() { - return this.group.dependencyProxyManifests.nodes; + return this.group.dependencyProxyManifests?.nodes; }, modalTitleWithCount() { return sprintf( @@ -132,7 +114,10 @@ export default { ); }, showDeleteDropdown() { - return this.group.dependencyProxyBlobCount > 0; + return this.group.dependencyProxyManifests?.nodes.length > 0; + }, + showDependencyProxyImagePrefix() { + return this.group.dependencyProxyImagePrefix?.length > 0; }, }, methods: { @@ -181,7 +166,7 @@ export default { > {{ deleteCacheAlertMessage }} </gl-alert> - <title-area :title="$options.i18n.pageTitle" :info-messages="infoMessages"> + <title-area :title="$options.i18n.pageTitle"> <template v-if="showDeleteDropdown" #right-actions> <gl-dropdown icon="ellipsis_v" @@ -198,41 +183,34 @@ export default { </gl-dropdown> </template> </title-area> - <gl-alert - v-if="!dependencyProxyAvailable" - :dismissible="false" - data-testid="proxy-not-available" - > - {{ $options.i18n.proxyNotAvailableText }} - </gl-alert> - <gl-skeleton-loader v-else-if="$apollo.queries.group.loading" /> - - <div v-else data-testid="main-area"> - <gl-form-group :label="$options.i18n.proxyImagePrefix"> - <gl-form-input-group - readonly - :value="group.dependencyProxyImagePrefix" - class="gl-layout-w-limited" - data-testid="proxy-url" - > - <template #append> - <clipboard-button - :text="group.dependencyProxyImagePrefix" - :title="$options.i18n.copyImagePrefixText" - /> - </template> - </gl-form-input-group> - <template #description> - <span data-qa-selector="dependency_proxy_count" data-testid="proxy-count"> - <gl-sprintf :message="$options.i18n.blobCountAndSize"> - <template #count>{{ group.dependencyProxyBlobCount }}</template> - <template #size>{{ group.dependencyProxyTotalSize }}</template> - </gl-sprintf> - </span> + <gl-form-group v-if="showDependencyProxyImagePrefix" :label="$options.i18n.proxyImagePrefix"> + <gl-form-input-group + readonly + :value="group.dependencyProxyImagePrefix" + class="gl-layout-w-limited" + data-testid="proxy-url" + > + <template #append> + <clipboard-button + :text="group.dependencyProxyImagePrefix" + :title="$options.i18n.copyImagePrefixText" + /> </template> - </gl-form-group> + </gl-form-input-group> + <template #description> + <span data-qa-selector="dependency_proxy_count" data-testid="proxy-count"> + <gl-sprintf :message="$options.i18n.blobCountAndSize"> + <template #count>{{ group.dependencyProxyBlobCount }}</template> + <template #size>{{ group.dependencyProxyTotalSize }}</template> + </gl-sprintf> + </span> + </template> + </gl-form-group> + <gl-skeleton-loader v-if="$apollo.queries.group.loading" /> + + <div v-else data-testid="main-area"> <manifests-list v-if="manifests && manifests.length" :manifests="manifests" diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue index 78880b6e3f4..1bbd0c32dc4 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/components/manifest_row.vue @@ -1,5 +1,6 @@ <script> -import { GlSprintf } from '@gitlab/ui'; +import { GlIcon, GlSprintf } from '@gitlab/ui'; +import { MANIFEST_PENDING_DESTRUCTION_STATUS } from '~/packages_and_registries/dependency_proxy/constants'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import { s__ } from '~/locale'; @@ -7,6 +8,7 @@ import { s__ } from '~/locale'; export default { name: 'ManifestRow', components: { + GlIcon, GlSprintf, ListItem, TimeagoTooltip, @@ -24,17 +26,31 @@ export default { version() { return this.manifest?.imageName.split(':')[1]; }, + isErrorStatus() { + return this.manifest?.status === MANIFEST_PENDING_DESTRUCTION_STATUS; + }, + disabledRowStyle() { + return this.isErrorStatus ? 'gl-font-weight-normal gl-text-gray-500' : ''; + }, }, i18n: { cachedAgoMessage: s__('DependencyProxy|Cached %{time}'), + scheduledForDeletion: s__('DependencyProxy|Scheduled for deletion'), }, }; </script> <template> - <list-item> - <template #left-primary> {{ name }} </template> - <template #left-secondary> {{ version }} </template> + <list-item :disabled="isErrorStatus"> + <template #left-primary> + <span :class="disabledRowStyle">{{ name }}</span> + </template> + <template #left-secondary> + {{ version }} + <span v-if="isErrorStatus" class="gl-ml-4" data-testid="status" + ><gl-icon name="clock" /> {{ $options.i18n.scheduledForDeletion }}</span + > + </template> <template #right-primary> </template> <template #right-secondary> <timeago-tooltip :time="manifest.createdAt" data-testid="cached-message"> diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js index 3c6ede6fdce..fdad69204ba 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/constants.js @@ -1 +1,2 @@ export const GRAPHQL_PAGE_SIZE = 20; +export const MANIFEST_PENDING_DESTRUCTION_STATUS = 'PENDING_DESTRUCTION'; diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql index 5c43b10a5e3..c1597625964 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql @@ -20,6 +20,7 @@ query getDependencyProxyDetails( id createdAt imageName + status } pageInfo { ...PageInfo diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js index dc73470e07d..14789aafdb7 100644 --- a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js +++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js @@ -1,5 +1,4 @@ import Vue from 'vue'; -import { parseBoolean } from '~/lib/utils/common_utils'; import app from '~/packages_and_registries/dependency_proxy/app.vue'; import { apolloProvider } from '~/packages_and_registries/dependency_proxy/graphql'; import Translate from '~/vue_shared/translate'; @@ -11,12 +10,11 @@ export const initDependencyProxyApp = () => { if (!el) { return null; } - const { dependencyProxyAvailable, ...dataset } = el.dataset; + const { ...dataset } = el.dataset; return new Vue({ el, apolloProvider, provide: { - dependencyProxyAvailable: parseBoolean(dependencyProxyAvailable), ...dataset, }, render(createElement) { diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue index f198d2e1bfa..425fb4596fd 100644 --- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue @@ -191,7 +191,10 @@ export default { <package-list-row v-for="v in packageEntity.versions" :key="v.id" - :package-entity="{ name: packageEntity.name, ...v }" + :package-entity="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ { + name: packageEntity.name, + ...v, + } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :package-link="v.id.toString()" :disable-delete="true" :show-package-type="false" diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue index c611f92036d..d3c38da1531 100644 --- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue +++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue @@ -33,7 +33,7 @@ export default { <registry-search :filter="filter" :sorting="sorting" - :tokens="[]" + :tokens="[] /* eslint-disable-line @gitlab/vue-no-new-non-primitive-in-template */" :sortable-fields="sortableFields" @sorting:changed="updateSorting" @filter:changed="setFilter" diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue index 118c509828c..f5946797626 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue @@ -1,5 +1,5 @@ <script> -import { GlIcon, GlSprintf, GlBadge, GlResizeObserverDirective } from '@gitlab/ui'; +import { GlSprintf, GlBadge, GlResizeObserverDirective } from '@gitlab/ui'; import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { __ } from '~/locale'; @@ -14,7 +14,6 @@ export default { name: 'PackageTitle', components: { TitleArea, - GlIcon, GlSprintf, PackageTags, MetadataItem, @@ -84,7 +83,6 @@ export default { data-qa-selector="package_title" > <template #sub-header> - <gl-icon name="eye" class="gl-mr-3" /> <span data-testid="sub-header"> <gl-sprintf :message="$options.i18n.packageInfo"> <template #version> 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 6222c2e73d7..04faff1a75b 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,12 @@ <script> -import { GlButton, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui'; +import { + GlDropdown, + GlDropdownItem, + GlIcon, + GlSprintf, + GlTooltipDirective, + GlTruncate, +} from '@gitlab/ui'; import { s__, __ } from '~/locale'; import ListItem from '~/vue_shared/components/registry/list_item.vue'; import { @@ -17,7 +24,9 @@ import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; export default { name: 'PackageListRow', components: { - GlButton, + GlDropdown, + GlDropdownItem, + GlIcon, GlSprintf, GlTruncate, PackageTags, @@ -50,31 +59,42 @@ export default { pipelineUser() { return this.pipeline?.user?.name; }, - showWarningIcon() { + errorStatusRow() { return this.packageEntity.status === PACKAGE_ERROR_STATUS; }, showTags() { return Boolean(this.packageEntity.tags?.nodes?.length); }, - disabledRow() { + nonDefaultRow() { return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS; }, routerLinkEvent() { - return this.disabledRow ? '' : 'click'; + return this.nonDefaultRow ? '' : 'click'; + }, + errorPackageStyle() { + return { + 'gl-text-red-500': this.errorStatusRow, + 'gl-font-weight-normal': this.errorStatusRow, + }; }, }, i18n: { erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'), createdAt: __('Created %{timestamp}'), + deletePackage: s__('PackageRegistry|Delete package'), + errorPublishing: s__('PackageRegistry|Error publishing'), + warning: __('Warning'), + moreActions: __('More actions'), }, }; </script> <template> - <list-item data-qa-selector="package_row" :disabled="disabledRow"> + <list-item data-qa-selector="package_row"> <template #left-primary> <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0"> <router-link + :class="errorPackageStyle" class="gl-text-body gl-min-w-0" data-testid="details-link" data-qa-selector="package_link" @@ -84,16 +104,6 @@ export default { <gl-truncate :text="packageEntity.name" /> </router-link> - <gl-button - v-if="showWarningIcon" - v-gl-tooltip="{ title: $options.i18n.erroredPackageText }" - class="gl-hover-bg-transparent!" - icon="warning" - category="tertiary" - data-testid="warning-icon" - :aria-label="__('Warning')" - /> - <package-tags v-if="showTags" class="gl-ml-3" @@ -104,7 +114,7 @@ export default { </div> </template> <template #left-secondary> - <div class="gl-display-flex" data-testid="left-secondary-infos"> + <div v-if="!errorStatusRow" class="gl-display-flex" data-testid="left-secondary-infos"> <span>{{ packageEntity.version }}</span> <div v-if="pipelineUser" class="gl-display-none gl-sm-display-flex gl-ml-2"> @@ -120,9 +130,19 @@ export default { <package-path v-if="isGroupPage" :path="packageEntity.project.fullPath" - :disabled="disabledRow" + :disabled="nonDefaultRow" /> </div> + <div v-else> + <gl-icon + v-gl-tooltip="{ title: $options.i18n.erroredPackageText }" + name="warning" + class="gl-text-red-500" + :aria-label="$options.i18n.warning" + data-testid="warning-icon" + /> + <span class="gl-text-red-500">{{ $options.i18n.errorPublishing }}</span> + </div> </template> <template #right-primary> @@ -139,16 +159,22 @@ export default { </span> </template> - <template v-if="!disabledRow" #right-action> - <gl-button - data-testid="action-delete" - icon="remove" - category="secondary" - variant="danger" - :title="s__('PackageRegistry|Remove package')" - :aria-label="s__('PackageRegistry|Remove package')" - @click="$emit('packageToDelete', packageEntity)" - /> + <template v-if="packageEntity.canDestroy" #right-action> + <gl-dropdown + data-testid="delete-dropdown" + icon="ellipsis_v" + :text="$options.i18n.moreActions" + :text-sr-only="true" + category="tertiary" + no-caret + > + <gl-dropdown-item + data-testid="action-delete" + variant="danger" + @click="$emit('packageToDelete', packageEntity)" + >{{ $options.i18n.deletePackage }}</gl-dropdown-item + > + </gl-dropdown> </template> </list-item> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue index bf41c36e09b..440e11a99f2 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue @@ -27,21 +27,15 @@ export default { packageAmountText() { return n__(`%d Package`, `%d Packages`, this.count); }, - infoMessages() { - return [{ text: this.$options.i18n.LIST_INTRO_TEXT, link: this.helpUrl }]; - }, }, i18n: { LIST_TITLE_TEXT: s__('PackageRegistry|Package Registry'), - LIST_INTRO_TEXT: s__( - 'PackageRegistry|Publish and share packages for a variety of common package managers. %{docLinkStart}More information%{docLinkEnd}', - ), }, }; </script> <template> - <title-area :title="$options.i18n.LIST_TITLE_TEXT" :info-messages="infoMessages"> + <title-area :title="$options.i18n.LIST_TITLE_TEXT"> <template #metadata-amount> <metadata-item v-if="showPackageCount" icon="package" :text="packageAmountText" /> </template> diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue index 298ed9bccdb..1aff23bc112 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue @@ -1,18 +1,20 @@ <script> -import { GlModal, GlSprintf, GlKeysetPagination } from '@gitlab/ui'; -import { s__ } from '~/locale'; +import { GlAlert, GlModal, GlSprintf, GlKeysetPagination } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue'; import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue'; import { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, CANCEL_DELETE_PACKAGE_TRACKING_ACTION, + PACKAGE_ERROR_STATUS, } from '~/packages_and_registries/package_registry/constants'; import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils'; import Tracking from '~/tracking'; export default { components: { + GlAlert, GlKeysetPagination, GlModal, GlSprintf, @@ -40,6 +42,7 @@ export default { data() { return { itemToBeDeleted: null, + errorPackages: [], }; }, computed: { @@ -70,6 +73,24 @@ export default { } }, }, + errorTitleAlert() { + return sprintf( + s__('PackageRegistry|There was an error publishing a %{packageName} package'), + { packageName: this.errorPackages[0].name }, + ); + }, + showErrorPackageAlert() { + return this.errorPackages.length > 0; + }, + }, + watch: { + list(newVal) { + this.errorPackages = newVal.filter((pkg) => pkg.status === PACKAGE_ERROR_STATUS); + }, + }, + created() { + this.errorPackages = + this.list.length > 0 ? this.list.filter((pkg) => pkg.status === PACKAGE_ERROR_STATUS) : []; }, methods: { setItemToBeDeleted(item) { @@ -83,12 +104,19 @@ export default { deleteItemCanceled() { this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION); }, + showConfirmationModal() { + this.setItemToBeDeleted(this.errorPackages[0]); + }, }, i18n: { deleteModalContent: s__( 'PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?', ), modalAction: s__('PackageRegistry|Delete package'), + errorMessageBodyAlert: s__( + 'PackageRegistry|There was a timeout and the package was not published. Delete this package and try again.', + ), + deleteThisPackage: s__('PackageRegistry|Delete this package'), }, }; </script> @@ -102,6 +130,14 @@ export default { </div> <template v-else> + <gl-alert + v-if="showErrorPackageAlert" + variant="danger" + :title="errorTitleAlert" + :primary-button-text="$options.i18n.deleteThisPackage" + @primaryAction="showConfirmationModal" + >{{ $options.i18n.errorMessageBodyAlert }}</gl-alert + > <div data-qa-selector="packages-table"> <packages-list-row v-for="packageEntity in list" diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql index 66315fda9e9..b5695a01376 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql @@ -5,6 +5,7 @@ fragment PackageData on Package { packageType createdAt status + canDestroy tags { nodes { id 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 c45cbe56e00..41b0c285fff 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 @@ -1,4 +1,4 @@ -query getPackageDetails($id: ID!) { +query getPackageDetails($id: PackagesPackageID!) { package(id: $id) { id name diff --git a/app/assets/javascripts/packages_and_registries/settings/group/bundle.js b/app/assets/javascripts/packages_and_registries/settings/group/bundle.js index 85a7aeb5561..482a3ef2ead 100644 --- a/app/assets/javascripts/packages_and_registries/settings/group/bundle.js +++ b/app/assets/javascripts/packages_and_registries/settings/group/bundle.js @@ -21,7 +21,6 @@ export default () => { groupPath: el.dataset.groupPath, groupDependencyProxyPath: el.dataset.groupDependencyProxyPath, defaultExpanded: parseBoolean(el.dataset.defaultExpanded), - dependencyProxyAvailable: parseBoolean(el.dataset.dependencyProxyAvailable), }, render(createElement) { return createElement(SettingsApp); diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue index fd62fe144b2..a5189201112 100644 --- a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue +++ b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue @@ -13,7 +13,6 @@ import { import { DEPENDENCY_PROXY_HEADER, - DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, DEPENDENCY_PROXY_DOCS_PATH, } from '~/packages_and_registries/settings/group/constants'; @@ -28,7 +27,6 @@ export default { }, i18n: { DEPENDENCY_PROXY_HEADER, - DEPENDENCY_PROXY_SETTINGS_DESCRIPTION, enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'), enabledProxyHelpText: s__( 'DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}', @@ -140,19 +138,6 @@ export default { data-qa-selector="dependency_proxy_settings_content" > <template #title> {{ $options.i18n.DEPENDENCY_PROXY_HEADER }} </template> - <template #description> - <span data-testid="description"> - <gl-sprintf :message="$options.i18n.DEPENDENCY_PROXY_SETTINGS_DESCRIPTION"> - <template #docLink="{ content }"> - <gl-link - data-testid="description-link" - :href="$options.links.DEPENDENCY_PROXY_DOCS_PATH" - >{{ content }}</gl-link - > - </template> - </gl-sprintf> - </span> - </template> <template #default> <div> <gl-toggle diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue index 64c12b4be6a..f285dfc0755 100644 --- a/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue +++ b/app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue @@ -13,7 +13,7 @@ export default { PackagesSettings, DependencyProxySettings, }, - inject: ['groupPath', 'dependencyProxyAvailable'], + inject: ['groupPath'], apollo: { group: { query: getGroupPackagesSettingsQuery, @@ -83,7 +83,6 @@ export default { /> <dependency-proxy-settings - v-if="dependencyProxyAvailable" :dependency-proxy-settings="dependencyProxySettings" :dependency-proxy-image-ttl-policy="dependencyProxyImageTtlPolicy" :is-loading="isLoading" diff --git a/app/assets/javascripts/packages_and_registries/settings/group/constants.js b/app/assets/javascripts/packages_and_registries/settings/group/constants.js index ee922457993..0249b475e46 100644 --- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js +++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js @@ -19,13 +19,10 @@ export const DUPLICATES_SETTINGS_EXCEPTION_LEGEND = s__( ); export const DEPENDENCY_PROXY_HEADER = s__('DependencyProxy|Dependency Proxy'); -export const DEPENDENCY_PROXY_SETTINGS_DESCRIPTION = s__( - 'DependencyProxy|Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.', -); // Parameters -export const PACKAGES_DOCS_PATH = helpPagePath('user/packages'); +export const PACKAGES_DOCS_PATH = helpPagePath('user/packages/index'); export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed'; export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex'; |