Skip to content

Commit

Permalink
Add facets to AppliedFilters
Browse files Browse the repository at this point in the history
J=1656
TEST=manual
  • Loading branch information
Yen Truong committed Oct 21, 2021
1 parent c85e3de commit 95d2a4f
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 54 deletions.
4 changes: 2 additions & 2 deletions sample-app/src/components/AppliedFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ function renderFilterLabel(label: string): JSX.Element {
function renderAppliedFilters(filters: Array<DisplayableFilter>): JSX.Element {
const filterElems = filters.map((filter: DisplayableFilter, index: number) => {
return (
<div className="AppliedFilters__filterValue" key={filter.filterLabel}>
<span className="AppliedFilters__filterValueText">{filter.filterLabel}</span>
<div className="AppliedFilters__filterValue" key={filter.label}>
<span className="AppliedFilters__filterValueText">{filter.label}</span>
{index < filters.length - 1 && <span className="AppliedFilters__filterValueComma">,</span>}
</div>
);
Expand Down
17 changes: 4 additions & 13 deletions sample-app/src/components/DecoratedAppliedFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import AppliedFilters from "./AppliedFilters";
import { AppliedQueryFilter } from "@yext/answers-core";
import { useAnswersState } from '@yext/answers-headless-react';
import { GroupedFilters } from '../models/groupedFilters';
import {
getAppliedFilters,
pruneAppliedFilters,
pruneNlpFilters,
createGroupedFilters
} from '../utils/filterutils';
import { getGroupedAppliedFilters } from '../utils/filterutils';

export interface DecoratedAppliedFiltersConfig {
showFieldNames?: boolean,
Expand All @@ -21,13 +16,9 @@ export interface DecoratedAppliedFiltersConfig {
* Container component for AppliedFilters
*/
export function DecoratedAppliedFiltersDisplay(props : DecoratedAppliedFiltersConfig): JSX.Element {
const { hiddenFields = [], appliedQueryFilters, ...otherProps } = props;
let appliedFilters = getAppliedFilters(useAnswersState(state => state.filters));
appliedFilters = pruneAppliedFilters(appliedFilters, hiddenFields);
let nlpFilters = appliedQueryFilters || [];
nlpFilters = pruneNlpFilters(nlpFilters, appliedFilters, hiddenFields);
const groupedFilters: Array<GroupedFilters> = createGroupedFilters(nlpFilters, appliedFilters);

const { hiddenFields = [], appliedQueryFilters = [], ...otherProps } = props;
const filterState = useAnswersState(state => state.filters);
const groupedFilters: Array<GroupedFilters> = getGroupedAppliedFilters(filterState, appliedQueryFilters, hiddenFields);
return <AppliedFilters appliedFilters={groupedFilters} {...otherProps}/>
}

Expand Down
12 changes: 8 additions & 4 deletions sample-app/src/models/displayableFilter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Filter } from '@yext/answers-core';
import { NearFilterValue, Matcher } from '@yext/answers-core';

export interface DisplayableFilter {
filter: Filter,
filterGroupLabel: string,
filterLabel: string
filterType: 'NLP_FILTER' | 'STATIC_FILTER' | 'FACET',
fieldId: string,
matcher: Matcher,
value: string | number | boolean | NearFilterValue,
count?: number,
groupLabel: string,
label: string
}
145 changes: 110 additions & 35 deletions sample-app/src/utils/filterutils.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { AppliedQueryFilter, CombinedFilter, Filter } from '@yext/answers-core';
import { AppliedQueryFilter, CombinedFilter, Filter, DisplayableFacet, NearFilterValue } from '@yext/answers-core';
import { FiltersState } from '@yext/answers-headless/lib/esm/models/slices/filters';
import { mapArrayToObject } from '../utils/arrayutils';
import { GroupedFilters } from '../models/groupedFilters';
import { DisplayableFilter } from '../models/displayableFilter';

/**
* Check if the object follows CombinedFilter interface
*/
export function isCombinedFilter(obj: Filter | CombinedFilter): obj is CombinedFilter {
function isCombinedFilter(obj: Filter | CombinedFilter): obj is CombinedFilter {
return 'filters' in obj && 'combinator' in obj;
}

/**
* Check if the object follows NearFilterValue interface
*/
function isNearFilterValue(obj: Object): obj is NearFilterValue {
return 'radius' in obj && 'lat' in obj && 'long' in obj;
}

/**
* Flatten the given filter, such as if the given filter is of type CombinedFilter
* with possible nested layers of Filter objects, into a 1-dimension array of Filter objects
*/
export function flattenFilters(filter: Filter | CombinedFilter | null | undefined): Array<Filter> {
function flattenFilters(filter: Filter | CombinedFilter | null | undefined): Array<Filter> {
let filters: Array<Filter> = [];
if(!filter) {
return filters;
Expand All @@ -28,9 +36,9 @@ export function flattenFilters(filter: Filter | CombinedFilter | null | undefine
}

/**
* Returns true if the two given filters are the same
* Returns true if the two given DisplayableFilters are the same
*/
export function isDuplicateFilter(thisFilter: Filter, otherFilter: Filter): boolean {
export function isDuplicateFilter(thisFilter: DisplayableFilter, otherFilter: DisplayableFilter): boolean {
if (thisFilter.fieldId !== otherFilter.fieldId) {
return false;
}
Expand All @@ -47,60 +55,127 @@ export function isDuplicateFilter(thisFilter: Filter, otherFilter: Filter): bool
* Returns a new list of nlp filters with duplicates of other filters and
* filter listed in hiddenFields removed from the given nlp filter list.
*/
export function pruneNlpFilters (nlpFilters: AppliedQueryFilter[], appliedFilters: Filter[],
hiddenFields: string[]): AppliedQueryFilter[] {
function pruneNlpFilters (nlpFilters: DisplayableFilter[], appliedFilters: DisplayableFilter[],
hiddenFields: string[]): DisplayableFilter[] {
const duplicatesRemoved = nlpFilters.filter(nlpFilter => {
const isDuplicate = appliedFilters.find(appliedFilter =>
isDuplicateFilter(nlpFilter.filter, appliedFilter)
isDuplicateFilter(nlpFilter, appliedFilter)
);
return !isDuplicate;
});
return duplicatesRemoved.filter(nlpFilter => {
return !hiddenFields.includes(nlpFilter.filter.fieldId);
});
return pruneAppliedFilters(duplicatesRemoved, hiddenFields);
}

/**
* Returns a new list of applied filters with filter on hiddenFields removed
* from the given nlp filter list.
* from the given applied filter list.
*/
export function pruneAppliedFilters(appliedFilters: Filter[], hiddenFields: string[]): Filter[] {
function pruneAppliedFilters(appliedFilters: DisplayableFilter[], hiddenFields: string[]): DisplayableFilter[] {
return appliedFilters.filter(filter => {
return !hiddenFields.includes(filter.fieldId);
});
}

/**
* Combine all of the applied filters into a list of GroupedFilters where each contains a label and
* list of filters under that same label or category. All filters will convert to DisplayableFilter format
* where displayValue will be used in the JSX element construction.
* list of filters under that same label or category.
*/
export function createGroupedFilters(nlpFilters: AppliedQueryFilter[], appliedFilters: Filter[]): Array<GroupedFilters> {
const getFieldName = (filter: Filter) => filter.fieldId;
const getNlpFieldName = (filter: AppliedQueryFilter) => filter.displayKey;
const getDisplayableAppliedFilter = (filter: Filter) => ({
filter: filter,
filterGroupLabel: filter.fieldId,
filterLabel: filter.value
});
const getDisplayableNlpFilter = (filter: AppliedQueryFilter, index: number) => ({
filter: filter.filter,
filterGroupLabel: filter.displayKey,
filterLabel: filter.displayValue
});

let groupedFilters = mapArrayToObject(appliedFilters, getFieldName, getDisplayableAppliedFilter);
groupedFilters = mapArrayToObject(nlpFilters, getNlpFieldName, getDisplayableNlpFilter, groupedFilters);
function createGroupedFilters(appliedFilters: DisplayableFilter[], nlpFilters: DisplayableFilter[]): Array<GroupedFilters> {
const getGroupLabel = (filter: DisplayableFilter) => filter.groupLabel;
const allFilters = appliedFilters.concat(nlpFilters);
const groupedFilters: Record<string, DisplayableFilter[]> = mapArrayToObject(allFilters, getGroupLabel);
return Object.keys(groupedFilters).map(label => ({
label: label,
filters: groupedFilters[label]
}));
}

/**
* Restructure and combine static filters from given FiltersState into a list of Filter objects
* get a filter's display value or label in string format
*/
export function getAppliedFilters(appliedFiltersState: FiltersState | undefined): Array<Filter> {
const appliedStaticFilters = flattenFilters(appliedFiltersState?.static);
function getFilterDisplayValue(filter: Filter): string {
const value = filter.value;
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return value.toString();
}
if (isNearFilterValue(value)) {
return `within ${value.radius}m radius`;
}
throw Error('unrecognized filter value type');
}

/**
* convert a list of facets to DisplayableFilter format with only selected facets returned.
*/
function getDisplayableAppliedFacets(facets: DisplayableFacet[] | undefined) {
let appliedFacets: DisplayableFilter[] = [];
facets?.forEach(facet => {
facet.options.forEach(option => {
if(option.selected) {
appliedFacets.push({
filterType: 'FACET',
fieldId: facet.fieldId,
matcher: option.matcher,
value: option.value,
groupLabel: facet.displayName,
label: option.displayName,
count: option.count
});
}
});
});
return appliedFacets;
}

/**
* convert a list of static filters to DisplayableFilter format.
*/
function getDisplayableStaticFilters(filters: Filter[]) {
let appliedStaticFilters: DisplayableFilter[] = [];
filters?.forEach(filter => {
appliedStaticFilters.push({
filterType: 'STATIC_FILTER',
fieldId: filter.fieldId,
matcher: filter.matcher,
value: filter.value,
groupLabel: filter.fieldId,
label: getFilterDisplayValue(filter),
});
});
return appliedStaticFilters;
}
}

/**
* convert a list of nlp filters to DisplayableFilter format.
*/
function getDisplayableNlpFilters(filters: AppliedQueryFilter[]) {
let appliedNplFilters: DisplayableFilter[] = [];
filters?.forEach(filter => {
appliedNplFilters.push({
filterType: 'NLP_FILTER',
fieldId: filter.filter.fieldId,
matcher: filter.filter.matcher,
value: filter.filter.value,
groupLabel: filter.displayKey,
label: filter.displayValue,
});
});
return appliedNplFilters;
}

/**
* Process all applied filter types (facets, static filters, and nlp filters) by removing
* duplicates and specified hidden fields, and grouped the applied filters into categories.
*/
export function getGroupedAppliedFilters(appliedFiltersState: FiltersState, nlpFilters: AppliedQueryFilter[], hiddenFields: string[]) {
const displayableStaticFilters = getDisplayableStaticFilters(flattenFilters(appliedFiltersState?.static));
const displayableFacets = getDisplayableAppliedFacets(appliedFiltersState?.facets);
const displayableNlpFilters = getDisplayableNlpFilters(nlpFilters);

const appliedFilters = displayableStaticFilters.concat(displayableFacets);
const prunedAppliedFilters = pruneAppliedFilters(appliedFilters, hiddenFields);
const prunedNlpFilters = pruneNlpFilters (displayableNlpFilters, prunedAppliedFilters, hiddenFields);

return createGroupedFilters(appliedFilters, prunedNlpFilters);

}

0 comments on commit 95d2a4f

Please sign in to comment.