import { ProductAttributesDataFragment, ProductVariantWebhookPayloadFragment, } from "../../../generated/graphql"; import { isNotNil } from "../isNotNil"; type PartialChannelListing = { channel: { slug: string; currencyCode: string; }; }; export function channelListingToAlgoliaIndexId( channelListing: PartialChannelListing, indexNamePrefix: string | undefined ) { /** * Index name should not start with . (dot) */ const normalizedPrefix = indexNamePrefix === "" ? undefined : indexNamePrefix; const nameSegments = [ normalizedPrefix, channelListing.channel.slug, channelListing.channel.currencyCode, "products", ]; 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 = {}; 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; /** * 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(", ") || "", }; }; export function productAndVariantToAlgolia({ variant, channel, }: { variant: ProductVariantWebhookPayloadFragment; channel: string; }) { const product = variant.product; const attributes = { ...product.attributes.reduce((acc, attr, idx) => { const preparedAttr = mapSelectedAttributesToRecord(attr); if (!preparedAttr) { return acc; } return { ...acc, ...preparedAttr, }; }, {}), ...variant.attributes.reduce((acc, attr, idx) => { const preparedAttr = mapSelectedAttributesToRecord(attr); if (!preparedAttr) { return acc; } return { ...acc, ...preparedAttr, }; }, {}), }; 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), }; return document; } export function productAndVariantToObjectID({ product, ...variant }: ProductVariantWebhookPayloadFragment) { return `${product.id}_${variant.id}`; }