(
- {category.title.title}
+ {category.title.raw}
))}
@@ -70,10 +70,10 @@ ProductTemplate.propTypes = propTypes
export default ProductTemplate
export const pageQuery = graphql`
- query($id: String!) {
+ query ($id: String!) {
contentfulProduct(id: { eq: $id }) {
productName {
- productName
+ raw
}
productDescription {
childMarkdownRemark {
@@ -86,14 +86,14 @@ export const pageQuery = graphql`
}
brand {
companyName {
- companyName
+ raw
}
}
categories {
id
gatsbyPath(filePath: "/categories/{ContentfulCategory.id}")
title {
- title
+ raw
}
}
}
diff --git a/packages/gatsby-source-contentful/README.md b/packages/gatsby-source-contentful/README.md
index c3512c12700ba..59142a9263e69 100644
--- a/packages/gatsby-source-contentful/README.md
+++ b/packages/gatsby-source-contentful/README.md
@@ -174,12 +174,6 @@ Additional config which will get passed to [Contentfuls JS SDK](https://github.c
Use this with caution, you might override values this plugin does set for you to connect to Contentful.
-**`enableTags`** [boolean][optional] [default: `false`]
-
-Enable the new [tags feature](https://www.contentful.com/blog/2021/04/08/governance-tagging-metadata/). This will disallow the content type name `tags` till the next major version of this plugin.
-
-Learn how to use them at the [Contentful Tags](#contentful-tags) section.
-
## How to query for nodes
Two standard node types are available from Contentful: `Asset` and `ContentType`.
@@ -257,7 +251,7 @@ On Contentful, a "Long text" field uses Markdown by default. The field is expose
{
contentfulCaseStudy {
body {
- body
+ raw
}
}
}
@@ -387,8 +381,6 @@ Check the [Reference Guide of gatsby-plugin-image](https://www.gatsbyjs.com/docs
## [Contentful Tags](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/content-tags)
-You need to set the `enableTags` flag to `true` to use this new feature.
-
### List available tags
This example lists all available tags. The sorting is optional.
diff --git a/packages/gatsby-source-contentful/package.json b/packages/gatsby-source-contentful/package.json
index 8ea6bb76e13c3..553823ed29297 100644
--- a/packages/gatsby-source-contentful/package.json
+++ b/packages/gatsby-source-contentful/package.json
@@ -9,6 +9,7 @@
"dependencies": {
"@babel/runtime": "^7.15.4",
"@contentful/rich-text-react-renderer": "^14.1.3",
+ "@contentful/rich-text-links": "^14.1.2",
"@contentful/rich-text-types": "^14.1.2",
"@hapi/joi": "^15.1.1",
"@vercel/fetch-retry": "^5.0.3",
@@ -16,6 +17,7 @@
"chalk": "^4.1.2",
"common-tags": "^1.8.2",
"contentful": "^8.5.8",
+ "contentful-resolve-response": "^1.3.0",
"fs-extra": "^10.0.0",
"gatsby-core-utils": "^3.8.0-next.0",
"gatsby-plugin-utils": "^3.2.0-next.0",
diff --git a/packages/gatsby-source-contentful/src/__fixtures__/content-types.js b/packages/gatsby-source-contentful/src/__fixtures__/content-types.js
new file mode 100644
index 0000000000000..d3e4a10105aa7
--- /dev/null
+++ b/packages/gatsby-source-contentful/src/__fixtures__/content-types.js
@@ -0,0 +1,786 @@
+export const contentTypes = [
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `number`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T16:59:23.010Z`,
+ updatedAt: `2021-05-21T11:20:58.851Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 5,
+ },
+ displayField: `title`,
+ name: `Number`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `integer`,
+ name: `Integer`,
+ type: `Integer`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `integerLocalized`,
+ name: `Integer Localized`,
+ type: `Integer`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `decimal`,
+ name: `Decimal`,
+ type: `Number`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `decimalLocalized`,
+ name: `Decimal Localized`,
+ type: `Number`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `text`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:02:35.612Z`,
+ updatedAt: `2021-05-21T10:48:06.210Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 3,
+ },
+ displayField: `title`,
+ name: `Text`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `short`,
+ name: `Short`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `shortLocalized`,
+ name: `Short Localized`,
+ type: `Symbol`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `shortList`,
+ name: `Short List`,
+ type: `Array`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Symbol`,
+ validations: [],
+ },
+ },
+ {
+ id: `shortListLocalized`,
+ name: `Short List Localized`,
+ type: `Array`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Symbol`,
+ validations: [],
+ },
+ },
+ {
+ id: `longPlain`,
+ name: `Long Plain`,
+ type: `Text`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `longMarkdown`,
+ name: `Long Markdown`,
+ type: `Text`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `longLocalized`,
+ name: `Long Localized`,
+ type: `Text`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `mediaReference`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:03:21.639Z`,
+ updatedAt: `2021-05-21T11:11:20.265Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 8,
+ },
+ displayField: `title`,
+ name: `Media Reference`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `one`,
+ name: `One`,
+ type: `Link`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Asset`,
+ },
+ {
+ id: `oneLocalized`,
+ name: `One Localized`,
+ type: `Link`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Asset`,
+ },
+ {
+ id: `many`,
+ name: `Many`,
+ type: `Array`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [],
+ linkType: `Asset`,
+ },
+ },
+ {
+ id: `manyLocalized`,
+ name: `Many Localized`,
+ type: `Array`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [],
+ linkType: `Asset`,
+ },
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `boolean`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:05:40.030Z`,
+ updatedAt: `2021-05-21T10:32:45.505Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 3,
+ },
+ displayField: `title`,
+ name: `Boolean`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `boolean`,
+ name: `Boolean`,
+ type: `Boolean`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `booleanLocalized`,
+ name: `Boolean Localized`,
+ type: `Boolean`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `date`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:07:02.629Z`,
+ updatedAt: `2021-05-20T18:16:28.584Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 2,
+ },
+ displayField: `title`,
+ name: `Date`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `date`,
+ name: `Date`,
+ type: `Date`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `dateTime`,
+ name: `Date Time`,
+ type: `Date`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `dateTimeTimezone`,
+ name: `Date Time Timezone`,
+ type: `Date`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `dateLocalized`,
+ name: `Date Localized`,
+ type: `Date`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `location`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:09:20.579Z`,
+ updatedAt: `2021-05-21T10:37:52.205Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 2,
+ },
+ displayField: `title`,
+ name: `Location`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `location`,
+ name: `Location`,
+ type: `Location`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `locationLocalized`,
+ name: `Location Localized`,
+ type: `Location`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `json`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:09:56.970Z`,
+ updatedAt: `2021-05-21T10:36:57.432Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 2,
+ },
+ displayField: `title`,
+ name: `JSON`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `json`,
+ name: `JSON`,
+ type: `Object`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `jsonLocalized`,
+ name: `JSON Localized`,
+ type: `Object`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `richText`,
+ type: `ContentType`,
+ createdAt: `2021-03-01T17:11:01.406Z`,
+ updatedAt: `2021-11-05T12:56:39.942Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 7,
+ },
+ displayField: `title`,
+ name: `Rich Text`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `richText`,
+ name: `Rich Text`,
+ type: `RichText`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `richTextLocalized`,
+ name: `Rich Text Localized`,
+ type: `RichText`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `richTextValidated`,
+ name: `Rich Text Validated`,
+ type: `RichText`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `contentReference`,
+ type: `ContentType`,
+ createdAt: `2021-03-02T09:17:08.210Z`,
+ updatedAt: `2021-05-21T10:36:34.506Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 7,
+ },
+ displayField: `title`,
+ name: `Content Reference`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `one`,
+ name: `One`,
+ type: `Link`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Entry`,
+ },
+ {
+ id: `oneLocalized`,
+ name: `One Localized`,
+ type: `Link`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Entry`,
+ },
+ {
+ id: `many`,
+ name: `Many`,
+ type: `Array`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [
+ {
+ linkContentType: [`contentReference`, `number`, `text`],
+ },
+ ],
+ linkType: `Entry`,
+ },
+ },
+ {
+ id: `manyLocalized`,
+ name: `Many Localized`,
+ type: `Array`,
+ localized: true,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [],
+ linkType: `Entry`,
+ },
+ },
+ ],
+ },
+ {
+ sys: {
+ space: {
+ sys: {
+ type: `Link`,
+ linkType: `Space`,
+ id: `k8iqpp6u0ior`,
+ },
+ },
+ id: `validatedContentReference`,
+ type: `ContentType`,
+ createdAt: `2021-05-20T15:29:49.734Z`,
+ updatedAt: `2021-05-20T15:33:26.620Z`,
+ environment: {
+ sys: {
+ id: `master`,
+ type: `Link`,
+ linkType: `Environment`,
+ },
+ },
+ revision: 4,
+ },
+ displayField: `title`,
+ name: `ValidatedContentReference`,
+ description: ``,
+ fields: [
+ {
+ id: `title`,
+ name: `Title`,
+ type: `Symbol`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ },
+ {
+ id: `oneItemSingleType`,
+ name: `One Item: Single Type`,
+ type: `Link`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Entry`,
+ validations: [
+ {
+ linkContentType: [`text`],
+ },
+ ],
+ },
+ {
+ id: `oneItemManyTypes`,
+ name: `One Item: Many Types`,
+ type: `Link`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Entry`,
+ validations: [
+ {
+ linkContentType: [`number`, `text`],
+ },
+ ],
+ },
+ {
+ id: `oneItemAllTypes`,
+ name: `One Item: All Types`,
+ type: `Link`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ linkType: `Entry`,
+ },
+ {
+ id: `multipleItemsSingleType`,
+ name: `Multiple Items: Single Type`,
+ type: `Array`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [
+ {
+ linkContentType: [`text`],
+ },
+ ],
+ linkType: `Entry`,
+ },
+ },
+ {
+ id: `multipleItemsManyTypes`,
+ name: `Multiple Items: Many Types`,
+ type: `Array`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [
+ {
+ linkContentType: [`number`, `text`],
+ },
+ ],
+ linkType: `Entry`,
+ },
+ },
+ {
+ id: `multipleItemsAllTypes`,
+ name: `Multiple Items: All Types`,
+ type: `Array`,
+ localized: false,
+ required: false,
+ disabled: false,
+ omitted: false,
+ items: {
+ type: `Link`,
+ validations: [],
+ linkType: `Entry`,
+ },
+ },
+ ],
+ },
+]
diff --git a/packages/gatsby-source-contentful/src/__fixtures__/restricted-content-type.js b/packages/gatsby-source-contentful/src/__fixtures__/restricted-content-type.js
deleted file mode 100644
index 8adda16238442..0000000000000
--- a/packages/gatsby-source-contentful/src/__fixtures__/restricted-content-type.js
+++ /dev/null
@@ -1,80 +0,0 @@
-exports.contentTypeItems = () => [
- {
- sys: {
- space: {
- sys: {
- type: `Link`,
- linkType: `Space`,
- id: `uzfinxahlog0`,
- contentful_id: `uzfinxahlog0`,
- },
- },
- id: `reference`,
- type: `ContentType`,
- createdAt: `2020-06-03T14:17:18.696Z`,
- updatedAt: `2020-06-03T14:17:18.696Z`,
- environment: {
- sys: {
- id: `master`,
- type: `Link`,
- linkType: `Environment`,
- },
- },
- revision: 1,
- contentful_id: `person`,
- },
- displayField: `name`,
- name: `Reference`,
- description: ``,
- fields: [
- {
- id: `name`,
- name: `Name`,
- type: `Symbol`,
- localized: false,
- required: true,
- disabled: false,
- omitted: false,
- },
- ],
- },
-]
-
-exports.initialSync = () => {
- return {
- currentSyncData: {
- entries: [],
- assets: [],
- deletedEntries: [],
- deletedAssets: [],
- nextSyncToken: `12345`,
- },
- defaultLocale: `en-US`,
- locales: [
- {
- code: `en-US`,
- name: `English (United States)`,
- default: true,
- fallbackCode: null,
- sys: {
- id: `1uSElBQA68GRKF30tpTxxT`,
- type: `Locale`,
- version: 1,
- },
- },
- ],
- space: {
- sys: { type: `Space`, id: `uzfinxahlog0` },
- name: `Starter Gatsby Blog`,
- locales: [
- {
- code: `en-US`,
- default: true,
- name: `English (United States)`,
- fallbackCode: null,
- },
- ],
- },
- tagItems: [],
- }
-}
diff --git a/packages/gatsby-source-contentful/src/__fixtures__/rich-text-data.js b/packages/gatsby-source-contentful/src/__fixtures__/rich-text-data.js
index 0be1eaa13ee55..98ee4aa277268 100644
--- a/packages/gatsby-source-contentful/src/__fixtures__/rich-text-data.js
+++ b/packages/gatsby-source-contentful/src/__fixtures__/rich-text-data.js
@@ -496,6 +496,7 @@ exports.initialSync = () => {
},
},
},
+ metadata: { tags: [] },
},
{
sys: {
@@ -658,6 +659,7 @@ exports.initialSync = () => {
},
},
},
+ metadata: { tags: [] },
},
],
assets: [
@@ -702,6 +704,7 @@ exports.initialSync = () => {
},
},
},
+ metadata: { tags: [] },
},
{
sys: {
@@ -744,6 +747,7 @@ exports.initialSync = () => {
},
},
},
+ metadata: { tags: [] },
},
],
deletedEntries: [],
diff --git a/packages/gatsby-source-contentful/src/__fixtures__/starter-blog-data.js b/packages/gatsby-source-contentful/src/__fixtures__/starter-blog-data.js
index 60dc97f683950..b2d94d44dde96 100644
--- a/packages/gatsby-source-contentful/src/__fixtures__/starter-blog-data.js
+++ b/packages/gatsby-source-contentful/src/__fixtures__/starter-blog-data.js
@@ -289,6 +289,7 @@ exports.initialSync = () => {
publishDate: { "en-US": `2017-05-12T00:00+02:00` },
tags: { "en-US": [`javascript`] },
},
+ metadata: { tags: [] },
},
{
sys: {
@@ -349,6 +350,7 @@ exports.initialSync = () => {
publishDate: { "en-US": `2017-05-15T00:00+02:00` },
tags: { "en-US": [`general`] },
},
+ metadata: { tags: [] },
},
{
sys: {
@@ -409,6 +411,7 @@ exports.initialSync = () => {
publishDate: { "en-US": `2017-05-16T00:00+02:00` },
tags: { "en-US": [`javascript`, `static-sites`] },
},
+ metadata: { tags: [] },
},
{
sys: {
@@ -461,6 +464,7 @@ exports.initialSync = () => {
},
},
},
+ metadata: { tags: [] },
},
],
assets: [
@@ -734,6 +738,7 @@ exports.createBlogPost = () => {
},
publishDate: { "en-US": `2020-04-01T00:00+02:00` },
},
+ metadata: { tags: [] },
},
],
assets: [
@@ -887,6 +892,7 @@ exports.updateBlogPost = () => {
},
publishDate: { "en-US": `2020-05-15T00:00+02:00` },
},
+ metadata: { tags: [] },
},
],
assets: [],
diff --git a/packages/gatsby-source-contentful/src/__tests__/__snapshots__/create-schema-customization.js.snap b/packages/gatsby-source-contentful/src/__tests__/__snapshots__/create-schema-customization.js.snap
new file mode 100644
index 0000000000000..23204abdcac9b
--- /dev/null
+++ b/packages/gatsby-source-contentful/src/__tests__/__snapshots__/create-schema-customization.js.snap
@@ -0,0 +1,849 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`create-schema-customization builds schema based on Contentful Content Model 1`] = `
+Array [
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "description": Object {
+ "type": "String!",
+ },
+ "displayField": Object {
+ "type": "String!",
+ },
+ "id": Object {
+ "type": "ID!",
+ },
+ "name": Object {
+ "type": "String!",
+ },
+ },
+ "interfaces": Array [
+ "Node",
+ ],
+ "name": "ContentfulContentType",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "contentType": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "contentType___NODE",
+ },
+ },
+ "type": "ContentfulContentType",
+ },
+ "environmentId": Object {
+ "type": " String!",
+ },
+ "firstPublishedAt": Object {
+ "type": " Date!",
+ },
+ "id": Object {
+ "type": " String!",
+ },
+ "locale": Object {
+ "type": " String!",
+ },
+ "publishedAt": Object {
+ "type": " Date!",
+ },
+ "publishedVersion": Object {
+ "type": " Int!",
+ },
+ "spaceId": Object {
+ "type": " String!",
+ },
+ "type": Object {
+ "type": " String!",
+ },
+ },
+ "interfaces": Array [
+ "Node",
+ ],
+ "name": "ContentfulSys",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "tags": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "tags___NODE",
+ },
+ },
+ "type": "[ContentfulTag]!",
+ },
+ },
+ "name": "ContentfulMetadata",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "contentful_id": Object {
+ "type": "String!",
+ },
+ "id": Object {
+ "type": "ID!",
+ },
+ "name": Object {
+ "type": "String!",
+ },
+ },
+ "interfaces": Array [
+ "Node",
+ ],
+ "name": "ContentfulTag",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "localFile": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ },
+ },
+ "type": "File",
+ },
+ },
+ "name": "ContentfulAssetFields",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "contentType": Object {
+ "type": "String",
+ },
+ "description": Object {
+ "type": "String",
+ },
+ "fields": Object {
+ "type": "ContentfulAssetFields",
+ },
+ "fileName": Object {
+ "type": "String",
+ },
+ "height": Object {
+ "type": "Int",
+ },
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "size": Object {
+ "type": "Int",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ "url": Object {
+ "type": "String",
+ },
+ "width": Object {
+ "type": "Int",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "Node",
+ ],
+ "name": "ContentfulAsset",
+ },
+ ],
+ Array [
+ Object {
+ "fields": Object {
+ "block": Object {
+ "resolve": [Function],
+ "type": "[ContentfulAsset]!",
+ },
+ "hyperlink": Object {
+ "resolve": [Function],
+ "type": "[ContentfulAsset]!",
+ },
+ },
+ "name": "ContentfulRichTextAssets",
+ },
+ ],
+ Array [
+ Object {
+ "fields": Object {
+ "block": Object {
+ "resolve": [Function],
+ "type": "[ContentfulEntry]!",
+ },
+ "hyperlink": Object {
+ "resolve": [Function],
+ "type": "[ContentfulEntry]!",
+ },
+ "inline": Object {
+ "resolve": [Function],
+ "type": "[ContentfulEntry]!",
+ },
+ },
+ "name": "ContentfulRichTextEntries",
+ },
+ ],
+ Array [
+ Object {
+ "fields": Object {
+ "assets": Object {
+ "resolve": [Function],
+ "type": "ContentfulRichTextAssets",
+ },
+ "entries": Object {
+ "resolve": [Function],
+ "type": "ContentfulRichTextEntries",
+ },
+ },
+ "name": "ContentfulRichTextLinks",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "json": Object {
+ "resolve": [Function],
+ "type": "JSON",
+ },
+ "links": Object {
+ "resolve": [Function],
+ "type": "ContentfulRichTextLinks",
+ },
+ },
+ "name": "ContentfulRichText",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "lat": Object {
+ "type": "Float!",
+ },
+ "lon": Object {
+ "type": "Float!",
+ },
+ },
+ "name": "ContentfulLocation",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "raw": "String!",
+ },
+ "interfaces": Array [
+ "Node",
+ ],
+ "name": "ContentfulText",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "decimal": Object {
+ "type": "Float",
+ },
+ "decimalLocalized": Object {
+ "type": "Float",
+ },
+ "id": Object {
+ "type": "ID!",
+ },
+ "integer": Object {
+ "type": "Int",
+ },
+ "integerLocalized": Object {
+ "type": "Int",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeNumber",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "longLocalized": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "longLocalized___NODE",
+ },
+ },
+ "type": "ContentfulText",
+ },
+ "longMarkdown": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "longMarkdown___NODE",
+ },
+ },
+ "type": "ContentfulText",
+ },
+ "longPlain": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "longPlain___NODE",
+ },
+ },
+ "type": "ContentfulText",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "short": Object {
+ "type": "String",
+ },
+ "shortList": Object {
+ "type": "[String]",
+ },
+ "shortListLocalized": Object {
+ "type": "[String]",
+ },
+ "shortLocalized": Object {
+ "type": "String",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeText",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "many": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "many___NODE",
+ },
+ },
+ "type": "[ContentfulAsset]",
+ },
+ "manyLocalized": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "manyLocalized___NODE",
+ },
+ },
+ "type": "[ContentfulAsset]",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "one": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "one___NODE",
+ },
+ },
+ "type": "ContentfulAsset",
+ },
+ "oneLocalized": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "oneLocalized___NODE",
+ },
+ },
+ "type": "ContentfulAsset",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeMediaReference",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "boolean": Object {
+ "type": "Boolean",
+ },
+ "booleanLocalized": Object {
+ "type": "Boolean",
+ },
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeBoolean",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "date": Object {
+ "extensions": Object {
+ "dateformat": Object {},
+ },
+ "type": "Date",
+ },
+ "dateLocalized": Object {
+ "extensions": Object {
+ "dateformat": Object {},
+ },
+ "type": "Date",
+ },
+ "dateTime": Object {
+ "extensions": Object {
+ "dateformat": Object {},
+ },
+ "type": "Date",
+ },
+ "dateTimeTimezone": Object {
+ "extensions": Object {
+ "dateformat": Object {},
+ },
+ "type": "Date",
+ },
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeDate",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "location": Object {
+ "type": "ContentfulLocation",
+ },
+ "locationLocalized": Object {
+ "type": "ContentfulLocation",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeLocation",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "json": Object {
+ "type": "JSON",
+ },
+ "jsonLocalized": Object {
+ "type": "JSON",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeJson",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "richText": Object {
+ "type": "ContentfulRichText",
+ },
+ "richTextLocalized": Object {
+ "type": "ContentfulRichText",
+ },
+ "richTextValidated": Object {
+ "type": "ContentfulRichText",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeRichText",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "many": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "many___NODE",
+ },
+ },
+ "type": "[UnionContentfulContentReferenceNumberText]",
+ },
+ "manyLocalized": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "manyLocalized___NODE",
+ },
+ },
+ "type": "[ContentfulEntry]",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "one": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "one___NODE",
+ },
+ },
+ "type": "ContentfulEntry",
+ },
+ "oneLocalized": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "oneLocalized___NODE",
+ },
+ },
+ "type": "ContentfulEntry",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeContentReference",
+ },
+ ],
+ Array [
+ Object {
+ "extensions": Object {
+ "dontInfer": Object {},
+ },
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "multipleItemsAllTypes": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "multipleItemsAllTypes___NODE",
+ },
+ },
+ "type": "[ContentfulEntry]",
+ },
+ "multipleItemsManyTypes": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "multipleItemsManyTypes___NODE",
+ },
+ },
+ "type": "[UnionContentfulNumberText]",
+ },
+ "multipleItemsSingleType": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "multipleItemsSingleType___NODE",
+ },
+ },
+ "type": "[ContentfulContentTypeText]",
+ },
+ "oneItemAllTypes": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "oneItemAllTypes___NODE",
+ },
+ },
+ "type": "ContentfulEntry",
+ },
+ "oneItemManyTypes": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "oneItemManyTypes___NODE",
+ },
+ },
+ "type": "UnionContentfulNumberText",
+ },
+ "oneItemSingleType": Object {
+ "extensions": Object {
+ "link": Object {
+ "by": "id",
+ "from": "oneItemSingleType___NODE",
+ },
+ },
+ "type": "ContentfulContentTypeText",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ "title": Object {
+ "type": "String",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "ContentfulEntry",
+ "Node",
+ ],
+ "name": "ContentfulContentTypeValidatedContentReference",
+ },
+ ],
+]
+`;
+
+exports[`create-schema-customization builds schema based on Contentful Content Model 2`] = `
+Array [
+ Array [
+ Object {
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ },
+ "interfaces": Array [
+ "Node",
+ ],
+ "name": "ContentfulEntity",
+ },
+ ],
+ Array [
+ Object {
+ "fields": Object {
+ "id": Object {
+ "type": "ID!",
+ },
+ "metadata": Object {
+ "type": "ContentfulMetadata!",
+ },
+ "sys": Object {
+ "type": "ContentfulSys!",
+ },
+ },
+ "interfaces": Array [
+ "ContentfulEntity",
+ "Node",
+ ],
+ "name": "ContentfulEntry",
+ },
+ ],
+]
+`;
+
+exports[`create-schema-customization builds schema based on Contentful Content Model 3`] = `
+Array [
+ Array [
+ Object {
+ "name": "UnionContentfulContentReferenceNumberText",
+ "types": Array [
+ "ContentfulContentTypeContentReference",
+ "ContentfulContentTypeNumber",
+ "ContentfulContentTypeText",
+ ],
+ },
+ ],
+ Array [
+ Object {
+ "name": "UnionContentfulNumberText",
+ "types": Array [
+ "ContentfulContentTypeNumber",
+ "ContentfulContentTypeText",
+ ],
+ },
+ ],
+]
+`;
diff --git a/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-node.js.snap b/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-node.js.snap
deleted file mode 100644
index 4961649230900..0000000000000
--- a/packages/gatsby-source-contentful/src/__tests__/__snapshots__/gatsby-node.js.snap
+++ /dev/null
@@ -1,17 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`gatsby-node stores rich text as raw with references attached 1`] = `
-Array [
- "ahntqop9oi7x___7oHxo6bs0us9wIkq27qdyK___Entry",
- "ahntqop9oi7x___6KpLS2NZyB3KAvDzWf4Ukh___Entry",
- "ahntqop9oi7x___4ZQrqcrTunWiuNaavhGYNT___Asset",
-]
-`;
-
-exports[`gatsby-node stores rich text as raw with references attached 2`] = `
-Array [
- "ahntqop9oi7x___7oHxo6bs0us9wIkq27qdyK___Entry___nl",
- "ahntqop9oi7x___6KpLS2NZyB3KAvDzWf4Ukh___Entry___nl",
- "ahntqop9oi7x___4ZQrqcrTunWiuNaavhGYNT___Asset___nl",
-]
-`;
diff --git a/packages/gatsby-source-contentful/src/__tests__/create-schema-customization.js b/packages/gatsby-source-contentful/src/__tests__/create-schema-customization.js
new file mode 100644
index 0000000000000..9ae3027eddacc
--- /dev/null
+++ b/packages/gatsby-source-contentful/src/__tests__/create-schema-customization.js
@@ -0,0 +1,46 @@
+// @ts-check
+import { createSchemaCustomization } from "../create-schema-customization"
+import { contentTypes } from "../__fixtures__/content-types"
+
+const createMockCache = () => {
+ return {
+ get: jest.fn(key => contentTypes),
+ }
+}
+
+describe(`create-schema-customization`, () => {
+ const actions = { createTypes: jest.fn() }
+ const schema = {
+ buildObjectType: jest.fn(),
+ buildInterfaceType: jest.fn(),
+ buildUnionType: jest.fn(),
+ }
+ const cache = createMockCache()
+ const reporter = {
+ info: jest.fn(),
+ verbose: jest.fn(),
+ panic: jest.fn(),
+ activityTimer: () => {
+ return { start: jest.fn(), end: jest.fn() }
+ },
+ }
+
+ beforeEach(() => {
+ cache.get.mockClear()
+ process.env.GATSBY_WORKER_ID = `mocked`
+ })
+
+ it(`builds schema based on Contentful Content Model`, async () => {
+ await createSchemaCustomization(
+ { schema, actions, reporter, cache },
+ { spaceId: `testSpaceId` }
+ )
+
+ expect(schema.buildObjectType).toHaveBeenCalled()
+ expect(schema.buildObjectType.mock.calls).toMatchSnapshot()
+ expect(schema.buildInterfaceType).toHaveBeenCalled()
+ expect(schema.buildInterfaceType.mock.calls).toMatchSnapshot()
+ expect(schema.buildUnionType).toHaveBeenCalled()
+ expect(schema.buildUnionType.mock.calls).toMatchSnapshot()
+ })
+})
diff --git a/packages/gatsby-source-contentful/src/__tests__/download-contentful-assets.js b/packages/gatsby-source-contentful/src/__tests__/download-contentful-assets.js
index 72484e5b5a344..5412ba7461c13 100644
--- a/packages/gatsby-source-contentful/src/__tests__/download-contentful-assets.js
+++ b/packages/gatsby-source-contentful/src/__tests__/download-contentful-assets.js
@@ -1,9 +1,6 @@
// @ts-check
import { downloadContentfulAssets } from "../download-contentful-assets"
import { createAssetNodes } from "../normalize"
-import { createPluginConfig } from "../plugin-options"
-
-const pluginConfig = createPluginConfig({})
jest.mock(`gatsby-source-filesystem`, () => {
return {
@@ -16,6 +13,8 @@ jest.mock(`gatsby-source-filesystem`, () => {
})
const reporter = {
+ info: jest.fn(),
+ warn: jest.fn(),
createProgress: jest.fn(() => {
return {
start: jest.fn(),
@@ -24,25 +23,40 @@ const reporter = {
}),
}
+const mockedContentfulEntity = {
+ sys: { id: `mocked` },
+}
+
const fixtures = [
{
- sys: {
- id: `idJjXOxmNga8CSnQGEwTw`,
- type: `Asset`,
- createdAt: new Date().toISOString(),
- updatedAt: new Date().toISOString(),
+ id: `aa1beda4-b14a-50f5-89a8-222992a46a41`,
+ internal: {
+ owner: `gatsby-source-contentful`,
+ type: `ContentfulAsset`,
},
fields: {
+ title: { "en-US": `TundraUS`, fr: `TundraFR` },
file: {
"en-US": {
url: `//images.ctfassets.net/testing/us-image.jpeg`,
+ details: { size: 123, image: { width: 123, height: 123 } },
+ },
+ fr: {
+ url: `//images.ctfassets.net/testing/fr-image.jpg`,
+ details: { size: 123, image: { width: 123, height: 123 } },
},
},
},
- title: {
- "en-US": `TundraUS`,
- fr: `TundraFR`,
+ sys: {
+ id: `idJjXOxmNga8CSnQGEwTw`,
+ type: `Asset`,
+ createdAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ space: mockedContentfulEntity,
+ environment: mockedContentfulEntity,
+ revision: 123,
},
+ metadata: { tags: [] },
},
]
@@ -52,11 +66,7 @@ describe(`downloadContentfulAssets`, () => {
const createNodeId = jest.fn(id => id)
const defaultLocale = `en-US`
const locales = [{ code: `en-US` }, { code: `fr`, fallbackCode: `en-US` }]
- const space = {
- sys: {
- id: `1234`,
- },
- }
+ const space = mockedContentfulEntity
const cache = {
get: jest.fn(() => Promise.resolve(null)),
@@ -74,7 +84,6 @@ describe(`downloadContentfulAssets`, () => {
defaultLocale,
locales,
space,
- pluginConfig,
})
))
)
@@ -90,10 +99,10 @@ describe(`downloadContentfulAssets`, () => {
assetNodes.forEach(n => {
expect(cache.get).toHaveBeenCalledWith(
- `contentful-asset-${n.contentful_id}-${n.node_locale}`
+ `contentful-asset-${n.sys.id}-${n.sys.locale}`
)
expect(cache.set).toHaveBeenCalledWith(
- `contentful-asset-${n.contentful_id}-${n.node_locale}`,
+ `contentful-asset-${n.sys.id}-${n.sys.locale}`,
expect.anything()
)
})
diff --git a/packages/gatsby-source-contentful/src/__tests__/fetch-backoff.js b/packages/gatsby-source-contentful/src/__tests__/fetch-backoff.js
index f9c951e172bd6..f9a10c5e40317 100644
--- a/packages/gatsby-source-contentful/src/__tests__/fetch-backoff.js
+++ b/packages/gatsby-source-contentful/src/__tests__/fetch-backoff.js
@@ -83,6 +83,12 @@ describe(`fetch-backoff`, () => {
`/spaces/${options.spaceId}/environments/master/sync?initial=true&limit=444`
)
.reply(200, { items: [] })
+ // Tags
+ .get(
+ `/spaces/${options.spaceId}/environments/master/tags?skip=0&limit=1000&order=sys.createdAt`
+ )
+ .reply(200, { items: [] })
+
await fetchContent({ pluginConfig, reporter, syncToken: null })
expect(reporter.panic).not.toBeCalled()
@@ -120,6 +126,11 @@ describe(`fetch-backoff`, () => {
`/spaces/${options.spaceId}/environments/master/sync?initial=true&limit=1000`
)
.reply(200, { items: [] })
+ // Tags
+ .get(
+ `/spaces/${options.spaceId}/environments/master/tags?skip=0&limit=1000&order=sys.createdAt`
+ )
+ .reply(200, { items: [] })
await fetchContent({ pluginConfig, reporter, syncToken: null })
diff --git a/packages/gatsby-source-contentful/src/__tests__/fetch-network-errors.js b/packages/gatsby-source-contentful/src/__tests__/fetch-network-errors.js
index d629c5966cbce..abdda891b617b 100644
--- a/packages/gatsby-source-contentful/src/__tests__/fetch-network-errors.js
+++ b/packages/gatsby-source-contentful/src/__tests__/fetch-network-errors.js
@@ -66,6 +66,11 @@ describe(`fetch-retry`, () => {
`/spaces/${options.spaceId}/environments/master/sync?initial=true&limit=1000`
)
.reply(200, { items: [] })
+ // Tags
+ .get(
+ `/spaces/${options.spaceId}/environments/master/tags?skip=0&limit=1000&order=sys.createdAt`
+ )
+ .reply(200, { items: [] })
await fetchContent({ pluginConfig, reporter, syncToken: null })
diff --git a/packages/gatsby-source-contentful/src/__tests__/fetch.js b/packages/gatsby-source-contentful/src/__tests__/fetch.js
index ee3da2e893090..b7541b0c94af7 100644
--- a/packages/gatsby-source-contentful/src/__tests__/fetch.js
+++ b/packages/gatsby-source-contentful/src/__tests__/fetch.js
@@ -194,7 +194,7 @@ it(`calls contentful.getContentTypes with custom plugin option page limit`, asyn
})
describe(`Tags feature`, () => {
- it(`tags are disabled by default`, async () => {
+ it(`calls contentful.getTags`, async () => {
await fetchContent({
pluginConfig: createPluginConfig({
accessToken: `6f35edf0db39085e9b9c19bd92943e4519c77e72c852d961968665f1324bfc94`,
@@ -205,21 +205,6 @@ describe(`Tags feature`, () => {
syncToken: null,
})
- expect(reporter.panic).not.toBeCalled()
- expect(mockClient.getTags).not.toBeCalled()
- })
- it(`calls contentful.getTags when enabled`, async () => {
- await fetchContent({
- pluginConfig: createPluginConfig({
- accessToken: `6f35edf0db39085e9b9c19bd92943e4519c77e72c852d961968665f1324bfc94`,
- spaceId: `rocybtov1ozk`,
- pageLimit: 50,
- enableTags: true,
- }),
- reporter,
- syncToken: null,
- })
-
expect(reporter.panic).not.toBeCalled()
expect(mockClient.getTags).toHaveBeenCalledWith({
limit: 50,
diff --git a/packages/gatsby-source-contentful/src/__tests__/gatsby-node.js b/packages/gatsby-source-contentful/src/__tests__/gatsby-node.js
index 02789dc29e4b6..4e7c495d4e3d9 100644
--- a/packages/gatsby-source-contentful/src/__tests__/gatsby-node.js
+++ b/packages/gatsby-source-contentful/src/__tests__/gatsby-node.js
@@ -6,15 +6,8 @@ import { fetchContent, fetchContentTypes } from "../fetch"
import { makeId } from "../normalize"
import startersBlogFixture from "../__fixtures__/starter-blog-data"
-import richTextFixture from "../__fixtures__/rich-text-data"
-import restrictedContentTypeFixture from "../__fixtures__/restricted-content-type"
jest.mock(`../fetch`)
-jest.mock(`gatsby-core-utils`, () => {
- return {
- createContentDigest: () => `contentDigest`,
- }
-})
const defaultPluginOptions = { spaceId: `testSpaceId` }
@@ -46,12 +39,33 @@ describe(`gatsby-node`, () => {
}),
touchNode: jest.fn(),
}
- const schema = { buildObjectType: jest.fn(), buildInterfaceType: jest.fn() }
+
+ const schemaCustomizationTypes = []
+ const schema = {
+ buildObjectType: jest.fn(config => {
+ schemaCustomizationTypes.push({
+ typeOrTypeDef: { config },
+ plugin: { name: `gatsby-source-contentful` },
+ })
+ }),
+ buildInterfaceType: jest.fn(config => {
+ schemaCustomizationTypes.push({
+ typeOrTypeDef: { config },
+ plugin: { name: `gatsby-source-contentful` },
+ })
+ }),
+ }
+
const store = {
getState: jest.fn(() => {
- return { program: { directory: process.cwd() }, status: {} }
+ return {
+ program: { directory: process.cwd() },
+ status: {},
+ schemaCustomization: { types: schemaCustomizationTypes },
+ }
}),
}
+
const cache = createMockCache()
const getCache = jest.fn(() => cache)
const reporter = {
@@ -162,9 +176,14 @@ describe(`gatsby-node`, () => {
const referenceKey = `${currentContentType.name.toLowerCase()}___NODE`
const reference = references.get(linkId)
const linkedNode = getNode(linkId)
- reference[referenceKey] =
- reference[referenceKey] || linkedNode[referenceKey] || []
- reference[referenceKey].push(nodeId)
+ reference[referenceKey] = [
+ ...(reference[referenceKey] ||
+ linkedNode[referenceKey] ||
+ []),
+ ]
+ if (!reference[referenceKey].includes(nodeId)) {
+ reference[referenceKey].push(nodeId)
+ }
references.set(linkId, reference)
}
break
@@ -263,6 +282,8 @@ describe(`gatsby-node`, () => {
})
)
+ const file = getFieldValue(asset.fields.file, locale, defaultLocale)
+
// check if asset exists
expect(getNode(assetId)).toMatchObject({
title: getFieldValue(asset.fields.title, locale, defaultLocale),
@@ -271,7 +292,12 @@ describe(`gatsby-node`, () => {
locale,
defaultLocale
),
- file: getFieldValue(asset.fields.file, locale, defaultLocale),
+ contentType: file.contentType,
+ fileName: file.fileName,
+ url: file.url,
+ size: file.details.size,
+ width: file.details?.image?.width || null,
+ height: file.details?.image?.height || null,
})
})
})
@@ -315,6 +341,7 @@ describe(`gatsby-node`, () => {
cache.set.mockClear()
reporter.info.mockClear()
reporter.panic.mockClear()
+ schemaCustomizationTypes.length = 0
})
it(`should create nodes from initial payload`, async () => {
@@ -461,7 +488,7 @@ describe(`gatsby-node`, () => {
"Contentful: 0 deleted entries",
],
Array [
- "Contentful: 22 cached entries",
+ "Contentful: 8 cached entries",
],
Array [
"Contentful: 1 new assets",
@@ -551,7 +578,7 @@ describe(`gatsby-node`, () => {
"Contentful: 0 deleted entries",
],
Array [
- "Contentful: 28 cached entries",
+ "Contentful: 10 cached entries",
],
Array [
"Contentful: 0 new assets",
@@ -640,7 +667,7 @@ describe(`gatsby-node`, () => {
expect(actions.createNode).toHaveBeenCalledTimes(44)
expect(actions.deleteNode).toHaveBeenCalledTimes(2)
- expect(actions.touchNode).toHaveBeenCalledTimes(74)
+ expect(actions.touchNode).toHaveBeenCalledTimes(72)
expect(reporter.info.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
@@ -653,7 +680,7 @@ describe(`gatsby-node`, () => {
"Contentful: 1 deleted entries",
],
Array [
- "Contentful: 28 cached entries",
+ "Contentful: 10 cached entries",
],
Array [
"Contentful: 0 new assets",
@@ -726,7 +753,7 @@ describe(`gatsby-node`, () => {
expect(actions.createNode).toHaveBeenCalledTimes(44)
expect(actions.deleteNode).toHaveBeenCalledTimes(2)
- expect(actions.touchNode).toHaveBeenCalledTimes(74)
+ expect(actions.touchNode).toHaveBeenCalledTimes(72)
expect(reporter.info.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
@@ -739,7 +766,7 @@ describe(`gatsby-node`, () => {
"Contentful: 0 deleted entries",
],
Array [
- "Contentful: 28 cached entries",
+ "Contentful: 10 cached entries",
],
Array [
"Contentful: 0 new assets",
@@ -757,29 +784,6 @@ describe(`gatsby-node`, () => {
`)
})
- it(`stores rich text as raw with references attached`, async () => {
- // @ts-ignore
- fetchContent.mockImplementationOnce(richTextFixture.initialSync)
- // @ts-ignore
- fetchContentTypes.mockImplementationOnce(richTextFixture.contentTypeItems)
-
- // initial sync
- await simulateGatsbyBuild()
-
- const initNodes = getNodes()
-
- const homeNodes = initNodes.filter(
- ({ contentful_id: id }) => id === `6KpLS2NZyB3KAvDzWf4Ukh`
- )
- expect(homeNodes).toHaveLength(2)
- homeNodes.forEach(homeNode => {
- expect(homeNode.content.references___NODE).toStrictEqual([
- ...new Set(homeNode.content.references___NODE),
- ])
- expect(homeNode.content.references___NODE).toMatchSnapshot()
- })
- })
-
it(`panics when localeFilter reduces locale list to 0`, async () => {
// @ts-ignore
fetchContent.mockImplementationOnce(startersBlogFixture.initialSync)
@@ -800,54 +804,4 @@ describe(`gatsby-node`, () => {
})
)
})
-
- it(`panics when response contains restricted content types`, async () => {
- // @ts-ignore
- fetchContent.mockImplementationOnce(
- restrictedContentTypeFixture.initialSync
- )
- // @ts-ignore
- fetchContentTypes.mockImplementationOnce(
- restrictedContentTypeFixture.contentTypeItems
- )
-
- await simulateGatsbyBuild()
-
- expect(reporter.panic).toBeCalledWith(
- expect.objectContaining({
- context: {
- sourceMessage: `Restricted ContentType name found. The name "reference" is not allowed.`,
- },
- })
- )
- })
-
- it(`panics when response contains content type Tag while enableTags is true`, async () => {
- // @ts-ignore
- fetchContent.mockImplementationOnce(
- restrictedContentTypeFixture.initialSync
- )
- const contentTypesWithTag = () => {
- const manipulatedContentTypeItems =
- restrictedContentTypeFixture.contentTypeItems()
- manipulatedContentTypeItems[0].name = `Tag`
- return manipulatedContentTypeItems
- }
- // @ts-ignore
- fetchContentTypes.mockImplementationOnce(contentTypesWithTag)
-
- await simulateGatsbyBuild({
- spaceId: `mocked`,
- enableTags: true,
- useNameForId: true,
- })
-
- expect(reporter.panic).toBeCalledWith(
- expect.objectContaining({
- context: {
- sourceMessage: `Restricted ContentType name found. The name "tag" is not allowed.`,
- },
- })
- )
- })
})
diff --git a/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js b/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js
index 571691402f9bd..f182b8158652e 100644
--- a/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js
+++ b/packages/gatsby-source-contentful/src/__tests__/gatsby-plugin-image.js
@@ -95,7 +95,7 @@ describe(`contentful extend node type`, () => {
spaceId: `k8iqpp6u0ior`,
createdAt: `2021-03-22T10:10:34.647Z`,
updatedAt: `2021-03-22T10:10:34.647Z`,
- file: { contentType: `image/png` },
+ contentType: `image/png`,
title: `Contentful Logo PNG`,
description: ``,
node_locale: `en-US`,
diff --git a/packages/gatsby-source-contentful/src/__tests__/normalize.js b/packages/gatsby-source-contentful/src/__tests__/normalize.js
index 2709c07332fd7..491a976bb1501 100644
--- a/packages/gatsby-source-contentful/src/__tests__/normalize.js
+++ b/packages/gatsby-source-contentful/src/__tests__/normalize.js
@@ -1,11 +1,12 @@
// @ts-check
+import { restrictedNodeFields } from "../config"
import {
buildEntryList,
- buildResolvableSet,
+ buildFallbackChain,
buildForeignReferenceMap,
- createNodesForContentType,
+ buildResolvableSet,
createAssetNodes,
- buildFallbackChain,
+ createNodesForContentType,
getLocalizedField,
makeId,
} from "../normalize"
@@ -20,18 +21,8 @@ const {
} = require(`./data.json`)
const conflictFieldPrefix = `contentful_test`
-// restrictedNodeFields from here https://www.gatsbyjs.org/docs/node-interface/
-const restrictedNodeFields = [
- `id`,
- `children`,
- `contentful_id`,
- `parent`,
- `fields`,
- `internal`,
-]
const pluginConfig = createPluginConfig({})
-
const unstable_createNodeManifest = jest.fn()
// Counts the created nodes per node type
@@ -219,35 +210,28 @@ describe(`Process contentful data (by name)`, () => {
const nodeTypeCounts = countCreatedNodeTypesFromMock(createNode.mock)
- expect(Object.keys(nodeTypeCounts)).toHaveLength(15)
-
expect(nodeTypeCounts).toEqual(
expect.objectContaining({
+ ContentfulContentType: contentTypeItems.length,
+ // Generated child entities
+ ContentfulText: 38,
// 3 Brand Contentful entries
- ContentfulBrand: 6,
- contentfulBrandCompanyDescriptionTextNode: 6,
- contentfulBrandCompanyNameTextNode: 6,
+ ContentfulContentTypeBrand: 6,
// 2 Category Contentful entries
- ContentfulCategory: 4,
- contentfulCategoryCategoryDescriptionTextNode: 4,
- contentfulCategoryTitleTextNode: 4,
- ContentfulContentType: contentTypeItems.length,
+ ContentfulContentTypeCategory: 4,
// 1 JSON Test Contentful entry
- ContentfulJsonTest: 2,
- contentfulJsonTestJsonStringTestJsonNode: 2,
- contentfulJsonTestJsonTestJsonNode: 2,
+ ContentfulContentTypeJsonTest: 2,
// 4 Product Contentful entries
- ContentfulProduct: 8,
- contentfulProductProductDescriptionTextNode: 8,
- contentfulProductProductNameTextNode: 8,
+ ContentfulContentTypeProduct: 8,
// 1 Remark Test Contentful entry
- ContentfulRemarkTest: 2,
- contentfulRemarkTestContentTextNode: 2,
+ ContentfulContentTypeRemarkTest: 2,
})
)
+ expect(Object.keys(nodeTypeCounts)).toHaveLength(7)
+
// Relevant to compare to compare warm and cold situation
- expect(createNode.mock.calls.length).toBe(69) // "cold build entries" count
+ expect(createNode.mock.calls.length).toBe(65) // "cold build entries" count
})
it(`creates nodes for each asset`, () => {
@@ -262,7 +246,6 @@ describe(`Process contentful data (by name)`, () => {
defaultLocale,
locales,
space,
- pluginConfig,
})
})
const nodeTypeCounts = countCreatedNodeTypesFromMock(createNode.mock)
@@ -332,32 +315,13 @@ describe(`Skip existing nodes in warm build`, () => {
const nodeTypeCounts = countCreatedNodeTypesFromMock(createNode.mock)
- expect(Object.keys(nodeTypeCounts)).toHaveLength(15)
+ expect(nodeTypeCounts.ContentfulContentTypeCategory).toBe(3)
- expect(nodeTypeCounts).toEqual(
- expect.objectContaining({
- ContentfulBrand: 6,
- contentfulBrandCompanyDescriptionTextNode: 6,
- contentfulBrandCompanyNameTextNode: 6,
- // These 3 category entities matter as the first node is skipped in the test
- ContentfulCategory: 3,
- contentfulCategoryCategoryDescriptionTextNode: 3,
- contentfulCategoryTitleTextNode: 3,
- ContentfulContentType: 5,
- ContentfulJsonTest: 2,
- contentfulJsonTestJsonStringTestJsonNode: 2,
- contentfulJsonTestJsonTestJsonNode: 2,
- ContentfulProduct: 8,
- contentfulProductProductDescriptionTextNode: 8,
- contentfulProductProductNameTextNode: 8,
- ContentfulRemarkTest: 2,
- contentfulRemarkTestContentTextNode: 2,
- })
- )
+ expect(Object.keys(nodeTypeCounts)).toHaveLength(7)
// Relevant to compare to compare warm and cold situation
// This number ought to be less than the cold build
- expect(createNode.mock.calls.length).toBe(66) // "warm build where entry was not changed" count
+ expect(createNode.mock.calls.length).toBe(62) // "warm build where entry was not changed" count
})
})
@@ -394,7 +358,7 @@ describe(`Process existing mutated nodes in warm build`, () => {
return {
id,
internal: {
- contentDigest: entryList[0][0].sys.updatedAt + `changed`,
+ contentDigest: entryList[0][0].sys.publishedAt + `changed`,
},
}
}
@@ -423,36 +387,28 @@ describe(`Process existing mutated nodes in warm build`, () => {
const nodeTypeCounts = countCreatedNodeTypesFromMock(createNode.mock)
- expect(Object.keys(nodeTypeCounts)).toHaveLength(15)
-
expect(nodeTypeCounts).toEqual(
expect.objectContaining({
+ ContentfulContentType: contentTypeItems.length,
+ // Child entities
+ ContentfulText: 38,
// 3 Brand Contentful entries
- ContentfulBrand: 6,
- contentfulBrandCompanyDescriptionTextNode: 6,
- contentfulBrandCompanyNameTextNode: 6,
+ ContentfulContentTypeBrand: 6,
// 2 Category Contentful entries
- ContentfulCategory: 4,
- contentfulCategoryCategoryDescriptionTextNode: 4,
- contentfulCategoryTitleTextNode: 4,
- ContentfulContentType: contentTypeItems.length,
+ ContentfulContentTypeCategory: 4,
// 1 JSON Test Contentful entry
- ContentfulJsonTest: 2,
- contentfulJsonTestJsonStringTestJsonNode: 2,
- contentfulJsonTestJsonTestJsonNode: 2,
+ ContentfulContentTypeJsonTest: 2,
// 4 Product Contentful entries
- ContentfulProduct: 8,
- contentfulProductProductDescriptionTextNode: 8,
- contentfulProductProductNameTextNode: 8,
+ ContentfulContentTypeProduct: 8,
// 1 Remark Test Contentful entry
- ContentfulRemarkTest: 2,
- contentfulRemarkTestContentTextNode: 2,
+ ContentfulContentTypeRemarkTest: 2,
})
)
+ expect(Object.keys(nodeTypeCounts)).toHaveLength(7)
// Relevant to compare to compare warm and cold situation
// This number ought to be the same as the cold build
- expect(createNode.mock.calls.length).toBe(69) // "warm build where entry was changed" count
+ expect(createNode.mock.calls.length).toBe(65) // "warm build where entry was changed" count
})
})
@@ -526,32 +482,23 @@ describe(`Process contentful data (by id)`, () => {
})
const nodeTypeCounts = countCreatedNodeTypesFromMock(createNode.mock)
- expect(Object.keys(nodeTypeCounts)).toHaveLength(15)
-
expect(nodeTypeCounts).toEqual(
expect.objectContaining({
+ ContentfulContentType: contentTypeItems.length,
// 3 Brand Contentful entries
- ContentfulSFzTZbSuM8CoEwygeUYes: 6,
- contentfulSFzTZbSuM8CoEwygeUYesCompanyDescriptionTextNode: 6,
- contentfulSFzTZbSuM8CoEwygeUYesCompanyNameTextNode: 6,
+ ContentfulContentTypeSFzTZbSuM8CoEwygeUYes: 6,
// 2 Category Contentful entries
- Contentful6XwpTaSiiI2Ak2Ww0Oi6Qa: 4,
- contentful6XwpTaSiiI2Ak2Ww0Oi6QaCategoryDescriptionTextNode: 4,
- contentful6XwpTaSiiI2Ak2Ww0Oi6QaTitleTextNode: 4,
- ContentfulContentType: contentTypeItems.length,
+ ContentfulContentType6XwpTaSiiI2Ak2Ww0Oi6Qa: 4,
// 1 JSON Test Contentful entry
- ContentfulJsonTest: 2,
- contentfulJsonTestJsonStringTestJsonNode: 2,
- contentfulJsonTestJsonTestJsonNode: 2,
+ ContentfulContentTypeJsonTest: 2,
// 4 Product Contentful entries
- Contentful2PqfXuJwE8QSyKuM0U6W8M: 8,
- contentful2PqfXuJwE8QSyKuM0U6W8MProductDescriptionTextNode: 8,
- contentful2PqfXuJwE8QSyKuM0U6W8MProductNameTextNode: 8,
+ ContentfulContentType2PqfXuJwE8QSyKuM0U6W8M: 8,
// 1 Remark Test Contentful entry
- ContentfulRemarkTest: 2,
- contentfulRemarkTestContentTextNode: 2,
+ ContentfulContentTypeRemarkTest: 2,
})
)
+
+ expect(Object.keys(nodeTypeCounts)).toHaveLength(7)
})
})
diff --git a/packages/gatsby-source-contentful/src/__tests__/rich-text.js b/packages/gatsby-source-contentful/src/__tests__/rich-text.js
index 46ad833a3bd55..9df80a17a5a7a 100644
--- a/packages/gatsby-source-contentful/src/__tests__/rich-text.js
+++ b/packages/gatsby-source-contentful/src/__tests__/rich-text.js
@@ -5,12 +5,13 @@
// @ts-check
import React from "react"
import { render } from "@testing-library/react"
-import { renderRichText } from "gatsby-source-contentful/rich-text"
+import { renderRichText } from "../rich-text"
import { BLOCKS, INLINES } from "@contentful/rich-text-types"
+import { getRichTextEntityLinks } from "@contentful/rich-text-links"
import { initialSync } from "../__fixtures__/rich-text-data"
import { cloneDeep } from "lodash"
-const raw = JSON.stringify({
+const json = {
nodeType: `document`,
data: {},
content: [
@@ -406,120 +407,132 @@ const raw = JSON.stringify({
data: {},
},
],
-})
+}
const fixtures = initialSync().currentSyncData
+const fixturesEntriesMap = new Map()
+const fixturesAssetsMap = new Map()
-const references = [
- ...fixtures.entries.map(entity => {
- return {
- sys: entity.sys,
- contentful_id: entity.sys.id,
- __typename: `ContentfulContent`,
- ...entity.fields,
- }
- }),
- ...fixtures.assets.map(entity => {
- return {
- sys: entity.sys,
- contentful_id: entity.sys.id,
- __typename: `ContentfulAsset`,
- ...entity.fields,
- }
- }),
-]
+fixtures.entries.forEach(entity =>
+ fixturesEntriesMap.set(entity.sys.id, { sys: entity.sys, ...entity.fields })
+)
+fixtures.assets.forEach(entity =>
+ fixturesAssetsMap.set(entity.sys.id, { sys: entity.sys, ...entity.fields })
+)
+
+const links = {
+ assets: {
+ block: getRichTextEntityLinks(json, `embedded-asset-block`)[`Asset`].map(
+ entity => fixturesAssetsMap.get(entity.id)
+ ),
+ hyperlink: getRichTextEntityLinks(json, `asset-hyperlink`)[`Asset`].map(
+ entity => fixturesAssetsMap.get(entity.id)
+ ),
+ },
+ entries: {
+ inline: getRichTextEntityLinks(json, `embedded-entry-inline`)[`Entry`].map(
+ entity => fixturesEntriesMap.get(entity.id)
+ ),
+ block: getRichTextEntityLinks(json, `embedded-entry-block`)[`Entry`].map(
+ entity => fixturesEntriesMap.get(entity.id)
+ ),
+ hyperlink: getRichTextEntityLinks(json, `entry-hyperlink`)[`Entry`].map(
+ entity => fixturesEntriesMap.get(entity.id)
+ ),
+ },
+}
describe(`rich text`, () => {
test(`renders with default options`, () => {
const { container } = render(
- <>
- {renderRichText({
- raw: cloneDeep(raw),
- references: cloneDeep(references),
- })}
- >
+ <>{renderRichText({ json: cloneDeep(json), links: cloneDeep(links) })}>
)
expect(container).toMatchSnapshot()
})
test(`renders with custom options`, () => {
- const options = {
- renderNode: {
- [INLINES.EMBEDDED_ENTRY]: node => {
- if (!node.data.target) {
- return (
-
- Unresolved INLINE ENTRY: {JSON.stringify(node, null, 2)}
-
- )
- }
- return (
-
- Resolved inline Entry ({node.data.target.contentful_id})
-
- )
- },
- [INLINES.ENTRY_HYPERLINK]: node => {
- if (!node.data.target) {
- return (
-
- Unresolved ENTRY HYPERLINK: {JSON.stringify(node, null, 2)}
-
- )
- }
- return (
-
- Resolved entry Hyperlink ({node.data.target.contentful_id})
-
- )
- },
- [INLINES.ASSET_HYPERLINK]: node => {
- if (!node.data.target) {
- return (
-
- Unresolved ASSET HYPERLINK: {JSON.stringify(node, null, 2)}
-
- )
- }
- return (
-
- Resolved asset Hyperlink ({node.data.target.contentful_id})
-
- )
- },
- [BLOCKS.EMBEDDED_ENTRY]: node => {
- if (!node.data.target) {
+ const makeOptions = ({
+ assetBlockMap,
+ assetHyperlinkMap,
+ entryBlockMap,
+ entryInlineMap,
+ entryHyperlinkMap,
+ }) => {
+ return {
+ renderNode: {
+ [INLINES.EMBEDDED_ENTRY]: node => {
+ const entry = entryInlineMap.get(node?.data?.target?.sys.id)
+ if (!entry) {
+ return (
+
+ Unresolved INLINE ENTRY:{` `}
+ {JSON.stringify({ node, entryInlineMap }, null, 2)}
+
+ )
+ }
+ return
Resolved inline Entry ({entry.sys.id})
+ },
+ [INLINES.ENTRY_HYPERLINK]: node => {
+ const entry = entryHyperlinkMap.get(node?.data?.target?.sys.id)
+ if (!entry) {
+ return (
+
+ Unresolved ENTRY HYPERLINK: {JSON.stringify(node, null, 2)}
+
+ )
+ }
+ return
Resolved entry Hyperlink ({entry.sys.id})
+ },
+ [INLINES.ASSET_HYPERLINK]: node => {
+ const entry = assetHyperlinkMap.get(node?.data?.target?.sys.id)
+ if (!entry) {
+ return (
+
+ Unresolved ASSET HYPERLINK: {JSON.stringify(node, null, 2)}
+
+ )
+ }
+ return
Resolved asset Hyperlink ({entry.sys.id})
+ },
+ [BLOCKS.EMBEDDED_ENTRY]: node => {
+ const entry = entryBlockMap.get(node?.data?.target?.sys.id)
+ if (!entry) {
+ return (
+
+ Unresolved ENTRY !!!!": {JSON.stringify(node, null, 2)}
+
+ )
+ }
return (
-
Unresolved ENTRY !!!!": {JSON.stringify(node, null, 2)}
+
+ Resolved embedded Entry: {entry.title[`en-US`]} ({entry.sys.id})
+
)
- }
- return (
-
- Resolved embedded Entry: {node.data.target.title[`en-US`]} (
- {node.data.target.contentful_id})
-
- )
- },
- [BLOCKS.EMBEDDED_ASSET]: node => {
- if (!node.data.target) {
+ },
+ [BLOCKS.EMBEDDED_ASSET]: node => {
+ const entry = assetBlockMap.get(node?.data?.target?.sys.id)
+ if (!entry) {
+ return (
+
+ Unresolved ASSET !!!!": {JSON.stringify(node, null, 2)}
+
+ )
+ }
return (
-
Unresolved ASSET !!!!": {JSON.stringify(node, null, 2)}
+
+ Resolved embedded Asset: {entry.title[`en-US`]} ({entry.sys.id})
+
)
- }
- return (
-
- Resolved embedded Asset: {node.data.target.title[`en-US`]} (
- {node.data.target.contentful_id})
-
- )
+ },
},
- },
+ }
}
+
const { container } = render(
<>
{renderRichText(
- { raw: cloneDeep(raw), references: cloneDeep(references) },
- options
+ { json: cloneDeep(json), links: cloneDeep(links) },
+ makeOptions
)}
>
)
diff --git a/packages/gatsby-source-contentful/src/config.js b/packages/gatsby-source-contentful/src/config.js
new file mode 100644
index 0000000000000..0fd811c357a80
--- /dev/null
+++ b/packages/gatsby-source-contentful/src/config.js
@@ -0,0 +1,10 @@
+export const restrictedNodeFields = [
+ // restrictedNodeFields from here https://www.gatsbyjs.org/docs/node-interface/
+ `id`,
+ `children`,
+ `parent`,
+ `fields`,
+ `internal`,
+ // Contentful Common resource attributes: https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/common-resource-attributes
+ `sys`,
+]
diff --git a/packages/gatsby-source-contentful/src/create-schema-customization.js b/packages/gatsby-source-contentful/src/create-schema-customization.js
index 6e16ef59330d3..ee4dc341b46f6 100644
--- a/packages/gatsby-source-contentful/src/create-schema-customization.js
+++ b/packages/gatsby-source-contentful/src/create-schema-customization.js
@@ -1,8 +1,8 @@
// @ts-check
import _ from "lodash"
import { fetchContentTypes } from "./fetch"
+import { generateSchema } from "./generate-schema"
import { createPluginConfig } from "./plugin-options"
-import { CODES } from "./report"
async function getContentTypesFromContentful({
cache,
@@ -12,33 +12,6 @@ async function getContentTypesFromContentful({
// Get content type items from Contentful
const contentTypeItems = await fetchContentTypes({ pluginConfig, reporter })
- // Check for restricted content type names and set id based on useNameForId
- const useNameForId = pluginConfig.get(`useNameForId`)
- const restrictedContentTypes = [`entity`, `reference`, `asset`]
-
- if (pluginConfig.get(`enableTags`)) {
- restrictedContentTypes.push(`tag`)
- }
-
- contentTypeItems.forEach(contentTypeItem => {
- // 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)
- let contentTypeItemId = contentTypeItem.sys.id
- if (useNameForId) {
- contentTypeItemId = contentTypeItem.name.toLowerCase()
- }
-
- if (restrictedContentTypes.includes(contentTypeItemId)) {
- reporter.panic({
- id: CODES.FetchContentTypes,
- context: {
- sourceMessage: `Restricted ContentType name found. The name "${contentTypeItemId}" is not allowed.`,
- },
- })
- }
- })
-
// Store processed content types in cache for sourceNodes
const sourceId = `${pluginConfig.get(`spaceId`)}-${pluginConfig.get(
`environment`
@@ -71,84 +44,6 @@ export async function createSchemaCustomization(
})
}
- const contentfulTypes = [
- schema.buildInterfaceType({
- name: `ContentfulEntry`,
- fields: {
- contentful_id: { type: `String!` },
- id: { type: `ID!` },
- node_locale: { type: `String!` },
- },
- extensions: { infer: false },
- interfaces: [`Node`],
- }),
- schema.buildInterfaceType({
- name: `ContentfulReference`,
- fields: {
- contentful_id: { type: `String!` },
- id: { type: `ID!` },
- },
- extensions: { infer: false },
- }),
- schema.buildObjectType({
- name: `ContentfulAsset`,
- fields: {
- contentful_id: { type: `String!` },
- id: { type: `ID!` },
- ...(pluginConfig.get(`downloadLocal`)
- ? {
- localFile: {
- type: `File`,
- extensions: {
- link: {
- from: `fields.localFile`,
- },
- },
- },
- }
- : {}),
- },
- interfaces: [`ContentfulReference`, `Node`],
- }),
- ]
-
- // Create types for each content type
- contentTypeItems.forEach(contentTypeItem =>
- contentfulTypes.push(
- schema.buildObjectType({
- name: _.upperFirst(
- _.camelCase(
- `Contentful ${
- pluginConfig.get(`useNameForId`)
- ? contentTypeItem.name
- : contentTypeItem.sys.id
- }`
- )
- ),
- fields: {
- contentful_id: { type: `String!` },
- id: { type: `ID!` },
- node_locale: { type: `String!` },
- },
- interfaces: [`ContentfulReference`, `ContentfulEntry`, `Node`],
- })
- )
- )
-
- if (pluginConfig.get(`enableTags`)) {
- contentfulTypes.push(
- schema.buildObjectType({
- name: `ContentfulTag`,
- fields: {
- name: { type: `String!` },
- contentful_id: { type: `String!` },
- id: { type: `ID!` },
- },
- interfaces: [`Node`],
- extensions: { dontInfer: {} },
- })
- )
- }
-
- createTypes(contentfulTypes)
+ // Generate schemas based on Contentful content model
+ generateSchema({ createTypes, schema, pluginConfig, contentTypeItems })
}
diff --git a/packages/gatsby-source-contentful/src/download-contentful-assets.js b/packages/gatsby-source-contentful/src/download-contentful-assets.js
index a6c0c28d0e653..c15c162a9168f 100644
--- a/packages/gatsby-source-contentful/src/download-contentful-assets.js
+++ b/packages/gatsby-source-contentful/src/download-contentful-assets.js
@@ -48,23 +48,19 @@ export async function downloadContentfulAssets(gatsbyFunctions) {
)
bar.start()
await distributeWorkload(
- assetNodes.map(node => async () => {
+ assetNodes.map(assetNode => async () => {
let fileNodeID
- const { contentful_id: id, node_locale: locale } = node
+ const {
+ sys: { id, locale },
+ } = assetNode
const remoteDataCacheKey = `contentful-asset-${id}-${locale}`
const cacheRemoteData = await cache.get(remoteDataCacheKey)
- if (!node.file) {
- reporter.log(id, locale)
- reporter.warn(`The asset with id: ${id}, contains no file.`)
- return Promise.resolve()
- }
- if (!node.file.url) {
- reporter.warn(
- `The asset with id: ${id} has a file but the file contains no url.`
- )
+
+ if (!assetNode.url) {
+ reporter.warn(`The asset with id: ${id} has no url.`)
return Promise.resolve()
}
- const url = createUrl(node.file.url)
+ const url = createUrl(assetNode.url)
// Avoid downloading the asset again if it's been cached
// Note: Contentful Assets do not provide useful metadata
@@ -94,10 +90,14 @@ export async function downloadContentfulAssets(gatsbyFunctions) {
}
if (fileNodeID) {
- createNodeField({ node, name: `localFile`, value: fileNodeID })
+ createNodeField({
+ node: assetNode,
+ name: `localFile`,
+ value: fileNodeID,
+ })
}
- return node
+ return assetNode
}),
assetDownloadWorkers
)
diff --git a/packages/gatsby-source-contentful/src/extend-node-type.js b/packages/gatsby-source-contentful/src/extend-node-type.js
index b25ceb2bfda8a..2e047524500b7 100644
--- a/packages/gatsby-source-contentful/src/extend-node-type.js
+++ b/packages/gatsby-source-contentful/src/extend-node-type.js
@@ -1,20 +1,9 @@
// @ts-check
import { stripIndent } from "common-tags"
-import {
- GraphQLBoolean,
- GraphQLInt,
- GraphQLJSON,
- GraphQLList,
-} from "gatsby/graphql"
+import { GraphQLBoolean, GraphQLInt, GraphQLJSON } from "gatsby/graphql"
import { resolveGatsbyImageData } from "./gatsby-plugin-image"
-import {
- ImageCropFocusType,
- ImageFormatType,
- ImageLayoutType,
- ImagePlaceholderType,
- ImageResizingBehavior,
-} from "./schemes"
+import { ImageCropFocusType, ImageResizingBehavior } from "./schemes"
export async function setFieldsOnGraphQLNodeType({ type, cache }) {
if (type.name !== `ContentfulAsset`) {
diff --git a/packages/gatsby-source-contentful/src/fetch.js b/packages/gatsby-source-contentful/src/fetch.js
index 209153198fefa..6815e6abb73c6 100644
--- a/packages/gatsby-source-contentful/src/fetch.js
+++ b/packages/gatsby-source-contentful/src/fetch.js
@@ -325,22 +325,20 @@ export async function fetchContent({ syncToken, pluginConfig, reporter }) {
}
// We need to fetch tags with the non-sync API as the sync API doesn't support this.
- let tagItems = []
- if (pluginConfig.get(`enableTags`)) {
- try {
- const tagsResult = await pagedGet(client, `getTags`, pageLimit)
- tagItems = tagsResult.items
- reporter.verbose(`Tags fetched ${tagItems.length}`)
- } catch (e) {
- reporter.panic({
- id: CODES.FetchTags,
- context: {
- sourceMessage: `Error fetching tags: ${createContentfulErrorMessage(
- e
- )}`,
- },
- })
- }
+ let tagItems
+ try {
+ const tagsResult = await pagedGet(client, `getTags`, pageLimit)
+ tagItems = tagsResult.items
+ reporter.verbose(`Tags fetched ${tagItems.length}`)
+ } catch (e) {
+ reporter.panic({
+ id: CODES.FetchTags,
+ context: {
+ sourceMessage: `Error fetching tags: ${createContentfulErrorMessage(
+ e
+ )}`,
+ },
+ })
}
const result = {
diff --git a/packages/gatsby-source-contentful/src/gatsby-node.js b/packages/gatsby-source-contentful/src/gatsby-node.js
index 7e7981df2c86d..b9d0ec13607b7 100644
--- a/packages/gatsby-source-contentful/src/gatsby-node.js
+++ b/packages/gatsby-source-contentful/src/gatsby-node.js
@@ -6,8 +6,8 @@ import fetchRetry from "@vercel/fetch-retry"
import { maskText } from "./plugin-options"
export { createSchemaCustomization } from "./create-schema-customization"
-export { sourceNodes } from "./source-nodes"
export { setFieldsOnGraphQLNodeType } from "./extend-node-type"
+export { sourceNodes } from "./source-nodes"
const fetch = fetchRetry(origFetch)
diff --git a/packages/gatsby-source-contentful/src/gatsby-plugin-image.js b/packages/gatsby-source-contentful/src/gatsby-plugin-image.js
index 959b3a1fd9a77..73e4d9a78ee08 100644
--- a/packages/gatsby-source-contentful/src/gatsby-plugin-image.js
+++ b/packages/gatsby-source-contentful/src/gatsby-plugin-image.js
@@ -29,7 +29,7 @@ export const getBase64Image = (imageProps, cache) => {
// Keep aspect ratio, image format and other transform options
const { aspectRatio } = imageProps
- const originalFormat = imageProps.image.file.contentType.split(`/`)[1]
+ const originalFormat = imageProps.image.contentType.split(`/`)[1]
const toFormat = imageProps.options.toFormat
const imageOptions = {
...imageProps.options,
@@ -53,9 +53,7 @@ export const getBase64Image = (imageProps, cache) => {
}
const loadImage = async () => {
- const {
- file: { contentType },
- } = imageProps.image
+ const { contentType } = imageProps.image
const extension = mimeTypeExtensions.get(contentType)
@@ -81,10 +79,7 @@ export const getBase64Image = (imageProps, cache) => {
const getTracedSVG = async ({ image, options, cache }) => {
const { traceSVG } = await import(`gatsby-plugin-sharp`)
-
- const {
- file: { contentType, url: imgUrl, fileName },
- } = image
+ const { url: imgUrl, fileName, contentType } = image
if (contentType.indexOf(`image/`) !== 0) {
return null
@@ -104,7 +99,7 @@ const getTracedSVG = async ({ image, options, cache }) => {
return traceSVG({
file: {
internal: image.internal,
- name: image.file.fileName,
+ name: fileName,
extension,
absolutePath,
},
@@ -127,9 +122,7 @@ const getDominantColor = async ({ image, options, cache }) => {
}
try {
- const {
- file: { contentType, url: imgUrl, fileName },
- } = image
+ const { contentType, url: imgUrl, fileName } = image
if (contentType.indexOf(`image/`) !== 0) {
return null
@@ -170,20 +163,18 @@ const getDominantColor = async ({ image, options, cache }) => {
}
function getBasicImageProps(image, args) {
- let aspectRatio
+ let { width, height } = image
if (args.width && args.height) {
- aspectRatio = args.width / args.height
- } else {
- aspectRatio =
- image.file.details.image.width / image.file.details.image.height
+ width = args.width
+ height = args.height
}
return {
- baseUrl: image.file.url,
- contentType: image.file.contentType,
- aspectRatio,
- width: image.file.details.image.width,
- height: image.file.details.image.height,
+ baseUrl: image.url,
+ contentType: image.contentType,
+ aspectRatio: width / height,
+ width,
+ height,
}
}
diff --git a/packages/gatsby-source-contentful/src/generate-schema.js b/packages/gatsby-source-contentful/src/generate-schema.js
new file mode 100644
index 0000000000000..50dbd2096992f
--- /dev/null
+++ b/packages/gatsby-source-contentful/src/generate-schema.js
@@ -0,0 +1,461 @@
+import { getRichTextEntityLinks } from "@contentful/rich-text-links"
+
+import { makeTypeName } from "./normalize"
+
+// Contentful content type schemas
+const ContentfulDataTypes = new Map([
+ [
+ `Symbol`,
+ () => {
+ return { type: `String` }
+ },
+ ],
+ [
+ `Text`,
+ field => {
+ return {
+ type: `ContentfulText`,
+ extensions: {
+ link: { by: `id`, from: `${field.id}___NODE` },
+ },
+ }
+ },
+ ],
+ [
+ `Integer`,
+ () => {
+ return { type: `Int` }
+ },
+ ],
+ [
+ `Number`,
+ () => {
+ return { type: `Float` }
+ },
+ ],
+ [
+ `Date`,
+ () => {
+ return {
+ type: `Date`,
+ extensions: {
+ dateformat: {},
+ },
+ }
+ },
+ ],
+ [
+ `Object`,
+ () => {
+ return { type: `JSON` }
+ },
+ ],
+ [
+ `Boolean`,
+ () => {
+ return { type: `Boolean` }
+ },
+ ],
+ [
+ `Location`,
+ () => {
+ return { type: `ContentfulLocation` }
+ },
+ ],
+ [
+ `RichText`,
+ () => {
+ return { type: `ContentfulRichText` }
+ },
+ ],
+])
+
+const unionsNameSet = new Set()
+
+const getLinkFieldType = (linkType, field, schema, createTypes) => {
+ // Check for validations
+ const validations =
+ field.type === `Array` ? field.items?.validations : field?.validations
+
+ if (validations) {
+ // We only handle content type validations
+ const linkContentTypeValidation = validations.find(
+ ({ linkContentType }) => !!linkContentType
+ )
+ if (linkContentTypeValidation) {
+ const { linkContentType } = linkContentTypeValidation
+ const contentTypes = Array.isArray(linkContentType)
+ ? linkContentType
+ : [linkContentType]
+
+ // Full type names for union members, shorter variant for the union type name
+ const translatedTypeNames = contentTypes.map(typeName =>
+ makeTypeName(typeName)
+ )
+ const shortTypeNames = contentTypes.map(typeName =>
+ makeTypeName(typeName, ``)
+ )
+
+ // Single content type
+ if (translatedTypeNames.length === 1) {
+ return {
+ type: translatedTypeNames.shift(),
+ extensions: {
+ link: { by: `id`, from: `${field.id}___NODE` },
+ },
+ }
+ }
+
+ // Multiple content types
+ const unionName = [`UnionContentful`, ...shortTypeNames].join(``)
+
+ if (!unionsNameSet.has(unionName)) {
+ unionsNameSet.add(unionName)
+ createTypes(
+ schema.buildUnionType({
+ name: unionName,
+ types: translatedTypeNames,
+ })
+ )
+ }
+
+ return {
+ type: unionName,
+ extensions: {
+ link: { by: `id`, from: `${field.id}___NODE` },
+ },
+ }
+ }
+ }
+
+ return {
+ type: `Contentful${linkType}`,
+ extensions: {
+ link: { by: `id`, from: `${field.id}___NODE` },
+ },
+ }
+}
+
+const translateFieldType = (field, schema, createTypes) => {
+ let fieldType
+ if (field.type === `Array`) {
+ // Arrays of Contentful Links or primitive types
+ const fieldData =
+ field.items.type === `Link`
+ ? getLinkFieldType(field.items.linkType, field, schema, createTypes)
+ : translateFieldType(field.items, schema, createTypes)
+
+ fieldType = { ...fieldData, type: `[${fieldData.type}]` }
+ } else if (field.type === `Link`) {
+ // Contentful Link (reference) field types
+ fieldType = getLinkFieldType(field.linkType, field, schema, createTypes)
+ } else {
+ // Primitive field types
+ fieldType = ContentfulDataTypes.get(field.type)(field)
+ }
+
+ if (field.required) {
+ fieldType.type = `${fieldType.type}!`
+ }
+
+ return fieldType
+}
+
+export function generateSchema({
+ createTypes,
+ schema,
+ pluginConfig,
+ contentTypeItems,
+}) {
+ // Generic Types
+ createTypes(
+ schema.buildInterfaceType({
+ name: `ContentfulEntity`,
+ fields: {
+ id: { type: `ID!` },
+ sys: { type: `ContentfulSys!` },
+ metadata: { type: `ContentfulMetadata!` },
+ },
+ interfaces: [`Node`],
+ })
+ )
+
+ createTypes(
+ schema.buildInterfaceType({
+ name: `ContentfulEntry`,
+ fields: {
+ id: { type: `ID!` },
+ sys: { type: `ContentfulSys!` },
+ metadata: { type: `ContentfulMetadata!` },
+ },
+ interfaces: [`ContentfulEntity`, `Node`],
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulContentType`,
+ fields: {
+ id: { type: `ID!` },
+ name: { type: `String!` },
+ displayField: { type: `String!` },
+ description: { type: `String!` },
+ },
+ interfaces: [`Node`],
+ extensions: { dontInfer: {} },
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulSys`,
+ fields: {
+ id: { type: ` String!` },
+ type: { type: ` String!` },
+ spaceId: { type: ` String!` },
+ environmentId: { type: ` String!` },
+ contentType: {
+ type: `ContentfulContentType`,
+ extensions: {
+ link: { by: `id`, from: `contentType___NODE` },
+ },
+ },
+ firstPublishedAt: { type: ` Date!` },
+ publishedAt: { type: ` Date!` },
+ publishedVersion: { type: ` Int!` },
+ locale: { type: ` String!` },
+ },
+ interfaces: [`Node`],
+ extensions: { dontInfer: {} },
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulMetadata`,
+ fields: {
+ tags: {
+ type: `[ContentfulTag]!`,
+ extensions: {
+ link: { by: `id`, from: `tags___NODE` },
+ },
+ },
+ },
+ extensions: { dontInfer: {} },
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulTag`,
+ fields: {
+ name: { type: `String!` },
+ contentful_id: { type: `String!` },
+ id: { type: `ID!` },
+ },
+ interfaces: [`Node`],
+ extensions: { dontInfer: {} },
+ })
+ )
+
+ // Assets
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulAssetFields`,
+ fields: {
+ localFile: {
+ type: `File`,
+ extensions: {
+ link: {
+ by: `id`,
+ },
+ },
+ },
+ },
+ extensions: { dontInfer: {} },
+ })
+ )
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulAsset`,
+ fields: {
+ id: { type: `ID!` },
+ sys: { type: `ContentfulSys!` },
+ metadata: { type: `ContentfulMetadata!` },
+ title: { type: `String` },
+ description: { type: `String` },
+ contentType: { type: `String` },
+ fileName: { type: `String` },
+ url: { type: `String` },
+ size: { type: `Int` },
+ width: { type: `Int` },
+ height: { type: `Int` },
+ fields: { type: `ContentfulAssetFields` },
+ },
+ interfaces: [`ContentfulEntity`, `Node`],
+ extensions: { dontInfer: {} },
+ })
+ )
+
+ // Rich Text
+ const makeRichTextLinksResolver =
+ (nodeType, entityType) => (source, args, context) => {
+ const links = getRichTextEntityLinks(source, nodeType)[entityType].map(
+ ({ id }) => id
+ )
+
+ return context.nodeModel.getAllNodes().filter(
+ node =>
+ node.internal.owner === `gatsby-source-contentful` &&
+ node?.sys?.id &&
+ node?.sys?.type === entityType &&
+ links.includes(node.sys.id)
+ // @todo how can we check for correct space and environment? We need to access the sys field of the fields parent entry.
+ )
+ }
+
+ // Contentful specific types
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulRichTextAssets`,
+ fields: {
+ block: {
+ type: `[ContentfulAsset]!`,
+ resolve: makeRichTextLinksResolver(`embedded-asset-block`, `Asset`),
+ },
+ hyperlink: {
+ type: `[ContentfulAsset]!`,
+ resolve: makeRichTextLinksResolver(`asset-hyperlink`, `Asset`),
+ },
+ },
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulRichTextEntries`,
+ fields: {
+ inline: {
+ type: `[ContentfulEntry]!`,
+ resolve: makeRichTextLinksResolver(`embedded-entry-inline`, `Entry`),
+ },
+ block: {
+ type: `[ContentfulEntry]!`,
+ resolve: makeRichTextLinksResolver(`embedded-entry-block`, `Entry`),
+ },
+ hyperlink: {
+ type: `[ContentfulEntry]!`,
+ resolve: makeRichTextLinksResolver(`entry-hyperlink`, `Entry`),
+ },
+ },
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulRichTextLinks`,
+ fields: {
+ assets: {
+ type: `ContentfulRichTextAssets`,
+ resolve(source) {
+ return source
+ },
+ },
+ entries: {
+ type: `ContentfulRichTextEntries`,
+ resolve(source) {
+ return source
+ },
+ },
+ },
+ })
+ )
+
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulRichText`,
+ fields: {
+ json: {
+ type: `JSON`,
+ resolve(source) {
+ return source
+ },
+ },
+ links: {
+ type: `ContentfulRichTextLinks`,
+ resolve(source) {
+ return source
+ },
+ },
+ },
+ extensions: { dontInfer: {} },
+ })
+ )
+
+ // Location
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulLocation`,
+ fields: {
+ lat: { type: `Float!` },
+ lon: { type: `Float!` },
+ },
+ extensions: {
+ dontInfer: {},
+ },
+ })
+ )
+
+ // Text
+ // @todo Is there a way to have this as string and let transformer-remark replace it with an object?
+ createTypes(
+ schema.buildObjectType({
+ name: `ContentfulText`,
+ fields: {
+ raw: `String!`,
+ },
+ // @todo do we need a node interface here?
+ interfaces: [`Node`],
+ extensions: {
+ dontInfer: {},
+ },
+ })
+ )
+
+ // Content types
+ for (const contentTypeItem of contentTypeItems) {
+ try {
+ const fields = {}
+ contentTypeItem.fields.forEach(field => {
+ if (field.disabled || field.omitted) {
+ return
+ }
+ fields[field.id] = translateFieldType(field, schema, createTypes)
+ })
+
+ const type = pluginConfig.get(`useNameForId`)
+ ? contentTypeItem.name
+ : contentTypeItem.sys.id
+
+ createTypes(
+ schema.buildObjectType({
+ name: makeTypeName(type),
+ fields: {
+ id: { type: `ID!` },
+ sys: { type: `ContentfulSys!` },
+ metadata: { type: `ContentfulMetadata!` },
+ ...fields,
+ },
+ interfaces: [`ContentfulEntity`, `ContentfulEntry`, `Node`],
+ extensions: { dontInfer: {} },
+ })
+ )
+ } catch (err) {
+ err.message = `Unable to create schema for Contentful Content Type ${
+ contentTypeItem.name || contentTypeItem.sys.id
+ }:\n${err.message}`
+ console.log(err.stack)
+ throw err
+ }
+ }
+}
diff --git a/packages/gatsby-source-contentful/src/image-helpers.js b/packages/gatsby-source-contentful/src/image-helpers.js
index 7bfe412e93f69..dcfc5ecde2836 100644
--- a/packages/gatsby-source-contentful/src/image-helpers.js
+++ b/packages/gatsby-source-contentful/src/image-helpers.js
@@ -20,7 +20,7 @@ export const mimeTypeExtensions = new Map([
// Check if Contentful asset is actually an image
export function isImage(image) {
- return mimeTypeExtensions.has(image?.file?.contentType)
+ return mimeTypeExtensions.has(image?.contentType)
}
// Create a Contentful Image API url
diff --git a/packages/gatsby-source-contentful/src/normalize.js b/packages/gatsby-source-contentful/src/normalize.js
index adc6e976cd2b2..1a70117c62eb4 100644
--- a/packages/gatsby-source-contentful/src/normalize.js
+++ b/packages/gatsby-source-contentful/src/normalize.js
@@ -1,11 +1,10 @@
// @ts-check
-import stringify from "json-stringify-safe"
import _ from "lodash"
import { getGatsbyVersion } from "gatsby-core-utils"
import { lt, prerelease } from "semver"
-const typePrefix = `Contentful`
-const makeTypeName = type => _.upperFirst(_.camelCase(`${typePrefix} ${type}`))
+export const makeTypeName = (type, typePrefix = `ContentfulContentType`) =>
+ _.upperFirst(_.camelCase(`${typePrefix} ${type}`))
const GATSBY_VERSION_MANIFEST_V2 = `4.3.0`
const gatsbyVersion =
@@ -57,6 +56,10 @@ const makeMakeId =
(spaceId, id, type) =>
createNodeId(makeId({ spaceId, id, currentLocale, defaultLocale, type }))
+// Generates an unique id per space for reference resolving
+export const generateReferenceId = nodeOrLink =>
+ `${nodeOrLink.sys.id}___${nodeOrLink.sys.linkType || nodeOrLink.sys.type}`
+
export const buildEntryList = ({ contentTypeItems, currentSyncData }) => {
// Create buckets for each type sys.id that we care about (we will always want an array for each, even if its empty)
const map = new Map(
@@ -80,20 +83,18 @@ export const buildResolvableSet = ({
}) => {
const resolvable = new Set()
existingNodes.forEach(node => {
- // We need to add only root level resolvable (assets and entries)
- // Derived nodes (markdown or JSON) will be recreated if needed.
- resolvable.add(`${node.contentful_id}___${node.sys.type}`)
+ if (node.internal.owner === `gatsby-source-contentful` && node?.sys?.id) {
+ // We need to add only root level resolvable (assets and entries)
+ // Derived nodes (markdown or JSON) will be recreated if needed.
+ resolvable.add(generateReferenceId(node))
+ }
})
entryList.forEach(entries => {
- entries.forEach(entry =>
- resolvable.add(`${entry.sys.id}___${entry.sys.type}`)
- )
+ entries.forEach(entry => resolvable.add(generateReferenceId(entry)))
})
- assets.forEach(assetItem =>
- resolvable.add(`${assetItem.sys.id}___${assetItem.sys.type}`)
- )
+ assets.forEach(assetItem => resolvable.add(generateReferenceId(assetItem)))
return resolvable
}
@@ -134,7 +135,7 @@ export const buildForeignReferenceMap = ({
entryItemFieldValue[0].sys.id
) {
entryItemFieldValue.forEach(v => {
- const key = `${v.sys.id}___${v.sys.linkType || v.sys.type}`
+ const key = generateReferenceId(v)
// Don't create link to an unresolvable field.
if (!resolvable.has(key)) {
return
@@ -155,9 +156,7 @@ export const buildForeignReferenceMap = ({
entryItemFieldValue?.sys?.type &&
entryItemFieldValue.sys.id
) {
- const key = `${entryItemFieldValue.sys.id}___${
- entryItemFieldValue.sys.linkType || entryItemFieldValue.sys.type
- }`
+ const key = generateReferenceId(entryItemFieldValue)
// Don't create link to an unresolvable field.
if (!resolvable.has(key)) {
return
@@ -186,49 +185,19 @@ function prepareTextNode(id, node, key, text) {
const textNode = {
id,
parent: node.id,
- children: [],
- [key]: str,
+ raw: str,
internal: {
- type: _.camelCase(`${node.internal.type} ${key} TextNode`),
+ type: `ContentfulText`,
mediaType: `text/markdown`,
content: str,
- // entryItem.sys.updatedAt is source of truth from contentful
- contentDigest: node.updatedAt,
- },
- sys: {
- type: node.sys.type,
+ // entryItem.sys.publishedAt is source of truth from contentful
+ contentDigest: node.sys.publishedAt,
},
}
- node.children = node.children.concat([id])
-
return textNode
}
-function prepareJSONNode(id, node, key, content) {
- const str = JSON.stringify(content)
- const JSONNode = {
- ...(_.isPlainObject(content) ? { ...content } : { content: content }),
- id,
- parent: node.id,
- children: [],
- internal: {
- type: _.camelCase(`${node.internal.type} ${key} JSONNode`),
- mediaType: `application/json`,
- content: str,
- // entryItem.sys.updatedAt is source of truth from contentful
- contentDigest: node.updatedAt,
- },
- sys: {
- type: node.sys.type,
- },
- }
-
- node.children = node.children.concat([id])
-
- return JSONNode
-}
-
let numberOfContentSyncDebugLogs = 0
const maxContentSyncDebugLogTimes = 50
@@ -332,17 +301,23 @@ export const createNodesForContentType = ({
// Create a node for the content type
const contentTypeNode = {
id: createNodeId(contentTypeItemId),
- parent: null,
- children: [],
name: contentTypeItem.name,
displayField: contentTypeItem.displayField,
description: contentTypeItem.description,
internal: {
- type: `${makeTypeName(`ContentType`)}`,
+ type: `ContentfulContentType`,
contentDigest: contentTypeItem.sys.updatedAt,
},
+ // https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/common-resource-attributes
+ // https://www.contentful.com/developers/docs/references/graphql/#/reference/schema-generation/sys-field
sys: {
type: contentTypeItem.sys.type,
+ id: contentTypeItem.sys.id,
+ spaceId: contentTypeItem.sys.space.sys.id,
+ environmentId: contentTypeItem.sys.environment.sys.id,
+ firstPublishedAt: contentTypeItem.sys.createdAt,
+ publishedAt: contentTypeItem.sys.updatedAt,
+ publishedVersion: contentTypeItem.sys.revision,
},
}
@@ -421,11 +396,7 @@ export const createNodesForContentType = ({
// creating an empty node field in case when original key field value
// is empty due to links to missing entities
const resolvableEntryItemFieldValue = entryItemFieldValue
- .filter(function (v) {
- return resolvable.has(
- `${v.sys.id}___${v.sys.linkType || v.sys.type}`
- )
- })
+ .filter(v => resolvable.has(generateReferenceId(v)))
.map(function (v) {
return mId(
space.sys.id,
@@ -441,14 +412,7 @@ export const createNodesForContentType = ({
delete entryItemFields[entryItemFieldKey]
}
} else if (entryItemFieldValue?.sys?.type === `Link`) {
- if (
- resolvable.has(
- `${entryItemFieldValue.sys.id}___${
- entryItemFieldValue.sys.linkType ||
- entryItemFieldValue.sys.type
- }`
- )
- ) {
+ if (resolvable.has(generateReferenceId(entryItemFieldValue))) {
entryItemFields[`${entryItemFieldKey}___NODE`] = mId(
space.sys.id,
entryItemFieldValue.sys.id,
@@ -463,7 +427,7 @@ export const createNodesForContentType = ({
// Add reverse linkages if there are any for this node
const foreignReferences =
- foreignReferenceMap[`${entryItem.sys.id}___${entryItem.sys.type}`]
+ foreignReferenceMap[generateReferenceId(entryItem)]
if (foreignReferences) {
foreignReferences.forEach(foreignReference => {
const existingReference = entryItemFields[foreignReference.name]
@@ -494,19 +458,28 @@ export const createNodesForContentType = ({
})
}
+ // Create actual entry node
let entryNode = {
id: entryNodeId,
- spaceId: space.sys.id,
- contentful_id: entryItem.sys.id,
- createdAt: entryItem.sys.createdAt,
- updatedAt: entryItem.sys.updatedAt,
parent: contentTypeItemId,
children: [],
internal: {
type: `${makeTypeName(contentTypeItemId)}`,
+ // The content of an entry is guaranteed to be updated if and only if the .sys.updatedAt field changed
+ contentDigest: entryItem.sys.updatedAt,
},
+ // https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/common-resource-attributes
+ // https://www.contentful.com/developers/docs/references/graphql/#/reference/schema-generation/sys-field
sys: {
type: entryItem.sys.type,
+ id: entryItem.sys.id,
+ locale: locale.code,
+ spaceId: entryItem.sys.space.sys.id,
+ environmentId: entryItem.sys.environment.sys.id,
+ contentType___NODE: createNodeId(contentTypeItemId),
+ firstPublishedAt: entryItem.sys.createdAt,
+ publishedAt: entryItem.sys.updatedAt,
+ publishedVersion: entryItem.sys.revision,
},
}
@@ -518,16 +491,6 @@ export const createNodesForContentType = ({
unstable_createNodeManifest,
})
- // Revision applies to entries, assets, and content types
- if (entryItem.sys.revision) {
- entryNode.sys.revision = entryItem.sys.revision
- }
-
- // Content type applies to entries only
- if (entryItem.sys.contentType) {
- entryNode.sys.contentType = entryItem.sys.contentType
- }
-
// Replace text fields with text nodes so we can process their markdown
// into HTML.
Object.keys(entryItemFields).forEach(entryItemFieldKey => {
@@ -566,105 +529,6 @@ export const createNodesForContentType = ({
}
entryItemFields[`${entryItemFieldKey}___NODE`] = textNodeId
- delete entryItemFields[entryItemFieldKey]
- } else if (
- fieldType === `RichText` &&
- _.isPlainObject(entryItemFields[entryItemFieldKey])
- ) {
- const fieldValue = entryItemFields[entryItemFieldKey]
-
- const rawReferences = []
-
- // Locate all Contentful Links within the rich text data
- const traverse = obj => {
- // eslint-disable-next-line guard-for-in
- for (const k in obj) {
- const v = obj[k]
- if (v && v.sys && v.sys.type === `Link`) {
- rawReferences.push(v)
- } else if (v && typeof v === `object`) {
- traverse(v)
- }
- }
- }
-
- traverse(fieldValue)
-
- // Build up resolvable reference list
- const resolvableReferenceIds = new Set()
- rawReferences
- .filter(function (v) {
- return resolvable.has(
- `${v.sys.id}___${v.sys.linkType || v.sys.type}`
- )
- })
- .forEach(function (v) {
- resolvableReferenceIds.add(
- mId(space.sys.id, v.sys.id, v.sys.linkType || v.sys.type)
- )
- })
-
- entryItemFields[entryItemFieldKey] = {
- raw: stringify(fieldValue),
- references___NODE: [...resolvableReferenceIds],
- }
- } else if (
- fieldType === `Object` &&
- _.isPlainObject(entryItemFields[entryItemFieldKey])
- ) {
- const jsonNodeId = createNodeId(
- `${entryNodeId}${entryItemFieldKey}JSONNode`
- )
-
- // 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(jsonNodeId)
- if (
- existingNode?.internal?.contentDigest !== entryItem.sys.updatedAt
- ) {
- const jsonNode = prepareJSONNode(
- jsonNodeId,
- entryNode,
- entryItemFieldKey,
- entryItemFields[entryItemFieldKey]
- )
- childrenNodes.push(jsonNode)
- }
-
- entryItemFields[`${entryItemFieldKey}___NODE`] = jsonNodeId
- delete entryItemFields[entryItemFieldKey]
- } else if (
- fieldType === `Object` &&
- _.isArray(entryItemFields[entryItemFieldKey])
- ) {
- entryItemFields[`${entryItemFieldKey}___NODE`] = []
-
- entryItemFields[entryItemFieldKey].forEach((obj, i) => {
- const jsonNodeId = createNodeId(
- `${entryNodeId}${entryItemFieldKey}${i}JSONNode`
- )
-
- // 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(jsonNodeId)
- if (
- existingNode?.internal?.contentDigest !==
- entryItem.sys.updatedAt
- ) {
- const jsonNode = prepareJSONNode(
- jsonNodeId,
- entryNode,
- entryItemFieldKey,
- obj
- )
- childrenNodes.push(jsonNode)
- }
-
- entryItemFields[`${entryItemFieldKey}___NODE`].push(jsonNodeId)
- })
-
delete entryItemFields[entryItemFieldKey]
}
})
@@ -672,21 +536,12 @@ export const createNodesForContentType = ({
entryNode = {
...entryItemFields,
...entryNode,
- node_locale: locale.code,
- }
-
- // The content of an entry is guaranteed to be updated if and only if the .sys.updatedAt field changed
- entryNode.internal.contentDigest = entryItem.sys.updatedAt
-
- // Link tags
- if (pluginConfig.get(`enableTags`)) {
- entryNode.metadata = {
+ metadata: {
tags___NODE: entryItem.metadata.tags.map(tag =>
createNodeId(`ContentfulTag__${space.sys.id}__${tag.sys.id}`)
),
- }
+ },
}
-
return entryNode
})
.filter(Boolean)
@@ -709,7 +564,6 @@ export const createAssetNodes = ({
defaultLocale,
locales,
space,
- pluginConfig,
}) => {
const createNodePromises = []
locales.forEach(locale => {
@@ -724,45 +578,46 @@ export const createAssetNodes = ({
localesFallback,
})
+ const file = getField(assetItem.fields.file)
+
const assetNode = {
- contentful_id: assetItem.sys.id,
- spaceId: space.sys.id,
id: mId(space.sys.id, assetItem.sys.id, assetItem.sys.type),
- createdAt: assetItem.sys.createdAt,
- updatedAt: assetItem.sys.updatedAt,
parent: null,
children: [],
- file: assetItem.fields.file ? getField(assetItem.fields.file) : null,
- title: assetItem.fields.title ? getField(assetItem.fields.title) : ``,
- description: assetItem.fields.description
- ? getField(assetItem.fields.description)
- : ``,
- node_locale: locale.code,
internal: {
- type: `${makeTypeName(`Asset`)}`,
+ type: `ContentfulAsset`,
+ // The content of an asset is guaranteed to be updated if and only if the .sys.updatedAt field changed
+ contentDigest: assetItem.sys.updatedAt,
},
+ // https://www.contentful.com/developers/docs/references/content-delivery-api/#/introduction/common-resource-attributes
+ // https://www.contentful.com/developers/docs/references/graphql/#/reference/schema-generation/sys-field
sys: {
type: assetItem.sys.type,
+ id: assetItem.sys.id,
+ locale: locale.code,
+ spaceId: assetItem.sys.space.sys.id,
+ environmentId: assetItem.sys.environment.sys.id,
+ firstPublishedAt: assetItem.sys.createdAt,
+ publishedAt: assetItem.sys.updatedAt,
+ publishedVersion: assetItem.sys.revision,
},
- }
-
- // Link tags
- if (pluginConfig.get(`enableTags`)) {
- assetNode.metadata = {
+ metadata: {
tags___NODE: assetItem.metadata.tags.map(tag =>
createNodeId(`ContentfulTag__${space.sys.id}__${tag.sys.id}`)
),
- }
- }
-
- // Revision applies to entries, assets, and content types
- if (assetItem.sys.revision) {
- assetNode.sys.revision = assetItem.sys.revision
+ },
+ title: assetItem.fields.title ? getField(assetItem.fields.title) : ``,
+ description: assetItem.fields.description
+ ? getField(assetItem.fields.description)
+ : ``,
+ contentType: file.contentType,
+ fileName: file.fileName,
+ url: file.url,
+ size: file.details.size,
+ width: file.details?.image?.width || null,
+ height: file.details?.image?.height || null,
}
- // The content of an entry is guaranteed to be updated if and only if the .sys.updatedAt field changed
- assetNode.internal.contentDigest = assetItem.sys.updatedAt
-
// if the node hasn't changed, createNode may return `undefined` instead of a Promise on some versions of Gatsby
const maybePromise = createNode(assetNode)
diff --git a/packages/gatsby-source-contentful/src/plugin-options.js b/packages/gatsby-source-contentful/src/plugin-options.js
index 397995090cd83..44ce4fff02ffa 100644
--- a/packages/gatsby-source-contentful/src/plugin-options.js
+++ b/packages/gatsby-source-contentful/src/plugin-options.js
@@ -11,7 +11,6 @@ const defaultOptions = {
localeFilter: () => true,
pageLimit: DEFAULT_PAGE_LIMIT,
useNameForId: true,
- enableTags: false,
}
const createPluginConfig = pluginOptions => {
diff --git a/packages/gatsby-source-contentful/src/rich-text.js b/packages/gatsby-source-contentful/src/rich-text.js
index bed7d4ca7b6fd..26e3e19fe4a76 100644
--- a/packages/gatsby-source-contentful/src/rich-text.js
+++ b/packages/gatsby-source-contentful/src/rich-text.js
@@ -1,46 +1,52 @@
// @ts-check
import { documentToReactComponents } from "@contentful/rich-text-react-renderer"
-import resolveResponse from "contentful-resolve-response"
-export function renderRichText({ raw, references }, options = {}) {
- const richText = JSON.parse(raw)
+export function renderRichText({ json, links }, makeOptions = {}) {
+ const options =
+ typeof makeOptions === `function`
+ ? makeOptions(generateLinkMaps(links))
+ : makeOptions
- // If no references are given, there is no need to resolve them
- if (!references || !references.length) {
- return documentToReactComponents(richText, options)
+ return documentToReactComponents(json, options)
+}
+
+exports.renderRichText = renderRichText
+
+/**
+ * Helper function to simplify Rich Text rendering. Based on:
+ * https://www.contentful.com/blog/2021/04/14/rendering-linked-assets-entries-in-contentful/
+ */
+function generateLinkMaps(links) {
+ const assetBlockMap = new Map()
+ for (const asset of links?.assets.block || []) {
+ assetBlockMap.set(asset.sys.id, asset)
}
- // Create dummy response so we can use official libraries for resolving the entries
- const dummyResponse = {
- items: [
- {
- sys: { type: `Entry` },
- richText,
- },
- ],
- includes: {
- Entry: references
- .filter(({ __typename }) => __typename !== `ContentfulAsset`)
- .map(reference => {
- return {
- ...reference,
- sys: { type: `Entry`, id: reference.contentful_id },
- }
- }),
- Asset: references
- .filter(({ __typename }) => __typename === `ContentfulAsset`)
- .map(reference => {
- return {
- ...reference,
- sys: { type: `Asset`, id: reference.contentful_id },
- }
- }),
- },
+ const assetHyperlinkMap = new Map()
+ for (const asset of links?.assets.hyperlink || []) {
+ assetHyperlinkMap.set(asset.sys.id, asset)
}
- const resolved = resolveResponse(dummyResponse, {
- removeUnresolved: true,
- })
+ const entryBlockMap = new Map()
+ for (const entry of links?.entries.block || []) {
+ entryBlockMap.set(entry.sys.id, entry)
+ }
- return documentToReactComponents(resolved[0].richText, options)
+ const entryInlineMap = new Map()
+ for (const entry of links?.entries.inline || []) {
+ entryInlineMap.set(entry.sys.id, entry)
+ }
+
+ const entryHyperlinkMap = new Map()
+ for (const entry of links?.entries.hyperlink || []) {
+ entryHyperlinkMap.set(entry.sys.id, entry)
+ }
+
+ return {
+ assetBlockMap,
+ assetHyperlinkMap,
+ entryBlockMap,
+ entryInlineMap,
+ entryHyperlinkMap,
+ }
}
diff --git a/packages/gatsby-source-contentful/src/source-nodes.js b/packages/gatsby-source-contentful/src/source-nodes.js
index c2885ac61358b..5d35d3c5e1fc0 100644
--- a/packages/gatsby-source-contentful/src/source-nodes.js
+++ b/packages/gatsby-source-contentful/src/source-nodes.js
@@ -4,12 +4,14 @@ import _ from "lodash"
import { downloadContentfulAssets } from "./download-contentful-assets"
import { fetchContent } from "./fetch"
+
import {
buildEntryList,
buildForeignReferenceMap,
buildResolvableSet,
createAssetNodes,
createNodesForContentType,
+ generateReferenceId,
makeId,
} from "./normalize"
import { createPluginConfig } from "./plugin-options"
@@ -180,13 +182,12 @@ export async function sourceNodes(
)
processingActivity.start()
- // Array of all existing Contentful nodes
+ // Array of all existing Contentful entry and asset nodes
const existingNodes = getNodes().filter(
n =>
- n.internal.owner === `gatsby-source-contentful` &&
- (pluginConfig.get(`enableTags`)
- ? n.internal.type !== `ContentfulTag`
- : true)
+ (n.internal.owner === `gatsby-source-contentful` &&
+ n.internal.type.indexOf(`ContentfulContentType`) === 0) ||
+ n.internal.type === `ContentfulAsset`
)
// Report existing, new and updated nodes
@@ -200,6 +201,7 @@ export async function sourceNodes(
deletedEntry: currentSyncData.deletedEntries.length,
deletedAsset: currentSyncData.deletedAssets.length,
}
+
existingNodes.forEach(node => nodeCounts[`existing${node.sys.type}`]++)
currentSyncData.entries.forEach(entry =>
entry.sys.revision === 1 ? nodeCounts.newEntry++ : nodeCounts.updatedEntry++
@@ -219,7 +221,10 @@ export async function sourceNodes(
reporter.verbose(`Building Contentful reference map`)
- const entryList = buildEntryList({ contentTypeItems, currentSyncData })
+ const entryList = buildEntryList({
+ currentSyncData,
+ contentTypeItems,
+ })
const { assets } = currentSyncData
// Create map of resolvable ids so we can check links against them while creating
@@ -245,35 +250,104 @@ export async function sourceNodes(
const newOrUpdatedEntries = new Set()
entryList.forEach(entries => {
entries.forEach(entry => {
- newOrUpdatedEntries.add(`${entry.sys.id}___${entry.sys.type}`)
+ newOrUpdatedEntries.add(generateReferenceId(entry))
+ })
+ })
+ // const state = store.getState()
+ // console.log(
+ // { schema, keys: Object.keys(schema) },
+ // state
+ // )
+
+ // const typeMap = await schema.getTypeMap()
+
+ // console.log({ typeMap })
+
+ // const contentfulTypeDefinitions = new Map()
+ // store
+ // .getState()
+ // .schemaCustomization.types.filter(
+ // ({ plugin }) => plugin.name === `gatsby-source-contentful`
+ // )
+ // .forEach(({ typeOrTypeDef }) => {
+ // contentfulTypeDefinitions.set(
+ // typeOrTypeDef.config.name,
+ // typeOrTypeDef.config
+ // )
+ // // console.log(typeOrTypeDef.config.fields)
+ // })
+
+ // console.log(contentfulTypeDefinitions)
+
+ const { deletedEntries, deletedAssets } = currentSyncData
+
+ const allDeletedNodes = [...deletedEntries, ...deletedAssets]
+ const deletedNodeIds = []
+
+ locales.forEach(locale => {
+ allDeletedNodes.forEach(n => {
+ deletedNodeIds.push(
+ makeId({
+ spaceId: n.sys.space.sys.id,
+ id: n.sys.id,
+ type: n.sys.type,
+ currentLocale: locale.code,
+ defaultLocale,
+ })
+ )
})
})
- // Update existing entry nodes that weren't updated but that need reverse links added.
+ // Update existing entry nodes that weren't updated but that need references updated.
existingNodes
- .filter(n => !newOrUpdatedEntries.has(`${n.id}___${n.sys.type}`))
+ .filter(n => !newOrUpdatedEntries.has(generateReferenceId(n)))
.forEach(n => {
- if (
- n.contentful_id &&
- foreignReferenceMap[`${n.contentful_id}___${n.sys.type}`]
- ) {
- foreignReferenceMap[`${n.contentful_id}___${n.sys.type}`].forEach(
- foreignReference => {
- const { name, id } = foreignReference
-
- // Create new reference field when none exists
- if (!n[name]) {
- n[name] = [id]
- return
- }
-
- // Add non existing references to reference field
- if (n[name] && !n[name].includes(id)) {
- n[name].push(id)
- }
+ const nodeId = generateReferenceId(n)
+
+ if (foreignReferenceMap[nodeId]) {
+ foreignReferenceMap[nodeId].forEach(foreignReference => {
+ const { name } = foreignReference
+
+ const referencedNodeId = makeId({
+ ...foreignReference,
+ currentLocale: n.sys.locale,
+ defaultLocale,
+ })
+
+ // Create new reference field when none exists
+ if (!n[name]) {
+ n[name] = [referencedNodeId]
+ return
}
- )
+
+ // Add non existing references to reference field
+ if (n[name] && !n[name].includes(referencedNodeId)) {
+ n[name].push(referencedNodeId)
+ }
+ })
}
+
+ // Remove references to deleted nodes in regular reference fields and reverse reference fields
+ Object.keys(n)
+ .filter(fieldName => fieldName.endsWith(`___NODE`))
+ .forEach(fieldName => {
+ const referenceField = n[fieldName]
+
+ if (!referenceField) {
+ return
+ }
+
+ if (Array.isArray(referenceField)) {
+ n[fieldName] = referenceField.filter(
+ id => !deletedNodeIds.includes(id)
+ )
+ return
+ }
+
+ if (deletedNodeIds.includes(referenceField)) {
+ n[fieldName] = null
+ }
+ })
})
function deleteContentfulNode(node) {
@@ -297,14 +371,10 @@ export async function sourceNodes(
.filter(node => node)
localizedNodes.forEach(node => {
- // touchNode first, to populate typeOwners & avoid erroring
- touchNode(node)
deleteNode(node)
})
}
- const { deletedEntries, deletedAssets } = currentSyncData
-
if (deletedEntries.length || deletedAssets.length) {
const deletionActivity = reporter.activityTimer(
`Contentful: Deleting ${deletedEntries.length} nodes and ${deletedAssets.length} assets (${sourceId})`,
@@ -326,6 +396,7 @@ export async function sourceNodes(
)
creationActivity.start()
+ // Create nodes for each entry of each content type
for (let i = 0; i < contentTypeItems.length; i++) {
const contentTypeItem = contentTypeItems[i]
@@ -379,7 +450,6 @@ export async function sourceNodes(
defaultLocale,
locales,
space,
- pluginConfig,
})
))
)
diff --git a/packages/gatsby-transformer-sqip/src/extend-node-type.js b/packages/gatsby-transformer-sqip/src/extend-node-type.js
index d07b79a382f2d..7673cee8d488a 100644
--- a/packages/gatsby-transformer-sqip/src/extend-node-type.js
+++ b/packages/gatsby-transformer-sqip/src/extend-node-type.js
@@ -194,9 +194,7 @@ async function sqipContentful({ type, cache, store }) {
mimeTypeExtensions,
} = require(`gatsby-source-contentful/image-helpers`)
- const {
- file: { contentType, url: imgUrl, fileName },
- } = asset
+ const { contentType, url: imgUrl, fileName } = asset
if (!contentType.includes(`image/`)) {
return null
diff --git a/yarn.lock b/yarn.lock
index 248df0c638e88..da966434d87cd 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1499,6 +1499,13 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@contentful/rich-text-links@^14.1.2":
+ version "14.1.2"
+ resolved "https://registry.npmjs.org/@contentful/rich-text-links/-/rich-text-links-14.1.2.tgz#993cd086d55af11f5d31b76060c02a9866c93a01"
+ integrity sha512-oK+y/c42fOJOdRM6XDKNLqw7uwVHZUIRGKzk9fJLDaOt5tygDm0pvgJ9bkvadaBwbAioxlQ0hHS0i5JP+UHkvA==
+ dependencies:
+ "@contentful/rich-text-types" "^14.1.2"
+
"@contentful/rich-text-react-renderer@^14.1.3":
version "14.1.3"
resolved "https://registry.npmjs.org/@contentful/rich-text-react-renderer/-/rich-text-react-renderer-14.1.3.tgz#501136677742d0ad3f4b50fa2c12b17fc1d68cc8"