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>2021-10-20 11:43:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-10-20 11:43:02 +0300
commitd9ab72d6080f594d0b3cae15f14b3ef2c6c638cb (patch)
tree2341ef426af70ad1e289c38036737e04b0aa5007 /app/assets/javascripts/packages_and_registries
parentd6e514dd13db8947884cd58fe2a9c2a063400a9b (diff)
Add latest changes from gitlab-org/gitlab@14-4-stable-eev14.4.0-rc42
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue105
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/index.js14
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql10
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/index.js26
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue240
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/details_title.vue (renamed from app/assets/javascripts/packages_and_registries/infrastructure_registry/components/details_title.vue)0
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/file_sha.vue41
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue165
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue165
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue (renamed from app/assets/javascripts/packages_and_registries/infrastructure_registry/components/terraform_installation.vue)0
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/constants.js5
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js59
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/getters.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/index.js18
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutations.js17
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details_app_bundle.js4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue15
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue30
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue19
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue134
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue151
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue81
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue132
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/publish_method.vue61
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js12
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql27
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql27
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/list.js10
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/bundle.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue110
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/duplicates_settings.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue167
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue139
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/constants.js8
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql8
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql3
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/cache_update.js13
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/optimistic_responses.js12
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/project/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/constants.js2
42 files changed, 1744 insertions, 298 deletions
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
new file mode 100644
index 00000000000..73fb3656af1
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -0,0 +1,105 @@
+<script>
+import { GlAlert, GlFormGroup, GlFormInputGroup, GlSkeletonLoader, GlSprintf } from '@gitlab/ui';
+import { __ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+import {
+ DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
+ DEPENDENCY_PROXY_DOCS_PATH,
+} from '~/packages_and_registries/settings/group/constants';
+
+import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
+
+export default {
+ components: {
+ GlFormGroup,
+ GlAlert,
+ GlFormInputGroup,
+ GlSprintf,
+ ClipboardButton,
+ TitleArea,
+ GlSkeletonLoader,
+ },
+ inject: ['groupPath', 'dependencyProxyAvailable'],
+ i18n: {
+ proxyNotAvailableText: __('Dependency Proxy feature is limited to public groups for now.'),
+ proxyDisabledText: __('Dependency Proxy disabled. To enable it, contact the group owner.'),
+ proxyImagePrefix: __('Dependency Proxy image prefix'),
+ copyImagePrefixText: __('Copy prefix'),
+ blobCountAndSize: __('Contains %{count} blobs of images (%{size})'),
+ },
+ data() {
+ return {
+ group: {},
+ };
+ },
+ apollo: {
+ group: {
+ query: getDependencyProxyDetailsQuery,
+ skip() {
+ return !this.dependencyProxyAvailable;
+ },
+ variables() {
+ return { fullPath: this.groupPath };
+ },
+ },
+ },
+ computed: {
+ infoMessages() {
+ return [
+ {
+ text: DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
+ link: DEPENDENCY_PROXY_DOCS_PATH,
+ },
+ ];
+ },
+ dependencyProxyEnabled() {
+ return this.group?.dependencyProxySetting?.enabled;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <title-area :title="__('Dependency Proxy')" :info-messages="infoMessages" />
+ <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-if="dependencyProxyEnabled" 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>
+ </template>
+ </gl-form-group>
+ </div>
+ <gl-alert v-else :dismissible="false" data-testid="proxy-disabled">
+ {{ $options.i18n.proxyDisabledText }}
+ </gl-alert>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/index.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/index.js
new file mode 100644
index 00000000000..16152eb81f6
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/index.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+
+Vue.use(VueApollo);
+
+export const apolloProvider = new VueApollo({
+ defaultClient: createDefaultClient(
+ {},
+ {
+ assumeImmutableResults: true,
+ },
+ ),
+});
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
new file mode 100644
index 00000000000..9058d349bf3
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
@@ -0,0 +1,10 @@
+query getDependencyProxyDetails($fullPath: ID!) {
+ group(fullPath: $fullPath) {
+ dependencyProxyBlobCount
+ dependencyProxyTotalSize
+ dependencyProxyImagePrefix
+ dependencyProxySetting {
+ enabled
+ }
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
new file mode 100644
index 00000000000..dc73470e07d
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
@@ -0,0 +1,26 @@
+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';
+
+Vue.use(Translate);
+
+export const initDependencyProxyApp = () => {
+ const el = document.getElementById('js-dependency-proxy');
+ if (!el) {
+ return null;
+ }
+ const { dependencyProxyAvailable, ...dataset } = el.dataset;
+ return new Vue({
+ el,
+ apolloProvider,
+ provide: {
+ dependencyProxyAvailable: parseBoolean(dependencyProxyAvailable),
+ ...dataset,
+ },
+ render(createElement) {
+ return createElement(app);
+ },
+ });
+};
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
new file mode 100644
index 00000000000..6016757c1b9
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
@@ -0,0 +1,240 @@
+<script>
+import {
+ GlButton,
+ GlModal,
+ GlModalDirective,
+ GlTooltipDirective,
+ GlEmptyState,
+ GlTab,
+ GlTabs,
+ GlSprintf,
+} from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { objectToQuery } from '~/lib/utils/url_utility';
+import { s__, __ } from '~/locale';
+import TerraformTitle from '~/packages_and_registries/infrastructure_registry/details/components/details_title.vue';
+import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue';
+import Tracking from '~/tracking';
+import PackageListRow from '~/packages/shared/components/package_list_row.vue';
+import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
+import { TrackingActions, SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
+import { packageTypeToTrackCategory } from '~/packages/shared/utils';
+import PackageFiles from './package_files.vue';
+import PackageHistory from './package_history.vue';
+
+export default {
+ name: 'PackagesApp',
+ components: {
+ GlButton,
+ GlEmptyState,
+ GlModal,
+ GlTab,
+ GlTabs,
+ GlSprintf,
+ TerraformTitle,
+ PackagesListLoader,
+ PackageListRow,
+ PackageHistory,
+ TerraformInstallation,
+ PackageFiles,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ GlModal: GlModalDirective,
+ },
+ mixins: [Tracking.mixin()],
+ trackingActions: { ...TrackingActions },
+ data() {
+ return {
+ fileToDelete: null,
+ };
+ },
+ computed: {
+ ...mapState([
+ 'projectName',
+ 'packageEntity',
+ 'packageFiles',
+ 'isLoading',
+ 'canDelete',
+ 'svgPath',
+ 'npmPath',
+ 'npmHelpPath',
+ 'projectListUrl',
+ 'groupListUrl',
+ ]),
+ isValidPackage() {
+ return Boolean(this.packageEntity.name);
+ },
+ tracking() {
+ return {
+ category: packageTypeToTrackCategory(this.packageEntity.package_type),
+ };
+ },
+ hasVersions() {
+ return this.packageEntity.versions?.length > 0;
+ },
+ },
+ methods: {
+ ...mapActions(['deletePackage', 'fetchPackageVersions', 'deletePackageFile']),
+ formatSize(size) {
+ return numberToHumanSize(size);
+ },
+ getPackageVersions() {
+ if (!this.packageEntity.versions) {
+ this.fetchPackageVersions();
+ }
+ },
+ async confirmPackageDeletion() {
+ this.track(TrackingActions.DELETE_PACKAGE);
+ await this.deletePackage();
+ const returnTo =
+ !this.groupListUrl || document.referrer.includes(this.projectName)
+ ? this.projectListUrl
+ : this.groupListUrl; // to avoid security issue url are supplied from backend
+ const modalQuery = objectToQuery({ [SHOW_DELETE_SUCCESS_ALERT]: true });
+ window.location.replace(`${returnTo}?${modalQuery}`);
+ },
+ handleFileDelete(file) {
+ this.track(TrackingActions.REQUEST_DELETE_PACKAGE_FILE);
+ this.fileToDelete = { ...file };
+ this.$refs.deleteFileModal.show();
+ },
+ confirmFileDelete() {
+ this.track(TrackingActions.DELETE_PACKAGE_FILE);
+ this.deletePackageFile(this.fileToDelete.id);
+ this.fileToDelete = null;
+ },
+ },
+ i18n: {
+ deleteModalTitle: s__(`PackageRegistry|Delete Package Version`),
+ deleteModalContent: s__(
+ `PackageRegistry|You are about to delete version %{version} of %{name}. Are you sure?`,
+ ),
+ deleteFileModalTitle: s__(`PackageRegistry|Delete Package File`),
+ deleteFileModalContent: s__(
+ `PackageRegistry|You are about to delete %{filename}. This is a destructive action that may render your package unusable. Are you sure?`,
+ ),
+ },
+ modal: {
+ packageDeletePrimaryAction: {
+ text: __('Delete'),
+ attributes: [
+ { variant: 'danger' },
+ { category: 'primary' },
+ { 'data-qa-selector': 'delete_modal_button' },
+ ],
+ },
+ fileDeletePrimaryAction: {
+ text: __('Delete'),
+ attributes: [{ variant: 'danger' }, { category: 'primary' }],
+ },
+ cancelAction: {
+ text: __('Cancel'),
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-empty-state
+ v-if="!isValidPackage"
+ :title="s__('PackageRegistry|Unable to load package')"
+ :description="s__('PackageRegistry|There was a problem fetching the details for this package.')"
+ :svg-path="svgPath"
+ />
+
+ <div v-else class="packages-app">
+ <terraform-title>
+ <template #delete-button>
+ <gl-button
+ v-if="canDelete"
+ v-gl-modal="'delete-modal'"
+ class="js-delete-button"
+ variant="danger"
+ category="primary"
+ data-qa-selector="delete_button"
+ >
+ {{ __('Delete') }}
+ </gl-button>
+ </template>
+ </terraform-title>
+
+ <gl-tabs>
+ <gl-tab :title="__('Detail')">
+ <div data-qa-selector="package_information_content">
+ <package-history :package-entity="packageEntity" :project-name="projectName" />
+ <terraform-installation />
+ </div>
+
+ <package-files
+ :package-files="packageFiles"
+ :can-delete="canDelete"
+ @download-file="track($options.trackingActions.PULL_PACKAGE)"
+ @delete-file="handleFileDelete"
+ />
+ </gl-tab>
+
+ <gl-tab
+ :title="__('Other versions')"
+ title-item-class="js-versions-tab"
+ @click="getPackageVersions"
+ >
+ <template v-if="isLoading && !hasVersions">
+ <packages-list-loader />
+ </template>
+
+ <template v-else-if="hasVersions">
+ <package-list-row
+ v-for="v in packageEntity.versions"
+ :key="v.id"
+ :package-entity="{ name: packageEntity.name, ...v }"
+ :package-link="v.id.toString()"
+ :disable-delete="true"
+ :show-package-type="false"
+ />
+ </template>
+
+ <p v-else class="gl-mt-3" data-testid="no-versions-message">
+ {{ s__('PackageRegistry|There are no other versions of this package.') }}
+ </p>
+ </gl-tab>
+ </gl-tabs>
+
+ <gl-modal
+ ref="deleteModal"
+ modal-id="delete-modal"
+ :action-primary="$options.modal.packageDeletePrimaryAction"
+ :action-cancel="$options.modal.cancelAction"
+ @primary="confirmPackageDeletion"
+ @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE)"
+ >
+ <template #modal-title>{{ $options.i18n.deleteModalTitle }}</template>
+ <gl-sprintf :message="$options.i18n.deleteModalContent">
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ </gl-sprintf>
+ </gl-modal>
+
+ <gl-modal
+ ref="deleteFileModal"
+ modal-id="delete-file-modal"
+ :action-primary="$options.modal.fileDeletePrimaryAction"
+ :action-cancel="$options.modal.cancelAction"
+ @primary="confirmFileDelete"
+ @canceled="track($options.trackingActions.CANCEL_DELETE_PACKAGE_FILE)"
+ >
+ <template #modal-title>{{ $options.i18n.deleteFileModalTitle }}</template>
+ <gl-sprintf v-if="fileToDelete" :message="$options.i18n.deleteFileModalContent">
+ <template #filename>
+ <strong>{{ fileToDelete.file_name }}</strong>
+ </template>
+ </gl-sprintf>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/details_title.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/details_title.vue
index 3e551706ed0..3e551706ed0 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/details_title.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/details_title.vue
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/file_sha.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/file_sha.vue
new file mode 100644
index 00000000000..a25839be7e1
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/file_sha.vue
@@ -0,0 +1,41 @@
+<script>
+import { s__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import DetailsRow from '~/vue_shared/components/registry/details_row.vue';
+
+export default {
+ name: 'FileSha',
+ components: {
+ DetailsRow,
+ ClipboardButton,
+ },
+ props: {
+ sha: {
+ type: String,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ },
+ i18n: {
+ copyButtonTitle: s__('PackageRegistry|Copy SHA'),
+ },
+};
+</script>
+
+<template>
+ <details-row dashed>
+ <div class="gl-px-4">
+ {{ title }}:
+ {{ sha }}
+ <clipboard-button
+ :text="sha"
+ :title="$options.i18n.copyButtonTitle"
+ category="tertiary"
+ size="small"
+ />
+ </div>
+ </details-row>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue
new file mode 100644
index 00000000000..ab4cfccd023
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_files.vue
@@ -0,0 +1,165 @@
+<script>
+import { GlLink, GlTable, GlDropdownItem, GlDropdown, GlIcon, GlButton } from '@gitlab/ui';
+import { last } from 'lodash';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { __ } from '~/locale';
+import Tracking from '~/tracking';
+import FileIcon from '~/vue_shared/components/file_icon.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import FileSha from './file_sha.vue';
+
+export default {
+ name: 'PackageFiles',
+ components: {
+ GlLink,
+ GlTable,
+ GlIcon,
+ GlDropdown,
+ GlDropdownItem,
+ GlButton,
+ FileIcon,
+ TimeAgoTooltip,
+ FileSha,
+ },
+ mixins: [Tracking.mixin()],
+ props: {
+ packageFiles: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ canDelete: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ computed: {
+ filesTableRows() {
+ return this.packageFiles.map((pf) => ({
+ ...pf,
+ size: this.formatSize(pf.size),
+ pipeline: last(pf.pipelines),
+ }));
+ },
+ showCommitColumn() {
+ return this.filesTableRows.some((row) => Boolean(row.pipeline?.id));
+ },
+ filesTableHeaderFields() {
+ return [
+ {
+ key: 'name',
+ label: __('Name'),
+ },
+ {
+ key: 'commit',
+ label: __('Commit'),
+ hide: !this.showCommitColumn,
+ },
+ {
+ key: 'size',
+ label: __('Size'),
+ },
+ {
+ key: 'created',
+ label: __('Created'),
+ class: 'gl-text-right',
+ },
+ {
+ key: 'actions',
+ label: '',
+ hide: !this.canDelete,
+ class: 'gl-text-right',
+ tdClass: 'gl-w-4',
+ },
+ ].filter((c) => !c.hide);
+ },
+ },
+ methods: {
+ formatSize(size) {
+ return numberToHumanSize(size);
+ },
+ hasDetails(item) {
+ return item.file_sha256 || item.file_md5 || item.file_sha1;
+ },
+ },
+ i18n: {
+ deleteFile: __('Delete file'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h3 class="gl-font-lg gl-mt-5">{{ __('Files') }}</h3>
+ <gl-table
+ :fields="filesTableHeaderFields"
+ :items="filesTableRows"
+ :tbody-tr-attr="{ 'data-testid': 'file-row' }"
+ >
+ <template #cell(name)="{ item, toggleDetails, detailsShowing }">
+ <gl-button
+ v-if="hasDetails(item)"
+ :icon="detailsShowing ? 'angle-up' : 'angle-down'"
+ :aria-label="detailsShowing ? __('Collapse') : __('Expand')"
+ category="tertiary"
+ size="small"
+ @click="toggleDetails"
+ />
+ <gl-link
+ :href="item.download_path"
+ class="gl-text-gray-500"
+ data-testid="download-link"
+ @click="$emit('download-file')"
+ >
+ <file-icon
+ :file-name="item.file_name"
+ css-classes="gl-relative file-icon"
+ class="gl-mr-1 gl-relative"
+ />
+ <span>{{ item.file_name }}</span>
+ </gl-link>
+ </template>
+
+ <template #cell(commit)="{ item }">
+ <gl-link
+ v-if="item.pipeline && item.pipeline.project"
+ :href="item.pipeline.project.commit_url"
+ class="gl-text-gray-500"
+ data-testid="commit-link"
+ >{{ item.pipeline.git_commit_message }}</gl-link
+ >
+ </template>
+
+ <template #cell(created)="{ item }">
+ <time-ago-tooltip :time="item.created_at" />
+ </template>
+
+ <template #cell(actions)="{ item }">
+ <gl-dropdown category="tertiary" right>
+ <template #button-content>
+ <gl-icon name="ellipsis_v" />
+ </template>
+ <gl-dropdown-item data-testid="delete-file" @click="$emit('delete-file', item)">
+ {{ $options.i18n.deleteFile }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </template>
+
+ <template #row-details="{ item }">
+ <div
+ class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-bg-gray-10 gl-rounded-base gl-inset-border-1-gray-100"
+ >
+ <file-sha
+ v-if="item.file_sha256"
+ data-testid="sha-256"
+ title="SHA-256"
+ :sha="item.file_sha256"
+ />
+ <file-sha v-if="item.file_md5" data-testid="md5" title="MD5" :sha="item.file_md5" />
+ <file-sha v-if="item.file_sha1" data-testid="sha-1" title="SHA-1" :sha="item.file_sha1" />
+ </div>
+ </template>
+ </gl-table>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue
new file mode 100644
index 00000000000..e5be98b87f7
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/package_history.vue
@@ -0,0 +1,165 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { first } from 'lodash';
+import { truncateSha } from '~/lib/utils/text_utility';
+import { s__, n__ } from '~/locale';
+import { HISTORY_PIPELINES_LIMIT } from '~/packages_and_registries/shared/constants';
+import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
+import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ name: 'PackageHistory',
+ i18n: {
+ createdOn: s__('PackageRegistry|%{name} version %{version} was first created %{datetime}'),
+ createdByCommitText: s__('PackageRegistry|Created by commit %{link} on branch %{branch}'),
+ createdByPipelineText: s__(
+ 'PackageRegistry|Built by pipeline %{link} triggered %{datetime} by %{author}',
+ ),
+ publishText: s__('PackageRegistry|Published to the %{project} Package Registry %{datetime}'),
+ combinedUpdateText: s__(
+ 'PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}',
+ ),
+ },
+ components: {
+ GlLink,
+ GlSprintf,
+ HistoryItem,
+ TimeAgoTooltip,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ projectName: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ showDescription: false,
+ };
+ },
+ computed: {
+ pipelines() {
+ return this.packageEntity.pipelines || [];
+ },
+ firstPipeline() {
+ return first(this.pipelines);
+ },
+ lastPipelines() {
+ return this.pipelines.slice(1).slice(-HISTORY_PIPELINES_LIMIT);
+ },
+ showPipelinesInfo() {
+ return Boolean(this.firstPipeline?.id);
+ },
+ archivedLines() {
+ return Math.max(this.pipelines.length - HISTORY_PIPELINES_LIMIT - 1, 0);
+ },
+ archivedPipelineMessage() {
+ return n__(
+ 'PackageRegistry|Package has %{updatesCount} archived update',
+ 'PackageRegistry|Package has %{updatesCount} archived updates',
+ this.archivedLines,
+ );
+ },
+ },
+ methods: {
+ truncate(value) {
+ return truncateSha(value);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="issuable-discussion">
+ <h3 class="gl-font-lg" data-testid="title">{{ __('History') }}</h3>
+ <ul class="timeline main-notes-list notes gl-mb-4" data-testid="timeline">
+ <history-item icon="clock" data-testid="created-on">
+ <gl-sprintf :message="$options.i18n.createdOn">
+ <template #name>
+ <strong>{{ packageEntity.name }}</strong>
+ </template>
+ <template #version>
+ <strong>{{ packageEntity.version }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.created_at" />
+ </template>
+ </gl-sprintf>
+ </history-item>
+
+ <template v-if="showPipelinesInfo">
+ <!-- FIRST PIPELINE BLOCK -->
+ <history-item icon="commit" data-testid="first-pipeline-commit">
+ <gl-sprintf :message="$options.i18n.createdByCommitText">
+ <template #link>
+ <gl-link :href="firstPipeline.project.commit_url"
+ >#{{ truncate(firstPipeline.sha) }}</gl-link
+ >
+ </template>
+ <template #branch>
+ <strong>{{ firstPipeline.ref }}</strong>
+ </template>
+ </gl-sprintf>
+ </history-item>
+ <history-item icon="pipeline" data-testid="first-pipeline-pipeline">
+ <gl-sprintf :message="$options.i18n.createdByPipelineText">
+ <template #link>
+ <gl-link :href="firstPipeline.project.pipeline_url">#{{ firstPipeline.id }}</gl-link>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="firstPipeline.created_at" />
+ </template>
+ <template #author>{{ firstPipeline.user.name }}</template>
+ </gl-sprintf>
+ </history-item>
+ </template>
+
+ <!-- PUBLISHED LINE -->
+ <history-item icon="package" data-testid="published">
+ <gl-sprintf :message="$options.i18n.publishText">
+ <template #project>
+ <strong>{{ projectName }}</strong>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="packageEntity.created_at" />
+ </template>
+ </gl-sprintf>
+ </history-item>
+
+ <history-item v-if="archivedLines" icon="history" data-testid="archived">
+ <gl-sprintf :message="archivedPipelineMessage">
+ <template #updatesCount>
+ <strong>{{ archivedLines }}</strong>
+ </template>
+ </gl-sprintf>
+ </history-item>
+
+ <!-- PIPELINES LIST ENTRIES -->
+ <history-item
+ v-for="pipeline in lastPipelines"
+ :key="pipeline.id"
+ icon="pencil"
+ data-testid="pipeline-entry"
+ >
+ <gl-sprintf :message="$options.i18n.combinedUpdateText">
+ <template #link>
+ <gl-link :href="pipeline.project.commit_url">#{{ truncate(pipeline.sha) }}</gl-link>
+ </template>
+ <template #branch>
+ <strong>{{ pipeline.ref }}</strong>
+ </template>
+ <template #pipeline>
+ <gl-link :href="pipeline.project.pipeline_url">#{{ pipeline.id }}</gl-link>
+ </template>
+ <template #datetime>
+ <time-ago-tooltip :time="pipeline.created_at" />
+ </template>
+ </gl-sprintf>
+ </history-item>
+ </ul>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/terraform_installation.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue
index c62bf7fb722..c62bf7fb722 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/terraform_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/terraform_installation.vue
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/constants.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/constants.js
new file mode 100644
index 00000000000..c0c67faffba
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/constants.js
@@ -0,0 +1,5 @@
+import { s__ } from '~/locale';
+
+export const FETCH_PACKAGE_VERSIONS_ERROR = s__(
+ 'PackageRegistry|Unable to fetch package version information.',
+);
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
new file mode 100644
index 00000000000..a03fa8d9d63
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js
@@ -0,0 +1,59 @@
+import Api from '~/api';
+import createFlash from '~/flash';
+import {
+ DELETE_PACKAGE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_ERROR_MESSAGE,
+ DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
+} from '~/packages/shared/constants';
+import { FETCH_PACKAGE_VERSIONS_ERROR } from '../constants';
+import * as types from './mutation_types';
+
+export const fetchPackageVersions = ({ commit, state }) => {
+ commit(types.SET_LOADING, true);
+
+ const { project_id, id } = state.packageEntity;
+
+ return Api.projectPackage(project_id, id)
+ .then(({ data }) => {
+ if (data.versions) {
+ commit(types.SET_PACKAGE_VERSIONS, data.versions.reverse());
+ }
+ })
+ .catch(() => {
+ createFlash({ message: FETCH_PACKAGE_VERSIONS_ERROR, type: 'warning' });
+ })
+ .finally(() => {
+ commit(types.SET_LOADING, false);
+ });
+};
+
+export const deletePackage = ({
+ state: {
+ packageEntity: { project_id, id },
+ },
+}) => {
+ return Api.deleteProjectPackage(project_id, id).catch(() => {
+ createFlash({ message: DELETE_PACKAGE_ERROR_MESSAGE, type: 'warning' });
+ });
+};
+
+export const deletePackageFile = (
+ {
+ state: {
+ packageEntity: { project_id, id },
+ packageFiles,
+ },
+ commit,
+ },
+ fileId,
+) => {
+ return Api.deleteProjectPackageFile(project_id, id, fileId)
+ .then(() => {
+ const filtered = packageFiles.filter((f) => f.id !== fileId);
+ commit(types.UPDATE_PACKAGE_FILES, filtered);
+ createFlash({ message: DELETE_PACKAGE_FILE_SUCCESS_MESSAGE, type: 'success' });
+ })
+ .catch(() => {
+ createFlash({ message: DELETE_PACKAGE_FILE_ERROR_MESSAGE, type: 'warning' });
+ });
+};
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/getters.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/getters.js
new file mode 100644
index 00000000000..6a17e7aa6d6
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/getters.js
@@ -0,0 +1,3 @@
+export const packagePipeline = ({ packageEntity }) => {
+ return packageEntity?.pipeline || null;
+};
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/index.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/index.js
new file mode 100644
index 00000000000..15e17bcfaac
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/index.js
@@ -0,0 +1,18 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import * as getters from './getters';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export default (initialState = {}) =>
+ new Vuex.Store({
+ actions,
+ getters,
+ mutations,
+ state: {
+ isLoading: false,
+ ...initialState,
+ },
+ });
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutation_types.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutation_types.js
new file mode 100644
index 00000000000..590f2d9f970
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutation_types.js
@@ -0,0 +1,3 @@
+export const SET_LOADING = 'SET_LOADING';
+export const SET_PACKAGE_VERSIONS = 'SET_PACKAGE_VERSIONS';
+export const UPDATE_PACKAGE_FILES = 'UPDATE_PACKAGE_FILES';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutations.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutations.js
new file mode 100644
index 00000000000..762fd5a4040
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/mutations.js
@@ -0,0 +1,17 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_LOADING](state, isLoading) {
+ state.isLoading = isLoading;
+ },
+
+ [types.SET_PACKAGE_VERSIONS](state, versions) {
+ state.packageEntity = {
+ ...state.packageEntity,
+ versions,
+ };
+ },
+ [types.UPDATE_PACKAGE_FILES](state, files) {
+ state.packageFiles = files;
+ },
+};
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details_app_bundle.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details_app_bundle.js
index 98942b1e578..32fbc9382fd 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details_app_bundle.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details_app_bundle.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
-import PackagesApp from '~/packages/details/components/app.vue';
-import createStore from '~/packages/details/store';
+import PackagesApp from '~/packages_and_registries/infrastructure_registry/details/components/app.vue';
+import createStore from '~/packages_and_registries/infrastructure_registry/details/store';
import Translate from '~/vue_shared/translate';
Vue.use(Translate);
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue
index f0da7db6c91..1360b03856f 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/metadata/nuget.vue
@@ -24,7 +24,13 @@ export default {
<template>
<div>
- <details-row icon="project" padding="gl-p-4" dashed data-testid="nuget-source">
+ <details-row
+ v-if="packageEntity.metadata.projectUrl"
+ icon="project"
+ padding="gl-p-4"
+ dashed
+ data-testid="nuget-source"
+ >
<gl-sprintf :message="$options.i18n.sourceText">
<template #link>
<gl-link :href="packageEntity.metadata.projectUrl" target="_blank">{{
@@ -33,7 +39,12 @@ export default {
</template>
</gl-sprintf>
</details-row>
- <details-row icon="license" padding="gl-p-4" data-testid="nuget-license">
+ <details-row
+ v-if="packageEntity.metadata.licenseUrl"
+ icon="license"
+ padding="gl-p-4"
+ data-testid="nuget-license"
+ >
<gl-sprintf :message="$options.i18n.licenseText">
<template #link>
<gl-link :href="packageEntity.metadata.licenseUrl" target="_blank">{{
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
index 47081e23318..2448324549e 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLink, GlSprintf } from '@gitlab/ui';
+import { GlLink, GlSprintf, GlFormRadioGroup } from '@gitlab/ui';
import { s__ } from '~/locale';
import InstallationTitle from '~/packages_and_registries/package_registry/components/details/installation_title.vue';
@@ -11,6 +11,8 @@ import {
TRACKING_LABEL_CODE_INSTRUCTION,
NPM_PACKAGE_MANAGER,
YARN_PACKAGE_MANAGER,
+ PROJECT_PACKAGE_ENDPOINT_TYPE,
+ INSTANCE_PACKAGE_ENDPOINT_TYPE,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -21,8 +23,9 @@ export default {
CodeInstruction,
GlLink,
GlSprintf,
+ GlFormRadioGroup,
},
- inject: ['npmHelpPath', 'npmPath'],
+ inject: ['npmHelpPath', 'npmPath', 'npmProjectPath'],
props: {
packageEntity: {
type: Object,
@@ -32,6 +35,7 @@ export default {
data() {
return {
instructionType: NPM_PACKAGE_MANAGER,
+ packageEndpointType: INSTANCE_PACKAGE_ENDPOINT_TYPE,
};
},
computed: {
@@ -39,13 +43,13 @@ export default {
return this.npmInstallationCommand(NPM_PACKAGE_MANAGER);
},
npmSetup() {
- return this.npmSetupCommand(NPM_PACKAGE_MANAGER);
+ return this.npmSetupCommand(NPM_PACKAGE_MANAGER, this.packageEndpointType);
},
yarnCommand() {
return this.npmInstallationCommand(YARN_PACKAGE_MANAGER);
},
yarnSetupCommand() {
- return this.npmSetupCommand(YARN_PACKAGE_MANAGER);
+ return this.npmSetupCommand(YARN_PACKAGE_MANAGER, this.packageEndpointType);
},
showNpm() {
return this.instructionType === NPM_PACKAGE_MANAGER;
@@ -58,14 +62,16 @@ export default {
return `${instruction} ${this.packageEntity.name}`;
},
- npmSetupCommand(type) {
+ npmSetupCommand(type, endpointType) {
const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/'));
+ const npmPathForEndpoint =
+ endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE ? this.npmPath : this.npmProjectPath;
if (type === NPM_PACKAGE_MANAGER) {
- return `echo ${scope}:registry=${this.npmPath}/ >> .npmrc`;
+ return `echo ${scope}:registry=${npmPathForEndpoint}/ >> .npmrc`;
}
- return `echo \\"${scope}:registry\\" \\"${this.npmPath}/\\" >> .yarnrc`;
+ return `echo \\"${scope}:registry\\" \\"${npmPathForEndpoint}/\\" >> .yarnrc`;
},
},
packageManagers: {
@@ -87,6 +93,10 @@ export default {
{ value: NPM_PACKAGE_MANAGER, label: s__('PackageRegistry|Show NPM commands') },
{ value: YARN_PACKAGE_MANAGER, label: s__('PackageRegistry|Show Yarn commands') },
],
+ packageEndpointTypeOptions: [
+ { value: INSTANCE_PACKAGE_ENDPOINT_TYPE, text: s__('PackageRegistry|Instance-level') },
+ { value: PROJECT_PACKAGE_ENDPOINT_TYPE, text: s__('PackageRegistry|Project-level') },
+ ],
};
</script>
@@ -116,6 +126,12 @@ export default {
<h3 class="gl-font-lg">{{ __('Registry setup') }}</h3>
+ <gl-form-radio-group
+ :options="$options.packageEndpointTypeOptions"
+ :checked="packageEndpointType"
+ @change="packageEndpointType = $event"
+ />
+
<code-instruction
v-if="showNpm"
:instruction="npmSetup"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
index 408bd2e3dfe..af6bd7079ba 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_history.vue
@@ -1,11 +1,10 @@
<script>
-/* eslint-disable @gitlab/require-string-literal-i18n-helpers */
import { GlLink, GlSprintf } from '@gitlab/ui';
import { first } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__, n__ } from '~/locale';
-import { HISTORY_PIPELINES_LIMIT } from '~/packages/details/constants';
+import { HISTORY_PIPELINES_LIMIT } from '~/packages_and_registries/shared/constants';
import HistoryItem from '~/vue_shared/components/registry/history_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -21,8 +20,6 @@ export default {
combinedUpdateText: s__(
'PackageRegistry|Package updated by commit %{link} on branch %{branch}, built by pipeline %{pipeline}, and published to the registry %{datetime}',
),
- archivedPipelineMessageSingular: s__('PackageRegistry|Package has %{number} archived update'),
- archivedPipelineMessagePlural: s__('PackageRegistry|Package has %{number} archived updates'),
},
components: {
GlLink,
@@ -58,14 +55,14 @@ export default {
showPipelinesInfo() {
return Boolean(this.firstPipeline?.id);
},
- archiviedLines() {
+ archivedLines() {
return Math.max(this.pipelines.length - HISTORY_PIPELINES_LIMIT - 1, 0);
},
archivedPipelineMessage() {
return n__(
- this.$options.i18n.archivedPipelineMessageSingular,
- this.$options.i18n.archivedPipelineMessagePlural,
- this.archiviedLines,
+ 'PackageRegistry|Package has %{updatesCount} archived update',
+ 'PackageRegistry|Package has %{updatesCount} archived updates',
+ this.archivedLines,
);
},
},
@@ -135,10 +132,10 @@ export default {
</gl-sprintf>
</history-item>
- <history-item v-if="archiviedLines" icon="history" data-testid="archived">
+ <history-item v-if="archivedLines" icon="history" data-testid="archived">
<gl-sprintf :message="archivedPipelineMessage">
- <template #number>
- <strong>{{ archiviedLines }}</strong>
+ <template #updatesCount>
+ <strong>{{ archivedLines }}</strong>
</template>
</gl-sprintf>
</history-item>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue
new file mode 100644
index 00000000000..08481ac5655
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue
@@ -0,0 +1,134 @@
+<script>
+/*
+ * The following component has several commented lines, this is because we are refactoring them piece by piece on several mrs
+ * For a complete overview of the plan please check: https://gitlab.com/gitlab-org/gitlab/-/issues/330846
+ * This work is behind feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/341136
+ */
+// import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import createFlash from '~/flash';
+import { historyReplaceState } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
+import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
+import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
+import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
+import {
+ PROJECT_RESOURCE_TYPE,
+ GROUP_RESOURCE_TYPE,
+ LIST_QUERY_DEBOUNCE_TIME,
+} from '~/packages_and_registries/package_registry/constants';
+import PackageTitle from './package_title.vue';
+import PackageSearch from './package_search.vue';
+// import PackageList from './packages_list.vue';
+
+export default {
+ components: {
+ // GlEmptyState,
+ // GlLink,
+ // GlSprintf,
+ // PackageList,
+ PackageTitle,
+ PackageSearch,
+ },
+ inject: [
+ 'packageHelpUrl',
+ 'emptyListIllustration',
+ 'emptyListHelpUrl',
+ 'isGroupPage',
+ 'fullPath',
+ ],
+ data() {
+ return {
+ packages: {},
+ sort: '',
+ filters: {},
+ };
+ },
+ apollo: {
+ packages: {
+ query: getPackagesQuery,
+ variables() {
+ return this.queryVariables;
+ },
+ update(data) {
+ return data[this.graphqlResource].packages;
+ },
+ debounce: LIST_QUERY_DEBOUNCE_TIME,
+ },
+ },
+ computed: {
+ queryVariables() {
+ return {
+ isGroupPage: this.isGroupPage,
+ fullPath: this.fullPath,
+ sort: this.isGroupPage ? undefined : this.sort,
+ groupSort: this.isGroupPage ? this.sort : undefined,
+ packageName: this.filters?.packageName,
+ packageType: this.filters?.packageType,
+ };
+ },
+ graphqlResource() {
+ return this.isGroupPage ? GROUP_RESOURCE_TYPE : PROJECT_RESOURCE_TYPE;
+ },
+ packagesCount() {
+ return this.packages?.count;
+ },
+ hasFilters() {
+ return this.filters.packageName && this.filters.packageType;
+ },
+ emptyStateTitle() {
+ return this.emptySearch
+ ? this.$options.i18n.emptyPageTitle
+ : this.$options.i18n.noResultsTitle;
+ },
+ },
+ mounted() {
+ this.checkDeleteAlert();
+ },
+ methods: {
+ checkDeleteAlert() {
+ const urlParams = new URLSearchParams(window.location.search);
+ const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
+ if (showAlert) {
+ // to be refactored to use gl-alert
+ createFlash({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, type: 'notice' });
+ const cleanUrl = window.location.href.split('?')[0];
+ historyReplaceState(cleanUrl);
+ }
+ },
+ handleSearchUpdate({ sort, filters }) {
+ this.sort = sort;
+ this.filters = { ...filters };
+ },
+ },
+ i18n: {
+ widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'),
+ emptyPageTitle: s__('PackageRegistry|There are no packages yet'),
+ noResultsTitle: s__('PackageRegistry|Sorry, your filter produced no results'),
+ noResultsText: s__(
+ 'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.',
+ ),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <package-title :help-url="packageHelpUrl" :count="packagesCount" />
+ <package-search @update="handleSearchUpdate" />
+
+ <!-- <package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest">
+ <template #empty-state>
+ <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration">
+ <template #description>
+ <gl-sprintf v-if="hasFilters" :message="$options.i18n.widenFilters" />
+ <gl-sprintf v-else :message="$options.i18n.noResultsText">
+ <template #noPackagesLink="{ content }">
+ <gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
+ </gl-empty-state>
+ </template>
+ </package-list> -->
+ </div>
+</template>
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
new file mode 100644
index 00000000000..195ff7af583
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
@@ -0,0 +1,151 @@
+<script>
+import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import ListItem from '~/vue_shared/components/registry/list_item.vue';
+import {
+ PACKAGE_ERROR_STATUS,
+ PACKAGE_DEFAULT_STATUS,
+} from '~/packages_and_registries/package_registry/constants';
+import { getPackageTypeLabel } from '~/packages/shared/utils';
+import PackagePath from '~/packages/shared/components/package_path.vue';
+import PackageTags from '~/packages/shared/components/package_tags.vue';
+import PublishMethod from '~/packages_and_registries/package_registry/components/list/publish_method.vue';
+import PackageIconAndName from '~/packages/shared/components/package_icon_and_name.vue';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+
+export default {
+ name: 'PackageListRow',
+ components: {
+ GlButton,
+ GlLink,
+ GlSprintf,
+ GlTruncate,
+ PackageTags,
+ PackagePath,
+ PublishMethod,
+ ListItem,
+ PackageIconAndName,
+ TimeagoTooltip,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ inject: ['isGroupPage'],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ packageType() {
+ return getPackageTypeLabel(this.packageEntity.packageType.toLowerCase());
+ },
+ packageLink() {
+ const { project, id } = this.packageEntity;
+ return `${project?.webUrl}/-/packages/${getIdFromGraphQLId(id)}`;
+ },
+ pipeline() {
+ return this.packageEntity?.pipelines?.nodes[0];
+ },
+ pipelineUser() {
+ return this.pipeline?.user?.name;
+ },
+ showWarningIcon() {
+ return this.packageEntity.status === PACKAGE_ERROR_STATUS;
+ },
+ showTags() {
+ return Boolean(this.packageEntity.tags?.nodes?.length);
+ },
+ disabledRow() {
+ return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS;
+ },
+ },
+ i18n: {
+ erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'),
+ },
+};
+</script>
+
+<template>
+ <list-item data-qa-selector="package_row" :disabled="disabledRow">
+ <template #left-primary>
+ <div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
+ <gl-link
+ :href="packageLink"
+ class="gl-text-body gl-min-w-0"
+ data-qa-selector="package_link"
+ :disabled="disabledRow"
+ >
+ <gl-truncate :text="packageEntity.name" />
+ </gl-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"
+ :tags="packageEntity.tags.nodes"
+ hide-label
+ :tag-display-limit="1"
+ />
+ </div>
+ </template>
+ <template #left-secondary>
+ <div 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">
+ <gl-sprintf :message="s__('PackageRegistry|published by %{author}')">
+ <template #author>{{ pipelineUser }}</template>
+ </gl-sprintf>
+ </div>
+
+ <package-icon-and-name>
+ {{ packageType }}
+ </package-icon-and-name>
+
+ <package-path
+ v-if="isGroupPage"
+ :path="packageEntity.project.fullPath"
+ :disabled="disabledRow"
+ />
+ </div>
+ </template>
+
+ <template #right-primary>
+ <publish-method :pipeline="pipeline" />
+ </template>
+
+ <template #right-secondary>
+ <span>
+ <gl-sprintf :message="__('Created %{timestamp}')">
+ <template #timestamp>
+ <timeago-tooltip :time="packageEntity.createdAt" />
+ </template>
+ </gl-sprintf>
+ </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>
+ </list-item>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
index 280d292ce0b..836df59ca58 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
@@ -1,10 +1,14 @@
<script>
-import { mapState, mapActions } from 'vuex';
import { s__ } from '~/locale';
import { sortableFields } from '~/packages/list/utils';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
+import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
+import {
+ FILTERED_SEARCH_TERM,
+ FILTERED_SEARCH_TYPE,
+} from '~/packages_and_registries/shared/constants';
import PackageTypeToken from './tokens/package_type_token.vue';
export default {
@@ -19,21 +23,71 @@ export default {
},
],
components: { RegistrySearch, UrlSync },
+ inject: ['isGroupPage'],
+ data() {
+ return {
+ filters: [],
+ sorting: {
+ orderBy: 'name',
+ sort: 'desc',
+ },
+ mountRegistrySearch: false,
+ };
+ },
computed: {
- ...mapState({
- isGroupPage: (state) => state.config.isGroupPage,
- sorting: (state) => state.sorting,
- filter: (state) => state.filter,
- }),
sortableFields() {
return sortableFields(this.isGroupPage);
},
+ parsedSorting() {
+ const cleanOrderBy = this.sorting?.orderBy.replace('_at', '');
+ return `${cleanOrderBy}_${this.sorting?.sort}`.toUpperCase();
+ },
+ parsedFilters() {
+ const parsed = {
+ packageName: '',
+ packageType: undefined,
+ };
+
+ return this.filters.reduce((acc, filter) => {
+ if (filter.type === FILTERED_SEARCH_TYPE && filter.value?.data) {
+ return {
+ ...acc,
+ packageType: filter.value.data.toUpperCase(),
+ };
+ }
+
+ if (filter.type === FILTERED_SEARCH_TERM) {
+ return {
+ ...acc,
+ packageName: `${acc.packageName} ${filter.value.data}`.trim(),
+ };
+ }
+
+ return acc;
+ }, parsed);
+ },
+ },
+ mounted() {
+ const queryParams = getQueryParams(window.document.location.search);
+ const { sorting, filters } = extractFilterAndSorting(queryParams);
+ this.updateSorting(sorting);
+ this.updateFilters(filters);
+ this.mountRegistrySearch = true;
+ this.emitUpdate();
},
methods: {
- ...mapActions(['setSorting', 'setFilter']),
+ updateFilters(newValue) {
+ this.filters = newValue;
+ },
updateSorting(newValue) {
- this.setSorting(newValue);
- this.$emit('update');
+ this.sorting = { ...this.sorting, ...newValue };
+ },
+ updateSortingAndEmitUpdate(newValue) {
+ this.updateSorting(newValue);
+ this.emitUpdate();
+ },
+ emitUpdate() {
+ this.$emit('update', { sort: this.parsedSorting, filters: this.parsedFilters });
},
},
};
@@ -43,13 +97,14 @@ export default {
<url-sync>
<template #default="{ updateQuery }">
<registry-search
- :filter="filter"
+ v-if="mountRegistrySearch"
+ :filter="filters"
:sorting="sorting"
:tokens="$options.tokens"
:sortable-fields="sortableFields"
- @sorting:changed="updateSorting"
- @filter:changed="setFilter"
- @filter:submit="$emit('update')"
+ @sorting:changed="updateSortingAndEmitUpdate"
+ @filter:changed="updateFilters"
+ @filter:submit="emitUpdate"
@query:changed="updateQuery"
/>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue
deleted file mode 100644
index 75fbdb80192..00000000000
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue
+++ /dev/null
@@ -1,132 +0,0 @@
-<script>
-import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
-import { mapActions, mapState } from 'vuex';
-import createFlash from '~/flash';
-import { historyReplaceState } from '~/lib/utils/common_utils';
-import { s__ } from '~/locale';
-import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
-import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
-import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
-import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
-import PackageList from './packages_list.vue';
-
-export default {
- components: {
- GlEmptyState,
- GlLink,
- GlSprintf,
- PackageList,
- PackageTitle: () =>
- import(/* webpackChunkName: 'package_registry_components' */ './package_title.vue'),
- PackageSearch: () =>
- import(/* webpackChunkName: 'package_registry_components' */ './package_search.vue'),
- InfrastructureTitle: () =>
- import(
- /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue'
- ),
- InfrastructureSearch: () =>
- import(
- /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue'
- ),
- },
- inject: {
- titleComponent: {
- from: 'titleComponent',
- default: 'PackageTitle',
- },
- searchComponent: {
- from: 'searchComponent',
- default: 'PackageSearch',
- },
- emptyPageTitle: {
- from: 'emptyPageTitle',
- default: s__('PackageRegistry|There are no packages yet'),
- },
- noResultsText: {
- from: 'noResultsText',
- default: s__(
- 'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.',
- ),
- },
- },
- computed: {
- ...mapState({
- emptyListIllustration: (state) => state.config.emptyListIllustration,
- emptyListHelpUrl: (state) => state.config.emptyListHelpUrl,
- filter: (state) => state.filter,
- selectedType: (state) => state.selectedType,
- packageHelpUrl: (state) => state.config.packageHelpUrl,
- packagesCount: (state) => state.pagination?.total,
- }),
- emptySearch() {
- return (
- this.filter.filter((f) => f.type !== FILTERED_SEARCH_TERM || f.value?.data).length === 0
- );
- },
-
- emptyStateTitle() {
- return this.emptySearch
- ? this.emptyPageTitle
- : s__('PackageRegistry|Sorry, your filter produced no results');
- },
- },
- mounted() {
- const queryParams = getQueryParams(window.document.location.search);
- const { sorting, filters } = extractFilterAndSorting(queryParams);
- this.setSorting(sorting);
- this.setFilter(filters);
- this.requestPackagesList();
- this.checkDeleteAlert();
- },
- methods: {
- ...mapActions([
- 'requestPackagesList',
- 'requestDeletePackage',
- 'setSelectedType',
- 'setSorting',
- 'setFilter',
- ]),
- onPageChanged(page) {
- return this.requestPackagesList({ page });
- },
- onPackageDeleteRequest(item) {
- return this.requestDeletePackage(item);
- },
- checkDeleteAlert() {
- const urlParams = new URLSearchParams(window.location.search);
- const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
- if (showAlert) {
- // to be refactored to use gl-alert
- createFlash({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, type: 'notice' });
- const cleanUrl = window.location.href.split('?')[0];
- historyReplaceState(cleanUrl);
- }
- },
- },
- i18n: {
- widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'),
- },
-};
-</script>
-
-<template>
- <div>
- <component :is="titleComponent" :help-url="packageHelpUrl" :count="packagesCount" />
- <component :is="searchComponent" @update="requestPackagesList" />
-
- <package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest">
- <template #empty-state>
- <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration">
- <template #description>
- <gl-sprintf v-if="!emptySearch" :message="$options.i18n.widenFilters" />
- <gl-sprintf v-else :message="noResultsText">
- <template #noPackagesLink="{ content }">
- <gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link>
- </template>
- </gl-sprintf>
- </template>
- </gl-empty-state>
- </template>
- </package-list>
- </div>
-</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/publish_method.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/publish_method.vue
new file mode 100644
index 00000000000..8ecf433f3ab
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/publish_method.vue
@@ -0,0 +1,61 @@
+<script>
+import { GlIcon, GlLink } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+
+export default {
+ name: 'PublishMethod',
+ components: {
+ ClipboardButton,
+ GlIcon,
+ GlLink,
+ },
+ props: {
+ pipeline: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ hasPipeline() {
+ return Boolean(this.pipeline);
+ },
+ packageShaShort() {
+ return this.pipeline?.sha?.substring(0, 8);
+ },
+ },
+ i18n: {
+ COPY_COMMIT_SHA: __('Copy commit SHA'),
+ MANUALLY_PUBLISHED: s__('PackageRegistry|Manually Published'),
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center">
+ <template v-if="hasPipeline">
+ <gl-icon name="git-merge" class="gl-mr-2" />
+ <span data-testid="pipeline-ref" class="gl-mr-2">{{ pipeline.ref }}</span>
+
+ <gl-icon name="commit" class="gl-mr-2" />
+ <gl-link data-testid="pipeline-sha" :href="pipeline.commitPath" class="gl-mr-2">{{
+ packageShaShort
+ }}</gl-link>
+
+ <clipboard-button
+ :text="pipeline.sha"
+ :title="$options.i18n.COPY_COMMIT_SHA"
+ category="tertiary"
+ size="small"
+ />
+ </template>
+
+ <template v-else>
+ <gl-icon name="upload" class="gl-mr-2" />
+ <span data-testid="manually-published">
+ {{ $options.i18n.MANUALLY_PUBLISHED }}
+ </span>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index f023b4481a0..6a88880fa90 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -1,5 +1,4 @@
-/* eslint-disable @gitlab/require-string-literal-i18n-helpers */
-import { __, s__ } from '~/locale';
+import { s__ } from '~/locale';
export const PACKAGE_TYPE_CONAN = 'CONAN';
export const PACKAGE_TYPE_MAVEN = 'MAVEN';
@@ -71,7 +70,7 @@ export const DELETE_PACKAGE_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while deleting the package.',
);
export const DELETE_PACKAGE_FILE_ERROR_MESSAGE = s__(
- __('PackageRegistry|Something went wrong while deleting the package file.'),
+ 'PackageRegistry|Something went wrong while deleting the package file.',
);
export const DELETE_PACKAGE_FILE_SUCCESS_MESSAGE = s__(
'PackageRegistry|Package file deleted successfully',
@@ -87,3 +86,10 @@ export const PACKAGE_PROCESSING_STATUS = 'PROCESSING';
export const NPM_PACKAGE_MANAGER = 'npm';
export const YARN_PACKAGE_MANAGER = 'yarn';
+
+export const PROJECT_PACKAGE_ENDPOINT_TYPE = 'project';
+export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance';
+
+export const PROJECT_RESOURCE_TYPE = 'project';
+export const GROUP_RESOURCE_TYPE = 'group';
+export const LIST_QUERY_DEBOUNCE_TIME = 50;
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
new file mode 100644
index 00000000000..aaf0eb54aff
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
@@ -0,0 +1,27 @@
+fragment PackageData on Package {
+ id
+ name
+ version
+ packageType
+ createdAt
+ status
+ tags {
+ nodes {
+ name
+ }
+ }
+ pipelines {
+ nodes {
+ sha
+ ref
+ commitPath
+ user {
+ name
+ }
+ }
+ }
+ project {
+ fullPath
+ webUrl
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
new file mode 100644
index 00000000000..74e6de87866
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
@@ -0,0 +1,27 @@
+#import "~/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql"
+
+query getPackages(
+ $fullPath: ID!
+ $isGroupPage: Boolean!
+ $sort: PackageSort
+ $groupSort: PackageGroupSort
+ $packageName: String
+ $packageType: PackageTypeEnum
+) {
+ project(fullPath: $fullPath) @skip(if: $isGroupPage) {
+ packages(sort: $sort, packageName: $packageName, packageType: $packageType) {
+ count
+ nodes {
+ ...PackageData
+ }
+ }
+ }
+ group(fullPath: $fullPath) @include(if: $isGroupPage) {
+ packages(sort: $groupSort, packageName: $packageName, packageType: $packageType) {
+ count
+ nodes {
+ ...PackageData
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.js b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.js
index 1e01b75aabc..d797a0a5327 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.js
@@ -1,14 +1,22 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
-import PackagesListApp from '../components/list/packages_list_app.vue';
+import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
+import PackagesListApp from '../components/list/app.vue';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-vue-packages-list');
+ const isGroupPage = el.dataset.pageType === 'groups';
+
return new Vue({
el,
+ apolloProvider,
+ provide: {
+ ...el.dataset,
+ isGroupPage,
+ },
render(createElement) {
return createElement(PackagesListApp);
},
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 5cd8261ac23..9b5a0d221b8 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
@@ -19,6 +19,7 @@ export default () => {
apolloProvider,
provide: {
defaultExpanded: parseBoolean(el.dataset.defaultExpanded),
+ dependencyProxyAvailable: parseBoolean(el.dataset.dependencyProxyAvailable),
groupPath: el.dataset.groupPath,
},
render(createElement) {
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
new file mode 100644
index 00000000000..2dbe36def0e
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
@@ -0,0 +1,110 @@
+<script>
+import { GlToggle, GlSprintf, GlLink } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
+import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
+import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
+import { updateGroupDependencyProxySettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+
+import {
+ DEPENDENCY_PROXY_HEADER,
+ DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
+ DEPENDENCY_PROXY_DOCS_PATH,
+} from '~/packages_and_registries/settings/group/constants';
+
+export default {
+ name: 'DependencyProxySettings',
+ components: {
+ GlToggle,
+ GlSprintf,
+ GlLink,
+ SettingsBlock,
+ },
+ i18n: {
+ DEPENDENCY_PROXY_HEADER,
+ DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
+ label: s__('DependencyProxy|Enable Proxy'),
+ },
+ links: {
+ DEPENDENCY_PROXY_DOCS_PATH,
+ },
+ inject: ['defaultExpanded', 'groupPath'],
+ props: {
+ dependencyProxySettings: {
+ type: Object,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ enabled: {
+ get() {
+ return this.dependencyProxySettings.enabled;
+ },
+ set(enabled) {
+ this.updateSettings({ enabled });
+ },
+ },
+ },
+ methods: {
+ async updateSettings(payload) {
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: updateDependencyProxySettings,
+ variables: {
+ input: {
+ groupPath: this.groupPath,
+ ...payload,
+ },
+ },
+ update: updateGroupPackageSettings(this.groupPath),
+ optimisticResponse: updateGroupDependencyProxySettingsOptimisticResponse({
+ ...this.dependencyProxySettings,
+ ...payload,
+ }),
+ });
+
+ if (data.updateDependencyProxySettings?.errors?.length > 0) {
+ throw new Error();
+ } else {
+ this.$emit('success');
+ }
+ } catch {
+ this.$emit('error');
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <settings-block
+ :default-expanded="defaultExpanded"
+ 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 :href="$options.links.DEPENDENCY_PROXY_DOCS_PATH">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #default>
+ <div>
+ <gl-toggle
+ v-model="enabled"
+ :disabled="isLoading"
+ :label="$options.i18n.label"
+ data-qa-selector="dependency_proxy_setting_toggle"
+ />
+ </div>
+ </template>
+ </settings-block>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/duplicates_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/duplicates_settings.vue
index d66a30e7e81..b0088838acc 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/duplicates_settings.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/duplicates_settings.vue
@@ -86,6 +86,7 @@ export default {
:label="$options.i18n.DUPLICATES_TOGGLE_LABEL"
label-position="hidden"
:value="duplicatesAllowed"
+ :disabled="loading"
@change="update(modelNames.allowed, $event)"
/>
<div class="gl-ml-5">
@@ -108,6 +109,7 @@ export default {
>
<gl-form-input
id="maven-duplicated-settings-regex-input"
+ :disabled="loading"
:value="duplicateExceptionRegex"
@change="update(modelNames.exception, $event)"
/>
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 ec3be43196c..b45cedcdd66 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
@@ -1,108 +1,66 @@
<script>
-import { GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
-import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
-import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
-import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
-import {
- PACKAGE_SETTINGS_HEADER,
- PACKAGE_SETTINGS_DESCRIPTION,
- PACKAGES_DOCS_PATH,
- ERROR_UPDATING_SETTINGS,
- SUCCESS_UPDATING_SETTINGS,
-} from '~/packages_and_registries/settings/group/constants';
-import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql';
+import { GlAlert } from '@gitlab/ui';
+import { n__ } from '~/locale';
+import PackagesSettings from '~/packages_and_registries/settings/group/components/packages_settings.vue';
+import DependencyProxySettings from '~/packages_and_registries/settings/group/components/dependency_proxy_settings.vue';
+
import getGroupPackagesSettingsQuery from '~/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql';
-import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
-import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
-import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
export default {
name: 'GroupSettingsApp',
- i18n: {
- PACKAGE_SETTINGS_HEADER,
- PACKAGE_SETTINGS_DESCRIPTION,
- },
- links: {
- PACKAGES_DOCS_PATH,
- },
components: {
GlAlert,
- GlSprintf,
- GlLink,
- SettingsBlock,
- MavenSettings,
- GenericSettings,
- DuplicatesSettings,
+ PackagesSettings,
+ DependencyProxySettings,
},
- inject: ['defaultExpanded', 'groupPath'],
+ inject: ['groupPath', 'dependencyProxyAvailable'],
apollo: {
- packageSettings: {
+ group: {
query: getGroupPackagesSettingsQuery,
variables() {
return {
fullPath: this.groupPath,
};
},
- update(data) {
- return data.group?.packageSettings;
- },
},
},
data() {
return {
- packageSettings: {},
- errors: {},
+ group: {},
alertMessage: null,
};
},
computed: {
+ packageSettings() {
+ return this.group?.packageSettings || {};
+ },
+ dependencyProxySettings() {
+ return this.group?.dependencyProxySetting || {};
+ },
isLoading() {
- return this.$apollo.queries.packageSettings.loading;
+ return this.$apollo.queries.group.loading;
},
},
methods: {
dismissAlert() {
this.alertMessage = null;
},
- updateSettings(payload) {
- this.errors = {};
- return this.$apollo
- .mutate({
- mutation: updateNamespacePackageSettings,
- variables: {
- input: {
- namespacePath: this.groupPath,
- ...payload,
- },
- },
- update: updateGroupPackageSettings(this.groupPath),
- optimisticResponse: updateGroupPackagesSettingsOptimisticResponse({
- ...this.packageSettings,
- ...payload,
- }),
- })
- .then(({ data }) => {
- if (data.updateNamespacePackageSettings?.errors?.length > 0) {
- this.alertMessage = ERROR_UPDATING_SETTINGS;
- } else {
- this.dismissAlert();
- this.$toast.show(SUCCESS_UPDATING_SETTINGS);
- }
- })
- .catch((e) => {
- if (e.graphQLErrors) {
- e.graphQLErrors.forEach((error) => {
- const [
- {
- path: [key],
- message,
- },
- ] = error.extensions.problems;
- this.errors = { ...this.errors, [key]: message };
- });
- }
- this.alertMessage = ERROR_UPDATING_SETTINGS;
- });
+ handleSuccess(amount = 1) {
+ const successMessage = n__(
+ 'Setting saved successfully',
+ 'Settings saved successfully',
+ amount,
+ );
+ this.$toast.show(successMessage);
+ this.dismissAlert();
+ },
+ handleError(amount = 1) {
+ const errorMessage = n__(
+ 'An error occurred while saving the setting',
+ 'An error occurred while saving the settings',
+ amount,
+ );
+ this.alertMessage = errorMessage;
},
},
};
@@ -114,50 +72,19 @@ export default {
{{ alertMessage }}
</gl-alert>
- <settings-block
- :default-expanded="defaultExpanded"
- data-qa-selector="package_registry_settings_content"
- >
- <template #title> {{ $options.i18n.PACKAGE_SETTINGS_HEADER }}</template>
- <template #description>
- <span data-testid="description">
- <gl-sprintf :message="$options.i18n.PACKAGE_SETTINGS_DESCRIPTION">
- <template #link="{ content }">
- <gl-link :href="$options.links.PACKAGES_DOCS_PATH" target="_blank">{{
- content
- }}</gl-link>
- </template>
- </gl-sprintf>
- </span>
- </template>
- <template #default>
- <maven-settings data-testid="maven-settings">
- <template #default="{ modelNames }">
- <duplicates-settings
- :duplicates-allowed="packageSettings.mavenDuplicatesAllowed"
- :duplicate-exception-regex="packageSettings.mavenDuplicateExceptionRegex"
- :duplicate-exception-regex-error="errors.mavenDuplicateExceptionRegex"
- :model-names="modelNames"
- :loading="isLoading"
- toggle-qa-selector="allow_duplicates_toggle"
- label-qa-selector="allow_duplicates_label"
- @update="updateSettings"
- />
- </template>
- </maven-settings>
- <generic-settings class="gl-mt-6" data-testid="generic-settings">
- <template #default="{ modelNames }">
- <duplicates-settings
- :duplicates-allowed="packageSettings.genericDuplicatesAllowed"
- :duplicate-exception-regex="packageSettings.genericDuplicateExceptionRegex"
- :duplicate-exception-regex-error="errors.genericDuplicateExceptionRegex"
- :model-names="modelNames"
- :loading="isLoading"
- @update="updateSettings"
- />
- </template>
- </generic-settings>
- </template>
- </settings-block>
+ <packages-settings
+ :package-settings="packageSettings"
+ :is-loading="isLoading"
+ @success="handleSuccess(2)"
+ @error="handleError(2)"
+ />
+
+ <dependency-proxy-settings
+ v-if="dependencyProxyAvailable"
+ :dependency-proxy-settings="dependencyProxySettings"
+ :is-loading="isLoading"
+ @success="handleSuccess"
+ @error="handleError"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue
new file mode 100644
index 00000000000..b7e88945dbd
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/packages_settings.vue
@@ -0,0 +1,139 @@
+<script>
+import { GlSprintf, GlLink } from '@gitlab/ui';
+import DuplicatesSettings from '~/packages_and_registries/settings/group/components/duplicates_settings.vue';
+import GenericSettings from '~/packages_and_registries/settings/group/components/generic_settings.vue';
+import MavenSettings from '~/packages_and_registries/settings/group/components/maven_settings.vue';
+import {
+ PACKAGE_SETTINGS_HEADER,
+ PACKAGE_SETTINGS_DESCRIPTION,
+ PACKAGES_DOCS_PATH,
+} from '~/packages_and_registries/settings/group/constants';
+import updateNamespacePackageSettings from '~/packages_and_registries/settings/group/graphql/mutations/update_group_packages_settings.mutation.graphql';
+import { updateGroupPackageSettings } from '~/packages_and_registries/settings/group/graphql/utils/cache_update';
+import { updateGroupPackagesSettingsOptimisticResponse } from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
+import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
+
+export default {
+ name: 'PackageSettings',
+ i18n: {
+ PACKAGE_SETTINGS_HEADER,
+ PACKAGE_SETTINGS_DESCRIPTION,
+ },
+ links: {
+ PACKAGES_DOCS_PATH,
+ },
+ components: {
+ GlSprintf,
+ GlLink,
+ SettingsBlock,
+ MavenSettings,
+ GenericSettings,
+ DuplicatesSettings,
+ },
+ inject: ['defaultExpanded', 'groupPath'],
+ props: {
+ packageSettings: {
+ type: Object,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ errors: {},
+ };
+ },
+ methods: {
+ async updateSettings(payload) {
+ this.errors = {};
+ try {
+ const { data } = await this.$apollo.mutate({
+ mutation: updateNamespacePackageSettings,
+ variables: {
+ input: {
+ namespacePath: this.groupPath,
+ ...payload,
+ },
+ },
+ update: updateGroupPackageSettings(this.groupPath),
+ optimisticResponse: updateGroupPackagesSettingsOptimisticResponse({
+ ...this.packageSettings,
+ ...payload,
+ }),
+ });
+
+ if (data.updateNamespacePackageSettings?.errors?.length > 0) {
+ throw new Error();
+ } else {
+ this.$emit('success');
+ }
+ } catch (e) {
+ if (e.graphQLErrors) {
+ e.graphQLErrors.forEach((error) => {
+ const [
+ {
+ path: [key],
+ message,
+ },
+ ] = error.extensions.problems;
+ this.errors = { ...this.errors, [key]: message };
+ });
+ }
+ this.$emit('error');
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <settings-block
+ :default-expanded="defaultExpanded"
+ data-qa-selector="package_registry_settings_content"
+ >
+ <template #title> {{ $options.i18n.PACKAGE_SETTINGS_HEADER }}</template>
+ <template #description>
+ <span data-testid="description">
+ <gl-sprintf :message="$options.i18n.PACKAGE_SETTINGS_DESCRIPTION">
+ <template #link="{ content }">
+ <gl-link :href="$options.links.PACKAGES_DOCS_PATH" target="_blank">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ <template #default>
+ <maven-settings data-testid="maven-settings">
+ <template #default="{ modelNames }">
+ <duplicates-settings
+ :duplicates-allowed="packageSettings.mavenDuplicatesAllowed"
+ :duplicate-exception-regex="packageSettings.mavenDuplicateExceptionRegex"
+ :duplicate-exception-regex-error="errors.mavenDuplicateExceptionRegex"
+ :model-names="modelNames"
+ :loading="isLoading"
+ toggle-qa-selector="allow_duplicates_toggle"
+ label-qa-selector="allow_duplicates_label"
+ @update="updateSettings"
+ />
+ </template>
+ </maven-settings>
+ <generic-settings class="gl-mt-6" data-testid="generic-settings">
+ <template #default="{ modelNames }">
+ <duplicates-settings
+ :duplicates-allowed="packageSettings.genericDuplicatesAllowed"
+ :duplicate-exception-regex="packageSettings.genericDuplicateExceptionRegex"
+ :duplicate-exception-regex-error="errors.genericDuplicateExceptionRegex"
+ :model-names="modelNames"
+ :loading="isLoading"
+ @update="updateSettings"
+ />
+ </template>
+ </generic-settings>
+ </template>
+ </settings-block>
+</template>
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 d29489a0b33..ee922457993 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/constants.js
@@ -18,9 +18,9 @@ export const DUPLICATES_SETTINGS_EXCEPTION_LEGEND = s__(
'PackageRegistry|Publish packages if their name or version matches this regex.',
);
-export const SUCCESS_UPDATING_SETTINGS = s__('PackageRegistry|Settings saved successfully');
-export const ERROR_UPDATING_SETTINGS = s__(
- 'PackageRegistry|An error occurred while saving the settings',
+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
@@ -28,3 +28,5 @@ export const ERROR_UPDATING_SETTINGS = s__(
export const PACKAGES_DOCS_PATH = helpPagePath('user/packages');
export const MAVEN_DUPLICATES_ALLOWED = 'mavenDuplicatesAllowed';
export const MAVEN_DUPLICATE_EXCEPTION_REGEX = 'mavenDuplicateExceptionRegex';
+
+export const DEPENDENCY_PROXY_DOCS_PATH = helpPagePath('user/packages/dependency_proxy/index');
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql
new file mode 100644
index 00000000000..d24a645fecb
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql
@@ -0,0 +1,8 @@
+mutation updateDependencyProxySettings($input: UpdateDependencyProxySettingsInput!) {
+ updateDependencyProxySettings(input: $input) {
+ dependencyProxySetting {
+ enabled
+ }
+ errors
+ }
+}
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql b/app/assets/javascripts/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql
index a1c01300893..d3edebfbe20 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql
@@ -1,5 +1,8 @@
query getGroupPackagesSettings($fullPath: ID!) {
group(fullPath: $fullPath) {
+ dependencyProxySetting {
+ enabled
+ }
packageSettings {
mavenDuplicatesAllowed
mavenDuplicateExceptionRegex
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/cache_update.js b/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/cache_update.js
index fb06f557d66..fe94203f51b 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/cache_update.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/cache_update.js
@@ -9,9 +9,16 @@ export const updateGroupPackageSettings = (fullPath) => (client, { data: updated
const sourceData = client.readQuery(queryAndParams);
const data = produce(sourceData, (draftState) => {
- draftState.group.packageSettings = {
- ...updatedData.updateNamespacePackageSettings.packageSettings,
- };
+ if (updatedData.updateNamespacePackageSettings) {
+ draftState.group.packageSettings = {
+ ...updatedData.updateNamespacePackageSettings.packageSettings,
+ };
+ }
+ if (updatedData.updateDependencyProxySettings) {
+ draftState.group.dependencyProxySetting = {
+ ...updatedData.updateDependencyProxySettings.dependencyProxySetting,
+ };
+ }
});
client.writeQuery({
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/optimistic_responses.js b/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/optimistic_responses.js
index f2c8de85bf8..a30d8ca0b81 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/optimistic_responses.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/optimistic_responses.js
@@ -9,3 +9,15 @@ export const updateGroupPackagesSettingsOptimisticResponse = (changes) => ({
},
},
});
+
+export const updateGroupDependencyProxySettingsOptimisticResponse = (changes) => ({
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Mutation',
+ updateDependencyProxySettings: {
+ __typename: 'UpdateDependencyProxySettingsPayload',
+ errors: [],
+ dependencyProxySetting: {
+ ...changes,
+ },
+ },
+});
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
index bf286c84d5f..7be3bba7cae 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/project/components/registry_settings_app.vue
@@ -95,7 +95,7 @@ export default {
<gl-sprintf
:message="
__(
- 'Save space and find images in the container Registry. remove unneeded tags and keep only the ones you want. %{linkStart}How does cleanup work?%{linkEnd}',
+ 'Save storage space by automatically deleting tags from the container registry and keeping the ones you want. %{linkStart}How does cleanup work?%{linkEnd}',
)
"
>
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/constants.js b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
index 165c4aae3cb..4d477fbd05d 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/constants.js
+++ b/app/assets/javascripts/packages_and_registries/settings/project/constants.js
@@ -73,6 +73,7 @@ export const OLDER_THAN_OPTIONS = [
{ key: 'SEVEN_DAYS', variable: 7, default: false },
{ key: 'FOURTEEN_DAYS', variable: 14, default: false },
{ key: 'THIRTY_DAYS', variable: 30, default: false },
+ { key: 'SIXTY_DAYS', variable: 60, default: false },
{ key: 'NINETY_DAYS', variable: 90, default: true },
];
diff --git a/app/assets/javascripts/packages_and_registries/shared/constants.js b/app/assets/javascripts/packages_and_registries/shared/constants.js
index 55b5816cc5a..7d2971bd8c7 100644
--- a/app/assets/javascripts/packages_and_registries/shared/constants.js
+++ b/app/assets/javascripts/packages_and_registries/shared/constants.js
@@ -1 +1,3 @@
export const FILTERED_SEARCH_TERM = 'filtered-search-term';
+export const FILTERED_SEARCH_TYPE = 'type';
+export const HISTORY_PIPELINES_LIMIT = 5;