Skip to content

Commit 069cb53

Browse files
author
Sam Slotsky
authored
Break variants into separate operation (#32027)
1 parent 06dfa9c commit 069cb53

File tree

11 files changed

+198
-90
lines changed

11 files changed

+198
-90
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"presets": [["babel-preset-gatsby-package"]],
3+
"overrides": [
4+
{
5+
"test": [],
6+
"presets": [["babel-preset-gatsby-package", { "browser": true, "esm": true }]]
7+
}
8+
]
9+
}

packages/gatsby-source-shopify/__tests__/fixtures.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
12
import {
23
GraphQLContext,
34
GraphQLRequest,
45
ResponseResolver,
56
graphql,
6-
ResponseComposition,
7+
GraphQLHandler,
78
MockedResponse,
89
} from "msw"
910

@@ -25,7 +26,7 @@ export function resolve<T>(data: T): Resolver<T> {
2526

2627
export function currentBulkOperation(
2728
status: BulkOperationStatus
28-
): Record<string, unknown> {
29+
): CurrentBulkOperationResponse {
2930
return {
3031
currentBulkOperation: {
3132
id: ``,
@@ -38,7 +39,9 @@ type BulkNodeOverrides = {
3839
[key in keyof BulkOperationNode]?: BulkOperationNode[key]
3940
}
4041

41-
export const startOperation = (overrides: BulkNodeOverrides = {}): any => {
42+
export const startOperation = (
43+
overrides: BulkNodeOverrides = {}
44+
): GraphQLHandler<GraphQLRequest<BulkOperationRunQueryResponse>> => {
4245
const { id = `12345` } = overrides
4346

4447
return graphql.mutation<BulkOperationRunQueryResponse>(

packages/gatsby-source-shopify/__tests__/make-source-from-operation.ts

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616

1717
const server = setupServer()
1818

19-
// @ts-ignore
19+
// @ts-ignore because these types will never match
2020
global.setTimeout = (fn: Promise<void>): Promise<void> => fn()
2121

2222
jest.mock(`gatsby-source-filesystem`, () => {
@@ -69,7 +69,9 @@ describe(`The collections operation`, () => {
6969
server.use(
7070
graphql.query<CurrentBulkOperationResponse>(
7171
`OPERATION_STATUS`,
72-
resolveOnce(currentBulkOperation(`COMPLETED`))
72+
resolveOnce<CurrentBulkOperationResponse>(
73+
currentBulkOperation(`COMPLETED`)
74+
)
7375
),
7476
startOperation(),
7577
graphql.query<{ node: BulkOperationNode }>(
@@ -709,14 +711,6 @@ describe(`The incremental products processor`, () => {
709711
{
710712
id: firstProductId,
711713
},
712-
{
713-
id: firstVariantId,
714-
__parentId: firstProductId,
715-
},
716-
{
717-
id: firstMetadataId,
718-
__parentId: firstVariantId,
719-
},
720714
{
721715
id: firstImageId,
722716
__parentId: firstProductId,
@@ -836,7 +830,7 @@ describe(`The incremental products processor`, () => {
836830

837831
await sourceFromOperation(operations.incrementalProducts(new Date()))
838832

839-
expect(createNode).toHaveBeenCalledTimes(4)
833+
expect(createNode).toHaveBeenCalledTimes(2)
840834
expect(deleteNode).toHaveBeenCalledTimes(6)
841835

842836
expect(deleteNode).toHaveBeenCalledWith(
@@ -888,20 +882,6 @@ describe(`The incremental products processor`, () => {
888882
})
889883
)
890884

891-
expect(createNode).toHaveBeenCalledWith(
892-
expect.objectContaining({
893-
id: firstMetadataId,
894-
productVariantId: firstVariantId,
895-
})
896-
)
897-
898-
expect(createNode).toHaveBeenCalledWith(
899-
expect.objectContaining({
900-
id: firstVariantId,
901-
productId: firstProductId,
902-
})
903-
)
904-
905885
expect(createNode).toHaveBeenCalledWith(
906886
expect.objectContaining({
907887
id: firstProductId,

packages/gatsby-source-shopify/src/gatsby-node.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,15 @@ async function sourceAllNodes(
5454
): Promise<void> {
5555
const {
5656
createProductsOperation,
57+
createProductVariantsOperation,
5758
createOrdersOperation,
5859
createCollectionsOperation,
5960
finishLastOperation,
6061
completedOperation,
6162
cancelOperationInProgress,
6263
} = createOperations(pluginOptions, gatsbyApi)
6364

64-
const operations = [createProductsOperation]
65+
const operations = [createProductsOperation, createProductVariantsOperation]
6566
if (pluginOptions.shopifyConnections?.includes(`orders`)) {
6667
operations.push(createOrdersOperation)
6768
}
@@ -106,6 +107,7 @@ async function sourceChangedNodes(
106107
): Promise<void> {
107108
const {
108109
incrementalProducts,
110+
incrementalProductVariants,
109111
incrementalOrders,
110112
incrementalCollections,
111113
finishLastOperation,
@@ -125,7 +127,11 @@ async function sourceChangedNodes(
125127
.forEach(node => gatsbyApi.actions.touchNode(node))
126128
}
127129

128-
const operations = [incrementalProducts(lastBuildTime)]
130+
const operations = [
131+
incrementalProducts(lastBuildTime),
132+
incrementalProductVariants(lastBuildTime),
133+
]
134+
129135
if (pluginOptions.shopifyConnections?.includes(`orders`)) {
130136
operations.push(incrementalOrders(lastBuildTime))
131137
}

packages/gatsby-source-shopify/src/node-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ export function nodeBuilder(
162162
pluginOptions: ShopifyPluginOptions
163163
): NodeBuilder {
164164
return {
165-
async buildNode(result: BulkResult): Promise<any> {
165+
async buildNode(result: BulkResult): Promise<NodeInput> {
166166
if (!pattern.test(result.id)) {
167167
throw new Error(
168168
`Expected an ID in the format gid://shopify/<typename>/<id>`

packages/gatsby-source-shopify/src/operations.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { NodeInput, SourceNodesArgs } from "gatsby"
22
import { shiftLeft } from "shift-left"
33
import { createClient } from "./client"
44
import { ProductsQuery } from "./query-builders/products-query"
5+
import { ProductVariantsQuery } from "./query-builders/product-variants-query"
56
import { CollectionsQuery } from "./query-builders/collections-query"
67
import { OrdersQuery } from "./query-builders/orders-query"
78
import {
89
collectionsProcessor,
910
incrementalProductsProcessor,
11+
productVariantsProcessor,
1012
} from "./processors"
1113
import { OperationError } from "./errors"
1214

@@ -29,9 +31,11 @@ export interface IShopifyBulkOperation {
2931

3032
interface IOperations {
3133
incrementalProducts: (date: Date) => IShopifyBulkOperation
34+
incrementalProductVariants: (date: Date) => IShopifyBulkOperation
3235
incrementalOrders: (date: Date) => IShopifyBulkOperation
3336
incrementalCollections: (date: Date) => IShopifyBulkOperation
3437
createProductsOperation: IShopifyBulkOperation
38+
createProductVariantsOperation: IShopifyBulkOperation
3539
createOrdersOperation: IShopifyBulkOperation
3640
createCollectionsOperation: IShopifyBulkOperation
3741
cancelOperationInProgress: () => Promise<void>
@@ -216,6 +220,14 @@ export function createOperations(
216220
)
217221
},
218222

223+
incrementalProductVariants(date: Date): IShopifyBulkOperation {
224+
return createOperation(
225+
new ProductVariantsQuery(options).query(date),
226+
`INCREMENTAL_PRODUCT_VARIANTS`,
227+
productVariantsProcessor
228+
)
229+
},
230+
219231
incrementalOrders(date: Date): IShopifyBulkOperation {
220232
return createOperation(
221233
new OrdersQuery(options).query(date),
@@ -236,6 +248,12 @@ export function createOperations(
236248
`PRODUCTS`
237249
),
238250

251+
createProductVariantsOperation: createOperation(
252+
new ProductVariantsQuery(options).query(),
253+
`PRODUCT_VARIANTS`,
254+
productVariantsProcessor
255+
),
256+
239257
createOrdersOperation: createOperation(
240258
new OrdersQuery(options).query(),
241259
`ORDERS`

packages/gatsby-source-shopify/src/processors/incremental-products.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,13 @@ export function incrementalProductsProcessor(
1414
return remoteType === `Product`
1515
})
1616

17-
const nodeIds = products.map(product =>
17+
const variants = objects.filter(obj => {
18+
const [, remoteType] = obj.id.match(idPattern) || []
19+
20+
return remoteType === `ProductVariant`
21+
})
22+
23+
const productNodeIds = products.map(product =>
1824
createNodeId(product.id, gatsbyApi, pluginOptions)
1925
)
2026

@@ -26,15 +32,29 @@ export function incrementalProductsProcessor(
2632
*/
2733
const variantsToDelete = gatsbyApi
2834
.getNodesByType(`${typePrefix}ShopifyProductVariant`)
29-
.filter(node => nodeIds.includes(node.productId as string))
35+
.filter(node => {
36+
const index = productNodeIds.indexOf(node.productId as string)
37+
if (index >= 0) {
38+
const product = products[index]
39+
const variantIds = variants
40+
.filter(v => v.__parentId === product.id)
41+
.map(v => createNodeId(v.id, gatsbyApi, pluginOptions))
42+
43+
if (!variantIds.includes(node.id)) {
44+
return true
45+
}
46+
}
47+
48+
return false
49+
})
3050

3151
variantsToDelete.forEach(variant => {
3252
gatsbyApi.actions.deleteNode(variant)
3353
})
3454

3555
const imagesToDelete = gatsbyApi
3656
.getNodesByType(`${typePrefix}ShopifyProductImage`)
37-
.filter(node => nodeIds.includes(node.productId as string))
57+
.filter(node => productNodeIds.includes(node.productId as string))
3858

3959
imagesToDelete.forEach(image => {
4060
gatsbyApi.actions.deleteNode(image)
@@ -54,5 +74,11 @@ export function incrementalProductsProcessor(
5474
}
5575
})
5676

57-
return objects.map(builder.buildNode)
77+
const objectsToBuild = objects.filter(obj => {
78+
const [, remoteType] = obj.id.match(idPattern) || []
79+
80+
return remoteType !== `ProductVariant`
81+
})
82+
83+
return objectsToBuild.map(builder.buildNode)
5884
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./collections"
22
export * from "./incremental-products"
3+
export * from "./product-variants"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { NodeInput } from "gatsby"
2+
import { pattern as idPattern } from "../node-builder"
3+
4+
export function productVariantsProcessor(
5+
objects: BulkResults,
6+
builder: NodeBuilder
7+
): Array<Promise<NodeInput>> {
8+
const objectsToBuild = objects.filter(obj => {
9+
const [, remoteType] = obj.id.match(idPattern) || []
10+
11+
return remoteType !== `Product`
12+
})
13+
14+
/**
15+
* We will need to attach presentmentPrices here as a simple array.
16+
* To achieve that, we could go through the results backwards and
17+
* save the ProductVariantPricePair records to a map that's keyed
18+
* by the variant ID, which can be obtained by reading the __parentId
19+
* field of the ProductVariantPricePair record.
20+
*
21+
* We do similar processing to collect the product IDs for a collection,
22+
* so please see the processors/collections.ts for reference.
23+
*/
24+
25+
return objectsToBuild.map(builder.buildNode)
26+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { BulkQuery } from "./bulk-query"
2+
3+
export class ProductVariantsQuery extends BulkQuery {
4+
query(date?: Date): string {
5+
const publishedStatus = this.pluginOptions.salesChannel
6+
? encodeURIComponent(`${this.pluginOptions.salesChannel}=visible`)
7+
: `published`
8+
9+
const filters = [`status:active`, `published_status:${publishedStatus}`]
10+
if (date) {
11+
const isoDate = date.toISOString()
12+
filters.push(`created_at:>='${isoDate}' OR updated_at:>='${isoDate}'`)
13+
}
14+
15+
const ProductVariantSortKey = `POSITION`
16+
17+
const queryString = filters.map(f => `(${f})`).join(` AND `)
18+
19+
const query = `
20+
{
21+
products(query: "${queryString}") {
22+
edges {
23+
node {
24+
id
25+
variants(sortKey: ${ProductVariantSortKey}) {
26+
edges {
27+
node {
28+
availableForSale
29+
barcode
30+
compareAtPrice
31+
createdAt
32+
displayName
33+
id
34+
image {
35+
id
36+
altText
37+
height
38+
width
39+
originalSrc
40+
transformedSrc
41+
}
42+
inventoryPolicy
43+
inventoryQuantity
44+
legacyResourceId
45+
position
46+
price
47+
selectedOptions {
48+
name
49+
value
50+
}
51+
sellingPlanGroupCount
52+
sku
53+
storefrontId
54+
taxCode
55+
taxable
56+
title
57+
updatedAt
58+
weight
59+
weightUnit
60+
metafields {
61+
edges {
62+
node {
63+
createdAt
64+
description
65+
id
66+
key
67+
legacyResourceId
68+
namespace
69+
ownerType
70+
updatedAt
71+
value
72+
valueType
73+
}
74+
}
75+
}
76+
}
77+
}
78+
}
79+
}
80+
}
81+
}
82+
}
83+
`
84+
85+
return this.bulkOperationQuery(query)
86+
}
87+
}

0 commit comments

Comments
 (0)