Skip to content

Commit

Permalink
feat(aggregations,relations): Add relation aggregation graphql enpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
doug-martin committed Jul 16, 2020
1 parent db6ecb2 commit 56bb7e0
Show file tree
Hide file tree
Showing 15 changed files with 648 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { AggregateQuery, QueryService } from '@nestjs-query/core';
import { mock, instance, when, deepEqual } from 'ts-mockito';
import { AggregateRelationsLoader } from '../../src/loader';

describe('AggregateRelationsLoader', () => {
describe('createLoader', () => {
class DTO {
id!: string;
}

class RelationDTO {
id!: string;
}

it('should return a function that accepts aggregate args', () => {
const service = mock<QueryService<DTO>>();
const queryRelationsLoader = new AggregateRelationsLoader(RelationDTO, 'relation');
expect(queryRelationsLoader.createLoader(instance(service))).toBeInstanceOf(Function);
});

it('should try to load the relations with the query args', () => {
const service = mock<QueryService<DTO>>();
const aggregateRelationsLoader = new AggregateRelationsLoader(RelationDTO, 'relation').createLoader(
instance(service),
);
const filter = {};
const aggregate: AggregateQuery<RelationDTO> = { count: ['id'] };
const dtos = [{ id: 'dto-1' }, { id: 'dto-2' }];
const dto1Aggregate = { count: { id: 2 } };
const dto2Aggregate = { count: { id: 3 } };
when(
service.aggregateRelations(RelationDTO, 'relation', deepEqual(dtos), deepEqual(filter), deepEqual(aggregate)),
).thenResolve(
new Map([
[dtos[0], dto1Aggregate],
[dtos[1], dto2Aggregate],
]),
);
return expect(
aggregateRelationsLoader([
{ dto: dtos[0], filter, aggregate },
{ dto: dtos[1], filter, aggregate },
]),
).resolves.toEqual([dto1Aggregate, dto2Aggregate]);
});

it('should try return an empty aggregate result for each dto if no results are found', () => {
const service = mock<QueryService<DTO>>();
const aggregateRelationsLoader = new AggregateRelationsLoader(RelationDTO, 'relation').createLoader(
instance(service),
);
const filter = {};
const aggregate: AggregateQuery<RelationDTO> = { count: ['id'] };
const dtos = [{ id: 'dto-1' }, { id: 'dto-2' }];
const dto1Aggregate = { count: { id: 2 } };
when(
service.aggregateRelations(RelationDTO, 'relation', deepEqual(dtos), deepEqual(filter), deepEqual(aggregate)),
).thenResolve(new Map([[dtos[0], dto1Aggregate]]));
return expect(
aggregateRelationsLoader([
{ dto: dtos[0], filter, aggregate },
{ dto: dtos[1], filter, aggregate },
]),
).resolves.toEqual([dto1Aggregate, {}]);
});

it('should group queryRelations calls by filter and return in the correct order', () => {
const service = mock<QueryService<DTO>>();
const queryRelationsLoader = new AggregateRelationsLoader(RelationDTO, 'relation').createLoader(
instance(service),
);
const filter1 = { id: { gt: 'a' } };
const filter2 = {};
const aggregate: AggregateQuery<RelationDTO> = { count: ['id'] };
const dtos = [{ id: 'dto-1' }, { id: 'dto-2' }, { id: 'dto-3' }, { id: 'dto-4' }];
const dto1Aggregate = { count: { id: 2 } };
const dto2Aggregate = { count: { id: 3 } };
const dto3Aggregate = { count: { id: 4 } };
const dto4Aggregate = { count: { id: 5 } };
when(
service.aggregateRelations(
RelationDTO,
'relation',
deepEqual([dtos[0], dtos[2]]),
deepEqual(filter1),
deepEqual(aggregate),
),
).thenResolve(
new Map([
[dtos[0], dto1Aggregate],
[dtos[2], dto3Aggregate],
]),
);
when(
service.aggregateRelations(
RelationDTO,
'relation',
deepEqual([dtos[1], dtos[3]]),
deepEqual(filter2),
deepEqual(aggregate),
),
).thenResolve(
new Map([
[dtos[1], dto2Aggregate],
[dtos[3], dto4Aggregate],
]),
);
return expect(
queryRelationsLoader([
{ dto: dtos[0], filter: filter1, aggregate },
{ dto: dtos[1], filter: filter2, aggregate },
{ dto: dtos[2], filter: filter1, aggregate },
{ dto: dtos[3], filter: filter2, aggregate },
]),
).resolves.toEqual([dto1Aggregate, dto2Aggregate, dto3Aggregate, dto4Aggregate]);
});

it('should group queryRelations calls by aggregate and return in the correct order', () => {
const service = mock<QueryService<DTO>>();
const queryRelationsLoader = new AggregateRelationsLoader(RelationDTO, 'relation').createLoader(
instance(service),
);
const filter = {};
const aggregate1: AggregateQuery<RelationDTO> = { count: ['id'] };
const aggregate2: AggregateQuery<RelationDTO> = { sum: ['id'] };
const dtos = [{ id: 'dto-1' }, { id: 'dto-2' }, { id: 'dto-3' }, { id: 'dto-4' }];
const dto1Aggregate = { count: { id: 2 } };
const dto2Aggregate = { sum: { id: 3 } };
const dto3Aggregate = { count: { id: 4 } };
const dto4Aggregate = { sum: { id: 5 } };
when(
service.aggregateRelations(
RelationDTO,
'relation',
deepEqual([dtos[0], dtos[2]]),
deepEqual(filter),
deepEqual(aggregate1),
),
).thenResolve(
new Map([
[dtos[0], dto1Aggregate],
[dtos[2], dto3Aggregate],
]),
);
when(
service.aggregateRelations(
RelationDTO,
'relation',
deepEqual([dtos[1], dtos[3]]),
deepEqual(filter),
deepEqual(aggregate2),
),
).thenResolve(
new Map([
[dtos[1], dto2Aggregate],
[dtos[3], dto4Aggregate],
]),
);
return expect(
queryRelationsLoader([
{ dto: dtos[0], filter, aggregate: aggregate1 },
{ dto: dtos[1], filter, aggregate: aggregate2 },
{ dto: dtos[2], filter, aggregate: aggregate1 },
{ dto: dtos[3], filter, aggregate: aggregate2 },
]),
).resolves.toEqual([dto1Aggregate, dto2Aggregate, dto3Aggregate, dto4Aggregate]);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
type TestResolverDTO {
id: ID!
stringField: String!
testsAggregate(
"""Filter to find records to aggregate on"""
filter: TestRelationDTOAggregateFilter
): TestResolverDTOTestsAggregateResponse!
}

input TestRelationDTOAggregateFilter {
and: [TestRelationDTOAggregateFilter!]
or: [TestRelationDTOAggregateFilter!]
id: IDFilterComparison
testResolverId: StringFieldComparison
}

input IDFilterComparison {
is: Boolean
isNot: Boolean
eq: ID
neq: ID
gt: ID
gte: ID
lt: ID
lte: ID
like: ID
notLike: ID
iLike: ID
notILike: ID
in: [ID!]
notIn: [ID!]
}

input StringFieldComparison {
is: Boolean
isNot: Boolean
eq: String
neq: String
gt: String
gte: String
lt: String
lte: String
like: String
notLike: String
iLike: String
notILike: String
in: [String!]
notIn: [String!]
}

type TestResolverDTORelationsCountAggregate {
id: Int
testResolverId: Int
}

type TestResolverDTORelationsMinAggregate {
id: ID
testResolverId: String
}

type TestResolverDTORelationsMaxAggregate {
id: ID
testResolverId: String
}

type TestResolverDTOTestsCountAggregate {
id: Int
testResolverId: Int
}

type TestResolverDTOTestsMinAggregate {
id: ID
testResolverId: String
}

type TestResolverDTOTestsMaxAggregate {
id: ID
testResolverId: String
}

type TestResolverDTOTestsAggregateResponse {
count: TestResolverDTOTestsCountAggregate
min: TestResolverDTOTestsMinAggregate
max: TestResolverDTOTestsMaxAggregate
}

type Query {
test: TestResolverDTO!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
type TestResolverDTO {
id: ID!
stringField: String!
}

type TestResolverDTORelationsCountAggregate {
id: Int
testResolverId: Int
}

type TestResolverDTORelationsMinAggregate {
id: ID
testResolverId: String
}

type TestResolverDTORelationsMaxAggregate {
id: ID
testResolverId: String
}

type TestResolverDTOTestsCountAggregate {
id: Int
testResolverId: Int
}

type TestResolverDTOTestsMinAggregate {
id: ID
testResolverId: String
}

type TestResolverDTOTestsMaxAggregate {
id: ID
testResolverId: String
}

type Query {
test: TestResolverDTO!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
type TestResolverDTO {
id: ID!
stringField: String!
}

type Query {
test: TestResolverDTO!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
type TestResolverDTO {
id: ID!
stringField: String!
relationsAggregate(
"""Filter to find records to aggregate on"""
filter: TestRelationDTOAggregateFilter
): TestResolverDTORelationsAggregateResponse!
}

input TestRelationDTOAggregateFilter {
and: [TestRelationDTOAggregateFilter!]
or: [TestRelationDTOAggregateFilter!]
id: IDFilterComparison
testResolverId: StringFieldComparison
}

input IDFilterComparison {
is: Boolean
isNot: Boolean
eq: ID
neq: ID
gt: ID
gte: ID
lt: ID
lte: ID
like: ID
notLike: ID
iLike: ID
notILike: ID
in: [ID!]
notIn: [ID!]
}

input StringFieldComparison {
is: Boolean
isNot: Boolean
eq: String
neq: String
gt: String
gte: String
lt: String
lte: String
like: String
notLike: String
iLike: String
notILike: String
in: [String!]
notIn: [String!]
}

type TestResolverDTORelationsCountAggregate {
id: Int
testResolverId: Int
}

type TestResolverDTORelationsMinAggregate {
id: ID
testResolverId: String
}

type TestResolverDTORelationsMaxAggregate {
id: ID
testResolverId: String
}

type TestResolverDTORelationsAggregateResponse {
count: TestResolverDTORelationsCountAggregate
min: TestResolverDTORelationsMinAggregate
max: TestResolverDTORelationsMaxAggregate
}

type Query {
test: TestResolverDTO!
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,16 @@ export const referenceRelationSDL = readGraphql(resolve(__dirname, 'reference',
export const referenceRelationNullableSDL = readGraphql(
resolve(__dirname, 'reference', 'reference-relation-nullable.resolver.graphql'),
);

export const aggregateRelationEmptyResolverSDL = readGraphql(
resolve(__dirname, 'aggregate', 'aggregate-relation-empty.resolver.graphql'),
);
export const aggregateRelationResolverSDL = readGraphql(
resolve(__dirname, 'aggregate', 'aggregate-relation.resolver.graphql'),
);
export const aggregateRelationCustomNameSDL = readGraphql(
resolve(__dirname, 'aggregate', 'aggregate-relation-custom-name.resolver.graphql'),
);
export const aggregateRelationDisabledSDL = readGraphql(
resolve(__dirname, 'aggregate', 'aggregate-relation-disabled.resolver.graphql'),
);
Loading

0 comments on commit 56bb7e0

Please sign in to comment.