Skip to content

Commit

Permalink
feat: Price range filter on PLP (#1364)
Browse files Browse the repository at this point in the history
  • Loading branch information
tlgimenes authored Jun 19, 2022
1 parent 7865d21 commit a4c3fa7
Show file tree
Hide file tree
Showing 24 changed files with 509 additions and 403 deletions.
5 changes: 3 additions & 2 deletions apps/docs/docs/reference/ui/molecules/PriceRange.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ function Component () {

return (
<PriceRange
max = "500"
min = "0"
max={500}
min={100}
formatter={formatter}
onEnd={(value) => window.alert(`Min: ${formatter(value.min)}, Max: ${formatter(value.max)}`)}
/>
)
}
Expand Down
34 changes: 26 additions & 8 deletions packages/api/src/__generated__/schema.ts

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

10 changes: 9 additions & 1 deletion packages/api/src/platforms/vtex/clients/search/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { fetchAPI } from '../fetch'
import type { IStoreSelectedFacet } from '../../../../__generated__/schema'
import type { Context, Options } from '../../index'
import type { SelectedFacet } from '../../utils/facets'
import type { FacetSearchResult } from './types/FacetSearchResult'
import type {
Facet,
FacetValueBoolean,
FacetSearchResult,
} from './types/FacetSearchResult'
import type {
ProductSearchResult,
Suggestion,
Expand Down Expand Up @@ -38,6 +42,10 @@ const POLICY_KEY = 'trade-policy'
const REGION_KEY = 'region-id'
const CHANNEL_KEYS = new Set([POLICY_KEY, REGION_KEY])

export const isFacetBoolean = (
facet: Facet
): facet is Facet<FacetValueBoolean> => facet.type === 'TEXT'

export const IntelligentSearch = (
{ account, environment, hideUnavailableItems }: Options,
ctx: Context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,28 @@ export interface FacetSearchResult {
breadcrumb: Breadcrumb
}

export interface Facet {
export interface Facet<T = FacetValueBoolean | FacetValueRange> {
type: FilterType
name: string
hidden: boolean
values: FacetValue[]
values: T[]
quantity?: number
key?: string
key: string
}

export interface FacetValue {
export interface FacetValueBoolean {
quantity: number
name: string
key: string
value: string
selected?: boolean
range?: {
selected: boolean
}

export interface FacetValueRange {
range: {
from: number
to: number
}
children?: FacetValue[]
id?: string
}

interface Breadcrumb {
Expand Down
20 changes: 14 additions & 6 deletions packages/api/src/platforms/vtex/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import { getClients } from './clients'
import type { SearchArgs } from './clients/search'
import { getLoaders } from './loaders'
import { StoreAggregateOffer } from './resolvers/aggregateOffer'
import { StoreAggregateRating } from './resolvers/aggregateRating'
import { StoreCollection } from './resolvers/collection'
import { StoreFacet } from './resolvers/facet'
import { StoreFacetValue } from './resolvers/facetValue'
import {
StoreFacet,
StoreFacetBoolean,
StoreFacetRange,
} from './resolvers/facet'
import { StoreFacetValueBoolean } from './resolvers/faceValue'
import { Mutation } from './resolvers/mutation'
import { ObjectOrString } from './resolvers/objectOrString'
import { StoreOffer } from './resolvers/offer'
import { StoreProduct } from './resolvers/product'
import { StoreProductGroup } from './resolvers/productGroup'
import { StorePropertyValue } from './resolvers/propertyValue'
import { Query } from './resolvers/query'
import { StoreReview } from './resolvers/review'
import { StoreSearchResult } from './resolvers/searchResult'
import { StoreSeo } from './resolvers/seo'
import { ObjectOrString } from './resolvers/objectOrString'
import { StorePropertyValue } from './resolvers/propertyValue'
import ChannelMarshal from './utils/channel'
import type { Loaders } from './loaders'
import type { Clients } from './clients'
import type { Channel } from './utils/channel'
import ChannelMarshal from './utils/channel'

export interface Options {
platform: 'vtex'
Expand Down Expand Up @@ -48,6 +53,7 @@ export interface Context {
channel: Required<Channel>
locale: string
flags: FeatureFlags
searchArgs?: Omit<SearchArgs, 'type'>
}
headers: Record<string, string>
}
Expand All @@ -65,7 +71,9 @@ const Resolvers = {
StoreProduct,
StoreSeo,
StoreFacet,
StoreFacetValue,
StoreFacetBoolean,
StoreFacetRange,
StoreFacetValueBoolean,
StoreOffer,
StoreAggregateRating,
StoreReview,
Expand Down
12 changes: 12 additions & 0 deletions packages/api/src/platforms/vtex/resolvers/faceValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { Resolver } from '..'
import type { FacetValueBoolean } from '../clients/search/types/FacetSearchResult'

export const StoreFacetValueBoolean: Record<
string,
Resolver<FacetValueBoolean>
> = {
value: ({ value }) => value,
label: ({ name }) => name || 'unknown',
selected: ({ selected }) => selected,
quantity: ({ quantity }) => quantity,
}
71 changes: 66 additions & 5 deletions packages/api/src/platforms/vtex/resolvers/facet.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,72 @@
import { parseRange } from '../utils/facets'
import { min } from '../utils/orderStatistics'
import type {
FacetValueBoolean,
Facet,
FacetValueRange,
} from '../clients/search/types/FacetSearchResult'
import type { Resolver } from '..'
import type { Facet } from '../clients/search/types/FacetSearchResult'

type Root = Facet

export const StoreFacet: Record<string, Resolver<Root>> = {
key: ({ key }) => key ?? '',
label: ({ name }) => name ?? 'unknown',
values: ({ values }) => values,
type: ({ type }) => (type === 'TEXT' ? 'BOOLEAN' : 'RANGE'),
__resolveType: ({ type }) =>
type === 'TEXT' ? 'StoreFacetBoolean' : 'StoreFacetRange',
}

export const StoreFacetBoolean: Record<
string,
Resolver<Facet<FacetValueBoolean>>
> = {
key: ({ key }) => key,
label: ({ name }) => name,
values: ({ values }) => values.sort((a, b) => a.name.localeCompare(b.name)),
}

export const StoreFacetRange: Record<
string,
Resolver<Facet<FacetValueRange>>
> = {
key: ({ key }) => key,
label: ({ name }) => name,
min: ({ values, key }, _, { storage: { searchArgs } }) => {
/**
* Fetch the selected range the user queried.
*
* This is necessary because, differently from boolean facets, Search API does
* not return the selected values, making us have to implement it in here
*/
const selectedRange = parseRange(
searchArgs?.selectedFacets?.find((facet) => facet.key === key)?.value ??
''
)

const facet = min(values, (a, b) => a.range.from - b.range.from)
const globalMin = facet?.range.from ?? 0

return {
selected: selectedRange?.[0] ?? globalMin,
absolute: globalMin,
}
},
max: ({ values, key }, _, { storage: { searchArgs } }) => {
/**
* Fetch the selected range the user queried.
*
* This is necessary because, differently from boolean facets, Search API does
* not return the selected values, making us have to implement it in here
*/
const selectedRange = parseRange(
searchArgs?.selectedFacets?.find((facet) => facet.key === key)?.value ??
''
)

const facet = min(values, (a, b) => b.range.to - a.range.to)
const globalMax = facet?.range.to ?? 0

return {
selected: selectedRange?.[1] ?? globalMax,
absolute: globalMax,
}
},
}
12 changes: 0 additions & 12 deletions packages/api/src/platforms/vtex/resolvers/facetValue.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/api/src/platforms/vtex/resolvers/offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export const StoreOffer: Record<string, Resolver<Root>> = {
},
listPrice: (root) => {
if (isSearchItem(root)) {
return root.ListPrice
return root.ListPrice ?? 0
}

if (isOrderFormItem(root)) {
Expand Down
35 changes: 9 additions & 26 deletions packages/api/src/platforms/vtex/resolvers/searchResult.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { enhanceSku } from '../utils/enhanceSku'
import type { Resolver } from '..'
import type { SearchArgs } from '../clients/search'
import type { Facet } from '../clients/search/types/FacetSearchResult'
import { enhanceSku } from '../utils/enhanceSku'

type Root = Omit<SearchArgs, 'type'>

const REMOVED_FACETS_FROM_COLLECTION_PAGE = ['departamento', 'Departamento']
const isRootFacet = (facet: Facet) => facet.key === 'category-1'

export const StoreSearchResult: Record<string, Resolver<Root>> = {
suggestions: async (searchArgs, _, ctx) => {
Expand Down Expand Up @@ -90,33 +90,16 @@ export const StoreSearchResult: Record<string, Resolver<Root>> = {
clients: { search: is },
} = ctx

const { facets } = await is.facets(searchArgs)

const isCollectionPage = !searchArgs.query
const filteredFacets = facets?.reduce((acc, currentFacet) => {
const shouldFilterFacet = REMOVED_FACETS_FROM_COLLECTION_PAGE.includes(
currentFacet.name
)

const shouldRemoveFacetFromCollectionPage =
isCollectionPage && shouldFilterFacet
ctx.storage.searchArgs = searchArgs

if (shouldRemoveFacetFromCollectionPage) {
return acc
}

currentFacet.values.sort((a, b) => {
const firstItemLabel = a.name ?? ''
const secondItemLabel = b.name ?? ''
const { facets = [] } = await is.facets(searchArgs)

return firstItemLabel.localeCompare(secondItemLabel)
})

acc.push(currentFacet)
const isCollectionPage = !searchArgs.query

return acc
}, [] as Facet[])
const filteredFacets = facets
// Remove root facet on category pages
.filter((facet) => !isCollectionPage || !isRootFacet(facet))

return filteredFacets ?? []
return filteredFacets
},
}
18 changes: 18 additions & 0 deletions packages/api/src/platforms/vtex/utils/facets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,29 @@ export const transformSelectedFacet = ({ key, value }: SelectedFacet) => {
return [] // remove this facet from search
}

case 'price': {
return { key, value: value.replace('-to-', ':') }
}

default:
return { key, value }
}
}

export const parseRange = (range: string): [number, number] | null => {
const splitted = range.split(':').map(Number)

if (
splitted.length !== 2 ||
Number.isNaN(splitted[0]) ||
Number.isNaN(splitted[1])
) {
return null
}

return splitted as [number, number]
}

export const findSlug = (facets?: Maybe<SelectedFacet[]>) =>
facets?.find((x) => x.key === 'slug')?.value ?? null

Expand Down
Loading

1 comment on commit a4c3fa7

@vercel
Copy link

@vercel vercel bot commented on a4c3fa7 Jun 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.