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

chore(gatsby-cms-plugin): fetch content by ID using REST API from CMS for preview #1120

Merged
merged 1 commit into from
Jan 26, 2022
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
25 changes: 24 additions & 1 deletion packages/gatsby-plugin-cms/src/gatsby-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ import type {
import type { StoreCollection } from '@vtex/gatsby-source-vtex'

import { Barrier } from './utils/barrier'
import { fetchAllNodes as fetchAllRemoteNodes } from './node-api/cms/fetchNodes'
import {
fetchNodeById,
fetchAllNodes as fetchAllRemoteNodes,
} from './node-api/cms/fetchNodes'
import {
createSchemaCustomization as createCmsSchemaCustomization,
sourceNode as sourceCmsNode,
sourceRestNode,
} from './node-api/cms/sourceNode'
import { fetchAllNodes as fetchAllLocalNodes } from './node-api/cms/sourceLocalNodes'
import {
Expand All @@ -30,6 +34,7 @@ import type {
IClusterCollection,
IBrandCollection,
} from './native-types/blocks/collection'
import { PLUGIN } from './constants'

interface CMSContentType {
id: string
Expand Down Expand Up @@ -100,6 +105,9 @@ export const sourceNodes = async (
gatsbyApi: SourceNodesArgs,
options: Options
) => {
const { webhookBody }: any = gatsbyApi
Copy link
Contributor

Choose a reason for hiding this comment

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

Can this object be typed with a better type, can't?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we can type it, but we can't guarantee the user passing this params, by default it's an open object

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, you're right. But at least could type something like interface Interface { webhookBody?: any }


// Fresh build. Let's source all data
// Warning: Do not source remote and local nodes in a different order since this
// is important for the local nodes not to overrider remote ones
const nodes = await Promise.all([
Expand All @@ -113,6 +121,21 @@ export const sourceNodes = async (
sourceCmsNode(gatsbyApi, node)
}

// Preview was triggered
if (webhookBody?.id && webhookBody.contentType) {
mateuspontes marked this conversation as resolved.
Show resolved Hide resolved
gatsbyApi.reporter.info(
`[${PLUGIN}]: Updating data from CMS: ${JSON.stringify(webhookBody)}`
)

const node = await fetchNodeById(gatsbyApi, options, webhookBody)

// Delete existing nodes
gatsbyApi.actions.deleteNode(gatsbyApi.getNode(node.id))

// Source the new node
sourceRestNode(gatsbyApi, node)
}

/**
* Add CMS overrides to StoreCollection Nodes
*/
Expand Down
66 changes: 64 additions & 2 deletions packages/gatsby-plugin-cms/src/node-api/cms/fetchNodes.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import type { ParentSpanPluginArgs } from 'gatsby'

import fetch from '../../utils/fetch'
import { fetch, fetchAPI } from '../../utils/fetch'
import { PLUGIN } from '../../constants'
import type { Options } from '../../gatsby-node'
import type { RelayPagination, RemotePageContent } from './types'
import type {
RelayPagination,
RemotePageContent,
RemoteRESTPageContent,
} from './types'

interface Query {
vtex: {
contents: RelayPagination<RemotePageContent>
}
}

const getApiUrl = ({ tenant, workspace }: Options) =>
`https://${workspace}--${tenant}.myvtex.com/_v/cms/api/faststore`

const LIST_PAGES_QUERY = `
query LIST_PAGES ($first: Int!, $after: String, $orderBy: VTEX_ContentsOrderInput, $filters: VTEX_ContentsFiltersInput) {
vtex {
Expand Down Expand Up @@ -173,3 +180,58 @@ export const fetchAllNodes = async (

return data
}

type ReleaseLocator = {
id: string
contentType: string
releaseId: string
}

type DraftLocator = {
id: string
contentType: string
versionId: string
}

const isReleaseLocator = (x: NodeLocator): x is ReleaseLocator =>
typeof (x as any).releaseId === 'string'

const isDraftLocator = (x: NodeLocator): x is DraftLocator =>
typeof (x as any).versionId === 'string'

export type NodeLocator = ReleaseLocator | DraftLocator

// Fetch the node on the REST API and transform it to the plugin
export const fetchNodeById = async (
gatsbyApi: ParentSpanPluginArgs,
options: Options,
locator: NodeLocator
) => {
const url = getApiUrl(options)

const activity = gatsbyApi.reporter.activityTimer(
`[${PLUGIN}]: fetching Node from remote`
)

activity.start()

const { id, contentType } = locator

const params = new URLSearchParams()

if (isReleaseLocator(locator)) {
params.set('releaseId', locator.releaseId)
}

if (isDraftLocator(locator)) {
params.set('versionId', locator.versionId)
}

const response = await fetchAPI<RemoteRESTPageContent>(
`${url}/${contentType}/${id}?${params.toString()}`
)

activity.end()

return response
}
40 changes: 37 additions & 3 deletions packages/gatsby-plugin-cms/src/node-api/cms/sourceNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { ParentSpanPluginArgs } from 'gatsby'

import { PLUGIN } from '../../constants'
import type { TransformedContent } from './fetchNodes'
import type { PageContent } from './types'
import type { PageContent, RemoteRESTPageContent } from './types'

export const getTypeName = (name: string) =>
camelcase(['cms', name], { pascalCase: true })
Expand Down Expand Up @@ -43,7 +43,7 @@ export const sourceNode = (
gatsbyApi: ParentSpanPluginArgs,
node: TransformedContent
) => {
const extra = node.variant.configurationDataSets?.reduce(
const extraBlocks = node.variant.configurationDataSets?.reduce(
(acc, { name, configurations }) => ({
...acc,
[camelcase(name)]: configurations?.reduce(
Expand All @@ -58,7 +58,7 @@ export const sourceNode = (
id: gatsbyApi.createNodeId(nodeId(node)),
name: node.name,
sections: node.variant.sections,
...extra,
...extraBlocks,
} as PageContent

gatsbyApi.actions.createNode(
Expand All @@ -74,6 +74,40 @@ export const sourceNode = (
)
}

export const sourceRestNode = (
gatsbyApi: ParentSpanPluginArgs,
node: RemoteRESTPageContent
) => {
const sections = node.sections?.reduce(
(acc, section) => [
...acc,
{
name: section.name,
props: section.data,
},
],
[] as Array<{ name: string; props: any }>
)

const data = ({
...node,
sections,
id: gatsbyApi.createNodeId(`${getTypeName(node.type)}:${node.id}`),
} as unknown) as PageContent

gatsbyApi.actions.createNode(
{
...data,
internal: {
type: getTypeName(node.type),
content: JSON.stringify(data),
contentDigest: gatsbyApi.createContentDigest(data),
},
},
{ name: PLUGIN }
)
}

export const deleteNode = (
gatsbyApi: ParentSpanPluginArgs,
remoteNode: TransformedContent
Expand Down
16 changes: 16 additions & 0 deletions packages/gatsby-plugin-cms/src/node-api/cms/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,19 @@ export type PageContent = {
name: string
sections: Block[]
} & Record<string, Record<string, Block['props']>>

export type RemoteRESTPageContent = {
id: string
name: string
status: string
type: string
versionId: string
sections: Section[]
children?: string[]
parent?: string
} & Record<string, unknown>

export interface Section {
name: string
data: unknown
}
14 changes: 12 additions & 2 deletions packages/gatsby-plugin-cms/src/utils/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import unfetch from 'isomorphic-unfetch'
import retry from 'fetch-retry'

const fetch = (input: RequestInfo, init?: RequestInit) =>
export const fetch = (input: RequestInfo, init?: RequestInit) =>
retry(unfetch, {
retries: 3,
retryDelay: 500,
})(input, init)

export default fetch
export const fetchAPI = async <T>(input: RequestInfo, init?: RequestInit) => {
const response = await fetch(input, init)

if (response.ok) {
return response.json() as Promise<T>
}

console.error(await response.text())

throw new Error(`Error while fetching ${input}`)
Comment on lines +16 to +19
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest putting this code inside an if branch and removing the response.json() from the branch. Response returning "ok" is the "happy path," so put it as most left as possible.

}