diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts new file mode 100644 index 0000000000..db2f06ed58 --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts @@ -0,0 +1,219 @@ +import type { RumActionEvent, RumResourceEvent } from '@datadog/browser-rum' +import { FacetRegistry } from '../../../hooks/useEvents' +import type { FacetValuesFilter } from '../../../hooks/useEvents' +import { FACET_ROOT } from '../../../facets.constants' +import type { Facet } from '../../../facets.constants' +import { computeSelectionState } from './computeFacetState' + +const rumResourceXHREvent = { + type: 'resource', + resource: { + type: 'xhr', + url: 'http://example.com', + }, +} as RumResourceEvent + +const rumResourceBeaconEvent = { + type: 'resource', + resource: { + type: 'beacon', + url: 'http://example.com', + }, +} as RumResourceEvent + +const rumCustomActionEvent = { + type: 'action', + action: { + type: 'custom', + }, +} as RumActionEvent + +// test that computeSelectionState returns the correct state +describe('computeSelectionState', () => { + describe('include mode', () => { + it('returns "unselected" when the filter is empty', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: {}, + } + const facet = { + path: 'resource.type', + label: 'Resource Type', + } + + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + const facetValue = 'xhr' + expect( + computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource', 'xhr']) + ).toBe('unselected') + }) + it('returns "selected" when the facet is in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = { + path: 'resource.type', + label: 'Resource Type', + } + + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + const facetValue = 'xhr' + expect( + computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource', 'xhr']) + ).toBe('selected') + }) + + it('returns "partial-selected" when some children are in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = FACET_ROOT.values!.rum?.facets![0] as Facet + const facetValue = 'resource' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumResourceBeaconEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource'])).toBe( + 'partial-selected' + ) + }) + + it('returns "selected" when all children are in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: { + 'resource.type': ['xhr', 'beacon'], + }, + } + const facet = FACET_ROOT.values!.rum?.facets![0] as Facet + const facetValue = 'resource' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumResourceBeaconEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource'])).toBe( + 'selected' + ) + }) + + it('returns "unselected" when the facet or children are not in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = { + path: 'action.type', + label: 'Action Type', + } + + const facetValue = 'action' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumCustomActionEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'action'])).toBe( + 'unselected' + ) + }) + }) + + describe('exclude mode', () => { + it('returns "selected" when the filter is empty', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: {}, + } + const facet = { + path: 'resource.type', + label: 'Resource Type', + } + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + const facetValue = 'xhr' + expect( + computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource', 'xhr']) + ).toBe('selected') + }) + + it('returns "unselected" when the facet is in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = { + path: 'resource.type', + label: 'Resource Type', + } + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + const facetValue = 'xhr' + expect( + computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource', 'xhr']) + ).toBe('unselected') + }) + it('returns "partial-selected" when some children are in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = FACET_ROOT.values!.rum?.facets![0] as Facet + + const facetValue = 'resource' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumResourceBeaconEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource'])).toBe( + 'partial-selected' + ) + }) + + it('returns "unelected" when all children are in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: { + 'resource.type': ['xhr', 'beacon'], + }, + } + const facet = FACET_ROOT.values!.rum?.facets![0] as Facet + const facetValue = 'resource' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumResourceBeaconEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource'])).toBe( + 'unselected' + ) + }) + + it('returns "selected" when the facet or children are not in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = { + path: 'action.type', + label: 'Action Type', + } + + const facetValue = 'action' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumCustomActionEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'action'])).toBe( + 'selected' + ) + }) + }) +}) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts new file mode 100644 index 0000000000..74c03ffe1d --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -0,0 +1,68 @@ +import type { FacetRegistry, FacetValuesFilter } from '../../../hooks/useEvents' +import type { Facet, FacetValue } from '../../../facets.constants' +type SelectionState = 'selected' | 'unselected' | 'partial-selected' + +function isAllChildrenFiltered(children: string[], filteredFacetValues: string[]) { + return children.every((child: FacetValue) => filteredFacetValues.includes(child)) +} + +function isAnyChildrenFiltered(children: string[], filteredFacetValues: string[]) { + return children.some((child: FacetValue) => filteredFacetValues.includes(child)) +} + +// limitation: only populate direct parents +export function computeSelectionState( + facetValuesFilter: FacetValuesFilter, + facetRegistry: FacetRegistry, + facet: Facet, + facetValue: FacetValue, + parentList: string[] +): SelectionState { + const childrenFacets = facet.values?.[facetValue]?.facets + + // we cannot know how many children in total there are, so we need to have facetRegistry + const children = + childrenFacets && childrenFacets.flatMap((child: Facet) => facetRegistry.getFacetChildrenValues(child.path)) + const filteredFacetValues = Object.values(facetValuesFilter.facetValues).flat() + const isFiltering = !!Object.keys(facetValuesFilter.facetValues) + + if (facetValuesFilter.type === 'include') { + if (!isFiltering) { + return 'unselected' + } + + for (const parent of parentList) { + if (filteredFacetValues.includes(parent)) { + return 'selected' + } + } + + // if all children are in the filter, then it should be selected' + if (children && isAllChildrenFiltered(children, filteredFacetValues)) { + return 'selected' + } + // if any of the direct children of the facet is in the filter, then it should be partial-selected + if (children && isAnyChildrenFiltered(children, filteredFacetValues)) { + return 'partial-selected' + } + } else if (facetValuesFilter.type === 'exclude') { + if (!isFiltering) { + return 'selected' + } + // if facet.value is in facetValueFilter, then it should be unselected + if (filteredFacetValues.includes(facetValue)) { + return 'unselected' + } + // if all children are in the filter, then it should be unselected + if (children && isAllChildrenFiltered(children, filteredFacetValues)) { + return 'unselected' + } + // if any of the children of the facet is in the filter, then it should be partial-selected + if (children && isAnyChildrenFiltered(children, filteredFacetValues)) { + return 'partial-selected' + } + return 'selected' + } + + return 'unselected' +} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx index 7270173426..27a0325582 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx @@ -24,9 +24,9 @@ export function EventsTabSide({ {facetRegistry && ( - onFiltersChange({ ...filters, excludedFacetValues: newExcludedFacetValues }) + onFiltersChange({ ...filters, facetValuesFilter: newExcludedFacetValues }) } /> )} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index bf6f24c230..b91ff7bd0b 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -1,26 +1,28 @@ -import { Box, Card, Checkbox, Collapse, Flex, Text } from '@mantine/core' +import { Box, Button, Card, Checkbox, Collapse, Flex, Text } from '@mantine/core' import React from 'react' -import type { ExcludedFacetValues, FacetRegistry } from '../../../hooks/useEvents' +import type { FacetValuesFilter, FacetRegistry } from '../../../hooks/useEvents' import type { Facet } from '../../../facets.constants' import { FACET_ROOT, FacetValue } from '../../../facets.constants' import * as classes from './facetList.module.css' +import { computeSelectionState } from './computeFacetState' export function FacetList({ facetRegistry, - excludedFacetValues, + facetValuesFilter, onExcludedFacetValuesChange, }: { facetRegistry: FacetRegistry - excludedFacetValues: ExcludedFacetValues - onExcludedFacetValuesChange: (newExcludedFacetValues: ExcludedFacetValues) => void + facetValuesFilter: FacetValuesFilter + onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { return ( ) } @@ -29,14 +31,16 @@ function FacetField({ facet, depth, facetRegistry, - excludedFacetValues, + facetValuesFilter, + parentList, onExcludedFacetValuesChange, }: { facet: Facet depth: number facetRegistry: FacetRegistry - excludedFacetValues: ExcludedFacetValues - onExcludedFacetValuesChange: (newExcludedFacetValues: ExcludedFacetValues) => void + facetValuesFilter: FacetValuesFilter + parentList: string[] + onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const facetValueCounts = facetRegistry.getFacetValueCounts(facet.path) @@ -56,7 +60,8 @@ function FacetField({ facetValueCount={facetValueCount} depth={depth} facetRegistry={facetRegistry} - excludedFacetValues={excludedFacetValues} + facetValuesFilter={facetValuesFilter} + parentList={parentList.includes(facetValue) ? parentList : [...parentList, facetValue]} onExcludedFacetValuesChange={onExcludedFacetValuesChange} /> ))} @@ -72,7 +77,8 @@ function FacetValue({ facetValueCount, depth, facetRegistry, - excludedFacetValues, + facetValuesFilter, + parentList, onExcludedFacetValuesChange, }: { facet: Facet @@ -80,27 +86,46 @@ function FacetValue({ facetValueCount: number depth: number facetRegistry: FacetRegistry - excludedFacetValues: ExcludedFacetValues - onExcludedFacetValuesChange: (newExcludedFacetValues: ExcludedFacetValues) => void + facetValuesFilter: FacetValuesFilter + parentList: string[] + onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const isTopLevel = depth === 0 - const isSelected = !excludedFacetValues[facet.path] || !excludedFacetValues[facet.path].includes(facetValue) + const facetSelectState = computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, parentList) + const isCollapsed = + !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) + const isFiltered = + facetValuesFilter.facetValues[facet.path] && facetValuesFilter.facetValues[facet.path].includes(facetValue) + const isOnly = facetValuesFilter.type === 'include' && facetSelectState === 'selected' const value = ( { - onExcludedFacetValuesChange(toggleExcludedFacetValue(facet, excludedFacetValues, facetValue)) + const filterType = facetSelectState === 'selected' ? 'exclude' : 'include' + onExcludedFacetValuesChange(toggleFacetValue(filterType, facet, facetValuesFilter, facetValue)) }} /> {facetValueCount} + ) const childFacets = facet.values?.[facetValue]?.facets const children = childFacets && ( - + {childFacets.map((facet) => ( ))} @@ -119,7 +145,7 @@ function FacetValue({ if (isTopLevel) { return ( - + {value} {children} @@ -135,28 +161,45 @@ function FacetValue({ ) } -function toggleExcludedFacetValue( +function toggleFacetValue( + type: 'include' | 'exclude', facet: Facet, - excludedFacetValues: ExcludedFacetValues, + facetValuesFilter: FacetValuesFilter, value: FacetValue -): ExcludedFacetValues { - const currentExcludedValues = excludedFacetValues[facet.path] +): FacetValuesFilter { + const currentValues = facetValuesFilter.facetValues[facet.path] + const newFacetValues = { ...facetValuesFilter.facetValues } - const newExcludedFacetValues = { ...excludedFacetValues } + if (facetValuesFilter.type !== type) { + // handle mode changes + if (type === 'exclude') { + // reset when change from include to exclude + return { + type, + facetValues: {}, + } + } else if (type === 'include' && currentValues) { + // should maintain one and only filter when change from exclude to include + return { + type, + facetValues: currentValues.includes(value) ? newFacetValues : { [facet.path]: [value] }, + } + } + } - if (!currentExcludedValues) { - // Add exclusion. Nothing was excluded yet, create a new list - newExcludedFacetValues[facet.path] = [value] - } else if (!currentExcludedValues.includes(value)) { - // Add exclusion. Some other values are already excluded, add it to the list - newExcludedFacetValues[facet.path] = currentExcludedValues.concat(value) - } else if (currentExcludedValues.length === 1) { - // Remove exclusion. If it's the only value, delete the list altogether. - delete newExcludedFacetValues[facet.path] + if (!currentValues) { + // Add exclusion or inclusion. Nothing was excluded yet, create a new list + newFacetValues[facet.path] = [value] + } else if (!currentValues.includes(value)) { + // Add exclusion or inclusion. Some other values are already added, add it to the list + newFacetValues[facet.path] = currentValues.concat(value) + } else if (currentValues.length === 1) { + // Remove exclusion or inclusion. If it's the only value, delete the list altogether. + delete newFacetValues[facet.path] } else { - // Remove exclusion. Filter out the the value from the existing list. - newExcludedFacetValues[facet.path] = currentExcludedValues.filter((other) => other !== value) + // Remove exclusion or inclusion. Filter out the the value from the existing list. + newFacetValues[facet.path] = currentValues.filter((other) => other !== value) } - return newExcludedFacetValues + return { type, facetValues: newFacetValues } } diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts index 4a0d78bb75..eea868f287 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts @@ -1,7 +1,70 @@ +import type { RumEvent } from '../../../../../packages/rum-core/src/rumEvent.types' +import type { LogsEvent } from '../../../../../packages/logs/src/logsEvent.types' import { isSafari } from '../../../../../packages/core/src/tools/utils/browserDetection' -import { parseQuery, matchWithWildcard } from './eventFilters' +import { parseQuery, matchWithWildcard, filterFacets } from './eventFilters' +import type { FacetValuesFilter } from './eventFilters' +import { FacetRegistry } from './facetRegistry' +const RUM_ERROR_EVENT = { type: 'error' } as RumEvent +const RUM_ACTION_EVENT = { type: 'action' } as RumEvent +const LOGS_EVENT = { status: 'info', origin: 'logger' } as LogsEvent +const RUM_XHR_RESOURCE_EVENT = { type: 'resource', resource: { type: 'xhr' } } as RumEvent if (!isSafari()) { + describe('filterFacets', () => { + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(RUM_ACTION_EVENT) + facetRegistry.addEvent(RUM_ERROR_EVENT) + facetRegistry.addEvent(RUM_ERROR_EVENT) + facetRegistry.addEvent(RUM_XHR_RESOURCE_EVENT) + facetRegistry.addEvent(LOGS_EVENT) + + it('should exclude selected facets when in exclusion mode', () => { + expect( + filterFacets( + [RUM_ACTION_EVENT, RUM_ERROR_EVENT, RUM_ERROR_EVENT], + { type: 'exclude', facetValues: { type: ['error'] } } as FacetValuesFilter, + facetRegistry + ) + ).toEqual([RUM_ACTION_EVENT]) + }) + it('should exclude unselected facets when in inclusion mode', () => { + expect( + filterFacets( + [RUM_ACTION_EVENT, RUM_ERROR_EVENT, RUM_ERROR_EVENT], + { type: 'include', facetValues: { type: ['error'] } } as FacetValuesFilter, + facetRegistry + ) + ).toEqual([RUM_ERROR_EVENT, RUM_ERROR_EVENT]) + }) + it('should include selected facets at different levels in inclusion mode', () => { + expect( + filterFacets( + [RUM_ACTION_EVENT, RUM_ERROR_EVENT, RUM_ERROR_EVENT, RUM_XHR_RESOURCE_EVENT], + { + type: 'include', + facetValues: { + type: ['action'], + 'resource.type': ['xhr'], + }, + } as FacetValuesFilter, + facetRegistry + ) + ).toEqual([RUM_ACTION_EVENT, RUM_XHR_RESOURCE_EVENT]) + }) + it('should exclude facets at different levels in exclusion mode', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: { + type: ['action', 'resource', 'view'], + $eventSource: ['logs'], + }, + } + expect(filterFacets([RUM_ACTION_EVENT, RUM_ERROR_EVENT, LOGS_EVENT], facetValuesFilter, facetRegistry)).toEqual([ + RUM_ERROR_EVENT, + ]) + }) + }) + describe('parseQuery', () => { it('return a simple field', () => { expect(parseQuery('foo:bar')).toEqual([['foo', 'bar']]) diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts index 74f9fd91e6..f9e1b53303 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts @@ -1,21 +1,21 @@ import type { SdkEvent } from '../../sdkEvent' import type { RumViewEvent } from '../../../../../packages/rum-core/src/rumEvent.types' import { isRumViewEvent } from '../../sdkEvent' -import type { FieldMultiValue } from '../../facets.constants' import type { FacetRegistry } from './facetRegistry' export interface EventFilters { - excludedFacetValues: ExcludedFacetValues + facetValuesFilter: FacetValuesFilter query: string outdatedVersions: boolean } -export interface ExcludedFacetValues { +export type FacetValuesFilter = { type: 'include' | 'exclude'; facetValues: FacetValues } +export interface FacetValues { [facetPath: string]: string[] } export const DEFAULT_FILTERS: EventFilters = { - excludedFacetValues: {}, + facetValuesFilter: { type: 'exclude', facetValues: {} }, query: '', outdatedVersions: false, } @@ -23,7 +23,7 @@ export const DEFAULT_FILTERS: EventFilters = { export function applyEventFilters(filters: EventFilters, events: SdkEvent[], facetRegistry: FacetRegistry) { let filteredEvents = events - filteredEvents = filterExcludedFacets(filteredEvents, filters.excludedFacetValues, facetRegistry) + filteredEvents = filterFacets(filteredEvents, filters.facetValuesFilter, facetRegistry) if (filters.query) { const queryParts: string[][] = parseQuery(filters.query) @@ -43,18 +43,21 @@ export function applyEventFilters(filters: EventFilters, events: SdkEvent[], fac return filteredEvents } -function filterExcludedFacets( +export function filterFacets( events: SdkEvent[], - excludedFacetValues: ExcludedFacetValues, + facetValuesFilter: FacetValuesFilter, facetRegistry: FacetRegistry ): SdkEvent[] { - return events.filter( - (event) => - !Object.entries(excludedFacetValues).some(([facetPath, excludedValues]) => - (excludedValues as Array).includes( - facetRegistry.getFieldValueForEvent(event, facetPath) - ) - ) + const filteredFacetValueEntries = Object.entries(facetValuesFilter.facetValues) + if (filteredFacetValueEntries.length === 0) { + return events + } + const isIncludeType = facetValuesFilter.type === 'include' + return events.filter((event) => + filteredFacetValueEntries[isIncludeType ? 'some' : 'every'](([facetPath, filteredValues]) => { + const eventValue = facetRegistry.getFieldValueForEvent(event, facetPath) + return isIncludeType === filteredValues.includes(eventValue as string) + }) ) } diff --git a/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts b/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts index 673dd54d23..0d3e3278f3 100644 --- a/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts +++ b/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts @@ -36,6 +36,14 @@ export class FacetRegistry { return this.allEventFieldPaths } + getFacetChildrenValues(fieldPath: FieldPath): FacetValue[] { + const facetValues = this.facetValueCounts.get(fieldPath) + if (!facetValues) { + return [] + } + return Array.from(facetValues.keys()) + } + clear() { this.facetValueCounts.clear() } diff --git a/developer-extension/src/panel/hooks/useEvents/index.ts b/developer-extension/src/panel/hooks/useEvents/index.ts index 609272630b..0bc530242b 100644 --- a/developer-extension/src/panel/hooks/useEvents/index.ts +++ b/developer-extension/src/panel/hooks/useEvents/index.ts @@ -1,3 +1,3 @@ export { useEvents } from './useEvents' -export type { EventFilters, ExcludedFacetValues } from './eventFilters' +export type { EventFilters, FacetValuesFilter } from './eventFilters' export { FacetRegistry } from './facetRegistry'