Skip to content

Commit 58b61ce

Browse files
committed
feat(facets): implement separate facets configuration
1 parent 42c31c8 commit 58b61ce

8 files changed

Lines changed: 141 additions & 18 deletions

File tree

packages/x-adapter-platform/src/schemas/requests/facets-request.schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ export const facetsRequestSchema = createMutableSchema<FacetsRequest, PlatformFa
1212
query: 'query',
1313
origin: 'origin',
1414
filter: ({ filters }) => mapFilters(filters),
15-
extraParams: 'extraParams',
15+
extraParams: ({ extraParams: { separateFacets, ...rest } = {} }) => rest,
1616
})

packages/x-adapter-platform/src/schemas/requests/search-request.schema.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ export const searchRequestSchema = createMutableSchema<SearchRequest, PlatformSe
1515
rows: 'rows',
1616
sort: 'sort',
1717
filter: ({ filters }) => mapFilters(filters),
18-
extraParams: 'extraParams',
18+
extraParams: ({ extraParams: { separateFacets, ...rest } = {} }) => ({
19+
...rest,
20+
facets: !separateFacets,
21+
}),
1922
})

packages/x-components/src/utils/filters.ts

Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,92 @@
1-
import type { Filter, RawFilter } from '@empathyco/x-types'
1+
import type { FacetsRequest, Filter, RawFilter } from '@empathyco/x-types'
22

33
/**
44
* Compares if two lists contains the same filters.
55
*
6-
* @param someFilters - A list of filters to compare.
7-
* @param anotherFilters - Another list of filters to compare.
6+
* @param filtersA - A list of filters to compare.
7+
* @param filtersB - Another list of filters to compare.
88
*
99
* @returns True if the two lists of filters are equal, which means that they have the same
1010
* filters. The position of the filter does not matter for this check.
1111
*
1212
* @public
1313
*/
14-
export function areFiltersDifferent(someFilters: Filter[], anotherFilters: Filter[]): boolean {
14+
export function areFiltersDifferent(filtersA: Filter[], filtersB: Filter[]): boolean {
1515
return (
16-
someFilters.length !== anotherFilters.length ||
17-
someFilters.some(filter => !anotherFilters.find(otherFilter => otherFilter.id === filter.id))
16+
filtersA.length !== filtersB.length ||
17+
filtersA.some(filter => !filtersB.find(otherFilter => otherFilter.id === filter.id))
1818
)
1919
}
2020

21+
/**
22+
* Compares if two filter dictionaries are different, ignoring empty filter arrays.
23+
*
24+
* @param filtersA - A filter dictionary to compare.
25+
* @param filtersB - Another filter dictionary to compare.
26+
*
27+
* @returns True if the filter dictionaries are different, false if they are equivalent.
28+
* Empty objects (\{\}) and objects with only empty arrays (\{brand: [], category: []\}) are
29+
* treated as equivalent since they both represent no active filters.
30+
*
31+
* @public
32+
*/
33+
export function areFilterDictionaryDifferent(
34+
filtersA: Record<string, Filter[]>,
35+
filtersB: Record<string, Filter[]>,
36+
): boolean {
37+
const allKeys = new Set([...Object.keys(filtersA), ...Object.keys(filtersB)])
38+
39+
for (const key of allKeys) {
40+
const arrayA = filtersA[key] || []
41+
const arrayB = filtersB[key] || []
42+
43+
// Skip if both arrays are empty (equivalent to no filters)
44+
if (arrayA.length === 0 && arrayB.length === 0) {
45+
continue
46+
}
47+
48+
// Compare the filter arrays (handles length and content differences)
49+
if (areFiltersDifferent(arrayA, arrayB)) {
50+
return true
51+
}
52+
}
53+
54+
return false
55+
}
56+
57+
/**
58+
* Compares if two facets requests are different.
59+
*
60+
* @param requestA - A facets request.
61+
* @param requestB - Another facets request.
62+
*
63+
* @returns True if the requests are different, false if they are equivalent.
64+
* Handles semantic equivalence of filter states (empty object vs object with empty arrays).
65+
*
66+
* @public
67+
*/
68+
export function areRequestsDifferent(
69+
requestA: FacetsRequest | null,
70+
requestB: FacetsRequest | null,
71+
): boolean {
72+
// Reference equality check
73+
if (requestA === requestB) return false
74+
75+
// Null transition check
76+
if (requestA === null || requestB === null) return true
77+
78+
// Compare query
79+
if (requestA.query !== requestB.query) return true
80+
81+
// Compare extraParams (shallow, simple)
82+
if (JSON.stringify(requestA.extraParams || {}) !== JSON.stringify(requestB.extraParams || {}))
83+
return true
84+
85+
// Use semantic filter comparison to avoid duplicate emissions
86+
// when filters go from {} to {brand: [], category: []}
87+
return areFilterDictionaryDifferent(requestA.filters || {}, requestB.filters || {})
88+
}
89+
2190
/**
2291
* Helper method which creates the filter entity from the filter ir of the url.
2392
*

packages/x-components/src/views/home/Home.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ export default defineComponent({
724724
setup() {
725725
const x = use$x()
726726
const stores = ['Spain', 'Portugal', 'Italy']
727-
const initialExtraParams = { store: 'Portugal' }
727+
const initialExtraParams = { store: 'Portugal', separateFacets: false }
728728
const searchInputPlaceholderMessages = [
729729
'Find shirts',
730730
'Find shoes',

packages/x-components/src/x-modules/facets/events.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
EditableNumberRangeFilter,
33
Facet,
4+
FacetsRequest,
45
Filter,
56
HierarchicalFilter,
67
NumberRangeFilter,
@@ -98,4 +99,10 @@ export interface FacetsXEvents {
9899
* Payload: The facets query.
99100
*/
100101
FacetsQueryChanged: string
102+
/**
103+
* Any property of the search request has been updated.
104+
* Payload: The new facets request or `null` if there is not enough data in the state to
105+
* conform a valid request.
106+
*/
107+
FacetsRequestUpdated: FacetsRequest | null
101108
}

packages/x-components/src/x-modules/facets/store/actions/fetch-and-save-facets-response.action.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,14 @@ const { fetchAndSave, cancelPrevious } = createFetchAndSaveActions<
99
FacetsRequest | null,
1010
FacetsResponse | null
1111
>({
12-
async fetch({ dispatch, getters }) {
13-
return getters.request
14-
? dispatch('fetchFacetsResponse', getters.request)
15-
: Promise.resolve(null)
12+
async fetch({ dispatch }, request) {
13+
return request ? dispatch('fetchFacetsResponse', request) : Promise.resolve(null)
1614
},
1715
onSuccess({ commit, getters }, response) {
1816
if (response !== null) {
1917
const selectedFilters = getters.selectedFilters
2018
const selectedIds = new Set(
21-
Object.values(selectedFilters ?? {})
22-
.filter((f: Filter) => f.selected)
23-
.map((f: Filter) => f.id),
19+
selectedFilters.filter((f: Filter) => f.selected).map((f: Filter) => f.id),
2420
)
2521
const facetsWithSelectedFilters: Facet[] = []
2622

packages/x-components/src/x-modules/facets/store/emitters.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createStoreEmitters } from '../../../store/utils/store-emitters.utils'
2-
import { areFiltersDifferent } from '../../../utils/filters'
2+
import { areFiltersDifferent, areRequestsDifferent } from '../../../utils/filters'
33
import { isNewQuery } from '../../../utils/is-new-query'
44
import { facetsXStoreModule } from './module'
55

@@ -27,4 +27,8 @@ export const facetsEmitters = createStoreEmitters(facetsXStoreModule, {
2727
selector: state => state.query,
2828
filter: isNewQuery,
2929
},
30+
FacetsRequestUpdated: {
31+
selector: (_, getters) => getters.request,
32+
filter: areRequestsDifferent,
33+
},
3034
})

packages/x-components/src/x-modules/facets/wiring.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import type { Facet } from '@empathyco/x-types'
22
import type { UrlParams } from '../../types/url-params'
33
import type { XEventPayload } from '../../wiring/index'
44
import { createRawFilters } from '../../utils/filters'
5-
import { namespacedWireCommit, namespacedWireCommitWithoutPayload } from '../../wiring/index'
5+
import {
6+
namespacedWireCommit,
7+
namespacedWireCommitWithoutPayload,
8+
namespacedWireDispatch,
9+
} from '../../wiring'
610
import { wireService, wireServiceWithoutPayload } from '../../wiring/wires.factory'
711
import { filter, mapWire } from '../../wiring/wires.operators'
812
import { createWiring } from '../../wiring/wiring.utils'
@@ -29,6 +33,13 @@ const wireCommit = namespacedWireCommit(moduleName)
2933
*/
3034
const wireCommitWithoutPayload = namespacedWireCommitWithoutPayload(moduleName)
3135

36+
/**
37+
* WireDispatch for {@link FacetsXModule}.
38+
*
39+
* @internal
40+
*/
41+
const wireDispatch = namespacedWireDispatch(moduleName)
42+
3243
/**
3344
* Wires factory for {@link DefaultFacetsService}.
3445
*/
@@ -180,6 +191,30 @@ export const setFiltersFromHistoryQueries = wireCommit(
180191
*/
181192
export const setQueryFromPreview = wireCommit('setQuery', ({ eventPayload: { query } }) => query)
182193

194+
/**
195+
* Sets the facets state `params`.
196+
*
197+
* @public
198+
*/
199+
export const setFacetsExtraParams = wireCommit('setParams')
200+
201+
/**
202+
* Requests and stores the facets response.
203+
*
204+
* @public
205+
*/
206+
export const fetchAndSaveFacetsResponseWire = wireDispatch('fetchAndSaveFacetsResponse')
207+
208+
/**
209+
* Filtered version of fetchAndSaveFacetsResponseWire that only executes when separateFacets are enabled.
210+
*
211+
* @internal
212+
*/
213+
const fetchAndSaveFacetsResponseWireIfEnabled = filter(
214+
fetchAndSaveFacetsResponseWire,
215+
({ store }) => !!store.state.x.facets.params.separateFacets,
216+
)
217+
183218
/**
184219
* Wiring configuration for the {@link FacetsXModule | facets module}.
185220
*
@@ -238,4 +273,13 @@ export const facetsWiring = createWiring({
238273
UserSelectedAHistoryQuery: {
239274
setFiltersFromHistoryQueries,
240275
},
276+
ExtraParamsChanged: {
277+
setFacetsExtraParams,
278+
},
279+
UserOpenedFacetsAside: {
280+
fetchAndSaveFacetsResponseWireIfEnabled,
281+
},
282+
FacetsRequestUpdated: {
283+
fetchAndSaveFacetsResponseWireIfEnabled,
284+
},
241285
})

0 commit comments

Comments
 (0)