Commit a377c0e8 authored by Simon Knox's avatar Simon Knox Committed by Kushal Pandya

Add pagination to iterations list

Basically completely copied from issues list.
Not ideal but simple and better than no pagination
parent 58ee6ba9
---
title: Add pagination to iterations list
merge_request: 37052
author:
type: added
<script>
import { GlButton, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui';
import { GlAlert, GlButton, GlLoadingIcon, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
import { __ } from '~/locale';
import IterationsList from './iterations_list.vue';
import GroupIterationQuery from '../queries/group_iterations.query.graphql';
const pageSize = 20;
export default {
components: {
IterationsList,
GlAlert,
GlButton,
GlLoadingIcon,
GlPagination,
GlTab,
GlTabs,
},
......@@ -28,26 +33,60 @@ export default {
},
},
apollo: {
iterations: {
group: {
query: GroupIterationQuery,
update: data => data.group.iterations.nodes,
variables() {
return this.queryVariables;
},
update: data => {
return {
fullPath: this.groupPath,
state: this.state,
iterations: data.group?.iterations?.nodes || [],
pageInfo: data.group?.iterations?.pageInfo || {},
};
},
error() {
this.error = __('Error loading iterations');
},
},
},
data() {
return {
iterations: [],
group: {
iterations: [],
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
},
},
pagination: {
currentPage: 1,
},
tabIndex: 0,
error: '',
};
},
computed: {
queryVariables() {
const vars = {
fullPath: this.groupPath,
state: this.state,
};
if (this.pagination.beforeCursor) {
vars.beforeCursor = this.pagination.beforeCursor;
vars.lastPageSize = pageSize;
} else {
vars.afterCursor = this.pagination.afterCursor;
vars.firstPageSize = pageSize;
}
return vars;
},
iterations() {
return this.group.iterations;
},
loading() {
return this.$apollo.queries.iterations.loading;
return this.$apollo.queries.group.loading;
},
state() {
switch (this.tabIndex) {
......@@ -60,12 +99,38 @@ export default {
return 'all';
}
},
prevPage() {
return Number(this.group.pageInfo.hasPreviousPage);
},
nextPage() {
return Number(this.group.pageInfo.hasNextPage);
},
},
methods: {
handlePageChange(page) {
const { startCursor, endCursor } = this.group.pageInfo;
if (page > this.pagination.currentPage) {
this.pagination = {
afterCursor: endCursor,
currentPage: page,
};
} else {
this.pagination = {
beforeCursor: startCursor,
currentPage: page,
};
}
},
handleTabChange() {
this.pagination = { currentPage: 1 };
},
},
};
</script>
<template>
<gl-tabs v-model="tabIndex">
<gl-tabs v-model="tabIndex" @activate-tab="handleTabChange">
<gl-tab v-for="tab in [__('Open'), __('Closed'), __('All')]" :key="tab">
<template #title>
{{ tab }}
......@@ -73,7 +138,23 @@ export default {
<div v-if="loading" class="gl-my-5">
<gl-loading-icon size="lg" />
</div>
<iterations-list v-else :iterations="iterations" />
<div v-else-if="error">
<gl-alert variant="danger" @dismiss="error = ''">
{{ error }}
</gl-alert>
</div>
<div v-else>
<iterations-list :iterations="iterations" />
<gl-pagination
v-if="prevPage || nextPage"
:value="pagination.currentPage"
:prev-page="prevPage"
:next-page="nextPage"
align="center"
class="gl-pagination gl-mt-3"
@input="handlePageChange"
/>
</div>
</gl-tab>
<template v-if="canAdmin" #tabs-end>
<li class="gl-ml-auto gl-display-flex gl-align-items-center">
......
query GroupIterations($fullPath: ID!, $state: IterationState!) {
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query GroupIterations(
$fullPath: ID!
$state: IterationState!
$beforeCursor: String = ""
$afterCursor: String = ""
$firstPageSize: Int
$lastPageSize: Int
) {
group(fullPath: $fullPath) {
iterations(state: $state, first: 20, includeAncestors: false) {
iterations(
state: $state
includeAncestors: false
before: $beforeCursor
after: $afterCursor
first: $firstPageSize
last: $lastPageSize
) {
nodes {
title
state
......@@ -9,6 +25,9 @@ query GroupIterations($fullPath: ID!, $state: IterationState!) {
startDate
dueDate
}
pageInfo {
...PageInfo
}
}
}
}
import Iterations from 'ee/iterations/components/iterations.vue';
import IterationsList from 'ee/iterations/components/iterations_list.vue';
import { shallowMount } from '@vue/test-utils';
import { GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui';
import { GlAlert, GlLoadingIcon, GlPagination, GlTab, GlTabs } from '@gitlab/ui';
describe('Iterations tabs', () => {
let wrapper;
......@@ -14,7 +14,7 @@ describe('Iterations tabs', () => {
propsData: props,
mocks: {
$apollo: {
queries: { iterations: { loading } },
queries: { group: { loading } },
},
},
stubs: {
......@@ -61,4 +61,93 @@ describe('Iterations tabs', () => {
expect(wrapper.vm.state).toEqual('all');
});
describe('pagination', () => {
const findPagination = () => wrapper.find(GlPagination);
const setPage = page => {
findPagination().vm.$emit('input', page);
return findPagination().vm.$nextTick();
};
beforeEach(() => {
mountComponent({
loading: false,
});
wrapper.setData({
group: {
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'first-item',
endCursor: 'last-item',
},
},
});
});
it('passes prev, next, and current page props', () => {
expect(findPagination().exists()).toBe(true);
expect(findPagination().props()).toEqual(
expect.objectContaining({
value: wrapper.vm.pagination.currentPage,
prevPage: wrapper.vm.prevPage,
nextPage: wrapper.vm.nextPage,
}),
);
});
it('updates query variables when going to previous page', async () => {
await setPage(1);
expect(wrapper.vm.queryVariables).toEqual({
beforeCursor: 'first-item',
lastPageSize: 20,
fullPath: defaultProps.groupPath,
state: 'opened',
});
});
it('updates query variables when going to next page', async () => {
await setPage(2);
expect(wrapper.vm.queryVariables).toEqual({
afterCursor: 'last-item',
firstPageSize: 20,
fullPath: defaultProps.groupPath,
state: 'opened',
});
});
it('resets pagination when changing tabs', async () => {
await setPage(2);
expect(wrapper.vm.pagination).toEqual({
currentPage: 2,
afterCursor: 'last-item',
});
wrapper.find(GlTabs).vm.$emit('activate-tab', 2);
await wrapper.vm.$nextTick();
expect(wrapper.vm.pagination).toEqual({
currentPage: 1,
});
});
});
describe('error', () => {
beforeEach(() => {
mountComponent({
loading: false,
});
wrapper.setData({
error: 'Oh no!',
});
});
it('tab shows error in alert', () => {
expect(wrapper.find(GlAlert).text()).toContain('Oh no!');
});
});
});
......@@ -9400,6 +9400,9 @@ msgstr ""
msgid "Error loading issues"
msgstr ""
msgid "Error loading iterations"
msgstr ""
msgid "Error loading last commit."
msgstr ""
......
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