Shopidy product variants can now be queried

This commit is contained in:
Matt Aitken
2023-01-10 15:13:01 +00:00
parent 0f062a2fe5
commit cd65d20015
6 changed files with 217 additions and 24 deletions
+5 -2
View File
@@ -15,12 +15,15 @@ const trigger = new Trigger({
run: async (event, ctx) => {
await ctx.logger.info("Get Shopify products for my store");
const response = await shopify.getProducts("get-shopify-products");
const response = await shopify.searchProductVariants(
"get-shopify-variants",
{}
);
console.log(response);
await ctx.logger.debug("Debug message");
return response.message;
return response;
},
});
@@ -5,10 +5,14 @@ import {
PerformRequestOptions,
RequestIntegration,
} from "../types";
import { createClient, gql } from "@urql/core";
import { Client, createClient, gql } from "@urql/core";
import { shopify } from "internal-providers";
import { z } from "zod";
const log = debug("trigger:integrations:slack");
type SearchVariantsSuccessResponse = z.infer<
typeof shopify.schemas.SearchVariantsSuccessResponseSchema
>;
class ShopifyRequestIntegration implements RequestIntegration {
constructor(
private readonly baseUrlFormat: string = "https://{shop}.myshopify.com/admin/api/2021-07/graphql.json"
@@ -39,14 +43,65 @@ class ShopifyRequestIntegration implements RequestIntegration {
},
});
switch (options.endpoint) {
case "productVariants.search": {
return this.#searchProductVariants(client, options.params);
}
default: {
throw new Error(`Unknown endpoint: ${options.endpoint}`);
}
}
}
displayProperties(endpoint: string, params: any): DisplayProperties {
return {
title: "Temporary",
};
throw new Error(`Unknown endpoint: ${endpoint}`);
}
async #searchProductVariants(
client: Client,
params: any
): Promise<PerformedRequestResponse> {
const parsedParams = shopify.schemas.SearchVariantsBodySchema.parse(params);
log("productVariants.search %O", parsedParams);
try {
const firstLast = buildFirstLast(parsedParams);
const filters = parsedParams.filter
? buildFilter(parsedParams.filter)
: undefined;
const query = gql`
query {
products(first: 5) {
productVariants(${firstLast}${filters ? `, ${filters}` : ""}) {
edges {
node {
id
handle
title
createdAt
updatedAt
price
product {
id
}
sku
barcode
compareAtPrice
fulfillmentService {
id
}
image {
id
}
inventoryQuantity
requiresShipping
position
taxCode
taxable
weight
weightUnit
}
}
pageInfo {
@@ -58,7 +113,7 @@ class ShopifyRequestIntegration implements RequestIntegration {
const result = await client.query(query, {}).toPromise();
if (result.error) {
console.error("Shopify result error", result.error);
log("productVariants.search failed %O", result.error);
return {
ok: false,
isRetryable: false,
@@ -69,18 +124,63 @@ class ShopifyRequestIntegration implements RequestIntegration {
};
}
console.log("Shopify success result", result.data);
if (result.data === undefined) {
log("productVariants.search data undefined %O");
return {
ok: false,
isRetryable: false,
response: {
output: {
message: "No data returned",
},
context: {},
},
};
}
return {
console.log("result.data", JSON.stringify(result.data));
const parsed = VariantsSearchQueryResultSchema.parse(result.data);
const response: SearchVariantsSuccessResponse = {
count: parsed.productVariants.edges.length,
productVariants: parsed.productVariants.edges.map((p) => ({
id: p.node.id,
title: p.node.id,
createdAt: p.node.createdAt,
updatedAt: p.node.updatedAt,
price: p.node.price,
product: p.node.product,
sku: p.node.sku,
barcode: p.node.barcode,
compareAtPrice: p.node.compareAtPrice,
fulfillmentService: p.node.fulfillmentService,
image: p.node.image,
inventoryQuantity: p.node.inventoryQuantity,
requiresShipping: p.node.requiresShipping,
position: p.node.position,
taxCode: p.node.taxCode,
taxable: p.node.taxable,
weight: p.node.weight,
weightUnit: p.node.weightUnit,
})),
};
const performedRequest = {
ok: true,
isRetryable: false,
response: {
output: result.data,
output: response,
context: {},
},
};
log("productVariants.search performedRequest %O", performedRequest);
return performedRequest;
} catch (error) {
console.error("Shopify query error", error);
console.error("productVariants.search query error %O", error);
log("productVariants.search query error %O", error);
return {
ok: false,
isRetryable: false,
@@ -91,13 +191,41 @@ class ShopifyRequestIntegration implements RequestIntegration {
};
}
}
displayProperties(endpoint: string, params: any): DisplayProperties {
return {
title: "Temporary",
};
throw new Error(`Unknown endpoint: ${endpoint}`);
}
}
export const requests = new ShopifyRequestIntegration();
function buildFirstLast(
firstLast: z.infer<typeof shopify.schemas.FirstOrLastSchema>
) {
let first = firstLast.first;
if (first === undefined && firstLast.last === undefined) {
first = 100;
}
if (first !== undefined) {
return `first: ${first}`;
}
return `last: ${firstLast.last}`;
}
function buildFilter(filter: Record<string, string[]>): string {
let filterQueries: string[] = [];
for (const [key, values] of Object.entries(filter)) {
filterQueries.push(
`(${values.map((value) => `${key}:${value}`).join(" OR ")})`
);
}
return filterQueries.join(" AND ");
}
const VariantsSearchQueryResultSchema = z.object({
productVariants: z.object({
edges: z.array(z.object({ node: shopify.schemas.ProductVariantSchema })),
pageInfo: z.object({
hasNextPage: z.boolean(),
}),
}),
});
@@ -1,4 +1,4 @@
// import * as schemas from "./schemas";
import * as schemas from "./schemas";
export const shopify = {
name: "Shopify",
@@ -20,5 +20,5 @@ export const shopify = {
],
documentation: `1. Follow (this guide)[https://help.shopify.com/en/manual/apps/custom-apps] to enable Custom apps`,
},
schemas: {},
schemas,
};
@@ -0,0 +1,49 @@
import { z } from "zod";
export const FirstOrLastSchema = z
.object({
first: z.number().optional(),
last: z.number().optional(),
})
.default({ first: 100 });
const objectWithId = z.object({
id: z.string(),
});
export const ProductVariantSchema = z.object({
id: z.string(),
title: z.string().nullable(),
createdAt: z.string().nullable(),
updatedAt: z.string().nullable(),
price: z.string().nullable(),
product: objectWithId,
sku: z.string().nullable(),
barcode: z.string().nullable(),
compareAtPrice: z.string().nullable(),
fulfillmentService: objectWithId.nullable(),
image: objectWithId.nullable(),
inventoryQuantity: z.number().nullable(),
requiresShipping: z.boolean().nullable(),
position: z.number().nullable(),
taxCode: z.string().nullable(),
taxable: z.boolean().nullable(),
weight: z.number().nullable(),
weightUnit: z.string().nullable(),
});
export const SearchVariantsBodySchema = FirstOrLastSchema.and(
z.object({
filter: z
.object({
productIds: z.array(z.string()).optional(),
skus: z.array(z.string()).optional(),
})
.optional(),
})
);
export const SearchVariantsSuccessResponseSchema = z.object({
count: z.number(),
productVariants: z.array(ProductVariantSchema),
});
@@ -2,7 +2,18 @@ import { getTriggerRun } from "@trigger.dev/sdk";
import { z } from "zod";
import { shopify } from "internal-providers";
export async function getProducts(key: string): Promise<any> {
export type SearchVariantsOptions = z.infer<
typeof shopify.schemas.SearchVariantsBodySchema
>;
export type SearchVariantsResponse = z.infer<
typeof shopify.schemas.SearchVariantsSuccessResponseSchema
>;
export async function searchProductVariants(
key: string,
options: SearchVariantsOptions
): Promise<SearchVariantsResponse> {
const run = getTriggerRun();
if (!run) {
@@ -11,10 +22,10 @@ export async function getProducts(key: string): Promise<any> {
const output = await run.performRequest(key, {
service: "shopify",
endpoint: "products.get",
params: {},
endpoint: "productVariants.search",
params: options,
response: {
schema: z.any(),
schema: shopify.schemas.SearchVariantsSuccessResponseSchema,
},
});
+2
View File
@@ -373,6 +373,8 @@ export class TriggerClient<TSchema extends z.ZodTypeAny> {
};
}
console.error(anyError);
return {
name: "UnknownError",
message: "An unknown error occurred",