Skip to content

Commit

Permalink
✨ Support filtering on linked nodes
Browse files Browse the repository at this point in the history
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 gatsbyjs#3613 gatsbyjs#3190
  • Loading branch information
alvinthen committed Jan 25, 2018
1 parent 6c440f6 commit cbfc16e
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
})
})
32 changes: 29 additions & 3 deletions packages/gatsby/src/schema/infer-graphql-input-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ const {
isEmptyObjectOrArray,
} = require(`./data-tree-utils`)

const { findLinkedNode } = require(`./infer-graphql-type`)
const { getNodes } = require(`../redux`)

import type {
GraphQLInputFieldConfig,
GraphQLInputFieldConfigMap,
Expand Down Expand Up @@ -185,6 +188,8 @@ type InferInputOptions = {
exampleValue?: Object,
}

const linkedNodeCache = {}

export function inferInputObjectStructureFromNodes({
nodes,
typeName = ``,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/src/schema/infer-graphql-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit cbfc16e

Please sign in to comment.