Skip to content

Commit

Permalink
Merge pull request #69 from Relewise/feat/facet-finder
Browse files Browse the repository at this point in the history
feat: Get Product Facet
  • Loading branch information
mzanoni authored Jul 4, 2024
2 parents add6976 + f76d023 commit 3cbde3d
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 5 deletions.
4 changes: 3 additions & 1 deletion packages/client/src/builders/conditionBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { ContainsCondition, DistinctCondition, EqualsCondition, GreaterThanCondition, LessThanCondition, ValueConditionCollection, DataValueBase, DataObjectFilterConditionBuilder, HasValueCondition, RelativeDateTimeCondition } from '..';
import { ContainsCondition, DistinctCondition, EqualsCondition, GreaterThanCondition, LessThanCondition, HasValueCondition, RelativeDateTimeCondition, ValueConditionCollection } from '../models/data-contracts';
import { DataValueBase } from '../models/dataValue';
import { DataObjectFilterConditionBuilder } from './dataObjectFilterConditionBuilder';

export type Conditions = ContainsCondition | DistinctCondition | EqualsCondition | GreaterThanCondition | LessThanCondition | HasValueCondition | RelativeDateTimeCondition;

Expand Down
4 changes: 3 additions & 1 deletion packages/client/src/builders/relevanceModifierBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { BrandIdRelevanceModifier, ConditionBuilder, ContentCategoryDataRelevanceModifier, ContentCategoryRecentlyViewedByUserRelevanceModifier, ContentDataRelevanceModifier, ContentRecentlyViewedByUserRelevanceModifier, DataDoubleSelector, FilterBuilder, FixedDoubleValueSelector, ProductAssortmentRelevanceModifier, ProductCategoryDataRelevanceModifier, ProductCategoryIdRelevanceModifier, ProductCategoryRecentlyViewedByUserRelevanceModifier, ProductDataRelevanceModifier, ProductIdRelevanceModifier, ProductListPriceRelevanceModifier, ProductRecentlyPurchasedByCompanyRelevanceModifier, ProductRecentlyPurchasedByUserCompanyRelevanceModifier, ProductRecentlyPurchasedByUserRelevanceModifier, ProductRecentlyViewedByCompanyRelevanceModifier, ProductRecentlyViewedByUserCompanyRelevanceModifier, ProductRecentlyViewedByUserRelevanceModifier, ProductSalesPriceRelevanceModifier, RelevanceModifierCollection, UserFavoriteProductRelevanceModifier, VariantAssortmentRelevanceModifier, VariantDataRelevanceModifier, VariantIdRelevanceModifier, VariantListPriceRelevanceModifier, VariantSalesPriceRelevanceModifier, VariantSpecificationsInCommonRelevanceModifier, VariantSpecificationValueRelevanceModifier } from '..';
import { BrandIdRelevanceModifier, ContentCategoryDataRelevanceModifier, ContentCategoryRecentlyViewedByUserRelevanceModifier, ContentDataRelevanceModifier, ContentRecentlyViewedByUserRelevanceModifier, ProductAssortmentRelevanceModifier, ProductCategoryDataRelevanceModifier, ProductCategoryIdRelevanceModifier, ProductCategoryRecentlyViewedByUserRelevanceModifier, ProductDataRelevanceModifier, ProductIdRelevanceModifier, ProductListPriceRelevanceModifier, ProductRecentlyPurchasedByCompanyRelevanceModifier, ProductRecentlyPurchasedByUserCompanyRelevanceModifier, ProductRecentlyPurchasedByUserRelevanceModifier, ProductRecentlyViewedByCompanyRelevanceModifier, ProductRecentlyViewedByUserCompanyRelevanceModifier, ProductRecentlyViewedByUserRelevanceModifier, ProductSalesPriceRelevanceModifier, UserFavoriteProductRelevanceModifier, VariantAssortmentRelevanceModifier, VariantDataRelevanceModifier, VariantIdRelevanceModifier, VariantListPriceRelevanceModifier, VariantSalesPriceRelevanceModifier, VariantSpecificationsInCommonRelevanceModifier, VariantSpecificationValueRelevanceModifier, DataDoubleSelector, FixedDoubleValueSelector, RelevanceModifierCollection } from '../models/data-contracts';
import { ConditionBuilder } from './conditionBuilder';
import { FilterBuilder } from './filterBuilder';

export class RelevanceModifierBuilder {
private modifiers: (
Expand Down
248 changes: 248 additions & 0 deletions packages/client/src/builders/search/getProductFacet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { BrandFacetResult, CategoryFacetResult, CategoryHierarchyFacetResult, PriceRangeFacetResult, PriceRangesFacetResult, ProductAssortmentFacetResult, ProductDataBooleanValueFacetResult, ProductDataDoubleRangeFacetResult, ProductDataDoubleRangesFacetResult, ProductDataDoubleValueFacetResult, ProductDataObjectFacetResult, ProductDataStringValueFacetResult, ProductFacetResult, VariantSpecificationFacetResult } from 'src/models/data-contracts';

export type DataSelectionStrategy = ProductDataDoubleRangeFacetResult['dataSelectionStrategy'];
export type PriceSelectionStrategy = PriceRangeFacetResult['priceSelectionStrategy'];

export class GetProductFacet {
public static productAssortment(facets: ProductFacetResult, selectionStrategy: 'Product' | 'Variant' | 'VariantWithFallbackToProduct' | 'ProductWithFallbackToVariant'): ProductAssortmentFacetResult | null {
if (!facets?.items) return null;

return facets
.items
.find((item): item is ProductAssortmentFacetResult =>
'$type' in item &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.ProductAssortmentFacetResult, Relewise.Client' &&
'field' in item &&
item.field === 'Assortment' &&
'assortmentSelectionStrategy' in item &&
item.assortmentSelectionStrategy === selectionStrategy) || null;
}

public static brand(facets: ProductFacetResult): BrandFacetResult | null {
if (!facets?.items) return null;

return facets
.items
.find((item): item is BrandFacetResult =>
'$type' in item &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.BrandFacetResult, Relewise.Client' &&
'field' in item &&
item.field === 'Brand') || null;
}

public static category(facets: ProductFacetResult, selectionStrategy: 'ImmediateParent' | 'Ancestors' | 'Descendants'): CategoryFacetResult | null {
if (!facets?.items) return null;

return facets
.items
.find((item): item is CategoryFacetResult =>
'$type' in item &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.CategoryFacetResult, Relewise.Client' &&
'categorySelectionStrategy' in item
&& item.categorySelectionStrategy === selectionStrategy) || null;
}

public static categoryHierarchy(facets: ProductFacetResult, selectionStrategy: 'ImmediateParent' | 'Ancestors' | 'Descendants'): CategoryHierarchyFacetResult | null {
if (!facets?.items) return null;

return facets
.items
.find((item): item is CategoryHierarchyFacetResult =>
'$type' in item &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.CategoryHierarchyFacet, Relewise.Client' &&
'categorySelectionStrategy' in item &&
item.categorySelectionStrategy === selectionStrategy) || null;
}

public static listPriceRange(
facets: ProductFacetResult,
selectionStrategy: PriceSelectionStrategy,
): PriceRangeFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is PriceRangeFacetResult =>
item.field === 'ListPrice' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.PriceRangeFacetResult, Relewise.Client' &&
'priceSelectionStrategy' in item &&
item.priceSelectionStrategy === selectionStrategy,
) || null;
}

public static salesPriceRange(
facets: ProductFacetResult,
selectionStrategy: PriceSelectionStrategy,
): PriceRangeFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is PriceRangeFacetResult =>
item.field === 'SalesPrice' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.PriceRangeFacetResult, Relewise.Client' &&
'priceSelectionStrategy' in item &&
item.priceSelectionStrategy === selectionStrategy,
) || null;
}

public static listPriceRanges(
facets: ProductFacetResult,
selectionStrategy: PriceSelectionStrategy,
): PriceRangesFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is PriceRangesFacetResult =>
item.field === 'ListPrice' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.PriceRangesFacetResult, Relewise.Client' &&
'priceSelectionStrategy' in item &&
item.priceSelectionStrategy === selectionStrategy,
) || null;
}

public static listPriceRangesWithRange(
facets: ProductFacetResult,
selectionStrategy: PriceSelectionStrategy,
expandedRangeSize: number | null,
): PriceRangesFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is PriceRangesFacetResult =>
item.field === 'ListPrice' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.PriceRangesFacetResult, Relewise.Client' &&
'priceSelectionStrategy' in item &&
item.priceSelectionStrategy === selectionStrategy &&
'expandedRangeSize' in item &&
item.expandedRangeSize === expandedRangeSize,
) || null;
}

public static salesPriceRanges(
facets: ProductFacetResult,
selectionStrategy: PriceSelectionStrategy,
): PriceRangesFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is PriceRangesFacetResult =>
item.field === 'SalesPrice' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.PriceRangesFacetResult, Relewise.Client' &&
'priceSelectionStrategy' in item &&
item.priceSelectionStrategy === selectionStrategy,
) || null;
}

public static salesPriceRangesWithRange(
facets: ProductFacetResult,
selectionStrategy: PriceSelectionStrategy,
expandedRangeSize: number | null,
): PriceRangesFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is PriceRangesFacetResult =>
item.field === 'SalesPrice' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.PriceRangesFacetResult, Relewise.Client' &&
'priceSelectionStrategy' in item &&
item.priceSelectionStrategy === selectionStrategy &&
'expandedRangeSize' in item &&
item.expandedRangeSize === expandedRangeSize,
) || null;
}

public static dataDoubleRange(
facets: ProductFacetResult,
selectionStrategy: DataSelectionStrategy,
key: string,
): ProductDataDoubleRangeFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is ProductDataDoubleRangeFacetResult =>
item.field === 'Data' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.ProductDataDoubleRangeFacetResult, Relewise.Client' &&
'dataSelectionStrategy' in item &&
item.dataSelectionStrategy === selectionStrategy &&
'key' in item &&
item.key === key,
) || null;
}

public static dataDoubleRanges(
facets: ProductFacetResult,
selectionStrategy: DataSelectionStrategy,
key: string,
): ProductDataDoubleRangesFacetResult | null {
if (!facets?.items) return null;

return facets.items.find((item): item is ProductDataDoubleRangesFacetResult =>
item.field === 'Data' &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.ProductDataDoubleRangesFacetResult, Relewise.Client' &&
'dataSelectionStrategy' in item &&
item.dataSelectionStrategy === selectionStrategy &&
'key' in item &&
item.key === key,
) || null;
}

public static variantSpecification(facets: ProductFacetResult, key: string): VariantSpecificationFacetResult | null {
if (!facets?.items) return null;

return facets
.items
.find((item): item is VariantSpecificationFacetResult =>
'$type' in item &&
item.$type === 'Relewise.Client.DataTypes.Search.items.Result.VariantSpecificationFacetResult, Relewise.Client' &&
'field' in item &&
item.field === 'VariantSpecification' &&
'key' in item && item.key === key) || null;
}

public static dataString(
facets: ProductFacetResult,
key: string,
selectionStrategy: DataSelectionStrategy,
): ProductDataStringValueFacetResult | null {
return this.data<ProductDataStringValueFacetResult>(facets, 'Relewise.Client.DataTypes.Search.Facets.Result.ProductDataStringValueFacetResult, Relewise.Client', selectionStrategy, key);
}

public static dataBoolean(
facets: ProductFacetResult,
key: string,
selectionStrategy: DataSelectionStrategy,
): ProductDataBooleanValueFacetResult | null {
return this.data<ProductDataBooleanValueFacetResult>(facets, 'Relewise.Client.DataTypes.Search.Facets.Result.ProductDataBooleanValueFacetResult, Relewise.Client', selectionStrategy, key);
}

public static dataNumber(
facets: ProductFacetResult,
key: string,
selectionStrategy: DataSelectionStrategy,
): ProductDataDoubleValueFacetResult | null {
return this.data<ProductDataDoubleValueFacetResult>(facets, 'Relewise.Client.DataTypes.Search.Facets.Result.ProductDataDoubleValueFacetResult, Relewise.Client', selectionStrategy, key);
}

public static dataObject(
facets: ProductFacetResult,
selectionStrategy: DataSelectionStrategy,
key: string,
): ProductDataObjectFacetResult | null {
if (!facets?.items) return null;

return (facets.items.find((a): a is ProductDataObjectFacetResult =>
a.$type === 'Relewise.Client.DataTypes.Search.Facets.Result.ProductDataObjectFacetResult, Relewise.Client' &&
a.field === 'Data' &&
(a as ProductDataObjectFacetResult).key === key &&
(a as ProductDataObjectFacetResult).dataSelectionStrategy === selectionStrategy,
) || null) as ProductDataObjectFacetResult;
}

private static data<TFacetResult>(
facets: ProductFacetResult,
$type: string,
selectionStrategy: DataSelectionStrategy,
key: string,
): TFacetResult | null {
if (!facets?.items) return null;

return (facets.items.find((a: any) =>
a.$type === $type &&
a.field === 'Data' &&
'dataSelectionStrategy' in a && a.dataSelectionStrategy === selectionStrategy &&
'key' in a && a.key === key,
) || null) as TFacetResult | null;
}
};
3 changes: 2 additions & 1 deletion packages/client/src/builders/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export * from './productCategorySearchBuilder';
export * from './productSearchBuilder';
export * from './searchCollectionBuilder';
export * from './searchTermPredictionBuilder';
export * from './dataObjectValueSelectorBuilder'
export * from './dataObjectValueSelectorBuilder';
export * from './getProductFacet';
3 changes: 2 additions & 1 deletion packages/client/src/factory/dataValue.factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { StringDataValue, StringCollectionDataValue, NumberDataValue, DoubleCollectionDataValue, BooleanDataValue, BooleanCollectionDataValue, MultiCurrencyDataValue, MultilingualDataValue, ObjectDataValue, ObjectCollectionDataValue, DataValue, MultilingualCollectionDataValue } from '..';
import { DataValue } from '../models/data-contracts';
import { StringDataValue, StringCollectionDataValue, NumberDataValue, DoubleCollectionDataValue, BooleanDataValue, BooleanCollectionDataValue, MultiCurrencyDataValue, MultilingualDataValue, MultilingualCollectionDataValue, ObjectDataValue, ObjectCollectionDataValue } from '../models/dataValue';

export class DataValueFactory {
static string(value: string): StringDataValue {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Searcher, ProductSearchBuilder, ProductSearchRequest, UserFactory, ValueSelectorFactory, DataValueFactory } from '../../src';
import { Searcher, ProductSearchBuilder, ProductSearchRequest, UserFactory, ValueSelectorFactory, DataValueFactory, GetProductFacet, ProductAssortmentFacet, ProductDataStringValueFacetResult, CategoryFacetResult } from '../../src';
import { test, expect } from '@jest/globals'

const { npm_config_API_KEY: API_KEY, npm_config_DATASET_ID: DATASET_ID, npm_config_SERVER_URL: SERVER_URL } = process.env;
Expand Down Expand Up @@ -58,5 +58,28 @@ test('Retail Media search', async() => {

const result = await searcher.searchProducts(request);

expect(result?.hits).toBeGreaterThan(0);
});

test('Facet result', async() => {
const request: ProductSearchRequest = baseProductBuilder()
.facets(f => f.addProductAssortmentFacet('Product'))
.facets(f => f.addProductDataStringValueFacet('AnyString', 'Product'))
.facets(f => f.addCategoryFacet('ImmediateParent'))
.build();

const result = await searcher.searchProducts(request);

if (result && result.facets) {
const facet: ProductAssortmentFacet | null = GetProductFacet.productAssortment(result.facets, 'Product');
expect(facet).toBeDefined();

const facet2: ProductDataStringValueFacetResult | null = GetProductFacet.dataString(result.facets, 'AnyString', 'Product');
expect(facet2).toBeDefined();

const facet3: CategoryFacetResult | null = GetProductFacet.category(result.facets, 'ImmediateParent');
expect(facet3).toBeDefined();
}

expect(result?.hits).toBeGreaterThan(0);
});

0 comments on commit 3cbde3d

Please sign in to comment.