From ccd110c0074ec6f7e802447a766b079146432a8a Mon Sep 17 00:00:00 2001 From: Michal Piechowiak Date: Sun, 14 Oct 2018 23:35:17 +0200 Subject: [PATCH] store inputobjects for root node types, skip invalid_value from example values, add support for proper filtering using mapped fields --- .../schema/__tests__/data-tree-utils-test.js | 29 ++-- .../src/schema/build-node-connections.js | 2 +- packages/gatsby/src/schema/data-tree-utils.js | 2 +- .../src/schema/infer-graphql-input-fields.js | 146 +++++++++++------- .../gatsby/src/schema/infer-graphql-type.js | 6 + 5 files changed, 108 insertions(+), 77 deletions(-) diff --git a/packages/gatsby/src/schema/__tests__/data-tree-utils-test.js b/packages/gatsby/src/schema/__tests__/data-tree-utils-test.js index 3f20c33860c90..c190bb30fbeb7 100644 --- a/packages/gatsby/src/schema/__tests__/data-tree-utils-test.js +++ b/packages/gatsby/src/schema/__tests__/data-tree-utils-test.js @@ -2,7 +2,6 @@ const { getExampleValues, buildFieldEnumValues, clearTypeExampleValues, - INVALID_VALUE, } = require(`../data-tree-utils`) const { typeConflictReporter, @@ -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`, () => { @@ -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({ @@ -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({ @@ -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({ @@ -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`, () => { @@ -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({ @@ -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({ @@ -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({ @@ -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({ @@ -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({ @@ -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({ @@ -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({ @@ -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) }) }) diff --git a/packages/gatsby/src/schema/build-node-connections.js b/packages/gatsby/src/schema/build-node-connections.js index 0ec2825e2bd3f..95a1108572df5 100644 --- a/packages/gatsby/src/schema/build-node-connections.js +++ b/packages/gatsby/src/schema/build-node-connections.js @@ -30,7 +30,7 @@ module.exports = (types: any) => { const inferredInputFieldsFromNodes = inferInputObjectStructureFromNodes({ nodes, - typeName, + typeName: type.name, }) const inferredInputFieldsFromPlugins = inferInputObjectStructureFromFields({ diff --git a/packages/gatsby/src/schema/data-tree-utils.js b/packages/gatsby/src/schema/data-tree-utils.js index fae402fd49c24..90e7871803a48 100644 --- a/packages/gatsby/src/schema/data-tree-utils.js +++ b/packages/gatsby/src/schema/data-tree-utils.js @@ -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 } diff --git a/packages/gatsby/src/schema/infer-graphql-input-fields.js b/packages/gatsby/src/schema/infer-graphql-input-fields.js index f53161d8a2d31..b3f192783a5fc 100644 --- a/packages/gatsby/src/schema/infer-graphql-input-fields.js +++ b/packages/gatsby/src/schema/infer-graphql-input-fields.js @@ -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 { @@ -77,6 +77,8 @@ function inferGraphQLInputFields({ value, nodes, prefix, + typeName, + selector, }): ?GraphQLInputFieldConfig { if (value == null || isEmptyObjectOrArray(value)) return null @@ -110,6 +112,8 @@ function inferGraphQLInputFields({ value: headValue, prefix, nodes, + selector, + typeName, }) invariant( inferredField, @@ -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`: { @@ -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) { @@ -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) } diff --git a/packages/gatsby/src/schema/infer-graphql-type.js b/packages/gatsby/src/schema/infer-graphql-type.js index fc159bb73b922..dedca499c12d2 100644 --- a/packages/gatsby/src/schema/infer-graphql-type.js +++ b/packages/gatsby/src/schema/infer-graphql-type.js @@ -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 =>