Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/sort selected #201

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"deno.lint": true,
"deno.unstable": true,
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
Expand Down
11 changes: 8 additions & 3 deletions commerce/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
/** Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file. */
/**
* Used at the top-level node to indicate the context for the JSON-LD objects used. The context provided in this type is compatible with the keys and URLs in the rest of this generated file.
*
* @format
*/

export declare type WithContext<T extends Things> = T & {
"@context": "https://schema.org";
};
Expand Down Expand Up @@ -129,8 +134,7 @@ export interface PriceSpecification extends Omit<Thing, "@type"> {
priceCurrency?: string;
}

export interface UnitPriceSpecification
extends Omit<PriceSpecification, "@type"> {
export interface UnitPriceSpecification extends Omit<PriceSpecification, "@type"> {
"@type": "UnitPriceSpecification";
/** Identifies a price component (for example, a line item on an invoice), part of the total price for an offer. */
priceComponentType?: PriceComponentTypeEnumeration;
Expand Down Expand Up @@ -386,6 +390,7 @@ export interface ProductListingPage {
previousPage: string | undefined;
records?: number | undefined;
recordPerPage?: number | undefined;
sort?: string;
};
sortOptions: SortOption[];
seo?: Seo | null;
Expand Down
168 changes: 68 additions & 100 deletions packs/vtex/loaders/intelligentSearch/productListingPage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/** @format */

import type { ProductListingPage } from "deco-sites/std/commerce/types.ts";
import type { Context } from "deco-sites/std/packs/vtex/accounts/vtex.ts";
import type {
Expand All @@ -10,29 +12,12 @@ import type {
SelectedFacet,
Sort,
} from "deco-sites/std/packs/vtex/types.ts";
import {
toPath,
withDefaultFacets,
withDefaultParams,
} from "deco-sites/std/packs/vtex/utils/intelligentSearch.ts";
import {
pageTypesFromPathname,
pageTypesToBreadcrumbList,
pageTypesToSeo,
} from "deco-sites/std/packs/vtex/utils/legacy.ts";
import { toPath, withDefaultFacets, withDefaultParams } from "deco-sites/std/packs/vtex/utils/intelligentSearch.ts";
import { pageTypesFromPathname, pageTypesToBreadcrumbList, pageTypesToSeo } from "deco-sites/std/packs/vtex/utils/legacy.ts";
import { paths } from "deco-sites/std/packs/vtex/utils/paths.ts";
import {
getSegment,
setSegment,
withSegmentCookie,
} from "deco-sites/std/packs/vtex/utils/segment.ts";
import { getSegment, setSegment, withSegmentCookie } from "deco-sites/std/packs/vtex/utils/segment.ts";
import { slugify } from "deco-sites/std/packs/vtex/utils/slugify.ts";
import {
filtersFromURL,
mergeFacets,
toFilter,
toProduct,
} from "deco-sites/std/packs/vtex/utils/transform.ts";
import { filtersFromURL, mergeFacets, toFilter, toProduct } from "deco-sites/std/packs/vtex/utils/transform.ts";
import { fetchAPI } from "deco-sites/std/utils/fetch.ts";
import { parseRange } from "deco-sites/std/utils/filters.ts";
import { withIsSimilarTo } from "../../utils/similars.ts";
Expand Down Expand Up @@ -67,9 +52,7 @@ const LEGACY_TO_IS: Record<string, Sort> = {
OrderByBestDiscountDESC: "discount:desc",
};

const mapLabelledFuzzyToFuzzy = (
labelledFuzzy?: LabelledFuzzy,
): Fuzzy | undefined => {
const mapLabelledFuzzyToFuzzy = (labelledFuzzy?: LabelledFuzzy): Fuzzy | undefined => {
switch (labelledFuzzy) {
case "automatic":
return "auto";
Expand Down Expand Up @@ -128,18 +111,13 @@ export interface Props {
}

// TODO (mcandeia) investigating bugs related to returning the same set of products but different queries.
const _singleFlightKey = (
props: Props,
{ request }: { request: Request },
) => {
const _singleFlightKey = (props: Props, { request }: { request: Request }) => {
const url = new URL(request.url);
const { query, count, sort, page, selectedFacets, fuzzy } = searchArgsOf(
props,
url,
);
return `${query}${count}${sort}${page}${fuzzy}${
selectedFacets.map((f) => `${f.key}${f.value}`).sort().join("")
}`;
const { query, count, sort, page, selectedFacets, fuzzy } = searchArgsOf(props, url);
return `${query}${count}${sort}${page}${fuzzy}${selectedFacets
.map((f) => `${f.key}${f.value}`)
.sort()
.join("")}`;
};

const searchArgsOf = (props: Props, url: URL) => {
Expand All @@ -148,20 +126,13 @@ const searchArgsOf = (props: Props, url: URL) => {
const query = props.query ?? url.searchParams.get("q") ?? "";
const currentPageoffset = props.pageOffset ?? 1;
const page = Math.min(
url.searchParams.get("page")
? Number(url.searchParams.get("page")) - currentPageoffset
: 0,
VTEX_MAX_PAGES - currentPageoffset,
url.searchParams.get("page") ? Number(url.searchParams.get("page")) - currentPageoffset : 0,
VTEX_MAX_PAGES - currentPageoffset
);
const sort = url.searchParams.get("sort") as Sort ??
LEGACY_TO_IS[url.searchParams.get("O") ?? ""] ?? props.sort ??
sortOptions[0].value;
const selectedFacets = mergeFacets(
props.selectedFacets ?? [],
filtersFromURL(url),
);
const fuzzy = mapLabelledFuzzyToFuzzy(props.fuzzy) ??
url.searchParams.get("fuzzy") as Fuzzy;
const sort =
(url.searchParams.get("sort") as Sort) ?? LEGACY_TO_IS[url.searchParams.get("O") ?? ""] ?? props.sort ?? sortOptions[0].value;
const selectedFacets = mergeFacets(props.selectedFacets ?? [], filtersFromURL(url));
const fuzzy = mapLabelledFuzzyToFuzzy(props.fuzzy) ?? (url.searchParams.get("fuzzy") as Fuzzy);

return {
query,
Expand Down Expand Up @@ -201,10 +172,13 @@ const filtersFromPathname = (pages: PageType[]) =>
return;
}

return key && page.name && {
key,
value: slugify(page.name),
};
return (
key &&
page.name && {
key,
value: slugify(page.name),
}
);
})
.filter((facet): facet is { key: string; value: string } => Boolean(facet));

Expand Down Expand Up @@ -237,85 +211,78 @@ const selectPriceFacet = (facets: Facet[], selectedFacets: SelectedFacet[]) => {
* @title VTEX Intelligent Search - Product Listing page
* @description Returns data ready for search pages like category,brand pages
*/
const loader = async (
props: Props,
req: Request,
ctx: Context,
): Promise<ProductListingPage | null> => {
const loader = async (props: Props, req: Request, ctx: Context): Promise<ProductListingPage | null> => {
const { url: baseUrl } = req;
const { configVTEX: config } = ctx;
const url = new URL(baseUrl);
const vtex = paths(config!);
const segment = getSegment(req);
const search = vtex.api.io._v.api["intelligent-search"];
const currentPageoffset = props.pageOffset ?? 1;
const {
selectedFacets: baseSelectedFacets,
page,
...args
} = searchArgsOf(props, url);
const { selectedFacets: baseSelectedFacets, page, ...args } = searchArgsOf(props, url);
const pageTypesPromise = pageTypesFromPathname(url.pathname, ctx);
const selectedFacets = baseSelectedFacets.length === 0
? filtersFromPathname(await pageTypesPromise)
: baseSelectedFacets;
const selectedFacets = baseSelectedFacets.length === 0 ? filtersFromPathname(await pageTypesPromise) : baseSelectedFacets;
const sort = props.sort;

const selected = withDefaultFacets(selectedFacets, ctx);
const fselected = selected.filter((f) => f.key !== "price");
const params = withDefaultParams({ ...args, page }, ctx);
// search products on VTEX. Feel free to change any of these parameters
const [productsResult, facetsResult] = await Promise.all([
fetchAPI<ProductSearchResult>(
`${search.product_search.facets(toPath(selected))}?${params}`,
{ withProxyCache: true, headers: withSegmentCookie(segment) },
),
fetchAPI<FacetSearchResult>(
`${search.facets.facets(toPath(fselected))}?${params}`,
{ withProxyCache: true, headers: withSegmentCookie(segment) },
),
fetchAPI<ProductSearchResult>(`${search.product_search.facets(toPath(selected))}?${params}`, {
withProxyCache: true,
headers: withSegmentCookie(segment),
}),
fetchAPI<FacetSearchResult>(`${search.facets.facets(toPath(fselected))}?${params}`, {
withProxyCache: true,
headers: withSegmentCookie(segment),
}),
]);

/** Intelligent search API analytics. Fire and forget 🔫 */
const fullTextTerm = params.get("query");
if (fullTextTerm) {
ctx.invoke("deco-sites/std/actions/vtex/analytics/sendEvent.ts", {
type: "session.ping",
}).then(() =>
ctx.invoke("deco-sites/std/actions/vtex/analytics/sendEvent.ts", {
type: "search.query",
text: fullTextTerm,
misspelled: productsResult.correction?.misspelled ?? false,
match: productsResult.recordsFiltered,
operator: productsResult.operator,
locale: config?.defaultLocale,
ctx
.invoke("deco-sites/std/actions/vtex/analytics/sendEvent.ts", {
type: "session.ping",
})
).catch(console.error);
.then(() =>
ctx.invoke("deco-sites/std/actions/vtex/analytics/sendEvent.ts", {
type: "search.query",
text: fullTextTerm,
misspelled: productsResult.correction?.misspelled ?? false,
match: productsResult.recordsFiltered,
operator: productsResult.operator,
locale: config?.defaultLocale,
})
)
.catch(console.error);
}

const { products: vtexProducts, pagination, recordsFiltered } =
productsResult;
const { products: vtexProducts, pagination, recordsFiltered } = productsResult;
const facets = selectPriceFacet(facetsResult.facets, selectedFacets);

// Transform VTEX product format into schema.org's compatible format
// If a property is missing from the final `products` array you can add
// it in here
const products = await Promise.all(
vtexProducts.map((p) =>
toProduct(p, p.items[0], 0, {
baseUrl: baseUrl,
priceCurrency: config!.defaultPriceCurrency,
})
).map((product) =>
props.similars
? withIsSimilarTo(ctx, product, {
hideUnavailableItems: props.hideUnavailableItems,
vtexProducts
.map((p) =>
toProduct(p, p.items[0], 0, {
baseUrl: baseUrl,
priceCurrency: config!.defaultPriceCurrency,
})
: product
),
)
.map((product) =>
props.similars
? withIsSimilarTo(ctx, product, {
hideUnavailableItems: props.hideUnavailableItems,
})
: product
)
);

const filters = facets.filter((f) => !f.hidden).map(
toFilter(selectedFacets, args.query),
);
const filters = facets.filter((f) => !f.hidden).map(toFilter(selectedFacets, args.query));
const pageTypes = await pageTypesPromise;
const itemListElement = pageTypesToBreadcrumbList(pageTypes, baseUrl);

Expand Down Expand Up @@ -349,6 +316,7 @@ const loader = async (
currentPage: page + currentPageoffset,
records: recordsFiltered,
recordPerPage: pagination.perPage,
sort,
},
sortOptions,
seo: pageTypesToSeo(pageTypes, req),
Expand Down
Loading