Skip to content

Commit aee5647

Browse files
authored
Merge pull request #491 from vtex-apps/feature/newIsApi
Send autocomplete shadow traffic to the new EKS Api.
2 parents 469af99 + b47144e commit aee5647

26 files changed

+1865
-153
lines changed

.vtexignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ node/**/*.test.js
1212
node/**/*.test.ts
1313
node/**/*.test.tsx
1414
node/coverage/*
15-
node/mocks
15+
node/mocks/

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+
- Send 1% of the production traffic to the new intsch service authough still uses the result from the I/O app.
13+
1014
## [1.76.0] - 2025-02-07
1115

1216
### Changed

lint.sh

Lines changed: 0 additions & 6 deletions
This file was deleted.

manifest.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@
6464
"path": "/*"
6565
}
6666
},
67+
{
68+
"name": "outbound-access",
69+
"attrs": {
70+
"host": "portal.vtexcommercestable.com.br",
71+
"path": "/api/intelligent-search"
72+
}
73+
},
74+
{
75+
"name": "outbound-access",
76+
"attrs": {
77+
"host": "portal.vtexcommercebeta.com.br",
78+
"path": "/api/intelligent-search"
79+
}
80+
},
6781
{
6882
"name": "vtex.messages:graphql-translate-messages"
6983
}

node/clients/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,26 @@ import { Search } from './search'
44
import { Checkout } from './checkout'
55
import { Rewriter } from './rewriter'
66
import { IntelligentSearchApi } from './intelligent-search-api'
7+
import { Intsch } from './intsch'
78

89
export class Clients extends IOClients {
910
public get search() {
1011
return this.getOrSet('search', Search)
1112
}
13+
1214
public get checkout() {
1315
return this.getOrSet('checkout', Checkout)
1416
}
17+
1518
public get rewriter() {
1619
return this.getOrSet('rewriter', Rewriter)
1720
}
21+
1822
public get intelligentSearchApi() {
1923
return this.getOrSet('intelligentSearchApi', IntelligentSearchApi)
2024
}
25+
26+
public get intsch() {
27+
return this.getOrSet('intsch', Intsch)
28+
}
2129
}

node/clients/intelligent-search-api.ts

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { ExternalClient, InstanceOptions, IOContext } from "@vtex/api";
2-
import { parseState } from "../utils/searchState";
1+
import type { InstanceOptions, IOContext } from '@vtex/api'
2+
import { ExternalClient } from '@vtex/api'
3+
4+
import { parseState } from '../utils/searchState'
5+
import type { IIntelligentSearchClient } from './intsch/types'
36

47
const isPathTraversal = (str: string) => str.indexOf('..') >= 0
8+
59
interface CorrectionParams {
610
query: string
711
}
@@ -44,59 +48,89 @@ const decodeQuery = (query: string) => {
4448
}
4549
}
4650

47-
export class IntelligentSearchApi extends ExternalClient {
51+
export class IntelligentSearchApi
52+
extends ExternalClient
53+
implements IIntelligentSearchClient
54+
{
4855
private locale: string | undefined
4956

5057
public constructor(context: IOContext, options?: InstanceOptions) {
51-
super(`http://${context.workspace}--${context.account}.myvtex.com/_v/api/intelligent-search`, context, {
52-
...options,
53-
headers: {
54-
...options?.headers,
58+
super(
59+
`http://${context.workspace}--${context.account}.myvtex.com/_v/api/intelligent-search`,
60+
context,
61+
{
62+
...options,
63+
headers: {
64+
...options?.headers,
65+
},
5566
}
56-
})
67+
)
5768

5869
const { locale, tenant } = context
70+
5971
this.locale = locale ?? tenant?.locale
6072
}
6173

6274
public async topSearches() {
63-
return this.http.get('/top_searches', {params: {
64-
locale: this.locale
65-
}, metric: 'topSearches'})
75+
return this.http.get('/top_searches', {
76+
params: {
77+
locale: this.locale,
78+
},
79+
metric: 'topSearches',
80+
})
6681
}
6782

6883
public async correction(params: CorrectionParams) {
69-
return this.http.get('/correction_search', {params: {...params, locale: this.locale}, metric: 'correction'})
84+
return this.http.get('/correction_search', {
85+
params: { ...params, locale: this.locale },
86+
metric: 'correction',
87+
})
7088
}
7189

7290
public async searchSuggestions(params: SearchSuggestionsParams) {
73-
return this.http.get('/search_suggestions', {params: {...params, locale: this.locale}, metric: 'searchSuggestions'})
91+
return this.http.get('/search_suggestions', {
92+
params: { ...params, locale: this.locale },
93+
metric: 'searchSuggestions',
94+
})
7495
}
7596

76-
public async autocompleteSearchSuggestions(params: AutocompleteSearchSuggestionsParams) {
77-
return this.http.get('/autocomplete_suggestions', {params: {...params, locale: this.locale}, metric: 'autocompleteSearchSuggestions'})
97+
public async fetchAutocompleteSuggestions(
98+
params: AutocompleteSearchSuggestionsParams
99+
) {
100+
return this.http.get('/autocomplete_suggestions', {
101+
params: { ...params, locale: this.locale },
102+
metric: 'autocompleteSearchSuggestions',
103+
})
78104
}
79105

80106
public async banners(params: BannersArgs, path: string) {
81107
if (isPathTraversal(path)) {
82-
throw new Error("Malformed URL")
108+
throw new Error('Malformed URL')
83109
}
84110

85-
return this.http.get(`/banners/${path}`, {params: {...params, query: params.query, locale: this.locale}, metric: 'banners'})
111+
return this.http.get(`/banners/${path}`, {
112+
params: { ...params, query: params.query, locale: this.locale },
113+
metric: 'banners',
114+
})
86115
}
87116

88-
public async facets(params: FacetsArgs, path: string, shippingHeader?: string[]) {
117+
public async facets(
118+
params: FacetsArgs,
119+
path: string,
120+
shippingHeader?: string[]
121+
) {
89122
if (isPathTraversal(path)) {
90-
throw new Error("Malformed URL")
123+
throw new Error('Malformed URL')
91124
}
92125

93-
const {query, leap, searchState} = params
126+
const { query, leap, searchState } = params
94127

95128
return this.http.get(`/facets/${path}`, {
96129
params: {
97130
...params,
98131
query: query && decodeQuery(query),
99132
locale: this.locale,
133+
// eslint-disable-next-line @typescript-eslint/camelcase
100134
bgy_leap: leap ? true : undefined,
101135
...parseState(searchState),
102136
},
@@ -107,16 +141,22 @@ export class IntelligentSearchApi extends ExternalClient {
107141
})
108142
}
109143

110-
public async productSearch(params: SearchResultArgs, path: string, shippingHeader?: string[]) {
111-
const {query, leap, searchState} = params
144+
public async productSearch(
145+
params: SearchResultArgs,
146+
path: string,
147+
shippingHeader?: string[]
148+
) {
149+
const { query, leap, searchState } = params
150+
112151
if (isPathTraversal(path)) {
113-
throw new Error("Malformed URL")
152+
throw new Error('Malformed URL')
114153
}
115154

116155
return this.http.get(`/product_search/${path}`, {
117156
params: {
118157
query: query && decodeQuery(query),
119158
locale: this.locale,
159+
// eslint-disable-next-line @typescript-eslint/camelcase
120160
bgy_leap: leap ? true : undefined,
121161
...parseState(searchState),
122162
...params,
@@ -128,16 +168,22 @@ export class IntelligentSearchApi extends ExternalClient {
128168
})
129169
}
130170

131-
public async sponsoredProducts(params: SearchResultArgs, path: string, shippingHeader?: string[]) {
132-
const {query, leap, searchState} = params
171+
public async sponsoredProducts(
172+
params: SearchResultArgs,
173+
path: string,
174+
shippingHeader?: string[]
175+
) {
176+
const { query, leap, searchState } = params
177+
133178
if (isPathTraversal(path)) {
134-
throw new Error("Malformed URL")
179+
throw new Error('Malformed URL')
135180
}
136181

137182
return this.http.get(`/sponsored_products/${path}`, {
138183
params: {
139184
query: query && decodeQuery(query),
140185
locale: this.locale,
186+
// eslint-disable-next-line @typescript-eslint/camelcase
141187
bgy_leap: leap ? true : undefined,
142188
...parseState(searchState),
143189
...params,

node/clients/intsch/index.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { InstanceOptions, IOContext, JanusClient } from '@vtex/api'
2+
3+
import {
4+
AutocompleteSuggestionsArgs,
5+
AutocompleteSuggestionsResponse,
6+
IIntelligentSearchClient,
7+
} from './types'
8+
9+
export class Intsch extends JanusClient implements IIntelligentSearchClient {
10+
private locale: string | undefined
11+
12+
public constructor(ctx: IOContext, options?: InstanceOptions) {
13+
const env = ctx.production ? 'stable' : 'beta'
14+
15+
super(ctx, options, env)
16+
17+
const { locale, tenant } = ctx
18+
19+
this.locale = locale ?? tenant?.locale
20+
}
21+
22+
public fetchAutocompleteSuggestions(
23+
args: AutocompleteSuggestionsArgs
24+
): Promise<AutocompleteSuggestionsResponse> {
25+
return this.http.get(
26+
'/api/intelligent-search/v0/autocomplete-suggestions',
27+
{
28+
params: { query: args.query, locale: this.locale },
29+
metric: 'autocompleteSearchSuggestions-new',
30+
}
31+
)
32+
}
33+
}

node/clients/intsch/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* eslint-disable @typescript-eslint/prefer-interface */
2+
3+
export type AutocompleteSuggestionsArgs = {
4+
query: string
5+
}
6+
7+
export type AutocompleteSuggestionsResponse = {
8+
searches: {
9+
term: string
10+
count: number
11+
attributes?: {
12+
key: string
13+
value: string
14+
labelKey: string
15+
labelValue: string
16+
}[]
17+
}[]
18+
}
19+
20+
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
21+
export interface IIntelligentSearchClient {
22+
fetchAutocompleteSuggestions(
23+
args: AutocompleteSuggestionsArgs
24+
): Promise<AutocompleteSuggestionsResponse>
25+
}

node/clients/search.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ enum SimulationBehavior {
2525

2626
const inflightKey = ({ baseURL, url, params, headers }: RequestConfig) => {
2727
return (
28-
baseURL! +
29-
url! +
28+
(baseURL ?? '') +
29+
(url ?? '') +
3030
stringify(params, { arrayFormat: 'repeat', addQueryPrefix: true }) +
31-
`&segmentToken=${headers['x-vtex-segment']}`
31+
`&segmentToken=${headers ? headers['x-vtex-segment'] : ''}`
3232
)
3333
}
3434

node/mocks/contextFactory.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import parse from 'co-body'
2+
3+
import { MockedIntelligentSearchApiClient } from './intelligent-search-api'
4+
import type { IntelligentSearchClientArgs } from './intsch'
5+
import { MockedIntschClient } from './intsch'
6+
7+
export function createContext<Ctx = Context>({
8+
accountName,
9+
intelligentSearchApiSettings,
10+
intschSettings,
11+
cookies,
12+
body,
13+
req,
14+
query,
15+
production,
16+
}: {
17+
production?: boolean
18+
appSettings?: Record<string, any>
19+
intschSettings?: IntelligentSearchClientArgs
20+
intelligentSearchApiSettings?: IntelligentSearchClientArgs
21+
req?: {
22+
body?: any
23+
}
24+
query?: Record<string, unknown>
25+
body?: any
26+
cookies?: Record<string, string>
27+
accountName?: string
28+
}) {
29+
if (req?.body instanceof Error) {
30+
jest.spyOn(parse, 'json').mockRejectedValue(req.body)
31+
} else {
32+
jest.spyOn(parse, 'json').mockReturnValue(req?.body ?? {})
33+
}
34+
35+
return {
36+
req: {},
37+
body: body ?? {},
38+
cookies: {
39+
get: jest.fn().mockImplementation((key: string) => cookies?.[key]),
40+
},
41+
get: jest.fn().mockReturnValue('localhost'),
42+
response: {
43+
set: jest.fn(),
44+
},
45+
clients: {
46+
intsch: new MockedIntschClient(intschSettings),
47+
intelligentSearchApi: new MockedIntelligentSearchApiClient(
48+
intelligentSearchApiSettings
49+
),
50+
apps: {
51+
getAppSettings: jest.fn().mockReturnValue({}),
52+
},
53+
},
54+
vtex: {
55+
production: production ?? false,
56+
account: accountName ?? 'biggy',
57+
logger: {
58+
info: jest.fn(),
59+
warn: jest.fn(),
60+
error: jest.fn(),
61+
},
62+
},
63+
query: query ?? {},
64+
} as unknown as Ctx
65+
}

0 commit comments

Comments
 (0)