Skip to content

Commit 1f5d2d4

Browse files
authored
Using a feature flag to route the PDP traffic to some specific accounts to our endpoint on EKS. (#497)
* Add the fetch product call * Extract fetchProduct logic * Refact build segment logic * Fix products call * Fix types * Fix segment typing * Using the segment info * Update changelog * Increase maxDepth * Add ignorred diffences feature to compareResults * Add feature flag * Remove intelbras from FF * Remove atob dep * Fix lint * Remove API compare * Update changelog * Using app seting to actvate the feature flag * Fix lint
1 parent 31c542e commit 1f5d2d4

File tree

17 files changed

+992
-736
lines changed

17 files changed

+992
-736
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+
- Using a feature flag to route the PDP traffic to some specific accounts.
13+
1014
## [1.84.0] - 2025-10-07
1115

1216
### Changed

manifest.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
"title": "Set to slugify links. Uses default catalog slug instead",
2626
"type": "boolean",
2727
"default": false
28+
},
29+
"shouldUseNewPDPEndpoint": {
30+
"title": "Feature flag to use the new PDP endpoint",
31+
"type": "boolean",
32+
"default": false
2833
}
2934
}
3035
},

node/clients/intelligent-search-api.ts

Lines changed: 5 additions & 1 deletion
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 { FetchBannersArgs, IIntelligentSearchClient } from './intsch/types'
5+
import type { FetchBannersArgs, IIntelligentSearchClient, FetchProductArgs, FetchProductResponse } from './intsch/types'
66

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

@@ -68,6 +68,10 @@ export class IntelligentSearchApi
6868
this.locale = locale ?? tenant?.locale
6969
}
7070

71+
fetchProduct(_: FetchProductArgs): Promise<FetchProductResponse> {
72+
throw new Error('Method not implemented.')
73+
}
74+
7175
public async fetchTopSearches() {
7276
return this.http.get('/top_searches', {
7377
params: {

node/clients/intsch/index.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import { InstanceOptions, IOContext, JanusClient } from '@vtex/api'
1+
import type { InstanceOptions, IOContext } from '@vtex/api'
2+
import { JanusClient } from '@vtex/api'
23

3-
import {
4+
import type {
45
AutocompleteSuggestionsArgs,
56
AutocompleteSuggestionsResponse,
67
CorrectionArgs,
78
CorrectionResponse,
89
FetchBannersArgs,
910
FetchBannersResponse,
11+
FetchProductArgs,
12+
FetchProductResponse,
1013
IIntelligentSearchClient,
1114
SearchSuggestionsArgs,
1215
SearchSuggestionsResponse,
@@ -16,7 +19,7 @@ import {
1619
export class Intsch extends JanusClient implements IIntelligentSearchClient {
1720
private locale: string | undefined
1821

19-
public constructor(ctx: IOContext, options?: InstanceOptions) {
22+
constructor(ctx: IOContext, options?: InstanceOptions) {
2023
const env = ctx.production ? 'stable' : 'beta'
2124

2225
super(ctx, options, env)
@@ -26,6 +29,19 @@ export class Intsch extends JanusClient implements IIntelligentSearchClient {
2629
this.locale = locale ?? tenant?.locale
2730
}
2831

32+
public fetchProduct(args: FetchProductArgs): Promise<FetchProductResponse> {
33+
return this.http.get('/api/intelligent-search/v1/products', {
34+
params: {
35+
field: args.field,
36+
value: args.value,
37+
sc: args.salesChannel ?? 1,
38+
regionId: args.regionId,
39+
locale: args.locale,
40+
},
41+
metric: 'search-product-new',
42+
})
43+
}
44+
2945
public fetchAutocompleteSuggestions(
3046
args: AutocompleteSuggestionsArgs
3147
): Promise<AutocompleteSuggestionsResponse> {

node/clients/intsch/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ export type FetchBannersResponse = {
6262
}[]
6363
}
6464

65+
export type FetchProductArgs = {
66+
field: 'id' | 'slug' | 'ean' | 'reference' | 'sku'
67+
value: string
68+
salesChannel?: string
69+
regionId?: string
70+
locale?:string
71+
}
72+
73+
export type FetchProductResponse = SearchProduct
6574
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
6675
export interface IIntelligentSearchClient {
6776
fetchAutocompleteSuggestions(
@@ -73,4 +82,5 @@ export interface IIntelligentSearchClient {
7382
): Promise<SearchSuggestionsResponse>
7483
fetchCorrection(args: CorrectionArgs): Promise<CorrectionResponse>
7584
fetchBanners(args: FetchBannersArgs): Promise<FetchBannersResponse>
85+
fetchProduct(args: FetchProductArgs): Promise<FetchProductResponse>
7686
}

node/directives/withSegment.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { defaultFieldResolver, GraphQLField } from 'graphql'
22
import { SchemaDirectiveVisitor } from 'graphql-tools'
3-
import atob from 'atob'
43

54
/**
65
* Warning: we stopped using the segment client to decode the segment token for us because it added unnecessary overhead for now.
@@ -12,11 +11,11 @@ export class WithSegment extends SchemaDirectiveVisitor {
1211
field.resolve = async (root, args, ctx: Context, info) => {
1312
const {
1413
vtex: { segmentToken },
15-
clients: {segment}
14+
clients: { segment },
1615
} = ctx
1716
ctx.vtex.segment = segmentToken
18-
? JSON.parse(atob(segmentToken))
19-
: (await segment.getSegment())
17+
? JSON.parse(Buffer.from(segmentToken, 'base64').toString())
18+
: await segment.getSegment()
2019
return resolve(root, args, ctx, info)
2120
}
2221
}

node/mocks/contextFactory.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export function createContext<Ctx = Context>({
1212
req,
1313
query,
1414
production,
15+
appSettings,
1516
}: {
1617
production?: boolean
1718
appSettings?: Record<string, any>
@@ -47,7 +48,7 @@ export function createContext<Ctx = Context>({
4748
intelligentSearchApiSettings
4849
),
4950
apps: {
50-
getAppSettings: jest.fn().mockReturnValue({}),
51+
getAppSettings: jest.fn().mockReturnValue(appSettings ?? {}),
5152
},
5253
},
5354
vtex: {

node/mocks/intsch.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
SearchSuggestionsResponse,
66
CorrectionResponse,
77
FetchBannersResponse,
8+
FetchProductResponse,
89
} from '../clients/intsch/types'
910

1011
export class MockedIntschClient implements IIntelligentSearchClient {
@@ -44,13 +45,19 @@ export class MockedIntschClient implements IIntelligentSearchClient {
4445
} else {
4546
this.fetchBanners.mockResolvedValue(args?.fetchBanners ?? null)
4647
}
48+
if (args?.fetchProduct instanceof Error) {
49+
this.fetchProduct.mockRejectedValue(args.fetchProduct)
50+
} else {
51+
this.fetchProduct.mockResolvedValue(args?.fetchProduct ?? null)
52+
}
4753
}
4854

4955
public fetchAutocompleteSuggestions = jest.fn()
5056
public fetchTopSearches = jest.fn()
5157
public fetchSearchSuggestions = jest.fn()
5258
public fetchCorrection = jest.fn()
5359
public fetchBanners = jest.fn()
60+
public fetchProduct = jest.fn()
5461
}
5562

5663
export type IntelligentSearchClientArgs = {
@@ -59,4 +66,5 @@ export type IntelligentSearchClientArgs = {
5966
fetchSearchSuggestions?: SearchSuggestionsResponse | Error
6067
fetchCorrection?: CorrectionResponse | Error
6168
fetchBanners?: FetchBannersResponse | Error
69+
fetchProduct?: FetchProductResponse | Error
6270
}

node/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
},
77
"dependencies": {
88
"@gocommerce/utils": "^0.6.11",
9-
"atob": "^2.1.2",
109
"camelcase": "^5.0.0",
1110
"co-body": "^6.2.0",
1211
"cookie": "^0.3.1",
@@ -18,7 +17,6 @@
1817
"vtex.search-graphql": "http://vtex.vtexassets.com/_v/public/typings/v1/[email protected]/public"
1918
},
2019
"devDependencies": {
21-
"@types/atob": "^2.1.2",
2220
"@types/camelcase": "^4.1.0",
2321
"@types/co-body": "^6.1.3",
2422
"@types/cookie": "^0.4.1",
@@ -30,8 +28,6 @@
3028
"@vtex/api": "^7.0.0",
3129
"@vtex/tsconfig": "^0.6.0",
3230
"axios": "^0.21.1",
33-
"eslint": "^5.15.3",
34-
"eslint-config-vtex": "^10.1.0",
3531
"jest": "^29.7.0",
3632
"ts-jest": "^29.2.5",
3733
"typemoq": "^2.1.0",

node/resolvers/search/index.ts

Lines changed: 28 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { NotFoundError, UserInputError, createMessagesLoader } from '@vtex/api'
2-
import { head, isEmpty, isNil, pathOr, test } from 'ramda'
2+
import { pathOr, test } from 'ramda'
33

44
import {
55
buildAttributePath,
66
convertOrderBy,
77
} from '../../commons/compatibility-layer'
88
import { getWorkspaceSearchParamsFromStorage } from '../../routes/workspaceSearchParams'
9+
import {
10+
buildVtexSegment,
11+
ProductArgs,
12+
ProductIdentifier,
13+
resolveProduct,
14+
} from '../../services/product'
915
import { shouldTranslateToTenantLocale } from '../../utils/i18n'
1016
import { resolvers as assemblyOptionResolvers } from './assemblyOption'
1117
import { resolvers as autocompleteResolvers } from './autocomplete'
@@ -33,24 +39,13 @@ import {
3339
getShippingOptionsFromSelectedFacets,
3440
validMapAndQuery,
3541
} from './utils'
36-
import {
42+
import {
3743
fetchAutocompleteSuggestions,
3844
fetchTopSearches,
3945
fetchSearchSuggestions,
4046
fetchCorrection,
4147
} from '../../services/autocomplete'
4248
import { fetchBanners } from '../../services/banners'
43-
interface ProductIndentifier {
44-
field: 'id' | 'slug' | 'ean' | 'reference' | 'sku'
45-
value: string
46-
}
47-
48-
interface ProductArgs {
49-
slug?: string
50-
identifier?: ProductIndentifier
51-
regionId?: string
52-
salesChannel?: number
53-
}
5449

5550
enum CrossSellingInput {
5651
view = 'view',
@@ -67,7 +62,7 @@ enum CrossSellingGroupByInput {
6762
}
6863

6964
interface ProductRecommendationArg {
70-
identifier?: ProductIndentifier
65+
identifier?: ProductIdentifier
7166
type?: CrossSellingInput
7267
groupBy?: CrossSellingGroupByInput
7368
}
@@ -88,25 +83,6 @@ const inputToSearchCrossSelling = {
8883
[CrossSellingInput.suggestions]: SearchCrossSellingTypes.suggestions,
8984
}
9085

91-
const buildVtexSegment = (
92-
vtexSegment?: SegmentData,
93-
tradePolicy?: number,
94-
regionId?: string | null
95-
): string => {
96-
const cookie = {
97-
regionId: regionId,
98-
channel: tradePolicy,
99-
utm_campaign: vtexSegment?.utm_campaign || '',
100-
utm_source: vtexSegment?.utm_source || '',
101-
utmi_campaign: vtexSegment?.utmi_campaign || '',
102-
currencyCode: vtexSegment?.currencyCode || '',
103-
currencySymbol: vtexSegment?.currencySymbol || '',
104-
countryCode: vtexSegment?.countryCode || '',
105-
cultureInfo: vtexSegment?.cultureInfo || '',
106-
}
107-
return new Buffer(JSON.stringify(cookie)).toString('base64')
108-
}
109-
11086
/**
11187
* There is an URL pattern in VTEX where the number of mapSegments doesn't match the number of querySegments. This function deals with these cases.
11288
* Since this should not be a search concern, this function will be removed soon.
@@ -261,9 +237,6 @@ const isLegacySearchFormat = ({
261237
return map.split(MAP_VALUES_SEP).length === query.split(PATH_SEPARATOR).length
262238
}
263239

264-
const isValidProductIdentifier = (identifier: ProductIndentifier | undefined) =>
265-
!!identifier && !isNil(identifier.value) && !isEmpty(identifier.value)
266-
267240
const getTranslatedSearchTerm = async (
268241
query: SearchArgs['query'],
269242
map: SearchArgs['map'],
@@ -403,61 +376,21 @@ export const queries = {
403376
},
404377

405378
product: async (_: any, rawArgs: ProductArgs, ctx: Context) => {
406-
const {
407-
clients: { search },
408-
} = ctx
409-
410-
const args =
411-
rawArgs && isValidProductIdentifier(rawArgs.identifier)
412-
? rawArgs
413-
: { identifier: { field: 'slug', value: rawArgs.slug! } }
414-
415-
if (!args.identifier) {
416-
throw new UserInputError('No product identifier provided')
417-
}
418-
419-
let cookie: SegmentData | undefined = ctx.vtex.segment
379+
const product = await resolveProduct(ctx, rawArgs)
420380

421-
const salesChannel = rawArgs.salesChannel || cookie?.channel || 1
422-
423-
const { field, value } = args.identifier
424-
425-
let products = [] as SearchProduct[]
426-
427-
const vtexSegment =
428-
!cookie || (!cookie?.regionId && rawArgs.regionId)
429-
? buildVtexSegment(cookie, salesChannel, rawArgs.regionId)
430-
: ctx.vtex.segmentToken
431-
432-
switch (field) {
433-
case 'id':
434-
products = await search.productById(value, vtexSegment, salesChannel)
435-
break
436-
case 'slug':
437-
products = await search.product(value, vtexSegment, salesChannel)
438-
break
439-
case 'ean':
440-
products = await search.productByEan(value, vtexSegment, salesChannel)
441-
break
442-
case 'reference':
443-
products = await search.productByReference(
444-
value,
445-
vtexSegment,
446-
salesChannel
447-
)
448-
break
449-
case 'sku':
450-
products = await search.productBySku(value, vtexSegment, salesChannel)
451-
break
452-
}
453-
454-
if (products.length > 0) {
455-
return head(products)
381+
if (!product) {
382+
const identifier = rawArgs?.identifier || {
383+
field: 'slug',
384+
value: rawArgs.slug!,
385+
}
386+
throw new NotFoundError(
387+
`No product was found with requested ${
388+
identifier.field
389+
} ${JSON.stringify({ identifier })}`
390+
)
456391
}
457392

458-
throw new NotFoundError(
459-
`No product was found with requested ${field} ${JSON.stringify(args)}`
460-
)
393+
return product
461394
},
462395

463396
products: async (_: any, args: ProductsInput, ctx: Context) => {
@@ -516,11 +449,13 @@ export const queries = {
516449

517450
const vtexSegment =
518451
!ctx.vtex.segment || (!ctx.vtex.segment?.regionId && args.regionId)
519-
? buildVtexSegment(
520-
ctx.vtex.segment,
521-
Number(args.salesChannel),
522-
args.regionId
523-
)
452+
? buildVtexSegment({
453+
vtexSegment: ctx.vtex.segment as
454+
| SegmentData
455+
| undefined,
456+
salesChannel: args.salesChannel?.toString(),
457+
regionId: args.regionId ?? undefined,
458+
})
524459
: ctx.vtex.segmentToken
525460

526461
switch (field) {

0 commit comments

Comments
 (0)