diff --git a/packages/api/local/server.ts b/packages/api/local/server.ts index a545b5e8e7..cfbd5e5985 100644 --- a/packages/api/local/server.ts +++ b/packages/api/local/server.ts @@ -12,6 +12,7 @@ const apiOptions = { locale: 'en-US', environment: 'vtexcommercestable', channel: '{"salesChannel":"1"}', + showSponsored: false, } as Options const graphQLContext = getContextFactory(apiOptions) diff --git a/packages/api/mocks/AllProductsQuery.ts b/packages/api/mocks/AllProductsQuery.ts index 3e23e96b9d..2ad640de6f 100644 --- a/packages/api/mocks/AllProductsQuery.ts +++ b/packages/api/mocks/AllProductsQuery.ts @@ -78,7 +78,7 @@ export const AllProductsQueryFirst5 = `query AllProducts { ` export const productSearchPage1Count5Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { diff --git a/packages/api/mocks/ProductQuery.ts b/packages/api/mocks/ProductQuery.ts index b9b72a4cbc..7bdfa029d8 100644 --- a/packages/api/mocks/ProductQuery.ts +++ b/packages/api/mocks/ProductQuery.ts @@ -63,7 +63,7 @@ export const ProductByIdQuery = `query ProductQuery { ` export const productSearchFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A64953394&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A64953394&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { diff --git a/packages/api/mocks/RedirectQuery.ts b/packages/api/mocks/RedirectQuery.ts index 1a1b1f1c21..126e358a32 100644 --- a/packages/api/mocks/RedirectQuery.ts +++ b/packages/api/mocks/RedirectQuery.ts @@ -6,7 +6,7 @@ export const RedirectQueryTermTech = `query RedirectSearch { ` export const redirectTermTechFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=2&count=1&query=tech&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=2&count=1&query=tech&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { diff --git a/packages/api/mocks/SearchQuery.ts b/packages/api/mocks/SearchQuery.ts index 1d426b9c04..443d9ea6e9 100644 --- a/packages/api/mocks/SearchQuery.ts +++ b/packages/api/mocks/SearchQuery.ts @@ -101,7 +101,7 @@ export const SearchQueryFirst5Products = `query SearchQuery { }` export const productSearchCategory1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { @@ -1395,7 +1395,7 @@ export const productSearchCategory1Fetch = { } export const attributeSearchCategory1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/facets/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip', + info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/facets/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&fuzzy=auto&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false', init: undefined, options: { storeCookies: expect.any(Function) }, result: { diff --git a/packages/api/src/__generated__/schema.ts b/packages/api/src/__generated__/schema.ts index 61a55df546..bd53a4c46f 100644 --- a/packages/api/src/__generated__/schema.ts +++ b/packages/api/src/__generated__/schema.ts @@ -99,6 +99,21 @@ export type Address = { street?: Maybe; }; +/** Advertisement information about a specific product in a campaign */ +export type Advertisement = { + __typename?: 'Advertisement'; + /** Cost of the action, usually Cost Per Click. */ + actionCost: Scalars['Float']; + /** Advertiser ID of the product. */ + adId: Scalars['String']; + /** Advertiser Request ID. */ + adRequestId: Scalars['String']; + /** Advertiser Response ID. */ + adResponseId: Scalars['String']; + /** Campaign ID. */ + campaignId: Scalars['String']; +}; + export type AvailableDeliveryWindows = { __typename?: 'AvailableDeliveryWindows'; /** Available delivery window end date in UTC */ @@ -944,6 +959,8 @@ export type StoreProduct = { __typename?: 'StoreProduct'; /** Array of additional properties. */ additionalProperty: Array; + /** Advertisement information about the product. */ + advertisement?: Maybe; /** Aggregate ratings data. */ aggregateRating: StoreAggregateRating; /** Product brand. */ diff --git a/packages/api/src/platforms/vtex/clients/search/index.ts b/packages/api/src/platforms/vtex/clients/search/index.ts index e8331c9f2b..a64a383c82 100644 --- a/packages/api/src/platforms/vtex/clients/search/index.ts +++ b/packages/api/src/platforms/vtex/clients/search/index.ts @@ -27,12 +27,13 @@ export interface SearchArgs { query?: string page: number count: number - type: 'product_search' | 'facets' + type: 'product_search' | 'facets' | 'sponsored_products' sort?: Sort selectedFacets?: SelectedFacet[] fuzzy?: '0' | '1' | 'auto' hideUnavailableItems?: boolean showInvisibleItems?: boolean + showSponsored?: boolean } export interface ProductLocator { @@ -49,7 +50,7 @@ export const isFacetBoolean = ( ): facet is Facet => facet.type === 'TEXT' export const IntelligentSearch = ( - { account, environment, hideUnavailableItems, simulationBehavior }: Options, + { account, environment, hideUnavailableItems, simulationBehavior, showSponsored }: Options, ctx: Context ) => { const base = `https://${account}.${environment}.com.br/api/io` @@ -136,6 +137,10 @@ export const IntelligentSearch = ( params.append('simulationBehavior', simulationBehavior.toString()) } + if (showSponsored !== undefined) { + params.append('showSponsored', showSponsored.toString()) + } + const pathname = addDefaultFacets(selectedFacets) .map(({ key, value }) => `${key}/${value}`) .join('/') diff --git a/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts b/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts index 0c9347dfa5..201a37c467 100644 --- a/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts +++ b/packages/api/src/platforms/vtex/clients/search/types/ProductSearchResult.ts @@ -92,6 +92,7 @@ export interface Product { properties: Array<{ name: string; values: string[] }> selectedProperties: Array<{ key: string; value: string }> releaseDate: string + advertisement?: Advertisement } interface Image { @@ -216,6 +217,14 @@ interface SpecificationGroup { }> } +interface Advertisement { + adId: string + campaignId: string + actionCost: number + adRequestId: string + adResponseId: string +} + export interface Attribute { id: string name: string diff --git a/packages/api/src/platforms/vtex/index.ts b/packages/api/src/platforms/vtex/index.ts index ba26455b95..5129f1f3fd 100644 --- a/packages/api/src/platforms/vtex/index.ts +++ b/packages/api/src/platforms/vtex/index.ts @@ -37,6 +37,7 @@ export interface Options { locale: string hideUnavailableItems: boolean simulationBehavior?: 'default' | 'skip' | 'only1P' + showSponsored: boolean incrementAddress: boolean flags?: FeatureFlags } diff --git a/packages/api/src/platforms/vtex/resolvers/product.ts b/packages/api/src/platforms/vtex/resolvers/product.ts index db945e61f9..0569c7ae60 100644 --- a/packages/api/src/platforms/vtex/resolvers/product.ts +++ b/packages/api/src/platforms/vtex/resolvers/product.ts @@ -156,4 +156,5 @@ export const StoreProduct: Record> & { ] }, releaseDate: ({ isVariantOf: { releaseDate } }) => releaseDate ?? '', + advertisement: ({ isVariantOf: { advertisement } }) => advertisement, } diff --git a/packages/api/src/typeDefs/advertisement.graphql b/packages/api/src/typeDefs/advertisement.graphql new file mode 100644 index 0000000000..f99328903c --- /dev/null +++ b/packages/api/src/typeDefs/advertisement.graphql @@ -0,0 +1,25 @@ +""" +Advertisement information about a specific product in a campaign +""" +type Advertisement { + """ + Advertiser ID of the product. + """ + adId: String! + """ + Campaign ID. + """ + campaignId: String! + """ + Cost of the action, usually Cost Per Click. + """ + actionCost: Float! + """ + Advertiser Request ID. + """ + adRequestId: String! + """ + Advertiser Response ID. + """ + adResponseId: String! +} \ No newline at end of file diff --git a/packages/api/src/typeDefs/product.graphql b/packages/api/src/typeDefs/product.graphql index 1fb4613112..b1a7dfcb50 100644 --- a/packages/api/src/typeDefs/product.graphql +++ b/packages/api/src/typeDefs/product.graphql @@ -70,6 +70,10 @@ type StoreProduct { Sku Unit Multiplier """ unitMultiplier: Float + """ + Advertisement information about the product. + """ + advertisement: Advertisement } """ diff --git a/packages/api/test/queries.test.ts b/packages/api/test/queries.test.ts index 78e4f772b5..73c2b55174 100644 --- a/packages/api/test/queries.test.ts +++ b/packages/api/test/queries.test.ts @@ -49,6 +49,7 @@ const apiOptions = { subDomainPrefix: ['www'], hideUnavailableItems: false, simulationBehavior: 'skip', + showSponsored: false, incrementAddress: false, flags: { enableOrderFormSync: true, diff --git a/packages/api/test/schema.test.ts b/packages/api/test/schema.test.ts index 5217cbe5de..47fa97b71d 100644 --- a/packages/api/test/schema.test.ts +++ b/packages/api/test/schema.test.ts @@ -83,6 +83,7 @@ beforeAll(async () => { subDomainPrefix: ['www'], hideUnavailableItems: false, incrementAddress: false, + showSponsored: false, flags: { enableOrderFormSync: true, }, diff --git a/packages/core/@generated/gql.ts b/packages/core/@generated/gql.ts index 7364cd0d25..415bcf1c7e 100644 --- a/packages/core/@generated/gql.ts +++ b/packages/core/@generated/gql.ts @@ -12,7 +12,7 @@ import * as types from './graphql' * Therefore it is highly recommended to use the babel or swc plugin for production. */ const documents = { - '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n': + '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n advertisement {\n adId\n adResponseId\n }\n }\n': types.ProductSummary_ProductFragmentDoc, '\n fragment Filter_facets on StoreFacet {\n ... on StoreFacetRange {\n key\n label\n\n min {\n selected\n absolute\n }\n\n max {\n selected\n absolute\n }\n\n __typename\n }\n ... on StoreFacetBoolean {\n key\n label\n values {\n label\n value\n selected\n quantity\n }\n\n __typename\n }\n }\n': types.Filter_FacetsFragmentDoc, @@ -62,7 +62,7 @@ const documents = { * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function gql( - source: '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n' + source: '\n fragment ProductSummary_product on StoreProduct {\n id: productID\n slug\n sku\n brand {\n brandName: name\n }\n name\n gtin\n\n isVariantOf {\n productGroupID\n name\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n listPrice\n listPriceWithTaxes\n quantity\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n advertisement {\n adId\n adResponseId\n }\n }\n' ): typeof import('./graphql').ProductSummary_ProductFragmentDoc /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. diff --git a/packages/core/@generated/graphql.ts b/packages/core/@generated/graphql.ts index b3d289130b..f61acb82d9 100644 --- a/packages/core/@generated/graphql.ts +++ b/packages/core/@generated/graphql.ts @@ -116,6 +116,20 @@ export type Address = { street: Maybe } +/** Advertisement information about a specific product in a campaign */ +export type Advertisement = { + /** Cost of the action, usually Cost Per Click. */ + actionCost: Scalars['Float']['output'] + /** Advertiser ID of the product. */ + adId: Scalars['String']['output'] + /** Advertiser Request ID. */ + adRequestId: Scalars['String']['output'] + /** Advertiser Response ID. */ + adResponseId: Scalars['String']['output'] + /** Campaign ID. */ + campaignId: Scalars['String']['output'] +} + export type AvailableDeliveryWindows = { /** Available delivery window end date in UTC */ endDateUtc: Maybe @@ -901,6 +915,8 @@ export type StorePerson = { export type StoreProduct = { /** Array of additional properties. */ additionalProperty: Array + /** Advertisement information about the product. */ + advertisement: Maybe /** Aggregate ratings data. */ aggregateRating: StoreAggregateRating /** Product brand. */ @@ -1123,6 +1139,7 @@ export type ProductSummary_ProductFragment = { value: any valueReference: any }> + advertisement: { adId: string; adResponseId: string } | null } type Filter_Facets_StoreFacetBoolean_Fragment = { @@ -1433,6 +1450,7 @@ export type ClientManyProductsQueryQuery = { value: any valueReference: any }> + advertisement: { adId: string; adResponseId: string } | null } }> } @@ -1560,6 +1578,7 @@ export type ClientSearchSuggestionsQueryQuery = { value: any valueReference: any }> + advertisement: { adId: string; adResponseId: string } | null }> } products: { pageInfo: { totalCount: number } } @@ -1690,6 +1709,10 @@ export const ProductSummary_ProductFragmentDoc = new TypedDocumentString( value valueReference } + advertisement { + adId + adResponseId + } } `, { fragmentName: 'ProductSummary_product' } @@ -2051,7 +2074,7 @@ export const SubscribeToNewsletterDocument = { export const ClientManyProductsQueryDocument = { __meta__: { operationName: 'ClientManyProductsQuery', - operationHash: '99012563e9885c3b27a716ca212a2c317e7ec12f', + operationHash: 'ad2eb78cfccb9dbd5a9f2d1e150cc70fea5da99a', }, } as unknown as TypedDocumentString< ClientManyProductsQueryQuery, @@ -2078,7 +2101,7 @@ export const ClientProductQueryDocument = { export const ClientSearchSuggestionsQueryDocument = { __meta__: { operationName: 'ClientSearchSuggestionsQuery', - operationHash: '71809c86cb940861f01bcc57dbaf57e6f41cb378', + operationHash: '4d9f934764d8578aea08673b8ba57e8bf738f534', }, } as unknown as TypedDocumentString< ClientSearchSuggestionsQueryQuery, diff --git a/packages/core/faststore.config.default.js b/packages/core/faststore.config.default.js index 842bfad166..7e8e34eedd 100644 --- a/packages/core/faststore.config.default.js +++ b/packages/core/faststore.config.default.js @@ -19,6 +19,7 @@ module.exports = { subDomainPrefix: ['www'], environment: 'vtexcommercestable', hideUnavailableItems: false, + showSponsored: false, incrementAddress: true, }, diff --git a/packages/core/src/components/product/ProductCard/ProductCard.tsx b/packages/core/src/components/product/ProductCard/ProductCard.tsx index d9aa1e034e..86b079fffa 100644 --- a/packages/core/src/components/product/ProductCard/ProductCard.tsx +++ b/packages/core/src/components/product/ProductCard/ProductCard.tsx @@ -77,6 +77,7 @@ function ProductCard({ sku, isVariantOf: { name }, image: [img], + advertisement, offers: { lowPrice, lowPriceWithTaxes, @@ -106,12 +107,21 @@ function ProductCard({ const hasDiscount = spotPrice <= listPrice + const advertisementDataAttributes = advertisement + ? { + 'data-van-res-id': advertisement.adResponseId, + 'data-van-aid': advertisement.adId, + 'data-van-prod-name': name, + } + : {} + return ( @@ -189,6 +199,11 @@ export const fragment = gql(` value valueReference } + + advertisement { + adId + adResponseId + } } `) diff --git a/packages/core/src/server/options.ts b/packages/core/src/server/options.ts index b40e7e3654..c75ccab9f8 100644 --- a/packages/core/src/server/options.ts +++ b/packages/core/src/server/options.ts @@ -8,6 +8,7 @@ export const apiOptions: APIOptions = { environment: storeConfig.api.environment as APIOptions['environment'], subDomainPrefix: storeConfig.api.subDomainPrefix ?? ['www'], hideUnavailableItems: storeConfig.api.hideUnavailableItems, + showSponsored: storeConfig.api.showSponsored, simulationBehavior: (storeConfig.api as Record) .simulationBehavior, incrementAddress: storeConfig.api.incrementAddress,