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

Add facets to AppliedFilters #43

Merged
merged 10 commits into from
Oct 22, 2021
Merged
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
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/appliedfilterutils';

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
5 changes: 3 additions & 2 deletions sample-app/src/models/displayableFilter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Filter } from '@yext/answers-core';

export interface DisplayableFilter {
filterType: 'NLP_FILTER' | 'STATIC_FILTER' | 'FACET',
filter: Filter,
filterGroupLabel: string,
filterLabel: string
groupLabel: string,
tmeyer2115 marked this conversation as resolved.
Show resolved Hide resolved
label: string
}
77 changes: 77 additions & 0 deletions sample-app/src/utils/appliedfilterutils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { AppliedQueryFilter } from "@yext/answers-core";
import { FiltersState } from "@yext/answers-headless/lib/esm/models/slices/filters";
import { DisplayableFilter } from "../models/displayableFilter";
import { GroupedFilters } from "../models/groupedFilters";
import { mapArrayToObject } from "./arrayutils";
import { isDuplicateFilter, flattenFilters } from './filterutils';
import {
getDisplayableStaticFilters,
getDisplayableAppliedFacets,
getDisplayableNlpFilters
} from "./displayablefilterutils";

/**
* Returns a new list of nlp filters with duplicates of other filters and
* filter listed in hiddenFields removed from the given nlp filter list.
*/
function pruneNlpFilters (
nlpFilters: DisplayableFilter[],
appliedFilters: DisplayableFilter[],
hiddenFields: string[]
): DisplayableFilter[] {
const duplicatesRemoved = nlpFilters.filter(nlpFilter => {
const isDuplicate = appliedFilters.find(appliedFilter =>
isDuplicateFilter(nlpFilter.filter, appliedFilter.filter)
);
return !isDuplicate;
});
return pruneAppliedFilters(duplicatesRemoved, hiddenFields);
}

/**
* Returns a new list of applied filters with filter on hiddenFields removed
* from the given applied filter list.
*/
function pruneAppliedFilters(
appliedFilters: DisplayableFilter[], hiddenFields: string[]): DisplayableFilter[] {
return appliedFilters.filter(appliedFilter => {
return !hiddenFields.includes(appliedFilter.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.
*/
function createGroupedFilters(
appliedFilters: DisplayableFilter[],
nlpFilters: DisplayableFilter[]
): Array<GroupedFilters> {
const getGroupLabel = (filter: DisplayableFilter) => filter.groupLabel;
const allFilters = [...appliedFilters, ...nlpFilters];
const groupedFilters: Record<string, DisplayableFilter[]> = mapArrayToObject(allFilters, getGroupLabel);
return Object.keys(groupedFilters).map(label => ({
label: label,
filters: groupedFilters[label]
}));
}

/**
* 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[]
): Array<GroupedFilters> {
const displayableStaticFilters = getDisplayableStaticFilters(flattenFilters(appliedFiltersState?.static));
const displayableFacets = getDisplayableAppliedFacets(appliedFiltersState?.facets);
const displayableNlpFilters = getDisplayableNlpFilters(nlpFilters);

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

return createGroupedFilters(appliedFilters, prunedNlpFilters);
}
59 changes: 59 additions & 0 deletions sample-app/src/utils/displayablefilterutils.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { AppliedQueryFilter, Filter, DisplayableFacet } from '@yext/answers-core';
import { DisplayableFilter } from '../models/displayableFilter';
import { getFilterDisplayValue } from './filterutils';

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

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

/**
* Convert a list of nlp filters to DisplayableFilter format.
*/
export function getDisplayableNlpFilters(filters: AppliedQueryFilter[]): DisplayableFilter[] {
let appliedNlpFilters: DisplayableFilter[] = [];
filters?.forEach(filter => {
appliedNlpFilters.push({
filterType: 'NLP_FILTER',
filter: filter.filter,
groupLabel: filter.displayKey,
label: filter.displayValue,
});
});
return appliedNlpFilters;
}
89 changes: 23 additions & 66 deletions sample-app/src/utils/filterutils.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import { AppliedQueryFilter, CombinedFilter, Filter } 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 { NearFilterValue, CombinedFilter, Filter } from '@yext/answers-core';

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

/**
* Get a filter's display value or label in string format
*/
export 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');
}


/**
* Check if the object follows CombinedFilter interface
Expand Down Expand Up @@ -42,65 +61,3 @@ export function isDuplicateFilter(thisFilter: Filter, otherFilter: Filter): bool
}
return true;
}

/**
* 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[] {
const duplicatesRemoved = nlpFilters.filter(nlpFilter => {
const isDuplicate = appliedFilters.find(appliedFilter =>
isDuplicateFilter(nlpFilter.filter, appliedFilter)
);
return !isDuplicate;
});
return duplicatesRemoved.filter(nlpFilter => {
return !hiddenFields.includes(nlpFilter.filter.fieldId);
});
}

/**
* Returns a new list of applied filters with filter on hiddenFields removed
* from the given nlp filter list.
*/
export function pruneAppliedFilters(appliedFilters: Filter[], hiddenFields: string[]): Filter[] {
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.
*/
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);
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
*/
export function getAppliedFilters(appliedFiltersState: FiltersState | undefined): Array<Filter> {
const appliedStaticFilters = flattenFilters(appliedFiltersState?.static);
return appliedStaticFilters;
}