Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Changed

- Start testing 10% of the fetchBanners traffic to intsch and compare the result against the VTEX /IO version.

## [1.82.0] - 2025-10-01

### Changed
Expand Down
13 changes: 5 additions & 8 deletions node/clients/intelligent-search-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { InstanceOptions, IOContext } from '@vtex/api'
import { ExternalClient } from '@vtex/api'

import { parseState } from '../utils/searchState'
import type { IIntelligentSearchClient } from './intsch/types'
import type { FetchBannersArgs, IIntelligentSearchClient } from './intsch/types'

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

Expand All @@ -18,9 +18,6 @@ interface AutocompleteSearchSuggestionsParams {
query: string
}

interface BannersArgs {
query: string
}

interface FacetsArgs {
query?: string
Expand Down Expand Up @@ -103,13 +100,13 @@ export class IntelligentSearchApi
})
}

public async banners(params: BannersArgs, path: string) {
if (isPathTraversal(path)) {
public async fetchBanners(params: FetchBannersArgs) {
if (isPathTraversal(params.path)) {
throw new Error('Malformed URL')
}

return this.http.get(`/banners/${path}`, {
params: { ...params, query: params.query, locale: this.locale },
return this.http.get(`/banners/${params.path}`, {
params: { query: params.query, locale: this.locale },
metric: 'banners',
})
}
Expand Down
9 changes: 9 additions & 0 deletions node/clients/intsch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
AutocompleteSuggestionsResponse,
CorrectionArgs,
CorrectionResponse,
FetchBannersArgs,
FetchBannersResponse,
IIntelligentSearchClient,
SearchSuggestionsArgs,
SearchSuggestionsResponse,
Expand Down Expand Up @@ -58,4 +60,11 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient {
metric: 'correction-new',
})
}

public fetchBanners(args: FetchBannersArgs): Promise<FetchBannersResponse> {
return this.http.get(`/api/intelligent-search/v0/banners/${args.path}`, {
params: { query: args.query, locale: this.locale },
metric: 'banners-new',
})
}
}
21 changes: 19 additions & 2 deletions node/clients/intsch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type AutocompleteSuggestionsResponse = {
}

export type TopSearchesResponse = {
searches: {
searches: {
term: string
count: number
}[]
Expand Down Expand Up @@ -48,12 +48,29 @@ export type CorrectionResponse = {
}
}

export type FetchBannersArgs = {
query: string
path: string
}

export type FetchBannersResponse = {
banners: {
id: string
name: string
area: string
html: string
}[]
}

// eslint-disable-next-line @typescript-eslint/interface-name-prefix
export interface IIntelligentSearchClient {
fetchAutocompleteSuggestions(
args: AutocompleteSuggestionsArgs
): Promise<AutocompleteSuggestionsResponse>
fetchTopSearches(): Promise<TopSearchesResponse>
fetchSearchSuggestions(args: SearchSuggestionsArgs): Promise<SearchSuggestionsResponse>
fetchSearchSuggestions(
args: SearchSuggestionsArgs
): Promise<SearchSuggestionsResponse>
fetchCorrection(args: CorrectionArgs): Promise<CorrectionResponse>
fetchBanners(args: FetchBannersArgs): Promise<FetchBannersResponse>
}
9 changes: 9 additions & 0 deletions node/mocks/intsch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
TopSearchesResponse,
SearchSuggestionsResponse,
CorrectionResponse,
FetchBannersResponse,
} from '../clients/intsch/types'

export class MockedIntschClient implements IIntelligentSearchClient {
Expand Down Expand Up @@ -37,17 +38,25 @@ export class MockedIntschClient implements IIntelligentSearchClient {
} else {
this.fetchCorrection.mockResolvedValue(args?.fetchCorrection ?? null)
}

if (args?.fetchBanners instanceof Error) {
this.fetchBanners.mockRejectedValue(args.fetchBanners)
} else {
this.fetchBanners.mockResolvedValue(args?.fetchBanners ?? null)
}
}

public fetchAutocompleteSuggestions = jest.fn()
public fetchTopSearches = jest.fn()
public fetchSearchSuggestions = jest.fn()
public fetchCorrection = jest.fn()
public fetchBanners = jest.fn()
}

export type IntelligentSearchClientArgs = {
fetchAutocompleteSuggestions?: AutocompleteSuggestionsResponse | Error
fetchTopSearches?: TopSearchesResponse | Error
fetchSearchSuggestions?: SearchSuggestionsResponse | Error
fetchCorrection?: CorrectionResponse | Error
fetchBanners?: FetchBannersResponse | Error
}
10 changes: 2 additions & 8 deletions node/resolvers/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
fetchSearchSuggestions,
fetchCorrection,
} from '../../services/autocomplete'
import { fetchBanners } from '../../services/banners'
interface ProductIndentifier {
field: 'id' | 'slug' | 'ean' | 'reference' | 'sku'
value: string
Expand Down Expand Up @@ -791,14 +792,7 @@ export const queries = {
args: { fullText: string; selectedFacets: SelectedFacet[] },
ctx: Context
) => {
const { intelligentSearchApi } = ctx.clients

return intelligentSearchApi.banners(
{
query: args.fullText,
},
buildAttributePath(args.selectedFacets)
)
return fetchBanners(ctx, args)
},
correction: (_: any, args: { fullText: string }, ctx: Context) => {
return fetchCorrection(ctx, args.fullText)
Expand Down
34 changes: 5 additions & 29 deletions node/services/autocomplete.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,8 @@
import type { Context } from '@vtex/api'

import type { Clients } from '../clients'

/**
* Helper function to execute intsch as primary with intelligentSearchApi as fallback
*/
async function withFallback<T>(
primaryFn: () => Promise<T>,
fallbackFn: () => Promise<T>,
logger: any,
operationName: string,
args?: Record<string, unknown>
): Promise<T> {
try {
return await primaryFn()
} catch (error) {
logger.warn({
message: `${operationName}: Primary call failed, using fallback`,
error: error.message,
args,
})

return fallbackFn()
}
}
import { withFallback } from '../utils/with-fallback'

export function fetchAutocompleteSuggestions(
ctx: Context<Clients>,
ctx: Context,
query: string
) {
const { intelligentSearchApi, intsch } = ctx.clients
Expand All @@ -40,7 +16,7 @@ export function fetchAutocompleteSuggestions(
)
}

export function fetchTopSearches(ctx: Context<Clients>) {
export function fetchTopSearches(ctx: Context) {
const { intelligentSearchApi, intsch } = ctx.clients

return withFallback(
Expand All @@ -52,7 +28,7 @@ export function fetchTopSearches(ctx: Context<Clients>) {
)
}

export function fetchSearchSuggestions(ctx: Context<Clients>, query: string) {
export function fetchSearchSuggestions(ctx: Context, query: string) {
const { intelligentSearchApi, intsch } = ctx.clients

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

export function fetchCorrection(ctx: Context<Clients>, query: string) {
export function fetchCorrection(ctx: Context, query: string) {
const { intelligentSearchApi, intsch } = ctx.clients

return withFallback(
Expand Down
26 changes: 26 additions & 0 deletions node/services/banners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { compareApiResults } from '../utils/compareResults'
import { buildAttributePath } from '../commons/compatibility-layer';

export async function fetchBanners(
ctx: Context,
args: { fullText: string; selectedFacets: SelectedFacet[] }
) {
const { intelligentSearchApi, intsch } = ctx.clients

const argumentsToFetchBanners = {
query: args.fullText,
path: buildAttributePath(args.selectedFacets),
}

return compareApiResults(
() => intelligentSearchApi.fetchBanners(argumentsToFetchBanners),
() =>
intsch.fetchBanners(argumentsToFetchBanners),
ctx.vtex.production ? 10 : 100,
ctx.vtex.logger,
{
logPrefix: 'Banners',
args: argumentsToFetchBanners,
}
)
}
22 changes: 22 additions & 0 deletions node/utils/with-fallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Helper function to execute intsch as primary with intelligentSearchApi as fallback
*/
export async function withFallback<T>(
primaryFn: () => Promise<T>,
fallbackFn: () => Promise<T>,
logger: any,
operationName: string,
args?: Record<string, unknown>
): Promise<T> {
try {
return await primaryFn()
} catch (error) {
logger.warn({
message: `${operationName}: Primary call failed, using fallback`,
error: error.message,
args,
})

return fallbackFn()
}
}
Loading