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) { project(fullPath: $fullPath) {
jobs(first: 20, statuses: $statuses) { jobs(after: $after, before: $before, first: $first, last: $last, statuses: $statuses) {
pageInfo { pageInfo {
endCursor endCursor
hasNextPage hasNextPage
......
<script> <script>
import { GlAlert, GlSkeletonLoader } from '@gitlab/ui'; import { GlAlert, GlPagination, GlSkeletonLoader } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { GRAPHQL_PAGE_SIZE } from './constants';
import GetJobs from './graphql/queries/get_jobs.query.graphql'; import GetJobs from './graphql/queries/get_jobs.query.graphql';
import JobsTable from './jobs_table.vue'; import JobsTable from './jobs_table.vue';
import JobsTableEmptyState from './jobs_table_empty_state.vue'; import JobsTableEmptyState from './jobs_table_empty_state.vue';
import JobsTableTabs from './jobs_table_tabs.vue'; import JobsTableTabs from './jobs_table_tabs.vue';
const initialPaginationState = {
currentPage: 1,
prevPageCursor: '',
nextPageCursor: '',
first: GRAPHQL_PAGE_SIZE,
last: null,
};
export default { export default {
i18n: { i18n: {
errorMsg: __('There was an error fetching the jobs for your project.'), errorMsg: __('There was an error fetching the jobs for your project.'),
}, },
components: { components: {
GlAlert, GlAlert,
GlPagination,
GlSkeletonLoader, GlSkeletonLoader,
JobsTable, JobsTable,
JobsTableEmptyState, JobsTableEmptyState,
...@@ -28,10 +38,18 @@ export default { ...@@ -28,10 +38,18 @@ export default {
variables() { variables() {
return { return {
fullPath: this.fullPath, fullPath: this.fullPath,
first: this.pagination.first,
last: this.pagination.last,
after: this.pagination.nextPageCursor,
before: this.pagination.prevPageCursor,
}; };
}, },
update({ project }) { update(data) {
return project?.jobs?.nodes || []; const { jobs: { nodes: list = [], pageInfo = {} } = {} } = data.project || {};
return {
list,
pageInfo,
};
}, },
error() { error() {
this.hasError = true; this.hasError = true;
...@@ -40,10 +58,11 @@ export default { ...@@ -40,10 +58,11 @@ export default {
}, },
data() { data() {
return { return {
jobs: null, jobs: {},
hasError: false, hasError: false,
isAlertDismissed: false, isAlertDismissed: false,
scope: null, scope: null,
pagination: initialPaginationState,
}; };
}, },
computed: { computed: {
...@@ -51,7 +70,16 @@ export default { ...@@ -51,7 +70,16 @@ export default {
return this.hasError && !this.isAlertDismissed; return this.hasError && !this.isAlertDismissed;
}, },
showEmptyState() { 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: { methods: {
...@@ -60,6 +88,24 @@ export default { ...@@ -60,6 +88,24 @@ export default {
this.$apollo.queries.jobs.refetch({ statuses: scope }); 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> </script>
...@@ -97,6 +143,17 @@ export default { ...@@ -97,6 +143,17 @@ export default {
<jobs-table-empty-state v-else-if="showEmptyState" /> <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> </div>
</template> </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 { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -25,6 +25,10 @@ describe('Job table app', () => { ...@@ -25,6 +25,10 @@ describe('Job table app', () => {
const findTabs = () => wrapper.findComponent(JobsTableTabs); const findTabs = () => wrapper.findComponent(JobsTableTabs);
const findAlert = () => wrapper.findComponent(GlAlert); const findAlert = () => wrapper.findComponent(GlAlert);
const findEmptyState = () => wrapper.findComponent(GlEmptyState); 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 createMockApolloProvider = (handler) => {
const requestHandlers = [[getJobsQuery, handler]]; const requestHandlers = [[getJobsQuery, handler]];
...@@ -32,8 +36,17 @@ describe('Job table app', () => { ...@@ -32,8 +36,17 @@ describe('Job table app', () => {
return createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
}; };
const createComponent = (handler = successHandler, mountFn = shallowMount) => { const createComponent = ({
handler = successHandler,
mountFn = shallowMount,
data = {},
} = {}) => {
wrapper = mountFn(JobsTableApp, { wrapper = mountFn(JobsTableApp, {
data() {
return {
...data,
};
},
provide: { provide: {
projectPath, projectPath,
}, },
...@@ -52,6 +65,7 @@ describe('Job table app', () => { ...@@ -52,6 +65,7 @@ describe('Job table app', () => {
expect(findSkeletonLoader().exists()).toBe(true); expect(findSkeletonLoader().exists()).toBe(true);
expect(findTable().exists()).toBe(false); expect(findTable().exists()).toBe(false);
expect(findPagination().exists()).toBe(false);
}); });
}); });
...@@ -65,9 +79,10 @@ describe('Job table app', () => { ...@@ -65,9 +79,10 @@ describe('Job table app', () => {
it('should display the jobs table with data', () => { it('should display the jobs table with data', () => {
expect(findTable().exists()).toBe(true); expect(findTable().exists()).toBe(true);
expect(findSkeletonLoader().exists()).toBe(false); 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()); jest.spyOn(wrapper.vm.$apollo.queries.jobs, 'refetch').mockImplementation(jest.fn());
expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0); expect(wrapper.vm.$apollo.queries.jobs.refetch).toHaveBeenCalledTimes(0);
...@@ -78,9 +93,59 @@ describe('Job table app', () => { ...@@ -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', () => { describe('error state', () => {
it('should show an alert if there is an error fetching the data', async () => { it('should show an alert if there is an error fetching the data', async () => {
createComponent(failedHandler); createComponent({ handler: failedHandler });
await waitForPromises(); await waitForPromises();
...@@ -90,7 +155,7 @@ describe('Job table app', () => { ...@@ -90,7 +155,7 @@ describe('Job table app', () => {
describe('empty state', () => { describe('empty state', () => {
it('should display empty state if there are no jobs and tab scope is null', async () => { 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(); await waitForPromises();
...@@ -99,7 +164,7 @@ describe('Job table app', () => { ...@@ -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 () => { 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(); 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