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:
Diffstat (limited to 'app/assets/javascripts/packages_and_registries/package_registry/components/list')
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue57
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue47
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue129
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue132
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue26
5 files changed, 391 insertions, 0 deletions
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
new file mode 100644
index 00000000000..280d292ce0b
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
@@ -0,0 +1,57 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { s__ } from '~/locale';
+import { sortableFields } from '~/packages/list/utils';
+import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
+import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
+import UrlSync from '~/vue_shared/components/url_sync.vue';
+import PackageTypeToken from './tokens/package_type_token.vue';
+
+export default {
+ tokens: [
+ {
+ type: 'type',
+ icon: 'package',
+ title: s__('PackageRegistry|Type'),
+ unique: true,
+ token: PackageTypeToken,
+ operators: OPERATOR_IS_ONLY,
+ },
+ ],
+ components: { RegistrySearch, UrlSync },
+ computed: {
+ ...mapState({
+ isGroupPage: (state) => state.config.isGroupPage,
+ sorting: (state) => state.sorting,
+ filter: (state) => state.filter,
+ }),
+ sortableFields() {
+ return sortableFields(this.isGroupPage);
+ },
+ },
+ methods: {
+ ...mapActions(['setSorting', 'setFilter']),
+ updateSorting(newValue) {
+ this.setSorting(newValue);
+ this.$emit('update');
+ },
+ },
+};
+</script>
+
+<template>
+ <url-sync>
+ <template #default="{ updateQuery }">
+ <registry-search
+ :filter="filter"
+ :sorting="sorting"
+ :tokens="$options.tokens"
+ :sortable-fields="sortableFields"
+ @sorting:changed="updateSorting"
+ @filter:changed="setFilter"
+ @filter:submit="$emit('update')"
+ @query:changed="updateQuery"
+ />
+ </template>
+ </url-sync>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
new file mode 100644
index 00000000000..6e00a48586e
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_title.vue
@@ -0,0 +1,47 @@
+<script>
+import { n__ } from '~/locale';
+import { LIST_INTRO_TEXT, LIST_TITLE_TEXT } from '~/packages/list/constants';
+import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
+import TitleArea from '~/vue_shared/components/registry/title_area.vue';
+
+export default {
+ name: 'PackageTitle',
+ components: {
+ TitleArea,
+ MetadataItem,
+ },
+ props: {
+ count: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ helpUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ showPackageCount() {
+ return Number.isInteger(this.count);
+ },
+ packageAmountText() {
+ return n__(`%d Package`, `%d Packages`, this.count);
+ },
+ infoMessages() {
+ return [{ text: LIST_INTRO_TEXT, link: this.helpUrl }];
+ },
+ },
+ i18n: {
+ LIST_TITLE_TEXT,
+ },
+};
+</script>
+
+<template>
+ <title-area :title="$options.i18n.LIST_TITLE_TEXT" :info-messages="infoMessages">
+ <template #metadata-amount>
+ <metadata-item v-if="showPackageCount" icon="package" :text="packageAmountText" />
+ </template>
+ </title-area>
+</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
new file mode 100644
index 00000000000..25bac687dbf
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list.vue
@@ -0,0 +1,129 @@
+<script>
+import { GlPagination, GlModal, GlSprintf } from '@gitlab/ui';
+import { mapState, mapGetters } from 'vuex';
+import { s__ } from '~/locale';
+import PackagesListRow from '~/packages/shared/components/package_list_row.vue';
+import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue';
+import { TrackingActions } from '~/packages/shared/constants';
+import { packageTypeToTrackCategory } from '~/packages/shared/utils';
+import Tracking from '~/tracking';
+
+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() {
+ const category = this.itemToBeDeleted
+ ? packageTypeToTrackCategory(this.itemToBeDeleted.package_type)
+ : undefined;
+ return {
+ category,
+ };
+ },
+ },
+ methods: {
+ setItemToBeDeleted(item) {
+ this.itemToBeDeleted = { ...item };
+ this.track(TrackingActions.REQUEST_DELETE_PACKAGE);
+ this.$refs.packageListDeleteModal.show();
+ },
+ deleteItemConfirmation() {
+ this.$emit('package:delete', this.itemToBeDeleted);
+ this.track(TrackingActions.DELETE_PACKAGE);
+ this.itemToBeDeleted = null;
+ },
+ deleteItemCanceled() {
+ this.track(TrackingActions.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"
+ 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/package_registry/components/list/packages_list_app.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue
new file mode 100644
index 00000000000..75fbdb80192
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/packages_list_app.vue
@@ -0,0 +1,132 @@
+<script>
+import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
+import { mapActions, mapState } from 'vuex';
+import createFlash from '~/flash';
+import { historyReplaceState } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
+import { DELETE_PACKAGE_SUCCESS_MESSAGE } from '~/packages/list/constants';
+import { SHOW_DELETE_SUCCESS_ALERT } from '~/packages/shared/constants';
+import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
+import { getQueryParams, extractFilterAndSorting } from '~/packages_and_registries/shared/utils';
+import PackageList from './packages_list.vue';
+
+export default {
+ components: {
+ GlEmptyState,
+ GlLink,
+ GlSprintf,
+ PackageList,
+ PackageTitle: () =>
+ import(/* webpackChunkName: 'package_registry_components' */ './package_title.vue'),
+ PackageSearch: () =>
+ import(/* webpackChunkName: 'package_registry_components' */ './package_search.vue'),
+ InfrastructureTitle: () =>
+ import(
+ /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_title.vue'
+ ),
+ InfrastructureSearch: () =>
+ import(
+ /* webpackChunkName: 'infrastructure_registry_components' */ '~/packages_and_registries/infrastructure_registry/components/infrastructure_search.vue'
+ ),
+ },
+ inject: {
+ titleComponent: {
+ from: 'titleComponent',
+ default: 'PackageTitle',
+ },
+ searchComponent: {
+ from: 'searchComponent',
+ default: 'PackageSearch',
+ },
+ emptyPageTitle: {
+ from: 'emptyPageTitle',
+ default: s__('PackageRegistry|There are no packages yet'),
+ },
+ noResultsText: {
+ from: 'noResultsText',
+ default: s__(
+ 'PackageRegistry|Learn how to %{noPackagesLinkStart}publish and share your packages%{noPackagesLinkEnd} with GitLab.',
+ ),
+ },
+ },
+ computed: {
+ ...mapState({
+ emptyListIllustration: (state) => state.config.emptyListIllustration,
+ emptyListHelpUrl: (state) => state.config.emptyListHelpUrl,
+ filter: (state) => state.filter,
+ selectedType: (state) => state.selectedType,
+ packageHelpUrl: (state) => state.config.packageHelpUrl,
+ packagesCount: (state) => state.pagination?.total,
+ }),
+ emptySearch() {
+ return (
+ this.filter.filter((f) => f.type !== FILTERED_SEARCH_TERM || f.value?.data).length === 0
+ );
+ },
+
+ emptyStateTitle() {
+ return this.emptySearch
+ ? this.emptyPageTitle
+ : s__('PackageRegistry|Sorry, your filter produced no results');
+ },
+ },
+ mounted() {
+ const queryParams = getQueryParams(window.document.location.search);
+ const { sorting, filters } = extractFilterAndSorting(queryParams);
+ this.setSorting(sorting);
+ this.setFilter(filters);
+ this.requestPackagesList();
+ this.checkDeleteAlert();
+ },
+ methods: {
+ ...mapActions([
+ 'requestPackagesList',
+ 'requestDeletePackage',
+ 'setSelectedType',
+ 'setSorting',
+ 'setFilter',
+ ]),
+ onPageChanged(page) {
+ return this.requestPackagesList({ page });
+ },
+ onPackageDeleteRequest(item) {
+ return this.requestDeletePackage(item);
+ },
+ checkDeleteAlert() {
+ const urlParams = new URLSearchParams(window.location.search);
+ const showAlert = urlParams.get(SHOW_DELETE_SUCCESS_ALERT);
+ if (showAlert) {
+ // to be refactored to use gl-alert
+ createFlash({ message: DELETE_PACKAGE_SUCCESS_MESSAGE, type: 'notice' });
+ const cleanUrl = window.location.href.split('?')[0];
+ historyReplaceState(cleanUrl);
+ }
+ },
+ },
+ i18n: {
+ widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <component :is="titleComponent" :help-url="packageHelpUrl" :count="packagesCount" />
+ <component :is="searchComponent" @update="requestPackagesList" />
+
+ <package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest">
+ <template #empty-state>
+ <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration">
+ <template #description>
+ <gl-sprintf v-if="!emptySearch" :message="$options.i18n.widenFilters" />
+ <gl-sprintf v-else :message="noResultsText">
+ <template #noPackagesLink="{ content }">
+ <gl-link :href="emptyListHelpUrl" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
+ </gl-empty-state>
+ </template>
+ </package-list>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue
new file mode 100644
index 00000000000..529a7893dfc
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/tokens/package_type_token.vue
@@ -0,0 +1,26 @@
+<script>
+import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui';
+import { PACKAGE_TYPES } from '~/packages/list/constants';
+
+export default {
+ components: {
+ GlFilteredSearchToken,
+ GlFilteredSearchSuggestion,
+ },
+ PACKAGE_TYPES,
+};
+</script>
+
+<template>
+ <gl-filtered-search-token v-bind="{ ...$attrs }" v-on="$listeners">
+ <template #suggestions>
+ <gl-filtered-search-suggestion
+ v-for="(type, index) in $options.PACKAGE_TYPES"
+ :key="index"
+ :value="type.type"
+ >
+ {{ type.title }}
+ </gl-filtered-search-suggestion>
+ </template>
+ </gl-filtered-search-token>
+</template>