Skip to content

Commit

Permalink
Add showSponsored as api configuration on faststore.config.js to Inte…
Browse files Browse the repository at this point in the history
…lligentSearch integration (#2438)

## What's the purpose of this pull request?

This PR adds support for showing sponsored products when using
Intelligent Search. FastShop and probably more retailers are interested
in our ad solution (VTEX Ad Network). This PR intends to create a
frictionless integration between FastStore and VTEX Ad Network.

## How it works?

The store will change the showSponsored configuration on the
feaststore.config.js file to allow intelligent search to return
sponsored products. This will only happen if the store is a active
publisher on VTEX Ad Network.

On faststore.config.js
```yaml
 // Platform specific configs for API
  API: {
    ...
    showSponsored: true,
  }
```

## How to test it?

* Change the `showSponsored` attribute to `true` on faststore.config.js
* Verify if the store is an active publisher on VTEX Ad Network
* Create a campaign to be delivered to the store with a valid product

Check the starter page with the `showSponsored` flag to true and no
label: [Starter With
Ads](https://starter-5q5m6v7l7-faststore.vercel.app/office) - related
[PR](vtex-sites/starter.store#540):

Example with no label:

![image](https://github.com/user-attachments/assets/119baa57-a5b8-4b7f-99c1-954aaae30e07)

Perfomance for category page:

![image](https://github.com/user-attachments/assets/714db14c-a3b4-435c-9acd-5c34f43d37b6)

![image](https://github.com/user-attachments/assets/e9d64b9d-aba5-4987-8477-21ebd6f0ce08)

Performance for full text search page:

![image](https://github.com/user-attachments/assets/de06dc1a-c2a5-47ec-814b-5ef6dada863c)

![image](https://github.com/user-attachments/assets/ce2c910f-6b46-48d2-91a6-fbe6fd62b0d6)


### Starters Deploy Preview


## References

* [VTEX Ad
Network](https://developers.vtex.com/docs/api-reference/vtex-ad-network-api#get-/sponsored_products/-facets-)
* [Intelligent Search
API](https://developers.vtex.com/docs/api-reference/intelligent-search-api#get-/product_search/-facets-)

---------

Co-authored-by: Pedro Soares <pedro.soares@vtex.com>
  • Loading branch information
fltiago and pedromtec authored Oct 1, 2024
1 parent 944909c commit cc3cc88
Show file tree
Hide file tree
Showing 19 changed files with 116 additions and 11 deletions.
1 change: 1 addition & 0 deletions packages/api/local/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const apiOptions = {
locale: 'en-US',
environment: 'vtexcommercestable',
channel: '{"salesChannel":"1"}',
showSponsored: false,
} as Options

const graphQLContext = getContextFactory(apiOptions)
Expand Down
2 changes: 1 addition & 1 deletion packages/api/mocks/AllProductsQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/mocks/ProductQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/mocks/RedirectQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 2 additions & 2 deletions packages/api/mocks/SearchQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand Down
17 changes: 17 additions & 0 deletions packages/api/src/__generated__/schema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions packages/api/src/platforms/vtex/clients/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -49,7 +50,7 @@ export const isFacetBoolean = (
): facet is Facet<FacetValueBoolean> => 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`
Expand Down Expand Up @@ -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('/')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/platforms/vtex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface Options {
locale: string
hideUnavailableItems: boolean
simulationBehavior?: 'default' | 'skip' | 'only1P'
showSponsored: boolean
incrementAddress: boolean
flags?: FeatureFlags
}
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/platforms/vtex/resolvers/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,4 +156,5 @@ export const StoreProduct: Record<string, Resolver<Root>> & {
]
},
releaseDate: ({ isVariantOf: { releaseDate } }) => releaseDate ?? '',
advertisement: ({ isVariantOf: { advertisement } }) => advertisement,
}
25 changes: 25 additions & 0 deletions packages/api/src/typeDefs/advertisement.graphql
Original file line number Diff line number Diff line change
@@ -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!
}
4 changes: 4 additions & 0 deletions packages/api/src/typeDefs/product.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ type StoreProduct {
Sku Unit Multiplier
"""
unitMultiplier: Float
"""
Advertisement information about the product.
"""
advertisement: Advertisement
}

"""
Expand Down
1 change: 1 addition & 0 deletions packages/api/test/queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const apiOptions = {
subDomainPrefix: ['www'],
hideUnavailableItems: false,
simulationBehavior: 'skip',
showSponsored: false,
incrementAddress: false,
flags: {
enableOrderFormSync: true,
Expand Down
1 change: 1 addition & 0 deletions packages/api/test/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ beforeAll(async () => {
subDomainPrefix: ['www'],
hideUnavailableItems: false,
incrementAddress: false,
showSponsored: false,
flags: {
enableOrderFormSync: true,
},
Expand Down
4 changes: 2 additions & 2 deletions packages/core/@generated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
27 changes: 25 additions & 2 deletions packages/core/@generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@ export type Address = {
street: Maybe<Scalars['String']['output']>
}

/** 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<Scalars['String']['output']>
Expand Down Expand Up @@ -901,6 +915,8 @@ export type StorePerson = {
export type StoreProduct = {
/** Array of additional properties. */
additionalProperty: Array<StorePropertyValue>
/** Advertisement information about the product. */
advertisement: Maybe<Advertisement>
/** Aggregate ratings data. */
aggregateRating: StoreAggregateRating
/** Product brand. */
Expand Down Expand Up @@ -1123,6 +1139,7 @@ export type ProductSummary_ProductFragment = {
value: any
valueReference: any
}>
advertisement: { adId: string; adResponseId: string } | null
}

type Filter_Facets_StoreFacetBoolean_Fragment = {
Expand Down Expand Up @@ -1433,6 +1450,7 @@ export type ClientManyProductsQueryQuery = {
value: any
valueReference: any
}>
advertisement: { adId: string; adResponseId: string } | null
}
}>
}
Expand Down Expand Up @@ -1560,6 +1578,7 @@ export type ClientSearchSuggestionsQueryQuery = {
value: any
valueReference: any
}>
advertisement: { adId: string; adResponseId: string } | null
}>
}
products: { pageInfo: { totalCount: number } }
Expand Down Expand Up @@ -1690,6 +1709,10 @@ export const ProductSummary_ProductFragmentDoc = new TypedDocumentString(
value
valueReference
}
advertisement {
adId
adResponseId
}
}
`,
{ fragmentName: 'ProductSummary_product' }
Expand Down Expand Up @@ -2051,7 +2074,7 @@ export const SubscribeToNewsletterDocument = {
export const ClientManyProductsQueryDocument = {
__meta__: {
operationName: 'ClientManyProductsQuery',
operationHash: '99012563e9885c3b27a716ca212a2c317e7ec12f',
operationHash: 'ad2eb78cfccb9dbd5a9f2d1e150cc70fea5da99a',
},
} as unknown as TypedDocumentString<
ClientManyProductsQueryQuery,
Expand All @@ -2078,7 +2101,7 @@ export const ClientProductQueryDocument = {
export const ClientSearchSuggestionsQueryDocument = {
__meta__: {
operationName: 'ClientSearchSuggestionsQuery',
operationHash: '71809c86cb940861f01bcc57dbaf57e6f41cb378',
operationHash: '4d9f934764d8578aea08673b8ba57e8bf738f534',
},
} as unknown as TypedDocumentString<
ClientSearchSuggestionsQueryQuery,
Expand Down
1 change: 1 addition & 0 deletions packages/core/faststore.config.default.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
subDomainPrefix: ['www'],
environment: 'vtexcommercestable',
hideUnavailableItems: false,
showSponsored: false,
incrementAddress: true,
},

Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/components/product/ProductCard/ProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function ProductCard({
sku,
isVariantOf: { name },
image: [img],
advertisement,
offers: {
lowPrice,
lowPriceWithTaxes,
Expand Down Expand Up @@ -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 (
<UIProductCard
outOfStock={outOfStock}
bordered={bordered}
variant={variant}
data-fs-product-card-sku={sku}
{...advertisementDataAttributes}
{...otherProps}
>
<UIProductCardImage aspectRatio={aspectRatio}>
Expand Down Expand Up @@ -189,6 +199,11 @@ export const fragment = gql(`
value
valueReference
}
advertisement {
adId
adResponseId
}
}
`)

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/server/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>)
.simulationBehavior,
incrementAddress: storeConfig.api.incrementAddress,
Expand Down

0 comments on commit cc3cc88

Please sign in to comment.