diff --git a/e2e-tests/contentful/gatsby-config.js b/e2e-tests/contentful/gatsby-config.js index 5d2a8e90d134d..9d12a02b4e5ec 100644 --- a/e2e-tests/contentful/gatsby-config.js +++ b/e2e-tests/contentful/gatsby-config.js @@ -11,7 +11,6 @@ module.exports = { // Use: https://www.gatsbyjs.com/docs/how-to/local-development/environment-variables/ spaceId: `k8iqpp6u0ior`, accessToken: `hO_7N0bLaCJFbu5nL3QVekwNeB_TNtg6tOCB_9qzKUw`, - enableTags: true, downloadLocal: true, }, }, diff --git a/e2e-tests/contentful/schema.gql b/e2e-tests/contentful/schema.gql index 7a501577d38cb..7118ffb9ac47c 100644 --- a/e2e-tests/contentful/schema.gql +++ b/e2e-tests/contentful/schema.gql @@ -1,4 +1,4 @@ -### Type definitions saved at 2021-09-25T11:33:25.217Z ### +### Type definitions saved at 2021-11-18T12:37:07.683Z ### type File implements Node @dontInfer { sourceInstanceName: String! @@ -32,6 +32,9 @@ type File implements Node @dontInfer { ctime: Date! @dateformat birthtime: Date @deprecated(reason: "Use `birthTime` instead") birthtimeMs: Float @deprecated(reason: "Use `birthTime` instead") + blksize: Int + blocks: Int + url: String } type Directory implements Node @dontInfer { @@ -73,6 +76,7 @@ type Site implements Node @dontInfer { siteMetadata: SiteSiteMetadata polyfill: Boolean pathPrefix: String + jsxRuntime: String } type SiteSiteMetadata { @@ -96,7 +100,8 @@ type SitePage implements Node @dontInfer { internalComponentName: String! componentChunkName: String! matchPath: String - pageContext: JSON + pageContext: JSON @proxy(from: "context", fromNode: false) + pluginCreator: SitePlugin @link(by: "id", from: "pluginCreatorId") } type SitePlugin implements Node @dontInfer { @@ -115,85 +120,36 @@ type SiteBuildMetadata implements Node @dontInfer { buildTime: Date @dateformat } -interface ContentfulEntry implements Node { - contentful_id: String! +interface ContentfulEntity implements Node { id: ID! - node_locale: String! -} - -interface ContentfulReference { - contentful_id: String! - id: ID! -} - -type ContentfulAsset implements ContentfulReference & Node @derivedTypes @dontInfer { - contentful_id: String! - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - file: ContentfulAssetFile - title: String - description: String - node_locale: String - sys: ContentfulAssetSys -} - -type ContentfulAssetFile @derivedTypes { - url: String - details: ContentfulAssetFileDetails - fileName: String - contentType: String -} - -type ContentfulAssetFileDetails @derivedTypes { - size: Int - image: ContentfulAssetFileDetailsImage -} - -type ContentfulAssetFileDetailsImage { - width: Int - height: Int -} - -type ContentfulAssetSys { - type: String - revision: Int + sys: ContentfulSys! } -type ContentfulNumber implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! - title: String - integerLocalized: Int - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulNumberSys - metadata: ContentfulNumberMetadata - decimal: Float - integer: Int - content_reference: [ContentfulContentReference] @link(by: "id", from: "content reference___NODE") @proxy(from: "content reference___NODE") - decimalLocalized: Float -} - -type ContentfulNumberSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulNumberSysContentType +type ContentfulSys implements Node @dontInfer { + type: String! + spaceId: String! + environmentId: String! + contentType: ContentfulContentType @link(by: "id", from: "contentType___NODE") + firstPublishedAt: Date! + publishedAt: Date! + publishedVersion: Int! + locale: String! } -type ContentfulNumberSysContentType @derivedTypes { - sys: ContentfulNumberSysContentTypeSys +type ContentfulContentType implements Node @dontInfer { + name: String! + displayField: String! + description: String! } -type ContentfulNumberSysContentTypeSys { - type: String - linkType: String - id: String +interface ContentfulEntry implements ContentfulEntity & Node { + id: ID! + sys: ContentfulSys! + metadata: ContentfulMetadata! } -type ContentfulNumberMetadata { - tags: [ContentfulTag] @link(by: "id", from: "tags___NODE") +type ContentfulMetadata @dontInfer { + tags: [ContentfulTag]! @link(by: "id", from: "tags___NODE") } type ContentfulTag implements Node @dontInfer { @@ -201,343 +157,176 @@ type ContentfulTag implements Node @dontInfer { contentful_id: String! } -type ContentfulContentReference implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! - title: String - one: ContentfulContentReferenceContentfulTextUnion @link(by: "id", from: "one___NODE") - content_reference: [ContentfulContentReference] @link(by: "id", from: "content reference___NODE") @proxy(from: "content reference___NODE") - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulContentReferenceSys - oneLocalized: ContentfulNumber @link(by: "id", from: "oneLocalized___NODE") - many: [ContentfulContentReferenceContentfulNumberContentfulTextUnion] @link(by: "id", from: "many___NODE") - manyLocalized: [ContentfulNumberContentfulTextUnion] @link(by: "id", from: "manyLocalized___NODE") -} - -union ContentfulContentReferenceContentfulTextUnion = ContentfulContentReference | ContentfulText - -type ContentfulContentReferenceSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulContentReferenceSysContentType -} - -type ContentfulContentReferenceSysContentType @derivedTypes { - sys: ContentfulContentReferenceSysContentTypeSys -} - -type ContentfulContentReferenceSysContentTypeSys { - type: String - linkType: String - id: String +type ContentfulAssetFields @dontInfer { + localFile: File @link(by: "id") } -union ContentfulContentReferenceContentfulNumberContentfulTextUnion = ContentfulContentReference | ContentfulNumber | ContentfulText - -union ContentfulNumberContentfulTextUnion = ContentfulNumber | ContentfulText - -type ContentfulText implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulAsset implements ContentfulEntity & Node @dontInfer { + sys: ContentfulSys! title: String - longLocalized: contentfulTextLongLocalizedTextNode @link(by: "id", from: "longLocalized___NODE") - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulTextSys - longMarkdown: contentfulTextLongMarkdownTextNode @link(by: "id", from: "longMarkdown___NODE") - shortLocalized: String - longPlain: contentfulTextLongPlainTextNode @link(by: "id", from: "longPlain___NODE") - shortList: [String] - short: String - content_reference: [ContentfulContentReference] @link(by: "id", from: "content reference___NODE") @proxy(from: "content reference___NODE") -} - -type contentfulTextLongLocalizedTextNode implements Node @derivedTypes @childOf(types: ["ContentfulText"]) @dontInfer { - longLocalized: String - sys: contentfulTextLongLocalizedTextNodeSys + description: String + contentType: String + fileName: String + url: String + size: Int + width: Int + height: Int + fields: ContentfulAssetFields } -type contentfulTextLongLocalizedTextNodeSys { - type: String +type ContentfulRichTextAssets { + block: [ContentfulAsset]! + hyperlink: [ContentfulAsset]! } -type ContentfulTextSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulTextSysContentType +type ContentfulRichTextEntries { + inline: [ContentfulEntry]! + block: [ContentfulEntry]! + hyperlink: [ContentfulEntry]! } -type ContentfulTextSysContentType @derivedTypes { - sys: ContentfulTextSysContentTypeSys +type ContentfulRichTextLinks { + assets: ContentfulRichTextAssets + entries: ContentfulRichTextEntries } -type ContentfulTextSysContentTypeSys { - type: String - linkType: String - id: String +type ContentfulRichText @dontInfer { + json: JSON + links: ContentfulRichTextLinks } -type contentfulTextLongMarkdownTextNode implements Node @derivedTypes @childOf(types: ["ContentfulText"]) @dontInfer { - longMarkdown: String - sys: contentfulTextLongMarkdownTextNodeSys +type ContentfulLocation @dontInfer { + lat: Float! + lon: Float! } -type contentfulTextLongMarkdownTextNodeSys { - type: String +type ContentfulText implements Node @dontInfer { + raw: String! } -type contentfulTextLongPlainTextNode implements Node @derivedTypes @childOf(types: ["ContentfulText"]) @dontInfer { - longPlain: String - sys: contentfulTextLongPlainTextNodeSys +type ContentfulContentTypeNumber implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! + title: String + integer: Int + integerLocalized: Int + decimal: Float + decimalLocalized: Float } -type contentfulTextLongPlainTextNodeSys { - type: String +type ContentfulContentTypeText implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! + title: String + short: String + shortLocalized: String + shortList: [String] + shortListLocalized: [String] + longPlain: ContentfulText @link(by: "id", from: "longPlain___NODE") + longMarkdown: ContentfulText @link(by: "id", from: "longMarkdown___NODE") + longLocalized: ContentfulText @link(by: "id", from: "longLocalized___NODE") } -type ContentfulMediaReference implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeMediaReference implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! title: String one: ContentfulAsset @link(by: "id", from: "one___NODE") - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulMediaReferenceSys oneLocalized: ContentfulAsset @link(by: "id", from: "oneLocalized___NODE") many: [ContentfulAsset] @link(by: "id", from: "many___NODE") manyLocalized: [ContentfulAsset] @link(by: "id", from: "manyLocalized___NODE") } -type ContentfulMediaReferenceSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulMediaReferenceSysContentType -} - -type ContentfulMediaReferenceSysContentType @derivedTypes { - sys: ContentfulMediaReferenceSysContentTypeSys -} - -type ContentfulMediaReferenceSysContentTypeSys { - type: String - linkType: String - id: String -} - -type ContentfulBoolean implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeBoolean implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! title: String - booleanLocalized: Boolean - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulBooleanSys boolean: Boolean + booleanLocalized: Boolean } -type ContentfulBooleanSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulBooleanSysContentType -} - -type ContentfulBooleanSysContentType @derivedTypes { - sys: ContentfulBooleanSysContentTypeSys -} - -type ContentfulBooleanSysContentTypeSys { - type: String - linkType: String - id: String -} - -type ContentfulDate implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeDate implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! title: String - dateTimeTimezone: Date @dateformat - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulDateSys date: Date @dateformat - dateLocalized: Date @dateformat dateTime: Date @dateformat + dateTimeTimezone: Date @dateformat + dateLocalized: Date @dateformat } -type ContentfulDateSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulDateSysContentType -} - -type ContentfulDateSysContentType @derivedTypes { - sys: ContentfulDateSysContentTypeSys -} - -type ContentfulDateSysContentTypeSys { - type: String - linkType: String - id: String -} - -type ContentfulLocation implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeLocation implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! title: String - locationLocalized: ContentfulLocationLocationLocalized - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulLocationSys - location: ContentfulLocationLocation -} - -type ContentfulLocationLocationLocalized { - lon: Float - lat: Float -} - -type ContentfulLocationSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulLocationSysContentType -} - -type ContentfulLocationSysContentType @derivedTypes { - sys: ContentfulLocationSysContentTypeSys -} - -type ContentfulLocationSysContentTypeSys { - type: String - linkType: String - id: String + location: ContentfulLocation + locationLocalized: ContentfulLocation } -type ContentfulLocationLocation { - lat: Float - lon: Float -} - -type ContentfulJson implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeJson implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! title: String - json: contentfulJsonJsonJsonNode @link(by: "id", from: "json___NODE") - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulJsonSys - jsonLocalized: contentfulJsonJsonLocalizedJsonNode @link(by: "id", from: "jsonLocalized___NODE") -} - -type contentfulJsonJsonJsonNode implements Node @derivedTypes @childOf(types: ["ContentfulJson"]) @dontInfer { - age: Int - city: String - name: String - sys: contentfulJsonJsonJsonNodeSys - Actors: [contentfulJsonJsonJsonNodeActors] -} - -type contentfulJsonJsonJsonNodeSys { - type: String -} - -type contentfulJsonJsonJsonNodeActors { - age: Int - name: String - wife: String - photo: String - weight: Float - Born_At: String @proxy(from: "Born At") - children: [String] - Birthdate: String - hasChildren: Boolean - hasGreyHair: Boolean -} - -type ContentfulJsonSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulJsonSysContentType -} - -type ContentfulJsonSysContentType @derivedTypes { - sys: ContentfulJsonSysContentTypeSys -} - -type ContentfulJsonSysContentTypeSys { - type: String - linkType: String - id: String + json: JSON + jsonLocalized: JSON } -type contentfulJsonJsonLocalizedJsonNode implements Node @derivedTypes @childOf(types: ["ContentfulJson"]) @dontInfer { - name: String - age: Int - city: String - sys: contentfulJsonJsonLocalizedJsonNodeSys -} - -type contentfulJsonJsonLocalizedJsonNodeSys { - type: String -} - -type ContentfulRichText implements ContentfulReference & ContentfulEntry & Node @derivedTypes @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeRichText implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! title: String - richText: ContentfulRichTextRichText - spaceId: String - createdAt: Date @dateformat - updatedAt: Date @dateformat - sys: ContentfulRichTextSys - richTextValidated: ContentfulRichTextRichTextValidated - richTextLocalized: ContentfulRichTextRichTextLocalized -} - -type ContentfulRichTextRichText { - raw: String - references: [ContentfulAssetContentfulContentReferenceContentfulLocationContentfulTextUnion] @link(by: "id", from: "references___NODE") -} - -union ContentfulAssetContentfulContentReferenceContentfulLocationContentfulTextUnion = ContentfulAsset | ContentfulContentReference | ContentfulLocation | ContentfulText - -type ContentfulRichTextSys @derivedTypes { - type: String - revision: Int - contentType: ContentfulRichTextSysContentType -} - -type ContentfulRichTextSysContentType @derivedTypes { - sys: ContentfulRichTextSysContentTypeSys -} - -type ContentfulRichTextSysContentTypeSys { - type: String - linkType: String - id: String -} - -type ContentfulRichTextRichTextValidated { - raw: String - references: [ContentfulAssetContentfulLocationContentfulNumberContentfulTextUnion] @link(by: "id", from: "references___NODE") -} - -union ContentfulAssetContentfulLocationContentfulNumberContentfulTextUnion = ContentfulAsset | ContentfulLocation | ContentfulNumber | ContentfulText - -type ContentfulRichTextRichTextLocalized { - raw: String + richText: ContentfulRichText + richTextLocalized: ContentfulRichText + richTextValidated: ContentfulRichText } -type ContentfulValidatedContentReference implements ContentfulReference & ContentfulEntry & Node @dontInfer { - contentful_id: String! - node_locale: String! +type ContentfulContentTypeContentReference implements ContentfulEntity & ContentfulEntry & Node + @isPlaceholder + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! + title: String + one: ContentfulEntry @link(by: "id", from: "one___NODE") + oneLocalized: ContentfulEntry @link(by: "id", from: "oneLocalized___NODE") + many: [UnionContentfulContentReferenceNumberText] + @link(by: "id", from: "many___NODE") + manyLocalized: [ContentfulEntry] @link(by: "id", from: "manyLocalized___NODE") +} + +union UnionContentfulContentReferenceNumberText = + ContentfulContentTypeContentReference + | ContentfulContentTypeNumber + | ContentfulContentTypeText + +union UnionContentfulNumberText = + ContentfulContentTypeNumber + | ContentfulContentTypeText + +type ContentfulContentTypeValidatedContentReference implements ContentfulEntity & ContentfulEntry & Node + @dontInfer { + sys: ContentfulSys! + metadata: ContentfulMetadata! + title: String + oneItemSingleType: ContentfulContentTypeText + @link(by: "id", from: "oneItemSingleType___NODE") + oneItemManyTypes: UnionContentfulNumberText + @link(by: "id", from: "oneItemManyTypes___NODE") + oneItemAllTypes: ContentfulEntry + @link(by: "id", from: "oneItemAllTypes___NODE") + multipleItemsSingleType: [ContentfulContentTypeText] + @link(by: "id", from: "multipleItemsSingleType___NODE") + multipleItemsManyTypes: [UnionContentfulNumberText] + @link(by: "id", from: "multipleItemsManyTypes___NODE") + multipleItemsAllTypes: [ContentfulEntry] + @link(by: "id", from: "multipleItemsAllTypes___NODE") } type MarkdownHeading { @@ -567,7 +356,13 @@ type MarkdownWordCount { words: Int } -type MarkdownRemark implements Node @childOf(mimeTypes: ["text/markdown", "text/x-markdown"], types: ["contentfulTextLongPlainTextNode", "contentfulTextLongMarkdownTextNode", "contentfulTextLongLocalizedTextNode"]) @derivedTypes @dontInfer { +type MarkdownRemark implements Node + @childOf( + mimeTypes: ["text/markdown", "text/x-markdown"] + types: ["ContentfulText"] + ) + @derivedTypes + @dontInfer { frontmatter: MarkdownRemarkFrontmatter excerpt: String rawMarkdownBody: String @@ -577,13 +372,397 @@ type MarkdownRemarkFrontmatter { title: String } -type ContentfulContentType implements Node @derivedTypes @dontInfer { +enum ImageFormat { + NO_CHANGE + AUTO + JPG + PNG + WEBP + AVIF +} + +enum ImageFit { + COVER + CONTAIN + FILL + INSIDE + OUTSIDE +} + +enum ImageLayout { + FIXED + FULL_WIDTH + CONSTRAINED +} + +enum ImageCropFocus { + CENTER + NORTH + NORTHEAST + EAST + SOUTHEAST + SOUTH + SOUTHWEST + WEST + NORTHWEST + ENTROPY + ATTENTION +} + +input DuotoneGradient { + highlight: String! + shadow: String! + opacity: Int +} + +enum PotraceTurnPolicy { + TURNPOLICY_BLACK + TURNPOLICY_WHITE + TURNPOLICY_LEFT + TURNPOLICY_RIGHT + TURNPOLICY_MINORITY + TURNPOLICY_MAJORITY +} + +input Potrace { + turnPolicy: PotraceTurnPolicy + turdSize: Float + alphaMax: Float + optCurve: Boolean + optTolerance: Float + threshold: Int + blackOnWhite: Boolean + color: String + background: String +} + +type ImageSharpFixed { + base64: String + tracedSVG: String + aspectRatio: Float + width: Float! + height: Float! + src: String! + srcSet: String! + srcWebp: String + srcSetWebp: String + originalName: String +} + +type ImageSharpFluid { + base64: String + tracedSVG: String + aspectRatio: Float! + src: String! + srcSet: String! + srcWebp: String + srcSetWebp: String + sizes: String! + originalImg: String + originalName: String + presentationWidth: Int! + presentationHeight: Int! +} + +enum ImagePlaceholder { + DOMINANT_COLOR + TRACED_SVG + BLURRED + NONE +} + +input BlurredOptions { + """ + Width of the generated low-res preview. Default is 20px + """ + width: Int + + """ + Force the output format for the low-res preview. Default is to use the same + format as the input. You should rarely need to change this + """ + toFormat: ImageFormat +} + +input JPGOptions { + quality: Int + progressive: Boolean = true +} + +input PNGOptions { + quality: Int + compressionSpeed: Int = 4 +} + +input WebPOptions { + quality: Int +} + +input AVIFOptions { + quality: Int + lossless: Boolean + speed: Int +} + +input TransformOptions { + grayscale: Boolean = false + duotone: DuotoneGradient + rotate: Int = 0 + trim: Float = 0 + cropFocus: ImageCropFocus = ATTENTION + fit: ImageFit = COVER +} + +type ImageSharpOriginal { + width: Float + height: Float + src: String +} + +type ImageSharpResize { + src: String + tracedSVG: String + width: Int + height: Int + aspectRatio: Float + originalName: String +} + +type ImageSharp implements Node @childOf(types: ["File"]) @dontInfer { + fixed( + width: Int + height: Int + base64Width: Int + jpegProgressive: Boolean = true + pngCompressionSpeed: Int = 4 + grayscale: Boolean = false + duotone: DuotoneGradient + traceSVG: Potrace + quality: Int + jpegQuality: Int + pngQuality: Int + webpQuality: Int + toFormat: ImageFormat = AUTO + toFormatBase64: ImageFormat = AUTO + cropFocus: ImageCropFocus = ATTENTION + fit: ImageFit = COVER + background: String = "rgba(0,0,0,1)" + rotate: Int = 0 + trim: Float = 0 + ): ImageSharpFixed + fluid( + maxWidth: Int + maxHeight: Int + base64Width: Int + grayscale: Boolean = false + jpegProgressive: Boolean = true + pngCompressionSpeed: Int = 4 + duotone: DuotoneGradient + traceSVG: Potrace + quality: Int + jpegQuality: Int + pngQuality: Int + webpQuality: Int + toFormat: ImageFormat = AUTO + toFormatBase64: ImageFormat = AUTO + cropFocus: ImageCropFocus = ATTENTION + fit: ImageFit = COVER + background: String = "rgba(0,0,0,1)" + rotate: Int = 0 + trim: Float = 0 + sizes: String = "" + + """ + A list of image widths to be generated. Example: [ 200, 340, 520, 890 ] + """ + srcSetBreakpoints: [Int] = [] + ): ImageSharpFluid + gatsbyImageData( + """ + The layout for the image. + FIXED: A static image sized, that does not resize according to the screen width + FULL_WIDTH: The image resizes to fit its container. Pass a "sizes" option if + it isn't going to be the full width of the screen. + CONSTRAINED: Resizes to fit its container, up to a maximum width, at which point it will remain fixed in size. + """ + layout: ImageLayout = CONSTRAINED + + """ + The display width of the generated image for layout = FIXED, and the maximum + display width of the largest image for layout = CONSTRAINED. + Ignored if layout = FLUID. + """ + width: Int + + """ + The display height of the generated image for layout = FIXED, and the + maximum display height of the largest image for layout = CONSTRAINED. + The image will be cropped if the aspect ratio does not match the source + image. If omitted, it is calculated from the supplied width, + matching the aspect ratio of the source image. + """ + height: Int + + """ + If set along with width or height, this will set the value of the other + dimension to match the provided aspect ratio, cropping the image if needed. + If neither width or height is provided, height will be set based on the intrinsic width of the source image. + """ + aspectRatio: Float + + """ + Format of generated placeholder image, displayed while the main image loads. + BLURRED: a blurred, low resolution image, encoded as a base64 data URI (default) + DOMINANT_COLOR: a solid color, calculated from the dominant color of the image. + TRACED_SVG: a low-resolution traced SVG of the image. + NONE: no placeholder. Set "background" to use a fixed background color. + """ + placeholder: ImagePlaceholder + + """ + Options for the low-resolution placeholder image. Set placeholder to "BLURRED" to use this + """ + blurredOptions: BlurredOptions + + """ + Options for traced placeholder SVGs. You also should set placeholder to "TRACED_SVG". + """ + tracedSVGOptions: Potrace + + """ + The image formats to generate. Valid values are "AUTO" (meaning the same + format as the source image), "JPG", "PNG", "WEBP" and "AVIF". + The default value is [AUTO, WEBP], and you should rarely need to change + this. Take care if you specify JPG or PNG when you do + not know the formats of the source images, as this could lead to unwanted + results such as converting JPEGs to PNGs. Specifying + both PNG and JPG is not supported and will be ignored. + """ + formats: [ImageFormat] + + """ + A list of image pixel densities to generate. It will never generate images + larger than the source, and will always include a 1x image. + Default is [ 1, 2 ] for FIXED images, meaning 1x and 2x and [0.25, 0.5, 1, + 2] for CONSTRAINED. In this case, an image with a constrained layout + and width = 400 would generate images at 100, 200, 400 and 800px wide. + Ignored for FULL_WIDTH images, which use breakpoints instead + """ + outputPixelDensities: [Float] + + """ + Specifies the image widths to generate. For FIXED and CONSTRAINED images it + is better to allow these to be determined automatically, + based on the image size. For FULL_WIDTH images this can be used to override + the default, which is [750, 1080, 1366, 1920]. + It will never generate any images larger than the source. + """ + breakpoints: [Int] + + """ + The "sizes" property, passed to the img tag. This describes the display size of the image. + This does not affect the generated images, but is used by the browser to decide which images to download. + You should usually leave this blank, and a suitable value will be calculated. The exception is if a FULL_WIDTH image + does not actually span the full width of the screen, in which case you should pass the correct size here. + """ + sizes: String + + """ + The default quality. This is overridden by any format-specific options + """ + quality: Int + + """ + Options to pass to sharp when generating JPG images. + """ + jpgOptions: JPGOptions + + """ + Options to pass to sharp when generating PNG images. + """ + pngOptions: PNGOptions + + """ + Options to pass to sharp when generating WebP images. + """ + webpOptions: WebPOptions + + """ + Options to pass to sharp when generating AVIF images. + """ + avifOptions: AVIFOptions + + """ + Options to pass to sharp to control cropping and other image manipulations. + """ + transformOptions: TransformOptions + + """ + Background color applied to the wrapper. Also passed to sharp to use as a + background when "letterboxing" an image to another aspect ratio. + """ + backgroundColor: String + ): JSON! + original: ImageSharpOriginal + resize( + width: Int + height: Int + quality: Int + jpegQuality: Int + pngQuality: Int + webpQuality: Int + jpegProgressive: Boolean = true + pngCompressionLevel: Int = 9 + pngCompressionSpeed: Int = 4 + grayscale: Boolean = false + duotone: DuotoneGradient + base64: Boolean = false + traceSVG: Potrace + toFormat: ImageFormat = AUTO + cropFocus: ImageCropFocus = ATTENTION + fit: ImageFit = COVER + background: String = "rgba(0,0,0,1)" + rotate: Int = 0 + trim: Float = 0 + ): ImageSharpResize +} + +enum GatsbyImageFormat { + NO_CHANGE + AUTO + JPG + PNG + WEBP + AVIF +} + +enum GatsbyImageLayout { + FIXED + FULL_WIDTH + CONSTRAINED +} + +enum GatsbyImagePlaceholder { + DOMINANT_COLOR + TRACED_SVG + BLURRED + NONE +} + +type ContentfulContentTypeContentType implements Node @derivedTypes @dontInfer { name: String displayField: String description: String - sys: ContentfulContentTypeSys + sys: ContentfulContentTypeContentTypeSys } -type ContentfulContentTypeSys { +type ContentfulContentTypeContentTypeSys { type: String -} \ No newline at end of file + id: String + locale: String + spaceId: String + environmentId: String + firstPublishedAt: Date @dateformat + publishedAt: Date @dateformat + publishedVersion: Int +} diff --git a/e2e-tests/contentful/snapshots.js b/e2e-tests/contentful/snapshots.js index 3cb165766826f..492be39f16f3e 100644 --- a/e2e-tests/contentful/snapshots.js +++ b/e2e-tests/contentful/snapshots.js @@ -2,57 +2,57 @@ module.exports = { "__version": "6.9.1", "content-reference": { "content-reference-many-2nd-level-loop": { - "1": "
\n

Content Reference: Many (2nd level loop)

\n

[ContentfulNumber]\n 42

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulReference]\n Content Reference: One (Loop A -> B)\n : [\n Content Reference: One (Loop B -> A)\n ]

\n
" + "1": "
\n

Content Reference: Many (2nd level loop)

\n

[ContentfulContentTypeNumber]\n 42

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulContentTypeContentReference]\n Content Reference: One (Loop A -> B)\n : [\n Content Reference: One (Loop B -> A)\n ]

\n
" }, "content-reference-many-loop-a-greater-b": { - "1": "
\n

Content Reference: Many (Loop A -> B)

\n

[ContentfulNumber]\n 42

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulReference]\n Content Reference: Many (Loop B -> A)\n : [\n Number: Integer, Text: Short, Content Reference: Many (Loop A ->\n B)\n ]

\n
" + "1": "
\n

Content Reference: Many (Loop A -> B)

\n

[ContentfulContentTypeNumber]\n 42

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulContentTypeContentReference]\n Content Reference: Many (Loop B -> A)\n : [\n Number: Integer, Text: Short, Content Reference: Many (Loop A ->\n B)\n ]

\n
" }, "content-reference-many-loop-b-greater-a": { - "1": "
\n

Content Reference: Many (Loop B -> A)

\n

[ContentfulNumber]\n 42

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulReference]\n Content Reference: Many (Loop A -> B)\n : [\n Number: Integer, Text: Short, Content Reference: Many (Loop B ->\n A)\n ]

\n
" + "1": "
\n

Content Reference: Many (Loop B -> A)

\n

[ContentfulContentTypeNumber]\n 42

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulContentTypeContentReference]\n Content Reference: Many (Loop A -> B)\n : [\n Number: Integer, Text: Short, Content Reference: Many (Loop B ->\n A)\n ]

\n
" }, "content-reference-many-self-reference": { - "1": "
\n

Content Reference: Many (Self Reference)

\n

[ContentfulNumber]\n 42

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulReference]\n Content Reference: Many (Self Reference)\n : [\n Number: Integer, Text: Short, Content Reference: Many (Self\n Reference)\n ]

\n
" + "1": "
\n

Content Reference: Many (Self Reference)

\n

[ContentfulContentTypeNumber]\n 42

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulContentTypeContentReference]\n Content Reference: Many (Self Reference)\n : [\n Number: Integer, Text: Short, Content Reference: Many (Self\n Reference)\n ]

\n
" }, "content-reference-one": { - "1": "
\n

Content Reference: One

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n
" + "1": "
\n

Content Reference: One

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n
" }, "content-reference-one-loop-a-greater-b": { - "1": "
\n

Content Reference: One (Loop A -> B)

\n

[ContentfulReference]\n Content Reference: One (Loop B -> A)\n : [\n Content Reference: One (Loop A -> B)\n ]

\n
" + "1": "
\n

Content Reference: One (Loop A -> B)

\n

[ContentfulContentTypeContentReference]\n Content Reference: One (Loop B -> A)\n : [\n Content Reference: One (Loop A -> B)\n ]

\n
" }, "content-reference-one-loop-b-greater-a": { - "1": "
\n

Content Reference: One (Loop B -> A)

\n

[ContentfulReference]\n Content Reference: One (Loop A -> B)\n : [\n Content Reference: One (Loop B -> A)\n ]

\n
" + "1": "
\n

Content Reference: One (Loop B -> A)

\n

[ContentfulContentTypeContentReference]\n Content Reference: One (Loop A -> B)\n : [\n Content Reference: One (Loop B -> A)\n ]

\n
" }, "content-reference-one-self-reference": { - "1": "
\n

Content Reference: One (Self Reference)

\n

[ContentfulReference]\n Content Reference: One (Self Reference)\n : [\n Content Reference: One (Self Reference)\n ]

\n
" + "1": "
\n

Content Reference: One (Self Reference)

\n

[ContentfulContentTypeContentReference]\n Content Reference: One (Self Reference)\n : [\n Content Reference: One (Self Reference)\n ]

\n
" } }, "rich-text": { "rich-text: All Features": { - "1": "
\n

Rich Text: All Features

\n

The European languages

\n

are members of the same family. Their separate existence is a myth. For:

\n \n

Europe uses the same vocabulary.

\n
\n
\"\"\n\n
\n
\n \n \n \"\"\n\n \n \n
\n

\n
\n

The languages only differ in:

\n
    \n
  1. \n

    their grammar

    \n
  2. \n
  3. \n

    their pronunciation

    \n
  4. \n
  5. \n

    their most common words

    \n
  6. \n
  7. \n

    [Inline-ContentfulText]\n Text: Short\n :\n The quick brown fox jumps over the lazy dog.

    \n
  8. \n
\n

Everyone realizes why a new common language would be desirable: one could\n refuse to pay expensive translators.

\n

{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n }

\n

To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words.

\n

[ContentfulLocation] Lat:\n 52.51627\n , Long:\n 13.3777

\n
\n

If several languages coalesce, the grammar of the resulting language is\n more simple and regular than that of the individual languages.

\n
\n

The new common language will be more simple and regular than the existing\n European languages. It will be as simple as Occidental; in fact, it will be\n

\n
\n
" + "1": "
\n

Rich Text: All Features

\n

The European languages

\n

are members of the same family. Their separate existence is a myth. For:

\n \n

Europe uses the same vocabulary.

\n
\n
\"\"\n\n
\n
\n \n \n \"\"\n\n \n \n
\n

\n
\n

The languages only differ in:

\n
    \n
  1. \n

    their grammar

    \n
  2. \n
  3. \n

    their pronunciation

    \n
  4. \n
  5. \n

    their most common words

    \n
  6. \n
  7. \n

    [Inline-ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

    \n
  8. \n
\n

Everyone realizes why a new common language would be desirable: one could\n refuse to pay expensive translators.

\n

{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n }

\n

To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words.

\n

[ContentfulContentTypeLocation] Lat:\n 52.51627\n , Long:\n 13.3777

\n
\n

If several languages coalesce, the grammar of the resulting language is\n more simple and regular than that of the individual languages.

\n
\n

The new common language will be more simple and regular than the existing\n European languages. It will be as simple as Occidental; in fact, it will be\n

\n
\n
" }, "rich-text: Basic": { "1": "
\n

Rich Text: Basic

\n

The European languages

\n

are members of the same family. Their separate existence is a myth. For:

\n \n

Europe uses the same vocabulary.

\n
\n

The languages only differ in:

\n
    \n
  1. \n

    their grammar

    \n
  2. \n
  3. \n

    their pronunciation

    \n
  4. \n
  5. \n

    their most common words

    \n
  6. \n
\n

Everyone realizes why a new common language would be desirable: one could\n refuse to pay expensive translators.

\n

{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n }

\n

To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words.

\n
\n

If several languages coalesce, the grammar of the resulting language is\n more simple and regular than that of the individual languages.

\n
\n

The new common language will be more simple and regular than the existing\n European languages. It will be as simple as Occidental; in fact, it will be\n

\n
\n
" }, "rich-text: Embedded Entry": { - "1": "
\n

Rich Text: Embedded Entry

\n

Embedded Entry

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

\n

\n
\n
" + "1": "
\n

Rich Text: Embedded Entry

\n

Embedded Entry

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n

\n

\n
\n
" }, "rich-text: Embedded Asset": { "1": "
\n

Rich Text: Embedded asset

\n

Embedded Asset

\n
\n
\"\"\n\n
\n
\n \n \n \"\"\n\n \n \n
\n

\n

\n

\n
\n
" }, "rich-text: Embedded Entry With Deep Reference Loop": { - "1": "
\n

Rich Text: Embedded entry with deep reference loop

\n

Embedded entry with deep reference loop

\n

[ContentfulReference]\n Content Reference: Many (2nd level loop)\n : [\n Number: Integer, Text: Short, Content Reference: One (Loop A ->\n B)\n ]

\n

\n

\n
\n
" + "1": "
\n

Rich Text: Embedded entry with deep reference loop

\n

Embedded entry with deep reference loop

\n

[ContentfulContentTypeContentReference]\n Content Reference: Many (2nd level loop)\n : [\n Number: Integer, Text: Short, Content Reference: One (Loop A ->\n B)\n ]

\n

\n

\n
\n
" }, "rich-text: Embedded Entry With Reference Loop": { - "1": "
\n

Rich Text: Embedded entry with reference loop

\n

Embedded entry with reference loop

\n

[ContentfulReference]\n Content Reference: One (Loop B -> A)\n : [\n Content Reference: One (Loop A -> B)\n ]

\n

\n
\n
" + "1": "
\n

Rich Text: Embedded entry with reference loop

\n

Embedded entry with reference loop

\n

[ContentfulContentTypeContentReference]\n Content Reference: One (Loop B -> A)\n : [\n Content Reference: One (Loop A -> B)\n ]

\n

\n
\n
" }, "rich-text: Inline Entry": { - "1": "
\n

Rich Text: Inline entry

\n

Inline entry with reference loop

\n

Should be rendered after this [Inline-ContentfulText]\n Text: Short\n :\n The quick brown fox jumps over the lazy dog. and before\n that

\n

\n

\n
\n
" + "1": "
\n

Rich Text: Inline entry

\n

Inline entry with reference loop

\n

Should be rendered after this [Inline-ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog. and before\n that

\n

\n

\n
\n
" }, "rich-text: Inline Entry With Deep Reference Loop": { - "1": "
\n

Rich Text: Inline entry with deep reference loop

\n

Inline entry with deep reference loop

\n

Should be rendered after this [Inline-\n ContentfulContentReference\n ]\n Content Reference: Many (2nd level loop) and before that\n

\n

\n

\n
\n
" + "1": "
\n

Rich Text: Inline entry with deep reference loop

\n

Inline entry with deep reference loop

\n

Should be rendered after this [Inline-\n ContentfulContentTypeContentReference\n ] and before that

\n

\n

\n
\n
" }, "rich-text: Inline Entry With Reference Loop": { - "1": "
\n

Rich Text: Inline entry with reference loop

\n

Inline entry with reference loop

\n

Should be rendered after this [Inline-\n ContentfulContentReference\n ]\n Content Reference: One (Loop A -> B) and before that

\n

\n

\n
\n
" + "1": "
\n

Rich Text: Inline entry with reference loop

\n

Inline entry with reference loop

\n

Should be rendered after this [Inline-\n ContentfulContentTypeContentReference\n ] and before that

\n

\n

\n
\n
" }, "rich-text: Localized": { "1": "
\n

Rich Text: Localized

\n

Rich Text in English

\n
\n
", @@ -82,16 +82,16 @@ module.exports = { }, "content-reference localized": { "english-content-reference-one-localized": { - "1": "
\n

Content Reference: One Localized

\n

[ContentfulNumber]\n 42

\n
" + "1": "
\n

Content Reference: One Localized

\n

[ContentfulContentTypeNumber]\n 42

\n
" }, "english-content-reference-many-localized": { - "1": "
\n

Content Reference: Many Localized

\n

[ContentfulText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulNumber]\n 42

\n
" + "1": "
\n

Content Reference: Many Localized

\n

[ContentfulContentTypeText]\n The quick brown fox jumps over the lazy dog.

\n

[ContentfulContentTypeNumber]\n 42

\n
" }, "german-content-reference-one-localized": { - "1": "
\n

Content Reference: One Localized

\n

[ContentfulNumber]\n 4.2

\n
" + "1": "
\n

Content Reference: One Localized

\n

[ContentfulContentTypeNumber]\n 4.2

\n
" }, "german-content-reference-many-localized": { - "1": "
\n

Content Reference: Many Localized

\n

[ContentfulNumber]\n 4.2

\n

[ContentfulText]\n The European languages are members of the same family. Their\n separate existence is a myth. For science, music, sport, etc, Europe uses\n the same vocabulary.\n\n The languages only differ in their grammar, their pronunciation and their\n most common words. Everyone realizes why a new common language would be\n desirable: one could refuse to pay expensive translators.\n\n To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words. If several languages coalesce, the\n grammar of the resulting language is more simple and regular than that of\n the individual languages. The new common language will be more simple and\n regular than the existing European languages. It will be as simple as\n Occidental; in fact, it will be.

\n
" + "1": "
\n

Content Reference: Many Localized

\n

[ContentfulContentTypeNumber]\n 4.2

\n

[ContentfulContentTypeText]\n The European languages are members of the same family. Their\n separate existence is a myth. For science, music, sport, etc, Europe uses\n the same vocabulary.\n\n The languages only differ in their grammar, their pronunciation and their\n most common words. Everyone realizes why a new common language would be\n desirable: one could refuse to pay expensive translators.\n\n To achieve this, it would be necessary to have uniform grammar,\n pronunciation and more common words. If several languages coalesce, the\n grammar of the resulting language is more simple and regular than that of\n the individual languages. The new common language will be more simple and\n regular than the existing European languages. It will be as simple as\n Occidental; in fact, it will be.

\n
" } }, "media-reference localized": { diff --git a/e2e-tests/contentful/src/components/references/content-reference.js b/e2e-tests/contentful/src/components/references/content-reference.js index f32ca3a656dde..60dcacd74512e 100644 --- a/e2e-tests/contentful/src/components/references/content-reference.js +++ b/e2e-tests/contentful/src/components/references/content-reference.js @@ -1,19 +1,10 @@ import React from "react" -export const ContentfulContentReference = ({ - one, - many, - content_reference, - title, -}) => { - const references = [ - one, - ...(many || []), - ...(content_reference || []), - ].filter(Boolean) +export const ContentfulContentTypeContentReference = ({ one, many, title }) => { + const references = [one, ...(many || [])].filter(Boolean) return (

- [ContentfulReference] {title}: [ + [ContentfulContentTypeContentReference] {title}: [ {references.map(ref => ref.title).join(", ")}]

) diff --git a/e2e-tests/contentful/src/components/references/index.js b/e2e-tests/contentful/src/components/references/index.js index a9d0675bb1577..f9e79b30222d6 100644 --- a/e2e-tests/contentful/src/components/references/index.js +++ b/e2e-tests/contentful/src/components/references/index.js @@ -1,4 +1,4 @@ -export { ContentfulContentReference } from "./content-reference" -export { ContentfulLocation } from "./location" -export { ContentfulNumber } from "./number" -export { ContentfulText } from "./text" +export { ContentfulContentTypeContentReference } from "./content-reference" +export { ContentfulContentTypeLocation } from "./location" +export { ContentfulContentTypeNumber } from "./number" +export { ContentfulContentTypeText } from "./text" diff --git a/e2e-tests/contentful/src/components/references/location.js b/e2e-tests/contentful/src/components/references/location.js index d25a79e529402..2f67d64026e2a 100644 --- a/e2e-tests/contentful/src/components/references/location.js +++ b/e2e-tests/contentful/src/components/references/location.js @@ -1,7 +1,7 @@ import React from "react" -export const ContentfulLocation = ({ location }) => ( +export const ContentfulContentTypeLocation = ({ location }) => (

- [ContentfulLocation] Lat: {location.lat}, Long: {location.lon} + [ContentfulContentTypeLocation] Lat: {location.lat}, Long: {location.lon}

) diff --git a/e2e-tests/contentful/src/components/references/number.js b/e2e-tests/contentful/src/components/references/number.js index db7e048b14636..9d728fe93f554 100644 --- a/e2e-tests/contentful/src/components/references/number.js +++ b/e2e-tests/contentful/src/components/references/number.js @@ -1,5 +1,5 @@ import React from "react" -export const ContentfulNumber = ({ integer, decimal }) => ( -

[ContentfulNumber] {integer || decimal}

+export const ContentfulContentTypeNumber = ({ integer, decimal }) => ( +

[ContentfulContentTypeNumber] {integer || decimal}

) diff --git a/e2e-tests/contentful/src/components/references/text.js b/e2e-tests/contentful/src/components/references/text.js index 8bcf361eb99c8..eb4ac609836e8 100644 --- a/e2e-tests/contentful/src/components/references/text.js +++ b/e2e-tests/contentful/src/components/references/text.js @@ -1,5 +1,5 @@ import React from "react" -export const ContentfulText = ({ short, longPlain }) => ( -

[ContentfulText] {short || longPlain?.longPlain}

+export const ContentfulContentTypeText = ({ short, longPlain }) => ( +

[ContentfulContentTypeText] {short || longPlain?.raw}

) diff --git a/e2e-tests/contentful/src/pages/boolean.js b/e2e-tests/contentful/src/pages/boolean.js index 410065f7074bd..a1392f06d065d 100644 --- a/e2e-tests/contentful/src/pages/boolean.js +++ b/e2e-tests/contentful/src/pages/boolean.js @@ -49,27 +49,36 @@ export default BooleanPage export const pageQuery = graphql` query BooleanQuery { - default: allContentfulBoolean( - sort: { fields: contentful_id } - filter: { node_locale: { eq: "en-US" }, booleanLocalized: { eq: null } } + default: allContentfulContentTypeBoolean( + sort: { fields: sys___id } + filter: { + sys: { locale: { eq: "en-US" } } + booleanLocalized: { eq: null } + } ) { nodes { title boolean } } - english: allContentfulBoolean( - sort: { fields: contentful_id } - filter: { node_locale: { eq: "en-US" }, booleanLocalized: { ne: null } } + english: allContentfulContentTypeBoolean( + sort: { fields: sys___id } + filter: { + sys: { locale: { eq: "en-US" } } + booleanLocalized: { ne: null } + } ) { nodes { title booleanLocalized } } - german: allContentfulBoolean( - sort: { fields: contentful_id } - filter: { node_locale: { eq: "de-DE" }, booleanLocalized: { ne: null } } + german: allContentfulContentTypeBoolean( + sort: { fields: sys___id } + filter: { + sys: { locale: { eq: "de-DE" } } + booleanLocalized: { ne: null } + } ) { nodes { title diff --git a/e2e-tests/contentful/src/pages/content-reference.js b/e2e-tests/contentful/src/pages/content-reference.js index 642722e2a92d6..d31fdb8f8b14b 100644 --- a/e2e-tests/contentful/src/pages/content-reference.js +++ b/e2e-tests/contentful/src/pages/content-reference.js @@ -24,7 +24,7 @@ const ContentReferencePage = ({ data }) => { return (

Default

- {defaultEntries.map(({ contentful_id, title, one, many }) => { + {defaultEntries.map(({ sys: { id }, title, one, many }) => { const slug = slugify(title, { strict: true, lower: true }) let content = null @@ -37,7 +37,7 @@ const ContentReferencePage = ({ data }) => { } return ( -
+

{title}

{content}
@@ -45,7 +45,7 @@ const ContentReferencePage = ({ data }) => { })}

English Locale

{englishEntries.map( - ({ contentful_id, title, oneLocalized, manyLocalized }) => { + ({ sys: { id }, title, oneLocalized, manyLocalized }) => { const slug = slugify(title, { strict: true, lower: true }) let content = null @@ -58,7 +58,7 @@ const ContentReferencePage = ({ data }) => { } return ( -
+

{title}

{content}
@@ -67,7 +67,7 @@ const ContentReferencePage = ({ data }) => { )}

German Locale

{germanEntries.map( - ({ contentful_id, title, oneLocalized, manyLocalized }) => { + ({ sys: { id }, title, oneLocalized, manyLocalized }) => { const slug = slugify(title, { strict: true, lower: true }) let content = null @@ -80,7 +80,7 @@ const ContentReferencePage = ({ data }) => { } return ( -
+

{title}

{content}
@@ -95,42 +95,58 @@ export default ContentReferencePage export const pageQuery = graphql` query ContentReferenceQuery { - default: allContentfulContentReference( + default: allContentfulContentTypeContentReference( sort: { fields: title } - filter: { node_locale: { eq: "en-US" }, title: { glob: "!*Localized*" } } + filter: { + sys: { locale: { eq: "en-US" } } + title: { glob: "!*Localized*" } + } ) { nodes { title - contentful_id + sys { + id + } one { __typename - ... on ContentfulText { - contentful_id + ... on ContentfulEntry { + sys { + id + } + } + ... on ContentfulContentTypeText { title short } - ... on ContentfulContentReference { - contentful_id + ... on ContentfulContentTypeNumber { + title + integer + } + ... on ContentfulContentTypeContentReference { title one { - ... on ContentfulText { + ... on ContentfulContentTypeText { title short } - ... on ContentfulContentReference { + ... on ContentfulContentTypeNumber { + title + integer + } + ... on ContentfulContentTypeContentReference { title } } many { - ... on ContentfulText { + ... on ContentfulContentTypeText { title short } - ... on ContentfulNumber { + ... on ContentfulContentTypeNumber { title integer } - ... on ContentfulContentReference { + ... on ContentfulContentTypeContentReference { title } } @@ -138,38 +154,49 @@ export const pageQuery = graphql` } many { __typename - ... on ContentfulText { - contentful_id + ... on ContentfulEntry { + sys { + id + } + } + ... on ContentfulContentTypeText { title short } - ... on ContentfulNumber { - contentful_id + ... on ContentfulContentTypeNumber { title integer } - ... on ContentfulContentReference { - contentful_id + ... on ContentfulContentTypeContentReference { title + ... on ContentfulEntry { + sys { + id + } + } one { - ... on ContentfulText { + ... on ContentfulContentTypeText { title short } - ... on ContentfulContentReference { + ... on ContentfulContentTypeNumber { + title + integer + } + ... on ContentfulContentTypeContentReference { title } } many { - ... on ContentfulText { + ... on ContentfulContentTypeText { title short } - ... on ContentfulNumber { + ... on ContentfulContentTypeNumber { title integer } - ... on ContentfulContentReference { + ... on ContentfulContentTypeContentReference { title } } @@ -177,61 +204,75 @@ export const pageQuery = graphql` } } } - english: allContentfulContentReference( + english: allContentfulContentTypeContentReference( sort: { fields: title } - filter: { node_locale: { eq: "en-US" }, title: { glob: "*Localized*" } } + filter: { + sys: { locale: { eq: "en-US" } } + title: { glob: "*Localized*" } + } ) { nodes { title - contentful_id + sys { + id + } oneLocalized { __typename - title - decimal - integer + ... on ContentfulContentTypeNumber { + title + decimal + integer + } } manyLocalized { __typename - ... on ContentfulNumber { + ... on ContentfulContentTypeNumber { title decimal integer } - ... on ContentfulText { + ... on ContentfulContentTypeText { title short longPlain { - longPlain + raw } } } } } - german: allContentfulContentReference( + german: allContentfulContentTypeContentReference( sort: { fields: title } - filter: { node_locale: { eq: "de-DE" }, title: { glob: "*Localized*" } } + filter: { + sys: { locale: { eq: "de-DE" } } + title: { glob: "*Localized*" } + } ) { nodes { title - contentful_id + sys { + id + } oneLocalized { __typename - title - decimal - integer + ... on ContentfulContentTypeNumber { + title + decimal + integer + } } manyLocalized { __typename - ... on ContentfulNumber { + ... on ContentfulContentTypeNumber { title decimal integer } - ... on ContentfulText { + ... on ContentfulContentTypeText { title short longPlain { - longPlain + raw } } } diff --git a/e2e-tests/contentful/src/pages/date.js b/e2e-tests/contentful/src/pages/date.js index bb3a0ff554a38..81a2c72d0990e 100644 --- a/e2e-tests/contentful/src/pages/date.js +++ b/e2e-tests/contentful/src/pages/date.js @@ -32,34 +32,36 @@ export default DatePage export const pageQuery = graphql` query DateQuery { - dateTime: contentfulDate(contentful_id: { eq: "38akBjGb3T1t4AjB87wQjo" }) { + dateTime: contentfulContentTypeDate( + sys: { id: { eq: "38akBjGb3T1t4AjB87wQjo" } } + ) { title date: dateTime formatted: dateTime(formatString: "D.M.YYYY - hh:mm") } - dateTimeTimezone: contentfulDate( - contentful_id: { eq: "6dZ8pK4tFWZDZPHgSC0tNS" } + dateTimeTimezone: contentfulContentTypeDate( + sys: { id: { eq: "6dZ8pK4tFWZDZPHgSC0tNS" } } ) { title date: dateTimeTimezone formatted: dateTimeTimezone(formatString: "D.M.YYYY - hh:mm (z)") } - date: contentfulDate(contentful_id: { eq: "5FuULz0jl0rKoKUKp2rshf" }) { + date: contentfulContentTypeDate( + sys: { id: { eq: "5FuULz0jl0rKoKUKp2rshf" } } + ) { title date formatted: date(formatString: "D.M.YYYY") } - dateEnglish: contentfulDate( - contentful_id: { eq: "1ERWZvDiYELryAZEP1dmKG" } - node_locale: { eq: "en-US" } + dateEnglish: contentfulContentTypeDate( + sys: { id: { eq: "1ERWZvDiYELryAZEP1dmKG" }, locale: { eq: "en-US" } } ) { title date: dateLocalized formatted: dateLocalized(formatString: "D.M.YYYY - HH:mm:ss") } - dateGerman: contentfulDate( - contentful_id: { eq: "1ERWZvDiYELryAZEP1dmKG" } - node_locale: { eq: "de-DE" } + dateGerman: contentfulContentTypeDate( + sys: { id: { eq: "1ERWZvDiYELryAZEP1dmKG" }, locale: { eq: "de-DE" } } ) { title date: dateLocalized diff --git a/e2e-tests/contentful/src/pages/download-local.js b/e2e-tests/contentful/src/pages/download-local.js index a5bdddb8a18f2..564302b009891 100644 --- a/e2e-tests/contentful/src/pages/download-local.js +++ b/e2e-tests/contentful/src/pages/download-local.js @@ -10,7 +10,9 @@ const DownloadLocalPage = ({ data }) => {

Test downloadLocal feature

) @@ -20,13 +22,13 @@ export default DownloadLocalPage export const pageQuery = graphql` query DownloadLocalQuery { - contentfulAsset(contentful_id: { eq: "3BSI9CgDdAn1JchXmY5IJi" }) { - contentful_id - title - localFile { - absolutePath - childImageSharp { - gatsbyImageData + contentfulAsset(sys: { id: { eq: "3BSI9CgDdAn1JchXmY5IJi" } }) { + fields { + localFile { + absolutePath + childImageSharp { + gatsbyImageData + } } } } diff --git a/e2e-tests/contentful/src/pages/gatsby-plugin-image.js b/e2e-tests/contentful/src/pages/gatsby-plugin-image.js index 98d3ea31eb993..0330f8f106ebf 100644 --- a/e2e-tests/contentful/src/pages/gatsby-plugin-image.js +++ b/e2e-tests/contentful/src/pages/gatsby-plugin-image.js @@ -16,14 +16,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.constrained ? ( ) : ( - + )}
))} @@ -34,14 +34,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.fullWidth ? ( ) : ( - + )}
))} @@ -53,14 +53,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.fixed ? ( ) : ( - + )}
))} @@ -72,14 +72,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.dominantColor ? ( ) : ( - + )}
))} @@ -91,14 +91,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.traced ? ( ) : ( - + )}
))} @@ -110,14 +110,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.blurred ? ( ) : ( - + )}
))} @@ -129,14 +129,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.customImageFormats ? ( ) : ( - + )}
))} @@ -148,7 +148,7 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} @@ -162,7 +162,7 @@ const GatsbyPluginImagePage = ({ data }) => { }} /> ) : ( - + )}
))} @@ -174,14 +174,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.constrained ? ( ) : ( - + )}
))} @@ -193,14 +193,14 @@ const GatsbyPluginImagePage = ({ data }) => {

- {node.title} ({node.file.fileName.split(".").pop()}) + {node.title} ({node.fileName.split(".").pop()})

{node.description &&

{node.description}

} {node.constrained ? ( ) : ( - + )}
))} @@ -215,24 +215,24 @@ export const pageQuery = graphql` query GatsbyPluginImageQuery { default: allContentfulAsset( filter: { - contentful_id: { - in: [ - "3ljGfnpegOnBTFGhV07iC1" - "3BSI9CgDdAn1JchXmY5IJi" - "65syuRuRVeKi03HvRsOkkb" - ] + sys: { + id: { + in: [ + "3ljGfnpegOnBTFGhV07iC1" + "3BSI9CgDdAn1JchXmY5IJi" + "65syuRuRVeKi03HvRsOkkb" + ] + } + locale: { eq: "en-US" } } - node_locale: { eq: "en-US" } } - sort: { fields: contentful_id } + sort: { fields: sys___id } ) { nodes { title description - file { - fileName - url - } + fileName + url constrained: gatsbyImageData(width: 420) fullWidth: gatsbyImageData(width: 200, layout: FIXED) fixed: gatsbyImageData(width: 200, layout: FIXED) @@ -259,35 +259,29 @@ export const pageQuery = graphql` } english: allContentfulAsset( filter: { - contentful_id: { in: ["4FwygYxkL3rAteERtoxxNC"] } - node_locale: { eq: "en-US" } + sys: { id: { in: ["4FwygYxkL3rAteERtoxxNC"] }, locale: { eq: "en-US" } } } - sort: { fields: contentful_id } + sort: { fields: sys___id } ) { nodes { title description - file { - fileName - url - } + fileName + url constrained: gatsbyImageData(width: 420) } } german: allContentfulAsset( filter: { - contentful_id: { in: ["4FwygYxkL3rAteERtoxxNC"] } - node_locale: { eq: "de-DE" } + sys: { id: { in: ["4FwygYxkL3rAteERtoxxNC"] }, locale: { eq: "de-DE" } } } - sort: { fields: contentful_id } + sort: { fields: sys___id } ) { nodes { title description - file { - fileName - url - } + fileName + url constrained: gatsbyImageData(width: 420) } } diff --git a/e2e-tests/contentful/src/pages/json.js b/e2e-tests/contentful/src/pages/json.js index b41d1bf54501e..548bce9c30608 100644 --- a/e2e-tests/contentful/src/pages/json.js +++ b/e2e-tests/contentful/src/pages/json.js @@ -31,7 +31,7 @@ const JSONPage = ({ data }) => {

Name: {actor.name}

Photo: {actor.photo}

Birthdate: {actor.Birthdate}

-

Born at: {actor.Born_At}

+

Born at: {actor["Born At"]}

Weight: {actor.weight}

Age: {actor.age}

Wife: {actor.wife}

@@ -61,50 +61,27 @@ export default JSONPage export const pageQuery = graphql` query JSONQuery { - simple: contentfulJson(contentful_id: { eq: "2r6tNjP8brkyy5yLR39hhh" }) { - json { - name - city - age - } + simple: contentfulContentTypeJson( + sys: { id: { eq: "2r6tNjP8brkyy5yLR39hhh" } } + ) { + json } - complex: contentfulJson(contentful_id: { eq: "2y71nV0cpW9vzTmJybq571" }) { - json { - Actors { - name - photo - Birthdate - Born_At - weight - age - wife - children - hasChildren - hasGreyHair - } - } + complex: contentfulContentTypeJson( + sys: { id: { eq: "2y71nV0cpW9vzTmJybq571" } } + ) { + json } - english: contentfulJson( - node_locale: { eq: "en-US" } - jsonLocalized: { id: { ne: null } } + english: contentfulContentTypeJson( + sys: { id: { eq: "7DvTBEPg5P6TRC7dI9zXuO" }, locale: { eq: "en-US" } } ) { title - jsonLocalized { - age - city - name - } + jsonLocalized } - german: contentfulJson( - node_locale: { eq: "de-DE" } - jsonLocalized: { id: { ne: null } } + german: contentfulContentTypeJson( + sys: { id: { eq: "7DvTBEPg5P6TRC7dI9zXuO" }, locale: { eq: "de-DE" } } ) { title - jsonLocalized { - age - city - name - } + jsonLocalized } } ` diff --git a/e2e-tests/contentful/src/pages/location.js b/e2e-tests/contentful/src/pages/location.js index 813f6cf9ed7dd..9314b17fabdc4 100644 --- a/e2e-tests/contentful/src/pages/location.js +++ b/e2e-tests/contentful/src/pages/location.js @@ -56,9 +56,12 @@ export default LocationPage export const pageQuery = graphql` query LocationQuery { - default: allContentfulLocation( - sort: { fields: contentful_id } - filter: { title: { glob: "!*Localized*" }, node_locale: { eq: "en-US" } } + default: allContentfulContentTypeLocation( + sort: { fields: sys___id } + filter: { + title: { glob: "!*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { title @@ -68,9 +71,12 @@ export const pageQuery = graphql` } } } - english: allContentfulLocation( - sort: { fields: contentful_id } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "en-US" } } + english: allContentfulContentTypeLocation( + sort: { fields: sys___id } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { title @@ -80,9 +86,12 @@ export const pageQuery = graphql` } } } - german: allContentfulLocation( - sort: { fields: contentful_id } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "de-DE" } } + german: allContentfulContentTypeLocation( + sort: { fields: sys___id } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "de-DE" } } + } ) { nodes { title diff --git a/e2e-tests/contentful/src/pages/media-reference.js b/e2e-tests/contentful/src/pages/media-reference.js index f6bf077d006e1..f521c3747450c 100644 --- a/e2e-tests/contentful/src/pages/media-reference.js +++ b/e2e-tests/contentful/src/pages/media-reference.js @@ -10,24 +10,22 @@ const MediaReferencePage = ({ data }) => { const germanEntries = data.german.nodes return ( - {defaultEntries.map(({ contentful_id, title, one, many }) => { + {defaultEntries.map(({ sys: { id }, title, one, many }) => { const slug = slugify(title, { strict: true, lower: true }) let content = null if (many) { content = many.map(imageData => ( - {title} + {title} )) } if (one) { - content = ( - {title} - ) + content = {title} } return ( -
+

{title}

{content}
@@ -35,48 +33,34 @@ const MediaReferencePage = ({ data }) => { })}

English Locale

{englishEntries.map( - ({ contentful_id, title, one, oneLocalized, many, manyLocalized }) => { + ({ sys: { id }, title, one, oneLocalized, many, manyLocalized }) => { const slug = slugify(title, { strict: true, lower: true }) let content = null if (manyLocalized) { content = manyLocalized.map(imageData => ( - {title} + {title} )) } if (oneLocalized) { content = ( - {title} + {title} ) } if (many) { content = many.map(imageData => ( - {title} + {title} )) } if (one) { - content = ( - {title} - ) + content = {title} } return ( -
+

{title}

{content}
@@ -86,48 +70,34 @@ const MediaReferencePage = ({ data }) => {

German Locale

{germanEntries.map( - ({ contentful_id, title, one, oneLocalized, many, manyLocalized }) => { + ({ sys: { id }, title, one, oneLocalized, many, manyLocalized }) => { const slug = slugify(title, { strict: true, lower: true }) let content = null if (manyLocalized) { content = manyLocalized.map(imageData => ( - {title} + {title} )) } if (oneLocalized) { content = ( - {title} + {title} ) } if (many) { content = many.map(imageData => ( - {title} + {title} )) } if (one) { - content = ( - {title} - ) + content = {title} } return ( -
+

{title}

{content}
@@ -142,80 +112,75 @@ export default MediaReferencePage export const pageQuery = graphql` query MediaReferenceQuery { - default: allContentfulMediaReference( + default: allContentfulContentTypeMediaReference( sort: { fields: title } - filter: { title: { glob: "!*Localized*" }, node_locale: { eq: "en-US" } } + filter: { + title: { glob: "!*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { title - contentful_id + sys { + id + } one { - file { - url - } + url } many { - file { - url - } + url } } } - english: allContentfulMediaReference( + english: allContentfulContentTypeMediaReference( sort: { fields: title } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "en-US" } } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { title - contentful_id + sys { + id + } one { - file { - url - } + url } many { - file { - url - } + url } oneLocalized { - file { - url - } + url } manyLocalized { - file { - url - } + url } } } - german: allContentfulMediaReference( + german: allContentfulContentTypeMediaReference( sort: { fields: title } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "de-DE" } } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "de-DE" } } + } ) { nodes { title - contentful_id + sys { + id + } one { - file { - url - } + url } many { - file { - url - } + url } oneLocalized { - file { - url - } + url } manyLocalized { - file { - url - } + url } } } diff --git a/e2e-tests/contentful/src/pages/number.js b/e2e-tests/contentful/src/pages/number.js index 5eff2ed704bba..e5d5dc2cc8066 100644 --- a/e2e-tests/contentful/src/pages/number.js +++ b/e2e-tests/contentful/src/pages/number.js @@ -53,9 +53,12 @@ export default NumberPage export const pageQuery = graphql` query NumberQuery { - default: allContentfulNumber( - sort: { fields: contentful_id } - filter: { title: { glob: "!*Localized*" }, node_locale: { eq: "en-US" } } + default: allContentfulContentTypeNumber( + sort: { fields: sys___id } + filter: { + title: { glob: "!*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { title @@ -63,9 +66,12 @@ export const pageQuery = graphql` decimal } } - english: allContentfulNumber( - sort: { fields: contentful_id } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "en-US" } } + english: allContentfulContentTypeNumber( + sort: { fields: sys___id } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { title @@ -73,9 +79,12 @@ export const pageQuery = graphql` decimalLocalized } } - german: allContentfulNumber( - sort: { fields: contentful_id } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "de-DE" } } + german: allContentfulContentTypeNumber( + sort: { fields: sys___id } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "de-DE" } } + } ) { nodes { title diff --git a/e2e-tests/contentful/src/pages/rich-text.js b/e2e-tests/contentful/src/pages/rich-text.js index b064694b7469b..33c360059c08f 100644 --- a/e2e-tests/contentful/src/pages/rich-text.js +++ b/e2e-tests/contentful/src/pages/rich-text.js @@ -20,13 +20,13 @@ function renderReferencedComponent(ref) { return } -const options = { +const makeOptions = ({ assetBlockMap, entryBlockMap, entryInlineMap }) => ({ renderMark: { [MARKS.BOLD]: text => {text}, }, renderNode: { [BLOCKS.EMBEDDED_ASSET]: node => { - const asset = node.data.target + const asset = assetBlockMap.get(node?.data?.target?.sys.id) if (asset.gatsbyImageData) { return } @@ -40,7 +40,7 @@ const options = { ) }, [BLOCKS.EMBEDDED_ENTRY]: node => { - const entry = node?.data?.target + const entry = entryBlockMap.get(node?.data?.target?.sys.id) if (!entry) { throw new Error( `Entity not available for node:\n${JSON.stringify(node, null, 2)}` @@ -49,11 +49,11 @@ const options = { return renderReferencedComponent(entry) }, [INLINES.EMBEDDED_ENTRY]: node => { - const entry = node.data.target - if (entry.__typename === "ContentfulText") { + const entry = entryInlineMap.get(node?.data?.target?.sys.id) + if (entry.__typename === "ContentfulContentTypeText") { return ( - [Inline-ContentfulText] {entry.title}: {entry.short} + [Inline-ContentfulContentTypeText] {entry.short} ) } @@ -64,7 +64,7 @@ const options = { ) }, }, -} +}) const RichTextPage = ({ data }) => { const defaultEntries = data.default.nodes @@ -77,7 +77,7 @@ const RichTextPage = ({ data }) => { return (

{title}

- {renderRichText(richText, options)} + {renderRichText(richText, makeOptions)}
) @@ -89,7 +89,7 @@ const RichTextPage = ({ data }) => { return (

{title}

- {renderRichText(richTextLocalized, options)} + {renderRichText(richTextLocalized, makeOptions)}
) @@ -101,7 +101,7 @@ const RichTextPage = ({ data }) => { return (

{title}

- {renderRichText(richTextLocalized, options)} + {renderRichText(richTextLocalized, makeOptions)}
) @@ -114,99 +114,145 @@ export default RichTextPage export const pageQuery = graphql` query RichTextQuery { - default: allContentfulRichText( + default: allContentfulContentTypeRichText( sort: { fields: title } filter: { title: { glob: "!*Localized*|*Validated*" } - node_locale: { eq: "en-US" } + sys: { locale: { eq: "en-US" } } } ) { nodes { id title richText { - raw - references { - __typename - ... on ContentfulAsset { - contentful_id - gatsbyImageData(width: 200) - } - ... on ContentfulText { - contentful_id - title - short - } - ... on ContentfulLocation { - contentful_id - location { - lat - lon + json + links { + assets { + block { + sys { + id + } + gatsbyImageData(width: 200) } } - ... on ContentfulContentReference { - contentful_id - title - one { - ... on ContentfulContentReference { - contentful_id - title - content_reference { - ... on ContentfulContentReference { - contentful_id - title - } - } + entries { + block { + __typename + sys { + id + type } - } - many { - ... on ContentfulText { - contentful_id + ... on ContentfulContentTypeText { title short } - ... on ContentfulNumber { - contentful_id - title - integer + ... on ContentfulContentTypeLocation { + location { + lat + lon + } } - ... on ContentfulContentReference { - contentful_id + ... on ContentfulContentTypeContentReference { title - content_reference { - ... on ContentfulContentReference { - id + one { + __typename + ... on ContentfulEntry { + sys { + id + } + } + ... on ContentfulContentTypeText { title + short + } + ... on ContentfulContentTypeContentReference { + title + one { + ... on ContentfulContentTypeContentReference { + title + } + } + many { + ... on ContentfulContentTypeContentReference { + title + } + } + } + } + many { + __typename + ... on ContentfulEntry { + sys { + id + } + } + ... on ContentfulContentTypeText { + title + short + } + ... on ContentfulContentTypeNumber { + title + integer + } + ... on ContentfulContentTypeContentReference { + title + one { + ... on ContentfulContentTypeContentReference { + title + } + } + many { + ... on ContentfulContentTypeContentReference { + title + } + } } } } } + inline { + __typename + sys { + id + type + } + ... on ContentfulContentTypeText { + title + short + } + } } } } } } - english: allContentfulRichText( + english: allContentfulContentTypeRichText( sort: { fields: title } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "en-US" } } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "en-US" } } + } ) { nodes { id title richTextLocalized { - raw + json } } } - german: allContentfulRichText( + german: allContentfulContentTypeRichText( sort: { fields: title } - filter: { title: { glob: "*Localized*" }, node_locale: { eq: "de-DE" } } + filter: { + title: { glob: "*Localized*" } + sys: { locale: { eq: "de-DE" } } + } ) { nodes { id title richTextLocalized { - raw + json } } } diff --git a/e2e-tests/contentful/src/pages/tags.js b/e2e-tests/contentful/src/pages/tags.js index 9d752ee279ba8..7af26c1113330 100644 --- a/e2e-tests/contentful/src/pages/tags.js +++ b/e2e-tests/contentful/src/pages/tags.js @@ -55,13 +55,13 @@ const TagsPage = ({ data }) => { data-cy-assets style={{ display: "flex", justifyContent: "space-between" }} > - {assets.map(({ title, file, metadata }) => { + {assets.map(({ title, url, metadata }) => { const slug = slugify(title, { strict: true, lower: true }) return (

{title}

{title} @@ -88,24 +88,23 @@ const TagsPage = ({ data }) => { ) } - export default TagsPage export const pageQuery = graphql` query TagsQuery { - tags: allContentfulTag(sort: { fields: contentful_id }) { + tags: allContentfulTag(sort: { fields: id }) { nodes { name contentful_id } } - integers: allContentfulNumber( - sort: { fields: contentful_id } + integers: allContentfulContentTypeNumber( + sort: { fields: sys___id } filter: { metadata: { tags: { elemMatch: { contentful_id: { eq: "numberInteger" } } } } - node_locale: { eq: "en-US" } + sys: { locale: { eq: "en-US" } } } ) { nodes { @@ -113,13 +112,13 @@ export const pageQuery = graphql` integer } } - decimals: allContentfulNumber( - sort: { fields: contentful_id } + decimals: allContentfulContentTypeNumber( + sort: { fields: sys___id } filter: { metadata: { tags: { elemMatch: { contentful_id: { eq: "numberDecimal" } } } } - node_locale: { eq: "en-US" } + sys: { locale: { eq: "en-US" } } } ) { nodes { @@ -128,17 +127,15 @@ export const pageQuery = graphql` } } assets: allContentfulAsset( - sort: { fields: contentful_id } + sort: { fields: sys___id } filter: { metadata: { tags: { elemMatch: { contentful_id: { eq: "animal" } } } } - node_locale: { eq: "en-US" } + sys: { locale: { eq: "en-US" } } } ) { nodes { title - file { - url - } + url metadata { tags { name diff --git a/e2e-tests/contentful/src/pages/text.js b/e2e-tests/contentful/src/pages/text.js index bc39dd5ddfa6e..b2e10ceeef546 100644 --- a/e2e-tests/contentful/src/pages/text.js +++ b/e2e-tests/contentful/src/pages/text.js @@ -33,7 +33,7 @@ const TextPage = ({ data }) => {

Long (Plain):

-

{longPlain.longPlain.longPlain}

+

{longPlain.longPlain.raw}

Markdown (Simple):

{

Long (Plain):

-

{longEnglish.longLocalized.longLocalized}

+

{longEnglish.longLocalized.raw}

German Locale

@@ -67,7 +67,7 @@ const TextPage = ({ data }) => {

Long (Plain):

-

{longGerman.longLocalized.longLocalized}

+

{longGerman.longLocalized.raw}

) @@ -77,29 +77,25 @@ export default TextPage export const pageQuery = graphql` query TextQuery { - short: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "5ZtcN1o7KpN7J7xgiTyaXo" } + short: contentfulContentTypeText( + sys: { id: { eq: "5ZtcN1o7KpN7J7xgiTyaXo" }, locale: { eq: "en-US" } } ) { short } - shortList: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "7b5U927WTFcQXO2Gewwa2k" } + shortList: contentfulContentTypeText( + sys: { id: { eq: "7b5U927WTFcQXO2Gewwa2k" }, locale: { eq: "en-US" } } ) { shortList } - longPlain: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "6ru8cSC9hZi3Ekvtw7P77S" } + longPlain: contentfulContentTypeText( + sys: { id: { eq: "6ru8cSC9hZi3Ekvtw7P77S" }, locale: { eq: "en-US" } } ) { longPlain { - longPlain + raw } } - longMarkdownSimple: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "NyPJw0mcSuCwY2gV0zYny" } + longMarkdownSimple: contentfulContentTypeText( + sys: { id: { eq: "NyPJw0mcSuCwY2gV0zYny" }, locale: { eq: "en-US" } } ) { longMarkdown { childMarkdownRemark { @@ -107,9 +103,8 @@ export const pageQuery = graphql` } } } - longMarkdownComplex: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "3pwKS9UWsYmOguo4UdE1EB" } + longMarkdownComplex: contentfulContentTypeText( + sys: { id: { eq: "3pwKS9UWsYmOguo4UdE1EB" }, locale: { eq: "en-US" } } ) { longMarkdown { childMarkdownRemark { @@ -117,32 +112,28 @@ export const pageQuery = graphql` } } } - shortEnglish: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "2sQRyOLUexvWZj9nkzS3nN" } + shortEnglish: contentfulContentTypeText( + sys: { id: { eq: "2sQRyOLUexvWZj9nkzS3nN" }, locale: { eq: "en-US" } } ) { shortLocalized } - shortGerman: contentfulText( - node_locale: { eq: "de-DE" } - contentful_id: { eq: "2sQRyOLUexvWZj9nkzS3nN" } + shortGerman: contentfulContentTypeText( + sys: { id: { eq: "2sQRyOLUexvWZj9nkzS3nN" }, locale: { eq: "de-DE" } } ) { shortLocalized } - longEnglish: contentfulText( - node_locale: { eq: "en-US" } - contentful_id: { eq: "5csovkwdDBqTKwSblAOHvd" } + longEnglish: contentfulContentTypeText( + sys: { id: { eq: "5csovkwdDBqTKwSblAOHvd" }, locale: { eq: "en-US" } } ) { longLocalized { - longLocalized + raw } } - longGerman: contentfulText( - node_locale: { eq: "de-DE" } - contentful_id: { eq: "5csovkwdDBqTKwSblAOHvd" } + longGerman: contentfulContentTypeText( + sys: { id: { eq: "5csovkwdDBqTKwSblAOHvd" }, locale: { eq: "de-DE" } } ) { longLocalized { - longLocalized + raw } } } diff --git a/examples/using-contentful/src/pages/categories/{ContentfulCategory.id}.js b/examples/using-contentful/src/pages/categories/{ContentfulCategory.id}.js index 1505b6d3b9136..3f8646a6de115 100644 --- a/examples/using-contentful/src/pages/categories/{ContentfulCategory.id}.js +++ b/examples/using-contentful/src/pages/categories/{ContentfulCategory.id}.js @@ -15,7 +15,7 @@ class CategoryTemplate extends React.Component { render() { const category = this.props.data.contentfulCategory const { - title: { title }, + title: { raw: title }, product, icon, } = category @@ -45,7 +45,7 @@ class CategoryTemplate extends React.Component { {product && product.map((p, i) => (
  • - {p.productName.productName} + {p.productName.raw}
  • ))} @@ -60,10 +60,10 @@ CategoryTemplate.propTypes = propTypes export default CategoryTemplate export const pageQuery = graphql` - query($id: String!) { + query ($id: String!) { contentfulCategory(id: { eq: $id }) { title { - title + raw } icon { gatsbyImageData(layout: FIXED, width: 75) @@ -72,7 +72,7 @@ export const pageQuery = graphql` gatsbyPath(filePath: "/products/{ContentfulProduct.id}") id productName { - productName + raw } } } diff --git a/examples/using-contentful/src/pages/image-api.js b/examples/using-contentful/src/pages/image-api.js index fb1104abe76ad..14126c469d708 100644 --- a/examples/using-contentful/src/pages/image-api.js +++ b/examples/using-contentful/src/pages/image-api.js @@ -317,7 +317,7 @@ export default ImageAPI export const pageQuery = graphql` query { - allContentfulAsset(filter: { node_locale: { eq: "en-US" } }) { + allContentfulAsset(filter: { sys: { locale: { eq: "en-US" } } }) { edges { node { title diff --git a/examples/using-contentful/src/pages/index.js b/examples/using-contentful/src/pages/index.js index 3f9ef900e998c..c93c2a1eaf5e3 100644 --- a/examples/using-contentful/src/pages/index.js +++ b/examples/using-contentful/src/pages/index.js @@ -34,7 +34,7 @@ const Product = ({ node }) => ( /> )}
    -
    {node.productName.productName}
    +
    {node.productName.raw}
    @@ -61,8 +61,8 @@ class IndexPage extends React.Component {

    An entry and asset node are created for each locale following fallback rules for missing localization. In addition, each node has - an additional field added, node_locale so you can - select for nodes from a single locale + an additional field added, sys.locale so you can select + for nodes from a single locale

    en-US

    {usProductEdges.map(({ node }, i) => ( @@ -86,13 +86,13 @@ export default IndexPage export const pageQuery = graphql` query { - us: allContentfulProduct(filter: { node_locale: { eq: "en-US" } }) { + us: allContentfulProduct(filter: { sys: { locale: { eq: "en-US" } } }) { edges { node { id gatsbyPath(filePath: "/products/{ContentfulProduct.id}") productName { - productName + raw } image { gatsbyImageData(layout: FIXED, width: 75) @@ -100,13 +100,13 @@ export const pageQuery = graphql` } } } - german: allContentfulProduct(filter: { node_locale: { eq: "de" } }) { + german: allContentfulProduct(filter: { sys: { locale: { eq: "de" } } }) { edges { node { id gatsbyPath(filePath: "/products/{ContentfulProduct.id}") productName { - productName + raw } image { gatsbyImageData(layout: FIXED, width: 75) diff --git a/examples/using-contentful/src/pages/products/{ContentfulProduct.id}.js b/examples/using-contentful/src/pages/products/{ContentfulProduct.id}.js index 14f574714916d..8e558768dee00 100644 --- a/examples/using-contentful/src/pages/products/{ContentfulProduct.id}.js +++ b/examples/using-contentful/src/pages/products/{ContentfulProduct.id}.js @@ -15,7 +15,7 @@ class ProductTemplate extends React.Component { render() { const product = this.props.data.contentfulProduct const { - productName: { productName }, + productName: { raw: productName }, productDescription, price, image, @@ -39,7 +39,7 @@ class ProductTemplate extends React.Component { )}

    {productName}

    -

    Made by {brand.companyName.companyName}

    +

    Made by {brand.companyName.raw}

    Price: ${price}
    (
  • - {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"