Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: restore alternate_language document field #371

Merged
merged 3 commits into from
May 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* NOTE: This field proxy is not used like the other field proxies. Because
* AlternativeLanguages is not a Prismic field type, it is manually handled in
* `documentFieldProxy`.
*/

import * as gatsbyPrismic from 'gatsby-source-prismic'
import * as RE from 'fp-ts/ReaderEither'
import * as A from 'fp-ts/Array'
import * as O from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'

import { ProxyDocumentSubtreeEnv } from '../lib/proxyDocumentSubtree'
import { createGetProxy } from '../lib/createGetProxy'

const proxyElement = (
element: gatsbyPrismic.PrismicAPIAlternateLanguageField,
): RE.ReaderEither<
ProxyDocumentSubtreeEnv,
Error,
gatsbyPrismic.PrismicAPIAlternateLanguageField
> =>
pipe(
RE.ask<ProxyDocumentSubtreeEnv>(),
RE.bind('enhancedFieldValue', () =>
RE.of({
...element,
raw: element,
}),
),
RE.map((env) =>
// A Proxy is used here to avoid an infinite loop if documents have
// circular references in link fields. This effectively makes the
// `document` field lazy.
createGetProxy(env.enhancedFieldValue, (target, prop, receiver) =>
pipe(
element.id,
O.fromPredicate(
(id): id is string => prop === 'document' && typeof id === 'string',
),
O.chain((id) => O.fromNullable(env.getNode(id))),
O.getOrElseW(() => Reflect.get(target, prop, receiver)),
),
),
),
)

export const proxyValue = (
fieldValue: gatsbyPrismic.PrismicAPIAlternateLanguageField[],
): RE.ReaderEither<
ProxyDocumentSubtreeEnv,
Error,
gatsbyPrismic.PrismicAPIAlternateLanguageField[]
> =>
pipe(
fieldValue,
A.map(proxyElement),
RE.sequenceArray,
RE.map(
// This is needed to return a non-readonly Array type.
(value) => value as gatsbyPrismic.PrismicAPIAlternateLanguageField[],
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
ProxyDocumentSubtreeEnv,
} from '../lib/proxyDocumentSubtree'

import * as alternativeLanguagesFieldProxy from '../fieldProxies/alternativeLanguagesFieldProxy'

// TODO: This is a poor type guard. It should use something stricter to ensure
// the object is a document.
export const valueRefinement = (value: unknown): value is prismic.Document =>
Expand All @@ -27,11 +29,17 @@ export const proxyValue = (
// This doesn't use `prismic-dom`'s `Link.url` function because this
// isn't a link. The `Link.url` function includes a check for
// link-specific fields which produces unwanted results.
// TODO: Once @prismicio/helpers V2 is released, use `documentToLinkField`
// along with `asLink`
RE.of(env.linkResolver(fieldValue)),
),
RE.bind('alternative_languages', () =>
alternativeLanguagesFieldProxy.proxyValue(fieldValue.alternate_languages),
),
RE.map((env) => ({
...fieldValue,
url: env.url,
alternate_languages: env.alternative_languages,
data: env.data,
})),
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as prismic from 'ts-prismic'
import md5 from 'tiny-hashes/md5'

const createId = () => md5(Math.random().toString())

export const createPrismicAPIDocument = <TData = Record<string, unknown>>(
data: TData = {} as TData,
): prismic.Document<TData> => {
const id = md5(Math.random().toString())
const id = createId()

return {
id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ test.serial(

test.todo('recursively merges data')

test.only('allows skipping', async (t) => {
test('allows skipping', async (t) => {
const pluginOptions = createPluginOptions(t)
const gatsbyContext = createGatsbyContext()
const config = createRepositoryConfigs(pluginOptions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,81 @@ test.serial(
},
)

test.serial('alternative languages', async (t) => {
const gatsbyContext = createGatsbyContext()
const pluginOptions = createPluginOptions(t)
const config = createRepositoryConfigs(pluginOptions)

const altLangDoc1 = createPrismicAPIDocument()
const altLangDoc2 = createPrismicAPIDocument()
const doc = {
...createPrismicAPIDocument(),
alternate_languages: [
{
id: altLangDoc1.id,
uid: altLangDoc1.uid,
type: altLangDoc1.type,
lang: 'alt-lang-1',
},
{
id: altLangDoc2.id,
uid: altLangDoc2.uid,
type: altLangDoc2.type,
lang: 'alt-lang-2',
},
],
}
const queryResponse = createPrismicAPIQueryResponse([
doc,
altLangDoc1,
altLangDoc2,
])

const result = await performPreview(
t,
// @ts-expect-error - Partial gatsbyContext provided
gatsbyContext,
pluginOptions,
config,
queryResponse,
'da6a5fa194ee3f44d5b0a7e99b6b1db2.json',
{
type: gatsbyPrismic.PrismicSpecialType.Document,
'type.data': gatsbyPrismic.PrismicSpecialType.DocumentData,
'type.data.doc_link': gatsbyPrismic.PrismicFieldType.Link,
'type.data.media_link': gatsbyPrismic.PrismicFieldType.Link,
},
)

const node = result.current.context[0].nodes[doc.id]
const altLangNode1 = result.current.context[0].nodes[altLangDoc1.id]
const altLangNode2 = result.current.context[0].nodes[altLangDoc2.id]

t.deepEqual(node.alternate_languages, [
{
...doc.alternate_languages[0],
// @ts-expect-error - This is not part of the base Prismic document type
raw: doc.alternate_languages[0],
},
{
...doc.alternate_languages[1],
// @ts-expect-error - This is not part of the base Prismic document type
raw: doc.alternate_languages[1],
},
])

// We must test the document field separately since it is only accessible
// via the Proxy handler. This field doesn't actually exist in the object.
t.true(
// @ts-expect-error - This is not part of the base Prismic document type
node.alternate_languages[0].document === altLangNode1,
)
t.true(
// @ts-expect-error - This is not part of the base Prismic document type
node.alternate_languages[1].document === altLangNode2,
)
})

test.serial('structured text', async (t) => {
const gatsbyContext = createGatsbyContext()
const pluginOptions = createPluginOptions(t)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as gatsby from 'gatsby'
import * as RTE from 'fp-ts/ReaderTaskEither'
import { pipe, identity } from 'fp-ts/function'

import { buildObjectType } from '../lib/buildObjectType'

import { Dependencies, PrismicAPIAlternateLanguageField } from '../types'

/**
* Builds a GraphQL Type used by a document's `alternate_language` field. The
* resulting type can be created using Gatsby's `createTypes` action.
*/
// TODO: Move typename to Dependencies (create in `buildDependencies.ts`).
export const buildAlternateLanguageType: RTE.ReaderTaskEither<
Dependencies,
never,
gatsby.GatsbyGraphQLType
> = pipe(
RTE.ask<Dependencies>(),
RTE.chain((deps) =>
buildObjectType({
name: deps.nodeHelpers.createTypeName('AlternateLanguageType'),
fields: {
id: 'ID',
uid: 'String',
lang: 'String',
type: 'String',
document: {
type: deps.nodeHelpers.createTypeName('AllDocumentTypes'),
resolve: (source: PrismicAPIAlternateLanguageField): string | null =>
deps.nodeHelpers.createNodeId(source.id),
extensions: { link: {} },
},
raw: { type: 'JSON', resolve: identity },
},
}),
),
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { buildTypePathType } from './builders/buildTypePathType'

import { Dependencies, Mutable, PluginOptions } from './types'
import { buildDependencies } from './buildDependencies'
import { buildAlternateLanguageType } from './builders/buildAlternateLanguageType'

const GatsbyGraphQLTypeM = A.getMonoid<gatsby.GatsbyGraphQLType>()

Expand All @@ -38,6 +39,7 @@ export const createBaseTypes: RTE.ReaderTaskEither<Dependencies, never, void> =
RTE.bind('baseTypes', () =>
pipe(
[
buildAlternateLanguageType,
buildEmbedType,
buildGeoPointType,
buildImageDimensionsType,
Expand Down
10 changes: 10 additions & 0 deletions packages/gatsby-source-prismic/src/lib/createCustomType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { buildObjectType } from './buildObjectType'
import { createType } from './createType'
import { buildFieldConfigMap } from './buildFieldConfigMap'
import { createTypePath } from './createTypePath'
import { listTypeName } from './listTypeName'
import { requiredTypeName } from './requiredTypeName'

/**
* Returns all fields from a Prismic Custom Type schema definition. It
Expand Down Expand Up @@ -142,6 +144,12 @@ export const createCustomType = (
...scope.rootFieldConfigMap,
...scope.dataFieldConfigMap,
[scope.nodeHelpers.createFieldName('id') as 'id']: 'ID!',
alternate_languages: pipe(
scope.nodeHelpers.createTypeName('AlternateLanguageType'),
requiredTypeName,
listTypeName,
requiredTypeName,
),
first_publication_date: {
type: 'Date!',
extensions: { dateformat: {} },
Expand All @@ -157,6 +165,8 @@ export const createCustomType = (
url: {
type: 'String',
resolve: (source: PrismicAPIDocumentNode) =>
// TODO: Once @prismicio/helpers V2 is released, use
// `documentToLinkField` along with `asLink`
scope.pluginOptions.linkResolver?.(source),
},
[PREVIEWABLE_NODE_ID_FIELD]: {
Expand Down
7 changes: 7 additions & 0 deletions packages/gatsby-source-prismic/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ export type PrismicAPILinkField = {
uid?: string
}

export type PrismicAPIAlternateLanguageField = {
id: string
uid?: string
type: string
lang: string
}

export type PrismicAPIImageField = {
dimensions: { width: number; height: number } | null
alt: string | null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as prismic from 'ts-prismic'
import md5 from 'tiny-hashes/md5'

const createId = () => md5(Math.random().toString())

export const createPrismicAPIDocument = <TData = Record<string, unknown>>(
fields?: Partial<prismic.Document<TData>>,
): prismic.Document<TData> => {
const id = md5(Math.random().toString())
const id = createId()
const alternateLanguageId1 = createId()
const alternateLanguageId2 = createId()

return {
id,
Expand All @@ -15,7 +19,20 @@ export const createPrismicAPIDocument = <TData = Record<string, unknown>>(
tags: ['tag'],
slugs: ['slug'],
lang: 'lang',
alternate_languages: [],
alternate_languages: [
{
id: alternateLanguageId1,
uid: alternateLanguageId1,
type: 'type',
lang: 'alt-lang-1',
},
{
id: alternateLanguageId2,
uid: alternateLanguageId2,
type: 'type',
lang: 'alt-lang-2',
},
],
first_publication_date: 'first_publication_date',
last_publication_date: 'last_publication_date',
...fields,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import test from 'ava'
import * as sinon from 'sinon'

import { createGatsbyContext } from './__testutils__/createGatsbyContext'
import { createPluginOptions } from './__testutils__/createPluginOptions'
import { findCreateTypesCall } from './__testutils__/findCreateTypesCall'

import { createSchemaCustomization } from '../src/gatsby-node'

test('creates base types', async (t) => {
const gatsbyContext = createGatsbyContext()
const pluginOptions = createPluginOptions(t)

// @ts-expect-error - Partial gatsbyContext provided
await createSchemaCustomization(gatsbyContext, pluginOptions)

t.true(
(gatsbyContext.actions.createTypes as sinon.SinonStub).calledWith({
kind: 'OBJECT',
config: sinon.match({
name: 'PrismicPrefixAlternateLanguageType',
fields: {
id: 'ID',
type: 'String',
lang: 'String',
uid: 'String',
document: {
type: 'PrismicPrefixAllDocumentTypes',
resolve: sinon.match.func,
extensions: { link: {} },
},
raw: {
type: 'JSON',
resolve: sinon.match.func,
},
},
}),
}),
)
})

test('document field resolves to linked node ID', async (t) => {
const gatsbyContext = createGatsbyContext()
const pluginOptions = createPluginOptions(t)

// @ts-expect-error - Partial gatsbyContext provided
await createSchemaCustomization(gatsbyContext, pluginOptions)

const call = findCreateTypesCall(
'PrismicPrefixAlternateLanguageType',
gatsbyContext.actions.createTypes as sinon.SinonStub,
)
const field = {
type: 'foo',
id: 'id',
uid: 'uid',
lang: 'lang',
}
const resolver = call.config.fields.document.resolve
const res = await resolver(field)

t.true(res === `Prismic prefix ${field.id}`)
})
Loading