Skip to content

Commit

Permalink
Feat: add search highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
SWH-Relewise committed Dec 4, 2024
1 parent 5ea6dea commit 3562080
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ For more information about how to use the SDK via CDN - go to our [docs site](ht

## Running integration tests

You can read about running the integration tests [here](/lib/dev.guide.md#testing).
You can read about running the integration tests [here](/packages/client/dev.guide.md#testing).

## Contributing

Expand Down
50 changes: 50 additions & 0 deletions packages/client/src/builders/search/contentHighlightingBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ContentHighlightProps, ContentSearchSettingsHighlightSettings, HighlightSettings2ContentContentHighlightPropsHighlightSettings2Limits, HighlightSettings2ContentContentHighlightPropsHighlightSettings2ResponseShape } from "src/models/data-contracts";

export class ContentHighlightingBuilder {
private enabled: boolean = true;
private highlightable: ContentHighlightProps = {
$type: 'Relewise.Client.Requests.Shared.Highlighting.ContentHighlightProps, Relewise.Client',
displayName: false
};
private limit: HighlightSettings2ContentContentHighlightPropsHighlightSettings2Limits = {};
private shape: HighlightSettings2ContentContentHighlightPropsHighlightSettings2ResponseShape = {
includeOffsets: false
};

public enable(enabled: boolean): this {
this.enabled = enabled;

return this;
}

public setHighlightable(highlightable: { displayName?: boolean, dataKeys?: string[] | null }): this {
this.highlightable.displayName = highlightable.displayName ?? false;
this.highlightable.dataKeys = highlightable.dataKeys;

return this;
}

public setLimit(limit: { maxEntryLimit?: number | null; maxSnippetsPerEntry?: number | null; maxSnippetsPerField?: number | null; }): this {
this.limit.maxEntryLimit = limit.maxEntryLimit;
this.limit.maxSnippetsPerEntry = limit.maxSnippetsPerEntry;
this.limit.maxSnippetsPerField = limit.maxSnippetsPerField;

return this;
}

public setShape(shape: { includeOffsets: boolean }): this {
this.shape.includeOffsets = shape.includeOffsets;

return this;
}

public build(): ContentSearchSettingsHighlightSettings {
return {
$type: 'Relewise.Client.Requests.Search.Settings.ContentSearchSettings+HighlightSettings, Relewise.Client',
enabled: this.enabled,
highlightable: this.highlightable,
limit: this.limit,
shape: this.shape
} as ContentSearchSettingsHighlightSettings;
}
}
10 changes: 10 additions & 0 deletions packages/client/src/builders/search/contentSearchBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ContentSearchRequest, ContentSearchSettings, RecommendationSettings, SelectedContentPropertiesSettings } from '../../models/data-contracts';
import { PaginationBuilder } from '../paginationBuilder';
import { Settings } from '../settings';
import { ContentHighlightingBuilder } from './contentHighlightingBuilder';
import { ContentSortingBuilder } from './contentSortingBuilder';
import { FacetBuilder } from './facetBuilder';
import { SearchBuilder } from './searchBuilder';
Expand All @@ -11,6 +12,7 @@ export class ContentSearchBuilder extends SearchRequestBuilder implements Search
private paginationBuilder: PaginationBuilder = new PaginationBuilder();
private sortingBuilder: ContentSortingBuilder = new ContentSortingBuilder();
private term: string | null | undefined;
private highlightingBuilder = new ContentHighlightingBuilder();

private searchSettings: ContentSearchSettings = {
$type: 'Relewise.Client.Requests.Search.Settings.ContentSearchSettings, Relewise.Client',
Expand Down Expand Up @@ -57,6 +59,14 @@ export class ContentSearchBuilder extends SearchRequestBuilder implements Search
return this;
}

public highlighting(highlightingBuilder: (highlightingBuilder: ContentHighlightingBuilder) => void): this {
highlightingBuilder(this.highlightingBuilder);

this.searchSettings.highlight = this.highlightingBuilder.build();

return this;
}

public build(): ContentSearchRequest {
const { take, skip } = this.paginationBuilder.build();
return {
Expand Down
50 changes: 50 additions & 0 deletions packages/client/src/builders/search/productHighlightingBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { HighlightSettings2ProductProductHighlightPropsHighlightSettings2Limits, HighlightSettings2ProductProductHighlightPropsHighlightSettings2ResponseShape, ProductHighlightProps, ProductSearchSettingsHighlightSettings } from "src/models/data-contracts";

export class ProductHighlightingBuilder {
private enabled: boolean = true;
private highlightable: ProductHighlightProps = {
$type: 'Relewise.Client.Requests.Shared.Highlighting.ProductHighlightProps, Relewise.Client',
displayName: false
};
private limit: HighlightSettings2ProductProductHighlightPropsHighlightSettings2Limits = {};
private shape: HighlightSettings2ProductProductHighlightPropsHighlightSettings2ResponseShape = {
includeOffsets: false
};

public enable(enabled: boolean): this {
this.enabled = enabled;

return this;
}

public setHighlightable(highlightable: { displayName?: boolean, dataKeys?: string[] | null }): this {
this.highlightable.displayName = highlightable.displayName ?? false;
this.highlightable.dataKeys = highlightable.dataKeys;

return this;
}

public setLimit(limit: { maxEntryLimit?: number | null; maxSnippetsPerEntry?: number | null; maxSnippetsPerField?: number | null; }): this {
this.limit.maxEntryLimit = limit.maxEntryLimit;
this.limit.maxSnippetsPerEntry = limit.maxSnippetsPerEntry;
this.limit.maxSnippetsPerField = limit.maxSnippetsPerField;

return this;
}

public setShape(shape: { includeOffsets: boolean }): this {
this.shape.includeOffsets = shape.includeOffsets;

return this;
}

public build(): ProductSearchSettingsHighlightSettings {
return {
$type: 'Relewise.Client.Requests.Search.Settings.ProductSearchSettings+HighlightSettings, Relewise.Client',
enabled: this.enabled,
highlightable: this.highlightable,
limit: this.limit,
shape: this.shape
} as ProductSearchSettingsHighlightSettings;
}
}
12 changes: 10 additions & 2 deletions packages/client/src/builders/search/productSearchBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ProductSearchRequest, ProductSearchSettings, RecommendationSettings, Re
import { PaginationBuilder } from '../paginationBuilder';
import { Settings } from '../settings';
import { FacetBuilder } from './facetBuilder';
import { ProductHighlightingBuilder } from './productHighlightingBuilder';
import { ProductSortingBuilder } from './productSortingBuilder';
import { SearchBuilder } from './searchBuilder';
import { SearchConstraintBuilder } from './searchConstraintBuilder';
Expand All @@ -14,6 +15,7 @@ export class ProductSearchBuilder extends SearchRequestBuilder implements Search
private sortingBuilder: ProductSortingBuilder = new ProductSortingBuilder();
private searchConstraintBuilder: SearchConstraintBuilder = new SearchConstraintBuilder();
private term: string | null | undefined;
private highlightingBuilder = new ProductHighlightingBuilder();

private searchSettings: ProductSearchSettings = {
$type: 'Relewise.Client.Requests.Search.Settings.ProductSearchSettings, Relewise.Client',
Expand Down Expand Up @@ -114,16 +116,22 @@ export class ProductSearchBuilder extends SearchRequestBuilder implements Search
return this;
}

public highlighting(highlightingBuilder: (highlightingBuilder: ProductHighlightingBuilder) => void): this {
highlightingBuilder(this.highlightingBuilder);

this.searchSettings.highlight = this.highlightingBuilder.build();

return this;
}

public build(): ProductSearchRequest {
const { take, skip } = this.paginationBuilder.build();
return {
$type: 'Relewise.Client.Requests.Search.ProductSearchRequest, Relewise.Client',
...this.baseBuild(),
take,
skip,

term: this.term,

facets: this.facetBuilder.build(),
settings: this.searchSettings,
sorting: this.sortingBuilder.build(),
Expand Down
87 changes: 87 additions & 0 deletions packages/client/src/models/data-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,14 @@ export type ContentCategoryView = Trackable & {
channel?: Channel | null;
};

export interface ContentContentHighlightPropsHighlightSettings {
$type: string;
enabled: boolean;
limit: HighlightSettings2ContentContentHighlightPropsHighlightSettings2Limits;
highlightable: ContentHighlightProps;
shape: HighlightSettings2ContentContentHighlightPropsHighlightSettings2ResponseShape;
}

export type ContentDataBooleanValueFacet = BooleanContentDataValueFacet;

export type ContentDataBooleanValueFacetResult = BooleanContentDataValueFacetResult;
Expand Down Expand Up @@ -1265,6 +1273,14 @@ export interface ContentFacetResult {

export type ContentHasCategoriesFilter = Filter;

export interface ContentHighlightProperties {
$type: string;
displayName: boolean;
dataKeys?: string[] | null;
}

export type ContentHighlightProps = ContentHighlightProperties;

export type ContentIdFilter = Filter & {
contentIds: string[];
};
Expand Down Expand Up @@ -1359,6 +1375,7 @@ export interface ContentResult {
categoryPaths?: CategoryPathResult[] | null;
viewedByUser?: ViewedByUserInfo | null;
custom?: Record<string, string | null>;
highlight?: HighlightResult | null;
}

export interface ContentResultDetails {
Expand Down Expand Up @@ -1397,8 +1414,11 @@ export type ContentSearchResponse = PaginatedSearchResponse & {
export type ContentSearchSettings = SearchSettings & {
selectedContentProperties?: SelectedContentPropertiesSettings | null;
recommendations: RecommendationSettings;
highlight?: ContentSearchSettingsHighlightSettings | null;
};

export type ContentSearchSettingsHighlightSettings = ContentContentHighlightPropsHighlightSettings;

export interface ContentSortBySpecification {
value?: ContentAttributeSorting | ContentDataSorting | ContentPopularitySorting | ContentRelevanceSorting | null;
}
Expand Down Expand Up @@ -2323,6 +2343,41 @@ export type HasRecentlyReceivedTriggerCondition = UserCondition & {

export type HasValueCondition = ValueCondition;

export interface HighlightResult {
offsets?: HighlightResultOffset | null;
}

export interface HighlightResultOffset {
displayName: Int32Range[];
data: StringRange1ArrayKeyValuePair[];
}

export interface HighlightSettings2ContentContentHighlightPropsHighlightSettings2Limits {
/** @format int32 */
maxEntryLimit?: number | null;
/** @format int32 */
maxSnippetsPerEntry?: number | null;
/** @format int32 */
maxSnippetsPerField?: number | null;
}

export interface HighlightSettings2ContentContentHighlightPropsHighlightSettings2ResponseShape {
includeOffsets: boolean;
}

export interface HighlightSettings2ProductProductHighlightPropsHighlightSettings2Limits {
/** @format int32 */
maxEntryLimit?: number | null;
/** @format int32 */
maxSnippetsPerEntry?: number | null;
/** @format int32 */
maxSnippetsPerField?: number | null;
}

export interface HighlightSettings2ProductProductHighlightPropsHighlightSettings2ResponseShape {
includeOffsets: boolean;
}

export type HtmlParser = Parser;

export type IChange = object;
Expand Down Expand Up @@ -2398,6 +2453,13 @@ export interface Int32ProductDataValueFacetResult {
field: "Category" | "Assortment" | "ListPrice" | "SalesPrice" | "Brand" | "Data" | "VariantSpecification" | "User";
}

export interface Int32Range {
/** @format int32 */
lowerBoundInclusive: number;
/** @format int32 */
upperBoundInclusive: number;
}

export interface KeyMultiplier {
key?: string | null;
/** @format double */
Expand Down Expand Up @@ -3652,6 +3714,14 @@ export type ProductHasVariantsFilter = Filter & {
numberOfVariants: Int32NullableRange;
};

export interface ProductHighlightProperties {
$type: string;
displayName: boolean;
dataKeys?: string[] | null;
}

export type ProductHighlightProps = ProductHighlightProperties;

export type ProductIdFilter = Filter & {
productIds: string[];
};
Expand Down Expand Up @@ -3820,6 +3890,14 @@ export interface ProductPerformanceResultViewsMetrics {

export type ProductPopularitySorting = ProductSorting;

export interface ProductProductHighlightPropsHighlightSettings {
$type: string;
enabled: boolean;
limit: HighlightSettings2ProductProductHighlightPropsHighlightSettings2Limits;
highlightable: ProductHighlightProps;
shape: HighlightSettings2ProductProductHighlightPropsHighlightSettings2ResponseShape;
}

export type ProductPromotion = Promotion & {
filters?: FilterCollection | null;
};
Expand Down Expand Up @@ -4070,6 +4148,7 @@ export interface ProductResult {
purchasedByUserCompany?: PurchasedByUserCompanyInfo | null;
viewedByUserCompany?: ViewedByUserCompanyInfo | null;
filteredVariants?: VariantResult[] | null;
highlight?: HighlightResult | null;
}

export interface ProductResultDetails {
Expand Down Expand Up @@ -4149,8 +4228,11 @@ export type ProductSearchSettings = SearchSettings & {
selectedBrandProperties?: SelectedBrandPropertiesSettings | null;
variantSettings?: VariantSearchSettings | null;
resultConstraint?: ResultMustHaveVariantConstraint | null;
highlight?: ProductSearchSettingsHighlightSettings | null;
};

export type ProductSearchSettingsHighlightSettings = ProductProductHighlightPropsHighlightSettings;

export interface ProductSortBySpecification {
value?:
| ProductAttributeSorting
Expand Down Expand Up @@ -5206,6 +5288,11 @@ export interface StringProductDataValueFacetResult {
field: "Category" | "Assortment" | "ListPrice" | "SalesPrice" | "Brand" | "Data" | "VariantSpecification" | "User";
}

export interface StringRange1ArrayKeyValuePair {
key: string;
value: Int32Range[];
}

export interface StringStringKeyValuePair {
key: string;
value: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,19 @@ test('Facet result', async() => {
}

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

test('Highlighting', async() => {
const request: ContentSearchRequest = baseContentBuilder()
.setTerm('highlighted')
.highlighting(h => {
h.setHighlightable({ dataKeys: ['Description'] })
// You have to specify to include the offset.
// Currently offset is the only way to get a result, so if not set, you won't get a result.
h.setShape({ includeOffsets: true })
}).build();

const result = await searcher.searchContents(request);

expect(result?.results![0].highlight?.offsets?.data[0].value.length).toBeGreaterThan(0);
})
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,18 @@ test('ProductSearch with search constraint', async() => {

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

test('Highlighting', async() => {
const request: ProductSearchRequest = baseProductBuilder()
.setTerm('highlighted')
.highlighting(h => {
h.setHighlightable({ dataKeys: ['Description'] })
// You have to specify to include the offset.
// Currently offset is the only way to get a result, so if not set, you won't get a result.
h.setShape({ includeOffsets: true })
}).build();

const result = await searcher.searchProducts(request);

expect(result?.results![0].highlight?.offsets?.data[0].value.length).toBeGreaterThan(0);
})
Loading

0 comments on commit 3562080

Please sign in to comment.