Commit a797d196 authored by Nicolò Maria Mezzopera's avatar Nicolò Maria Mezzopera Committed by Natalia Tepluhina

Refactor and connect package list for GraphQl implementation

parent 8cd2e168
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
* For a complete overview of the plan please check: https://gitlab.com/gitlab-org/gitlab/-/issues/330846 * For a complete overview of the plan please check: https://gitlab.com/gitlab-org/gitlab/-/issues/330846
* This work is behind feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/341136 * This work is behind feature flag: https://gitlab.com/gitlab-org/gitlab/-/issues/341136
*/ */
// import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui'; import { GlEmptyState, GlLink, GlSprintf } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { historyReplaceState } from '~/lib/utils/common_utils'; import { historyReplaceState } from '~/lib/utils/common_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -15,17 +15,18 @@ import { ...@@ -15,17 +15,18 @@ import {
PROJECT_RESOURCE_TYPE, PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE, GROUP_RESOURCE_TYPE,
LIST_QUERY_DEBOUNCE_TIME, LIST_QUERY_DEBOUNCE_TIME,
GRAPHQL_PAGE_SIZE,
} from '~/packages_and_registries/package_registry/constants'; } from '~/packages_and_registries/package_registry/constants';
import PackageTitle from './package_title.vue'; import PackageTitle from './package_title.vue';
import PackageSearch from './package_search.vue'; import PackageSearch from './package_search.vue';
// import PackageList from './packages_list.vue'; import PackageList from './packages_list.vue';
export default { export default {
components: { components: {
// GlEmptyState, GlEmptyState,
// GlLink, GlLink,
// GlSprintf, GlSprintf,
// PackageList, PackageList,
PackageTitle, PackageTitle,
PackageSearch, PackageSearch,
}, },
...@@ -64,17 +65,24 @@ export default { ...@@ -64,17 +65,24 @@ export default {
groupSort: this.isGroupPage ? this.sort : undefined, groupSort: this.isGroupPage ? this.sort : undefined,
packageName: this.filters?.packageName, packageName: this.filters?.packageName,
packageType: this.filters?.packageType, packageType: this.filters?.packageType,
first: GRAPHQL_PAGE_SIZE,
}; };
}, },
graphqlResource() { graphqlResource() {
return this.isGroupPage ? GROUP_RESOURCE_TYPE : PROJECT_RESOURCE_TYPE; return this.isGroupPage ? GROUP_RESOURCE_TYPE : PROJECT_RESOURCE_TYPE;
}, },
pageInfo() {
return this.packages?.pageInfo ?? {};
},
packagesCount() { packagesCount() {
return this.packages?.count; return this.packages?.count;
}, },
hasFilters() { hasFilters() {
return this.filters.packageName && this.filters.packageType; return this.filters.packageName && this.filters.packageType;
}, },
emptySearch() {
return !this.filters.packageName && !this.filters.packageType;
},
emptyStateTitle() { emptyStateTitle() {
return this.emptySearch return this.emptySearch
? this.$options.i18n.emptyPageTitle ? this.$options.i18n.emptyPageTitle
...@@ -99,6 +107,35 @@ export default { ...@@ -99,6 +107,35 @@ export default {
this.sort = sort; this.sort = sort;
this.filters = { ...filters }; this.filters = { ...filters };
}, },
updateQuery(_, { fetchMoreResult }) {
return fetchMoreResult;
},
fetchNextPage() {
const variables = {
...this.queryVariables,
first: GRAPHQL_PAGE_SIZE,
last: null,
after: this.pageInfo?.endCursor,
};
this.$apollo.queries.packages.fetchMore({
variables,
updateQuery: this.updateQuery,
});
},
fetchPreviousPage() {
const variables = {
...this.queryVariables,
first: null,
last: GRAPHQL_PAGE_SIZE,
before: this.pageInfo?.startCursor,
};
this.$apollo.queries.packages.fetchMore({
variables,
updateQuery: this.updateQuery,
});
},
}, },
i18n: { i18n: {
widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'), widenFilters: s__('PackageRegistry|To widen your search, change or remove the filters above.'),
...@@ -116,7 +153,13 @@ export default { ...@@ -116,7 +153,13 @@ export default {
<package-title :help-url="packageHelpUrl" :count="packagesCount" /> <package-title :help-url="packageHelpUrl" :count="packagesCount" />
<package-search @update="handleSearchUpdate" /> <package-search @update="handleSearchUpdate" />
<!-- <package-list @page:changed="onPageChanged" @package:delete="onPackageDeleteRequest"> <package-list
:list="packages.nodes"
:is-loading="$apollo.queries.packages.loading"
:page-info="pageInfo"
@prev-page="fetchPreviousPage"
@next-page="fetchNextPage"
>
<template #empty-state> <template #empty-state>
<gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration"> <gl-empty-state :title="emptyStateTitle" :svg-path="emptyListIllustration">
<template #description> <template #description>
...@@ -129,6 +172,6 @@ export default { ...@@ -129,6 +172,6 @@ export default {
</template> </template>
</gl-empty-state> </gl-empty-state>
</template> </template>
</package-list> --> </package-list>
</div> </div>
</template> </template>
<script> <script>
import { GlPagination, GlModal, GlSprintf } from '@gitlab/ui'; import { GlModal, GlSprintf, GlKeysetPagination } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import PackagesListRow from '~/packages/shared/components/package_list_row.vue'; 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/shared/components/packages_list_loader.vue';
import { TrackingActions } from '~/packages/shared/constants'; import {
import { packageTypeToTrackCategory } from '~/packages/shared/utils'; DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
CANCEL_DELETE_PACKAGE_TRACKING_ACTION,
} from '~/packages_and_registries/package_registry/constants';
import { packageTypeToTrackCategory } from '~/packages_and_registries/package_registry/utils';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
export default { export default {
components: { components: {
GlPagination, GlKeysetPagination,
GlModal, GlModal,
GlSprintf, GlSprintf,
PackagesListLoader, PackagesListLoader,
PackagesListRow, PackagesListRow,
}, },
mixins: [Tracking.mixin()], mixins: [Tracking.mixin()],
props: {
list: {
type: Array,
required: false,
default: () => [],
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
pageInfo: {
type: Object,
required: true,
},
},
data() { data() {
return { return {
itemToBeDeleted: null, itemToBeDeleted: null,
}; };
}, },
computed: { 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() { isListEmpty() {
return !this.list || this.list.length === 0; return !this.list || this.list.length === 0;
}, },
modalAction() {
return s__('PackageRegistry|Delete package');
},
deletePackageName() { deletePackageName() {
return this.itemToBeDeleted?.name ?? ''; return this.itemToBeDeleted?.name ?? '';
}, },
tracking() { tracking() {
const category = this.itemToBeDeleted const category = this.itemToBeDeleted
? packageTypeToTrackCategory(this.itemToBeDeleted.package_type) ? packageTypeToTrackCategory(this.itemToBeDeleted.packageType)
: undefined; : undefined;
return { return {
category, category,
}; };
}, },
showPagination() {
return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
},
}, },
methods: { methods: {
setItemToBeDeleted(item) { setItemToBeDeleted(item) {
this.itemToBeDeleted = { ...item }; this.itemToBeDeleted = { ...item };
this.track(TrackingActions.REQUEST_DELETE_PACKAGE); this.track(REQUEST_DELETE_PACKAGE_TRACKING_ACTION);
this.$refs.packageListDeleteModal.show(); this.$refs.packageListDeleteModal.show();
}, },
deleteItemConfirmation() { deleteItemConfirmation() {
this.$emit('package:delete', this.itemToBeDeleted); this.$emit('package:delete', this.itemToBeDeleted);
this.track(TrackingActions.DELETE_PACKAGE); this.track(DELETE_PACKAGE_TRACKING_ACTION);
this.itemToBeDeleted = null; this.itemToBeDeleted = null;
}, },
deleteItemCanceled() { deleteItemCanceled() {
this.track(TrackingActions.CANCEL_DELETE_PACKAGE); this.track(CANCEL_DELETE_PACKAGE_TRACKING_ACTION);
this.itemToBeDeleted = null; this.itemToBeDeleted = null;
}, },
}, },
...@@ -77,6 +81,7 @@ export default { ...@@ -77,6 +81,7 @@ export default {
deleteModalContent: s__( deleteModalContent: s__(
'PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?', 'PackageRegistry|You are about to delete %{name}, this operation is irreversible, are you sure?',
), ),
modalAction: s__('PackageRegistry|Delete package'),
}, },
}; };
</script> </script>
...@@ -95,19 +100,19 @@ export default { ...@@ -95,19 +100,19 @@ export default {
v-for="packageEntity in list" v-for="packageEntity in list"
:key="packageEntity.id" :key="packageEntity.id"
:package-entity="packageEntity" :package-entity="packageEntity"
:package-link="packageEntity._links.web_path"
:is-group="isGroupPage"
@packageToDelete="setItemToBeDeleted" @packageToDelete="setItemToBeDeleted"
/> />
</div> </div>
<gl-pagination <div class="gl-display-flex gl-justify-content-center">
v-model="currentPage" <gl-keyset-pagination
:per-page="perPage" v-if="showPagination"
:total-items="totalItems" v-bind="pageInfo"
align="center" class="gl-mt-3"
class="gl-w-full gl-mt-3" @prev="$emit('prev-page')"
/> @next="$emit('next-page')"
/>
</div>
<gl-modal <gl-modal
ref="packageListDeleteModal" ref="packageListDeleteModal"
...@@ -116,8 +121,8 @@ export default { ...@@ -116,8 +121,8 @@ export default {
@ok="deleteItemConfirmation" @ok="deleteItemConfirmation"
@cancel="deleteItemCanceled" @cancel="deleteItemCanceled"
> >
<template #modal-title>{{ modalAction }}</template> <template #modal-title>{{ $options.i18n.modalAction }}</template>
<template #modal-ok>{{ modalAction }}</template> <template #modal-ok>{{ $options.i18n.modalAction }}</template>
<gl-sprintf :message="$options.i18n.deleteModalContent"> <gl-sprintf :message="$options.i18n.deleteModalContent">
<template #name> <template #name>
<strong>{{ deletePackageName }}</strong> <strong>{{ deletePackageName }}</strong>
......
...@@ -59,12 +59,6 @@ export const TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND = ...@@ -59,12 +59,6 @@ export const TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND =
export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND = export const TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND =
'copy_composer_package_include_command'; 'copy_composer_package_include_command';
export const TrackingCategories = {
[PACKAGE_TYPE_MAVEN]: 'MavenPackages',
[PACKAGE_TYPE_NPM]: 'NpmPackages',
[PACKAGE_TYPE_CONAN]: 'ConanPackages',
};
export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert'; export const SHOW_DELETE_SUCCESS_ALERT = 'showSuccessDeleteAlert';
export const DELETE_PACKAGE_ERROR_MESSAGE = s__( export const DELETE_PACKAGE_ERROR_MESSAGE = s__(
'PackageRegistry|Something went wrong while deleting the package.', 'PackageRegistry|Something went wrong while deleting the package.',
...@@ -93,3 +87,4 @@ export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance'; ...@@ -93,3 +87,4 @@ export const INSTANCE_PACKAGE_ENDPOINT_TYPE = 'instance';
export const PROJECT_RESOURCE_TYPE = 'project'; export const PROJECT_RESOURCE_TYPE = 'project';
export const GROUP_RESOURCE_TYPE = 'group'; export const GROUP_RESOURCE_TYPE = 'group';
export const LIST_QUERY_DEBOUNCE_TIME = 50; export const LIST_QUERY_DEBOUNCE_TIME = 50;
export const GRAPHQL_PAGE_SIZE = 20;
#import "~/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql" #import "~/packages_and_registries/package_registry/graphql/fragments/package_data.fragment.graphql"
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query getPackages( query getPackages(
$fullPath: ID! $fullPath: ID!
...@@ -7,21 +8,47 @@ query getPackages( ...@@ -7,21 +8,47 @@ query getPackages(
$groupSort: PackageGroupSort $groupSort: PackageGroupSort
$packageName: String $packageName: String
$packageType: PackageTypeEnum $packageType: PackageTypeEnum
$first: Int
$last: Int
$after: String
$before: String
) { ) {
project(fullPath: $fullPath) @skip(if: $isGroupPage) { project(fullPath: $fullPath) @skip(if: $isGroupPage) {
packages(sort: $sort, packageName: $packageName, packageType: $packageType) { packages(
sort: $sort
packageName: $packageName
packageType: $packageType
after: $after
before: $before
first: $first
last: $last
) {
count count
nodes { nodes {
...PackageData ...PackageData
} }
pageInfo {
...PageInfo
}
} }
} }
group(fullPath: $fullPath) @include(if: $isGroupPage) { group(fullPath: $fullPath) @include(if: $isGroupPage) {
packages(sort: $groupSort, packageName: $packageName, packageType: $packageType) { packages(
sort: $groupSort
packageName: $packageName
packageType: $packageType
after: $after
before: $before
first: $first
last: $last
) {
count count
nodes { nodes {
...PackageData ...PackageData
} }
pageInfo {
...PageInfo
}
} }
} }
} }
import { capitalize } from 'lodash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { import {
PACKAGE_TYPE_CONAN, PACKAGE_TYPE_CONAN,
...@@ -38,3 +39,5 @@ export const getPackageTypeLabel = (packageType) => { ...@@ -38,3 +39,5 @@ export const getPackageTypeLabel = (packageType) => {
return null; return null;
} }
}; };
export const packageTypeToTrackCategory = (type) => `UI::${capitalize(type)}Packages`;
...@@ -8,5 +8,62 @@ exports[`PackagesListApp renders 1`] = ` ...@@ -8,5 +8,62 @@ exports[`PackagesListApp renders 1`] = `
/> />
<package-search-stub /> <package-search-stub />
<div>
<section
class="row empty-state text-center"
>
<div
class="col-12"
>
<div
class="svg-250 svg-content"
>
<img
alt=""
class="gl-max-w-full"
role="img"
src="emptyListIllustration"
/>
</div>
</div>
<div
class="col-12"
>
<div
class="text-content gl-mx-auto gl-my-0 gl-p-5"
>
<h1
class="h4"
>
There are no packages yet
</h1>
<p>
Learn how to
<b-link-stub
class="gl-link"
event="click"
href="emptyListHelpUrl"
routertag="a"
target="_blank"
>
publish and share your packages
</b-link-stub>
with GitLab.
</p>
<div
class="gl-display-flex gl-flex-wrap gl-justify-content-center"
>
<!---->
<!---->
</div>
</div>
</div>
</section>
</div>
</div> </div>
`; `;
...@@ -2,22 +2,25 @@ import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui'; ...@@ -2,22 +2,25 @@ import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
import { createLocalVue } from '@vue/test-utils'; import { createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { nextTick } from 'vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import PackageListApp from '~/packages_and_registries/package_registry/components/list/app.vue'; import PackageListApp from '~/packages_and_registries/package_registry/components/list/app.vue';
import PackageTitle from '~/packages_and_registries/package_registry/components/list/package_title.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 PackageSearch from '~/packages_and_registries/package_registry/components/list/package_search.vue';
import OriginalPackageList from '~/packages_and_registries/package_registry/components/list/packages_list.vue';
import { import {
PROJECT_RESOURCE_TYPE, PROJECT_RESOURCE_TYPE,
GROUP_RESOURCE_TYPE, GROUP_RESOURCE_TYPE,
LIST_QUERY_DEBOUNCE_TIME, LIST_QUERY_DEBOUNCE_TIME,
GRAPHQL_PAGE_SIZE,
} from '~/packages_and_registries/package_registry/constants'; } from '~/packages_and_registries/package_registry/constants';
import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql'; import getPackagesQuery from '~/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql';
import { packagesListQuery } from '../../mock_data'; import { packagesListQuery, packageData, pagination } from '../../mock_data';
jest.mock('~/lib/utils/common_utils'); jest.mock('~/lib/utils/common_utils');
jest.mock('~/flash'); jest.mock('~/flash');
...@@ -39,11 +42,19 @@ describe('PackagesListApp', () => { ...@@ -39,11 +42,19 @@ describe('PackagesListApp', () => {
const PackageList = { const PackageList = {
name: 'package-list', name: 'package-list',
template: '<div><slot name="empty-state"></slot></div>', template: '<div><slot name="empty-state"></slot></div>',
props: OriginalPackageList.props,
}; };
const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' }; const GlLoadingIcon = { name: 'gl-loading-icon', template: '<div>loading</div>' };
const searchPayload = {
sort: 'VERSION_DESC',
filters: { packageName: 'foo', packageType: 'CONAN' },
};
const findPackageTitle = () => wrapper.findComponent(PackageTitle); const findPackageTitle = () => wrapper.findComponent(PackageTitle);
const findSearch = () => wrapper.findComponent(PackageSearch); const findSearch = () => wrapper.findComponent(PackageSearch);
const findListComponent = () => wrapper.findComponent(PackageList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const mountComponent = ({ const mountComponent = ({
resolver = jest.fn().mockResolvedValue(packagesListQuery()), resolver = jest.fn().mockResolvedValue(packagesListQuery()),
...@@ -105,25 +116,55 @@ describe('PackagesListApp', () => { ...@@ -105,25 +116,55 @@ describe('PackagesListApp', () => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery()); const resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver }); mountComponent({ resolver });
const payload = { findSearch().vm.$emit('update', searchPayload);
sort: 'VERSION_DESC',
filters: { packageName: 'foo', packageType: 'CONAN' },
};
findSearch().vm.$emit('update', payload);
await waitForDebouncedApollo(); await waitForDebouncedApollo();
jest.advanceTimersByTime(LIST_QUERY_DEBOUNCE_TIME); jest.advanceTimersByTime(LIST_QUERY_DEBOUNCE_TIME);
expect(resolver).toHaveBeenCalledWith( expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
groupSort: payload.sort, groupSort: searchPayload.sort,
...payload.filters, ...searchPayload.filters,
}), }),
); );
}); });
}); });
describe('list component', () => {
let resolver;
beforeEach(() => {
resolver = jest.fn().mockResolvedValue(packagesListQuery());
mountComponent({ resolver });
return waitForDebouncedApollo();
});
it('exists and has the right props', () => {
expect(findListComponent().props()).toMatchObject({
list: expect.arrayContaining([expect.objectContaining({ id: packageData().id })]),
isLoading: false,
pageInfo: expect.objectContaining({ endCursor: pagination().endCursor }),
});
});
it('when list emits next-page fetches the next set of records', () => {
findListComponent().vm.$emit('next-page');
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ after: pagination().endCursor, first: GRAPHQL_PAGE_SIZE }),
);
});
it('when list emits prev-page fetches the prev set of records', () => {
findListComponent().vm.$emit('prev-page');
expect(resolver).toHaveBeenCalledWith(
expect.objectContaining({ before: pagination().startCursor, last: GRAPHQL_PAGE_SIZE }),
);
});
});
describe.each` describe.each`
type | sortType type | sortType
${PROJECT_RESOURCE_TYPE} | ${'sort'} ${PROJECT_RESOURCE_TYPE} | ${'sort'}
...@@ -136,7 +177,7 @@ describe('PackagesListApp', () => { ...@@ -136,7 +177,7 @@ describe('PackagesListApp', () => {
beforeEach(() => { beforeEach(() => {
provide = { ...defaultProvide, isGroupPage }; provide = { ...defaultProvide, isGroupPage };
resolver = jest.fn().mockResolvedValue(packagesListQuery(type)); resolver = jest.fn().mockResolvedValue(packagesListQuery({ type }));
mountComponent({ provide, resolver }); mountComponent({ provide, resolver });
return waitForDebouncedApollo(); return waitForDebouncedApollo();
}); });
...@@ -151,4 +192,40 @@ describe('PackagesListApp', () => { ...@@ -151,4 +192,40 @@ describe('PackagesListApp', () => {
); );
}); });
}); });
describe('empty state', () => {
beforeEach(() => {
const resolver = jest.fn().mockResolvedValue(packagesListQuery({ extend: { nodes: [] } }));
mountComponent({ resolver });
return waitForDebouncedApollo();
});
it('generate the correct empty list link', () => {
const link = findListComponent().findComponent(GlLink);
expect(link.attributes('href')).toBe(defaultProvide.emptyListHelpUrl);
expect(link.text()).toBe('publish and share your packages');
});
it('includes the right content on the default tab', () => {
expect(findEmptyState().text()).toContain(PackageListApp.i18n.emptyPageTitle);
});
});
describe('filter without results', () => {
beforeEach(async () => {
mountComponent();
await waitForDebouncedApollo();
findSearch().vm.$emit('update', searchPayload);
return nextTick();
});
it('should show specific empty message', () => {
expect(findEmptyState().text()).toContain(PackageListApp.i18n.noResultsTitle);
expect(findEmptyState().text()).toContain(PackageListApp.i18n.widenFilters);
});
});
}); });
import capitalize from 'lodash/capitalize';
export const packageTags = () => [ export const packageTags = () => [
{ id: 'gid://gitlab/Packages::Tag/87', name: 'bananas_9', __typename: 'PackageTag' }, { id: 'gid://gitlab/Packages::Tag/87', name: 'bananas_9', __typename: 'PackageTag' },
{ id: 'gid://gitlab/Packages::Tag/86', name: 'bananas_8', __typename: 'PackageTag' }, { id: 'gid://gitlab/Packages::Tag/86', name: 'bananas_8', __typename: 'PackageTag' },
...@@ -156,6 +158,15 @@ export const nugetMetadata = () => ({ ...@@ -156,6 +158,15 @@ export const nugetMetadata = () => ({
projectUrl: 'projectUrl', projectUrl: 'projectUrl',
}); });
export const pagination = (extend) => ({
endCursor: 'eyJpZCI6IjIwNSIsIm5hbWUiOiJteS9jb21wYW55L2FwcC9teS1hcHAifQ',
hasNextPage: true,
hasPreviousPage: true,
startCursor: 'eyJpZCI6IjI0NyIsIm5hbWUiOiJ2ZXJzaW9uX3Rlc3QxIn0',
__typename: 'PageInfo',
...extend,
});
export const packageDetailsQuery = (extendPackage) => ({ export const packageDetailsQuery = (extendPackage) => ({
data: { data: {
package: { package: {
...@@ -256,7 +267,7 @@ export const packageDestroyFileMutationError = () => ({ ...@@ -256,7 +267,7 @@ export const packageDestroyFileMutationError = () => ({
], ],
}); });
export const packagesListQuery = (type = 'group') => ({ export const packagesListQuery = ({ type = 'group', extend = {}, extendPagination = {} } = {}) => ({
data: { data: {
[type]: { [type]: {
packages: { packages: {
...@@ -277,9 +288,11 @@ export const packagesListQuery = (type = 'group') => ({ ...@@ -277,9 +288,11 @@ export const packagesListQuery = (type = 'group') => ({
pipelines: { nodes: [] }, pipelines: { nodes: [] },
}, },
], ],
pageInfo: pagination(extendPagination),
__typename: 'PackageConnection', __typename: 'PackageConnection',
}, },
__typename: 'Group', ...extend,
__typename: capitalize(type),
}, },
}, },
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment