From 3fed1c2d3f2d44f8aab5b345d3f4be1bfe2cced6 Mon Sep 17 00:00:00 2001 From: Kyle Mathews Date: Mon, 19 Jun 2017 20:18:05 -0700 Subject: [PATCH] An array of linked nodes linking to heterogeneous node types is now converted to a union type (#1211) --- .../__tests__/infer-graphql-type-test.js | 41 ++++++++++ packages/gatsby/src/schema/data-tree-utils.js | 6 ++ .../gatsby/src/schema/infer-graphql-type.js | 75 +++++++++++++------ packages/gatsby/src/schema/run-sift.js | 1 + 4 files changed, 101 insertions(+), 22 deletions(-) diff --git a/packages/gatsby/src/schema/__tests__/infer-graphql-type-test.js b/packages/gatsby/src/schema/__tests__/infer-graphql-type-test.js index 50cc8f62a3809..1637917829a9c 100644 --- a/packages/gatsby/src/schema/__tests__/infer-graphql-type-test.js +++ b/packages/gatsby/src/schema/__tests__/infer-graphql-type-test.js @@ -190,6 +190,16 @@ describe(`GraphQL type inferance`, () => { }), }), }, + { + name: `Pet`, + nodeObjectType: new GraphQLObjectType({ + name: `Pet`, + fields: inferObjectStructureFromNodes({ + nodes: [{ id: `pet_1`, species: `dog` }], + types: [{ name: `Pet` }], + }), + }), + }, ] store.dispatch({ @@ -200,6 +210,10 @@ describe(`GraphQL type inferance`, () => { type: `CREATE_NODE`, payload: { id: `child_2`, internal: { type: `Child` }, hair: `blonde` }, }) + store.dispatch({ + type: `CREATE_NODE`, + payload: { id: `pet_1`, internal: { type: `Pet` }, species: `dog` }, + }) }) it(`Links nodes`, async () => { @@ -261,6 +275,33 @@ describe(`GraphQL type inferance`, () => { `"Bar" available to link to this node.` ) }) + + it(`Creates union types when an array field is linking to multiple node types`, async () => { + let result = await queryResult( + [{ linked___NODE: [`child_1`, `pet_1`] }], + ` + linked { + __typename + ... on Child { + hair + } + ... on Pet { + species + } + } + `, + { types } + ) + expect(result.errors).not.toBeDefined() + expect(result.data.listNode[0].linked[0].hair).toEqual(`brown`) + expect(result.data.listNode[0].linked[0].__typename).toEqual(`Child`) + expect(result.data.listNode[0].linked[1].species).toEqual(`dog`) + expect(result.data.listNode[0].linked[1].__typename).toEqual(`Pet`) + store.dispatch({ + type: `CREATE_NODE`, + payload: { id: `baz`, internal: { type: `Bar` } }, + }) + }) }) it(`Infers graphql type from array of nodes`, () => diff --git a/packages/gatsby/src/schema/data-tree-utils.js b/packages/gatsby/src/schema/data-tree-utils.js index e4994ffd47584..94f528412ea87 100644 --- a/packages/gatsby/src/schema/data-tree-utils.js +++ b/packages/gatsby/src/schema/data-tree-utils.js @@ -71,6 +71,12 @@ const extractFieldExamples = (nodes: any[]) => if (!array.length) return null if (!areAllSameType(array)) return INVALID_VALUE + // Linked node arrays don't get reduced further as we + // want to preserve all the linked node types. + if (_.includes(key, `___NODE`)) { + return array + } + // primitive values don't get merged further, just take the first item if (!_.isObject(array[0])) return array.slice(0, 1) let merged = extractFieldExamples(array) diff --git a/packages/gatsby/src/schema/infer-graphql-type.js b/packages/gatsby/src/schema/infer-graphql-type.js index 2d0ca7c1776f0..99aaed3268a9e 100644 --- a/packages/gatsby/src/schema/infer-graphql-type.js +++ b/packages/gatsby/src/schema/infer-graphql-type.js @@ -6,6 +6,7 @@ const { GraphQLFloat, GraphQLInt, GraphQLList, + GraphQLUnionType, } = require(`graphql`) const _ = require(`lodash`) const invariant = require(`invariant`) @@ -246,37 +247,63 @@ function findLinkedNode(value, linkedField, path) { function inferFromFieldName(value, selector, types): GraphQLFieldConfig<*, *> { let isArray = false if (_.isArray(value)) { - value = value[0] isArray = true + // Reduce values to nodes with unique types. + value = _.uniqBy(value, v => getNode(v).internal.type) } + const key = selector.split(`.`).pop() const [, , linkedField] = key.split(`___`) - const linkedNode = findLinkedNode(value, linkedField) + const validateLinkedNode = linkedNode => { + invariant( + linkedNode, + oneLine` + Encountered an error trying to infer a GraphQL type for: "${selector}". + There is no corresponding node with the ${linkedField || `id`} + field matching: "${value}" + ` + ) + } + const validateField = (linkedNode, field) => { + invariant( + field, + oneLine` + Encountered an error trying to infer a GraphQL type for: "${selector}". + There is no corresponding GraphQL type "${linkedNode.internal + .type}" available + to link to this node. + ` + ) + } - invariant( - linkedNode, - oneLine` - Encountered an error trying to infer a GraphQL type for: "${selector}". - There is no corresponding node with the ${linkedField || `id`} - field matching: "${value}" - ` - ) - const field = types.find(type => type.name === linkedNode.internal.type) - - invariant( - field, - oneLine` - Encountered an error trying to infer a GraphQL type for: "${selector}". - There is no corresponding GraphQL type "${linkedNode.internal - .type}" available - to link to this node. - ` - ) + const findNodeType = node => + types.find(type => type.name === node.internal.type) if (isArray) { + const linkedNodes = value.map(v => findLinkedNode(v)) + linkedNodes.forEach(node => validateLinkedNode(node)) + const fields = linkedNodes.map(node => findNodeType(node)) + fields.forEach((field, i) => validateField(linkedNodes[i], field)) + + let type + // If there's more than one type, we'll create a union type. + if (fields.length > 1) { + type = new GraphQLUnionType({ + name: `Union_${key}_${fields.map(f => f.name).join(`__`)}`, + description: `Union interface for the field "${key}" for types [${fields + .map(f => f.name) + .join(`, `)}]`, + types: fields.map(f => f.nodeObjectType), + resolveType: data => + fields.find(f => f.name == data.internal.type).nodeObjectType, + }) + } else { + type = fields[0].nodeObjectType + } + return { - type: new GraphQLList(field.nodeObjectType), + type: new GraphQLList(type), resolve: (node, a, b = {}) => { let fieldValue = node[key] if (fieldValue) { @@ -290,6 +317,10 @@ function inferFromFieldName(value, selector, types): GraphQLFieldConfig<*, *> { } } + const linkedNode = findLinkedNode(value, linkedField) + validateLinkedNode(linkedNode) + const field = findNodeType(linkedNode) + validateField(linkedNode, field) return { type: field.nodeObjectType, resolve: (node, a, b = {}) => { diff --git a/packages/gatsby/src/schema/run-sift.js b/packages/gatsby/src/schema/run-sift.js index 844fad03f3025..76654b068f0a8 100644 --- a/packages/gatsby/src/schema/run-sift.js +++ b/packages/gatsby/src/schema/run-sift.js @@ -77,5 +77,6 @@ module.exports = ({ args, nodes, connection = false, path = `` }) => { path, nodeId: result[0].id, }) + return result[0] }