Algolia fields filtering (#946)

* wip

* crud for algolia fields settings

* add ui form fields confiugraion

* adjust app to new config

* filter mapping with fields

* fix lang

* fix lang
This commit is contained in:
Lukasz Ostrowski 2023-09-01 17:01:41 +02:00 committed by GitHub
parent 23e71bc7a2
commit 1e3c08c029
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 274 additions and 43 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-app-search": minor
---
Added fields filtering form. Unused fields can be unchecked to match Algolia limits. By default every field is selected

View file

@ -31,14 +31,14 @@ export const AlgoliaConfigurationForm = () => {
const { isLoading: isQueryLoading, refetch: refetchConfig } = const { isLoading: isQueryLoading, refetch: refetchConfig } =
trpcClient.configuration.getConfig.useQuery(undefined, { trpcClient.configuration.getConfig.useQuery(undefined, {
onSuccess(data) { onSuccess(data) {
setValue("secretKey", data?.secretKey || ""); setValue("secretKey", data?.appConfig?.secretKey || "");
setValue("appId", data?.appId || ""); setValue("appId", data?.appConfig?.appId || "");
setValue("indexNamePrefix", data?.indexNamePrefix || ""); setValue("indexNamePrefix", data?.appConfig?.indexNamePrefix || "");
}, },
}); });
const { mutate: setConfig, isLoading: isMutationLoading } = const { mutate: setConfig, isLoading: isMutationLoading } =
trpcClient.configuration.setConfig.useMutation({ trpcClient.configuration.setConnectionConfig.useMutation({
onSuccess: async () => { onSuccess: async () => {
await Promise.all([ await Promise.all([
refetchConfig(), refetchConfig(),
@ -59,6 +59,7 @@ export const AlgoliaConfigurationForm = () => {
appId: conf.appId ?? "", appId: conf.appId ?? "",
apiKey: conf.secretKey ?? "", apiKey: conf.secretKey ?? "",
indexNamePrefix: conf.indexNamePrefix, indexNamePrefix: conf.indexNamePrefix,
enabledKeys: [], // not required for ping but should be refactored
}); });
try { try {
@ -85,6 +86,7 @@ export const AlgoliaConfigurationForm = () => {
disabled={isFormDisabled} disabled={isFormDisabled}
required required
label="Application ID" label="Application ID"
/* cspell:disable-next-line */
helperText="Usually 10 characters, e.g. XYZAAABB00" helperText="Usually 10 characters, e.g. XYZAAABB00"
/> />
</Box> </Box>

View file

@ -0,0 +1,80 @@
import { Box, Checkbox, Divider, Skeleton, Button } from "@saleor/macaw-ui/next";
import { trpcClient } from "../modules/trpc/trpc-client";
import {
AlgoliaRootFields,
AlgoliaRootFieldsKeys,
AlgoliaRootFieldsLabelsMap,
} from "../lib/algolia-fields";
import { Controller, useForm } from "react-hook-form";
import { useEffect } from "react";
import { useDashboardNotification } from "@saleor/apps-shared";
export const AlgoliaFieldsSelectionForm = () => {
const { notifySuccess } = useDashboardNotification();
const { setValue, control, handleSubmit } = useForm<Record<AlgoliaRootFields, boolean>>({});
const { data: config, isLoading } = trpcClient.configuration.getConfig.useQuery();
const { mutate } = trpcClient.configuration.setFieldsMappingConfig.useMutation({
onSuccess() {
notifySuccess("Success", "Algolia will be updated only with selected fields");
},
});
useEffect(() => {
if (config) {
config.fieldsMapping.enabledAlgoliaFields.forEach((field) => {
setValue(field as AlgoliaRootFields, true);
});
}
}, [config, setValue]);
if (isLoading || !config) {
// todo replace with Section Skeleton
return <Skeleton height={5} />;
}
return (
<Box>
<form
onSubmit={handleSubmit((values) => {
const selectedValues = Object.entries(values)
.filter(([key, selected]) => selected)
.map(([key]) => key);
mutate({
enabledAlgoliaFields: selectedValues,
});
})}
>
<Box padding={5}>
{AlgoliaRootFieldsKeys.map((field) => (
<Box key={field} marginBottom={5}>
<Controller
name={field}
control={control}
render={({ field: { value, onChange } }) => {
return (
<Checkbox
onCheckedChange={(v) => {
onChange(v);
}}
checked={value}
name={field}
>
{AlgoliaRootFieldsLabelsMap[field]}
</Checkbox>
);
}}
/>
</Box>
))}
</Box>
<Divider margin={0} marginTop={5} />
<Box padding={5} display="flex" justifyContent="flex-end">
<Button type="submit">Save</Button>
</Box>
</form>
</Box>
);
};

View file

@ -17,18 +17,19 @@ export const ImportProductsToAlgolia = () => {
const { data: algoliaConfiguration } = trpcClient.configuration.getConfig.useQuery(); const { data: algoliaConfiguration } = trpcClient.configuration.getConfig.useQuery();
const searchProvider = useMemo(() => { const searchProvider = useMemo(() => {
if (!algoliaConfiguration?.appId || !algoliaConfiguration.secretKey) { if (!algoliaConfiguration?.appConfig?.appId || !algoliaConfiguration.appConfig?.secretKey) {
return null; return null;
} }
return new AlgoliaSearchProvider({ return new AlgoliaSearchProvider({
appId: algoliaConfiguration.appId, appId: algoliaConfiguration.appConfig.appId,
apiKey: algoliaConfiguration.secretKey, apiKey: algoliaConfiguration.appConfig.secretKey,
indexNamePrefix: algoliaConfiguration.indexNamePrefix, indexNamePrefix: algoliaConfiguration.appConfig.indexNamePrefix,
enabledKeys: algoliaConfiguration.fieldsMapping.enabledAlgoliaFields,
}); });
}, [ }, [
algoliaConfiguration?.appId, algoliaConfiguration?.appConfig?.appId,
algoliaConfiguration?.indexNamePrefix, algoliaConfiguration?.appConfig?.indexNamePrefix,
algoliaConfiguration?.secretKey, algoliaConfiguration?.appConfig?.secretKey,
]); ]);
const importProducts = useCallback(() => { const importProducts = useCallback(() => {

View file

@ -8,7 +8,8 @@ export const IndicesSettings = () => {
const { data: algoliaConfiguration } = trpcClient.configuration.getConfig.useQuery(); const { data: algoliaConfiguration } = trpcClient.configuration.getConfig.useQuery();
const updateWebhooksMutation = useIndicesSetupMutation(); const updateWebhooksMutation = useIndicesSetupMutation();
const isConfigured = algoliaConfiguration?.appId && algoliaConfiguration?.secretKey; const isConfigured =
algoliaConfiguration?.appConfig?.appId && algoliaConfiguration?.appConfig?.secretKey;
return ( return (
<Box> <Box>

View file

@ -1,6 +1,9 @@
import { Client } from "urql"; import { Client } from "urql";
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { IWebhooksActivityClient, WebhookActivityTogglerService } from "./WebhookActivityToggler.service"; import {
IWebhooksActivityClient,
WebhookActivityTogglerService,
} from "./WebhookActivityToggler.service";
describe("WebhookActivityTogglerService", function () { describe("WebhookActivityTogglerService", function () {
let mockWebhooksClient: IWebhooksActivityClient; let mockWebhooksClient: IWebhooksActivityClient;
@ -11,6 +14,8 @@ describe("WebhookActivityTogglerService", function () {
enableSingleWebhook: vi.fn(), enableSingleWebhook: vi.fn(),
disableSingleWebhook: vi.fn(), disableSingleWebhook: vi.fn(),
fetchAppWebhooksIDs: vi.fn(), fetchAppWebhooksIDs: vi.fn(),
createWebhook: vi.fn(),
removeSingleWebhook: vi.fn(),
}; };
service = new WebhookActivityTogglerService("ID", {} as Client, { service = new WebhookActivityTogglerService("ID", {} as Client, {

View file

@ -0,0 +1,34 @@
export type AlgoliaRootFields =
| "attributes"
| "media"
| "description"
| "descriptionPlaintext"
| "categories"
| "collections"
| "metadata"
| "variantMetadata"
| "otherVariants";
export const AlgoliaRootFieldsLabelsMap = {
attributes: "Product and variant attributes",
categories: "Product categories (5 levels)",
collections: "Product collection names",
description: "Product description - JSON",
descriptionPlaintext: "Product description - plain text",
media: "Variant media (images and videos)",
metadata: "Product metadata",
otherVariants: "IDs of other variants of the same product",
variantMetadata: "Variant metadata",
} satisfies Record<AlgoliaRootFields, string>;
export const AlgoliaRootFieldsKeys = [
"attributes",
"media",
"description",
"descriptionPlaintext",
"categories",
"collections",
"metadata",
"variantMetadata",
"otherVariants",
] as const;

View file

@ -18,6 +18,7 @@ export interface AlgoliaSearchProviderOptions {
apiKey: string; apiKey: string;
indexNamePrefix?: string; indexNamePrefix?: string;
channels?: Array<{ slug: string; currencyCode: string }>; channels?: Array<{ slug: string; currencyCode: string }>;
enabledKeys: string[];
} }
const logger = createLogger({ name: "AlgoliaSearchProvider" }); const logger = createLogger({ name: "AlgoliaSearchProvider" });
@ -26,13 +27,21 @@ export class AlgoliaSearchProvider implements SearchProvider {
#algolia: SearchClient; #algolia: SearchClient;
#indexNamePrefix?: string | undefined; #indexNamePrefix?: string | undefined;
#indexNames: Array<string>; #indexNames: Array<string>;
#enabledKeys: string[];
constructor({ appId, apiKey, indexNamePrefix, channels }: AlgoliaSearchProviderOptions) { constructor({
appId,
apiKey,
indexNamePrefix,
channels,
enabledKeys,
}: AlgoliaSearchProviderOptions) {
this.#algolia = Algoliasearch(appId, apiKey); this.#algolia = Algoliasearch(appId, apiKey);
this.#indexNamePrefix = indexNamePrefix; this.#indexNamePrefix = indexNamePrefix;
this.#indexNames = this.#indexNames =
channels?.map((c) => channelListingToAlgoliaIndexId({ channel: c }, this.#indexNamePrefix)) || channels?.map((c) => channelListingToAlgoliaIndexId({ channel: c }, this.#indexNamePrefix)) ||
[]; [];
this.#enabledKeys = enabledKeys;
} }
private async saveGroupedByIndex(groupedByIndex: GroupedByIndex) { private async saveGroupedByIndex(groupedByIndex: GroupedByIndex) {
@ -96,6 +105,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
const groupedByIndex = groupProductsByIndexName(productsBatch, { const groupedByIndex = groupProductsByIndexName(productsBatch, {
visibleInListings: true, visibleInListings: true,
indexNamePrefix: this.#indexNamePrefix, indexNamePrefix: this.#indexNamePrefix,
enabledKeys: this.#enabledKeys,
}); });
await this.saveGroupedByIndex(groupedByIndex); await this.saveGroupedByIndex(groupedByIndex);
@ -139,6 +149,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
const groupedByIndexToSave = groupVariantByIndexName(productVariant, { const groupedByIndexToSave = groupVariantByIndexName(productVariant, {
visibleInListings: true, visibleInListings: true,
indexNamePrefix: this.#indexNamePrefix, indexNamePrefix: this.#indexNamePrefix,
enabledKeys: this.#enabledKeys,
}); });
if (groupedByIndexToSave && !!Object.keys(groupedByIndexToSave).length) { if (groupedByIndexToSave && !!Object.keys(groupedByIndexToSave).length) {
@ -193,7 +204,12 @@ const groupVariantByIndexName = (
{ {
visibleInListings, visibleInListings,
indexNamePrefix, indexNamePrefix,
}: { visibleInListings: true | false | null; indexNamePrefix: string | undefined }, enabledKeys,
}: {
visibleInListings: true | false | null;
indexNamePrefix: string | undefined;
enabledKeys: string[];
},
) => { ) => {
logger.debug("Grouping variants per index name"); logger.debug("Grouping variants per index name");
if (!productVariant.channelListings) { if (!productVariant.channelListings) {
@ -225,6 +241,7 @@ const groupVariantByIndexName = (
const object = productAndVariantToAlgolia({ const object = productAndVariantToAlgolia({
variant: productVariant, variant: productVariant,
channel: channelListing.channel.slug, channel: channelListing.channel.slug,
enabledKeys,
}); });
return { return {
@ -246,13 +263,18 @@ const groupProductsByIndexName = (
{ {
visibleInListings, visibleInListings,
indexNamePrefix, indexNamePrefix,
}: { visibleInListings: true | false | null; indexNamePrefix: string | undefined }, enabledKeys,
}: {
visibleInListings: true | false | null;
indexNamePrefix: string | undefined;
enabledKeys: string[];
},
) => { ) => {
logger.debug(`groupProductsByIndexName called`); logger.debug(`groupProductsByIndexName called`);
const batchesAndIndices = productsBatch const batchesAndIndices = productsBatch
.flatMap((p) => p.variants) .flatMap((p) => p.variants)
.filter(isNotNil) .filter(isNotNil)
.map((p) => groupVariantByIndexName(p, { visibleInListings, indexNamePrefix })) .map((p) => groupVariantByIndexName(p, { visibleInListings, indexNamePrefix, enabledKeys }))
.filter(isNotNil) .filter(isNotNil)
.flatMap((x) => Object.entries(x)); .flatMap((x) => Object.entries(x));

View file

@ -6,6 +6,7 @@ import {
import { isNotNil } from "../isNotNil"; import { isNotNil } from "../isNotNil";
import { safeParseJson } from "../safe-parse-json"; import { safeParseJson } from "../safe-parse-json";
import { metadataToAlgoliaAttribute } from "./metadata-to-algolia-attribute"; import { metadataToAlgoliaAttribute } from "./metadata-to-algolia-attribute";
import { AlgoliaRootFields, AlgoliaRootFieldsKeys } from "../algolia-fields";
type PartialChannelListing = { type PartialChannelListing = {
channel: { channel: {
@ -82,9 +83,11 @@ const mapSelectedAttributesToRecord = (attr: ProductAttributesDataFragment) => {
export function productAndVariantToAlgolia({ export function productAndVariantToAlgolia({
variant, variant,
channel, channel,
enabledKeys,
}: { }: {
variant: ProductVariantWebhookPayloadFragment; variant: ProductVariantWebhookPayloadFragment;
channel: string; channel: string;
enabledKeys: string[];
}) { }) {
const product = variant.product; const product = variant.product;
const attributes = { const attributes = {
@ -138,7 +141,16 @@ export function productAndVariantToAlgolia({
metadata: metadataToAlgoliaAttribute(variant.product.metadata), metadata: metadataToAlgoliaAttribute(variant.product.metadata),
variantMetadata: metadataToAlgoliaAttribute(variant.metadata), variantMetadata: metadataToAlgoliaAttribute(variant.metadata),
otherVariants: variant.product.variants?.map((v) => v.id).filter((v) => v !== variant.id) || [], otherVariants: variant.product.variants?.map((v) => v.id).filter((v) => v !== variant.id) || [],
}; } satisfies Record<AlgoliaRootFields | string, unknown>;
// todo refactor
AlgoliaRootFieldsKeys.forEach((field) => {
const enabled = enabledKeys.includes(field);
if (!enabled) {
delete document[field];
}
});
return document; return document;
} }

View file

@ -2,13 +2,13 @@ import { createLogger } from "@saleor/apps-shared";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { ChannelsDocument } from "../../../generated/graphql"; import { ChannelsDocument } from "../../../generated/graphql";
import { WebhookActivityTogglerService } from "../../domain/WebhookActivityToggler.service"; import { WebhookActivityTogglerService } from "../../domain/WebhookActivityToggler.service";
import { AppConfigurationFields, AppConfigurationSchema } from "./configuration";
import { AlgoliaSearchProvider } from "../../lib/algolia/algoliaSearchProvider"; import { AlgoliaSearchProvider } from "../../lib/algolia/algoliaSearchProvider";
import { createSettingsManager } from "../../lib/metadata"; import { createSettingsManager } from "../../lib/metadata";
import { protectedClientProcedure } from "../trpc/protected-client-procedure"; import { protectedClientProcedure } from "../trpc/protected-client-procedure";
import { router } from "../trpc/trpc-server"; import { router } from "../trpc/trpc-server";
import { fetchLegacyConfiguration } from "./legacy-configuration";
import { AppConfigMetadataManager } from "./app-config-metadata-manager"; import { AppConfigMetadataManager } from "./app-config-metadata-manager";
import { AppConfigurationSchema, FieldsConfigSchema } from "./configuration";
import { fetchLegacyConfiguration } from "./legacy-configuration";
const logger = createLogger({ name: "configuration.router" }); const logger = createLogger({ name: "configuration.router" });
@ -17,7 +17,7 @@ export const configurationRouter = router({
const settingsManager = createSettingsManager(ctx.apiClient, ctx.appId); const settingsManager = createSettingsManager(ctx.apiClient, ctx.appId);
/** /**
* Backwards compatbitility * Backwards compatibility
*/ */
const domain = new URL(ctx.saleorApiUrl).host; const domain = new URL(ctx.saleorApiUrl).host;
@ -39,7 +39,7 @@ export const configurationRouter = router({
return config.getConfig(); return config.getConfig();
} }
}), }),
setConfig: protectedClientProcedure setConnectionConfig: protectedClientProcedure
.meta({ requiredClientPermissions: ["MANAGE_APPS"] }) .meta({ requiredClientPermissions: ["MANAGE_APPS"] })
.input(AppConfigurationSchema) .input(AppConfigurationSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
@ -51,6 +51,7 @@ export const configurationRouter = router({
apiKey: input.secretKey, apiKey: input.secretKey,
indexNamePrefix: input.indexNamePrefix, indexNamePrefix: input.indexNamePrefix,
channels, channels,
enabledKeys: [], // not required to ping algolia, but should be refactored
}); });
const settingsManager = createSettingsManager(ctx.apiClient, ctx.appId); const settingsManager = createSettingsManager(ctx.apiClient, ctx.appId);
@ -84,4 +85,17 @@ export const configurationRouter = router({
return null; return null;
}), }),
setFieldsMappingConfig: protectedClientProcedure
.meta({ requiredClientPermissions: ["MANAGE_APPS"] })
.input(FieldsConfigSchema)
.mutation(async ({ ctx, input }) => {
const settingsManager = createSettingsManager(ctx.apiClient, ctx.appId);
const configManager = new AppConfigMetadataManager(settingsManager);
const config = await configManager.get(ctx.saleorApiUrl);
config.setFieldsMapping(input.enabledAlgoliaFields);
configManager.set(config, ctx.saleorApiUrl);
}),
}); });

View file

@ -1,4 +1,5 @@
import { z } from "zod"; import { z } from "zod";
import { AlgoliaRootFieldsKeys } from "../../lib/algolia-fields";
export const AppConfigurationSchema = z.object({ export const AppConfigurationSchema = z.object({
appId: z.string().min(3), appId: z.string().min(3),
@ -6,17 +7,29 @@ export const AppConfigurationSchema = z.object({
secretKey: z.string().min(3), secretKey: z.string().min(3),
}); });
export type AppConfigurationFields = z.infer<typeof AppConfigurationSchema>; export const FieldsConfigSchema = z.object({
enabledAlgoliaFields: z.array(z.string()),
});
export const AppConfigRootSchema = AppConfigurationSchema.nullable(); const AppConfigRootSchema = z.object({
appConfig: AppConfigurationSchema.nullable(),
fieldsMapping: FieldsConfigSchema,
});
export type AppConfigurationFields = z.infer<typeof AppConfigurationSchema>;
export type AppConfigRootSchemaFields = z.infer<typeof AppConfigRootSchema>; export type AppConfigRootSchemaFields = z.infer<typeof AppConfigRootSchema>;
export class AppConfig { export class AppConfig {
private rootData: AppConfigRootSchemaFields = null; private rootData: AppConfigRootSchemaFields = {
appConfig: null,
fieldsMapping: {
enabledAlgoliaFields: [...AlgoliaRootFieldsKeys],
},
};
constructor(initialData?: AppConfigRootSchemaFields) { constructor(initialData?: AppConfigRootSchemaFields) {
if (initialData) { if (initialData) {
this.rootData = AppConfigurationSchema.parse(initialData); this.rootData = AppConfigRootSchema.parse(initialData);
} }
} }
@ -29,7 +42,15 @@ export class AppConfig {
} }
setAlgoliaSettings(settings: AppConfigurationFields) { setAlgoliaSettings(settings: AppConfigurationFields) {
this.rootData = AppConfigurationSchema.parse(settings); this.rootData.appConfig = AppConfigurationSchema.parse(settings);
return this;
}
setFieldsMapping(fieldsMapping: string[]) {
this.rootData.fieldsMapping = {
enabledAlgoliaFields: z.array(z.string()).parse(fieldsMapping),
};
return this; return this;
} }

View file

@ -7,6 +7,7 @@ import { createGraphQLClient } from "@saleor/apps-shared";
import { Client } from "urql"; import { Client } from "urql";
import { ChannelsDocument } from "../../../generated/graphql"; import { ChannelsDocument } from "../../../generated/graphql";
import { AlgoliaSearchProvider } from "../../lib/algolia/algoliaSearchProvider"; import { AlgoliaSearchProvider } from "../../lib/algolia/algoliaSearchProvider";
import { AppConfigMetadataManager } from "../../modules/configuration/app-config-metadata-manager";
const logger = createLogger({ const logger = createLogger({
service: "setupIndicesHandler", service: "setupIndicesHandler",
@ -31,28 +32,28 @@ export const setupIndicesHandlerFactory =
logger.debug("Fetching settings"); logger.debug("Fetching settings");
const client = graphqlClientFactory(authData.saleorApiUrl, authData.token); const client = graphqlClientFactory(authData.saleorApiUrl, authData.token);
const settingsManager = settingsManagerFactory(client, authData.appId); const settingsManager = settingsManagerFactory(client, authData.appId);
const configManager = new AppConfigMetadataManager(settingsManager);
const domain = new URL(authData.saleorApiUrl).host; const [config, channelsRequest] = await Promise.all([
configManager.get(authData.saleorApiUrl),
const [secretKey, appId, indexNamePrefix, channelsRequest] = await Promise.all([
settingsManager.get("secretKey", domain),
settingsManager.get("appId", domain),
settingsManager.get("indexNamePrefix", domain),
client.query(ChannelsDocument, {}).toPromise(), client.query(ChannelsDocument, {}).toPromise(),
]); ]);
if (!secretKey || !appId) { const configData = config.getConfig();
logger.debug("Missing secretKey or appId, returning 400");
if (!configData.appConfig) {
logger.debug("Missing config, returning 400");
return res.status(400).end(); return res.status(400).end();
} }
const channels = channelsRequest.data?.channels || []; const channels = channelsRequest.data?.channels || [];
const algoliaClient = new AlgoliaSearchProvider({ const algoliaClient = new AlgoliaSearchProvider({
appId, appId: configData.appConfig.appId,
apiKey: secretKey, apiKey: configData.appConfig.secretKey,
indexNamePrefix: indexNamePrefix, indexNamePrefix: configData.appConfig.indexNamePrefix,
channels, channels,
enabledKeys: configData.fieldsMapping.enabledAlgoliaFields,
}); });
try { try {

View file

@ -126,7 +126,7 @@ export default createProtectedHandler(
return new WebhookActivityTogglerService(appId, client); return new WebhookActivityTogglerService(appId, client);
}, },
algoliaSearchProviderFactory(appId, apiKey) { algoliaSearchProviderFactory(appId, apiKey) {
return new AlgoliaSearchProvider({ appId, apiKey }); return new AlgoliaSearchProvider({ appId, apiKey, enabledKeys: [] });
}, },
graphqlClientFactory(saleorApiUrl: string, token: string) { graphqlClientFactory(saleorApiUrl: string, token: string) {
return createGraphQLClient({ saleorApiUrl, token }); return createGraphQLClient({ saleorApiUrl, token });

View file

@ -7,6 +7,7 @@ import { MainInstructions } from "../../components/MainInstructions";
import { WebhooksStatusInstructions } from "../../components/WebhooksStatusInstructions"; import { WebhooksStatusInstructions } from "../../components/WebhooksStatusInstructions";
import { TextLink } from "@saleor/apps-ui"; import { TextLink } from "@saleor/apps-ui";
import { IndicesSettings } from "../../components/IndicesSettings"; import { IndicesSettings } from "../../components/IndicesSettings";
import { AlgoliaFieldsSelectionForm } from "../../components/AlgoliaFieldsSelectionForm";
const ALGOLIA_DASHBOARD_TOKENS_URL = "https://www.algolia.com/account/api-keys/all"; const ALGOLIA_DASHBOARD_TOKENS_URL = "https://www.algolia.com/account/api-keys/all";
@ -44,6 +45,22 @@ export const ConfigurationView = () => {
</Box> </Box>
} }
/> />
<AppSection
marginTop={14}
heading="Fields filtering"
mainContent={<AlgoliaFieldsSelectionForm />}
sideContent={
<Box>
<Text as="p" marginBottom={1.5}>
Decide which fields app should send with each product variant.
</Text>
<Text as="p" marginBottom={1.5}>
You should remove fields you do not need, to ensure Algolia limits will not be
exceeded.
</Text>
</Box>
}
/>
<AppSection <AppSection
includePadding includePadding
marginTop={14} marginTop={14}

View file

@ -26,11 +26,16 @@ export const createWebhookContext = async ({ authData }: { authData: AuthData })
throw new Error(errorMessage); throw new Error(errorMessage);
} }
if (!settings.appConfig) {
throw new Error("App not configured");
}
const algoliaClient = new AlgoliaSearchProvider({ const algoliaClient = new AlgoliaSearchProvider({
appId: settings.appId, appId: settings.appConfig?.appId,
apiKey: settings.secretKey, apiKey: settings.appConfig?.secretKey,
indexNamePrefix: settings.indexNamePrefix, indexNamePrefix: settings.appConfig?.indexNamePrefix,
channels, channels,
enabledKeys: settings.fieldsMapping.enabledAlgoliaFields,
}); });
return { return {

View file

@ -44,7 +44,18 @@
"Undiscounted", "Undiscounted",
"Upstash", "Upstash",
"urql", "urql",
"Vercel" "Vercel",
"RudderStack",
"DataDog",
"Clearpay",
"Afterpay",
"Nuvo",
"Appstore",
"tRPC",
"Algoliasearch",
"tanstack",
"hookform",
"urql"
], ],
"ignorePaths": ["node_modules", "package.json", "pnpm-lock.yaml", ".gitignore"] "ignorePaths": ["node_modules", "package.json", "pnpm-lock.yaml", ".gitignore"]
} }