Skip to content

Commit

Permalink
feat(elasticsearch-plugin): Custom mappings can return lists & allow …
Browse files Browse the repository at this point in the history
…additional Product/variant relation hydration

Closes #1054, closes #1141
  • Loading branch information
Izayda authored Oct 12, 2021
1 parent 4efe258 commit ee47095
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 97 deletions.
147 changes: 79 additions & 68 deletions packages/elasticsearch-plugin/src/indexer.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,13 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes

for (const productId of productIds) {
operations.push(...(await this.deleteProductOperations(productId)));
const optionsProductRelations = this.options.additionalProductRelationsToFetchFromDB ?
this.options.additionalProductRelationsToFetchFromDB: [];
const optionsVariantRelations = this.options.additionalVariantRelationsToFetchFromDB ?
this.options.additionalVariantRelationsToFetchFromDB: [];

const product = await this.connection.getRepository(Product).findOne(productId, {
relations: productRelations,
relations: [...productRelations,...optionsProductRelations],
where: {
deletedAt: null,
},
Expand All @@ -452,7 +457,7 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
const updatedProductVariants = await this.connection.getRepository(ProductVariant).findByIds(
product.variants.map(v => v.id),
{
relations: variantRelations,
relations: [...variantRelations,...optionsVariantRelations],
where: {
deletedAt: null,
},
Expand Down Expand Up @@ -692,75 +697,81 @@ export class ElasticsearchIndexerController implements OnModuleInit, OnModuleDes
ctx: RequestContext,
languageCode: LanguageCode,
): Promise<VariantIndexItem> {
const productAsset = v.product.featuredAsset;
const variantAsset = v.featuredAsset;
const productTranslation = this.getTranslation(v.product, languageCode);
const variantTranslation = this.getTranslation(v, languageCode);
const collectionTranslations = v.collections.map(c => this.getTranslation(c, languageCode));

const productCollectionTranslations = variants.reduce(
(translations, variant) => [
...translations,
...variant.collections.map(c => this.getTranslation(c, languageCode)),
],
[] as Array<Translation<Collection>>,
);
const prices = variants.map(variant => variant.price);
const pricesWithTax = variants.map(variant => variant.priceWithTax);

const item: VariantIndexItem = {
channelId: ctx.channelId,
languageCode,
productVariantId: v.id,
sku: v.sku,
slug: productTranslation.slug,
productId: v.product.id,
productName: productTranslation.name,
productAssetId: productAsset ? productAsset.id : undefined,
productPreview: productAsset ? productAsset.preview : '',
productPreviewFocalPoint: productAsset ? productAsset.focalPoint || undefined : undefined,
productVariantName: variantTranslation.name,
productVariantAssetId: variantAsset ? variantAsset.id : undefined,
productVariantPreview: variantAsset ? variantAsset.preview : '',
productVariantPreviewFocalPoint: variantAsset ? variantAsset.focalPoint || undefined : undefined,
price: v.price,
priceWithTax: v.priceWithTax,
currencyCode: v.currencyCode,
description: productTranslation.description,
facetIds: this.getFacetIds([v]),
channelIds: v.channels.map(c => c.id),
facetValueIds: this.getFacetValueIds([v]),
collectionIds: v.collections.map(c => c.id.toString()),
collectionSlugs: collectionTranslations.map(c => c.slug),
enabled: v.enabled && v.product.enabled,
productEnabled: variants.some(variant => variant.enabled) && v.product.enabled,
productPriceMin: Math.min(...prices),
productPriceMax: Math.max(...prices),
productPriceWithTaxMin: Math.min(...pricesWithTax),
productPriceWithTaxMax: Math.max(...pricesWithTax),
productFacetIds: this.getFacetIds(variants),
productFacetValueIds: this.getFacetValueIds(variants),
productCollectionIds: unique(
variants.reduce(
(ids, variant) => [...ids, ...variant.collections.map(c => c.id)],
[] as ID[],
try {
const productAsset = v.product.featuredAsset;
const variantAsset = v.featuredAsset;
const productTranslation = this.getTranslation(v.product, languageCode);
const variantTranslation = this.getTranslation(v, languageCode);
const collectionTranslations = v.collections.map(c => this.getTranslation(c, languageCode));

const productCollectionTranslations = variants.reduce(
(translations, variant) => [
...translations,
...variant.collections.map(c => this.getTranslation(c, languageCode)),
],
[] as Array<Translation<Collection>>,
);
const prices = variants.map(variant => variant.price);
const pricesWithTax = variants.map(variant => variant.priceWithTax);

const item: VariantIndexItem = {
channelId: ctx.channelId,
languageCode,
productVariantId: v.id,
sku: v.sku,
slug: productTranslation.slug,
productId: v.product.id,
productName: productTranslation.name,
productAssetId: productAsset ? productAsset.id : undefined,
productPreview: productAsset ? productAsset.preview : '',
productPreviewFocalPoint: productAsset ? productAsset.focalPoint || undefined : undefined,
productVariantName: variantTranslation.name,
productVariantAssetId: variantAsset ? variantAsset.id : undefined,
productVariantPreview: variantAsset ? variantAsset.preview : '',
productVariantPreviewFocalPoint: variantAsset ? variantAsset.focalPoint || undefined : undefined,
price: v.price,
priceWithTax: v.priceWithTax,
currencyCode: v.currencyCode,
description: productTranslation.description,
facetIds: this.getFacetIds([v]),
channelIds: v.channels.map(c => c.id),
facetValueIds: this.getFacetValueIds([v]),
collectionIds: v.collections.map(c => c.id.toString()),
collectionSlugs: collectionTranslations.map(c => c.slug),
enabled: v.enabled && v.product.enabled,
productEnabled: variants.some(variant => variant.enabled) && v.product.enabled,
productPriceMin: Math.min(...prices),
productPriceMax: Math.max(...prices),
productPriceWithTaxMin: Math.min(...pricesWithTax),
productPriceWithTaxMax: Math.max(...pricesWithTax),
productFacetIds: this.getFacetIds(variants),
productFacetValueIds: this.getFacetValueIds(variants),
productCollectionIds: unique(
variants.reduce(
(ids, variant) => [...ids, ...variant.collections.map(c => c.id)],
[] as ID[],
),
),
),
productCollectionSlugs: unique(productCollectionTranslations.map(c => c.slug)),
productChannelIds: v.product.channels.map(c => c.id),
inStock: 0 < (await this.productVariantService.getSaleableStockLevel(ctx, v)),
productInStock: await this.getProductInStockValue(ctx, variants),
};
const variantCustomMappings = Object.entries(this.options.customProductVariantMappings);
for (const [name, def] of variantCustomMappings) {
item[`variant-${name}`] = def.valueFn(v, languageCode);
}
productCollectionSlugs: unique(productCollectionTranslations.map(c => c.slug)),
productChannelIds: v.product.channels.map(c => c.id),
inStock: 0 < (await this.productVariantService.getSaleableStockLevel(ctx, v)),
productInStock: await this.getProductInStockValue(ctx, variants),
};
const variantCustomMappings = Object.entries(this.options.customProductVariantMappings);
for (const [name, def] of variantCustomMappings) {
item[`variant-${name}`] = def.valueFn(v, languageCode);
}

const productCustomMappings = Object.entries(this.options.customProductMappings);
for (const [name, def] of productCustomMappings) {
item[`product-${name}`] = def.valueFn(v.product, variants, languageCode);
const productCustomMappings = Object.entries(this.options.customProductMappings);
for (const [name, def] of productCustomMappings) {
item[`product-${name}`] = def.valueFn(v.product, variants, languageCode);
}
return item;
}
catch (err) {
Logger.error(err.toString());
throw Error(`Error while reindexing!`);
}
return item;
}

private async getProductInStockValue(ctx: RequestContext, variants: ProductVariant[]): Promise<boolean> {
Expand Down
12 changes: 12 additions & 0 deletions packages/elasticsearch-plugin/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,16 @@ export interface ElasticsearchOptions {
};
// TODO: docs
bufferUpdates?: boolean;
/**
* @description
* Additional product relations that will be fetched from DB while reindexing.
*/
additionalProductRelationsToFetchFromDB?: [string]|[];
/**
* @description
* Additional variant relations that will be fetched from DB while reindexing.
*/
additionalVariantRelationsToFetchFromDB?: [string]|[];
}

/**
Expand Down Expand Up @@ -421,6 +431,8 @@ export const defaultOptions: ElasticsearchRuntimeOptions = {
customProductMappings: {},
customProductVariantMappings: {},
bufferUpdates: false,
additionalProductRelationsToFetchFromDB:[],
additionalVariantRelationsToFetchFromDB:[],
};

export function mergeWithDefaults(userOptions: ElasticsearchOptions): ElasticsearchRuntimeOptions {
Expand Down
68 changes: 39 additions & 29 deletions packages/elasticsearch-plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,28 @@ export type IndexItemAssets = {
productVariantPreviewFocalPoint: Coordinate | undefined;
};

export type VariantIndexItem = Omit<
SearchResult,
'score' | 'price' | 'priceWithTax' | 'productAsset' | 'productVariantAsset'
> &
export type VariantIndexItem = Omit<SearchResult,
'score' | 'price' | 'priceWithTax' | 'productAsset' | 'productVariantAsset'> &
IndexItemAssets & {
channelId: ID;
languageCode: LanguageCode;
price: number;
priceWithTax: number;
collectionSlugs: string[];
productEnabled: boolean;
productPriceMin: number;
productPriceMax: number;
productPriceWithTaxMin: number;
productPriceWithTaxMax: number;
productFacetIds: ID[];
productFacetValueIds: ID[];
productCollectionIds: ID[];
productCollectionSlugs: string[];
productChannelIds: ID[];
[customMapping: string]: any;
inStock: boolean;
productInStock: boolean;
};
channelId: ID;
languageCode: LanguageCode;
price: number;
priceWithTax: number;
collectionSlugs: string[];
productEnabled: boolean;
productPriceMin: number;
productPriceMax: number;
productPriceWithTaxMin: number;
productPriceWithTaxMax: number;
productFacetIds: ID[];
productFacetValueIds: ID[];
productCollectionIds: ID[];
productCollectionSlugs: string[];
productChannelIds: ID[];
[customMapping: string]: any;
inStock: boolean;
productInStock: boolean;
};

export type ProductIndexItem = IndexItemAssets & {
sku: string;
Expand Down Expand Up @@ -245,24 +243,36 @@ export type UpdateIndexQueueJobData =
| RemoveVariantFromChannelJobData;

type CustomStringMapping<Args extends any[]> = CustomMappingDefinition<Args, 'String!', string>;
type CustomStringMappingList<Args extends any[]> = CustomMappingDefinition<Args, '[String!]', string[]>;
type CustomStringMappingNullable<Args extends any[]> = CustomMappingDefinition<Args, 'String', Maybe<string>>;
type CustomStringMappingNullableList<Args extends any[]> = CustomMappingDefinition<Args, '[String]', Array<Maybe<string>>>;
type CustomIntMapping<Args extends any[]> = CustomMappingDefinition<Args, 'Int!', number>;
type CustomIntMappingList<Args extends any[]> = CustomMappingDefinition<Args, '[Int!]', number[]>;
type CustomIntMappingNullable<Args extends any[]> = CustomMappingDefinition<Args, 'Int', Maybe<number>>;
type CustomIntMappingNullableList<Args extends any[]> = CustomMappingDefinition<Args, '[Int]', Array<Maybe<number>>>;
type CustomFloatMapping<Args extends any[]> = CustomMappingDefinition<Args, 'Float!', number>;
type CustomFloatMappingList<Args extends any[]> = CustomMappingDefinition<Args, '[Float!]', number[]>;
type CustomFloatMappingNullable<Args extends any[]> = CustomMappingDefinition<Args, 'Float', Maybe<number>>;
type CustomFloatMappingNullableList<Args extends any[]> = CustomMappingDefinition<Args, '[Float]', Array<Maybe<number>>>;
type CustomBooleanMapping<Args extends any[]> = CustomMappingDefinition<Args, 'Boolean!', boolean>;
type CustomBooleanMappingNullable<Args extends any[]> = CustomMappingDefinition<
Args,
'Boolean',
Maybe<boolean>
>;
type CustomBooleanMappingList<Args extends any[]> = CustomMappingDefinition<Args, '[Boolean!]', boolean[]>;
type CustomBooleanMappingNullable<Args extends any[]> = CustomMappingDefinition<Args, 'Boolean', Maybe<boolean>>;
type CustomBooleanMappingNullableList<Args extends any[]> = CustomMappingDefinition<Args, '[Boolean]', Array<Maybe<boolean>>>;

export type CustomMapping<Args extends any[]> =
| CustomStringMapping<Args>
| CustomStringMappingList<Args>
| CustomStringMappingNullable<Args>
| CustomStringMappingNullableList<Args>
| CustomIntMapping<Args>
| CustomIntMappingList<Args>
| CustomIntMappingNullable<Args>
| CustomIntMappingNullableList<Args>
| CustomFloatMapping<Args>
| CustomFloatMappingList<Args>
| CustomFloatMappingNullable<Args>
| CustomFloatMappingNullableList<Args>
| CustomBooleanMapping<Args>
| CustomBooleanMappingNullable<Args>;
| CustomBooleanMappingList<Args>
| CustomBooleanMappingNullable<Args>
| CustomBooleanMappingNullableList<Args>;

0 comments on commit ee47095

Please sign in to comment.