Commit cecccc88 authored by Jose Vargas's avatar Jose Vargas Committed by Jose Ivan Vargas

Add pagination to the jobs table refactor

This adds the pagination component to the jobs
table refactor in vue
parent 4acff819
query getJobs($fullPath: ID!, $statuses: [CiJobStatus!]) {
query getJobs(
$fullPath: ID!
$first: Int
$last: Int
$after: String
$before: String
$statuses: [CiJobStatus!]
) {
project(fullPath: $fullPath) {
jobs(first: 20, statuses: $statuses) {
jobs(after: $after, before: $before, first: $first, last: $last, statuses: $statuses) {
pageInfo {
endCursor
hasNextPage
......
<script>
import { GlAlert, GlSkeletonLoader } from '@gitlab/ui';
import { GlAlert, GlPagination, GlSkeletonLoader } from '@gitlab/ui';
import { __ } from '~/locale';
import { GRAPHQL_PAGE_SIZE } from './constants';
import GetJobs from './graphql/queries/get_jobs.query.graphql';
import JobsTable from './jobs_table.vue';
import JobsTableEmptyState from './jobs_table_empty_state.vue';
import JobsTableTabs from './jobs_table_tabs.vue';
const initialPaginationState = {
currentPage: 1,
prevPageCursor: '',
nextPageCursor: '',
first: GRAPHQL_PAGE_SIZE,
last: null,
};
export default {
i18n: {
errorMsg: __('There was an error fetching the jobs for your project.'),
},
components: {
GlAlert,
GlPagination,
GlSkeletonLoader,
JobsTable,
JobsTableEmptyState,
......@@ -28,10 +38,18 @@ export default {
variables() {
return {
fullPath: this.fullPath,
first: this.pagination.first,
last: this.pagination.last,
after: this.pagination.nextPageCursor,
before: this.pagination.prevPageCursor,
};
},
update({ project }) {
return project?.jobs?.nodes || [];
update(data) {
const { jobs: { nodes: list = [], pageInfo = {} } = {} } = data.project || {};
return {
list,
pageInfo,
};
},
error() {
this.hasError = true;
......@@ -40,10 +58,11 @@ export default {
},
data() {
return {
jobs: null,
jobs: {},
hasError: false,
isAlertDismissed: false,
scope: null,
pagination: initialPaginationState,
};
},
computed: {
......@@ -51,7 +70,16 @@ export default {
return this.hasError && !this.isAlertDismissed;
},
showEmptyState() {
return this.jobs.length === 0 && !this.scope;
return this.jobs.list.length === 0 && !this.scope;
},
prevPage() {
return Math.max(this.pagination.currentPage - 1, 0);
},
nextPage() {
return this.jobs.pageInfo?.hasNextPage ? this.pagination.currentPage + 1 : null;
},
showPaginationControls() {
return Boolean(this.prevPage || this.nextPage);
},
},
methods: {
......@@ -60,6 +88,24 @@ export default {
this.$apollo.queries.jobs.refetch({ statuses: scope });
},
handlePageChange(page) {
const { startCursor, endCursor } = this.jobs.pageInfo;
if (page > this.pagination.currentPage) {
this.pagination = {
...initialPaginationState,
nextPageCursor: endCursor,
currentPage: page,
};
} else {
this.pagination = {
lastPageSize: GRAPHQL_PAGE_SIZE,
firstPageSize: null,
prevPageCursor: startCursor,
currentPage: page,
};
}
},
},
};
</script>
......@@ -97,6 +143,17 @@ export default {
<jobs-table-empty-state v-else-if="showEmptyState" />
<jobs-table v-else :jobs="jobs" />
<jobs-table v-else :jobs="jobs.list" />
<gl-pagination
v-if="showPaginationControls"
:disabled="$apollo.loading"
:value="pagination.currentPage"
:prev-page="prevPage"
:next-page="nextPage"
align="center"
class="gl-mt-3"
@input="handlePageChange"
/>
</div>
</template>
import { GlSkeletonLoader, GlAlert, GlEmptyState } from '@gitlab/ui';
import { GlSkeletonLoader, GlAlert, GlEmptyState, GlPagination } from '@gitlab/ui';
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
......@@ -25,6 +25,10 @@ describe('Job table app', () => {
const findTabs = () => wrapper.findComponent(JobsTableTabs);
const findAlert = () => wrapper.findComponent(GlAlert);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findPagination = () => wrapper.findComponent(GlPagination);
const findPrevious = () => findPagination().findAll('.page-item').at(0);
const findNext = () => findPagination().findAll('.page-item').at(1);
const createMockApolloProvider = (handler) => {
const requestHandlers = [[getJobsQuery, handler]];
......@@ -32,8 +36,17 @@ describe('Job table app', () => {
return createMockApollo(requestHandlers);
};
const createComponent = (handler = successHandler, mountFn = shallowMount) => {
const createComponent = ({
handler = successHandler,
mountFn = shallowMount,
data = {},
} = {}) => {
wrapper = mountFn(JobsTableApp, {
data() {
return {
...data,
};
},
provide: {
projectPath,
},
......@@ -52,6 +65,7 @@ describe('Job table app', () => {
expect(findSkeletonLoader().exists()).toBe(true);
expect(findTable().exists()).toBe(false);
expect(findPagination().exists()).toBe(false);
});
});
......@@ -65,9 +79,10 @@ describe('Job table app', () => {
it('should display the jobs table with data', () => {
expect(findTable().exists()).toBe(true);
expect(findSkeletonLoader().exists()).toBe(false);
expect(findPagination().exists()).toBe(true);
});
it('should retfech jobs query on fetchJobsByStatus event', async () => {
it('should refetch jobs query on fetchJobsByStatus event', async () => {
jest.spyOn(wrapper.vm.$apollo.queries.jobs, 'refetch').mockImplementation(jest.fn());
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
......@@ -78,9 +93,59 @@ describe('Job table app', () => {
});
});
describe('pagination', () => {
it('should disable the next page button on the last page', async () => {
createComponent({
handler: successHandler,
mountFn: mount,
data: {
pagination: {
currentPage: 3,
},
jobs: {
pageInfo: {
hasNextPage: false,
hasPreviousPage: true,
startCursor: 'abc',
endCursor: 'bcd',
},
},
},
});
expect(findPrevious().exists()).toBe(true);
expect(findNext().exists()).toBe(true);
expect(findNext().classes('disabled')).toBe(true);
});
it('should disable the preivous page button on the first page', async () => {
createComponent({
handler: successHandler,
mountFn: mount,
data: {
pagination: {
currentPage: 1,
},
jobs: {
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
startCursor: 'abc',
endCursor: 'bcd',
},
},
},
});
expect(findPrevious().exists()).toBe(true);
expect(findPrevious().classes('disabled')).toBe(true);
expect(findNext().exists()).toBe(true);
});
});
describe('error state', () => {
it('should show an alert if there is an error fetching the data', async () => {
createComponent(failedHandler);
createComponent({ handler: failedHandler });
await waitForPromises();
......@@ -90,7 +155,7 @@ describe('Job table app', () => {
describe('empty state', () => {
it('should display empty state if there are no jobs and tab scope is null', async () => {
createComponent(emptyHandler, mount);
createComponent({ handler: emptyHandler, mountFn: mount });
await waitForPromises();
......@@ -99,7 +164,7 @@ describe('Job table app', () => {
});
it('should not display empty state if there are jobs and tab scope is not null', async () => {
createComponent(successHandler, mount);
createComponent({ handler: successHandler, mountFn: mount });
await waitForPromises();
......
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