Skip to content

Commit c4082e9

Browse files
alvinthenKyleAMathews
authored andcommitted
✨ Support filtering on linked nodes (#3691)
* ✨ Support filtering on linked nodes 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 * ✅ Test filtering on linked nodes
1 parent 7d64a7c commit c4082e9

File tree

3 files changed

+108
-4
lines changed

3 files changed

+108
-4
lines changed

packages/gatsby/src/schema/__tests__/infer-graphql-input-type-test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,3 +483,81 @@ describe(`GraphQL Input args`, () => {
483483
expect(result).toMatchSnapshot()
484484
})
485485
})
486+
487+
describe(`filtering on linked nodes`, () => {
488+
let types
489+
beforeEach(() => {
490+
const { store } = require(`../../redux`)
491+
types = [
492+
{
493+
name: `Child`,
494+
nodeObjectType: new GraphQLObjectType({
495+
name: `Child`,
496+
fields: inferObjectStructureFromNodes({
497+
nodes: [{ id: `child_1`, hair: `brown`, height: 101 }],
498+
types: [{ name: `Child` }],
499+
}),
500+
}),
501+
},
502+
{
503+
name: `Pet`,
504+
nodeObjectType: new GraphQLObjectType({
505+
name: `Pet`,
506+
fields: inferObjectStructureFromNodes({
507+
nodes: [{ id: `pet_1`, species: `dog` }],
508+
types: [{ name: `Pet` }],
509+
}),
510+
}),
511+
},
512+
]
513+
514+
store.dispatch({
515+
type: `CREATE_NODE`,
516+
payload: { id: `child_1`, internal: { type: `Child` }, hair: `brown` },
517+
})
518+
store.dispatch({
519+
type: `CREATE_NODE`,
520+
payload: { id: `child_2`, internal: { type: `Child` }, hair: `blonde`, height: 101 },
521+
})
522+
store.dispatch({
523+
type: `CREATE_NODE`,
524+
payload: { id: `pet_1`, internal: { type: `Pet` }, species: `dog` },
525+
})
526+
})
527+
528+
it(`filters on linked nodes via id`, async () => {
529+
let result = await queryResult(
530+
[{ linked___NODE: `child_2`, foo: `bar` }, { linked___NODE: `child_1`, foo: `baz` }],
531+
`
532+
{
533+
allNode(filter: { linked: { hair: { eq: "blonde" } } }) {
534+
edges { node { linked { hair, height }, foo } }
535+
}
536+
}
537+
`,
538+
{ types }
539+
)
540+
expect(result.data.allNode.edges.length).toEqual(1)
541+
expect(result.data.allNode.edges[0].node.linked.hair).toEqual(`blonde`)
542+
expect(result.data.allNode.edges[0].node.linked.height).toEqual(101)
543+
expect(result.data.allNode.edges[0].node.foo).toEqual(`bar`)
544+
})
545+
546+
it(`returns all matching linked nodes`, async () => {
547+
let result = await queryResult(
548+
[{ linked___NODE: `child_2`, foo: `bar` }, { linked___NODE: `child_2`, foo: `baz` }],
549+
`
550+
{
551+
allNode(filter: { linked: { hair: { eq: "blonde" } } }) {
552+
edges { node { linked { hair, height }, foo } }
553+
}
554+
}
555+
`,
556+
{ types }
557+
)
558+
expect(result.data.allNode.edges[0].node.linked.hair).toEqual(`blonde`)
559+
expect(result.data.allNode.edges[0].node.linked.height).toEqual(101)
560+
expect(result.data.allNode.edges[0].node.foo).toEqual(`bar`)
561+
expect(result.data.allNode.edges[1].node.foo).toEqual(`baz`)
562+
})
563+
})

packages/gatsby/src/schema/infer-graphql-input-fields.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ const {
1919
isEmptyObjectOrArray,
2020
} = require(`./data-tree-utils`)
2121

22+
const { findLinkedNode } = require(`./infer-graphql-type`)
23+
const { getNodes } = require(`../redux`)
24+
2225
import type {
2326
GraphQLInputFieldConfig,
2427
GraphQLInputFieldConfigMap,
@@ -185,6 +188,8 @@ type InferInputOptions = {
185188
exampleValue?: Object,
186189
}
187190

191+
const linkedNodeCache = {}
192+
188193
export function inferInputObjectStructureFromNodes({
189194
nodes,
190195
typeName = ``,
@@ -196,13 +201,34 @@ export function inferInputObjectStructureFromNodes({
196201

197202
prefix = isRoot ? typeName : prefix
198203

199-
_.each(exampleValue, (value, key) => {
204+
_.each(exampleValue, (v, k) => {
205+
let value = v
206+
let key = k
200207
// Remove fields for traversing through nodes as we want to control
201208
// setting traversing up not try to automatically infer them.
202209
if (isRoot && EXCLUDE_KEYS[key]) return
203210

204-
// Input arguments on linked fields aren't currently supported
205-
if (_.includes(key, `___NODE`)) return
211+
if (_.includes(key, `___NODE`)) {
212+
// TODO: Union the objects in array
213+
const nodeToFind = _.isArray(value) ? value[0] : value
214+
const linkedNode = findLinkedNode(nodeToFind)
215+
216+
// Get from cache if found, else store into it
217+
if (linkedNodeCache[linkedNode.internal.type]) {
218+
value = linkedNodeCache[linkedNode.internal.type]
219+
} else {
220+
const relatedNodes = getNodes().filter(node => node.internal.type === linkedNode.internal.type)
221+
value = extractFieldExamples(relatedNodes)
222+
value = _.omitBy(value, (_v, _k) => _.includes(_k, `___NODE`))
223+
linkedNodeCache[linkedNode.internal.type] = value
224+
}
225+
226+
if (_.isArray(value)) {
227+
value = [value]
228+
}
229+
230+
;[key] = key.split(`___`)
231+
}
206232

207233
let field = inferGraphQLInputFields({
208234
nodes,

packages/gatsby/src/schema/infer-graphql-type.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ function inferFromMapping(
252252
}
253253
}
254254

255-
function findLinkedNode(value, linkedField, path) {
255+
export function findLinkedNode(value, linkedField, path) {
256256
let linkedNode
257257
// If the field doesn't link to the id, use that for searching.
258258
if (linkedField) {

0 commit comments

Comments
 (0)