2023-02-13 16:08:20 +00:00
|
|
|
import {
|
|
|
|
ProductAttributesDataFragment,
|
|
|
|
ProductVariantWebhookPayloadFragment,
|
|
|
|
} from "../../../generated/graphql";
|
2023-02-08 08:28:14 +00:00
|
|
|
import { isNotNil } from "../isNotNil";
|
|
|
|
|
|
|
|
type PartialChannelListing = {
|
|
|
|
channel: {
|
|
|
|
slug: string;
|
|
|
|
currencyCode: string;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
export function channelListingToAlgoliaIndexId(
|
|
|
|
channelListing: PartialChannelListing,
|
2023-02-10 10:13:59 +00:00
|
|
|
indexNamePrefix: string | undefined
|
2023-02-08 08:28:14 +00:00
|
|
|
) {
|
2023-04-25 16:20:20 +00:00
|
|
|
/**
|
|
|
|
* Index name should not start with . (dot)
|
|
|
|
*/
|
|
|
|
const normalizedPrefix = indexNamePrefix === "" ? undefined : indexNamePrefix;
|
|
|
|
|
2023-02-08 08:28:14 +00:00
|
|
|
const nameSegments = [
|
2023-04-25 16:20:20 +00:00
|
|
|
normalizedPrefix,
|
2023-02-08 08:28:14 +00:00
|
|
|
channelListing.channel.slug,
|
|
|
|
channelListing.channel.currencyCode,
|
|
|
|
"products",
|
|
|
|
];
|
2023-04-18 13:10:00 +00:00
|
|
|
|
2023-02-08 08:28:14 +00:00
|
|
|
return nameSegments.filter(isNotNil).join(".");
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Produces category tree in the format expected by hierarchical Algolia widgets, for example:
|
|
|
|
*
|
|
|
|
* {
|
|
|
|
* "lvl0": "Root Category",
|
|
|
|
* "lvl1": "Root Category > Subcategory"
|
|
|
|
* "lvl2": "Root Category > Subcategory > Sub-subcategory"
|
|
|
|
* }
|
|
|
|
* https://www.algolia.com/doc/guides/managing-results/refine-results/faceting/#hierarchical-facets
|
|
|
|
*/
|
|
|
|
export function categoryHierarchicalFacets({ product }: ProductVariantWebhookPayloadFragment) {
|
|
|
|
const categoryParents = [
|
|
|
|
product.category?.parent?.parent?.parent?.parent?.name,
|
|
|
|
product.category?.parent?.parent?.parent?.name,
|
|
|
|
product.category?.parent?.parent?.name,
|
|
|
|
product.category?.parent?.name,
|
|
|
|
product.category?.name,
|
|
|
|
].filter((category) => category?.length);
|
|
|
|
|
|
|
|
const categoryLvlMapping: Record<string, string> = {};
|
|
|
|
|
|
|
|
for (let i = 0; i < categoryParents.length; i += 1) {
|
|
|
|
categoryLvlMapping[`lvl${i}`] = categoryParents.slice(0, i + 1).join(" > ");
|
|
|
|
}
|
|
|
|
|
|
|
|
return categoryLvlMapping;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatMetadata({ product }: ProductVariantWebhookPayloadFragment) {
|
|
|
|
return Object.fromEntries(product.metadata?.map(({ key, value }) => [key, value]) || []);
|
|
|
|
}
|
|
|
|
|
|
|
|
export type AlgoliaObject = ReturnType<typeof productAndVariantToAlgolia>;
|
|
|
|
|
2023-02-13 16:08:20 +00:00
|
|
|
/**
|
|
|
|
* Returns object with a key being attribute name and value of all attribute values
|
|
|
|
* separated by comma. If no value is selected, an empty string will be used instead.
|
|
|
|
*/
|
|
|
|
const mapSelectedAttributesToRecord = (attr: ProductAttributesDataFragment) => {
|
|
|
|
if (!attr.attribute.name?.length) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
const filteredValues = attr.values.filter((v) => !!v.name?.length);
|
|
|
|
|
|
|
|
return {
|
|
|
|
[attr.attribute.name]: filteredValues.map((v) => v.name).join(", ") || "",
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-02-08 08:28:14 +00:00
|
|
|
export function productAndVariantToAlgolia({
|
|
|
|
variant,
|
|
|
|
channel,
|
|
|
|
}: {
|
|
|
|
variant: ProductVariantWebhookPayloadFragment;
|
|
|
|
channel: string;
|
|
|
|
}) {
|
|
|
|
const product = variant.product;
|
|
|
|
const attributes = {
|
|
|
|
...product.attributes.reduce((acc, attr, idx) => {
|
2023-02-13 16:08:20 +00:00
|
|
|
const preparedAttr = mapSelectedAttributesToRecord(attr);
|
2023-04-18 13:10:00 +00:00
|
|
|
|
2023-02-13 16:08:20 +00:00
|
|
|
if (!preparedAttr) {
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
...preparedAttr,
|
|
|
|
};
|
2023-02-08 08:28:14 +00:00
|
|
|
}, {}),
|
|
|
|
...variant.attributes.reduce((acc, attr, idx) => {
|
2023-02-13 16:08:20 +00:00
|
|
|
const preparedAttr = mapSelectedAttributesToRecord(attr);
|
2023-04-18 13:10:00 +00:00
|
|
|
|
2023-02-13 16:08:20 +00:00
|
|
|
if (!preparedAttr) {
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
...acc,
|
|
|
|
...preparedAttr,
|
|
|
|
};
|
2023-02-08 08:28:14 +00:00
|
|
|
}, {}),
|
|
|
|
};
|
|
|
|
|
|
|
|
const listing = variant.channelListings?.find((l) => l.channel.slug === channel);
|
|
|
|
|
|
|
|
const document = {
|
|
|
|
objectID: productAndVariantToObjectID(variant),
|
|
|
|
productId: product.id,
|
|
|
|
variantId: variant.id,
|
|
|
|
name: `${product.name} - ${variant.name}`,
|
|
|
|
productName: product.name,
|
|
|
|
variantName: variant.name,
|
|
|
|
attributes,
|
|
|
|
description: product.description,
|
|
|
|
slug: product.slug,
|
|
|
|
thumbnail: product.thumbnail?.url,
|
|
|
|
grossPrice: listing?.price?.amount,
|
|
|
|
categories: categoryHierarchicalFacets(variant),
|
|
|
|
collections: product.collections?.map((collection) => collection.name) || [],
|
|
|
|
metadata: formatMetadata(variant),
|
|
|
|
};
|
2023-04-18 13:10:00 +00:00
|
|
|
|
2023-02-08 08:28:14 +00:00
|
|
|
return document;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function productAndVariantToObjectID({
|
|
|
|
product,
|
|
|
|
...variant
|
|
|
|
}: ProductVariantWebhookPayloadFragment) {
|
|
|
|
return `${product.id}_${variant.id}`;
|
|
|
|
}
|