Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/enum relationship properties #253

Merged
11 changes: 3 additions & 8 deletions packages/graphql/src/classes/Relationship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,10 @@
* limitations under the License.
*/

import type { PrimitiveField, DateTimeField, PointField } from "../types";
import type { PrimitiveField, DateTimeField, PointField, CustomEnumField } from "../types";

// export interface NodeConstructor {
// otherDirectives: DirectiveNode[];
// ignoredFields: BaseField[];
// }

// TODO do CustomScalarField and CustomEnumField need to be in the mix?
export type RelationshipField = PrimitiveField | DateTimeField | PointField;
// TODO does CustomScalarField need to be in the mix?
export type RelationshipField = PrimitiveField | DateTimeField | PointField | CustomEnumField;

export interface RelationshipConstructor {
name: string;
Expand Down
1 change: 1 addition & 0 deletions packages/graphql/src/schema/get-obj-field-meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ function getObjFieldMeta({
}

const enumField: CustomEnumField = {
kind: "Enum",
...baseField,
};
res.enumFields.push(enumField);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("getRelationshipFieldMeta", () => {
(d) => d.kind === "InterfaceTypeDefinition"
) as InterfaceTypeDefinitionNode;

const fieldMeta = getRelationshipFieldMeta({ relationship });
const fieldMeta = getRelationshipFieldMeta({ relationship, enums: [] });

expect(fieldMeta).toEqual([
{
Expand Down
393 changes: 176 additions & 217 deletions packages/graphql/src/schema/get-relationship-field-meta.ts

Large diffs are not rendered by default.

40 changes: 21 additions & 19 deletions packages/graphql/src/schema/make-augmented-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
import pluralize from "pluralize";
import { Node, Exclude } from "../classes";
import getAuth from "./get-auth";
import { PrimitiveField, Auth } from "../types";
import { PrimitiveField, Auth, CustomEnumField } from "../types";
import { upperFirstLetter } from "../utils";
import { findResolver, createResolver, deleteResolver, cypherResolver, updateResolver } from "./resolvers";
import checkNodeImplementsInterfaces from "./check-node-implements-interfaces";
Expand Down Expand Up @@ -139,6 +139,22 @@ function makeAugmentedSchema(

const relationshipPropertyInterfaceNames = new Set<string>();

const extraDefinitions = [
...enums,
...scalars,
...directives,
...inputs,
...unions,
...([
customResolvers.customQuery,
customResolvers.customMutation,
customResolvers.customSubscription,
] as ObjectTypeDefinitionNode[]),
].filter(Boolean) as DefinitionNode[];
if (extraDefinitions.length) {
composer.addTypeDefs(print({ kind: "Document", definitions: extraDefinitions }));
}

const nodes = objectNodes.map((definition) => {
if (definition.name.value === "PageInfo") {
throw new Error(
Expand Down Expand Up @@ -222,7 +238,7 @@ function makeAugmentedSchema(
const relationshipFields = new Map<string, RelationshipField[]>();

relationshipProperties.forEach((relationship) => {
const relationshipFieldMeta = getRelationshipFieldMeta({ relationship });
const relationshipFieldMeta = getRelationshipFieldMeta({ relationship, enums });

if (!pointInTypeDefs) {
pointInTypeDefs = relationshipFieldMeta.some((field) => field.typeMeta.name === "Point");
Expand Down Expand Up @@ -275,7 +291,9 @@ function makeAugmentedSchema(
typeName: relationship.name.value,
fields: {
scalarFields: [],
enumFields: [],
enumFields: relationshipFieldMeta.filter(
(f) => (f as CustomEnumField).kind === "Enum"
) as CustomEnumField[],
dateTimeFields: relationshipFieldMeta.filter((f) => f.typeMeta.name === "DateTime"),
pointFields: relationshipFieldMeta.filter((f) => ["Point", "CartesianPoint"].includes(f.typeMeta.name)),
primitiveFields: relationshipFieldMeta.filter((f) =>
Expand Down Expand Up @@ -1046,22 +1064,6 @@ function makeAugmentedSchema(
}
});

const extraDefinitions = [
...enums,
...scalars,
...directives,
...inputs,
...unions,
...([
customResolvers.customQuery,
customResolvers.customMutation,
customResolvers.customSubscription,
] as ObjectTypeDefinitionNode[]),
].filter(Boolean) as DefinitionNode[];
if (extraDefinitions.length) {
composer.addTypeDefs(print({ kind: "Document", definitions: extraDefinitions }));
}

interfaces.forEach((inter) => {
const objectFields = getObjFieldMeta({ obj: inter, scalars, enums, interfaces, unions, objects: objectNodes });

Expand Down
5 changes: 4 additions & 1 deletion packages/graphql/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,10 @@ export interface PrimitiveField extends BaseField {

export type CustomScalarField = BaseField;

export type CustomEnumField = BaseField;
export interface CustomEnumField extends BaseField {
// TODO Must be "Enum" - really needs refactoring into classes
kind: string;
}

export interface UnionField extends BaseField {
nodes?: string[];
Expand Down
138 changes: 138 additions & 0 deletions packages/graphql/tests/integration/connections/enums.int.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* 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 { Driver } from "neo4j-driver";
import { graphql } from "graphql";
import { generate } from "randomstring";
import neo4j from "../neo4j";
import { Neo4jGraphQL } from "../../../src/classes";

describe("Enum Relationship Properties", () => {
let driver: Driver;

beforeAll(async () => {
driver = await neo4j();
});

afterAll(async () => {
await driver.close();
});

test("should create a movie and an actor, with an enum as a relationship property", async () => {
const session = driver.session();

const roleTypeResolver = {
LEADING: "Leading",
SUPPORTING: "Supporting",
};

const typeDefs = `
type Actor {
name: String!
movies: [Movie] @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn")
}

type Movie {
title: String!
actors: [Actor]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

enum RoleType {
LEADING
SUPPORTING
}

interface ActedIn {
roleType: RoleType!
}
`;

const neoSchema = new Neo4jGraphQL({
typeDefs,
resolvers: { RoleType: roleTypeResolver },
});

const title = generate({
charset: "alphabetic",
});

const name = generate({
charset: "alphabetic",
});

const create = `
mutation CreateMovies($title: String!, $name: String!) {
createMovies(
input: [
{
title: $title
actors: {
create: [
{
properties: { roleType: LEADING }
node: { name: $name }
}
]
}
}
]
) {
movies {
title
actorsConnection {
edges {
roleType
node {
name
}
}
}
}
}
}
`;

try {
const gqlResult = await graphql({
schema: neoSchema.schema,
source: create,
contextValue: { driver },
variableValues: { title, name },
});

expect(gqlResult.errors).toBeFalsy();

expect(gqlResult.data).toEqual({
createMovies: {
movies: [{ title, actorsConnection: { edges: [{ roleType: "LEADING", node: { name } }] } }],
},
});

const result = await session.run(`
MATCH (m:Movie)<-[ai:ACTED_IN]-(a:Actor)
WHERE m.title = "${title}" AND a.name = "${name}"
RETURN ai
`);

expect((result.records[0].toObject() as any).ai.properties).toEqual({ roleType: "Leading" });
} finally {
await session.close();
}
});
});
Loading