Skip to content

Commit

Permalink
feat: add promotionPinned field to search results (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkaho authored Dec 5, 2021
1 parent 1368767 commit 2d4cca5
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sajari/sdk-js",
"description": "Sajari JavaScript SDK",
"version": "2.5.0",
"version": "2.6.0",
"main": "dist/index.js",
"umd:main": "dist/sajarisdk.umd.production.js",
"module": "dist/sajarisdk.esm.production.js",
Expand Down
65 changes: 65 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,20 @@ class QueryPipeline extends EventEmitter {
return obj;
}, {});

const activePins: Record<string, Set<string>> = {};
if (jsonProto.activePromotions) {
jsonProto.activePromotions.forEach((promotion) => {
if (promotion.activePins) {
promotion.activePins.forEach(({ key }) => {
if (!activePins[key.field]) {
activePins[key.field] = new Set<string>();
}
activePins[key.field].add(key.value);
});
}
});
}

const results: Result[] = (jsonProto.searchResponse?.results || []).map(
({ indexScore, score, values }, index) => {
let t: Token | undefined = undefined;
Expand All @@ -436,11 +450,26 @@ class QueryPipeline extends EventEmitter {
}
}

let promotionPinned = false;
if (Object.keys(activePins).length > 0) {
Object.entries(activePins).forEach(
([pinKeyFieldName, pinKeyFieldValues]) => {
const fieldValue = valueFromProto(
values[pinKeyFieldName]
) as string;
if (pinKeyFieldValues.has(fieldValue)) {
promotionPinned = true;
}
}
);
}

return {
indexScore,
score,
values: processProtoValues(values),
token: t,
promotionPinned,
};
}
);
Expand Down Expand Up @@ -523,6 +552,10 @@ export interface Result {
* token is the [[Token]] associated with this [[Result]] (if any).
*/
token?: Token;
/**
* promotionPinned indicates whether this [[Result]] is pinned via an [[ActivePromotion]].
*/
promotionPinned?: boolean;
}

export type Token = ClickToken | PosNegToken;
Expand Down Expand Up @@ -570,6 +603,37 @@ export interface Redirects {
[redirectQuery: string]: RedirectTarget;
}

/**
* PromotionPin defines a promotion pin.
*/
export interface PromotionPin {
key: {
field: string;
value: string;
};
position: number;
}

/**
* PromotionExclusion defines a promotion exclusion.
*/
export interface PromotionExclusion {
key: {
field: string;
value: string;
};
}

/**
* A Promotion adjusts search results via a set of rules configured by the client.
* All promotions which are triggered by the current search are returned.
*/
export interface ActivePromotion {
promotionId: string;
activePins: PromotionPin[];
activeExclusions: PromotionExclusion[];
}

/**
* @hidden
*/
Expand All @@ -584,6 +648,7 @@ export interface SearchResponseProto {
tokens?: TokenProto[];
values?: Record<string, string>;
redirects?: Redirects;
activePromotions?: ActivePromotion[];
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/user-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ if (scriptTag) {
* user agent of sdk
* @hidden
*/
export const USER_AGENT = ["sajari-sdk-js/2.5.0", suffix]
export const USER_AGENT = ["sajari-sdk-js/2.6.0", suffix]
.filter(Boolean)
.join(" ");
209 changes: 209 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,220 @@ describe("Pipeline", () => {
values: {
neuralTitleHash: "JmW0p9EzjBH...",
},
promotionPinned: false,
},
],
aggregates: {},
aggregateFilters: {},
redirects: {},
});
});

it("search with promotions", async () => {
const responseObj: SearchResponseProto = {
searchResponse: {
time: "0.003s",
totalResults: "2",
results: [
{
indexScore: 1,
score: 0.231125,
values: {
_id: { single: "876120cc-c9c6-95f2-bafb-58b8e2aa962f" },
},
},
{
indexScore: 1,
score: 0.12938,
values: {
_id: { single: "ac369f52-1d84-b28b-5d08-22d5ba9ddeac" },
},
},
],
},
activePromotions: [
{
promotionId: "1",
activePins: [
{
key: {
field: "_id",
value: "876120cc-c9c6-95f2-bafb-58b8e2aa962f",
},
position: 1,
},
],
activeExclusions: [],
},
],
};
fetchMock.mockResponseOnce(JSON.stringify(responseObj));

const session = new DefaultSession(TrackingType.None, "url");
const [response, values] = await client
.pipeline("test", "test")
.search({ q: "hello" }, session.next());

expect(values).toEqual({});
expect(response).toStrictEqual({
time: 0.003,
totalResults: 2,
results: [
{
indexScore: 1,
score: 0.231125,
token: undefined,
values: {
_id: "876120cc-c9c6-95f2-bafb-58b8e2aa962f",
},
promotionPinned: true,
},
{
indexScore: 1,
score: 0.12938,
token: undefined,
values: {
_id: "ac369f52-1d84-b28b-5d08-22d5ba9ddeac",
},
promotionPinned: false,
},
],
aggregates: {},
aggregateFilters: {},
redirects: {},
});

expect(fetchMock.mock.calls.length).toEqual(1);
expect(fetchMock.mock.calls[0][0]).toEqual(
"test.com/sajari.api.pipeline.v1.Query/Search"
);
});

it("search with promotions that have active pins with different key fields", async () => {
const responseObj: SearchResponseProto = {
searchResponse: {
time: "0.003s",
totalResults: "3",
results: [
{
indexScore: 1,
score: 0.231125,
values: {
_id: { single: "876120cc-c9c6-95f2-bafb-58b8e2aa962f" },
sku: { single: "sku-1" },
name: { single: "apple" },
},
},
{
indexScore: 1,
score: 0.12938,
values: {
_id: { single: "ac369f52-1d84-b28b-5d08-22d5ba9ddeac" },
sku: { single: "sku-2" },
name: { single: "orange" },
},
},
{
indexScore: 1,
score: 0.12341,
values: {
_id: { single: "bx874f21-9sj2-h12l-2e9d-j219dsjdk971" },
sku: { single: "sku-3" },
name: { single: "pear" },
},
},
],
},
activePromotions: [
{
promotionId: "1",
activePins: [
{
key: {
field: "_id",
value: "876120cc-c9c6-95f2-bafb-58b8e2aa962f",
},
position: 1,
},
{
key: {
field: "sku",
value: "sku-2",
},
position: 4,
},
],
activeExclusions: [],
},
{
promotionId: "2",
activePins: [
{
key: {
field: "name",
value: "pear",
},
position: 2,
},
],
activeExclusions: [],
},
],
};
fetchMock.mockResponseOnce(JSON.stringify(responseObj));

const session = new DefaultSession(TrackingType.None, "url");
const [response, values] = await client
.pipeline("test", "test")
.search({ q: "hello" }, session.next());

expect(values).toEqual({});
expect(response).toStrictEqual({
time: 0.003,
totalResults: 3,
results: [
{
indexScore: 1,
score: 0.231125,
token: undefined,
values: {
_id: "876120cc-c9c6-95f2-bafb-58b8e2aa962f",
sku: "sku-1",
name: "apple",
},
promotionPinned: true,
},
{
indexScore: 1,
score: 0.12938,
token: undefined,
values: {
_id: "ac369f52-1d84-b28b-5d08-22d5ba9ddeac",
sku: "sku-2",
name: "orange",
},
promotionPinned: true,
},
{
indexScore: 1,
score: 0.12341,
token: undefined,
values: {
_id: "bx874f21-9sj2-h12l-2e9d-j219dsjdk971",
sku: "sku-3",
name: "pear",
},
promotionPinned: true,
},
],
aggregates: {},
aggregateFilters: {},
redirects: {},
});

expect(fetchMock.mock.calls.length).toEqual(1);
expect(fetchMock.mock.calls[0][0]).toEqual(
"test.com/sajari.api.pipeline.v1.Query/Search"
);
});
});

0 comments on commit 2d4cca5

Please sign in to comment.