Skip to content

Commit 10fd390

Browse files
authored
Merge pull request #498 from vtex-apps/feature/banners
Route Shadow traffic from banners queries
2 parents 75de010 + c295777 commit 10fd390

File tree

9 files changed

+101
-47
lines changed

9 files changed

+101
-47
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
77

88
## [Unreleased]
99

10+
### Changed
11+
12+
- Start testing 10% of the fetchBanners traffic to intsch and compare the result against the VTEX /IO version.
13+
1014
## [1.82.0] - 2025-10-01
1115

1216
### Changed

node/clients/intelligent-search-api.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { InstanceOptions, IOContext } from '@vtex/api'
22
import { ExternalClient } from '@vtex/api'
33

44
import { parseState } from '../utils/searchState'
5-
import type { IIntelligentSearchClient } from './intsch/types'
5+
import type { FetchBannersArgs, IIntelligentSearchClient } from './intsch/types'
66

77
const isPathTraversal = (str: string) => str.indexOf('..') >= 0
88

@@ -18,9 +18,6 @@ interface AutocompleteSearchSuggestionsParams {
1818
query: string
1919
}
2020

21-
interface BannersArgs {
22-
query: string
23-
}
2421

2522
interface FacetsArgs {
2623
query?: string
@@ -103,13 +100,13 @@ export class IntelligentSearchApi
103100
})
104101
}
105102

106-
public async banners(params: BannersArgs, path: string) {
107-
if (isPathTraversal(path)) {
103+
public async fetchBanners(params: FetchBannersArgs) {
104+
if (isPathTraversal(params.path)) {
108105
throw new Error('Malformed URL')
109106
}
110107

111-
return this.http.get(`/banners/${path}`, {
112-
params: { ...params, query: params.query, locale: this.locale },
108+
return this.http.get(`/banners/${params.path}`, {
109+
params: { query: params.query, locale: this.locale },
113110
metric: 'banners',
114111
})
115112
}

node/clients/intsch/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
AutocompleteSuggestionsResponse,
66
CorrectionArgs,
77
CorrectionResponse,
8+
FetchBannersArgs,
9+
FetchBannersResponse,
810
IIntelligentSearchClient,
911
SearchSuggestionsArgs,
1012
SearchSuggestionsResponse,
@@ -58,4 +60,11 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient {
5860
metric: 'correction-new',
5961
})
6062
}
63+
64+
public fetchBanners(args: FetchBannersArgs): Promise<FetchBannersResponse> {
65+
return this.http.get(`/api/intelligent-search/v0/banners/${args.path}`, {
66+
params: { query: args.query, locale: this.locale },
67+
metric: 'banners-new',
68+
})
69+
}
6170
}

node/clients/intsch/types.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export type AutocompleteSuggestionsResponse = {
1818
}
1919

2020
export type TopSearchesResponse = {
21-
searches: {
21+
searches: {
2222
term: string
2323
count: number
2424
}[]
@@ -48,12 +48,29 @@ export type CorrectionResponse = {
4848
}
4949
}
5050

51+
export type FetchBannersArgs = {
52+
query: string
53+
path: string
54+
}
55+
56+
export type FetchBannersResponse = {
57+
banners: {
58+
id: string
59+
name: string
60+
area: string
61+
html: string
62+
}[]
63+
}
64+
5165
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
5266
export interface IIntelligentSearchClient {
5367
fetchAutocompleteSuggestions(
5468
args: AutocompleteSuggestionsArgs
5569
): Promise<AutocompleteSuggestionsResponse>
5670
fetchTopSearches(): Promise<TopSearchesResponse>
57-
fetchSearchSuggestions(args: SearchSuggestionsArgs): Promise<SearchSuggestionsResponse>
71+
fetchSearchSuggestions(
72+
args: SearchSuggestionsArgs
73+
): Promise<SearchSuggestionsResponse>
5874
fetchCorrection(args: CorrectionArgs): Promise<CorrectionResponse>
75+
fetchBanners(args: FetchBannersArgs): Promise<FetchBannersResponse>
5976
}

node/mocks/intsch.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
TopSearchesResponse,
55
SearchSuggestionsResponse,
66
CorrectionResponse,
7+
FetchBannersResponse,
78
} from '../clients/intsch/types'
89

910
export class MockedIntschClient implements IIntelligentSearchClient {
@@ -37,17 +38,25 @@ export class MockedIntschClient implements IIntelligentSearchClient {
3738
} else {
3839
this.fetchCorrection.mockResolvedValue(args?.fetchCorrection ?? null)
3940
}
41+
42+
if (args?.fetchBanners instanceof Error) {
43+
this.fetchBanners.mockRejectedValue(args.fetchBanners)
44+
} else {
45+
this.fetchBanners.mockResolvedValue(args?.fetchBanners ?? null)
46+
}
4047
}
4148

4249
public fetchAutocompleteSuggestions = jest.fn()
4350
public fetchTopSearches = jest.fn()
4451
public fetchSearchSuggestions = jest.fn()
4552
public fetchCorrection = jest.fn()
53+
public fetchBanners = jest.fn()
4654
}
4755

4856
export type IntelligentSearchClientArgs = {
4957
fetchAutocompleteSuggestions?: AutocompleteSuggestionsResponse | Error
5058
fetchTopSearches?: TopSearchesResponse | Error
5159
fetchSearchSuggestions?: SearchSuggestionsResponse | Error
5260
fetchCorrection?: CorrectionResponse | Error
61+
fetchBanners?: FetchBannersResponse | Error
5362
}

node/resolvers/search/index.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {
3939
fetchSearchSuggestions,
4040
fetchCorrection,
4141
} from '../../services/autocomplete'
42+
import { fetchBanners } from '../../services/banners'
4243
interface ProductIndentifier {
4344
field: 'id' | 'slug' | 'ean' | 'reference' | 'sku'
4445
value: string
@@ -791,14 +792,7 @@ export const queries = {
791792
args: { fullText: string; selectedFacets: SelectedFacet[] },
792793
ctx: Context
793794
) => {
794-
const { intelligentSearchApi } = ctx.clients
795-
796-
return intelligentSearchApi.banners(
797-
{
798-
query: args.fullText,
799-
},
800-
buildAttributePath(args.selectedFacets)
801-
)
795+
return fetchBanners(ctx, args)
802796
},
803797
correction: (_: any, args: { fullText: string }, ctx: Context) => {
804798
return fetchCorrection(ctx, args.fullText)

node/services/autocomplete.ts

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,8 @@
1-
import type { Context } from '@vtex/api'
21

3-
import type { Clients } from '../clients'
4-
5-
/**
6-
* Helper function to execute intsch as primary with intelligentSearchApi as fallback
7-
*/
8-
async function withFallback<T>(
9-
primaryFn: () => Promise<T>,
10-
fallbackFn: () => Promise<T>,
11-
logger: any,
12-
operationName: string,
13-
args?: Record<string, unknown>
14-
): Promise<T> {
15-
try {
16-
return await primaryFn()
17-
} catch (error) {
18-
logger.warn({
19-
message: `${operationName}: Primary call failed, using fallback`,
20-
error: error.message,
21-
args,
22-
})
23-
24-
return fallbackFn()
25-
}
26-
}
2+
import { withFallback } from '../utils/with-fallback'
273

284
export function fetchAutocompleteSuggestions(
29-
ctx: Context<Clients>,
5+
ctx: Context,
306
query: string
317
) {
328
const { intelligentSearchApi, intsch } = ctx.clients
@@ -40,7 +16,7 @@ export function fetchAutocompleteSuggestions(
4016
)
4117
}
4218

43-
export function fetchTopSearches(ctx: Context<Clients>) {
19+
export function fetchTopSearches(ctx: Context) {
4420
const { intelligentSearchApi, intsch } = ctx.clients
4521

4622
return withFallback(
@@ -52,7 +28,7 @@ export function fetchTopSearches(ctx: Context<Clients>) {
5228
)
5329
}
5430

55-
export function fetchSearchSuggestions(ctx: Context<Clients>, query: string) {
31+
export function fetchSearchSuggestions(ctx: Context, query: string) {
5632
const { intelligentSearchApi, intsch } = ctx.clients
5733

5834
return withFallback(
@@ -64,7 +40,7 @@ export function fetchSearchSuggestions(ctx: Context<Clients>, query: string) {
6440
)
6541
}
6642

67-
export function fetchCorrection(ctx: Context<Clients>, query: string) {
43+
export function fetchCorrection(ctx: Context, query: string) {
6844
const { intelligentSearchApi, intsch } = ctx.clients
6945

7046
return withFallback(

node/services/banners.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { compareApiResults } from '../utils/compareResults'
2+
import { buildAttributePath } from '../commons/compatibility-layer';
3+
4+
export async function fetchBanners(
5+
ctx: Context,
6+
args: { fullText: string; selectedFacets: SelectedFacet[] }
7+
) {
8+
const { intelligentSearchApi, intsch } = ctx.clients
9+
10+
const argumentsToFetchBanners = {
11+
query: args.fullText,
12+
path: buildAttributePath(args.selectedFacets),
13+
}
14+
15+
return compareApiResults(
16+
() => intelligentSearchApi.fetchBanners(argumentsToFetchBanners),
17+
() =>
18+
intsch.fetchBanners(argumentsToFetchBanners),
19+
ctx.vtex.production ? 10 : 100,
20+
ctx.vtex.logger,
21+
{
22+
logPrefix: 'Banners',
23+
args: argumentsToFetchBanners,
24+
}
25+
)
26+
}

node/utils/with-fallback.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Helper function to execute intsch as primary with intelligentSearchApi as fallback
3+
*/
4+
export async function withFallback<T>(
5+
primaryFn: () => Promise<T>,
6+
fallbackFn: () => Promise<T>,
7+
logger: any,
8+
operationName: string,
9+
args?: Record<string, unknown>
10+
): Promise<T> {
11+
try {
12+
return await primaryFn()
13+
} catch (error) {
14+
logger.warn({
15+
message: `${operationName}: Primary call failed, using fallback`,
16+
error: error.message,
17+
args,
18+
})
19+
20+
return fallbackFn()
21+
}
22+
}

0 commit comments

Comments
 (0)