Skip to content

Commit

Permalink
feat: large projects now can decide which text fields are turned into…
Browse files Browse the repository at this point in the history
… markdown nodes
  • Loading branch information
axe312ger committed Jun 29, 2023
1 parent 0106ede commit cdede37
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ Ignored if layout = FLUID.",
"interfaces": Array [
"Node",
],
"name": "ContentfulText",
"name": "ContentfulMarkdown",
},
],
Array [
Expand Down Expand Up @@ -447,7 +447,7 @@ Ignored if layout = FLUID.",
"from": "longLocalized",
},
},
"type": "ContentfulText",
"type": "ContentfulMarkdown",
},
"longMarkdown": Object {
"extensions": Object {
Expand All @@ -456,7 +456,7 @@ Ignored if layout = FLUID.",
"from": "longMarkdown",
},
},
"type": "ContentfulText",
"type": "ContentfulMarkdown",
},
"longPlain": Object {
"extensions": Object {
Expand All @@ -465,7 +465,7 @@ Ignored if layout = FLUID.",
"from": "longPlain",
},
},
"type": "ContentfulText",
"type": "ContentfulMarkdown",
},
"metadata": Object {
"type": "ContentfulMetadata!",
Expand Down
44 changes: 43 additions & 1 deletion packages/gatsby-source-contentful/src/__tests__/gatsby-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jest.mock(`gatsby-core-utils`, () => {
}
})

/** @type {import("gatsby-plugin-utils/types").IPluginInfoOptions} */
const defaultPluginOptions = {
...defaultOptions,
spaceId: `testSpaceId`,
Expand Down Expand Up @@ -284,7 +285,7 @@ describe(`gatsby-node`, () => {
break
}
case `Text`: {
const linkId = createNodeId(`${nodeId}${field}TextNode`)
const linkId = createNodeId(`${nodeId}${field}MarkdownNode`)
matchedObject[field] = linkId
break
}
Expand Down Expand Up @@ -1114,6 +1115,47 @@ describe(`gatsby-node`, () => {
})
})

it(`should ignore markdown conversion when disabled in config`, async () => {
// @ts-ignore
fetchContent.mockImplementationOnce(startersBlogFixture.initialSync)
schema.buildObjectType.mockClear()

await simulateGatsbyBuild({
...defaultPluginOptions,
enableMarkdownDetection: false,
})
expect(actions.createNode).toHaveBeenCalledTimes(18)
expect(actions.deleteNode).toHaveBeenCalledTimes(0)
expect(actions.touchNode).toHaveBeenCalledTimes(0)

expect(
actions.createNode.mock.calls.filter(
call => call[0].internal.type == `ContentfulMarkdown`
)
).toHaveLength(0)
})

it(`should be able to create markdown nodes from a predefined list in config`, async () => {
// @ts-ignore
fetchContent.mockImplementationOnce(startersBlogFixture.initialSync)
schema.buildObjectType.mockClear()

await simulateGatsbyBuild({
...defaultPluginOptions,
enableMarkdownDetection: false,
markdownFields: [[`blogPost`, [`body`]]],
})
expect(actions.createNode).toHaveBeenCalledTimes(24)
expect(actions.deleteNode).toHaveBeenCalledTimes(0)
expect(actions.touchNode).toHaveBeenCalledTimes(0)

expect(
actions.createNode.mock.calls.filter(
call => call[0].internal.type == `ContentfulMarkdown`
)
).toHaveLength(6)
})

it(`is able to render unpublished fields in Delivery API`, async () => {
const locales = [`en-US`, `nl`]

Expand Down
8 changes: 4 additions & 4 deletions packages/gatsby-source-contentful/src/__tests__/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ describe(`Process contentful data (by name)`, () => {
expect(nodeTypeCounts).toEqual(
expect.objectContaining({
ContentfulContentType: contentTypeItems.length,
// Generated child entities
ContentfulText: 38,
// Generated markdown child entities
ContentfulMarkdown: 38,
// 3 Brand Contentful entries
ContentfulContentTypeBrand: 6,
// 2 Category Contentful entries
Expand Down Expand Up @@ -343,8 +343,8 @@ describe(`Process existing mutated nodes in warm build`, () => {
expect(nodeTypeCounts).toEqual(
expect.objectContaining({
ContentfulContentType: contentTypeItems.length,
// Child entities
ContentfulText: 38,
// Markdown child entities
ContentfulMarkdown: 38,
// 3 Brand Contentful entries
ContentfulContentTypeBrand: 6,
// 2 Category Contentful entries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ import { CODES } from "./report"
import { resolveGatsbyImageData } from "./gatsby-plugin-image"
import { makeTypeName } from "./normalize"
import { ImageCropFocusType, ImageResizingBehavior } from "./schemes"
import type { IPluginOptions } from "./types/plugin"
import type { IPluginOptions, MarkdownFieldDefinition } from "./types/plugin"
import type { ContentType, ContentTypeField, FieldItem } from "contentful"

import type {
IContentfulAsset,
IContentfulEntry,
IContentfulImageAPITransformerOptions,
} from "./types/contentful"
import { detectMarkdownField } from "./utils"

type CreateTypes = CreateSchemaCustomizationArgs["actions"]["createTypes"]

Expand All @@ -50,9 +51,15 @@ const ContentfulDataTypes: Map<
],
[
`Text`,
(): IContentfulGraphQLField => {
return { type: GraphQLString }
},
],
[
`Markdown`,
(field): IContentfulGraphQLField => {
return {
type: `ContentfulText`,
type: `ContentfulMarkdown`,
extensions: {
link: { by: `id`, from: field.id },
},
Expand Down Expand Up @@ -191,9 +198,12 @@ const getLinkFieldType = (
// Translate Contentful field types to GraphQL field types
const translateFieldType = (
field: ContentTypeField | FieldItem,
contentTypeItem: ContentType,
schema: NodePluginSchema,
createTypes: CreateTypes,
contentTypePrefix: string
contentTypePrefix: string,
enableMarkdownDetection: boolean,
markdownFields: MarkdownFieldDefinition
): GraphQLFieldConfig<unknown, unknown> => {
let fieldType
if (field.type === `Array`) {
Expand All @@ -218,9 +228,12 @@ const translateFieldType = (
)
: translateFieldType(
field.items,
contentTypeItem,
schema,
createTypes,
contentTypePrefix
contentTypePrefix,
enableMarkdownDetection,
markdownFields
)

fieldType = { ...fieldData, type: `[${fieldData.type}]` }
Expand All @@ -234,8 +247,16 @@ const translateFieldType = (
contentTypePrefix
)
} else {
// Detect markdown in text fields
const typeName = detectMarkdownField(
field as ContentTypeField,
contentTypeItem,
enableMarkdownDetection,
markdownFields
)

// Primitive field types
const primitiveType = ContentfulDataTypes.get(field.type)
const primitiveType = ContentfulDataTypes.get(typeName)
if (!primitiveType) {
throw new Error(`Contentful field type ${field.type} is not supported.`)
}
Expand Down Expand Up @@ -299,6 +320,12 @@ export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"]

const contentTypePrefix = pluginConfig.get(`contentTypePrefix`)
const useNameForId = pluginConfig.get(`useNameForId`)
const enableMarkdownDetection: boolean = pluginConfig.get(
`enableMarkdownDetection`
)
const markdownFields: MarkdownFieldDefinition = new Map(
pluginConfig.get(`markdownFields`)
)

let contentTypeItems: Array<ContentType>
if (process.env.GATSBY_WORKER_ID) {
Expand Down Expand Up @@ -644,11 +671,10 @@ export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"]
})
)

// Text
// TODO: Is there a way to have this as string and let transformer-remark replace it with an object?
// Text (Markdown, as text is a pure string)
createTypes(
schema.buildObjectType({
name: `ContentfulText`,
name: `ContentfulMarkdown`,
fields: {
raw: `String!`,
},
Expand Down Expand Up @@ -677,9 +703,12 @@ export const createSchemaCustomization: GatsbyNode["createSchemaCustomization"]
}
fields[field.id] = translateFieldType(
field,
contentTypeItem,
schema,
createTypes,
contentTypePrefix
contentTypePrefix,
enableMarkdownDetection,
markdownFields
)
})

Expand Down
14 changes: 14 additions & 0 deletions packages/gatsby-source-contentful/src/gatsby-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,20 @@ List of locales and their codes can be found in Contentful app -> Settings -> Lo
For example, to exclude content types starting with "page" \`contentTypeFilter: contentType => !contentType.sys.id.startsWith('page')\``
)
.default(() => (): boolean => true),
enableMarkdownDetection: Joi.boolean()
.description(
`Assumes that every long text field in Contentful is a markdown field. Can be a performance bottle-neck on big projects. Requires gatsby-transformer-remark.`
)
.default(true),
markdownFields: Joi.array()
.description(
`List of text fields that contain markdown content. Needs gatsby-transformer-remark.`
)
.default([])
.example([
[`product`, [`description`, `summary`]],
[`otherContentTypeId`, [`someMarkdownFieldId`]],
]),
pageLimit: Joi.number()
.integer()
.description(
Expand Down
60 changes: 40 additions & 20 deletions packages/gatsby-source-contentful/src/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import type {
DeletedEntry,
EntitySys,
} from "contentful"
import type { IProcessedPluginOptions } from "./types/plugin"
import type {
IProcessedPluginOptions,
MarkdownFieldDefinition,
} from "./types/plugin"
import { detectMarkdownField } from "./utils"

export const makeTypeName = (
type: string,
Expand Down Expand Up @@ -354,19 +358,19 @@ export const buildForeignReferenceMap = ({
return foreignReferenceMapState
}

function prepareTextNode(
function prepareMarkdownNode(
id: string,
node: IContentfulEntry,
_key: string,
text: unknown
): Node {
const str = _.isString(text) ? text : ``
const textNode: Node = {
const markdownNode: Node = {
id,
parent: node.id,
raw: str,
internal: {
type: `ContentfulText`,
type: `ContentfulMarkdown`,
mediaType: `text/markdown`,
content: str,
// entryItem.sys.publishedAt is source of truth from contentful
Expand All @@ -375,7 +379,7 @@ function prepareTextNode(
children: [],
}

return textNode
return markdownNode
}

let numberOfContentSyncDebugLogs = 0
Expand Down Expand Up @@ -547,6 +551,13 @@ export const createNodesForContentType = ({
createNode,
})

const enableMarkdownDetection: boolean = pluginConfig.get(
`enableMarkdownDetection`
)
const markdownFields: MarkdownFieldDefinition = new Map(
pluginConfig.get(`markdownFields`)
)

// Establish identifier for content type
// Use `name` if specified, otherwise, use internal id (usually a natural-language constant,
// but sometimes a base62 uuid generated by Contentful, hence the option)
Expand Down Expand Up @@ -820,26 +831,35 @@ export const createNodesForContentType = ({
: f.id) === entryItemFieldKey
)
if (field?.type === `Text`) {
const textNodeId = createNodeId(
`${entryNodeId}${entryItemFieldKey}TextNode`
const fieldType = detectMarkdownField(
field,
contentTypeItem,
enableMarkdownDetection,
markdownFields
)

// The Contentful model has `.sys.updatedAt` leading for an entry. If the updatedAt value
// of an entry did not change, then we can trust that none of its children were changed either.
// (That's why child nodes use the updatedAt of the parent node as their digest, too)
const existingNode = getNode(textNodeId)
if (existingNode?.updatedAt !== entryItem.sys.updatedAt) {
const textNode = prepareTextNode(
textNodeId,
entryNode,
entryItemFieldKey,
entryItemFields[entryItemFieldKey]
if (fieldType == `Markdown`) {
const textNodeId = createNodeId(
`${entryNodeId}${entryItemFieldKey}${fieldType}Node`
)

childrenNodes.push(textNode)
}
// The Contentful model has `.sys.updatedAt` leading for an entry. If the updatedAt value
// of an entry did not change, then we can trust that none of its children were changed either.
// (That's why child nodes use the updatedAt of the parent node as their digest, too)
const existingNode = getNode(textNodeId)
if (existingNode?.updatedAt !== entryItem.sys.updatedAt) {
const textNode = prepareMarkdownNode(
textNodeId,
entryNode,
entryItemFieldKey,
entryItemFields[entryItemFieldKey]
)

childrenNodes.push(textNode)
}

entryItemFields[entryItemFieldKey] = textNodeId
entryItemFields[entryItemFieldKey] = textNodeId
}
}
})

Expand Down
4 changes: 3 additions & 1 deletion packages/gatsby-source-contentful/src/plugin-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const defaultOptions: Omit<IPluginOptions, "spaceId" | "accessToken"> = {
pageLimit: DEFAULT_PAGE_LIMIT,
useNameForId: true,
contentTypePrefix: `ContentfulContentType`,
enableMarkdownDetection: true,
markdownFields: [],
}

/**
Expand All @@ -32,7 +34,7 @@ const maskText = (input: string): string => {
}

const createPluginConfig = (
pluginOptions: IPluginOptions
pluginOptions: Partial<IPluginOptions>
): IProcessedPluginOptions => {
const conf = { ...defaultOptions, ...pluginOptions }

Expand Down

0 comments on commit cdede37

Please sign in to comment.