diff --git a/CHANGELOG.md b/CHANGELOG.md index f19bbd07..7f2cf6c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Changed +- Call IS for `product` query instead of SOLR API. + ## [1.69.0] - 2024-03-21 - Parameter `groupBy` into `recommendations` and `productRecommendations` resolvers and `groupByProduct` in `crossSelling` search client. diff --git a/manifest.json b/manifest.json index 731ba198..6e645fe5 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "vendor": "vtex", "name": "search-resolver", - "version": "1.69.0", + "version": "1.70.0-beta.0", "title": "GraphQL resolver for the VTEX store APIs", "description": "GraphQL resolvers for the VTEX API for the catalog and orders.", "credentialType": "absolute", diff --git a/node/clients/intelligent-search-api.ts b/node/clients/intelligent-search-api.ts index 73c10f0b..6ad96124 100644 --- a/node/clients/intelligent-search-api.ts +++ b/node/clients/intelligent-search-api.ts @@ -85,6 +85,19 @@ export class IntelligentSearchApi extends ExternalClient { return this.http.get(`/banners/${path}`, {params: {...params, query: params.query, locale: this.locale}, metric: 'banners'}) } + public async product(params: { field: string, value: string, salesChannel: number }) { + const { field, value, salesChannel } = params + return this.http.get('/product', { + params: { + id: value, + type: field, + locale: this.locale, + salesChannel, + }, + metric: 'product', + }) + } + public async facets(params: FacetsArgs, path: string, shippingHeader?: string[]) { if (isPathTraversal(path)) { throw new Error("Malformed URL") diff --git a/node/clients/search.ts b/node/clients/search.ts index 8365fc79..9998cc3b 100644 --- a/node/clients/search.ts +++ b/node/clients/search.ts @@ -95,44 +95,6 @@ export class Search extends AppClient { ) } - public product = ( - slug: string, - vtexSegment?: string, - salesChannel?: string | number | null - ) => - this.get( - this.addCompleteSpecifications( - this.addSalesChannel( - `/pub/products/search/${this.searchEncodeURI( - slug && slug.toLowerCase() - )}/p`, - salesChannel - ) - ), - { - metric: 'search-product', - headers: this.getVtexSegmentCookieAsHeader(vtexSegment), - } - ) - - public productByEan = ( - id: string, - vtexSegment?: string, - salesChannel?: string | number | null - ) => - this.get( - this.addCompleteSpecifications( - this.addSalesChannel( - `/pub/products/search?fq=alternateIds_Ean:${id}`, - salesChannel - ) - ), - { - metric: 'search-productByEan', - headers: this.getVtexSegmentCookieAsHeader(vtexSegment), - } - ) - public productsByEan = ( ids: string[], vtexSegment?: string, @@ -195,24 +157,6 @@ export class Search extends AppClient { } ) - public productByReference = ( - id: string, - vtexSegment?: string, - salesChannel?: string | number | null - ) => - this.get( - this.addCompleteSpecifications( - this.addSalesChannel( - `/pub/products/search?fq=alternateIds_RefId:${id}`, - salesChannel - ) - ), - { - metric: 'search-productByReference', - headers: this.getVtexSegmentCookieAsHeader(vtexSegment), - } - ) - public productsByReference = ( ids: string[], vtexSegment?: string, diff --git a/node/resolvers/search/index.ts b/node/resolvers/search/index.ts index 48985eb7..c6b47ace 100644 --- a/node/resolvers/search/index.ts +++ b/node/resolvers/search/index.ts @@ -375,7 +375,7 @@ export const queries = { product: async (_: any, rawArgs: ProductArgs, ctx: Context) => { const { - clients: { search }, + clients: { intelligentSearchApi }, } = ctx const args = @@ -394,27 +394,28 @@ export const queries = { const { field, value } = args.identifier let products = [] as SearchProduct[] - - const vtexSegment = (!cookie || (!cookie?.regionId && rawArgs.regionId)) ? buildVtexSegment(cookie, salesChannel, rawArgs.regionId) : ctx.vtex.segmentToken + let fieldId = '' switch (field) { case 'id': - products = await search.productById(value, vtexSegment, salesChannel) + fieldId = 'product.id' break case 'slug': - products = await search.product(value, vtexSegment, salesChannel) + fieldId = 'product.link' break case 'ean': - products = await search.productByEan(value, vtexSegment, salesChannel) + fieldId = 'sku.ean' break case 'reference': - products = await search.productByReference(value, vtexSegment, salesChannel) + fieldId = 'sku.reference' break case 'sku': - products = await search.productBySku(value, vtexSegment, salesChannel) + fieldId = 'sku.id' break } + products = await intelligentSearchApi.product({ field: fieldId, value, salesChannel}) + if (products.length > 0) { return head(products) } diff --git a/node/resolvers/search/offer.ts b/node/resolvers/search/offer.ts index b1135f58..c9c56fbb 100644 --- a/node/resolvers/search/offer.ts +++ b/node/resolvers/search/offer.ts @@ -79,7 +79,7 @@ export const resolvers = { }, giftSkuIds: propOr([], 'GiftSkuIds'), gifts: async ({ GiftSkuIds }: CommertialOffer, _: any, ctx: Context) => { - if (GiftSkuIds.length === 0) { + if (!GiftSkuIds || GiftSkuIds.length === 0) { return [] } diff --git a/node/resolvers/search/product.ts b/node/resolvers/search/product.ts index 0e2aef30..cb3ae996 100644 --- a/node/resolvers/search/product.ts +++ b/node/resolvers/search/product.ts @@ -69,6 +69,8 @@ const knownNotPG = [ 'link', 'linkText', 'productReference', + 'origin', + 'cacheId', ] const removeTrailingSlashes = (str: string) => @@ -181,7 +183,7 @@ export const resolvers = { cacheId || linkText, clusterHighlights: ({origin, clusterHighlights }: SearchProduct) => { - if (origin === 'intelligent-search') { + if (origin === 'intelligent-search' || origin === 'search-document') { return clusterHighlights } @@ -201,7 +203,7 @@ export const resolvers = { }, productClusters: ({origin, productClusters }: SearchProduct) => { - if (origin === 'intelligent-search') { + if (origin === 'intelligent-search' || origin === 'search-document') { return productClusters } @@ -211,8 +213,8 @@ export const resolvers = { properties: async (product: SearchProduct, _: unknown, ctx: Context) => { let valuesUntranslated = [] - if (product.origin === 'intelligent-search') { - valuesUntranslated = product.properties ?? [] + if (product.origin === 'intelligent-search' || product.origin === 'search-document') { + valuesUntranslated = product.properties?.map((prop) => ({...prop, name: prop.originalName ?? prop.name })) ?? [] } else { valuesUntranslated = (product.allSpecifications ?? []).map((name: string) => { const value = (product as unknown as DynamicKey)[name] @@ -287,45 +289,57 @@ export const resolvers = { specificationGroups: async (product: SearchProduct, _: unknown, ctx: Context) => { if (product.origin === 'intelligent-search') { - return product.specificationGroups + return product.specificationGroups ?? [] } - const allSpecificationsGroups = (product.allSpecificationsGroups ?? []).concat(['allSpecifications']) - - const visibleSpecifications = product.completeSpecifications - ? product.completeSpecifications.reduce>((acc, specification) => { - acc[specification.Name] = specification.IsOnProductDetails - return acc - }, {}) - : null - - let noTranslationSpecificationGroups = allSpecificationsGroups.map( - (groupName: string) => { - let groupSpecifications = (product as unknown as DynamicKey)?.[groupName] ?? [] + let noTranslationSpecificationGroups = [] - groupSpecifications = groupSpecifications.filter(specificationName => { - if (visibleSpecifications && visibleSpecifications[specificationName] != null) - return visibleSpecifications[specificationName] - return true + if (product.origin === 'search-document') { + noTranslationSpecificationGroups = product.specificationGroups?.map( + (group) => ({ + ...group, + specifications: group.specifications.map((spec) => ({...spec, name: spec.originalName })) }) - - return { - originalName: groupName, - name: groupName, - specifications: groupSpecifications.map((name) => { - const values = (product as unknown as DynamicKey)[name] || [] - return { - originalName: name, - name, - values, + ) ?? [] + } else { + const allSpecificationsGroups = (product.allSpecificationsGroups ?? []).concat(['allSpecifications']) + + const visibleSpecifications = product.completeSpecifications + ? product.completeSpecifications.reduce>((acc, specification) => { + acc[specification.Name] = specification.IsOnProductDetails + return acc + }, {}) + : null + + noTranslationSpecificationGroups = allSpecificationsGroups.map( + (groupName: string) => { + let groupSpecifications = (product as unknown as DynamicKey)?.[groupName] ?? [] + + groupSpecifications = groupSpecifications.filter(specificationName => { + if (visibleSpecifications && visibleSpecifications[specificationName] != null) + return visibleSpecifications[specificationName] + return true + }) + + return { + originalName: groupName, + name: groupName, + specifications: groupSpecifications.map((name) => { + const values = (product as unknown as DynamicKey)[name] || [] + return { + originalName: name, + name, + values, + } } - } - ), + ), + } } - } - ) + ) - noTranslationSpecificationGroups = noTranslationSpecificationGroups.filter(group => group.specifications.length > 0) + noTranslationSpecificationGroups = noTranslationSpecificationGroups.filter(group => group.specifications.length > 0) + + } if (!shouldTranslateToUserLocale(ctx)) { return noTranslationSpecificationGroups diff --git a/node/resolvers/search/sku.ts b/node/resolvers/search/sku.ts index 3cd10d83..c1cb3acc 100644 --- a/node/resolvers/search/sku.ts +++ b/node/resolvers/search/sku.ts @@ -53,6 +53,11 @@ export const resolvers = { if (!sku) { return sku } + + if (sku.variations?.length && typeof sku.variations[0] !== 'string') { + return sku.variations + } + const variations = (sku.variations || []).map(variationObj => { const variationName = typeof variationObj === 'string' ? variationObj : (variationObj as {name: string}).name const fieldId = (sku.skuSpecifications || []).find(specification => specification.field.name === variationName)?.field?.id diff --git a/node/typings/Catalog.ts b/node/typings/Catalog.ts index 030a919f..84a30cc1 100644 --- a/node/typings/Catalog.ts +++ b/node/typings/Catalog.ts @@ -81,7 +81,7 @@ enum FacetsBehavior { interface SpecificationGroup { name: string originalName: string - specifications: { name: string; originalName: string; values: string[] } + specifications: { name: string; originalName: string; values: string[] }[] } interface SearchProduct { origin?: string @@ -112,7 +112,9 @@ interface SearchProduct { completeSpecifications?: CompleteSpecification[] skuSpecifications?: SkuSpecification[] specificationGroups?: SpecificationGroup[] - properties?: { name: string; values: string[] }[] + properties?: { + originalName?: string; name: string; values: string[] +}[] } interface SearchItem { @@ -220,7 +222,7 @@ interface CommertialOffer { }[] GetInfoErrorMessage: any | null CacheVersionUsedToCallCheckout: string - // Supports the Intelligent Search API which + // Supports the Intelligent Search API which // uses the same name from the simulation discountHighlights: any[] } diff --git a/node/utils/attributes.test.ts b/node/utils/attributes.test.ts deleted file mode 100644 index 39f8da3a..00000000 --- a/node/utils/attributes.test.ts +++ /dev/null @@ -1,294 +0,0 @@ -import { attributesToFilters, buildHref } from './attributes' - -describe('attributesToFilters', () => { - it('should convert a text attribute correctly', () => { - expect( - attributesToFilters({ - total: 108, - attributes: [ - { - visible: true, - values: [ - { - id: undefined, - count: 108, - active: false, - key: 'lions-pride', - label: 'Lions Pride', - }, - ], - key: 'brand', - label: 'Brand', - type: 'text' as 'text', - }, - ], - breadcrumb: [], - }) - ).toEqual([ - { - hidden: false, - name: 'Brand', - type: 'TEXT', - values: [ - { - href: 'lions-pride?map=brand', - id: undefined, - key: 'brand', - name: 'Lions Pride', - quantity: 108, - selected: false, - value: 'lions-pride', - }, - ], - }, - ]) - }) - - it('should convert price attribute correctly', () => { - expect( - attributesToFilters({ - total: 367, - attributes: [ - { - visible: true, - values: [ - { - count: 180, - active: false, - from: '*', - to: '30', - }, - { - count: 90, - active: false, - from: '30', - to: '50', - }, - { - count: 97, - active: false, - from: '50', - to: '*', - }, - ], - active: false, - key: 'price', - label: 'Price', - type: 'number' as 'number', - minValue: 10, - maxValue: 100, - }, - ], - breadcrumb: [], - }) - ).toEqual([ - { - values: [ - { - quantity: 180, - name: '', - key: 'price', - selected: false, - range: { from: 10, to: 30 }, - }, - { - quantity: 90, - name: '', - key: 'price', - selected: false, - range: { from: 30, to: 50 }, - }, - { - quantity: 97, - name: '', - key: 'price', - selected: false, - range: { from: 50, to: 100 }, - }, - ], - type: 'PRICERANGE', - name: 'Price', - hidden: false, - }, - ]) - }) - - it('should convert number attributes correctly', () => { - expect( - attributesToFilters({ - total: 13, - attributes: [ - { - visible: true, - values: [ - { - count: 13, - active: false, - from: '0', - to: '*', - }, - ], - active: false, - key: 'polegadas', - label: 'Polegadas', - type: 'number' as 'number', - minValue: 0, - maxValue: 0, - }, - ], - breadcrumb: [], - }) - ).toEqual([ - { - hidden: false, - name: 'Polegadas', - type: 'TEXT', - values: [ - { - key: 'polegadas', - name: '0 - 0', - quantity: 13, - selected: false, - value: '0:0', - range: { - from: 0, - to: 0, - }, - }, - ], - }, - ]) - }) - - it('should convert not selected location attributes correctly', () => { - expect( - attributesToFilters({ - total: 124, - attributes: [ - { - visible: true, - values: [ - { - count: 71, - active: false, - from: '0', - to: '5505580', - }, - { - count: 53, - active: false, - from: '0', - to: '4477620', - }, - ], - active: false, - key: 'location', - label: 'Location', - type: 'location' as 'location', - minValue: 2421698.143072009, - maxValue: 6533501.777013255, - }, - ], - breadcrumb: [], - }) - ).toEqual([ - { - hidden: false, - name: 'Location', - type: 'TEXT', - values: [ - { - key: 'location', - name: '0 - 5505580', - quantity: 71, - selected: false, - value: 'l:0:5505580', - range: { - from: 0, - to: 5505580, - }, - }, - { - key: 'location', - name: '0 - 4477620', - quantity: 53, - selected: false, - value: 'l:0:4477620', - range: { - from: 0, - to: 4477620, - }, - }, - ], - }, - ]) - }) - - it('should convert selected location attributes correctly', () => { - expect( - attributesToFilters({ - total: 124, - attributes: [ - { - visible: true, - values: [ - { - count: 71, - active: false, - from: '0', - to: '5505580', - }, - { - count: 53, - active: false, - from: '0', - to: '4477620', - }, - ], - active: true, - activeFrom: '0', - activeTo: '3605120', - key: 'location', - label: 'Location', - type: 'location' as 'location', - minValue: 2421698.143072009, - maxValue: 6533501.777013255, - }, - ], - breadcrumb: [], - }) - ).toEqual([ - { - hidden: false, - name: 'Location', - type: 'TEXT', - values: [ - { - key: 'location', - name: '0 - 3605120', - quantity: 124, - selected: true, - value: 'l:0:3605120', - range: { - from: 2421698.143072009, - to: 6533501.777013255, - }, - }, - ], - }, - ]) - }) -}) - -describe('buildHref', () => { - it('should create an href from nothing', () => { - expect(buildHref('', '', '')).toEqual('') - expect(buildHref('', 'hello', 'world')).toEqual('world?map=hello') - }) - - it('should create an href from existing base', () => { - expect(buildHref('hello?map=world', 'ola', 'mundo')).toEqual( - 'hello/mundo?map=world,ola' - ) - }) -}) diff --git a/node/yarn.lock b/node/yarn.lock index dc79d503..81e9c231 100644 --- a/node/yarn.lock +++ b/node/yarn.lock @@ -4684,7 +4684,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"stats-lite@github:vtex/node-stats-lite#dist": +stats-lite@vtex/node-stats-lite#dist: version "2.2.0" resolved "https://codeload.github.com/vtex/node-stats-lite/tar.gz/1b0d39cc41ef7aaecfd541191f877887a2044797" dependencies: