From cbfc16e26e02d6876e769aa5c85fbf73986f5c24 Mon Sep 17 00:00:00 2001 From: Yao Bin Then Date: Thu, 25 Jan 2018 15:20:01 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20Support=20filtering=20on=20link?= =?UTF-8?q?ed=20nodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The filtering is done by extracting all the related nodes using `extractFieldExample`. The example values are then cached to save some time. (As of this patch, caching is not tested automatically) Further linking are disabled to avoid cyclic dependencies (input fields only). TODO: Filtering on nodes linked by mappings and File Note: If linking is done via array of IDs, only the structure of the first item is extracted. I'll be happy to add union support if someone can show me how to `$in` filter on an array of object. Related #3613 #3190 --- .../infer-graphql-input-type-test.js | 60 +++++++++++++++++++ .../src/schema/infer-graphql-input-fields.js | 32 +++++++++- .../gatsby/src/schema/infer-graphql-type.js | 2 +- 3 files changed, 90 insertions(+), 4 deletions(-) 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..624f9a4f2505c 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,63 @@ 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_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) { From 4f4e73c1be0c515b2d2844113863cfeaa2d84633 Mon Sep 17 00:00:00 2001 From: Yao Bin Then Date: Thu, 25 Jan 2018 19:17:48 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E2=9C=85=20Test=20filtering=20on=20linked?= =?UTF-8?q?=20nodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/infer-graphql-input-type-test.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 624f9a4f2505c..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 @@ -526,6 +526,24 @@ describe(`filtering on linked nodes`, () => { }) 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` }], `