Skip to content

Commit 7511982

Browse files
committed
group by root input
1 parent fedf86a commit 7511982

File tree

8 files changed

+100
-16
lines changed

8 files changed

+100
-16
lines changed

packages/graphql/src/schema-model/attribute/model-adapters/AttributeAdapter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ export class AttributeAdapter {
185185
);
186186
}
187187

188+
canGroupBy(): boolean {
189+
return Boolean(this.annotations.groupBy);
190+
}
191+
188192
isAggregationWhereField(): boolean {
189193
if (
190194
this.typeHelper.isList() ||

packages/graphql/src/schema-model/entity/model-adapters/ConcreteEntityAdapter.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ export class ConcreteEntityAdapter {
174174
return Array.from(this.attributes.values()).filter((attribute) => attribute.isAggregableField());
175175
}
176176

177+
public get groupByFields(): AttributeAdapter[] {
178+
return Array.from(this.attributes.values()).filter((attribute) => attribute.canGroupBy());
179+
}
180+
177181
public get aggregationWhereFields(): AttributeAdapter[] {
178182
return Array.from(this.attributes.values()).filter((attribute) => attribute.isAggregationWhereField());
179183
}

packages/graphql/src/schema-model/entity/model-adapters/ConcreteEntityOperations.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ export class ConcreteEntityOperations extends ImplementingEntityOperations<Concr
5555
return `${this.entityAdapter.name}Edge`;
5656
}
5757

58+
public getConnectionGroupByInputTypename(): string {
59+
return `${this.entityAdapter.name}GroupByInput`;
60+
}
61+
62+
public getConnectionGroupByTypename(): string {
63+
return `${this.entityAdapter.name}GroupByInput`;
64+
}
65+
5866
public get rootTypeFieldNames(): RootTypeFieldNames {
5967
return {
6068
...super.rootTypeFieldNames,

packages/graphql/src/schema-model/relationship/model-adapters/RelationshipBaseOperations.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ export abstract class RelationshipBaseOperations<T extends RelationshipAdapter |
122122
return `${this.prefixForTypenameWithInheritance}${ifUnionRelationshipTargetEntity?.name || ""}ConnectionWhere`;
123123
}
124124

125+
public getConnectionGroupByTypename(): string {
126+
return `${this.prefixForTypenameWithInheritance}GroupBy`;
127+
}
128+
125129
public getUpdateConnectionInputTypename(ifUnionRelationshipTargetEntity?: ConcreteEntityAdapter): string {
126130
return `${this.prefixForTypename}${ifUnionRelationshipTargetEntity?.name || ""}UpdateConnectionInput`;
127131
}

packages/graphql/src/schema/generation/augment-object-or-interface.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export function augmentObjectOrInterfaceTypeWithConnectionField(
106106
(directive) => directive.name.value === DEPRECATED
107107
)
108108
);
109+
109110
const composeNodeArgs: ObjectTypeComposerArgumentConfigMapDefinition = {
110111
where: makeConnectionWhereInputType({
111112
relationshipAdapter,
@@ -139,7 +140,7 @@ export function augmentObjectOrInterfaceTypeWithConnectionField(
139140
resolve: (source: any, args: ConnectionQueryArgs, _ctx: any, info: GraphQLResolveInfo) => {
140141
return connectionFieldResolver({
141142
connectionFieldName: relationshipAdapter.operations.connectionFieldName,
142-
args,
143+
args,
143144
info,
144145
source,
145146
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
20+
import type { InputTypeComposer, SchemaComposer } from "graphql-compose";
21+
import type { ConcreteEntityAdapter } from "../../schema-model/entity/model-adapters/ConcreteEntityAdapter";
22+
23+
export function makeConnectionGroupByInputType({
24+
entityAdapter,
25+
composer,
26+
}: {
27+
entityAdapter: ConcreteEntityAdapter;
28+
composer: SchemaComposer;
29+
}): InputTypeComposer | undefined {
30+
const typeName = entityAdapter.operations.getConnectionGroupByInputTypename();
31+
if (composer.has(typeName)) {
32+
return composer.getITC(typeName);
33+
}
34+
35+
const groupByFields = entityAdapter.groupByFields;
36+
37+
if (groupByFields.length === 0) {
38+
return undefined;
39+
}
40+
41+
const connectionGroupByITC = composer.createInputTC(typeName);
42+
for (const attribute of groupByFields) {
43+
connectionGroupByITC.addFields({
44+
[attribute.name]: "Boolean",
45+
});
46+
}
47+
48+
return connectionGroupByITC;
49+
}

packages/graphql/src/schema/resolvers/query/root-connection.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,17 @@ import {
2525
type GraphQLResolveInfo,
2626
type SelectionSetNode,
2727
} from "graphql";
28-
import type { InputTypeComposer, SchemaComposer } from "graphql-compose";
28+
import type { ObjectTypeComposerArgumentConfigDefinition, SchemaComposer } from "graphql-compose";
2929
import { PageInfo } from "../../../graphql/objects/PageInfo";
30-
import type { ConcreteEntityAdapter } from "../../../schema-model/entity/model-adapters/ConcreteEntityAdapter";
30+
import { ConcreteEntityAdapter } from "../../../schema-model/entity/model-adapters/ConcreteEntityAdapter";
3131
import type { InterfaceEntityAdapter } from "../../../schema-model/entity/model-adapters/InterfaceEntityAdapter";
3232
import { UnionEntityAdapter } from "../../../schema-model/entity/model-adapters/UnionEntityAdapter";
3333
import type { Neo4jGraphQLSchemaModel } from "../../../schema-model/Neo4jGraphQLSchemaModel";
3434
import { translateRead } from "../../../translate/translate-read";
3535
import { execute } from "../../../utils";
3636
import getNeo4jResolveTree from "../../../utils/get-neo4j-resolve-tree";
3737
import { isNeoInt } from "../../../utils/utils";
38+
import { makeConnectionGroupByInputType } from "../../generation/connection-group-by";
3839
import { makeSortInput } from "../../generation/sort-and-options-input";
3940
import { createConnectionWithEdgeProperties } from "../../pagination";
4041
import { graphqlDirectivesToCompose } from "../../to-compose";
@@ -120,20 +121,33 @@ export function rootConnectionResolver({
120121
});
121122
}
122123

124+
const args: Record<string, ObjectTypeComposerArgumentConfigDefinition> = {
125+
first: isLimitRequired ? new GraphQLNonNull(GraphQLInt) : GraphQLInt,
126+
after: GraphQLString,
127+
where: entityAdapter.operations.whereInputTypeName,
128+
};
129+
123130
// since sort is not created when there is nothing to sort, we check for its existence
124-
let sortArg: InputTypeComposer | undefined;
125131
if (!(entityAdapter instanceof UnionEntityAdapter)) {
126-
sortArg = makeSortInput({ entityAdapter, userDefinedFieldDirectives: new Map(), composer });
132+
const sortArg = makeSortInput({ entityAdapter, userDefinedFieldDirectives: new Map(), composer });
133+
if (sortArg) {
134+
args.sort = sortArg.NonNull.List;
135+
}
136+
}
137+
138+
if (entityAdapter instanceof ConcreteEntityAdapter) {
139+
const groupBy = makeConnectionGroupByInputType({
140+
composer,
141+
entityAdapter,
142+
});
143+
if (groupBy) {
144+
args.groupBy = groupBy;
145+
}
127146
}
128147

129148
return {
130149
type: rootConnection.NonNull,
131150
resolve,
132-
args: {
133-
first: isLimitRequired ? new GraphQLNonNull(GraphQLInt) : GraphQLInt,
134-
after: GraphQLString,
135-
where: entityAdapter.operations.whereInputTypeName,
136-
...(sortArg ? { sort: sortArg.NonNull.List } : {}),
137-
},
151+
args,
138152
};
139153
}

packages/graphql/tests/schema/directives/group-by.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ describe("@groupBy directive", () => {
123123
node: Movie!
124124
}
125125
126+
input MovieGroupByInput {
127+
title: Boolean
128+
}
129+
126130
\\"\\"\\"
127131
Fields to sort Movies by. The order in which sorts are applied is not guaranteed when specifying many fields in one MovieSort object.
128132
\\"\\"\\"
@@ -156,10 +160,6 @@ describe("@groupBy directive", () => {
156160
edges: [MovieEdge!]!
157161
}
158162
159-
input MovieGroupByInput {
160-
title: Boolean
161-
}
162-
163163
type Mutation {
164164
createMovies(input: [MovieCreateInput!]!): CreateMoviesMutationResponse!
165165
deleteMovies(where: MovieWhere): DeleteInfo!
@@ -176,7 +176,7 @@ describe("@groupBy directive", () => {
176176
177177
type Query {
178178
movies(limit: Int, offset: Int, sort: [MovieSort!], where: MovieWhere): [Movie!]!
179-
moviesConnection(after: String, first: Int, sort: [MovieSort!], where: MovieWhere, groupBy: MovieGroupByInput): MoviesConnection!
179+
moviesConnection(after: String, first: Int, groupBy: MovieGroupByInput, sort: [MovieSort!], where: MovieWhere): MoviesConnection!
180180
}
181181
182182
\\"\\"\\"An enum for sorting in either ascending or descending order.\\"\\"\\"

0 commit comments

Comments
 (0)