Skip to content

Commit

Permalink
store inputobjects for root node types, skip invalid_value from examp…
Browse files Browse the repository at this point in the history
…le values, add support for proper filtering using mapped fields
  • Loading branch information
pieh committed Oct 14, 2018
1 parent 44ed045 commit ccd110c
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 77 deletions.
29 changes: 14 additions & 15 deletions packages/gatsby/src/schema/__tests__/data-tree-utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const {
getExampleValues,
buildFieldEnumValues,
clearTypeExampleValues,
INVALID_VALUE,
} = require(`../data-tree-utils`)
const {
typeConflictReporter,
Expand Down Expand Up @@ -137,14 +136,14 @@ describe(`Gatsby data tree utils`, () => {
let example = getExampleValues({
nodes: [{ foo: null }, { foo: [1] }, { foo: { field: 1 } }],
})
expect(example.foo).toBe(INVALID_VALUE)
expect(example.foo).toBe(undefined)
})

it(`handles polymorphic arrays`, () => {
let example = getExampleValues({
nodes: [{ foo: [[`foo`, `bar`]] }, { foo: [{ field: 1 }] }],
})
expect(example.foo).toBe(INVALID_VALUE)
expect(example.foo).toBe(undefined)
})

it(`doesn't confuse empty fields for polymorhpic ones`, () => {
Expand Down Expand Up @@ -236,7 +235,7 @@ describe(`Gatsby data tree utils`, () => {
{ date: `2017-01-12T18:13:38.326Z` },
],
})
expect(example.date).not.toBe(INVALID_VALUE)
expect(example.date).not.toBe(undefined)

// should be invalid (string is not a date)
example = getExampleValues({
Expand All @@ -245,7 +244,7 @@ describe(`Gatsby data tree utils`, () => {
{ date: `This is not a date!!!!!!` },
],
})
expect(example.date).toBe(INVALID_VALUE)
expect(example.date).toBe(undefined)

// should be valid - reversed order
example = getExampleValues({
Expand All @@ -254,7 +253,7 @@ describe(`Gatsby data tree utils`, () => {
{ date: new Date(`2017-12-01T14:59:45.600Z`) },
],
})
expect(example.date).not.toBe(INVALID_VALUE)
expect(example.date).not.toBe(undefined)

// should be invalid (string is not a date) - reversed order
example = getExampleValues({
Expand All @@ -263,7 +262,7 @@ describe(`Gatsby data tree utils`, () => {
{ date: new Date(`2017-12-01T14:59:45.600Z`) },
],
})
expect(example.date).toBe(INVALID_VALUE)
expect(example.date).toBe(undefined)
})

it(`handles arrays with mix of date strings and date objects`, () => {
Expand All @@ -276,7 +275,7 @@ describe(`Gatsby data tree utils`, () => {
{ dates: [`2017-01-12T18:13:38.326Z`] },
],
})
expect(example.dates).not.toBe(INVALID_VALUE)
expect(example.dates).not.toBe(undefined)

// should be invalid - separate arrays of unique types (string is not a date)
example = getExampleValues({
Expand All @@ -285,7 +284,7 @@ describe(`Gatsby data tree utils`, () => {
{ dates: [`This is not a date!!!!!!`] },
],
})
expect(example.dates).toBe(INVALID_VALUE)
expect(example.dates).toBe(undefined)

// should be valid - single array of mixed types
example = getExampleValues({
Expand All @@ -298,7 +297,7 @@ describe(`Gatsby data tree utils`, () => {
},
],
})
expect(example.dates).not.toBe(INVALID_VALUE)
expect(example.dates).not.toBe(undefined)

// should be invalid - single array of mixed types (string is not a date)
example = getExampleValues({
Expand All @@ -311,7 +310,7 @@ describe(`Gatsby data tree utils`, () => {
},
],
})
expect(example.dates).toBe(INVALID_VALUE)
expect(example.dates).toBe(undefined)

// should be valid - separate arrays of both unique types and mixed types
example = getExampleValues({
Expand All @@ -326,7 +325,7 @@ describe(`Gatsby data tree utils`, () => {
{ dates: [`2017-01-12T18:13:38.326Z`] },
],
})
expect(example.dates).not.toBe(INVALID_VALUE)
expect(example.dates).not.toBe(undefined)

// should be valid - separate arrays of both unique types and mixed types (string is not a date) #1
example = getExampleValues({
Expand All @@ -341,7 +340,7 @@ describe(`Gatsby data tree utils`, () => {
{ dates: [`2017-01-12T18:13:38.326Z`] },
],
})
expect(example.dates).toBe(INVALID_VALUE)
expect(example.dates).toBe(undefined)

// should be valid - separate arrays of both unique types and mixed types (string is not a date) #2
example = getExampleValues({
Expand All @@ -356,7 +355,7 @@ describe(`Gatsby data tree utils`, () => {
{ dates: [`This is not a date!!!!!!`] },
],
})
expect(example.dates).toBe(INVALID_VALUE)
expect(example.dates).toBe(undefined)

// should be valid - separate arrays of both unique types and mixed types (string is not a date) #2
example = getExampleValues({
Expand All @@ -371,7 +370,7 @@ describe(`Gatsby data tree utils`, () => {
{ dates: [`This is not a date!!!!!!`] },
],
})
expect(example.dates).toBe(INVALID_VALUE)
expect(example.dates).toBe(undefined)
})
})

Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/src/schema/build-node-connections.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = (types: any) => {

const inferredInputFieldsFromNodes = inferInputObjectStructureFromNodes({
nodes,
typeName,
typeName: type.name,
})

const inferredInputFieldsFromPlugins = inferInputObjectStructureFromFields({
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby/src/schema/data-tree-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ const extractFieldExamples = (
})

const value = extractFromEntries(entries, nextSelector, key)
if (!isDefined(value)) continue
if (!isDefined(value) || isEmptyObjectOrArray(value)) continue

example[key] = value
}
Expand Down
146 changes: 86 additions & 60 deletions packages/gatsby/src/schema/infer-graphql-input-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const {
} = require(`./data-tree-utils`)

const { findLinkedNode } = require(`./infer-graphql-type`)
const { getNodes } = require(`../redux`)
const { getNodes, store } = require(`../redux`)
const is32BitInteger = require(`../utils/is-32-bit-integer`)

import type {
Expand Down Expand Up @@ -77,6 +77,8 @@ function inferGraphQLInputFields({
value,
nodes,
prefix,
typeName,
selector,
}): ?GraphQLInputFieldConfig {
if (value == null || isEmptyObjectOrArray(value)) return null

Expand Down Expand Up @@ -110,6 +112,8 @@ function inferGraphQLInputFields({
value: headValue,
prefix,
nodes,
selector,
typeName,
})
invariant(
inferredField,
Expand Down Expand Up @@ -169,20 +173,18 @@ function inferGraphQLInputFields({
}
}
case `object`: {
const fields = inferInputObjectStructureFromNodes({
nodes,
prefix,
exampleValue: value,
}).inferredFields
if (!_.isEmpty(fields)) {
return {
type: new GraphQLInputObjectType({
name: createTypeName(`${prefix}InputObject`),
fields,
}),
}
} else {
return null
return {
type: new GraphQLInputObjectType({
name: createTypeName(`${prefix}InputObject`),
fields: () =>
inferInputObjectStructureFromNodes({
nodes,
prefix,
exampleValue: value,
selector,
typeName,
}).inferredFields,
}),
}
}
case `number`: {
Expand Down Expand Up @@ -219,35 +221,60 @@ type InferInputOptions = {
exampleValue?: Object,
}

const recursiveOmitBy = (value, fn) => {
if (_.isObject(value)) {
if (_.isPlainObject(value)) {
value = _.omitBy(value, fn)
} else if (_.isArray(value)) {
// don't mutate original value
value = _.clone(value)
}
_.each(value, (v, k) => {
value[k] = recursiveOmitBy(v, fn)
})
if (_.isEmpty(value)) {
// don't return empty objects - gatsby doesn't support these
return null
}
// const recursiveOmitBy = (value, fn) => {
// if (_.isObject(value)) {
// if (_.isPlainObject(value)) {
// value = _.omitBy(value, fn)
// } else if (_.isArray(value)) {
// // don't mutate original value
// value = _.clone(value)
// }
// _.each(value, (v, k) => {
// value[k] = recursiveOmitBy(v, fn)
// })
// if (_.isEmpty(value)) {
// // don't return empty objects - gatsby doesn't support these
// return null
// }
// }
// return value
// }

const RootNodeInputObjectMap = new Map()
const getRootNodeInputObject = ({ typeName, isArray }) => {
const mapKey = `${typeName}_${isArray ? `[]` : ``}`
if (RootNodeInputObjectMap.has(mapKey)) {
return RootNodeInputObjectMap.get(mapKey)
}
return value
}

const linkedNodeCache = {}
const nodes = getNodes().filter(node => node.internal.type === typeName)
const exampleValue = getExampleValues({
nodes,
typeName,
})

const field = inferGraphQLInputFields({
nodes,
value: isArray ? [exampleValue] : exampleValue,
prefix: `Linked${typeName}${isArray ? `Array` : `Single`}`,
typeName,
selector: ``,
})
RootNodeInputObjectMap.set(mapKey, field)
return field
}

export function inferInputObjectStructureFromNodes({
nodes,
typeName = ``,
prefix = ``,
selector = ``,
exampleValue = null,
}: InferInputOptions): Object {
const inferredFields = {}
const isRoot = !prefix
const config = store.getState().config
const mapping = config && config.mapping

prefix = isRoot ? typeName : prefix
if (exampleValue === null) {
Expand All @@ -267,46 +294,45 @@ export function inferInputObjectStructureFromNodes({
// setting traversing up not try to automatically infer them.
if (value === INVALID_VALUE || (isRoot && EXCLUDE_KEYS[key])) return

if (_.includes(key, `___NODE`)) {
// Several checks to see if a field is pointing to custom type
// before we try automatic inference.
const nextSelector = selector ? `${selector}.${key}` : key
const fieldSelector = `${typeName}.${nextSelector}`

let field
if (mapping && _.includes(Object.keys(mapping), fieldSelector)) {
field = getRootNodeInputObject({
typeName: mapping[fieldSelector].split(`.`)[0],
isArray: _.isArray(value),
})
console.log(`should map`, fieldSelector, k, v)
} else 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 = getExampleValues({
nodes: relatedNodes,
typeName: linkedNode.internal.type,
})
value = recursiveOmitBy(value, (_v, _k) => _.includes(_k, `___NODE`))
linkedNodeCache[linkedNode.internal.type] = value
}

if (_.isArray(value)) {
value = [value]
}

field = getRootNodeInputObject({
typeName: linkedNode.internal.type,
isArray: _.isArray(value),
})
;[key] = key.split(`___`)
} else {
field = inferGraphQLInputFields({
nodes,
value,
prefix: `${prefix}${_.upperFirst(key)}`,
typeName,
selector: nextSelector,
})
}

let field = inferGraphQLInputFields({
nodes,
value,
prefix: `${prefix}${_.upperFirst(key)}`,
})

if (field == null) return
inferredFields[createKey(key)] = field
})

// Add sorting (but only to the top level).
let sort = []
if (typeName) {
if (isRoot) {
sort = extractFieldNames(nodes)
}

Expand Down
6 changes: 6 additions & 0 deletions packages/gatsby/src/schema/infer-graphql-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ function inferFromMapping(
}

const findNode = (fieldValue, path) => {
if (_.isPlainObject(fieldValue) && fieldValue.id) {
// if we filter by mapped value, run-sift will already run our resolver,
// so fieldValue will already be our node - we just need to return it
return fieldValue
}

const linkedNode = _.find(
getNodes(),
n =>
Expand Down

0 comments on commit ccd110c

Please sign in to comment.