Improve Search/Algolia mappings (#827)
* parse metadata before sending to algolia * variants mapping * extract metadata mapping * Changeset * rename field
This commit is contained in:
parent
fa65735571
commit
2cb7e5edee
8 changed files with 105 additions and 9 deletions
9
.changeset/friendly-planets-care.md
Normal file
9
.changeset/friendly-planets-care.md
Normal file
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
"saleor-app-search": minor
|
||||
---
|
||||
|
||||
Improved attributes mapping. Now Algolia will receive better products data:
|
||||
- Added `otherVariants` field which is *an array of variant id strings*. It will only contain other variants, so if the array is empty, it means the variant is the only one. It can be quickly used to count alternative variants or to reference them by ID.
|
||||
- Added `variantMetadata` field. Now `metadata` contains data for product parent and `variantMetadata` for each variant.
|
||||
- Improved JSON fields mapping. Now json-like fields will be sent to Algolia as structure jsons, not strings. This include: `description`, `metadata`, `variantMetadata`, `otherVariants`.
|
||||
|
|
@ -1,4 +1,7 @@
|
|||
fragment ProductData on Product {
|
||||
variants {
|
||||
id
|
||||
}
|
||||
id
|
||||
name
|
||||
description
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
fragment ProductVariantData on ProductVariant {
|
||||
metadata {
|
||||
key
|
||||
value
|
||||
}
|
||||
id
|
||||
name
|
||||
sku
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
ProductVariantWebhookPayloadFragment,
|
||||
} from "../../../generated/graphql";
|
||||
import { isNotNil } from "../isNotNil";
|
||||
import { safeParseJson } from "../safe-parse-json";
|
||||
import { metadataToAlgoliaAttribute } from "./metadata-to-algolia-attribute";
|
||||
|
||||
type PartialChannelListing = {
|
||||
channel: {
|
||||
|
@ -58,10 +60,6 @@ export function categoryHierarchicalFacets({ product }: ProductVariantWebhookPay
|
|||
return categoryLvlMapping;
|
||||
}
|
||||
|
||||
export function formatMetadata({ product }: ProductVariantWebhookPayloadFragment) {
|
||||
return Object.fromEntries(product.metadata?.map(({ key, value }) => [key, value]) || []);
|
||||
}
|
||||
|
||||
export type AlgoliaObject = ReturnType<typeof productAndVariantToAlgolia>;
|
||||
|
||||
/**
|
||||
|
@ -123,13 +121,15 @@ export function productAndVariantToAlgolia({
|
|||
productName: product.name,
|
||||
variantName: variant.name,
|
||||
attributes,
|
||||
description: product.description,
|
||||
description: safeParseJson(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),
|
||||
metadata: metadataToAlgoliaAttribute(variant.product.metadata),
|
||||
variantMetadata: metadataToAlgoliaAttribute(variant.metadata),
|
||||
otherVariants: variant.product.variants?.map((v) => v.id).filter((v) => v !== variant.id) || [],
|
||||
};
|
||||
|
||||
return document;
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import { metadataToAlgoliaAttribute } from "./metadata-to-algolia-attribute";
|
||||
import { map } from "zod";
|
||||
|
||||
describe("metadataToAlgoliaAttribute", () => {
|
||||
it("Maps string attribute", () => {
|
||||
expect(
|
||||
metadataToAlgoliaAttribute([
|
||||
{
|
||||
key: "foo",
|
||||
value: "bar",
|
||||
},
|
||||
{
|
||||
key: "foobar",
|
||||
value: "baz",
|
||||
},
|
||||
])
|
||||
).toEqual({
|
||||
foo: "bar",
|
||||
foobar: "baz",
|
||||
});
|
||||
});
|
||||
|
||||
it("Maps json attribute to nested json", () => {
|
||||
expect(
|
||||
metadataToAlgoliaAttribute([
|
||||
{
|
||||
key: "foo",
|
||||
value: `{"bar": "baz"}`,
|
||||
},
|
||||
{
|
||||
key: "foobar",
|
||||
value: `["a", "b", "c"]`,
|
||||
},
|
||||
])
|
||||
).toEqual({
|
||||
foo: {
|
||||
bar: "baz",
|
||||
},
|
||||
foobar: ["a", "b", "c"],
|
||||
});
|
||||
});
|
||||
|
||||
it("Maps invalid json attribute to string", () => {
|
||||
const invalidJson = `{"bar": "baz"`;
|
||||
|
||||
expect(metadataToAlgoliaAttribute([{ key: "invalidJson", value: invalidJson }])).toEqual({
|
||||
invalidJson: `{"bar": "baz"`,
|
||||
});
|
||||
});
|
||||
|
||||
it("Maps empty value", () => {
|
||||
expect(
|
||||
metadataToAlgoliaAttribute([
|
||||
{
|
||||
key: "foo",
|
||||
value: "",
|
||||
},
|
||||
])
|
||||
).toEqual({
|
||||
foo: null,
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import { MetadataItem } from "../../../generated/graphql";
|
||||
import { safeParseJson } from "../safe-parse-json";
|
||||
|
||||
export function metadataToAlgoliaAttribute(metadata: MetadataItem[]) {
|
||||
return Object.fromEntries(metadata?.map(({ key, value }) => [key, safeParseJson(value)]) || []);
|
||||
}
|
11
apps/search/src/lib/safe-parse-json.ts
Normal file
11
apps/search/src/lib/safe-parse-json.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export const safeParseJson = (jsonString: string) => {
|
||||
if (!jsonString) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (e) {
|
||||
return jsonString;
|
||||
}
|
||||
};
|
|
@ -1,15 +1,14 @@
|
|||
import { NextWebhookApiHandler, SaleorAsyncWebhook } from "@saleor/app-sdk/handlers/next";
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
import {
|
||||
ProductVariantUpdated,
|
||||
ProductVariantUpdatedDocument,
|
||||
} from "../../../../../generated/graphql";
|
||||
import { saleorApp } from "../../../../../saleor-app";
|
||||
import { WebhookActivityTogglerService } from "../../../../domain/WebhookActivityToggler.service";
|
||||
import { AlgoliaSearchProvider } from "../../../../lib/algolia/algoliaSearchProvider";
|
||||
import { getAlgoliaConfiguration } from "../../../../lib/algolia/getAlgoliaConfiguration";
|
||||
import { createDebug } from "../../../../lib/debug";
|
||||
import { createLogger } from "../../../../lib/logger";
|
||||
import { WebhookActivityTogglerService } from "../../../../domain/WebhookActivityToggler.service";
|
||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
|
|
Loading…
Reference in a new issue