Skip to content

Commit bfc58d8

Browse files
edit (content): add script refs for custom js
1 parent 14d89bd commit bfc58d8

1 file changed

Lines changed: 309 additions & 0 deletions

File tree

content/docs/pipelines/how-to/ecommerce-search.md

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,240 @@ This guide shows how to implement an e-commerce search pipeline that:
9595
]
9696
```
9797

98+
Script: generateRequest.js
99+
100+
```js
101+
function handleRequest() {
102+
const queryTerm = context.envs.query;
103+
104+
/* verifying that format of query is as expected before modifying */
105+
const requestBody = JSON.parse(context.request.body);
106+
107+
if (!requestBody.query || requestBody.query.length == 0) {
108+
/* Update the queries with the defaultQuery */
109+
requestBody.query.forEach((queryEach, queryIndex) => {
110+
console.log("query id each: ", queryEach["id"]);
111+
if (
112+
queryEach.type == "suggestion" ||
113+
queryEach.type == "search" ||
114+
queryEach.type == undefined ||
115+
queryEach.type == "term"
116+
) {
117+
requestBody.query[queryIndex] = injectDefaultQuery(queryEach);
118+
}
119+
});
120+
121+
return {
122+
request: { body: JSON.stringify(requestBody) }
123+
};
124+
}
125+
126+
/* If there is only one query and it is a suggestion type of
127+
* query, we need to inject a `result` id query where there will
128+
* be the defaultQuery and the current `search` query will react to
129+
* it.
130+
**/
131+
if (
132+
requestBody.query.length == 1 &&
133+
requestBody.query[0].type == "suggestion" &&
134+
requestBody.query[0].id == "search"
135+
) {
136+
requestBody.query[0].id = "result";
137+
requestBody.query[0].execute = false;
138+
let resultQuery = {
139+
...requestBody.query[0],
140+
id: "search",
141+
react: { and: ["result"] },
142+
execute: true
143+
};
144+
requestBody.query.push(resultQuery);
145+
}
146+
147+
/* Iterate through and if there is a suggestion or search query, update it
148+
* with the extra fields.
149+
**/
150+
console.log("query length: ", requestBody.query.length);
151+
requestBody.query.forEach((queryEach, queryIndex) => {
152+
console.log("query id: ", queryEach["id"]);
153+
154+
/* If there is a term query of id result, change it to `search` type */
155+
if (queryEach.id == "result" && queryEach.type == "term") {
156+
queryEach.type = "search";
157+
}
158+
159+
if (queryEach.type == "suggestion") {
160+
console.log("query id: ", queryEach["id"]);
161+
requestBody.query[queryIndex] = updateSuggestionsQuery(queryTerm, queryEach);
162+
} else if (queryEach.type == "search") {
163+
requestBody.query[queryIndex] = updateSearchQuery(queryTerm, queryEach);
164+
console.log(JSON.stringify(requestBody.query[queryIndex]));
165+
}
166+
167+
/* Inject the defaultQuery
168+
*
169+
* If there is only one query, we don't want defaultQuery to
170+
* be injected since that will override the main query.
171+
*/
172+
console.log(queryEach.id, ": ", queryEach.type);
173+
if (
174+
requestBody.query.length > 1 &&
175+
(queryEach.type == "suggestion" ||
176+
queryEach.type == "search" ||
177+
queryEach.type == undefined ||
178+
queryEach.type == "term")
179+
) {
180+
requestBody.query[queryIndex] = injectDefaultQuery(queryEach);
181+
}
182+
});
183+
184+
return {
185+
request: { body: JSON.stringify(requestBody) }
186+
};
187+
}
188+
189+
function updateSuggestionsQuery(queryTerm, suggestionQuery) {
190+
if (!queryTerm) queryTerm = "";
191+
const queryLength = queryTerm.length;
192+
193+
const dataFields = [
194+
{ field: "longDescription", weight: 0.5 },
195+
{ field: "name", weight: 3 },
196+
{ field: "name.autosuggest",weight: 0.1 },
197+
{ field: "name.search", weight: 0.01},
198+
{ field: "class", weight: 5 },
199+
{ field: "class.search", weight: 1 }
200+
];
201+
202+
suggestionQuery["dataField"] = dataFields;
203+
suggestionQuery["queryFormat"] = "and";
204+
suggestionQuery["includeFields"] = [
205+
"class", "globalRank", "categoryRank", "name",
206+
"shortDescription", "longDescription", "salePrice", "image"
207+
];
208+
suggestionQuery["distinctField"] = "class.keyword";
209+
suggestionQuery["distinctFieldConfig"] = {
210+
inner_hits: {
211+
name: "rel",
212+
size: 2,
213+
_source: [
214+
"class", "globalRank", "categoryRank", "name",
215+
"shortDescription", "longDescription", "salePrice", "image"
216+
]
217+
}
218+
};
219+
220+
console.log("query id: ", suggestionQuery["id"]);
221+
222+
/* Responding to the user intent based on the query length */
223+
if (queryLength <= 4) {
224+
// user is perhaps checking out the search
225+
suggestionQuery.enablePopularSuggestions = true;
226+
suggestionQuery.enableFeaturedSuggestions = true;
227+
suggestionQuery.enableRecentSuggestions = true;
228+
} else if (queryLength >= 5 && queryLength <= 9) {
229+
// user is looking for something specific
230+
suggestionQuery.enablePopularSuggestions = false;
231+
suggestionQuery.enableFeaturedSuggestions = false;
232+
suggestionQuery.enableRecentSuggestions = true;
233+
} else if (queryLength >= 10) {
234+
// user is definitely looking for something specific
235+
suggestionQuery.enableRecentSuggestions = false;
236+
suggestionQuery.enablePopularSuggestions = false;
237+
suggestionQuery.enableFeaturedSuggestions = false;
238+
}
239+
240+
return suggestionQuery;
241+
}
242+
243+
function updateSearchQuery(queryTerm, searchQuery) {
244+
const dataFields = [
245+
{ field: "longDescription", weight: 1 },
246+
{ field: "name", weight: 3 },
247+
{ field: "name.autosuggest",weight: 0.2 },
248+
{ field: "name.delimiter", weight: 1 },
249+
{ field: "name.search", weight: 0.1 },
250+
{ field: "class", weight: 5 },
251+
{ field: "class.search", weight: 1 }
252+
];
253+
254+
searchQuery["dataField"] = dataFields;
255+
searchQuery["queryFormat"] = "and";
256+
searchQuery["includeFields"] = [
257+
"class", "globalRank", "categoryRank", "name",
258+
"shortDescription", "longDescription", "salePrice", "image"
259+
];
260+
searchQuery["distinctField"] = "class.keyword";
261+
searchQuery["distinctFieldConfig"] = {
262+
inner_hits: {
263+
name: "rel",
264+
size: 2,
265+
_source: [
266+
"class", "globalRank", "categoryRank", "name",
267+
"shortDescription", "longDescription", "salePrice", "image"
268+
]
269+
}
270+
};
271+
272+
/*searchQuery['sortField'] = ['globalRank', 'categoryRank'];
273+
searchQuery['sortBy'] = 'asc'; */
274+
275+
console.log("query is: ", searchQuery["id"]);
276+
console.log("datafields are: ", JSON.stringify(searchQuery["dataField"]));
277+
278+
if (!queryTerm) return searchQuery;
279+
280+
const queryLength = queryTerm.length;
281+
if (!queryTerm || queryLength === 0) return searchQuery;
282+
283+
if (
284+
queryLength >= 20 &&
285+
queryTerm.split(" ").length >= 4 &&
286+
queryTerm[queryTerm.length - 1] == "?"
287+
) {
288+
searchQuery["enableAI"] = true;
289+
searchQuery["execute"] = true;
290+
}
291+
292+
return searchQuery;
293+
}
294+
295+
function injectDefaultQuery(query) {
296+
/* Use exists query to boost score of results that contain
297+
either globalRank or categoryRank */
298+
console.log("existing default query: ", JSON.stringify(query.defaultQuery));
299+
if (
300+
(!query.defaultQuery || JSON.stringify(query.defaultQuery) == "{}") &&
301+
query.execute
302+
) {
303+
query.defaultQuery = {
304+
query: {
305+
bool: {
306+
must_not: [
307+
{ match: { class: "ACCY" } },
308+
{ match: { class: "APPLECARE" } },
309+
{ match: { class: "ACCESSORIES" } },
310+
{ match: { class: "ACCESS." } },
311+
{ match: { class: "IPAD MONTHLY"} },
312+
{ match: { class: "SVC FEE" } }
313+
],
314+
should: [
315+
{ range: { salePrice: { gte: 499, boost: 5 } } },
316+
{ range: { salePrice: { gte: 299, boost: 5 } } },
317+
{ range: { salePrice: { gte: 99, boost: 4 } } },
318+
{ range: { categoryRank:{ lte: 10, boost: 2 } } },
319+
{ range: { categoryRank:{ lte: 5, boost: 2 } } },
320+
{ range: { categoryRank:{ lte: 1, boost: 1 } } },
321+
{ range: { globalRank: { lte: 10, boost: 5 } } },
322+
{ range: { salePrice: { gte: 50, boost: 0.01 } } }
323+
]
324+
}
325+
}
326+
};
327+
}
328+
return query;
329+
}
330+
```
331+
98332
### 3) First pass: RS → ES
99333

100334
```json
@@ -127,6 +361,81 @@ This guide shows how to implement an e-commerce search pipeline that:
127361
]
128362
```
129363

364+
Script: checkTypo.js
365+
366+
```js
367+
function handleRequest() {
368+
const queryTerm = context.envs.query;
369+
370+
if (queryTerm && queryTerm.length < 5) {
371+
return {};
372+
}
373+
374+
const globalRankWeight = 0.2;
375+
const categoryRankWeight = 0.5;
376+
377+
const responseBody = JSON.parse(context.response.body);
378+
if (
379+
responseBody.search?.hits?.hits?.length != 0 &&
380+
responseBody.result?.hits?.hits?.length != 0
381+
) {
382+
/* We have hits: re-score using rank signals */
383+
const results = responseBody.result?.hits?.hits;
384+
if (results) {
385+
results.forEach((hitEach, hitIndex) => {
386+
if (!hitEach._source.categoryRank && !hitEach._source.globalRank) {
387+
hitEach._source.categoryRank = 10;
388+
}
389+
390+
let isGlobalRank = false;
391+
392+
if (hitEach._source.globalRank) {
393+
const ogScore = hitEach._score;
394+
results[hitIndex]._score =
395+
ogScore - globalRankWeight * hitEach._source.globalRank;
396+
isGlobalRank = true;
397+
}
398+
399+
if (hitEach._source.categoryRank && !isGlobalRank) {
400+
const ogScore = hitEach._score;
401+
results[hitIndex]._score =
402+
ogScore - categoryRankWeight * hitEach._source.categoryRank;
403+
}
404+
});
405+
406+
/* Sort descending by updated score */
407+
results.sort((a, b) => b._score - a._score);
408+
responseBody.result.hits.hits = results;
409+
410+
return {
411+
response: { body: JSON.stringify(responseBody) }
412+
};
413+
}
414+
return {};
415+
}
416+
417+
/* No hits: enable fuzzy fallback and trigger research pass */
418+
const rsBody = JSON.parse(context.envs["ORIGINAL_RS_BODY"]);
419+
420+
let fuzziness = 1;
421+
if (queryTerm.length >= 5 && queryTerm.length <= 9) {
422+
fuzziness = 1;
423+
} else {
424+
fuzziness = 2;
425+
}
426+
427+
rsBody.query?.forEach((queryEach, queryIndex) => {
428+
queryEach.fuzziness = fuzziness;
429+
rsBody.query[queryIndex] = queryEach;
430+
});
431+
432+
return {
433+
request: { body: JSON.stringify(rsBody) },
434+
envs: { research: true, ...context.envs }
435+
};
436+
}
437+
```
438+
130439
### 5) Second pass (conditional)
131440
132441
```json

0 commit comments

Comments
 (0)