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-12-20 16:37:47 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-12-20 16:37:47 +0300
commitaee0a117a889461ce8ced6fcf73207fe017f1d99 (patch)
tree891d9ef189227a8445d83f35c1b0fc99573f4380 /app/assets/javascripts/packages_and_registries
parent8d46af3258650d305f53b819eabf7ab18d22f59e (diff)
Add latest changes from gitlab-org/gitlab@14-6-stable-eev14.6.0-rc42
Diffstat (limited to 'app/assets/javascripts/packages_and_registries')
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue86
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue1
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue23
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/details/store/actions.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue (renamed from app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue)4
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue (renamed from app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue)0
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list.vue127
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue119
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js51
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js83
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/getters.js5
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/index.js20
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutation_types.js7
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutations.js33
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/state.js54
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list/utils.js25
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js7
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/constants.js1
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/infrastructure_icon_and_name.vue (renamed from app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue)0
-rw-r--r--app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/package_list_row.vue161
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue3
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js24
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql6
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql7
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql2
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/index.js30
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/index.vue5
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/list.js24
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue (renamed from app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue)28
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/router.js21
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/bundle.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue123
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/group_settings_app.vue4
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/components/settings_titles.vue7
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql11
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/queries/get_group_packages_settings.query.graphql5
-rw-r--r--app/assets/javascripts/packages_and_registries/settings/group/graphql/utils/cache_update.js5
-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/graphql/queries/get_expiration_policy.query.graphql1
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue17
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_path.vue86
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue110
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue60
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue64
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue124
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/constants.js36
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/utils.js10
59 files changed, 1522 insertions, 173 deletions
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
index f857c96c9d1..7a8a1bbcf09 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/delete_modal.vue
@@ -82,6 +82,7 @@ export default {
ref="deleteModal"
modal-id="delete-tag-modal"
ok-variant="danger"
+ size="sm"
:action-primary="{
text: __('Delete'),
attributes: [{ variant: 'danger' }, { disabled: disablePrimaryButton }],
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
index e9e36151fe6..d988ad8d8ca 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
@@ -46,7 +46,6 @@ export default {
data() {
return {
containerRepository: {},
- fetchTagsCount: false,
};
},
apollo: {
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
index 3e19a646f53..2d32295b537 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
@@ -1,7 +1,8 @@
<script>
-import { GlButton, GlKeysetPagination } from '@gitlab/ui';
import createFlash from '~/flash';
+import { n__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
+import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
import {
REMOVE_TAGS_BUTTON_TITLE,
TAGS_LIST_TITLE,
@@ -16,11 +17,10 @@ import TagsLoader from './tags_loader.vue';
export default {
name: 'TagsList',
components: {
- GlButton,
- GlKeysetPagination,
TagsListRow,
EmptyState,
TagsLoader,
+ RegistryList,
},
inject: ['config'],
props: {
@@ -61,11 +61,13 @@ export default {
},
data() {
return {
- selectedItems: {},
containerRepository: {},
};
},
computed: {
+ listTitle() {
+ return n__('%d tag', '%d tags', this.tags.length);
+ },
tags() {
return this.containerRepository?.tags?.nodes || [];
},
@@ -78,18 +80,9 @@ export default {
first: GRAPHQL_PAGE_SIZE,
};
},
- hasSelectedItems() {
- return this.tags.some((tag) => this.selectedItems[tag.name]);
- },
showMultiDeleteButton() {
return this.tags.some((tag) => tag.canDelete) && !this.isMobile;
},
- multiDeleteButtonIsDisabled() {
- return !this.hasSelectedItems || this.disabled;
- },
- showPagination() {
- return this.tagsPageInfo.hasPreviousPage || this.tagsPageInfo.hasNextPage;
- },
hasNoTags() {
return this.tags.length === 0;
},
@@ -98,19 +91,13 @@ export default {
},
},
methods: {
- updateSelectedItems(name) {
- this.$set(this.selectedItems, name, !this.selectedItems[name]);
- },
- mapTagsToBeDleeted(items) {
- return this.tags.filter((tag) => items[tag.name]);
- },
fetchNextPage() {
this.$apollo.queries.containerRepository.fetchMore({
variables: {
after: this.tagsPageInfo?.endCursor,
first: GRAPHQL_PAGE_SIZE,
},
- updateQuery(previousResult, { fetchMoreResult }) {
+ updateQuery(_, { fetchMoreResult }) {
return fetchMoreResult;
},
});
@@ -122,7 +109,7 @@ export default {
before: this.tagsPageInfo?.startCursor,
last: GRAPHQL_PAGE_SIZE,
},
- updateQuery(previousResult, { fetchMoreResult }) {
+ updateQuery(_, { fetchMoreResult }) {
return fetchMoreResult;
},
});
@@ -137,42 +124,27 @@ export default {
<template v-else>
<empty-state v-if="hasNoTags" :no-containers-image="config.noContainersImage" />
<template v-else>
- <div class="gl-display-flex gl-justify-content-space-between gl-mb-3">
- <h5 data-testid="list-title">
- {{ $options.i18n.TAGS_LIST_TITLE }}
- </h5>
-
- <gl-button
- v-if="showMultiDeleteButton"
- :disabled="multiDeleteButtonIsDisabled"
- category="secondary"
- variant="danger"
- @click="$emit('delete', mapTagsToBeDleeted(selectedItems))"
- >
- {{ $options.i18n.REMOVE_TAGS_BUTTON_TITLE }}
- </gl-button>
- </div>
- <tags-list-row
- v-for="(tag, index) in tags"
- :key="tag.path"
- :tag="tag"
- :first="index === 0"
- :selected="selectedItems[tag.name]"
- :is-mobile="isMobile"
- :disabled="disabled"
- @select="updateSelectedItems(tag.name)"
- @delete="$emit('delete', mapTagsToBeDleeted({ [tag.name]: true }))"
- />
- <div class="gl-display-flex gl-justify-content-center">
- <gl-keyset-pagination
- v-if="showPagination"
- :has-next-page="tagsPageInfo.hasNextPage"
- :has-previous-page="tagsPageInfo.hasPreviousPage"
- class="gl-mt-3"
- @prev="fetchPreviousPage"
- @next="fetchNextPage"
- />
- </div>
+ <registry-list
+ :title="listTitle"
+ :pagination="tagsPageInfo"
+ :items="tags"
+ id-property="name"
+ @prev-page="fetchPreviousPage"
+ @next-page="fetchNextPage"
+ @delete="$emit('delete', $event)"
+ >
+ <template #default="{ selectItem, isSelected, item, first }">
+ <tags-list-row
+ :tag="item"
+ :first="first"
+ :selected="isSelected(item)"
+ :is-mobile="isMobile"
+ :disabled="disabled"
+ @select="selectItem(item)"
+ @delete="$emit('delete', [item])"
+ />
+ </template>
+ </registry-list>
</template>
</template>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql
index 01cb7fa1cab..bc34e9b5ef2 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repositories_details.query.graphql
@@ -9,6 +9,7 @@ query getContainerRepositoriesDetails(
$sort: ContainerRepositorySort
) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) {
+ id
containerRepositories(
name: $name
after: $after
@@ -24,6 +25,7 @@ query getContainerRepositoriesDetails(
}
}
group(fullPath: $fullPath) @include(if: $isGroupPage) {
+ id
containerRepositories(
name: $name
after: $after
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
index b5a99fd9ac1..916740f41b8 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_details.query.graphql
@@ -11,6 +11,7 @@ query getContainerRepositoryDetails($id: ID!) {
expirationPolicyStartedAt
expirationPolicyCleanupStatus
project {
+ id
visibility
path
containerExpirationPolicy {
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
index a703c2dd0ac..502382010f9 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
@@ -9,6 +9,7 @@ query getContainerRepositoryTags(
) {
containerRepository(id: $id) {
id
+ tagsCount
tags(after: $after, before: $before, first: $first, last: $last) {
nodes {
digest
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
index feabc4f770b..bc6e3091f0e 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
@@ -25,9 +25,11 @@ import {
UNFINISHED_STATUS,
MISSING_OR_DELETED_IMAGE_BREADCRUMB,
ROOT_IMAGE_TEXT,
+ GRAPHQL_PAGE_SIZE,
} from '../constants/index';
import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql';
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql';
+import getContainerRepositoryTagsQuery from '../graphql/queries/get_container_repository_tags.query.graphql';
export default {
name: 'RegistryDetailsPage',
@@ -133,8 +135,8 @@ export default {
awaitRefetchQueries: true,
refetchQueries: [
{
- query: getContainerRepositoryDetailsQuery,
- variables: this.queryVariables,
+ query: getContainerRepositoryTagsQuery,
+ variables: { ...this.queryVariables, first: GRAPHQL_PAGE_SIZE },
},
],
});
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
index 73b957f42f2..3274de05803 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/list.vue
@@ -388,6 +388,7 @@ export default {
<template #default="{ doDelete }">
<gl-modal
ref="deleteModal"
+ size="sm"
modal-id="delete-image-modal"
:action-primary="{ text: __('Remove'), attributes: { variant: 'danger' } }"
@primary="doDelete"
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
index 71e8cf4f634..eb112238c11 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -1,11 +1,11 @@
<script>
import {
GlAlert,
+ GlEmptyState,
GlFormGroup,
GlFormInputGroup,
GlSkeletonLoader,
GlSprintf,
- GlEmptyState,
} from '@gitlab/ui';
import { s__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
@@ -36,15 +36,15 @@ export default {
proxyNotAvailableText: s__(
'DependencyProxy|Dependency Proxy feature is limited to public groups for now.',
),
- proxyDisabledText: s__(
- 'DependencyProxy|Dependency Proxy disabled. To enable it, contact the group owner.',
- ),
proxyImagePrefix: s__('DependencyProxy|Dependency Proxy image prefix'),
copyImagePrefixText: s__('DependencyProxy|Copy prefix'),
blobCountAndSize: s__('DependencyProxy|Contains %{count} blobs of images (%{size})'),
pageTitle: s__('DependencyProxy|Dependency Proxy'),
noManifestTitle: s__('DependencyProxy|There are no images in the cache'),
},
+ links: {
+ DEPENDENCY_PROXY_DOCS_PATH,
+ },
data() {
return {
group: {},
@@ -70,9 +70,7 @@ export default {
},
];
},
- dependencyProxyEnabled() {
- return this.group?.dependencyProxySetting?.enabled;
- },
+
queryVariables() {
return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
},
@@ -122,7 +120,7 @@ export default {
<gl-skeleton-loader v-else-if="$apollo.queries.group.loading" />
- <div v-else-if="dependencyProxyEnabled" data-testid="main-area">
+ <div v-else data-testid="main-area">
<gl-form-group :label="$options.i18n.proxyImagePrefix">
<gl-form-input-group
readonly
@@ -161,8 +159,5 @@ export default {
:title="$options.i18n.noManifestTitle"
/>
</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/queries/get_dependency_proxy_details.query.graphql b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
index 63d5469c955..9241dccb2d5 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql
@@ -8,6 +8,7 @@ query getDependencyProxyDetails(
$before: String
) {
group(fullPath: $fullPath) {
+ id
dependencyProxyBlobCount
dependencyProxyTotalSize
dependencyProxyImagePrefix
@@ -16,6 +17,7 @@ query getDependencyProxyDetails(
}
dependencyProxyManifests(after: $after, before: $before, first: $first, last: $last) {
nodes {
+ id
createdAt
imageName
}
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
index 6016757c1b9..f198d2e1bfa 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/details/components/app.vue
@@ -16,10 +16,13 @@ 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 PackageListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import {
+ TRACKING_ACTIONS,
+ SHOW_DELETE_SUCCESS_ALERT,
+} from '~/packages_and_registries/shared/constants';
+import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
import PackageFiles from './package_files.vue';
import PackageHistory from './package_history.vue';
@@ -44,7 +47,7 @@ export default {
GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
- trackingActions: { ...TrackingActions },
+ trackingActions: { ...TRACKING_ACTIONS },
data() {
return {
fileToDelete: null,
@@ -68,7 +71,7 @@ export default {
},
tracking() {
return {
- category: packageTypeToTrackCategory(this.packageEntity.package_type),
+ category: TRACK_CATEGORY,
};
},
hasVersions() {
@@ -86,7 +89,7 @@ export default {
}
},
async confirmPackageDeletion() {
- this.track(TrackingActions.DELETE_PACKAGE);
+ this.track(TRACKING_ACTIONS.DELETE_PACKAGE);
await this.deletePackage();
const returnTo =
!this.groupListUrl || document.referrer.includes(this.projectName)
@@ -96,12 +99,12 @@ export default {
window.location.replace(`${returnTo}?${modalQuery}`);
},
handleFileDelete(file) {
- this.track(TrackingActions.REQUEST_DELETE_PACKAGE_FILE);
+ this.track(TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE_FILE);
this.fileToDelete = { ...file };
this.$refs.deleteFileModal.show();
},
confirmFileDelete() {
- this.track(TrackingActions.DELETE_PACKAGE_FILE);
+ this.track(TRACKING_ACTIONS.DELETE_PACKAGE_FILE);
this.deletePackageFile(this.fileToDelete.id);
this.fileToDelete = null;
},
@@ -203,6 +206,7 @@ export default {
<gl-modal
ref="deleteModal"
+ size="sm"
modal-id="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
@@ -223,6 +227,7 @@ export default {
<gl-modal
ref="deleteFileModal"
+ size="sm"
modal-id="delete-file-modal"
:action-primary="$options.modal.fileDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
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
index a03fa8d9d63..26d4aa13715 100644
--- 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
@@ -4,7 +4,7 @@ import {
DELETE_PACKAGE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_ERROR_MESSAGE,
DELETE_PACKAGE_FILE_SUCCESS_MESSAGE,
-} from '~/packages/shared/constants';
+} from '~/packages_and_registries/shared/constants';
import { FETCH_PACKAGE_VERSIONS_ERROR } from '../constants';
import * as types from './mutation_types';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue
index 4928da862ea..c611f92036d 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue
@@ -1,7 +1,7 @@
<script>
import { mapState, mapActions } from 'vuex';
-import { LIST_KEY_PACKAGE_TYPE } from '~/packages/list/constants';
-import { sortableFields } from '~/packages/list/utils';
+import { LIST_KEY_PACKAGE_TYPE } from '~/packages_and_registries/infrastructure_registry/list/constants';
+import { sortableFields } from '~/packages_and_registries/infrastructure_registry/list/utils';
import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue
index 2a479c65d0c..2a479c65d0c 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list.vue
new file mode 100644
index 00000000000..a5f367bc1f6
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list.vue
@@ -0,0 +1,127 @@
+<script>
+import { GlPagination, GlModal, GlSprintf } from '@gitlab/ui';
+import { mapState, mapGetters } from 'vuex';
+import { s__ } from '~/locale';
+import Tracking from '~/tracking';
+import PackagesListRow from '~/packages_and_registries/infrastructure_registry/shared/package_list_row.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
+import { TRACKING_ACTIONS } from '~/packages_and_registries/shared/constants';
+import { TRACK_CATEGORY } from '~/packages_and_registries/infrastructure_registry/shared/constants';
+
+export default {
+ components: {
+ GlPagination,
+ GlModal,
+ GlSprintf,
+ PackagesListLoader,
+ PackagesListRow,
+ },
+ mixins: [Tracking.mixin()],
+ data() {
+ return {
+ itemToBeDeleted: null,
+ };
+ },
+ computed: {
+ ...mapState({
+ perPage: (state) => state.pagination.perPage,
+ totalItems: (state) => state.pagination.total,
+ page: (state) => state.pagination.page,
+ isGroupPage: (state) => state.config.isGroupPage,
+ isLoading: 'isLoading',
+ }),
+ ...mapGetters({ list: 'getList' }),
+ currentPage: {
+ get() {
+ return this.page;
+ },
+ set(value) {
+ this.$emit('page:changed', value);
+ },
+ },
+ isListEmpty() {
+ return !this.list || this.list.length === 0;
+ },
+ modalAction() {
+ return s__('PackageRegistry|Delete package');
+ },
+ deletePackageName() {
+ return this.itemToBeDeleted?.name ?? '';
+ },
+ tracking() {
+ return {
+ category: TRACK_CATEGORY,
+ };
+ },
+ },
+ methods: {
+ setItemToBeDeleted(item) {
+ this.itemToBeDeleted = { ...item };
+ this.track(TRACKING_ACTIONS.REQUEST_DELETE_PACKAGE);
+ this.$refs.packageListDeleteModal.show();
+ },
+ deleteItemConfirmation() {
+ this.$emit('package:delete', this.itemToBeDeleted);
+ this.track(TRACKING_ACTIONS.DELETE_PACKAGE);
+ this.itemToBeDeleted = null;
+ },
+ deleteItemCanceled() {
+ this.track(TRACKING_ACTIONS.CANCEL_DELETE_PACKAGE);
+ this.itemToBeDeleted = null;
+ },
+ },
+ i18n: {
+ deleteModalContent: s__(
+ 'PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?',
+ ),
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-flex-direction-column">
+ <slot v-if="isListEmpty && !isLoading" name="empty-state"></slot>
+
+ <div v-else-if="isLoading">
+ <packages-list-loader />
+ </div>
+
+ <template v-else>
+ <div data-qa-selector="packages-table">
+ <packages-list-row
+ v-for="packageEntity in list"
+ :key="packageEntity.id"
+ :package-entity="packageEntity"
+ :package-link="packageEntity._links.web_path"
+ :is-group="isGroupPage"
+ @packageToDelete="setItemToBeDeleted"
+ />
+ </div>
+
+ <gl-pagination
+ v-model="currentPage"
+ :per-page="perPage"
+ :total-items="totalItems"
+ align="center"
+ class="gl-w-full gl-mt-3"
+ />
+
+ <gl-modal
+ ref="packageListDeleteModal"
+ size="sm"
+ modal-id="confirm-delete-pacakge"
+ ok-variant="danger"
+ @ok="deleteItemConfirmation"
+ @cancel="deleteItemCanceled"
+ >
+ <template #modal-title>{{ modalAction }}</template>
+ <template #modal-ok>{{ modalAction }}</template>
+ <gl-sprintf :message="$options.i18n.deleteModalContent">
+ <template #name>
+ <strong>{{ deletePackageName }}</strong>
+ </template>
+ </gl-sprintf>
+ </gl-modal>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue
new file mode 100644
index 00000000000..462618a7f12
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue
@@ -0,0 +1,119 @@
+<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 {
+ SHOW_DELETE_SUCCESS_ALERT,
+ FILTERED_SEARCH_TERM,
+} from '~/packages_and_registries/shared/constants';
+
+import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
+import InfrastructureTitle from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_title.vue';
+import InfrastructureSearch from '~/packages_and_registries/infrastructure_registry/list/components/infrastructure_search.vue';
+import PackageList from '~/packages_and_registries/infrastructure_registry/list/components/packages_list.vue';
+import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages_and_registries/infrastructure_registry/list/constants';
+
+export default {
+ components: {
+ GlEmptyState,
+ GlLink,
+ GlSprintf,
+ PackageList,
+ InfrastructureTitle,
+ InfrastructureSearch,
+ },
+ inject: {
+ 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>
+ <infrastructure-title :help-url="packageHelpUrl" :count="packagesCount" />
+ <infrastructure-search @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/infrastructure_registry/list/constants.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
new file mode 100644
index 00000000000..7af3fc1c2db
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/constants.js
@@ -0,0 +1,51 @@
+import { __ } from '~/locale';
+
+export const FETCH_PACKAGES_LIST_ERROR_MESSAGE = __(
+ 'Something went wrong while fetching the packages list.',
+);
+export const DELETE_PACKAGE_SUCCESS_MESSAGE = __('Package deleted successfully');
+
+export const DEFAULT_PAGE = 1;
+export const DEFAULT_PAGE_SIZE = 20;
+
+export const GROUP_PAGE_TYPE = 'groups';
+
+export const LIST_KEY_NAME = 'name';
+export const LIST_KEY_PROJECT = 'project_path';
+export const LIST_KEY_VERSION = 'version';
+export const LIST_KEY_PACKAGE_TYPE = 'type';
+export const LIST_KEY_CREATED_AT = 'created_at';
+
+export const LIST_LABEL_NAME = __('Name');
+export const LIST_LABEL_PROJECT = __('Project');
+export const LIST_LABEL_VERSION = __('Version');
+export const LIST_LABEL_PACKAGE_TYPE = __('Type');
+export const LIST_LABEL_CREATED_AT = __('Published');
+
+// The following is not translated because it is used to build a JavaScript exception error message
+export const MISSING_DELETE_PATH_ERROR = 'Missing delete_api_path link';
+
+export const SORT_FIELDS = [
+ {
+ orderBy: LIST_KEY_NAME,
+ label: LIST_LABEL_NAME,
+ },
+ {
+ orderBy: LIST_KEY_PROJECT,
+ label: LIST_LABEL_PROJECT,
+ },
+ {
+ orderBy: LIST_KEY_VERSION,
+ label: LIST_LABEL_VERSION,
+ },
+ {
+ orderBy: LIST_KEY_PACKAGE_TYPE,
+ label: LIST_LABEL_PACKAGE_TYPE,
+ },
+ {
+ orderBy: LIST_KEY_CREATED_AT,
+ label: LIST_LABEL_CREATED_AT,
+ },
+];
+
+export const TERRAFORM_SEARCH_TYPE = Object.freeze({ value: { data: 'terraform_module' } });
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
new file mode 100644
index 00000000000..488860e5bc2
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/actions.js
@@ -0,0 +1,83 @@
+import Api from '~/api';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { DELETE_PACKAGE_ERROR_MESSAGE } from '~/packages_and_registries/shared/constants';
+import {
+ FETCH_PACKAGES_LIST_ERROR_MESSAGE,
+ DELETE_PACKAGE_SUCCESS_MESSAGE,
+ DEFAULT_PAGE,
+ DEFAULT_PAGE_SIZE,
+ MISSING_DELETE_PATH_ERROR,
+ TERRAFORM_SEARCH_TYPE,
+} from '../constants';
+import { getNewPaginationPage } from '../utils';
+import * as types from './mutation_types';
+
+export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
+export const setLoading = ({ commit }, data) => commit(types.SET_MAIN_LOADING, data);
+export const setSorting = ({ commit }, data) => commit(types.SET_SORTING, data);
+export const setFilter = ({ commit }, data) => commit(types.SET_FILTER, data);
+
+export const receivePackagesListSuccess = ({ commit }, { data, headers }) => {
+ commit(types.SET_PACKAGE_LIST_SUCCESS, data);
+ commit(types.SET_PAGINATION, headers);
+};
+
+export const requestPackagesList = ({ dispatch, state }, params = {}) => {
+ dispatch('setLoading', true);
+
+ const { page = DEFAULT_PAGE, per_page = DEFAULT_PAGE_SIZE } = params;
+ const { sort, orderBy } = state.sorting;
+ const type = state.config.forceTerraform
+ ? TERRAFORM_SEARCH_TYPE
+ : state.filter.find((f) => f.type === 'type');
+ const name = state.filter.find((f) => f.type === 'filtered-search-term');
+ const packageFilters = { package_type: type?.value?.data, package_name: name?.value?.data };
+
+ const apiMethod = state.config.isGroupPage ? 'groupPackages' : 'projectPackages';
+
+ return Api[apiMethod](state.config.resourceId, {
+ params: { page, per_page, sort, order_by: orderBy, ...packageFilters },
+ })
+ .then(({ data, headers }) => {
+ dispatch('receivePackagesListSuccess', { data, headers });
+ })
+ .catch(() => {
+ createFlash({
+ message: FETCH_PACKAGES_LIST_ERROR_MESSAGE,
+ });
+ })
+ .finally(() => {
+ dispatch('setLoading', false);
+ });
+};
+
+export const requestDeletePackage = ({ dispatch, state }, { _links }) => {
+ if (!_links || !_links.delete_api_path) {
+ createFlash({
+ message: DELETE_PACKAGE_ERROR_MESSAGE,
+ });
+ const error = new Error(MISSING_DELETE_PATH_ERROR);
+ return Promise.reject(error);
+ }
+
+ dispatch('setLoading', true);
+ return axios
+ .delete(_links.delete_api_path)
+ .then(() => {
+ const { page: currentPage, perPage, total } = state.pagination;
+ const page = getNewPaginationPage(currentPage, perPage, total - 1);
+
+ dispatch('requestPackagesList', { page });
+ createFlash({
+ message: DELETE_PACKAGE_SUCCESS_MESSAGE,
+ type: 'success',
+ });
+ })
+ .catch(() => {
+ dispatch('setLoading', false);
+ createFlash({
+ message: DELETE_PACKAGE_ERROR_MESSAGE,
+ });
+ });
+};
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/getters.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/getters.js
new file mode 100644
index 00000000000..5989303280e
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/getters.js
@@ -0,0 +1,5 @@
+import { beautifyPath } from '~/packages_and_registries/shared/utils';
+import { LIST_KEY_PROJECT } from '../constants';
+
+export default (state) =>
+ state.packages.map((p) => ({ ...p, projectPathName: beautifyPath(p[LIST_KEY_PROJECT]) }));
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/index.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/index.js
new file mode 100644
index 00000000000..1d6a4bf831d
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/index.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import getList from './getters';
+import mutations from './mutations';
+import state from './state';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ state,
+ getters: {
+ getList,
+ },
+ actions,
+ mutations,
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutation_types.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutation_types.js
new file mode 100644
index 00000000000..561ad97f7e3
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutation_types.js
@@ -0,0 +1,7 @@
+export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
+
+export const SET_PACKAGE_LIST_SUCCESS = 'SET_PACKAGE_LIST_SUCCESS';
+export const SET_PAGINATION = 'SET_PAGINATION';
+export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
+export const SET_SORTING = 'SET_SORTING';
+export const SET_FILTER = 'SET_FILTER';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutations.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutations.js
new file mode 100644
index 00000000000..98165e581b0
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/mutations.js
@@ -0,0 +1,33 @@
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { GROUP_PAGE_TYPE } from '../constants';
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_INITIAL_STATE](state, config) {
+ state.config = {
+ ...config,
+ isGroupPage: config.pageType === GROUP_PAGE_TYPE,
+ };
+ },
+
+ [types.SET_PACKAGE_LIST_SUCCESS](state, packages) {
+ state.packages = packages;
+ },
+
+ [types.SET_MAIN_LOADING](state, isLoading) {
+ state.isLoading = isLoading;
+ },
+
+ [types.SET_PAGINATION](state, headers) {
+ const normalizedHeaders = normalizeHeaders(headers);
+ state.pagination = parseIntPagination(normalizedHeaders);
+ },
+
+ [types.SET_SORTING](state, sorting) {
+ state.sorting = { ...state.sorting, ...sorting };
+ },
+
+ [types.SET_FILTER](state, filter) {
+ state.filter = filter;
+ },
+};
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/state.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/state.js
new file mode 100644
index 00000000000..60f02eddc9f
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/stores/state.js
@@ -0,0 +1,54 @@
+export default () => ({
+ /**
+ * Determine if the component is loading data from the API
+ */
+ isLoading: false,
+ /**
+ * configuration object, set once at store creation with the following structure
+ * {
+ * resourceId: String,
+ * pageType: String,
+ * emptyListIllustration: String,
+ * emptyListHelpUrl: String,
+ * comingSoon: { projectPath: String, suggestedContributions : String } | null;
+ * }
+ */
+ config: {},
+ /**
+ * Each object in `packages` has the following structure:
+ * {
+ * id: String
+ * name: String,
+ * version: String,
+ * package_type: String // endpoint to request the list
+ * }
+ */
+ packages: [],
+ /**
+ * Pagination object has the following structure:
+ * {
+ * perPage: Number,
+ * page: Number
+ * total: Number
+ * }
+ */
+ pagination: {},
+ /**
+ * Sorting object has the following structure:
+ * {
+ * sort: String,
+ * orderBy: String
+ * }
+ */
+ sorting: {
+ sort: 'desc',
+ orderBy: 'created_at',
+ },
+ /**
+ * The search query that is used to filter packages by name
+ */
+ filter: [],
+ /**
+ * The selected TAB of the package types tabs
+ */
+});
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/utils.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/utils.js
new file mode 100644
index 00000000000..537b30d2ca4
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list/utils.js
@@ -0,0 +1,25 @@
+import { LIST_KEY_PROJECT, SORT_FIELDS } from './constants';
+
+export const sortableFields = (isGroupPage) =>
+ SORT_FIELDS.filter((f) => f.orderBy !== LIST_KEY_PROJECT || isGroupPage);
+
+/**
+ * A small util function that works out if the delete action has deleted the
+ * last item on the current paginated page and if so, returns the previous
+ * page. This ensures the user won't end up on an empty paginated page.
+ *
+ * @param {number} currentPage The current page the user is on
+ * @param {number} perPage Number of items to display per page
+ * @param {number} totalPackages The total number of items
+ */
+export const getNewPaginationPage = (currentPage, perPage, totalItems) => {
+ if (totalItems <= perPage) {
+ return 1;
+ }
+
+ if (currentPage > 1 && (currentPage - 1) * perPage >= totalItems) {
+ return currentPage - 1;
+ }
+
+ return currentPage;
+};
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js
index 7e6e98f4fb5..1467218dd41 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/list_app_bundle.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import { s__ } from '~/locale';
-import PackagesListApp from '~/packages/list/components/packages_list_app.vue';
-import { createStore } from '~/packages/list/stores';
+import PackagesListApp from '~/packages_and_registries/infrastructure_registry/list/components/packages_list_app.vue';
+import { createStore } from '~/packages_and_registries/infrastructure_registry/list/stores';
import Translate from '~/vue_shared/translate';
Vue.use(Translate);
@@ -18,9 +18,6 @@ export default () => {
PackagesListApp,
},
provide: {
- titleComponent: 'InfrastructureTitle',
- searchComponent: 'InfrastructureSearch',
- iconComponent: 'InfrastructureIconAndName',
emptyPageTitle: s__('InfrastructureRegistry|You have no Terraform modules in your project'),
noResultsText: s__(
'InfrastructureRegistry|Terraform modules are the main way to package and reuse resource configurations with Terraform. Learn more about how to %{noPackagesLinkStart}create Terraform modules%{noPackagesLinkEnd} in GitLab.',
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/constants.js b/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/constants.js
new file mode 100644
index 00000000000..ab52ec01d40
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/constants.js
@@ -0,0 +1 @@
+export const TRACK_CATEGORY = 'UI::TerraformPackages';
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/infrastructure_icon_and_name.vue
index 3100a1a7296..3100a1a7296 100644
--- a/app/assets/javascripts/packages_and_registries/infrastructure_registry/components/infrastructure_icon_and_name.vue
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/infrastructure_icon_and_name.vue
diff --git a/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/package_list_row.vue b/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/package_list_row.vue
new file mode 100644
index 00000000000..3c6b8344c34
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/infrastructure_registry/shared/package_list_row.vue
@@ -0,0 +1,161 @@
+<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 timeagoMixin from '~/vue_shared/mixins/timeago';
+import {
+ PACKAGE_ERROR_STATUS,
+ PACKAGE_DEFAULT_STATUS,
+} from '~/packages_and_registries/shared/constants';
+import PackagePath from '~/packages_and_registries/shared/components/package_path.vue';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
+import PublishMethod from '~/packages_and_registries/shared/components/publish_method.vue';
+import InfrastructureIconAndName from '~/packages_and_registries/infrastructure_registry/shared/infrastructure_icon_and_name.vue';
+
+export default {
+ name: 'PackageListRow',
+ components: {
+ GlButton,
+ GlLink,
+ GlSprintf,
+ GlTruncate,
+ PackageTags,
+ PackagePath,
+ PublishMethod,
+ ListItem,
+ InfrastructureIconAndName,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ mixins: [timeagoMixin],
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ packageLink: {
+ type: String,
+ required: true,
+ },
+ disableDelete: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ isGroup: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ showPackageType: {
+ type: Boolean,
+ default: true,
+ required: false,
+ },
+ },
+ computed: {
+ hasPipeline() {
+ return Boolean(this.packageEntity.pipeline);
+ },
+ hasProjectLink() {
+ return Boolean(this.packageEntity.project_path);
+ },
+ showWarningIcon() {
+ return this.packageEntity.status === PACKAGE_ERROR_STATUS;
+ },
+ disabledRow() {
+ return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS;
+ },
+ disabledDeleteButton() {
+ return this.disabledRow || !this.packageEntity._links.delete_api_path;
+ },
+ },
+ 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="packageEntity.tags && packageEntity.tags.length"
+ class="gl-ml-3"
+ :tags="packageEntity.tags"
+ hide-label
+ :tag-display-limit="1"
+ />
+ </div>
+ </template>
+ <template #left-secondary>
+ <div class="gl-display-flex">
+ <span>{{ packageEntity.version }}</span>
+
+ <div v-if="hasPipeline" class="gl-display-none gl-sm-display-flex gl-ml-2">
+ <gl-sprintf :message="s__('PackageRegistry|published by %{author}')">
+ <template #author>{{ packageEntity.pipeline.user.name }}</template>
+ </gl-sprintf>
+ </div>
+
+ <infrastructure-icon-and-name v-if="showPackageType" />
+
+ <package-path
+ v-if="hasProjectLink"
+ :path="packageEntity.project_path"
+ :disabled="disabledRow"
+ />
+ </div>
+ </template>
+
+ <template #right-primary>
+ <publish-method :package-entity="packageEntity" :is-group="isGroup" />
+ </template>
+
+ <template #right-secondary>
+ <span>
+ <gl-sprintf :message="__('Created %{timestamp}')">
+ <template #timestamp>
+ <span v-gl-tooltip :title="tooltipTitle(packageEntity.created_at)">
+ {{ timeFormatted(packageEntity.created_at) }}
+ </span>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+
+ <template v-if="!disableDelete" #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')"
+ :disabled="disabledDeleteButton"
+ @click="$emit('packageToDelete', packageEntity)"
+ />
+ </template>
+ </list-item>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
index bcbeec72961..d49c1be5202 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
@@ -15,7 +15,7 @@ import { convertToGraphQLId } from '~/graphql_shared/utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { objectToQuery } from '~/lib/utils/url_utility';
import { s__, __ } from '~/locale';
-import { packageTypeToTrackCategory } from '~/packages/shared/utils';
+import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils';
import AdditionalMetadata from '~/packages_and_registries/package_registry/components/details/additional_metadata.vue';
import DependencyRow from '~/packages_and_registries/package_registry/components/details/dependency_row.vue';
import InstallationCommands from '~/packages_and_registries/package_registry/components/details/installation_commands.vue';
@@ -304,6 +304,7 @@ export default {
<template #default="{ deletePackage }">
<gl-modal
ref="deleteModal"
+ size="sm"
modal-id="delete-modal"
data-testid="delete-modal"
:action-primary="$options.modal.packageDeletePrimaryAction"
@@ -327,6 +328,7 @@ export default {
<gl-modal
ref="deleteFileModal"
+ size="sm"
modal-id="delete-file-modal"
:action-primary="$options.modal.fileDeletePrimaryAction"
:action-cancel="$options.modal.cancelAction"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
index 44d7807639d..118c509828c 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_title.vue
@@ -3,7 +3,7 @@ import { GlIcon, GlSprintf, GlBadge, GlResizeObserverDirective } from '@gitlab/u
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
import { PACKAGE_TYPE_NUGET } from '~/packages_and_registries/package_registry/constants';
import { getPackageTypeLabel } from '~/packages_and_registries/package_registry/utils';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
index d218a405af6..1afd1b69db0 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/version_row.vue
@@ -1,8 +1,8 @@
<script>
import { GlLink, GlSprintf, GlTruncate } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import PackageTags from '~/packages/shared/components/package_tags.vue';
-import PublishMethod from '~/packages/shared/components/publish_method.vue';
+import PackageTags from '~/packages_and_registries/shared/components/package_tags.vue';
+import PublishMethod from '~/packages_and_registries/shared/components/publish_method.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { PACKAGE_DEFAULT_STATUS } from '../../constants';
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
index 195ff7af583..6fd96c0654f 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
@@ -1,16 +1,16 @@
<script>
import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
-import { s__ } from '~/locale';
+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 { getPackageTypeLabel } from '~/packages_and_registries/package_registry/utils';
+import PackagePath from '~/packages_and_registries/shared/components/package_path.vue';
+import PackageTags from '~/packages_and_registries/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 PackageIconAndName from '~/packages_and_registries/shared/components/package_icon_and_name.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -40,7 +40,7 @@ export default {
},
computed: {
packageType() {
- return getPackageTypeLabel(this.packageEntity.packageType.toLowerCase());
+ return getPackageTypeLabel(this.packageEntity.packageType);
},
packageLink() {
const { project, id } = this.packageEntity;
@@ -64,6 +64,7 @@ export default {
},
i18n: {
erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'),
+ createdAt: __('Created %{timestamp}'),
},
};
</script>
@@ -127,8 +128,8 @@ export default {
</template>
<template #right-secondary>
- <span>
- <gl-sprintf :message="__('Created %{timestamp}')">
+ <span data-testid="created-date">
+ <gl-sprintf :message="$options.i18n.createdAt">
<template #timestamp>
<timeago-tooltip :time="packageEntity.createdAt" />
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
index 2a946544c2f..298ed9bccdb 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
@@ -2,7 +2,7 @@
import { GlModal, GlSprintf, GlKeysetPagination } from '@gitlab/ui';
import { s__ } from '~/locale';
import PackagesListRow from '~/packages_and_registries/package_registry/components/list/package_list_row.vue';
-import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
+import PackagesListLoader from '~/packages_and_registries/shared/components/packages_list_loader.vue';
import {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
@@ -124,6 +124,7 @@ export default {
<gl-modal
v-model="showDeleteModal"
modal-id="confirm-delete-pacakge"
+ size="sm"
ok-variant="danger"
@ok="deleteItemConfirmation"
@cancel="deleteItemCanceled"
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 9fd8880861c..ab6541e4264 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -1,4 +1,15 @@
import { s__, __ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+
+export {
+ DELETE_PACKAGE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
+ PULL_PACKAGE_TRACKING_ACTION,
+ DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+} from '~/packages_and_registries/shared/constants';
export const PACKAGE_TYPE_CONAN = 'CONAN';
export const PACKAGE_TYPE_MAVEN = 'MAVEN';
@@ -11,14 +22,6 @@ export const PACKAGE_TYPE_GENERIC = 'GENERIC';
export const PACKAGE_TYPE_DEBIAN = 'DEBIAN';
export const PACKAGE_TYPE_HELM = 'HELM';
-export const DELETE_PACKAGE_TRACKING_ACTION = 'delete_package';
-export const REQUEST_DELETE_PACKAGE_TRACKING_ACTION = 'request_delete_package';
-export const CANCEL_DELETE_PACKAGE_TRACKING_ACTION = 'cancel_delete_package';
-export const PULL_PACKAGE_TRACKING_ACTION = 'pull_package';
-export const DELETE_PACKAGE_FILE_TRACKING_ACTION = 'delete_package_file';
-export const REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'request_delete_package_file';
-export const CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'cancel_delete_package_file';
-
export const TRACKING_LABEL_CODE_INSTRUCTION = 'code_instruction';
export const TRACKING_LABEL_CONAN_INSTALLATION = 'conan_installation';
export const TRACKING_LABEL_MAVEN_INSTALLATION = 'maven_installation';
@@ -134,3 +137,8 @@ export const PACKAGE_TYPES = [
s__('PackageRegistry|Debian'),
s__('PackageRegistry|Helm'),
];
+
+// links
+
+export const EMPTY_LIST_HELP_URL = helpPagePath('user/packages/package_registry/index');
+export const PACKAGE_HELP_URL = helpPagePath('user/packages/index');
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
index aaf0eb54aff..66315fda9e9 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql
@@ -7,20 +7,24 @@ fragment PackageData on Package {
status
tags {
nodes {
+ id
name
}
}
- pipelines {
+ pipelines(last: 1) {
nodes {
+ id
sha
ref
commitPath
user {
+ id
name
}
}
}
project {
+ id
fullPath
webUrl
}
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 14aa14e9822..08ea0938a59 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
@@ -8,6 +8,7 @@ query getPackageDetails($id: ID!) {
updatedAt
status
project {
+ id
path
}
tags(first: 10) {
@@ -25,9 +26,11 @@ query getPackageDetails($id: ID!) {
commitPath
path
user {
+ id
name
}
project {
+ id
name
webUrl
}
@@ -86,15 +89,18 @@ query getPackageDetails($id: ID!) {
}
}
... on PypiMetadata {
+ id
requiredPython
}
... on ConanMetadata {
+ id
packageChannel
packageUsername
recipe
recipePath
}
... on MavenMetadata {
+ id
appName
appGroup
appVersion
@@ -102,6 +108,7 @@ query getPackageDetails($id: ID!) {
}
... on NugetMetadata {
+ id
iconUrl
licenseUrl
projectUrl
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
index e3115365f8b..4b913590949 100644
--- 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
@@ -14,6 +14,7 @@ query getPackages(
$before: String
) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) {
+ id
packages(
sort: $sort
packageName: $packageName
@@ -33,6 +34,7 @@ query getPackages(
}
}
group(fullPath: $fullPath) @include(if: $isGroupPage) {
+ id
packages(
sort: $groupSort
packageName: $packageName
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/index.js b/app/assets/javascripts/packages_and_registries/package_registry/index.js
new file mode 100644
index 00000000000..7ec931ff9a0
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/index.js
@@ -0,0 +1,30 @@
+import Vue from 'vue';
+import Translate from '~/vue_shared/translate';
+import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
+import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue';
+import createRouter from './router';
+
+Vue.use(Translate);
+
+export default () => {
+ const el = document.getElementById('js-vue-packages-list');
+ const { endpoint, resourceId, fullPath, pageType, emptyListIllustration } = el.dataset;
+ const router = createRouter(endpoint);
+
+ const isGroupPage = pageType === 'groups';
+
+ return new Vue({
+ el,
+ router,
+ apolloProvider,
+ provide: {
+ resourceId,
+ fullPath,
+ emptyListIllustration,
+ isGroupPage,
+ },
+ render(createElement) {
+ return createElement(PackageRegistry);
+ },
+ });
+};
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/index.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/index.vue
new file mode 100644
index 00000000000..a14d0c32cbe
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/index.vue
@@ -0,0 +1,5 @@
+<template>
+ <div>
+ <router-view />
+ </div>
+</template>
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
deleted file mode 100644
index d797a0a5327..00000000000
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import Vue from 'vue';
-import Translate from '~/vue_shared/translate';
-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/package_registry/components/list/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
index 11eeaf933ff..38df701157a 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/app.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
@@ -3,19 +3,21 @@ import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import createFlash from '~/flash';
import { historyReplaceState } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
-import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
+import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages_and_registries/shared/constants';
import {
PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE,
GRAPHQL_PAGE_SIZE,
DELETE_PACKAGE_SUCCESS_MESSAGE,
+ EMPTY_LIST_HELP_URL,
+ PACKAGE_HELP_URL,
} from '~/packages_and_registries/package_registry/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import DeletePackage from '~/packages_and_registries/package_registry/components/functional/delete_package.vue';
-import PackageTitle from './package_title.vue';
-import PackageSearch from './package_search.vue';
-import PackageList from './packages_list.vue';
+import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.vue';
+import PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue';
+import PackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
export default {
components: {
@@ -27,13 +29,7 @@ export default {
PackageSearch,
DeletePackage,
},
- inject: [
- 'packageHelpUrl',
- 'emptyListIllustration',
- 'emptyListHelpUrl',
- 'isGroupPage',
- 'fullPath',
- ],
+ inject: ['emptyListIllustration', 'isGroupPage', 'fullPath'],
data() {
return {
packages: {},
@@ -156,12 +152,16 @@ export default {
'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.',
),
},
+ links: {
+ EMPTY_LIST_HELP_URL,
+ PACKAGE_HELP_URL,
+ },
};
</script>
<template>
<div>
- <package-title :help-url="packageHelpUrl" :count="packagesCount" />
+ <package-title :help-url="$options.links.PACKAGE_HELP_URL" :count="packagesCount" />
<package-search @update="handleSearchUpdate" />
<delete-package
@@ -185,7 +185,9 @@ export default {
<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>
+ <gl-link :href="$options.links.EMPTY_LIST_HELP_URL" target="_blank">{{
+ content
+ }}</gl-link>
</template>
</gl-sprintf>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/router.js b/app/assets/javascripts/packages_and_registries/package_registry/router.js
new file mode 100644
index 00000000000..ea5b740e879
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/router.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import List from '~/packages_and_registries/package_registry/pages/list.vue';
+
+Vue.use(VueRouter);
+
+export default function createRouter(base) {
+ const router = new VueRouter({
+ base,
+ mode: 'history',
+ routes: [
+ {
+ name: 'list',
+ path: '/',
+ component: List,
+ },
+ ],
+ });
+
+ return router;
+}
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 9b5a0d221b8..85a7aeb5561 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
+++ b/app/assets/javascripts/packages_and_registries/settings/group/bundle.js
@@ -18,9 +18,10 @@ export default () => {
el,
apolloProvider,
provide: {
+ groupPath: el.dataset.groupPath,
+ groupDependencyProxyPath: el.dataset.groupDependencyProxyPath,
defaultExpanded: parseBoolean(el.dataset.defaultExpanded),
dependencyProxyAvailable: parseBoolean(el.dataset.dependencyProxyAvailable),
- groupPath: el.dataset.groupPath,
},
render(createElement) {
return createElement(SettingsApp);
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
index 5815c6393a7..fd62fe144b2 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/dependency_proxy_settings.vue
@@ -2,9 +2,14 @@
import { GlToggle, GlSprintf, GlLink } from '@gitlab/ui';
import { s__ } from '~/locale';
import SettingsBlock from '~/vue_shared/components/settings/settings_block.vue';
+import SettingsTitles from '~/packages_and_registries/settings/group/components/settings_titles.vue';
import updateDependencyProxySettings from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_settings.mutation.graphql';
+import updateDependencyProxyImageTtlGroupPolicy from '~/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.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 {
+ updateGroupDependencyProxySettingsOptimisticResponse,
+ updateDependencyProxyImageTtlGroupPolicyOptimisticResponse,
+} from '~/packages_and_registries/settings/group/graphql/utils/optimistic_responses';
import {
DEPENDENCY_PROXY_HEADER,
@@ -19,21 +24,34 @@ export default {
GlSprintf,
GlLink,
SettingsBlock,
+ SettingsTitles,
},
i18n: {
DEPENDENCY_PROXY_HEADER,
DEPENDENCY_PROXY_SETTINGS_DESCRIPTION,
- label: s__('DependencyProxy|Enable Proxy'),
+ enabledProxyLabel: s__('DependencyProxy|Enable Dependency Proxy'),
+ enabledProxyHelpText: s__(
+ 'DependencyProxy|To see the image prefix and what is in the cache, visit the %{linkStart}Dependency Proxy%{linkEnd}',
+ ),
+ storageSettingsTitle: s__('DependencyProxy|Storage settings'),
+ ttlPolicyEnabledLabel: s__('DependencyProxy|Clear the Dependency Proxy cache automatically'),
+ ttlPolicyEnabledHelpText: s__(
+ 'DependencyProxy|When enabled, images older than 90 days will be removed from the cache.',
+ ),
},
links: {
DEPENDENCY_PROXY_DOCS_PATH,
},
- inject: ['defaultExpanded', 'groupPath'],
+ inject: ['defaultExpanded', 'groupPath', 'groupDependencyProxyPath'],
props: {
dependencyProxySettings: {
type: Object,
required: true,
},
+ dependencyProxyImageTtlPolicy: {
+ type: Object,
+ required: true,
+ },
isLoading: {
type: Boolean,
required: false,
@@ -49,26 +67,35 @@ export default {
this.updateSettings({ enabled });
},
},
+ ttlEnabled: {
+ get() {
+ return this.dependencyProxyImageTtlPolicy.enabled;
+ },
+ set(enabled) {
+ const payload = {
+ enabled,
+ ttl: 90, // hardocded TTL for the MVC version
+ };
+ this.updateDependencyProxyImageTtlGroupPolicy(payload);
+ },
+ },
+ helpText() {
+ return this.enabled ? this.$options.i18n.enabledProxyHelpText : '';
+ },
},
methods: {
- async updateSettings(payload) {
+ mutationVariables(payload) {
+ return {
+ input: {
+ groupPath: this.groupPath,
+ ...payload,
+ },
+ };
+ },
+ async executeMutation(config, resource) {
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) {
+ const { data } = await this.$apollo.mutate(config);
+ if (data[resource]?.errors.length > 0) {
throw new Error();
} else {
this.$emit('success');
@@ -77,6 +104,32 @@ export default {
this.$emit('error');
}
},
+ async updateSettings(payload) {
+ const apolloConfig = {
+ mutation: updateDependencyProxySettings,
+ variables: this.mutationVariables(payload),
+ update: updateGroupPackageSettings(this.groupPath),
+ optimisticResponse: updateGroupDependencyProxySettingsOptimisticResponse({
+ ...this.dependencyProxySettings,
+ ...payload,
+ }),
+ };
+
+ this.executeMutation(apolloConfig, 'updateDependencyProxySettings');
+ },
+ async updateDependencyProxyImageTtlGroupPolicy(payload) {
+ const apolloConfig = {
+ mutation: updateDependencyProxyImageTtlGroupPolicy,
+ variables: this.mutationVariables(payload),
+ update: updateGroupPackageSettings(this.groupPath),
+ optimisticResponse: updateDependencyProxyImageTtlGroupPolicyOptimisticResponse({
+ ...this.dependencyProxyImageTtlPolicy,
+ ...payload,
+ }),
+ };
+
+ this.executeMutation(apolloConfig, 'updateDependencyProxyImageTtlGroupPolicy');
+ },
},
};
</script>
@@ -91,7 +144,11 @@ export default {
<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>
+ <gl-link
+ data-testid="description-link"
+ :href="$options.links.DEPENDENCY_PROXY_DOCS_PATH"
+ >{{ content }}</gl-link
+ >
</template>
</gl-sprintf>
</span>
@@ -101,9 +158,31 @@ export default {
<gl-toggle
v-model="enabled"
:disabled="isLoading"
- :label="$options.i18n.label"
+ :label="$options.i18n.enabledProxyLabel"
+ :help="helpText"
data-qa-selector="dependency_proxy_setting_toggle"
data-testid="dependency-proxy-setting-toggle"
+ >
+ <template #help>
+ <span class="gl-overflow-break-word gl-max-w-100vw gl-display-inline-block">
+ <gl-sprintf :message="$options.i18n.enabledProxyHelpText">
+ <template #link="{ content }">
+ <gl-link data-testid="toggle-help-link" :href="groupDependencyProxyPath">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </template>
+ </gl-toggle>
+
+ <settings-titles :title="$options.i18n.storageSettingsTitle" class="gl-my-6" />
+ <gl-toggle
+ v-model="ttlEnabled"
+ :disabled="isLoading"
+ :label="$options.i18n.ttlPolicyEnabledLabel"
+ :help="$options.i18n.ttlPolicyEnabledHelpText"
+ data-testid="dependency-proxy-ttl-policies-toggle"
/>
</div>
</template>
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 b45cedcdd66..64c12b4be6a 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
@@ -37,6 +37,9 @@ export default {
dependencyProxySettings() {
return this.group?.dependencyProxySetting || {};
},
+ dependencyProxyImageTtlPolicy() {
+ return this.group?.dependencyProxyImageTtlPolicy || {};
+ },
isLoading() {
return this.$apollo.queries.group.loading;
},
@@ -82,6 +85,7 @@ export default {
<dependency-proxy-settings
v-if="dependencyProxyAvailable"
:dependency-proxy-settings="dependencyProxySettings"
+ :dependency-proxy-image-ttl-policy="dependencyProxyImageTtlPolicy"
:is-loading="isLoading"
@success="handleSuccess"
@error="handleError"
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/components/settings_titles.vue b/app/assets/javascripts/packages_and_registries/settings/group/components/settings_titles.vue
index 3f0ab7686e5..1e93875c1e3 100644
--- a/app/assets/javascripts/packages_and_registries/settings/group/components/settings_titles.vue
+++ b/app/assets/javascripts/packages_and_registries/settings/group/components/settings_titles.vue
@@ -8,7 +8,8 @@ export default {
},
subTitle: {
type: String,
- required: true,
+ required: false,
+ default: '',
},
},
};
@@ -16,10 +17,10 @@ export default {
<template>
<div>
- <h5 class="gl-border-b-solid gl-border-b-1 gl-border-gray-200">
+ <h5 class="gl-border-b-solid gl-border-b-1 gl-border-gray-200 gl-pb-3">
{{ title }}
</h5>
- <p>{{ subTitle }}</p>
+ <p v-if="subTitle">{{ subTitle }}</p>
<slot></slot>
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql b/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql
new file mode 100644
index 00000000000..81250f52dfb
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/settings/group/graphql/mutations/update_dependency_proxy_image_ttl_group_policy.mutation.graphql
@@ -0,0 +1,11 @@
+mutation updateDependencyProxyImageTtlGroupPolicy(
+ $input: UpdateDependencyProxyImageTtlGroupPolicyInput!
+) {
+ updateDependencyProxyImageTtlGroupPolicy(input: $input) {
+ dependencyProxyImageTtlPolicy {
+ enabled
+ ttl
+ }
+ 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 d3edebfbe20..404d9d26d49 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,8 +1,13 @@
query getGroupPackagesSettings($fullPath: ID!) {
group(fullPath: $fullPath) {
+ id
dependencyProxySetting {
enabled
}
+ dependencyProxyImageTtlPolicy {
+ ttl
+ 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 fe94203f51b..c7b0899fa4c 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
@@ -19,6 +19,11 @@ export const updateGroupPackageSettings = (fullPath) => (client, { data: updated
...updatedData.updateDependencyProxySettings.dependencyProxySetting,
};
}
+ if (updatedData.updateDependencyProxyImageTtlGroupPolicy) {
+ draftState.group.dependencyProxyImageTtlPolicy = {
+ ...updatedData.updateDependencyProxyImageTtlGroupPolicy.dependencyProxyImageTtlPolicy,
+ };
+ }
});
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 a30d8ca0b81..92f6e117911 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
@@ -21,3 +21,15 @@ export const updateGroupDependencyProxySettingsOptimisticResponse = (changes) =>
},
},
});
+
+export const updateDependencyProxyImageTtlGroupPolicyOptimisticResponse = (changes) => ({
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ __typename: 'Mutation',
+ updateDependencyProxyImageTtlGroupPolicy: {
+ __typename: 'UpdateDependencyProxyImageTtlGroupPolicyPayload',
+ errors: [],
+ dependencyProxyImageTtlPolicy: {
+ ...changes,
+ },
+ },
+});
diff --git a/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql b/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql
index c171be0ad07..6a862da92df 100644
--- a/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/settings/project/graphql/queries/get_expiration_policy.query.graphql
@@ -2,6 +2,7 @@
query getProjectExpirationPolicy($projectPath: ID!) {
project(fullPath: $projectPath) {
+ id
containerExpirationPolicy {
...ContainerExpirationPolicyFields
}
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue
new file mode 100644
index 00000000000..105f7bbe132
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/package_icon_and_name.vue
@@ -0,0 +1,17 @@
+<script>
+import { GlIcon } from '@gitlab/ui';
+
+export default {
+ name: 'PackageIconAndName',
+ components: {
+ GlIcon,
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center">
+ <gl-icon name="package" class="gl-ml-3 gl-mr-2" />
+ <span><slot></slot></span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_path.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_path.vue
new file mode 100644
index 00000000000..6fb001e5e92
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/package_path.vue
@@ -0,0 +1,86 @@
+<script>
+import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui';
+import { joinPaths } from '~/lib/utils/url_utility';
+
+export default {
+ name: 'PackagePath',
+ components: {
+ GlIcon,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ path: {
+ type: String,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ pathPieces() {
+ return this.path.split('/');
+ },
+ root() {
+ // we skip the first part of the path since is the 'base' group
+ return this.pathPieces[1];
+ },
+ rootLink() {
+ return joinPaths(this.pathPieces[0], this.root);
+ },
+ leaf() {
+ return this.pathPieces[this.pathPieces.length - 1];
+ },
+ deeplyNested() {
+ return this.pathPieces.length > 3;
+ },
+ hasGroup() {
+ return this.root !== this.leaf;
+ },
+ },
+};
+</script>
+
+<template>
+ <div data-qa-selector="package-path" class="gl-display-flex gl-align-items-center">
+ <gl-icon data-testid="base-icon" name="project" class="gl-mx-3 gl-min-w-0" />
+
+ <gl-link
+ data-testid="root-link"
+ class="gl-text-gray-500 gl-min-w-0"
+ :href="`/${rootLink}`"
+ :disabled="disabled"
+ >
+ {{ root }}
+ </gl-link>
+
+ <template v-if="hasGroup">
+ <gl-icon data-testid="root-chevron" name="chevron-right" class="gl-mx-2 gl-min-w-0" />
+
+ <template v-if="deeplyNested">
+ <span
+ v-gl-tooltip="{ title: path }"
+ data-testid="ellipsis-icon"
+ class="gl-inset-border-1-gray-200 gl-rounded-base gl-px-2 gl-min-w-0"
+ >
+ <gl-icon name="ellipsis_h" />
+ </span>
+ <gl-icon data-testid="ellipsis-chevron" name="chevron-right" class="gl-mx-2 gl-min-w-0" />
+ </template>
+
+ <gl-link
+ data-testid="leaf-link"
+ class="gl-text-gray-500 gl-min-w-0"
+ :href="`/${path}`"
+ :disabled="disabled"
+ >
+ {{ leaf }}
+ </gl-link>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue b/app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue
new file mode 100644
index 00000000000..5ec950e4d45
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/package_tags.vue
@@ -0,0 +1,110 @@
+<script>
+import { GlBadge, GlIcon, GlSprintf, GlTooltipDirective } from '@gitlab/ui';
+import { n__ } from '~/locale';
+
+export default {
+ name: 'PackageTags',
+ components: {
+ GlBadge,
+ GlIcon,
+ GlSprintf,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ tagDisplayLimit: {
+ type: Number,
+ required: false,
+ default: 2,
+ },
+ tags: {
+ type: Array,
+ required: true,
+ default: () => [],
+ },
+ hideLabel: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ tagCount() {
+ return this.tags.length;
+ },
+ tagsToRender() {
+ return this.tags.slice(0, this.tagDisplayLimit);
+ },
+ moreTagsDisplay() {
+ return Math.max(0, this.tags.length - this.tagDisplayLimit);
+ },
+ moreTagsTooltip() {
+ if (this.moreTagsDisplay) {
+ return this.tags
+ .slice(this.tagDisplayLimit)
+ .map((x) => x.name)
+ .join(', ');
+ }
+
+ return '';
+ },
+ tagsDisplay() {
+ return n__('%d tag', '%d tags', this.tagCount);
+ },
+ },
+ methods: {
+ tagBadgeClass(index) {
+ return {
+ 'gl-display-none': true,
+ 'gl-display-flex': this.tagCount === 1,
+ 'd-md-flex': this.tagCount > 1,
+ 'gl-mr-2': index !== this.tagsToRender.length - 1,
+ 'gl-ml-3': !this.hideLabel && index === 0,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center">
+ <div v-if="!hideLabel" data-testid="tagLabel" class="gl-display-flex gl-align-items-center">
+ <gl-icon name="labels" class="gl-text-gray-500 gl-mr-3" />
+ <span class="gl-font-weight-bold">{{ tagsDisplay }}</span>
+ </div>
+
+ <gl-badge
+ v-for="(tag, index) in tagsToRender"
+ :key="index"
+ data-testid="tagBadge"
+ :class="tagBadgeClass(index)"
+ variant="info"
+ size="sm"
+ >{{ tag.name }}</gl-badge
+ >
+
+ <gl-badge
+ v-if="moreTagsDisplay"
+ v-gl-tooltip
+ data-testid="moreBadge"
+ variant="muted"
+ :title="moreTagsTooltip"
+ size="sm"
+ class="gl-display-none gl-md-display-flex gl-ml-2"
+ ><gl-sprintf :message="__('+%{tags} more')">
+ <template #tags>
+ {{ moreTagsDisplay }}
+ </template>
+ </gl-sprintf></gl-badge
+ >
+
+ <gl-badge
+ v-if="moreTagsDisplay && hideLabel"
+ data-testid="moreBadge"
+ variant="muted"
+ class="gl-md-display-none gl-ml-2"
+ >{{ tagsDisplay }}</gl-badge
+ >
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue b/app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue
new file mode 100644
index 00000000000..cf555f46f8c
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/packages_list_loader.vue
@@ -0,0 +1,60 @@
+<script>
+import { GlSkeletonLoader } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlSkeletonLoader,
+ },
+ shapes: [
+ { type: 'rect', width: '220', height: '10', x: '0', y: '20' },
+ { type: 'rect', width: '60', height: '10', x: '305', y: '20' },
+ { type: 'rect', width: '60', height: '10', x: '535', y: '20' },
+ { type: 'rect', width: '100', height: '10', x: '760', y: '20' },
+ { type: 'rect', width: '30', height: '30', x: '970', y: '10', ref: 'button-loader' },
+ ],
+ rowsToRender: {
+ mobile: 5,
+ desktop: 20,
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-flex-direction-column gl-sm-display-none" data-testid="mobile-loader">
+ <gl-skeleton-loader
+ v-for="index in $options.rowsToRender.mobile"
+ :key="index"
+ :width="500"
+ :height="170"
+ preserve-aspect-ratio="xMinYMax meet"
+ >
+ <rect width="500" height="10" x="0" y="15" rx="4" />
+ <rect width="500" height="10" x="0" y="45" rx="4" />
+ <rect width="500" height="10" x="0" y="75" rx="4" />
+ <rect width="500" height="10" x="0" y="105" rx="4" />
+ <rect width="500" height="10" x="0" y="135" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ <div
+ class="gl-display-none gl-sm-display-flex gl-flex-direction-column"
+ data-testid="desktop-loader"
+ >
+ <gl-skeleton-loader
+ v-for="index in $options.rowsToRender.desktop"
+ :key="index"
+ :width="1000"
+ :height="54"
+ preserve-aspect-ratio="xMinYMax meet"
+ >
+ <component
+ :is="r.type"
+ v-for="(r, rIndex) in $options.shapes"
+ :key="rIndex"
+ rx="4"
+ v-bind="r"
+ />
+ </gl-skeleton-loader>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue b/app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue
new file mode 100644
index 00000000000..8a66a33f2ab
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/publish_method.vue
@@ -0,0 +1,64 @@
+<script>
+import { GlIcon, GlLink } from '@gitlab/ui';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
+import { getCommitLink } from '../utils';
+
+export default {
+ name: 'PublishMethod',
+ components: {
+ ClipboardButton,
+ GlIcon,
+ GlLink,
+ },
+ props: {
+ packageEntity: {
+ type: Object,
+ required: true,
+ },
+ isGroup: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ hasPipeline() {
+ return Boolean(this.packageEntity.pipeline);
+ },
+ packageShaShort() {
+ return this.packageEntity.pipeline?.sha.substring(0, 8);
+ },
+ linkToCommit() {
+ return getCommitLink(this.packageEntity, this.isGroup);
+ },
+ },
+};
+</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">{{ packageEntity.pipeline.ref }}</span>
+
+ <gl-icon name="commit" class="gl-mr-2" />
+ <gl-link data-testid="pipeline-sha" :href="linkToCommit" class="gl-mr-2">{{
+ packageShaShort
+ }}</gl-link>
+
+ <clipboard-button
+ :text="packageEntity.pipeline.sha"
+ :title="__('Copy commit SHA')"
+ category="tertiary"
+ size="small"
+ />
+ </template>
+
+ <template v-else>
+ <gl-icon name="upload" class="gl-mr-2" />
+ <span data-testid="manually-published">
+ {{ s__('PackageRegistry|Manually Published') }}
+ </span>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
new file mode 100644
index 00000000000..79381f82009
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_list.vue
@@ -0,0 +1,124 @@
+<script>
+import { GlButton, GlFormCheckbox, GlKeysetPagination } from '@gitlab/ui';
+import { filter } from 'lodash';
+import { __ } from '~/locale';
+
+export default {
+ name: 'RegistryList',
+ components: {
+ GlButton,
+ GlFormCheckbox,
+ GlKeysetPagination,
+ },
+ props: {
+ title: {
+ type: String,
+ required: true,
+ },
+ isLoading: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ hiddenDelete: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ pagination: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ items: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ idProperty: {
+ type: String,
+ required: false,
+ default: 'id',
+ },
+ },
+ data() {
+ return {
+ selectedReferences: {},
+ };
+ },
+ computed: {
+ showPagination() {
+ return this.pagination.hasPreviousPage || this.pagination.hasNextPage;
+ },
+ disableDeleteButton() {
+ return this.isLoading || filter(this.selectedReferences).length === 0;
+ },
+ selectedItems() {
+ return this.items.filter(this.isSelected);
+ },
+ selectAll: {
+ get() {
+ return this.items.every(this.isSelected);
+ },
+ set(value) {
+ this.items.forEach((item) => {
+ const id = item[this.idProperty];
+ this.$set(this.selectedReferences, id, value);
+ });
+ },
+ },
+ },
+ methods: {
+ selectItem(item) {
+ const id = item[this.idProperty];
+ this.$set(this.selectedReferences, id, !this.selectedReferences[id]);
+ },
+ isSelected(item) {
+ const id = item[this.idProperty];
+ return this.selectedReferences[id];
+ },
+ },
+ i18n: {
+ deleteSelected: __('Delete Selected'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-display-flex gl-justify-content-space-between gl-mb-3 gl-align-items-center">
+ <gl-form-checkbox v-if="!hiddenDelete" v-model="selectAll" class="gl-ml-2 gl-pt-2">
+ <span class="gl-font-weight-bold">{{ title }}</span>
+ </gl-form-checkbox>
+
+ <gl-button
+ v-if="!hiddenDelete"
+ :disabled="disableDeleteButton"
+ category="secondary"
+ variant="danger"
+ @click="$emit('delete', selectedItems)"
+ >
+ {{ $options.i18n.deleteSelected }}
+ </gl-button>
+ </div>
+
+ <div v-for="(item, index) in items" :key="index">
+ <slot
+ :select-item="selectItem"
+ :is-selected="isSelected"
+ :item="item"
+ :first="index === 0"
+ ></slot>
+ </div>
+
+ <div class="gl-display-flex gl-justify-content-center">
+ <gl-keyset-pagination
+ v-if="showPagination"
+ v-bind="pagination"
+ class="gl-mt-3"
+ @prev="$emit('prev-page')"
+ @next="$emit('next-page')"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/constants.js b/app/assets/javascripts/packages_and_registries/shared/constants.js
index 7d2971bd8c7..afc72a2c627 100644
--- a/app/assets/javascripts/packages_and_registries/shared/constants.js
+++ b/app/assets/javascripts/packages_and_registries/shared/constants.js
@@ -1,3 +1,39 @@
+import { s__ } from '~/locale';
+
export const FILTERED_SEARCH_TERM = 'filtered-search-term';
export const FILTERED_SEARCH_TYPE = 'type';
export const HISTORY_PIPELINES_LIMIT = 5;
+
+export const DELETE_PACKAGE_TRACKING_ACTION = 'delete_package';
+export const REQUEST_DELETE_PACKAGE_TRACKING_ACTION = 'request_delete_package';
+export const CANCEL_DELETE_PACKAGE_TRACKING_ACTION = 'cancel_delete_package';
+export const PULL_PACKAGE_TRACKING_ACTION = 'pull_package';
+export const DELETE_PACKAGE_FILE_TRACKING_ACTION = 'delete_package_file';
+export const REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'request_delete_package_file';
+export const CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION = 'cancel_delete_package_file';
+
+export const TRACKING_ACTIONS = {
+ DELETE_PACKAGE: DELETE_PACKAGE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE: REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE: CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
+ PULL_PACKAGE: PULL_PACKAGE_TRACKING_ACTION,
+ DELETE_PACKAGE_FILE: DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ REQUEST_DELETE_PACKAGE_FILE: REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+ CANCEL_DELETE_PACKAGE_FILE: CANCEL_DELETE_PACKAGE_FILE_TRACKING_ACTION,
+};
+
+export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert';
+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.',
+);
+export const DELETE_PACKAGE_FILE_SUCCESS_MESSAGE = s__(
+ 'PackageRegistry|Package file deleted successfully',
+);
+
+export const PACKAGE_ERROR_STATUS = 'error';
+export const PACKAGE_DEFAULT_STATUS = 'default';
+export const PACKAGE_HIDDEN_STATUS = 'hidden';
+export const PACKAGE_PROCESSING_STATUS = 'processing';
diff --git a/app/assets/javascripts/packages_and_registries/shared/utils.js b/app/assets/javascripts/packages_and_registries/shared/utils.js
index 93eb90535d1..cf18f655e79 100644
--- a/app/assets/javascripts/packages_and_registries/shared/utils.js
+++ b/app/assets/javascripts/packages_and_registries/shared/utils.js
@@ -28,3 +28,13 @@ export const extractFilterAndSorting = (queryObject) => {
}
return { filters, sorting };
};
+
+export const beautifyPath = (path) => (path ? path.split('/').join(' / ') : '');
+
+export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGroup = false) => {
+ if (isGroup) {
+ return `/${projectPath}/commit/${pipeline.sha}`;
+ }
+
+ return `../commit/${pipeline.sha}`;
+};