Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes committed Jul 28, 2021
1 parent 14bc893 commit 44e23c0
Show file tree
Hide file tree
Showing 17 changed files with 905 additions and 238 deletions.
4 changes: 3 additions & 1 deletion packages/gatsby-plugin-cms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,12 @@
"dependencies": {
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.1",
"@vtex/gatsby-source-vtex": "^0.372.18",
"camelcase": "^6.2.0",
"chokidar": "^3.5.0",
"fetch-retry": "^4.1.1",
"fs-extra": "^9.0.1",
"gatsby-graphql-source-toolkit": "^2.0.1",
"isomorphic-unfetch": "^3.1.0",
"p-map": "^4.0.0"
}
}
178 changes: 168 additions & 10 deletions packages/gatsby-plugin-cms/src/gatsby-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,31 @@ import type {
CreatePagesArgs,
SourceNodesArgs,
PluginOptionsSchemaArgs,
CreateNodeArgs,
} from 'gatsby'
import type { StoreCollection } from '@vtex/gatsby-source-vtex'
import { sourceStoreCollectionNode } from '@vtex/gatsby-source-vtex'

import type {
IClusterCollection,
IBrandCollection,
ICategoryCollection,
} from './node-api/catalog/index'
import {
isBrandCollection,
isClusterCollection,
} from './node-api/catalog/index'
import { PLUGIN } from './constants'
import { fetchAllNodes } from './node-api/fetchNodes'
import { createSchemaCustomization, sourceNode } from './node-api/sourceNode'
import { sourceAllLocalNodes } from './node-api/sourceLocalNodes'
import { fetchAllNodes as fetchAllCmsNodes } from './node-api/cms/fetchNodes'
import {
createSchemaCustomization as createCmsSchemaCustomizations,
nodeId,
sourceNode as sourceCmsNode,
} from './node-api/cms/sourceNode'
import { sourceAllLocalNodes } from './node-api/cms/sourceLocalNodes'
import type { BuilderConfig } from './index'
import type { ICollection } from './native-types'
import { isCategoryCollection } from './native-types'

interface CMSContentType {
id: string
Expand Down Expand Up @@ -47,28 +65,168 @@ const SHADOWED_INDEX_PATH = join(root, 'src', name, 'index.ts')

export interface Options {
tenant: string
workspace?: string
workspace: string
environment: 'vtexcommercestable' | 'vtexcommercebeta'
sourcingMode: 'cms-first' | 'catalog-first'
}

export const pluginOptionsSchema = ({ Joi }: PluginOptionsSchemaArgs) =>
Joi.object({
tenant: Joi.string().required(),
workspace: Joi.string(),
workspace: Joi.string().required(),
environment: Joi.string()
.required()
.valid('vtexcommercestable')
.valid('vtexcommercebeta'),
sourcingMode: Joi.string().valid('cms-first').valid('catalog-first'),
})

type WithPLP<T> = T & { plp: string }

interface Overrides {
Category: Record<string, WithPLP<ICategoryCollection>>
Department: Record<string, WithPLP<ICategoryCollection>>
Cluster: Record<string, WithPLP<IClusterCollection>>
Brand: Record<string, WithPLP<IBrandCollection>>
}

let setOverrides: (value: Overrides) => void | undefined

const getOverrides = new Promise<Overrides>((resolve) => {
setOverrides = resolve
})

export const sourceNodes = async (
gatsbyApi: SourceNodesArgs,
options: Options
) => {
const nodes = await fetchAllNodes(gatsbyApi, options)
// Warning: Do not source cms and local nodes in parallel since this order is
// important for the local nodes not to overrider remote nodes
const cmsNodes = await fetchAllCmsNodes(gatsbyApi, options)

createSchemaCustomization(gatsbyApi, nodes)

for (const node of nodes) {
sourceNode(gatsbyApi, node)
for (const node of cmsNodes) {
sourceCmsNode(gatsbyApi, node)
}

await sourceAllLocalNodes(gatsbyApi, process.cwd(), PLUGIN)

createCmsSchemaCustomizations(gatsbyApi, cmsNodes)

/**
* Add CMS overrides to StoreCollection Nodes
*/

const collectionBlocks: Array<WithPLP<ICollection>> = []

for (const node of cmsNodes) {
for (const extraBlock of node.extraBlocks) {
const block = extraBlock.blocks.find((x) => x.name === 'Collection')

if (block) {
const props = (block.props as unknown) as ICollection

collectionBlocks.push({
...props,
plp: gatsbyApi.createNodeId(nodeId(node)),
})
}
}
}

const categories = collectionBlocks
.filter((x): x is WithPLP<ICategoryCollection> => isCategoryCollection(x))
.reduce(
(acc, curr) => ({ ...acc, [curr.categoryId]: curr }),
{} as Record<string, WithPLP<ICategoryCollection>>
)

const brands = collectionBlocks
.filter((x): x is WithPLP<IBrandCollection> => isBrandCollection(x))
.reduce(
(acc, curr) => ({ ...acc, [curr.brandId]: curr }),
{} as Record<string, WithPLP<IBrandCollection>>
)

const clusters = collectionBlocks
.filter((x): x is WithPLP<IClusterCollection> => isClusterCollection(x))
.reduce(
(acc, curr) => ({ ...acc, [curr.clusterId]: curr }),
{} as Record<string, WithPLP<IClusterCollection>>
)

setOverrides({
Category: categories,
Department: categories,
Brand: brands,
Cluster: clusters,
})

/**
* Source StoreCollection from clusters. This part isn't done in
* gatsby-source-vtex because collections do not have a defined
* path on the store
*/
for (const cluster of Object.values(clusters)) {
const node: StoreCollection = {
id: `${cluster.clusterId}:${cluster.seo.slug}`,
remoteId: cluster.clusterId,
slug: cluster.seo.slug,
seo: {
title: cluster.seo.title,
description: cluster.seo.description,
},
type: 'Cluster',
}

sourceStoreCollectionNode(gatsbyApi, node)
}

// TODO
if (options.sourcingMode === 'cms-first') {
throw new Error('NotImplemented')
}
}

const TypeKeyMap = {
Cluster: 'productClusterIds',
Brand: 'b',
Category: 'c',
Department: 'c',
}

export const onCreateNode = async (gatsbyApi: CreateNodeArgs) => {
const { node } = gatsbyApi

if (node.internal.type !== 'StoreCollection') {
return
}

const collection = (node as unknown) as StoreCollection
const overrides = await getOverrides

const maybeOverride = overrides[collection.type][collection.remoteId]

gatsbyApi.actions.createNodeField({
node,
name: 'searchParams',
value: {
sort: maybeOverride?.sort ?? '""',
itemsPerPage: 12,
selectedFacets:
collection.type === 'Cluster'
? [{ key: TypeKeyMap.Cluster, value: collection.remoteId }]
: collection.slug.split('/').map((segment) => ({
key: TypeKeyMap[collection.type],
value: segment,
})),
},
})

gatsbyApi.actions.createNodeField({
node,
name: `plp___NODE`,
value: maybeOverride?.plp ?? null,
})
}

export const createPages = async ({ graphql, reporter }: CreatePagesArgs) => {
Expand Down
12 changes: 12 additions & 0 deletions packages/gatsby-plugin-cms/src/native-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export {
Collection,
isCategoryCollection,
isBrandCollection,
isClusterCollection,
} from './node-api/catalog'
export type {
ICollection,
ICategoryCollection,
IClusterCollection,
IBrandCollection,
} from './node-api/catalog'
153 changes: 153 additions & 0 deletions packages/gatsby-plugin-cms/src/node-api/catalog/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import type { Schema } from '../../index'

type Sort =
| '' // 'Relevance',
| 'price:desc' // 'Price: High to Low',
| 'price:asc' // 'Price: Low to High',
| 'orders:desc' // 'Sales',
| 'name:desc' // 'Name, descending',
| 'name:asc' // 'Name, ascending',
| 'release:desc' // 'Release date',
| 'discount:desc' // 'Discount',

export interface ICategoryCollection {
sort: Sort
categoryId: string
}

export interface IBrandCollection {
sort: Sort
brandId: string
}

export interface IClusterCollection {
seo: {
slug: string
title: string
description: string
}
sort: Sort
clusterId: string
}

/**
* Definition of a Collection in the CMS
*/
export type ICollection =
| ICategoryCollection
| IBrandCollection
| IClusterCollection

export const isCategoryCollection = (
x: ICollection
): x is ICategoryCollection => typeof (x as any).categoryId === 'string'

export const isBrandCollection = (x: ICollection): x is IBrandCollection =>
typeof (x as any).brandId === 'string'

export const isClusterCollection = (x: ICollection): x is IClusterCollection =>
typeof (x as any).clusterId === 'string'

const SeoSchema = {
type: 'object',
title: 'Seo',
widget: {
'ui:ObjectFieldTemplate': 'GoogleSeoPreview',
},
required: ['title', 'description', 'slug'],
properties: {
title: {
type: 'string',
title: 'Title',
description:
'Appears in the browser tab and is suggested for search engines',
default: 'Page title',
},
slug: {
type: 'string',
title: 'URL slug',
description: "Final part of the page's address. No spaces allowed.",
default: '/path/to/page',
pattern: '^/([a-zA-Z0-9]|-|/|_)*',
},
description: {
type: 'string',
title: 'Description (Meta description)',
description: 'Suggested for search engines',
default: 'Page description',
},
},
} as Schema

const SortSchema = {
title: 'Default ordering',
type: 'string',
default: '""',
enum: [
'""',
'discount:desc',
'release:desc',
'name:asc',
'name:desc',
'orders:desc',
'price:asc',
'price:desc',
],
enumNames: [
'Relevance',
'Discount',
'Release date',
'Name, ascending',
'Name, descending',
'Sales',
'Price: Low to High',
'Price: High to Low',
],
} as Schema

export const Collection = {
title: 'Collection',
description: 'Definition of a Collection for the CMS',
oneOf: [
{
title: 'Category',
description: 'Configure a Category',
type: 'object',
required: ['categoryId', 'sort'],
properties: {
categoryId: {
title: 'Category ID',
type: 'string',
},
sort: SortSchema,
},
},
{
title: 'Brand',
description: 'Configure a Brand',
type: 'object',
required: ['brandId', 'sort'],
properties: {
brandId: {
title: 'Brand ID',
type: 'string',
},
sort: SortSchema,
},
},
{
title: 'Collection',
description: 'Configure a Collection',
type: 'object',
required: ['clusterId', 'sort', 'seo'],
properties: {
clusterId: {
title: 'Collection ID',
type: 'string',
},
sort: SortSchema,
seo: SeoSchema,
},
},
],
} as Schema
Loading

0 comments on commit 44e23c0

Please sign in to comment.