Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gatsby-source-wordpress): MediaItem.excludeFieldNames / auto exclude interface types that have no fields #37062

Merged
merged 10 commits into from
Nov 21, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -1493,7 +1493,6 @@ Array [
Object {
"fields": Array [
"avatar",
"databaseId",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because this field was already excluded in the test site but due to a bug that this PR happens to fix it's now properly excluded in the schema.

"id",
"name",
"url",
Expand Down Expand Up @@ -1544,7 +1543,6 @@ Array [
"modifiedGmt",
"slug",
"status",
"template",
"uri",
"nodeType",
"parent",
Expand Down Expand Up @@ -5783,7 +5781,6 @@ Array [
"sourceUrl",
"srcSet",
"status",
"template",
"title",
"uri",
"nodeType",
Expand Down Expand Up @@ -6301,12 +6298,6 @@ Array [
],
"name": "WpNodeWithRevisionsToContentNodeConnectionEdge",
},
Object {
"fields": Array [
"template",
],
"name": "WpNodeWithTemplate",
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These snapshot changes are because I added options.type.MediaItem.excludeFieldNames: [template] in the test site gatsby-config

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WpNodeWithTemplate is now automatically excluded because one of the types that implement that interface (WpMediaItem in this test site) has excluded the only field that interface defines: template.
Any type that implements an interface must have every field in that interface. So to prevent errors, this type must also be excluded as a result of excluding MediaItem.template.

Object {
"fields": Array [
"title",
Expand Down
1 change: 1 addition & 0 deletions integration-tests/gatsby-source-wordpress/gatsby-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ require(`dotenv`).config({
console.log(`Sourcing data from ` + process.env.WPGRAPHQL_URL)

const mediaItemTypeSettings = {
excludeFieldNames: [`template`],
localFile: {
excludeByMimeTypes: ["video/mp4"],
/**
Expand Down
21 changes: 21 additions & 0 deletions packages/gatsby-source-wordpress/docs/plugin-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
- [type.\_\_all.beforeChangeNode](#type__allbeforechangenode)
- [type.RootQuery](#typerootquery)
- [type.MediaItem](#typemediaitem)
- [type.MediaItem.excludeFieldNames](#typemediaitemexcludefieldnames)
- [type.MediaItem.placeholderSizeName](#typemediaitemplaceholdersizename)
- [type.MediaItem.createFileNodes](#typemediaitemcreatefilenodes)
- [type.MediaItem.lazyNodes](#typemediaitemlazynodes)
Expand Down Expand Up @@ -1230,6 +1231,26 @@ A special type which is applied to any non-node root fields that are ingested an

**Field type**: `Object`

#### type.MediaItem.excludeFieldNames

Excludes fields on the MediaItem type by field name.

**Field type**: `Array`

```js
{
resolve: `gatsby-source-wordpress`,
options: {
type: {
MediaItem: {
excludeFieldNames: [`dateGmt`, `parent`],
},
},
},
}

```

#### type.MediaItem.placeholderSizeName

This option allows you to choose the placeholder size used in the new Gatsby image service (currently in ALPHA/BETA) for the small placeholder image. Please make this image size very small for better performance. 20px or smaller width is recommended. To use, create a new image size in WP and name it "gatsby-image-placeholder" (or the name that you pass to this option) and that new size will be used automatically for placeholder images in the Gatsby build.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
fieldOfTypeWasFetched,
getTypeSettingsByType,
filterTypeDefinition,
getTypesThatImplementInterfaceType,
} from "./helpers"

const unionType = typeBuilderApi => {
Expand Down Expand Up @@ -47,38 +48,24 @@ const unionType = typeBuilderApi => {
}

const interfaceType = typeBuilderApi => {
const { type, schema, gatsbyNodeTypes, fieldAliases, fieldBlacklist } =
typeBuilderApi
const { type, schema } = typeBuilderApi

const state = store.getState()
const { ingestibles, typeMap } = state.remoteSchema
const { ingestibles } = state.remoteSchema
const { nodeInterfaceTypes } = ingestibles

const allTypes = typeMap.values()

const implementingTypes = Array.from(allTypes)
.filter(
({ interfaces }) =>
interfaces &&
// find types that implement this interface type
interfaces.find(singleInterface => singleInterface.name === type.name)
)
.map(type => typeMap.get(type.name))
.filter(
type =>
type.kind !== `UNION` ||
// if this is a union type, make sure the union type has one or more member types, otherwise schema customization will throw an error
(!!type.possibleTypes && !!type.possibleTypes.length)
)
const implementingTypes = getTypesThatImplementInterfaceType(type)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I abstracted all this code into getTypesThatImplementInterfaceType because it needs to be run during query generation as well as schema customization, where before it was only needed during schema customization.

const transformedFields = transformFields({
parentInterfacesImplementingTypes: implementingTypes,
parentType: type,
fields: type.fields,
gatsbyNodeTypes,
fieldAliases,
fieldBlacklist,
Comment on lines -77 to -79
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformFields now gets these values internally to make it easier to use transformFields in multiple places (which this PR is doing now)

})

if (!transformedFields) {
return null
}

let typeDef = {
name: buildTypeName(type.name),
fields: transformedFields,
Expand Down Expand Up @@ -144,7 +131,11 @@ const objectType = typeBuilderApi => {
.filter(interfaceType => {
const interfaceTypeSettings = getTypeSettingsByType(interfaceType)

return !interfaceTypeSettings.exclude && fieldOfTypeWasFetched(type)
return (
!interfaceTypeSettings.exclude &&
fieldOfTypeWasFetched(type) &&
fieldOfTypeWasFetched(interfaceType)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interface types that were not fetched (added during query building) shouldn't appear in the schema, so this filters them out

)
})
.map(({ name }) => buildTypeName(name))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ export const fieldOfTypeWasFetched = type => {
return typeWasFetched
}

const implementingTypeCache = new Map()

export const getTypesThatImplementInterfaceType = type => {
if (implementingTypeCache.has(type.name)) {
return implementingTypeCache.get(type.name)
}

const state = store.getState()
const { typeMap } = state.remoteSchema

const allTypes = typeMap.values()

const implementingTypes = Array.from(allTypes)
.filter(
({ interfaces }) =>
interfaces &&
// find types that implement this interface type
interfaces.find(singleInterface => singleInterface.name === type.name)
)
.map(type => typeMap.get(type.name))
.filter(
type =>
type.kind !== `UNION` ||
// if this is a union type, make sure the union type has one or more member types, otherwise schema customization will throw an error
(!!type.possibleTypes && !!type.possibleTypes.length)
)

implementingTypeCache.set(type.name, implementingTypes)

return implementingTypes
}

const supportedScalars = [
`Int`,
`Float`,
Expand All @@ -84,7 +116,7 @@ export const typeIsASupportedScalar = type => {
return supportedScalars.includes(findTypeName(type))
}

const typeSettingCache = {}
const typeSettingCache = new Map()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to use a Map for caching since this function is called tons of times during schema customization and query building


// retrieves plugin settings for the provided type
export const getTypeSettingsByType = type => {
Expand All @@ -94,7 +126,11 @@ export const getTypeSettingsByType = type => {

const typeName = findTypeName(type)

const cachedTypeSettings = typeSettingCache[typeName]
if (!typeName) {
return {}
}

const cachedTypeSettings = typeSettingCache.get(typeName)

if (cachedTypeSettings) {
return cachedTypeSettings
Expand All @@ -116,12 +152,12 @@ export const getTypeSettingsByType = type => {
if (typeSettings) {
const mergedSettings = merge(__allTypeSetting, typeSettings)

typeSettingCache[typeName] = mergedSettings
typeSettingCache.set(typeName, mergedSettings)

return mergedSettings
}

typeSettingCache[typeName] = __allTypeSetting
typeSettingCache.set(typeName, __allTypeSetting)

return __allTypeSetting
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ const customizeSchema = async ({ actions, schema, store: gatsbyStore }) => {
break
case `SCALAR`:
/**
* custom scalar types aren't imlemented currently.
* @todo make this hookable so sub-plugins or plugin options can add custom scalar support.
Comment on lines -65 to -66
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're not going to do this so I removed the todo

* custom scalar types aren't supported.
*/
break
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fieldTransformers } from "./field-transformers"
import { getGatsbyNodeTypeNames } from "../../source-nodes/fetch-nodes/fetch-nodes"
import store from "~/store"

import {
Expand Down Expand Up @@ -88,19 +89,19 @@ const excludeField = ({
* with proper node linking and type namespacing
* also filters out unusable fields and types
*/

export const transformFields = ({
fields,
fieldAliases,
fieldBlacklist,
parentType,
parentInterfacesImplementingTypes,
gatsbyNodeTypes,
}) => {
if (!fields || !fields.length) {
return null
}

const gatsbyNodeTypes = getGatsbyNodeTypeNames()

const { fieldAliases, fieldBlacklist } = store.getState().remoteSchema

const parentTypeSettings = getTypeSettingsByType(parentType)

const parentInterfacesImplementingTypeSettings =
Expand Down Expand Up @@ -197,5 +198,9 @@ export const transformFields = ({
return fieldsObject
}, {})

if (!Object.keys(transformedFields).length) {
return null
}

return transformedFields
}
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,20 @@ When using this option, be sure to gitignore the wordpress-cache directory in th
`),
}),
MediaItem: Joi.object({
excludeFieldNames: Joi.array()
.items(Joi.string())
.allow(null)
.allow(false)
.description(`Excludes fields on the MediaItem type by field name.`)
.meta({
example: wrapOptions(`
type: {
MediaItem: {
excludeFieldNames: [\`dateGmt\`, \`parent\`],
},
},
`),
}),
placeholderSizeName: Joi.string()
.default(`gatsby-image-placeholder`)
.description(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -542,11 +542,8 @@ const transformFields = ({
?.filter(
field =>
!fieldIsExcludedOnParentType({
pluginOptions,
field,
parentType,
mainType,
parentField,
}) &&
!fieldIsExcludedOnAll({
pluginOptions,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import store from "~/store"
import { typeIsExcluded } from "~/steps/ingest-remote-schema/is-excluded"
import { typeIsABuiltInScalar } from "../create-schema-customization/helpers"
import { findTypeName } from "~/steps/create-schema-customization/helpers"
import {
findTypeName,
getTypesThatImplementInterfaceType,
} from "~/steps/create-schema-customization/helpers"
import { transformFields } from "~/steps/create-schema-customization/transform-fields"
import { getPersistentCache } from "~/utils/cache"

const identifyAndStoreIngestableFieldsAndTypes = async () => {
Expand Down Expand Up @@ -46,6 +50,23 @@ const identifyAndStoreIngestableFieldsAndTypes = async () => {
continue
}

if (!interfaceType.fields) {
continue
}

const typesThatImplementInterface =
getTypesThatImplementInterfaceType(interfaceType)

const shouldSkipInterfaceType = !transformFields({
fields: interfaceType.fields,
parentType: interfaceType,
parentInterfacesImplementingTypes: typesThatImplementInterface,
})

if (shouldSkipInterfaceType && interfaceType.name !== `Node`) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We always need the WpNode type no matter what and it causes other problems if it's auto-excluded

continue
}

store.dispatch.remoteSchema.addFetchedType(interfaceType)

if (interfaceType.fields) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import store from "~/store"
import { findTypeName } from "~/steps/create-schema-customization/helpers"
import {
findTypeName,
getTypeSettingsByType,
} from "~/steps/create-schema-customization/helpers"

const typeIsExcluded = ({ pluginOptions, typeName }) =>
pluginOptions &&
Expand All @@ -15,9 +18,7 @@ const fieldIsExcludedOnAll = ({ pluginOptions, field }) => {
return !!allFieldSettings?.excludeFieldNames?.includes(field?.name)
}

const fieldIsExcludedOnParentType = ({ pluginOptions, field, parentType }) => {
const allTypeSettings = pluginOptions.type

const fieldIsExcludedOnParentType = ({ field, parentType }) => {
const state = store.getState()
const { typeMap } = state.remoteSchema

Expand All @@ -27,15 +28,16 @@ const fieldIsExcludedOnParentType = ({ pluginOptions, field, parentType }) => {
field => field.name === `nodes`
)

const parentTypeNodesFieldTypeName = findTypeName(parentTypeNodesField?.type)
const parentTypeNameSettings = getTypeSettingsByType(parentType)
const parentTypeNodesFieldTypeNameSettings = getTypeSettingsByType(
parentTypeNodesField?.type
)
Comment on lines +31 to +34
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the proper way to look up plugin options type settings, not sure why I did it the other way before ;p


const fieldIsExcludedOnParentType =
// if this field is excluded on either the parent type
allTypeSettings[parentType?.name]?.excludeFieldNames?.includes(
field?.name
) ||
parentTypeNameSettings?.excludeFieldNames?.includes(field?.name) ||
// or the parent type has a "nodes" field and that type has this field excluded
allTypeSettings[parentTypeNodesFieldTypeName]?.excludeFieldNames?.includes(
parentTypeNodesFieldTypeNameSettings?.excludeFieldNames?.includes(
field?.name
)

Expand Down
Loading