diff --git a/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-test.js b/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-test.js index dd11b0e4448d9..4485e13a34508 100644 --- a/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-test.js +++ b/packages/gatsby/src/schema/__tests__/infer-graphql-input-type-test.js @@ -483,3 +483,81 @@ describe(`GraphQL Input args`, () => { expect(result).toMatchSnapshot() }) }) + +describe(`filtering on linked nodes`, () => { + let types + beforeEach(() => { + const { store } = require(`../../redux`) + types = [ + { + name: `Child`, + nodeObjectType: new GraphQLObjectType({ + name: `Child`, + fields: inferObjectStructureFromNodes({ + nodes: [{ id: `child_1`, hair: `brown`, height: 101 }], + types: [{ name: `Child` }], + }), + }), + }, + { + name: `Pet`, + nodeObjectType: new GraphQLObjectType({ + name: `Pet`, + fields: inferObjectStructureFromNodes({ + nodes: [{ id: `pet_1`, species: `dog` }], + types: [{ name: `Pet` }], + }), + }), + }, + ] + + store.dispatch({ + type: `CREATE_NODE`, + payload: { id: `child_1`, internal: { type: `Child` }, hair: `brown` }, + }) + store.dispatch({ + type: `CREATE_NODE`, + payload: { id: `child_2`, internal: { type: `Child` }, hair: `blonde`, height: 101 }, + }) + store.dispatch({ + type: `CREATE_NODE`, + payload: { id: `pet_1`, internal: { type: `Pet` }, species: `dog` }, + }) + }) + + it(`filters on linked nodes via id`, async () => { + let result = await queryResult( + [{ linked___NODE: `child_2`, foo: `bar` }, { linked___NODE: `child_1`, foo: `baz` }], + ` + { + allNode(filter: { linked: { hair: { eq: "blonde" } } }) { + edges { node { linked { hair, height }, foo } } + } + } + `, + { types } + ) + expect(result.data.allNode.edges.length).toEqual(1) + expect(result.data.allNode.edges[0].node.linked.hair).toEqual(`blonde`) + expect(result.data.allNode.edges[0].node.linked.height).toEqual(101) + expect(result.data.allNode.edges[0].node.foo).toEqual(`bar`) + }) + + it(`returns all matching linked nodes`, async () => { + let result = await queryResult( + [{ linked___NODE: `child_2`, foo: `bar` }, { linked___NODE: `child_2`, foo: `baz` }], + ` + { + allNode(filter: { linked: { hair: { eq: "blonde" } } }) { + edges { node { linked { hair, height }, foo } } + } + } + `, + { types } + ) + expect(result.data.allNode.edges[0].node.linked.hair).toEqual(`blonde`) + expect(result.data.allNode.edges[0].node.linked.height).toEqual(101) + expect(result.data.allNode.edges[0].node.foo).toEqual(`bar`) + expect(result.data.allNode.edges[1].node.foo).toEqual(`baz`) + }) +}) diff --git a/packages/gatsby/src/schema/infer-graphql-input-fields.js b/packages/gatsby/src/schema/infer-graphql-input-fields.js index fea138023e433..b206be6a78919 100644 --- a/packages/gatsby/src/schema/infer-graphql-input-fields.js +++ b/packages/gatsby/src/schema/infer-graphql-input-fields.js @@ -19,6 +19,9 @@ const { isEmptyObjectOrArray, } = require(`./data-tree-utils`) +const { findLinkedNode } = require(`./infer-graphql-type`) +const { getNodes } = require(`../redux`) + import type { GraphQLInputFieldConfig, GraphQLInputFieldConfigMap, @@ -185,6 +188,8 @@ type InferInputOptions = { exampleValue?: Object, } +const linkedNodeCache = {} + export function inferInputObjectStructureFromNodes({ nodes, typeName = ``, @@ -196,13 +201,34 @@ export function inferInputObjectStructureFromNodes({ prefix = isRoot ? typeName : prefix - _.each(exampleValue, (value, key) => { + _.each(exampleValue, (v, k) => { + let value = v + let key = k // Remove fields for traversing through nodes as we want to control // setting traversing up not try to automatically infer them. if (isRoot && EXCLUDE_KEYS[key]) return - // Input arguments on linked fields aren't currently supported - if (_.includes(key, `___NODE`)) return + if (_.includes(key, `___NODE`)) { + // TODO: Union the objects in array + const nodeToFind = _.isArray(value) ? value[0] : value + const linkedNode = findLinkedNode(nodeToFind) + + // Get from cache if found, else store into it + if (linkedNodeCache[linkedNode.internal.type]) { + value = linkedNodeCache[linkedNode.internal.type] + } else { + const relatedNodes = getNodes().filter(node => node.internal.type === linkedNode.internal.type) + value = extractFieldExamples(relatedNodes) + value = _.omitBy(value, (_v, _k) => _.includes(_k, `___NODE`)) + linkedNodeCache[linkedNode.internal.type] = value + } + + if (_.isArray(value)) { + value = [value] + } + + ;[key] = key.split(`___`) + } let field = inferGraphQLInputFields({ nodes, diff --git a/packages/gatsby/src/schema/infer-graphql-type.js b/packages/gatsby/src/schema/infer-graphql-type.js index 22a400c24031d..34b5bf75db102 100644 --- a/packages/gatsby/src/schema/infer-graphql-type.js +++ b/packages/gatsby/src/schema/infer-graphql-type.js @@ -252,7 +252,7 @@ function inferFromMapping( } } -function findLinkedNode(value, linkedField, path) { +export function findLinkedNode(value, linkedField, path) { let linkedNode // If the field doesn't link to the id, use that for searching. if (linkedField) {