Skip to content

Commit

Permalink
Add Pagination utils with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
manelcecs committed Oct 31, 2023
1 parent 822e5ff commit ae70c3d
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/domain-services/flows/flow-search-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class FlowSearchService {
);

return {
items,
flows: items,
hasNextPage: first <= flows.length,
hasPreviousPage: afterCursor !== undefined,
startCursor: flows.length ? flows[0].id.valueOf() : 0,
Expand Down
2 changes: 1 addition & 1 deletion src/domain-services/flows/graphql/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default class Flow implements ItemPaged {
@ObjectType()
export class FlowSearchResult extends PageInfo<FlowSortField> {
@Field(() => [Flow], { nullable: false })
items: Flow[];
flows: Flow[];
}

export type FlowSortField =
Expand Down
64 changes: 64 additions & 0 deletions src/utils/graphql/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Op } from '@unocha/hpc-api-core/src/db/util/conditions';
import { createBrandedValue } from '@unocha/hpc-api-core/src/util/types';
import { ObjectType, Field } from 'type-graphql';

export interface ItemPaged {
cursor: number;
}

@ObjectType()
export class PageInfo<TSortFields extends string> {
@Field({ nullable: false })
hasNextPage: boolean;

@Field({ nullable: false })
hasPreviousPage: boolean;

@Field({ nullable: false })
startCursor: number;

@Field({ nullable: false })
endCursor: number;

@Field({ nullable: false })
pageSize: number;

@Field(() => String, { nullable: false })
sortField: TSortFields;

@Field({ nullable: false })
sortOrder: string;

@Field({ nullable: false })
total: number;
}

export function prepareConditionFromCursor(
sortCondition: { column: string; order: 'asc' | 'desc' },
afterCursor?: number,
beforeCursor?: number
): any {
if (afterCursor && beforeCursor) {
throw new Error('Cannot use before and after cursor at the same time');
}

if (afterCursor || beforeCursor) {
const isAscending = sortCondition.order === 'asc';
const cursorValue = afterCursor || beforeCursor;

let op;
if (isAscending) {
op = afterCursor ? Op.GT : Op.LT;
} else {
op = beforeCursor ? Op.GT : Op.LT;
}

return {
id: {
[op]: createBrandedValue(cursorValue),
},
};
}

return {};
}
72 changes: 72 additions & 0 deletions tests/unit/pagination.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Op } from '@unocha/hpc-api-core/src/db/util/conditions';
import { prepareConditionFromCursor } from '../../src/utils/graphql/pagination';

describe('Based on cursor and order for pagination', () => {
describe('Order is asc', () => {
const sortCondition = { column: 'id', order: 'asc' as const };

it("Should return 'GT' when afterCursor is defined", () => {
const afterCursor = 1;
const beforeCursor = undefined;
const result = prepareConditionFromCursor(
sortCondition,
afterCursor,
beforeCursor
);
expect(result.id).toEqual({ [Op.GT]: afterCursor });
});

it("Should return 'LT' when beforeCursor is defined", () => {
const afterCursor = undefined;
const beforeCursor = 1;
const result = prepareConditionFromCursor(
sortCondition,
afterCursor,
beforeCursor
);
expect(result.id).toEqual({ [Op.LT]: beforeCursor });
});

it('Should throw an error when both afterCursor and beforeCursor are defined', () => {
const afterCursor = 1;
const beforeCursor = 2;
expect(() =>
prepareConditionFromCursor(sortCondition, afterCursor, beforeCursor)
).toThrowError('Cannot use before and after cursor at the same time');
});
});

describe("Order is 'desc'", () => {
const sortCondition = { column: 'id', order: 'desc' as const };

it("Should return 'LT' when afterCursor is defined", () => {
const afterCursor = 1;
const beforeCursor = undefined;
const result = prepareConditionFromCursor(
sortCondition,
afterCursor,
beforeCursor
);
expect(result.id).toEqual({ [Op.LT]: afterCursor });
});

it("Should return 'GT' when beforeCursor is defined", () => {
const afterCursor = undefined;
const beforeCursor = 1;
const result = prepareConditionFromCursor(
sortCondition,
afterCursor,
beforeCursor
);
expect(result.id).toEqual({ [Op.GT]: beforeCursor });
});

it('Should throw an error when both afterCursor and beforeCursor are defined', () => {
const afterCursor = 1;
const beforeCursor = 2;
expect(() =>
prepareConditionFromCursor(sortCondition, afterCursor, beforeCursor)
).toThrowError('Cannot use before and after cursor at the same time');
});
});
});

0 comments on commit ae70c3d

Please sign in to comment.