Skip to content

Commit

Permalink
feat(graphql): Added new offset connection with totalCount
Browse files Browse the repository at this point in the history
* Updated offset paging strategy to return a connection with pageInfo and nodes
* Updated offset paging strategy to support total count
  • Loading branch information
doug-martin committed Feb 26, 2021
1 parent f2f6b99 commit 2780e7e
Show file tree
Hide file tree
Showing 65 changed files with 1,285 additions and 287 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ input TestDtoFilter {
numberEnumField: NumberEnumFilterComparison
timestampField: TimestampFieldComparison
filterableRelation: TestFilterDtoFilterTestRelationDtoFilter
filterableConnection: TestFilterDtoFilterTestRelationDtoFilter
filterableRelations: TestFilterDtoFilterTestRelationDtoFilter
filterableCursorConnection: TestFilterDtoFilterTestRelationDtoFilter
filterableOffsetConnection: TestFilterDtoFilterTestRelationDtoFilter
filterableUnPagedRelations: TestFilterDtoFilterTestRelationDtoFilter
}

input TestFilterDtoFilter {
Expand All @@ -34,8 +35,9 @@ input TestFilterDtoFilter {
numberEnumField: NumberEnumFilterComparison
timestampField: TimestampFieldComparison
filterableRelation: TestFilterDtoFilterTestRelationDtoFilter
filterableConnection: TestFilterDtoFilterTestRelationDtoFilter
filterableRelations: TestFilterDtoFilterTestRelationDtoFilter
filterableCursorConnection: TestFilterDtoFilterTestRelationDtoFilter
filterableOffsetConnection: TestFilterDtoFilterTestRelationDtoFilter
filterableUnPagedRelations: TestFilterDtoFilterTestRelationDtoFilter
}

input NumberFieldComparison {
Expand Down
11 changes: 8 additions & 3 deletions packages/query-graphql/__tests__/__fixtures__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,13 @@ export const noPagingQueryArgsTypeSDL = readGraphql(resolve(__dirname, './no-pag
export const noPagingQueryArgsFilterRequiredTypeSDL = readGraphql(
resolve(__dirname, './no-paging-query-args-required-filter-type.graphql'),
);
export const connectionObjectTypeSDL = readGraphql(resolve(__dirname, './connection-object-type.graphql'));
export const connectionObjectTypeWithTotalCountSDL = readGraphql(
resolve(__dirname, './connection-object-type-with-total-count.graphql'),
export const cursorConnectionObjectTypeSDL = readGraphql(resolve(__dirname, './cursor-connection-object-type.graphql'));
export const cursorConnectionObjectTypeWithTotalCountSDL = readGraphql(
resolve(__dirname, './cursor-connection-object-type-with-total-count.graphql'),
);
export const edgeObjectTypeSDL = readGraphql(resolve(__dirname, './edge-object-type.graphql'));

export const offsetConnectionObjectTypeSDL = readGraphql(resolve(__dirname, './offset-connection-object-type.graphql'));
export const offsetConnectionObjectTypeWithTotalCountSDL = readGraphql(
resolve(__dirname, './offset-connection-object-type-with-total-count.graphql'),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type Test {
stringField: String!
numberField: Float!
boolField: Boolean!
}

type TestTotalCount {
stringField: String!
}

type OffsetPageInfo {
"""true if paging forward and there are more records."""
hasNextPage: Boolean

"""true if paging backwards and there are more records."""
hasPreviousPage: Boolean
}

type TestTotalCountOffsetConnection {
"""Paging information"""
pageInfo: OffsetPageInfo!

"""Array of nodes."""
nodes: [TestTotalCount!]!

"""Fetch total count of records"""
totalCount: Int!
}

type Query {
test: TestTotalCountOffsetConnection!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
type Test {
stringField: String!
numberField: Float!
boolField: Boolean!
}

type OffsetPageInfo {
"""true if paging forward and there are more records."""
hasNextPage: Boolean

"""true if paging backwards and there are more records."""
hasPreviousPage: Boolean
}

type TestOffsetConnection {
"""Paging information"""
pageInfo: OffsetPageInfo!

"""Array of nodes."""
nodes: [Test!]!
}

type Query {
test: TestOffsetConnection!
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { Filter } from '@nestjs-query/core';
import { Injectable } from '@nestjs/common';
import { Authorizer, Relation, Authorize } from '../../src';
import { Authorizer, Relation, Authorize, UnPagedRelation } from '../../src';
import { getAuthorizerToken } from '../../src/auth';
import { createAuthorizerProviders } from '../../src/providers';

Expand Down Expand Up @@ -39,7 +39,7 @@ describe('createDefaultAuthorizer', () => {
@Relation('relations', () => TestRelation, {
auth: { authorize: (ctx: UserContext) => ({ relationOwnerId: { eq: ctx.user.id } }) },
})
@Relation('decoratorRelations', () => [TestDecoratorRelation])
@UnPagedRelation('decoratorRelations', () => TestDecoratorRelation)
@Relation('authorizerRelation', () => RelationWithAuthorizer)
class TestDTO {
ownerId!: number;
Expand Down
157 changes: 118 additions & 39 deletions packages/query-graphql/__tests__/decorators/relation.decorator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// eslint-disable-next-line max-classes-per-file
import { ObjectType } from '@nestjs/graphql';
import { Relation, Connection, PagingStrategies, FilterableRelation, FilterableConnection } from '../../src';
import { getRelations } from '../../src/decorators';
import { Relation, PagingStrategies, UnPagedRelation, OffsetConnection, FilterableRelation } from '../../src';
import {
CursorConnection,
FilterableCursorConnection,
FilterableOffsetConnection,
getRelations,
FilterableUnPagedRelation,
} from '../../src/decorators';

@ObjectType()
class TestRelation {}
Expand All @@ -15,72 +21,114 @@ describe('@Relation', () => {
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({ one: { test: { DTO: TestRelation, ...relationOpts } } });
expect(relations).toEqual({ one: { test: { DTO: TestRelation, allowFiltering: false, ...relationOpts } } });
});
});

describe('@FilterableRelation', () => {
it('should add the relation metadata to the metadata storage', () => {
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@FilterableRelation('test', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({ one: { test: { DTO: TestRelation, ...relationOpts, allowFiltering: true } } });
});
});

describe('@UnPagedConnection', () => {
it('should set the isMany flag if the relationFn returns an array', () => {
const relationFn = () => [TestRelation];
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@Relation('tests', relationFn, relationOpts)
@UnPagedRelation('tests', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({
many: { tests: { DTO: TestRelation, ...relationOpts, pagingStrategy: PagingStrategies.OFFSET } },
many: {
tests: { DTO: TestRelation, ...relationOpts, allowFiltering: false, pagingStrategy: PagingStrategies.NONE },
},
});
});
});

describe('@FilterableRelation', () => {
describe('@FilterableUnPagedRelation', () => {
it('should add the relation metadata to the metadata storage', () => {
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@FilterableRelation('test', relationFn, relationOpts)
@FilterableUnPagedRelation('test', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({ one: { test: { DTO: TestRelation, ...relationOpts, allowFiltering: true } } });
expect(relations).toEqual({
many: {
test: { DTO: TestRelation, pagingStrategy: PagingStrategies.NONE, ...relationOpts, allowFiltering: true },
},
});
});
});

it('should set the isMany flag if the relationFn returns an array', () => {
const relationFn = () => [TestRelation];
describe('@OffsetConnection', () => {
it('should add the relation metadata to the metadata storage', () => {
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@FilterableRelation('tests', relationFn, relationOpts)
@OffsetConnection('test', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({
many: {
tests: { DTO: TestRelation, ...relationOpts, pagingStrategy: PagingStrategies.OFFSET, allowFiltering: true },
test: { DTO: TestRelation, ...relationOpts, allowFiltering: false, pagingStrategy: PagingStrategies.OFFSET },
},
});
});
});

describe('@Connection', () => {
describe('@FilterableOffsetConnection', () => {
it('should add the relation metadata to the metadata storage', () => {
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@Connection('test', relationFn, relationOpts)
@FilterableOffsetConnection('test', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({
many: { test: { DTO: TestRelation, ...relationOpts, pagingStrategy: PagingStrategies.CURSOR } },
many: {
test: { DTO: TestRelation, ...relationOpts, pagingStrategy: PagingStrategies.OFFSET, allowFiltering: true },
},
});
});
});

describe('@FilterableConnection', () => {
describe('@CursorConnection', () => {
it('should add the relation metadata to the metadata storage', () => {
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@FilterableConnection('test', relationFn, relationOpts)
@CursorConnection('test', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
expect(relations).toEqual({
many: {
test: { DTO: TestRelation, ...relationOpts, allowFiltering: false, pagingStrategy: PagingStrategies.CURSOR },
},
});
});
});

describe('@FilterableCursorConnection', () => {
it('should add the relation metadata to the metadata storage', () => {
const relationFn = () => TestRelation;
const relationOpts = { disableRead: true };
@ObjectType()
@FilterableCursorConnection('test', relationFn, relationOpts)
class TestDTO {}

const relations = getRelations(TestDTO);
Expand All @@ -98,61 +146,92 @@ describe('getRelations', () => {

@ObjectType({ isAbstract: true })
@Relation('test', () => SomeRelation)
@Relation('tests', () => [SomeRelation])
@Connection('testConnection', () => SomeRelation)
@UnPagedRelation('unPagedTests', () => SomeRelation)
@OffsetConnection('offsetTests', () => SomeRelation)
@CursorConnection('cursorTests', () => SomeRelation)
class BaseType {}

@ObjectType()
@Relation('implementedRelation', () => SomeRelation)
@Relation('implementedRelations', () => [SomeRelation])
@Connection('implementedConnection', () => SomeRelation)
@UnPagedRelation('implementedUnPagedRelations', () => SomeRelation)
@OffsetConnection('implementedOffsetConnection', () => SomeRelation)
@CursorConnection('implementedCursorConnection', () => SomeRelation)
class ImplementingClass extends BaseType {}

@ObjectType()
@Relation('implementedRelation', () => SomeRelation, { relationName: 'test' })
@Relation('implementedRelations', () => [SomeRelation], { relationName: 'tests' })
@Connection('implementedConnection', () => SomeRelation, { relationName: 'testConnection' })
@UnPagedRelation('implementedUnPagedRelations', () => SomeRelation, { relationName: 'tests' })
@OffsetConnection('implementedOffsetConnection', () => SomeRelation, { relationName: 'tests' })
@CursorConnection('implementedCursorConnection', () => SomeRelation, { relationName: 'testConnection' })
class DuplicateImplementor extends ImplementingClass {}

it('should return relations for a type', () => {
expect(getRelations(BaseType)).toEqual({
one: {
test: { DTO: SomeRelation },
test: { DTO: SomeRelation, allowFiltering: false },
},
many: {
tests: { DTO: SomeRelation, pagingStrategy: 'offset' },
testConnection: { DTO: SomeRelation, pagingStrategy: 'cursor' },
unPagedTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.NONE },
offsetTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.OFFSET },
cursorTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.CURSOR },
},
});
});

it('should return inherited relations fields for a type', () => {
expect(getRelations(ImplementingClass)).toEqual({
one: {
test: { DTO: SomeRelation },
implementedRelation: { DTO: SomeRelation },
test: { DTO: SomeRelation, allowFiltering: false },
implementedRelation: { DTO: SomeRelation, allowFiltering: false },
},
many: {
tests: { DTO: SomeRelation, pagingStrategy: PagingStrategies.OFFSET },
testConnection: { DTO: SomeRelation, pagingStrategy: PagingStrategies.CURSOR },
implementedRelations: { DTO: SomeRelation, pagingStrategy: PagingStrategies.OFFSET },
implementedConnection: { DTO: SomeRelation, pagingStrategy: PagingStrategies.CURSOR },
unPagedTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.NONE },
offsetTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.OFFSET },
cursorTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.CURSOR },
implementedUnPagedRelations: {
DTO: SomeRelation,
allowFiltering: false,
pagingStrategy: PagingStrategies.NONE,
},
implementedOffsetConnection: {
DTO: SomeRelation,
allowFiltering: false,
pagingStrategy: PagingStrategies.OFFSET,
},
implementedCursorConnection: {
DTO: SomeRelation,
allowFiltering: false,
pagingStrategy: PagingStrategies.CURSOR,
},
},
});
});

it('should exclude duplicate inherited relations fields for a type', () => {
expect(getRelations(DuplicateImplementor)).toEqual({
one: {
test: { DTO: SomeRelation },
implementedRelation: { DTO: SomeRelation, relationName: 'test' },
test: { DTO: SomeRelation, allowFiltering: false },
implementedRelation: { DTO: SomeRelation, allowFiltering: false, relationName: 'test' },
},
many: {
tests: { DTO: SomeRelation, pagingStrategy: PagingStrategies.OFFSET },
testConnection: { DTO: SomeRelation, pagingStrategy: PagingStrategies.CURSOR },
implementedRelations: { DTO: SomeRelation, pagingStrategy: PagingStrategies.OFFSET, relationName: 'tests' },
implementedConnection: {
unPagedTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.NONE },
offsetTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.OFFSET },
cursorTests: { DTO: SomeRelation, allowFiltering: false, pagingStrategy: PagingStrategies.CURSOR },
implementedUnPagedRelations: {
DTO: SomeRelation,
allowFiltering: false,
pagingStrategy: PagingStrategies.NONE,
relationName: 'tests',
},
implementedOffsetConnection: {
DTO: SomeRelation,
allowFiltering: false,
pagingStrategy: PagingStrategies.OFFSET,
relationName: 'tests',
},
implementedCursorConnection: {
DTO: SomeRelation,
allowFiltering: false,
pagingStrategy: PagingStrategies.CURSOR,
relationName: 'testConnection',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,11 @@ export const readCustomConnectionResolverSDL = readGraphql(
);
export const readCustomQueryResolverSDL = readGraphql(resolve(__dirname, 'read', 'read-custom-query.resolver.graphql'));
export const readOffsetQueryResolverSDL = readGraphql(resolve(__dirname, 'read', 'read-offset-query.resolver.graphql'));
export const readConnectionWithTotalCountSDL = readGraphql(
resolve(__dirname, 'read', 'read-connection-with-total-count.resolver.graphql'),
export const readCursorConnectionWithTotalCountSDL = readGraphql(
resolve(__dirname, 'read', 'read-cursor-connection-with-total-count.resolver.graphql'),
);
export const readOffsetConnectionWithTotalCountSDL = readGraphql(
resolve(__dirname, 'read', 'read-offset-connection-with-total-count.resolver.graphql'),
);
export const readCustomOneQueryResolverSDL = readGraphql(
resolve(__dirname, 'read', 'read-custom-one-query.resolver.graphql'),
Expand Down
Loading

0 comments on commit 2780e7e

Please sign in to comment.