From d9b0ace9e3e66a9b87317149ac0e43e6750d5502 Mon Sep 17 00:00:00 2001
From: Sanz <50715158+Git-the-Sanz@users.noreply.github.com>
Date: Tue, 9 May 2023 18:08:02 +0200
Subject: [PATCH] feat: implement redirect query (#1746)
## What's the purpose of this pull request?
This PR adds a query to solve redirects when performing a search.
In case of VTEX platform, this is solved by the IS api.
## How it works?
When performing a search if a term or facet has a redirect, the value of
the `redirect` field should be an URL; this can later be used to
redirect the user to an internal or external URL.
From VTEX admin:
It get solved as:
## How to test it?
- Run the `@faststore/api` package individually.
- Browse [localhost/graphql](http://localhost:4000/graphql)
- Run the redirect query with a term that is active in
`/admin/search/v4/redirects/`
- For storeframework, you can use the term `tech`
PR related at vtex-sites:
https://github.com/vtex-sites/nextjs.store/pull/382
PR related 2.x: https://github.com/vtex/faststore/pull/1749
---------
Co-authored-by: beatrizmaselli
---
packages/api/mocks/RedirectQuery.ts | 40 +++++++++++++++++++
packages/api/src/__generated__/schema.ts | 18 +++++++++
.../api/src/platforms/vtex/resolvers/query.ts | 28 +++++++++++--
packages/api/src/typeDefs/query.graphql | 26 ++++++++++++
.../test/__snapshots__/queries.test.ts.snap | 10 +++++
packages/api/test/queries.test.ts | 25 ++++++++++++
packages/api/test/schema.test.ts | 1 +
7 files changed, 145 insertions(+), 3 deletions(-)
create mode 100644 packages/api/mocks/RedirectQuery.ts
diff --git a/packages/api/mocks/RedirectQuery.ts b/packages/api/mocks/RedirectQuery.ts
new file mode 100644
index 0000000000..df18d8a860
--- /dev/null
+++ b/packages/api/mocks/RedirectQuery.ts
@@ -0,0 +1,40 @@
+export const RedirectQueryTermTech = `query RedirectSearch {
+ redirect(term: "tech") {
+ url
+ }
+ }
+ `
+
+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',
+ init: undefined,
+ result: {
+ products: [],
+ recordsFiltered: 0,
+ fuzzy: 'auto',
+ operator: 'and',
+ redirect: '/technology',
+ translated: false,
+ pagination: {
+ count: 1,
+ current: {
+ index: 0,
+ },
+ before: [],
+ after: [],
+ perPage: 0,
+ next: {
+ index: 0,
+ },
+ previous: {
+ index: 0,
+ },
+ first: {
+ index: 0,
+ },
+ last: {
+ index: 0,
+ },
+ },
+ },
+}
diff --git a/packages/api/src/__generated__/schema.ts b/packages/api/src/__generated__/schema.ts
index 58217a7527..fd514ce5de 100644
--- a/packages/api/src/__generated__/schema.ts
+++ b/packages/api/src/__generated__/schema.ts
@@ -393,6 +393,8 @@ export type Query = {
collection: StoreCollection;
/** Returns the details of a product based on the specified locator. */
product: StoreProduct;
+ /** Returns if there's a redirect for a search. */
+ redirect?: Maybe;
/** Returns the result of a product, facet, or suggestion search. */
search: StoreSearchResult;
/** Returns information about shipping simulation. */
@@ -422,6 +424,12 @@ export type QueryProductArgs = {
};
+export type QueryRedirectArgs = {
+ selectedFacets?: Maybe>;
+ term?: Maybe;
+};
+
+
export type QuerySearchArgs = {
after?: Maybe;
first: Scalars['Int'];
@@ -896,6 +904,16 @@ export type StorePropertyValue = {
valueReference: Scalars['String'];
};
+/**
+ * Redirect informations, including url returned by the query.
+ * https://schema.org/Thing
+ */
+export type StoreRedirect = {
+ __typename?: 'StoreRedirect';
+ /** URL to redirect */
+ url?: Maybe;
+};
+
/** Information of a given review. */
export type StoreReview = {
__typename?: 'StoreReview';
diff --git a/packages/api/src/platforms/vtex/resolvers/query.ts b/packages/api/src/platforms/vtex/resolvers/query.ts
index 7828116dd2..a2caf44a6a 100644
--- a/packages/api/src/platforms/vtex/resolvers/query.ts
+++ b/packages/api/src/platforms/vtex/resolvers/query.ts
@@ -19,6 +19,7 @@ import type {
QueryProductArgs,
QuerySearchArgs,
QueryShippingArgs,
+ QueryRedirectArgs
} from "../../../__generated__/schema"
import type { CategoryTree } from "../clients/commerce/types/CategoryTree"
import type { Context } from "../index"
@@ -142,9 +143,8 @@ export const Query = {
productId: crossSelling.value,
})
- query = `product:${
- products.map((x) => x.productId).slice(0, first).join(";")
- }`
+ query = `product:${products.map((x) => x.productId).slice(0, first).join(";")
+ }`
}
const after = maybeAfter ? Number(maybeAfter) : 0
@@ -273,4 +273,26 @@ export const Query = {
address,
}
},
+ redirect: async (
+ _: unknown,
+ { term, selectedFacets }: QueryRedirectArgs,
+ ctx: Context
+ ) => {
+ // Currently the search redirection can be done through a search term or filter (facet) so we limit the redirect query to always have one of these values otherwise we do not execute it.
+ // https://help.vtex.com/en/tracks/vtex-intelligent-search--19wrbB7nEQcmwzDPl1l4Cb/4Gd2wLQFbCwTsh8RUDwSoL?&utm_source=autocomplete
+ if (!term && (!selectedFacets || !selectedFacets.length)) {
+ return null
+ }
+
+ const { redirect } = await ctx.clients.search.products({
+ page: 1,
+ count: 1,
+ query: term ?? undefined,
+ selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [],
+ })
+
+ return {
+ url: redirect
+ }
+ },
}
diff --git a/packages/api/src/typeDefs/query.graphql b/packages/api/src/typeDefs/query.graphql
index 82199df676..ed026c2b24 100644
--- a/packages/api/src/typeDefs/query.graphql
+++ b/packages/api/src/typeDefs/query.graphql
@@ -282,4 +282,30 @@ type Query {
country: String!
): ShippingData
@cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
+
+ """
+ Returns if there's a redirect for a search.
+ """
+ redirect(
+ """
+ Search term.
+ """
+ term: String
+ """
+ Array of selected search facets.
+ """
+ selectedFacets: [IStoreSelectedFacet!]
+ ): StoreRedirect
+ @cacheControl(scope: "public", sMaxAge: 120, staleWhileRevalidate: 3600)
+}
+
+"""
+Redirect informations, including url returned by the query.
+https://schema.org/Thing
+"""
+type StoreRedirect {
+ """
+ URL to redirect
+ """
+ url: String
}
diff --git a/packages/api/test/__snapshots__/queries.test.ts.snap b/packages/api/test/__snapshots__/queries.test.ts.snap
index 9e6a84a3ef..490e38c701 100644
--- a/packages/api/test/__snapshots__/queries.test.ts.snap
+++ b/packages/api/test/__snapshots__/queries.test.ts.snap
@@ -1167,3 +1167,13 @@ Object {
},
}
`;
+
+exports[`\`redirect\` query 1`] = `
+Object {
+ "data": Object {
+ "redirect": Object {
+ "url": "/technology",
+ },
+ },
+}
+`;
\ No newline at end of file
diff --git a/packages/api/test/queries.test.ts b/packages/api/test/queries.test.ts
index 30ac9228da..7103983200 100644
--- a/packages/api/test/queries.test.ts
+++ b/packages/api/test/queries.test.ts
@@ -34,6 +34,10 @@ import {
addressFetch,
ShippingSimulationQueryResult,
} from '../mocks/ShippingQuery'
+import {
+ RedirectQueryTermTech,
+ redirectTermTechFetch,
+} from '../mocks/RedirectQuery'
const apiOptions = {
platform: 'vtex',
@@ -236,3 +240,24 @@ test('`shipping` query', async () => {
expect(response).toMatchSnapshot()
})
+
+test('`redirect` query', async () => {
+ const fetchAPICalls = [redirectTermTechFetch]
+
+ mockedFetch.mockImplementation((info, init) =>
+ pickFetchAPICallResult(info, init, fetchAPICalls)
+ )
+
+ const response = await run(RedirectQueryTermTech)
+
+ expect(mockedFetch).toHaveBeenCalledTimes(1)
+
+ fetchAPICalls.forEach((fetchAPICall) => {
+ expect(mockedFetch).toHaveBeenCalledWith(
+ fetchAPICall.info,
+ fetchAPICall.init
+ )
+ })
+
+ expect(response).toMatchSnapshot()
+})
diff --git a/packages/api/test/schema.test.ts b/packages/api/test/schema.test.ts
index 113f4f9c24..08ca69facb 100644
--- a/packages/api/test/schema.test.ts
+++ b/packages/api/test/schema.test.ts
@@ -65,6 +65,7 @@ const QUERIES = [
'allProducts',
'allCollections',
'shipping',
+ 'redirect',
]
const MUTATIONS = ['validateCart', 'validateSession', 'subscribeToNewsletter']