Skip to content

Commit

Permalink
Merge pull request #6029 from darrellwarde/feature/sortable-directive
Browse files Browse the repository at this point in the history
Add `@sortable` directive
  • Loading branch information
darrellwarde authored Feb 28, 2025
1 parent 33ca95a commit f792a02
Show file tree
Hide file tree
Showing 11 changed files with 1,575 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-walls-grin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@neo4j/graphql": minor
---

Add a new field directive `@sortable` which can be used to configure whether results can be sorted by field values or not.
1 change: 1 addition & 0 deletions packages/graphql/src/graphql/directives/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export { relationshipPropertiesDirective } from "./relationship-properties";
export { relayIdDirective } from "./relay-id";
export { selectableDirective } from "./selectable";
export { settableDirective } from "./settable";
export { sortableDirective } from "./sortable";
export { subscriptionDirective } from "./subscription";
export { timestampDirective } from "./timestamp";
export { uniqueDirective } from "./unique";
Expand Down
33 changes: 33 additions & 0 deletions packages/graphql/src/graphql/directives/sortable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { DirectiveLocation, GraphQLBoolean, GraphQLDirective, GraphQLNonNull } from "graphql";

export const sortableDirective = new GraphQLDirective({
name: "sortable",
description: "Instructs @neo4j/graphql to generate sorting inputs for this field.",
locations: [DirectiveLocation.FIELD_DEFINITION],
args: {
byValue: {
description: "Generates sorting inputs for this field",
type: new GraphQLNonNull(GraphQLBoolean),
defaultValue: true,
},
},
});
4 changes: 4 additions & 0 deletions packages/graphql/src/schema-model/annotation/Annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { parsePopulatedByAnnotation } from "../parser/annotations-parser/populat
import { parseQueryAnnotation } from "../parser/annotations-parser/query-annotation";
import { parseSelectableAnnotation } from "../parser/annotations-parser/selectable-annotation";
import { parseSettableAnnotation } from "../parser/annotations-parser/settable-annotation";
import { parseSortableAnnotation } from "../parser/annotations-parser/sortable-annotation";
import { parseSubscriptionAnnotation } from "../parser/annotations-parser/subscription-annotation";
import { parseSubscriptionsAuthorizationAnnotation } from "../parser/annotations-parser/subscriptions-authorization-annotation";
import { parseTimestampAnnotation } from "../parser/annotations-parser/timestamp-annotation";
Expand All @@ -61,6 +62,7 @@ import type { QueryAnnotation } from "./QueryAnnotation";
import { RelayIDAnnotation } from "./RelayIDAnnotation";
import type { SelectableAnnotation } from "./SelectableAnnotation";
import type { SettableAnnotation } from "./SettableAnnotation";
import type { SortableAnnotation } from "./SortableAnnotation";
import type { SubscriptionAnnotation } from "./SubscriptionAnnotation";
import type { SubscriptionsAuthorizationAnnotation } from "./SubscriptionsAuthorizationAnnotation";
import type { TimestampAnnotation } from "./TimestampAnnotation";
Expand Down Expand Up @@ -99,6 +101,7 @@ export type Annotations = CheckAnnotationName<{
relayId: RelayIDAnnotation;
selectable: SelectableAnnotation;
settable: SettableAnnotation;
sortable: SortableAnnotation;
subscription: SubscriptionAnnotation;
subscriptionsAuthorization: SubscriptionsAuthorizationAnnotation;
timestamp: TimestampAnnotation;
Expand Down Expand Up @@ -131,6 +134,7 @@ export const annotationsParsers: { [key in keyof Annotations]: AnnotationParser<
limit: parseLimitAnnotation,
selectable: parseSelectableAnnotation,
settable: parseSettableAnnotation,
sortable: parseSortableAnnotation,
subscription: parseSubscriptionAnnotation,
subscriptionsAuthorization: parseSubscriptionsAuthorizationAnnotation,
timestamp: parseTimestampAnnotation,
Expand Down
29 changes: 29 additions & 0 deletions packages/graphql/src/schema-model/annotation/SortableAnnotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type { Annotation } from "./Annotation";

export class SortableAnnotation implements Annotation {
readonly name = "sortable";
public readonly byValue: boolean;

constructor({ byValue }: { byValue: boolean }) {
this.byValue = byValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ export class AttributeAdapter {

isSortableField(): boolean {
return (
this.isSortable() &&
!this.typeHelper.isList() &&
!this.isCustomResolvable() &&
(this.typeHelper.isScalar() || this.typeHelper.isEnum() || this.typeHelper.isSpatial()) &&
Expand Down Expand Up @@ -193,7 +194,7 @@ export class AttributeAdapter {
isAggregableField(): boolean {
return (
!this.typeHelper.isList() &&
//uncomment me on 7.x !this.typeHelper.isID() &&
//uncomment me on 7.x !this.typeHelper.isID() &&
(this.typeHelper.isScalar() || this.typeHelper.isEnum()) &&
this.isAggregable()
);
Expand Down Expand Up @@ -347,6 +348,10 @@ export class AttributeAdapter {
return this.annotations.filterable?.byValue !== false;
}

isSortable(): boolean {
return this.annotations.sortable?.byValue !== false;
}

isCustomResolvable(): boolean {
return !!this.annotations.customResolver;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/graphql/src/schema-model/library-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Annotations } from "./annotation/Annotation";
import { annotationsParsers } from "./annotation/Annotation";
import type { DEPRECATED } from "../constants";
import { SHAREABLE } from "../constants";
import type { ValueOf } from "../utils/value-of";
import type { Annotations } from "./annotation/Annotation";
import { annotationsParsers } from "./annotation/Annotation";

const additionalDirectives = [
"alias",
Expand All @@ -43,6 +43,7 @@ export const SCHEMA_CONFIGURATION_FIELD_DIRECTIVES = [
"filterable",
"selectable",
"settable",
"sortable",
] as const satisfies readonly LibraryDirectives[];

export const FIELD_DIRECTIVES = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { makeDirectiveNode } from "@graphql-tools/utils";
import { sortableDirective } from "../../../graphql/directives";
import { parseSortableAnnotation } from "./sortable-annotation";

const tests = [
{
name: "should parse correctly when byValue is true",
directive: makeDirectiveNode(
"sortable",
{
byValue: true,
},
sortableDirective
),
expected: {
byValue: true,
},
},
{
name: "should parse correctly when byValue is false",
directive: makeDirectiveNode(
"sortable",
{
byValue: false,
},
sortableDirective
),
expected: {
byValue: false,
},
},
];

describe("parseSortableAnnotation", () => {
tests.forEach((test) => {
it(`${test.name}`, () => {
const sortableAnnotation = parseSortableAnnotation(test.directive);
expect(sortableAnnotation.byValue).toBe(test.expected.byValue);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DirectiveNode } from "graphql";
import { sortableDirective } from "../../../graphql/directives";
import { SortableAnnotation } from "../../annotation/SortableAnnotation";
import { parseArguments } from "../parse-arguments";

export function parseSortableAnnotation(directive: DirectiveNode): SortableAnnotation {
const { byValue } = parseArguments<{
byValue: boolean;
}>(sortableDirective, directive);

return new SortableAnnotation({
byValue,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const invalidFieldCombinations: InvalidFieldCombinations = {
"filterable",
"settable",
"selectable",
"sortable",
],
cypher: ["jwtClaim", "alias", "id", "relationship", "unique"],
default: ["jwtClaim", "populatedBy", "relationship"],
Expand Down Expand Up @@ -71,6 +72,7 @@ export const invalidFieldCombinations: InvalidFieldCombinations = {
selectable: ["jwtClaim", "customResolver"],
settable: ["jwtClaim", "customResolver"],
filterable: ["jwtClaim", "customResolver"],
sortable: ["jwtClaim", "customResolver"],
declareRelationship: ["jwtClaim"],
};

Expand Down
Loading

0 comments on commit f792a02

Please sign in to comment.