diff options
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
4 files changed, 187 insertions, 168 deletions
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 10ac4c5383b..3157653648b 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 @@ -8,8 +8,10 @@ import { GlButton, GlFormCheckbox, GlLoadingIcon, + GlModal, + GlSprintf, } from '@gitlab/ui'; -import { last } from 'lodash'; +import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert'; import { numberToHumanSize } from '~/lib/utils/number_utils'; import { __, s__ } from '~/locale'; import FileSha from '~/packages_and_registries/package_registry/components/details/file_sha.vue'; @@ -22,10 +24,22 @@ import { GRAPHQL_PACKAGE_FILES_PAGE_SIZE, REQUEST_DELETE_SELECTED_PACKAGE_FILE_TRACKING_ACTION, SELECT_PACKAGE_FILE_TRACKING_ACTION, + DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION, + CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, + DELETE_PACKAGE_FILE_TRACKING_ACTION, + REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION, TRACKING_LABEL_PACKAGE_ASSET, TRACKING_ACTION_EXPAND_PACKAGE_ASSET, + DELETE_PACKAGE_FILE_ERROR_MESSAGE, + DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, + DELETE_PACKAGE_FILES_ERROR_MESSAGE, + DELETE_PACKAGE_FILES_SUCCESS_MESSAGE, + DELETE_PACKAGE_FILES_TRACKING_ACTION, + DELETE_ALL_PACKAGE_FILES_MODAL_CONTENT, + DELETE_LAST_PACKAGE_FILE_MODAL_CONTENT, } from '~/packages_and_registries/package_registry/constants'; import getPackageFilesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql'; +import destroyPackageFilesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_files.mutation.graphql'; export default { name: 'PackageFiles', @@ -38,22 +52,25 @@ export default { GlFormCheckbox, GlButton, GlLoadingIcon, + GlModal, + GlSprintf, FileIcon, TimeAgoTooltip, FileSha, }, mixins: [Tracking.mixin()], + trackingActions: { + DELETE_PACKAGE_FILE_TRACKING_ACTION, + REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION, + CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, + DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION, + }, props: { canDelete: { type: Boolean, required: false, default: false, }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, packageId: { type: String, required: true, @@ -62,6 +79,10 @@ export default { type: String, required: true, }, + projectPath: { + type: String, + required: true, + }, }, apollo: { packageFiles: { @@ -73,7 +94,7 @@ export default { return this.queryVariables; }, update(data) { - return data.package?.packageFiles?.nodes || []; + return data.package?.packageFiles ?? {}; }, error() { this.fetchPackageFilesError = true; @@ -83,29 +104,33 @@ export default { data() { return { fetchPackageFilesError: false, - packageFiles: [], + filesToDelete: [], + packageFiles: {}, + mutationLoading: false, selectedReferences: [], }; }, computed: { + files() { + return this.packageFiles?.nodes ?? []; + }, areFilesSelected() { return this.selectedReferences.length > 0; }, areAllFilesSelected() { - return this.packageFiles.length > 0 && this.packageFiles.every(this.isSelected); + return this.files.length > 0 && this.files.every(this.isSelected); }, filesTableRows() { - return this.packageFiles.map((pf) => ({ + return this.files.map((pf) => ({ ...pf, size: this.formatSize(pf.size), - pipeline: last(pf.pipelines), })); }, hasSelectedSomeFiles() { return this.areFilesSelected && !this.areAllFilesSelected; }, - loading() { - return this.$apollo.queries.packageFiles.loading || this.isLoading; + isLoading() { + return this.$apollo.queries.packageFiles.loading || this.mutationLoading; }, filesTableHeaderFields() { return [ @@ -148,6 +173,29 @@ export default { category: packageTypeToTrackCategory(this.packageType), }; }, + refetchQueriesData() { + return [ + { + query: getPackageFilesQuery, + variables: this.queryVariables, + }, + ]; + }, + modalAction() { + return this.hasOneItem(this.filesToDelete) + ? this.$options.modal.fileDeletePrimaryAction + : this.$options.modal.filesDeletePrimaryAction; + }, + modalTitle() { + return this.hasOneItem(this.filesToDelete) + ? this.$options.i18n.deleteFileModalTitle + : this.$options.i18n.deleteFilesModalTitle; + }, + modalDescription() { + return this.hasOneItem(this.filesToDelete) + ? this.$options.i18n.deleteFileModalContent + : this.$options.i18n.deleteFilesModalContent; + }, }, methods: { formatSize(size) { @@ -170,15 +218,97 @@ export default { }, handleFileDeleteSelected() { this.track(REQUEST_DELETE_SELECTED_PACKAGE_FILE_TRACKING_ACTION); - this.$emit('delete-files', this.selectedReferences); + this.handleFileDelete(this.selectedReferences); + }, + async deletePackageFiles(ids) { + this.mutationLoading = true; + try { + const { data } = await this.$apollo.mutate({ + mutation: destroyPackageFilesMutation, + variables: { + projectPath: this.projectPath, + ids, + }, + awaitRefetchQueries: true, + refetchQueries: this.refetchQueriesData, + }); + if (data?.destroyPackageFiles?.errors[0]) { + throw data.destroyPackageFiles.errors[0]; + } + createAlert({ + message: this.hasOneItem(ids) + ? DELETE_PACKAGE_FILE_SUCCESS_MESSAGE + : DELETE_PACKAGE_FILES_SUCCESS_MESSAGE, + variant: VARIANT_SUCCESS, + }); + } catch (error) { + createAlert({ + message: this.hasOneItem(ids) + ? DELETE_PACKAGE_FILE_ERROR_MESSAGE + : DELETE_PACKAGE_FILES_ERROR_MESSAGE, + variant: VARIANT_WARNING, + captureError: true, + error, + }); + } finally { + this.mutationLoading = false; + this.filesToDelete = []; + this.selectedReferences = []; + } + }, + handleFileDelete(files) { + this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION); + if (files.length === this.files.length && !this.packageFiles?.pageInfo?.hasNextPage) { + this.$emit( + 'delete-all-files', + this.hasOneItem(files) + ? DELETE_LAST_PACKAGE_FILE_MODAL_CONTENT + : DELETE_ALL_PACKAGE_FILES_MODAL_CONTENT, + ); + } else { + this.filesToDelete = files; + this.$refs.deleteFilesModal.show(); + } + }, + hasOneItem(items) { + return items.length === 1; + }, + confirmFilesDelete() { + if (this.hasOneItem(this.filesToDelete)) { + this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION); + } else { + this.track(DELETE_PACKAGE_FILES_TRACKING_ACTION); + } + this.deletePackageFiles(this.filesToDelete.map((file) => file.id)); }, }, i18n: { - deleteFile: __('Delete asset'), + deleteFile: s__('PackageRegistry|Delete asset'), + deleteFileModalTitle: s__('PackageRegistry|Delete package asset'), + deleteFileModalContent: s__( + 'PackageRegistry|You are about to delete %{filename}. This is a destructive action that may render your package unusable. Are you sure?', + ), + deleteFilesModalTitle: s__('PackageRegistry|Delete %{count} assets'), + deleteFilesModalContent: s__( + 'PackageRegistry|You are about to delete %{count} assets. This operation is irreversible.', + ), deleteSelected: s__('PackageRegistry|Delete selected'), moreActionsText: __('More actions'), fetchPackageFilesErrorMessage: FETCH_PACKAGE_FILES_ERROR_MESSAGE, }, + modal: { + fileDeletePrimaryAction: { + text: __('Delete'), + attributes: { variant: 'danger', category: 'primary' }, + }, + filesDeletePrimaryAction: { + text: s__('PackageRegistry|Permanently delete assets'), + attributes: { variant: 'danger', category: 'primary' }, + }, + cancelAction: { + text: __('Cancel'), + }, + }, }; </script> @@ -188,7 +318,7 @@ export default { <h3 class="gl-font-lg gl-mt-5">{{ __('Assets') }}</h3> <gl-button v-if="!fetchPackageFilesError && canDelete" - :disabled="loading || !areFilesSelected" + :disabled="isLoading || !areFilesSelected" category="secondary" variant="danger" data-testid="delete-selected" @@ -206,7 +336,7 @@ export default { </gl-alert> <gl-table v-else - :busy="loading" + :busy="isLoading" :fields="filesTableHeaderFields" :items="filesTableRows" show-empty @@ -255,7 +385,7 @@ export default { :href="item.downloadPath" class="gl-text-gray-500" data-testid="download-link" - @click="$emit('download-file')" + @click="track($options.trackingActions.DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION)" > <file-icon :file-name="item.fileName" @@ -279,7 +409,7 @@ export default { no-caret right > - <gl-dropdown-item data-testid="delete-file" @click="$emit('delete-files', [item])"> + <gl-dropdown-item data-testid="delete-file" @click="handleFileDelete([item])"> {{ $options.i18n.deleteFile }} </gl-dropdown-item> </gl-dropdown> @@ -300,5 +430,34 @@ export default { </div> </template> </gl-table> + + <gl-modal + ref="deleteFilesModal" + size="sm" + modal-id="delete-files-modal" + :action-primary="modalAction" + :action-cancel="$options.modal.cancelAction" + data-testid="delete-files-modal" + @primary="confirmFilesDelete" + @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)" + > + <template #modal-title> + <gl-sprintf :message="modalTitle"> + <template #count> + {{ filesToDelete.length }} + </template> + </gl-sprintf> + </template> + + <gl-sprintf :message="modalDescription"> + <template #filename> + <strong>{{ filesToDelete[0].fileName }}</strong> + </template> + + <template #count> + {{ filesToDelete.length }} + </template> + </gl-sprintf> + </gl-modal> </div> </template> 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 e5ef9265f3e..2a9cfb955a7 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 @@ -47,9 +47,6 @@ query getPackageDetails($id: PackagesPackageID!) { } } packageFiles(first: 100) { - pageInfo { - hasNextPage - } nodes { id size diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql index 7851cd39200..e6f292ec1d3 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql +++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_files.query.graphql @@ -2,6 +2,9 @@ query getPackageFiles($id: PackagesPackageID!, $first: Int) { package(id: $id) { id packageFiles(first: $first) { + pageInfo { + hasNextPage + } nodes { id fileMd5 diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue index 48a45956ef1..922886fa9cd 100644 --- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue +++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue @@ -11,7 +11,7 @@ import { GlTabs, GlSprintf, } from '@gitlab/ui'; -import { createAlert, VARIANT_SUCCESS, VARIANT_WARNING } from '~/alert'; +import { createAlert } from '~/alert'; import { TYPENAME_PACKAGES_PACKAGE } from '~/graphql_shared/constants'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import { numberToHumanSize } from '~/lib/utils/number_utils'; @@ -33,27 +33,15 @@ import { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, CANCEL_DELETE_PACKAGE_TRACKING_ACTION, - DELETE_PACKAGE_FILE_TRACKING_ACTION, - DELETE_PACKAGE_FILES_TRACKING_ACTION, - REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION, REQUEST_FORWARDING_HELP_PAGE_PATH, - CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, SHOW_DELETE_SUCCESS_ALERT, FETCH_PACKAGE_DETAILS_ERROR_MESSAGE, - DELETE_PACKAGE_FILE_ERROR_MESSAGE, - DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, - DELETE_PACKAGE_FILES_ERROR_MESSAGE, - DELETE_PACKAGE_FILES_SUCCESS_MESSAGE, DELETE_PACKAGE_REQUEST_FORWARDING_MODAL_CONTENT, - DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION, DELETE_MODAL_TITLE, DELETE_MODAL_CONTENT, - DELETE_ALL_PACKAGE_FILES_MODAL_CONTENT, - DELETE_LAST_PACKAGE_FILE_MODAL_CONTENT, GRAPHQL_PAGE_SIZE, } from '~/packages_and_registries/package_registry/constants'; -import destroyPackageFilesMutation from '~/packages_and_registries/package_registry/graphql/mutations/destroy_package_files.mutation.graphql'; import getPackageDetails from '~/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql'; import getPackageVersionsQuery from '~/packages_and_registries/package_registry/graphql/queries/get_package_versions.query.graphql'; import Tracking from '~/tracking'; @@ -92,10 +80,6 @@ export default { DELETE_PACKAGE_TRACKING_ACTION, REQUEST_DELETE_PACKAGE_TRACKING_ACTION, CANCEL_DELETE_PACKAGE_TRACKING_ACTION, - DELETE_PACKAGE_FILE_TRACKING_ACTION, - REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION, - CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION, - DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION, }, data() { return { @@ -158,9 +142,6 @@ export default { isLoading() { return this.$apollo.queries.packageEntity.loading; }, - packageFilesMutationLoading() { - return this.mutationLoading; - }, isValidPackage() { return this.isLoading || Boolean(this.packageEntity.name); }, @@ -196,14 +177,6 @@ export default { PACKAGE_TYPE_PYPI, ].includes(this.packageType); }, - refetchQueriesData() { - return [ - { - query: getPackageDetails, - variables: this.queryVariables, - }, - ]; - }, refetchVersionsQueryData() { return [ { @@ -230,71 +203,9 @@ export default { window.location.replace(`${returnTo}?${modalQuery}`); }, - async deletePackageFiles(ids) { - this.mutationLoading = true; - try { - const { data } = await this.$apollo.mutate({ - mutation: destroyPackageFilesMutation, - variables: { - projectPath: this.projectPath, - ids, - }, - awaitRefetchQueries: true, - refetchQueries: this.refetchQueriesData, - }); - if (data?.destroyPackageFiles?.errors[0]) { - throw data.destroyPackageFiles.errors[0]; - } - createAlert({ - message: this.isLastItem(ids) - ? DELETE_PACKAGE_FILE_SUCCESS_MESSAGE - : DELETE_PACKAGE_FILES_SUCCESS_MESSAGE, - variant: VARIANT_SUCCESS, - }); - } catch (error) { - createAlert({ - message: this.isLastItem(ids) - ? DELETE_PACKAGE_FILE_ERROR_MESSAGE - : DELETE_PACKAGE_FILES_ERROR_MESSAGE, - variant: VARIANT_WARNING, - captureError: true, - error, - }); - } - this.mutationLoading = false; - }, - handleFileDelete(files) { - this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION); - if ( - files.length === this.packageFiles.length && - !this.packageEntity.packageFiles?.pageInfo?.hasNextPage - ) { - if (this.isLastItem(files)) { - this.deletePackageModalContent = DELETE_LAST_PACKAGE_FILE_MODAL_CONTENT; - } else { - this.deletePackageModalContent = DELETE_ALL_PACKAGE_FILES_MODAL_CONTENT; - } - this.$refs.deleteModal.show(); - } else { - this.filesToDelete = files; - if (this.isLastItem(files)) { - this.$refs.deleteFileModal.show(); - } else if (files.length > 1) { - this.$refs.deleteFilesModal.show(); - } - } - }, - isLastItem(items) { - return items.length === 1; - }, - confirmFilesDelete() { - if (this.isLastItem(this.filesToDelete)) { - this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION); - } else { - this.track(DELETE_PACKAGE_FILES_TRACKING_ACTION); - } - this.deletePackageFiles(this.filesToDelete.map((file) => file.id)); - this.filesToDelete = []; + handleAllFilesDelete(content) { + this.deletePackageModalContent = content; + this.$refs.deleteModal.show(); }, resetDeleteModalContent() { this.deletePackageModalContent = DELETE_MODAL_CONTENT; @@ -302,10 +213,6 @@ export default { }, i18n: { DELETE_MODAL_TITLE, - deleteFileModalTitle: s__(`PackageRegistry|Delete package asset`), - deleteFileModalContent: s__( - `PackageRegistry|You are about to delete %{filename}. This is a destructive action that may render your package unusable. Are you sure?`, - ), otherVersionsTabTitle: s__('PackageRegistry|Other versions'), }, links: { @@ -374,11 +281,10 @@ export default { <package-files v-if="showFiles" :can-delete="packageEntity.canDestroy" - :is-loading="packageFilesMutationLoading" :package-id="packageEntity.id" :package-type="packageType" - @download-file="track($options.trackingActions.DOWNLOAD_PACKAGE_ASSET_TRACKING_ACTION)" - @delete-files="handleFileDelete" + :project-path="projectPath" + @delete-all-files="handleAllFilesDelete" /> </div> </gl-tab> @@ -471,51 +377,5 @@ export default { </gl-modal> </template> </delete-packages> - - <gl-modal - ref="deleteFileModal" - size="sm" - modal-id="delete-file-modal" - :action-primary="$options.modal.fileDeletePrimaryAction" - :action-cancel="$options.modal.cancelAction" - data-testid="delete-file-modal" - @primary="confirmFilesDelete" - @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)" - > - <template #modal-title>{{ $options.i18n.deleteFileModalTitle }}</template> - <gl-sprintf v-if="isLastItem(filesToDelete)" :message="$options.i18n.deleteFileModalContent"> - <template #filename> - <strong>{{ filesToDelete[0].fileName }}</strong> - </template> - </gl-sprintf> - </gl-modal> - - <gl-modal - ref="deleteFilesModal" - size="sm" - modal-id="delete-files-modal" - :action-primary="$options.modal.filesDeletePrimaryAction" - :action-cancel="$options.modal.cancelAction" - data-testid="delete-files-modal" - @primary="confirmFilesDelete" - @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)" - > - <template #modal-title>{{ - n__( - `PackageRegistry|Delete 1 asset`, - `PackageRegistry|Delete %d assets`, - filesToDelete.length, - ) - }}</template> - <span v-if="filesToDelete.length > 0"> - {{ - n__( - `PackageRegistry|You are about to delete 1 asset. This operation is irreversible.`, - `PackageRegistry|You are about to delete %d assets. This operation is irreversible.`, - filesToDelete.length, - ) - }} - </span> - </gl-modal> </div> </template> |