From 5306504c2c1aef95955d50c12822588d42dce7f6 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Fri, 26 Jul 2024 13:15:03 +0200 Subject: [PATCH 01/17] --wip-- [skip ci] --wip-- [skip ci] --wip-- [skip ci] --wip-- [skip ci] --- .../tabs/eventsTab/computeFacetState.ts | 76 ++++++++ .../tabs/eventsTab/eventsTabSide.tsx | 4 +- .../tabs/eventsTab/facetList.spec.ts | 164 ++++++++++++++++++ .../components/tabs/eventsTab/facetList.tsx | 90 +++++++--- .../src/panel/hooks/useEvents/eventFilters.ts | 40 +++-- .../panel/hooks/useEvents/facetRegistry.ts | 8 + .../src/panel/hooks/useEvents/index.ts | 2 +- 7 files changed, 346 insertions(+), 38 deletions(-) create mode 100644 developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts create mode 100644 developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts 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..957bc890f1 --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -0,0 +1,76 @@ +import type { FacetRegistry, FacetValuesFilter } from '../../../hooks/useEvents' +import type { Facet, FacetValue } from '../../../facets.constants' +type SelectionState = 'selected' | 'unselected' | 'partial-selected' + +export function computeSelectionState( + facetValuesFilter: FacetValuesFilter, + facetRegistry: FacetRegistry, + facet: Facet, + facetValue: FacetValue +): SelectionState { + const childrenFacets = getAllChildren(facet, facetValue) + + // we cannot know how many children in total there are, so we need to have facetRegistry + const children = childrenFacets.flatMap((child) => facetRegistry.getFacetChildrenValues(child.path)) + const filteredFacetValues = childrenFacets.flatMap((child) => facetValuesFilter.facetValues[child.path] ?? []) + filteredFacetValues.push(...(facetValuesFilter.facetValues[facet.path] ?? [])) + const ifFilterEmpty = Object.keys(facetValuesFilter.facetValues).length === 0 + if (facetValuesFilter.type === 'include') { + if (ifFilterEmpty) { + return 'unselected' + } + // if facet.value is in facetValueFilter, then it should be selected + if (filteredFacetValues.includes(facetValue)) { + return 'selected' + } + // if all children are in the filter, then it should be selected' + if (children.length > 0 && children.every((child) => filteredFacetValues.includes(child))) { + return 'selected' + } + // if any of the children of the facet is in the filter, then it should be partial-selected + if (children.length > 0 && children.some((child) => filteredFacetValues.includes(child))) { + return 'partial-selected' + } + } else if (facetValuesFilter.type === 'exclude') { + if (ifFilterEmpty) { + 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.length > 0 && children.every((child) => filteredFacetValues.includes(child))) { + return 'unselected' + } + // if any of the children of the facet is in the filter, then it should be partial-selected + if (children.length > 0 && children.some((child) => filteredFacetValues.includes(child))) { + return 'partial-selected' + } + return 'selected' + } + + return 'unselected' +} + +export const getAllSiblingTrees = (facet: Facet): Facet[] => { + const children = facet.values ? Object.values(facet.values).flatMap((value) => value?.facets ?? []) : [] + return children.concat(children.flatMap(getAllSiblingTrees)) +} + +export const getAllChildren = (facet: Facet, facetValue: FacetValue): Facet[] => { + const children = + facet.values && Object.keys(facet.values).includes(facetValue) + ? Object.values(facet.values).flatMap((value) => { + if (value?.facets) { + for (const childFacet of value.facets) { + if (childFacet.path.includes(facetValue)) { + return [childFacet] + } + } + } + return [] + }) + : [] + return children.concat(children.flatMap((f: Facet) => getAllChildren(f, facetValue))) +} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx index 7270173426..846f4e72bc 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.spec.ts b/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts new file mode 100644 index 0000000000..15c4c8ac31 --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts @@ -0,0 +1,164 @@ +import type { RumActionEvent, RumResourceEvent } from '@datadog/browser-rum' +import { FacetRegistry, FacetValuesFilter } from '../../../hooks/useEvents' +import { FACET_ROOT } 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 +fdescribe('computeSelectionState', () => { + describe('include mode', () => { + it('returns "selected" when the facet is in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = FACET_ROOT.values?.rum + + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + const facetValue = 'xhr' + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('selected') + }) + + it('returns "partial-selected" when some children are in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'include', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = { + path: 'type', + label: 'Type', + values: { + action: { + facets: [ + { + path: 'resource.type', + label: 'Resource Type', + }, + ], + }, + }, + } + const facetValue = 'resource' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumResourceBeaconEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('partial-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: 'type', + label: 'Type', + values: { + action: { + facets: [ + { + 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)).toBe('unselected') + }) + }) + + describe('exclude mode', () => { + it('returns "unselected" when the facet is in the filter', () => { + const facetValuesFilter: FacetValuesFilter = { + type: 'exclude', + facetValues: { + 'resource.type': ['xhr'], + }, + } + const facet = FACET_ROOT.values?.rum + + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + const facetValue = 'xhr' + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).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 + const facetValue = 'resource' + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(rumResourceXHREvent) + facetRegistry.addEvent(rumResourceBeaconEvent) + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('partial-selected') + }) + + 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: 'type', + label: 'Type', + values: { + action: { + facets: [ + { + 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)).toBe('selected') + }) + }) +}) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index bf6f24c230..640ef09af7 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -1,25 +1,26 @@ -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 +30,14 @@ function FacetField({ facet, depth, facetRegistry, - excludedFacetValues, + facetValuesFilter, onExcludedFacetValuesChange, }: { facet: Facet depth: number facetRegistry: FacetRegistry - excludedFacetValues: ExcludedFacetValues - onExcludedFacetValuesChange: (newExcludedFacetValues: ExcludedFacetValues) => void + facetValuesFilter: FacetValuesFilter + onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const facetValueCounts = facetRegistry.getFacetValueCounts(facet.path) @@ -56,7 +57,7 @@ function FacetField({ facetValueCount={facetValueCount} depth={depth} facetRegistry={facetRegistry} - excludedFacetValues={excludedFacetValues} + facetValuesFilter={facetValuesFilter} onExcludedFacetValuesChange={onExcludedFacetValuesChange} /> ))} @@ -72,7 +73,7 @@ function FacetValue({ facetValueCount, depth, facetRegistry, - excludedFacetValues, + facetValuesFilter, onExcludedFacetValuesChange, }: { facet: Facet @@ -80,27 +81,42 @@ function FacetValue({ facetValueCount: number depth: number facetRegistry: FacetRegistry - excludedFacetValues: ExcludedFacetValues - onExcludedFacetValuesChange: (newExcludedFacetValues: ExcludedFacetValues) => void + facetValuesFilter: FacetValuesFilter + 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) + // console.log(facetSelectState, facetValuesFilter) + const isCollapsed = !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) + const isSelected = facetValuesFilter.facetValues[facet.path] && facetValuesFilter.facetValues[facet.path].includes(facetValue) + const isOnly = facetValuesFilter.type === 'include' && Object.keys(facetValuesFilter.facetValues).length === 1 const value = ( { - onExcludedFacetValuesChange(toggleExcludedFacetValue(facet, excludedFacetValues, facetValue)) + onExcludedFacetValuesChange(toggleExcludedFacetValue(facet, facetValuesFilter, facetValue)) }} /> {facetValueCount} + ) const childFacets = facet.values?.[facetValue]?.facets const children = childFacets && ( - + {childFacets.map((facet) => ( ))} @@ -137,12 +153,12 @@ function FacetValue({ function toggleExcludedFacetValue( facet: Facet, - excludedFacetValues: ExcludedFacetValues, + excludedFacetValues: FacetValuesFilter, value: FacetValue -): ExcludedFacetValues { - const currentExcludedValues = excludedFacetValues[facet.path] +): FacetValuesFilter { + const currentExcludedValues = excludedFacetValues.facetValues[facet.path] - const newExcludedFacetValues = { ...excludedFacetValues } + const newExcludedFacetValues = { ...excludedFacetValues.facetValues } if (!currentExcludedValues) { // Add exclusion. Nothing was excluded yet, create a new list @@ -158,5 +174,33 @@ function toggleExcludedFacetValue( newExcludedFacetValues[facet.path] = currentExcludedValues.filter((other) => other !== value) } - return newExcludedFacetValues + return {type: 'exclude', facetValues: newExcludedFacetValues} +} + +function toggleFacetValue( + type: 'include' | 'exclude', + facet: Facet, + facetValuesFilter: FacetValuesFilter, + value: FacetValue +): FacetValuesFilter { + + const currentValues = facetValuesFilter.facetValues[facet.path] + + const newFacetValues = { ...facetValuesFilter.facetValues } + + if (!currentValues) { + // Add exclusion. Nothing was excluded yet, create a new list + newFacetValues[facet.path] = [value] + } else if (!currentValues.includes(value)) { + // Add exclusion. Some other values are already excluded, add it to the list + newFacetValues[facet.path] = currentValues.concat(value) + } else if (currentValues.length === 1) { + // Remove exclusion. 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. + newFacetValues[facet.path] = currentValues.filter((other) => other !== value) + } + + return { type, facetValues: newFacetValues } } diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts index 74f9fd91e6..a05346a42e 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts @@ -5,17 +5,18 @@ 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 +24,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 = filterExcludedFacets(filteredEvents, filters.facetValuesFilter, facetRegistry) if (filters.query) { const queryParts: string[][] = parseQuery(filters.query) @@ -45,17 +46,32 @@ export function applyEventFilters(filters: EventFilters, events: SdkEvent[], fac function filterExcludedFacets( 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) + if(facetValuesFilter.type === 'exclude') { + const excludedFacetValues = facetValuesFilter.facetValues + return events.filter( + (event) => + !Object.entries(excludedFacetValues).some(([facetPath, excludedValues]) => + (excludedValues as Array).includes( + facetRegistry.getFieldValueForEvent(event, facetPath) + ) ) - ) - ) + ) + } else if (facetValuesFilter.type === 'include') { + console.log('filter include', facetValuesFilter.facetValues) + const includedFacetValues = facetValuesFilter.facetValues + return events.filter( + (event) => + Object.entries(includedFacetValues).every(([facetPath, includedValues]) => + (includedValues as Array).includes( + facetRegistry.getFieldValueForEvent(event, facetPath) + ) + ) + ) + } + return events } function filterOutdatedVersions(events: SdkEvent[]): SdkEvent[] { 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 71b9f74402..4872537ee4 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 { EventFilters, ExcludedFacetValues } from './eventFilters' +export { EventFilters, FacetValuesFilter } from './eventFilters' export { FacetRegistry } from './facetRegistry' From 94b9beece06d30d4f95bac45c573089b88dccd56 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Fri, 2 Aug 2024 11:40:51 +0200 Subject: [PATCH 02/17] Re-implement with standalone compute select status --- .../tabs/eventsTab/computeFacetState.ts | 5 -- .../tabs/eventsTab/facetList.spec.ts | 65 ++++++------------- .../components/tabs/eventsTab/facetList.tsx | 35 +++------- 3 files changed, 28 insertions(+), 77 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts index 957bc890f1..c365b068d0 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -9,7 +9,6 @@ export function computeSelectionState( facetValue: FacetValue ): SelectionState { const childrenFacets = getAllChildren(facet, facetValue) - // we cannot know how many children in total there are, so we need to have facetRegistry const children = childrenFacets.flatMap((child) => facetRegistry.getFacetChildrenValues(child.path)) const filteredFacetValues = childrenFacets.flatMap((child) => facetValuesFilter.facetValues[child.path] ?? []) @@ -53,10 +52,6 @@ export function computeSelectionState( return 'unselected' } -export const getAllSiblingTrees = (facet: Facet): Facet[] => { - const children = facet.values ? Object.values(facet.values).flatMap((value) => value?.facets ?? []) : [] - return children.concat(children.flatMap(getAllSiblingTrees)) -} export const getAllChildren = (facet: Facet, facetValue: FacetValue): Facet[] => { const children = diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts b/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts index 15c4c8ac31..2a105f2be5 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts @@ -1,7 +1,8 @@ import type { RumActionEvent, RumResourceEvent } from '@datadog/browser-rum' -import { FacetRegistry, FacetValuesFilter } from '../../../hooks/useEvents' +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 = { @@ -28,7 +29,7 @@ const rumCustomActionEvent = { } as RumActionEvent // test that computeSelectionState returns the correct state -fdescribe('computeSelectionState', () => { +describe('computeSelectionState', () => { describe('include mode', () => { it('returns "selected" when the facet is in the filter', () => { const facetValuesFilter: FacetValuesFilter = { @@ -37,7 +38,10 @@ fdescribe('computeSelectionState', () => { 'resource.type': ['xhr'], }, } - const facet = FACET_ROOT.values?.rum + const facet = { + path: 'resource.type', + label: 'Resource Type', + } const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) @@ -52,20 +56,7 @@ fdescribe('computeSelectionState', () => { 'resource.type': ['xhr'], }, } - const facet = { - path: 'type', - label: 'Type', - values: { - action: { - facets: [ - { - path: 'resource.type', - label: 'Resource Type', - }, - ], - }, - }, - } + const facet = FACET_ROOT.values!.rum?.facets![0] as Facet const facetValue = 'resource' const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) @@ -81,18 +72,8 @@ fdescribe('computeSelectionState', () => { }, } const facet = { - path: 'type', - label: 'Type', - values: { - action: { - facets: [ - { - path: 'action.type', - label: 'Action Type', - }, - ], - }, - }, + path: 'action.type', + label: 'Action Type', } const facetValue = 'action' @@ -111,8 +92,10 @@ fdescribe('computeSelectionState', () => { 'resource.type': ['xhr'], }, } - const facet = FACET_ROOT.values?.rum - + const facet = { + path: 'resource.type', + label: 'Resource Type', + } const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) const facetValue = 'xhr' @@ -125,7 +108,8 @@ fdescribe('computeSelectionState', () => { 'resource.type': ['xhr'], }, } - const facet = FACET_ROOT + const facet = FACET_ROOT.values!.rum?.facets![0] as Facet + const facetValue = 'resource' const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) @@ -141,19 +125,10 @@ fdescribe('computeSelectionState', () => { }, } const facet = { - path: 'type', - label: 'Type', - values: { - action: { - facets: [ - { - path: 'action.type', - label: 'Action Type', - }, - ], - }, - }, + path: 'action.type', + label: 'Action Type', } + const facetValue = 'action' const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index 640ef09af7..228cd2ac2d 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -97,7 +97,14 @@ function FacetValue({ checked={facetSelectState === 'selected'} indeterminate={facetSelectState === 'partial-selected'} onChange={() => { - onExcludedFacetValuesChange(toggleExcludedFacetValue(facet, facetValuesFilter, facetValue)) + if (isOnly && !facetValuesFilter.facetValues[facet.path]?.includes(facetValue)) { + facetValuesFilter.facetValues = { + [facet.path]: [facetValue], + } + onExcludedFacetValuesChange(facetValuesFilter) + } else { + onExcludedFacetValuesChange(toggleFacetValue('exclude', facet, facetValuesFilter, facetValue)) + } }} /> {facetValueCount} @@ -151,32 +158,6 @@ function FacetValue({ ) } -function toggleExcludedFacetValue( - facet: Facet, - excludedFacetValues: FacetValuesFilter, - value: FacetValue -): FacetValuesFilter { - const currentExcludedValues = excludedFacetValues.facetValues[facet.path] - - const newExcludedFacetValues = { ...excludedFacetValues.facetValues } - - 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] - } else { - // Remove exclusion. Filter out the the value from the existing list. - newExcludedFacetValues[facet.path] = currentExcludedValues.filter((other) => other !== value) - } - - return {type: 'exclude', facetValues: newExcludedFacetValues} -} - function toggleFacetValue( type: 'include' | 'exclude', facet: Facet, From d0057f0d8b0bb5e737a0d606a2dbe973f4d87ba5 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Fri, 2 Aug 2024 11:51:38 +0200 Subject: [PATCH 03/17] Clean up code --- ...tList.spec.ts => computeFacetState.spec.ts} | 0 .../tabs/eventsTab/computeFacetState.ts | 1 - .../tabs/eventsTab/eventsTabSide.tsx | 2 +- .../components/tabs/eventsTab/facetList.tsx | 11 +++++++---- .../src/panel/hooks/useEvents/eventFilters.ts | 18 ++++++++---------- 5 files changed, 16 insertions(+), 16 deletions(-) rename developer-extension/src/panel/components/tabs/eventsTab/{facetList.spec.ts => computeFacetState.spec.ts} (100%) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts similarity index 100% rename from developer-extension/src/panel/components/tabs/eventsTab/facetList.spec.ts rename to developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts index c365b068d0..39eb7d4509 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -52,7 +52,6 @@ export function computeSelectionState( return 'unselected' } - export const getAllChildren = (facet: Facet, facetValue: FacetValue): Facet[] => { const children = facet.values && Object.keys(facet.values).includes(facetValue) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx index 846f4e72bc..27a0325582 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx @@ -26,7 +26,7 @@ export function EventsTabSide({ facetRegistry={facetRegistry} facetValuesFilter={filters.facetValuesFilter} onExcludedFacetValuesChange={(newExcludedFacetValues) => - onFiltersChange({ ...filters, facetValuesFilter: 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 228cd2ac2d..ffec8c1603 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -87,8 +87,10 @@ function FacetValue({ const isTopLevel = depth === 0 const facetSelectState = computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue) // console.log(facetSelectState, facetValuesFilter) - const isCollapsed = !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) - const isSelected = facetValuesFilter.facetValues[facet.path] && facetValuesFilter.facetValues[facet.path].includes(facetValue) + const isCollapsed = + !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) + const isSelected = + facetValuesFilter.facetValues[facet.path] && facetValuesFilter.facetValues[facet.path].includes(facetValue) const isOnly = facetValuesFilter.type === 'include' && Object.keys(facetValuesFilter.facetValues).length === 1 const value = ( @@ -117,7 +119,9 @@ function FacetValue({ const filterType = isOnly ? 'exclude' : 'include' onExcludedFacetValuesChange(toggleFacetValue(filterType, facet, facetValuesFilter, facetValue)) }} - >{isOnly && isSelected ? 'all' : 'only'} + > + {isOnly && isSelected ? 'all' : 'only'} + ) @@ -164,7 +168,6 @@ function toggleFacetValue( facetValuesFilter: FacetValuesFilter, value: FacetValue ): FacetValuesFilter { - const currentValues = facetValuesFilter.facetValues[facet.path] const newFacetValues = { ...facetValuesFilter.facetValues } diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts index a05346a42e..8b4ce3fc7d 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts @@ -10,13 +10,13 @@ export interface EventFilters { outdatedVersions: boolean } -export type FacetValuesFilter = {type: 'include' | 'exclude', facetValues: FacetValues} +export type FacetValuesFilter = { type: 'include' | 'exclude'; facetValues: FacetValues } export interface FacetValues { [facetPath: string]: string[] } export const DEFAULT_FILTERS: EventFilters = { - facetValuesFilter: {type: 'exclude', facetValues: {}}, + facetValuesFilter: { type: 'exclude', facetValues: {} }, query: '', outdatedVersions: false, } @@ -49,7 +49,7 @@ function filterExcludedFacets( facetValuesFilter: FacetValuesFilter, facetRegistry: FacetRegistry ): SdkEvent[] { - if(facetValuesFilter.type === 'exclude') { + if (facetValuesFilter.type === 'exclude') { const excludedFacetValues = facetValuesFilter.facetValues return events.filter( (event) => @@ -60,15 +60,13 @@ function filterExcludedFacets( ) ) } else if (facetValuesFilter.type === 'include') { - console.log('filter include', facetValuesFilter.facetValues) const includedFacetValues = facetValuesFilter.facetValues - return events.filter( - (event) => - Object.entries(includedFacetValues).every(([facetPath, includedValues]) => - (includedValues as Array).includes( - facetRegistry.getFieldValueForEvent(event, facetPath) - ) + return events.filter((event) => + Object.entries(includedFacetValues).every(([facetPath, includedValues]) => + (includedValues as Array).includes( + facetRegistry.getFieldValueForEvent(event, facetPath) ) + ) ) } return events From df9cbec1ae05a2a982d0bab3a3250a8afbfe8a3d Mon Sep 17 00:00:00 2001 From: cy-moi Date: Fri, 2 Aug 2024 12:06:15 +0200 Subject: [PATCH 04/17] Fix switching from exclude to include --- .../src/panel/components/tabs/eventsTab/facetList.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index ffec8c1603..517fa51a40 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -86,7 +86,6 @@ function FacetValue({ }) { const isTopLevel = depth === 0 const facetSelectState = computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue) - // console.log(facetSelectState, facetValuesFilter) const isCollapsed = !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) const isSelected = @@ -168,8 +167,12 @@ function toggleFacetValue( facetValuesFilter: FacetValuesFilter, value: FacetValue ): FacetValuesFilter { - const currentValues = facetValuesFilter.facetValues[facet.path] - + let currentValues = facetValuesFilter.facetValues[facet.path] + if (type !== facetValuesFilter.type) { + if (type === 'include') { + currentValues = [] + } + } const newFacetValues = { ...facetValuesFilter.facetValues } if (!currentValues) { From cabc1cfbfde26bc7b862212f5395baded4d4ec39 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Mon, 5 Aug 2024 18:06:46 +0200 Subject: [PATCH 05/17] Add parents check for include mode --- .../components/tabs/eventsTab/computeFacetState.ts | 12 +++++++++--- .../panel/components/tabs/eventsTab/facetList.tsx | 11 +++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts index 39eb7d4509..4da903e52c 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -6,18 +6,24 @@ export function computeSelectionState( facetValuesFilter: FacetValuesFilter, facetRegistry: FacetRegistry, facet: Facet, - facetValue: FacetValue + facetValue: FacetValue, + parentList: string[] ): SelectionState { const childrenFacets = getAllChildren(facet, facetValue) // we cannot know how many children in total there are, so we need to have facetRegistry const children = childrenFacets.flatMap((child) => facetRegistry.getFacetChildrenValues(child.path)) - const filteredFacetValues = childrenFacets.flatMap((child) => facetValuesFilter.facetValues[child.path] ?? []) - filteredFacetValues.push(...(facetValuesFilter.facetValues[facet.path] ?? [])) + const filteredFacetValues = Object.values(facetValuesFilter.facetValues).flat() const ifFilterEmpty = Object.keys(facetValuesFilter.facetValues).length === 0 + if (facetValuesFilter.type === 'include') { if (ifFilterEmpty) { return 'unselected' } + for (const parent of parentList) { + if (filteredFacetValues.includes(parent)) { + return 'selected' + } + } // if facet.value is in facetValueFilter, then it should be selected if (filteredFacetValues.includes(facetValue)) { return 'selected' diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index 517fa51a40..a12b4ccfec 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -22,6 +22,7 @@ export function FacetList({ facetRegistry={facetRegistry} facetValuesFilter={facetValuesFilter} onExcludedFacetValuesChange={onExcludedFacetValuesChange} + parentList={[]} /> ) } @@ -31,12 +32,14 @@ function FacetField({ depth, facetRegistry, facetValuesFilter, + parentList, onExcludedFacetValuesChange, }: { facet: Facet depth: number facetRegistry: FacetRegistry facetValuesFilter: FacetValuesFilter + parentList: string[] onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const facetValueCounts = facetRegistry.getFacetValueCounts(facet.path) @@ -58,6 +61,7 @@ function FacetField({ depth={depth} facetRegistry={facetRegistry} facetValuesFilter={facetValuesFilter} + parentList={parentList.includes(facetValue) ? parentList : [...parentList, facetValue]} onExcludedFacetValuesChange={onExcludedFacetValuesChange} /> ))} @@ -74,6 +78,7 @@ function FacetValue({ depth, facetRegistry, facetValuesFilter, + parentList, onExcludedFacetValuesChange, }: { facet: Facet @@ -82,10 +87,11 @@ function FacetValue({ depth: number facetRegistry: FacetRegistry facetValuesFilter: FacetValuesFilter + parentList: string[] onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const isTopLevel = depth === 0 - const facetSelectState = computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue) + const facetSelectState = computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, parentList) const isCollapsed = !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) const isSelected = @@ -126,7 +132,7 @@ function FacetValue({ const childFacets = facet.values?.[facetValue]?.facets const children = childFacets && ( - + {childFacets.map((facet) => ( ))} From fb1208cd1eb6baa6e1091101627e6329e37b4a65 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Tue, 6 Aug 2024 11:58:42 +0200 Subject: [PATCH 06/17] Simplify toggle facet value --- .../components/tabs/eventsTab/facetList.tsx | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index a12b4ccfec..937ecb15ac 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -94,8 +94,7 @@ function FacetValue({ const facetSelectState = computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, parentList) const isCollapsed = !facetValuesFilter.facetValues[facet.path] || !facetValuesFilter.facetValues[facet.path].includes(facetValue) - const isSelected = - 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' && Object.keys(facetValuesFilter.facetValues).length === 1 const value = ( @@ -105,6 +104,7 @@ function FacetValue({ indeterminate={facetSelectState === 'partial-selected'} onChange={() => { if (isOnly && !facetValuesFilter.facetValues[facet.path]?.includes(facetValue)) { + // when switching from only to include, remove all existing filter values facetValuesFilter.facetValues = { [facet.path]: [facetValue], } @@ -116,16 +116,16 @@ function FacetValue({ /> {facetValueCount} ) @@ -152,7 +152,7 @@ function FacetValue({ if (isTopLevel) { return ( - + {value} {children} @@ -174,25 +174,27 @@ function toggleFacetValue( facetValuesFilter: FacetValuesFilter, value: FacetValue ): FacetValuesFilter { - let currentValues = facetValuesFilter.facetValues[facet.path] + const currentValues = facetValuesFilter.facetValues[facet.path] + const newFacetValues = { ...facetValuesFilter.facetValues } + if (type !== facetValuesFilter.type) { - if (type === 'include') { - currentValues = [] + // when switching from exclude to include, remove all existing filter values + return { + type, facetValues: type === 'include' ? { [facet.path]: [value] } : {}, } } - const newFacetValues = { ...facetValuesFilter.facetValues } if (!currentValues) { - // Add exclusion. Nothing was excluded yet, create a new list + // Add exclusion or inclusion. Nothing was excluded yet, create a new list newFacetValues[facet.path] = [value] } else if (!currentValues.includes(value)) { - // Add exclusion. Some other values are already excluded, add it to the list + // 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. If it's the only value, delete the list altogether. + // 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. + // Remove exclusion or inclusion. Filter out the the value from the existing list. newFacetValues[facet.path] = currentValues.filter((other) => other !== value) } From 454912cd905362a3985c38a6f6c7ef06b64e8d49 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Tue, 6 Aug 2024 14:10:01 +0200 Subject: [PATCH 07/17] Simplify compute status and fix tests --- .../tabs/eventsTab/computeFacetState.spec.ts | 24 ++++++++++++++----- .../tabs/eventsTab/computeFacetState.ts | 20 +++++++--------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts index 2a105f2be5..88a02c4386 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts @@ -46,7 +46,9 @@ describe('computeSelectionState', () => { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) const facetValue = 'xhr' - expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('selected') + expect( + computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource', 'xhr']) + ).toBe('selected') }) it('returns "partial-selected" when some children are in the filter', () => { @@ -61,7 +63,9 @@ describe('computeSelectionState', () => { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) facetRegistry.addEvent(rumResourceBeaconEvent) - expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('partial-selected') + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource'])).toBe( + 'partial-selected' + ) }) it('returns "unselected" when the facet or children are not in the filter', () => { @@ -80,7 +84,9 @@ describe('computeSelectionState', () => { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) facetRegistry.addEvent(rumCustomActionEvent) - expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('unselected') + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'action'])).toBe( + 'unselected' + ) }) }) @@ -99,7 +105,9 @@ describe('computeSelectionState', () => { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) const facetValue = 'xhr' - expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('unselected') + 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 = { @@ -114,7 +122,9 @@ describe('computeSelectionState', () => { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) facetRegistry.addEvent(rumResourceBeaconEvent) - expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('partial-selected') + expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue, ['rum', 'resource'])).toBe( + 'partial-selected' + ) }) it('returns "selected" when the facet or children are not in the filter', () => { @@ -133,7 +143,9 @@ describe('computeSelectionState', () => { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(rumResourceXHREvent) facetRegistry.addEvent(rumCustomActionEvent) - expect(computeSelectionState(facetValuesFilter, facetRegistry, facet, facetValue)).toBe('selected') + 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 index 4da903e52c..29c3d4f49c 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -13,31 +13,29 @@ export function computeSelectionState( // we cannot know how many children in total there are, so we need to have facetRegistry const children = childrenFacets.flatMap((child) => facetRegistry.getFacetChildrenValues(child.path)) const filteredFacetValues = Object.values(facetValuesFilter.facetValues).flat() - const ifFilterEmpty = Object.keys(facetValuesFilter.facetValues).length === 0 + const isFiltering = !!Object.keys(facetValuesFilter.facetValues) if (facetValuesFilter.type === 'include') { - if (ifFilterEmpty) { + if (!isFiltering) { return 'unselected' } + for (const parent of parentList) { if (filteredFacetValues.includes(parent)) { return 'selected' } } - // if facet.value is in facetValueFilter, then it should be selected - if (filteredFacetValues.includes(facetValue)) { - return 'selected' - } + // if all children are in the filter, then it should be selected' - if (children.length > 0 && children.every((child) => filteredFacetValues.includes(child))) { + if (children && children.every((child: FacetValue) => filteredFacetValues.includes(child))) { return 'selected' } // if any of the children of the facet is in the filter, then it should be partial-selected - if (children.length > 0 && children.some((child) => filteredFacetValues.includes(child))) { + if (children && children.some((child: FacetValue) => filteredFacetValues.includes(child))) { return 'partial-selected' } } else if (facetValuesFilter.type === 'exclude') { - if (ifFilterEmpty) { + if (!isFiltering) { return 'selected' } // if facet.value is in facetValueFilter, then it should be unselected @@ -45,11 +43,11 @@ export function computeSelectionState( return 'unselected' } // if all children are in the filter, then it should be unselected - if (children.length > 0 && children.every((child) => filteredFacetValues.includes(child))) { + if (children && children.every((child: FacetValue) => filteredFacetValues.includes(child))) { return 'unselected' } // if any of the children of the facet is in the filter, then it should be partial-selected - if (children.length > 0 && children.some((child) => filteredFacetValues.includes(child))) { + if (children && children.some((child: FacetValue) => filteredFacetValues.includes(child))) { return 'partial-selected' } return 'selected' From 0415462745ef010d61200349ce5589892b2ab5cb Mon Sep 17 00:00:00 2001 From: cy-moi Date: Tue, 6 Aug 2024 14:32:17 +0200 Subject: [PATCH 08/17] Simplify facet select mode switching --wip-- [skip ci] --- .../components/tabs/eventsTab/facetList.tsx | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index 937ecb15ac..16266a5a6d 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -39,7 +39,7 @@ function FacetField({ depth: number facetRegistry: FacetRegistry facetValuesFilter: FacetValuesFilter - parentList: string[] + parentList: string[] onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const facetValueCounts = facetRegistry.getFacetValueCounts(facet.path) @@ -87,14 +87,15 @@ function FacetValue({ depth: number facetRegistry: FacetRegistry facetValuesFilter: FacetValuesFilter - parentList: string[] + parentList: string[] onExcludedFacetValuesChange: (newExcludedFacetValues: FacetValuesFilter) => void }) { const isTopLevel = depth === 0 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 isFiltered = + facetValuesFilter.facetValues[facet.path] && facetValuesFilter.facetValues[facet.path].includes(facetValue) const isOnly = facetValuesFilter.type === 'include' && Object.keys(facetValuesFilter.facetValues).length === 1 const value = ( @@ -103,15 +104,7 @@ function FacetValue({ checked={facetSelectState === 'selected'} indeterminate={facetSelectState === 'partial-selected'} onChange={() => { - if (isOnly && !facetValuesFilter.facetValues[facet.path]?.includes(facetValue)) { - // when switching from only to include, remove all existing filter values - facetValuesFilter.facetValues = { - [facet.path]: [facetValue], - } - onExcludedFacetValuesChange(facetValuesFilter) - } else { - onExcludedFacetValuesChange(toggleFacetValue('exclude', facet, facetValuesFilter, facetValue)) - } + onExcludedFacetValuesChange(toggleFacetValue(facetValuesFilter.type, facet, facetValuesFilter, facetValue)) }} /> {facetValueCount} @@ -125,7 +118,7 @@ function FacetValue({ onExcludedFacetValuesChange(toggleFacetValue(filterType, facet, facetValuesFilter, facetValue)) }} > - {isOnly && isFiltered ? 'all' : 'only'} + {isOnly && facetSelectState === 'selected' ? 'all' : 'only'} ) @@ -180,7 +173,8 @@ function toggleFacetValue( if (type !== facetValuesFilter.type) { // when switching from exclude to include, remove all existing filter values return { - type, facetValues: type === 'include' ? { [facet.path]: [value] } : {}, + type, + facetValues: type === 'include' ? { [facet.path]: [value] } : {}, } } From 430f73a03daf4e31ee5910fbab764d3e4312b990 Mon Sep 17 00:00:00 2001 From: cy-moi Date: Tue, 6 Aug 2024 15:42:47 +0200 Subject: [PATCH 09/17] Add compute select test coverage --- .../tabs/eventsTab/computeFacetState.spec.ts | 68 +++++++++++++++++++ .../tabs/eventsTab/computeFacetState.ts | 3 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts index 88a02c4386..db2f06ed58 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.spec.ts @@ -31,6 +31,23 @@ const rumCustomActionEvent = { // 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', @@ -68,6 +85,23 @@ describe('computeSelectionState', () => { ) }) + 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', @@ -91,6 +125,23 @@ describe('computeSelectionState', () => { }) 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', @@ -127,6 +178,23 @@ describe('computeSelectionState', () => { ) }) + 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', diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts index 29c3d4f49c..fb27cf8b2c 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -11,7 +11,8 @@ export function computeSelectionState( ): SelectionState { const childrenFacets = getAllChildren(facet, facetValue) // we cannot know how many children in total there are, so we need to have facetRegistry - const children = childrenFacets.flatMap((child) => facetRegistry.getFacetChildrenValues(child.path)) + const children = + childrenFacets && childrenFacets.flatMap((child: Facet) => facetRegistry.getFacetChildrenValues(child.path)) const filteredFacetValues = Object.values(facetValuesFilter.facetValues).flat() const isFiltering = !!Object.keys(facetValuesFilter.facetValues) From 8a0d4dff78e7b75efd1bc53a94b7d424e9d43fca Mon Sep 17 00:00:00 2001 From: zcy Date: Mon, 30 Dec 2024 15:41:25 +0100 Subject: [PATCH 10/17] Make the behavior consistent with web-ui --- .../components/tabs/eventsTab/facetList.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index 16266a5a6d..2d88505bb6 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -96,7 +96,7 @@ function FacetValue({ !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' && Object.keys(facetValuesFilter.facetValues).length === 1 + const isOnly = facetValuesFilter.type === 'include' && facetSelectState === 'selected' const value = ( { - onExcludedFacetValuesChange(toggleFacetValue(facetValuesFilter.type, facet, facetValuesFilter, facetValue)) + onExcludedFacetValuesChange(toggleFacetValue('exclude', facet, facetValuesFilter, facetValue)) }} /> {facetValueCount} ) @@ -170,11 +169,17 @@ function toggleFacetValue( const currentValues = facetValuesFilter.facetValues[facet.path] const newFacetValues = { ...facetValuesFilter.facetValues } - if (type !== facetValuesFilter.type) { - // when switching from exclude to include, remove all existing filter values + if (type === 'include') { // switch between the only values return { type, - facetValues: type === 'include' ? { [facet.path]: [value] } : {}, + facetValues: { [facet.path]: [value] }, + } + } else if (type === 'exclude' && facetValuesFilter.type !== type) { + // when switching from include to exclude, reset to all included first + // consistent with the facet behaviors of datadog web-ui + return { + type, + facetValues: {} } } From a3dfae8225584f4c1cd6041b547aae06da4efd98 Mon Sep 17 00:00:00 2001 From: zcy Date: Mon, 30 Dec 2024 17:42:52 +0100 Subject: [PATCH 11/17] Enhance code and add tests --- .../tabs/eventsTab/computeFacetState.ts | 19 ++++++++--- .../components/tabs/eventsTab/facetList.tsx | 29 ++++++++-------- .../hooks/useEvents/eventFilters.spec.ts | 33 +++++++++++++++++-- .../src/panel/hooks/useEvents/eventFilters.ts | 2 +- 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts index fb27cf8b2c..1a11944e47 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/computeFacetState.ts @@ -2,6 +2,15 @@ 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, @@ -28,11 +37,11 @@ export function computeSelectionState( } // if all children are in the filter, then it should be selected' - if (children && children.every((child: FacetValue) => filteredFacetValues.includes(child))) { + if (children && isAllChildrenFiltered(children, filteredFacetValues)) { return 'selected' } - // if any of the children of the facet is in the filter, then it should be partial-selected - if (children && children.some((child: FacetValue) => filteredFacetValues.includes(child))) { + // 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') { @@ -44,11 +53,11 @@ export function computeSelectionState( return 'unselected' } // if all children are in the filter, then it should be unselected - if (children && children.every((child: FacetValue) => filteredFacetValues.includes(child))) { + 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 && children.some((child: FacetValue) => filteredFacetValues.includes(child))) { + if (children && isAnyChildrenFiltered(children, filteredFacetValues)) { return 'partial-selected' } return 'selected' diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index 2d88505bb6..cee7953a38 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -102,9 +102,9 @@ function FacetValue({ { - onExcludedFacetValuesChange(toggleFacetValue('exclude', facet, facetValuesFilter, facetValue)) + onExcludedFacetValuesChange(toggleFacetValue(facetValuesFilter.type, facet, facetValuesFilter, facetValue)) }} /> {facetValueCount} @@ -169,17 +169,20 @@ function toggleFacetValue( const currentValues = facetValuesFilter.facetValues[facet.path] const newFacetValues = { ...facetValuesFilter.facetValues } - if (type === 'include') { // switch between the only values - return { - type, - facetValues: { [facet.path]: [value] }, - } - } else if (type === 'exclude' && facetValuesFilter.type !== type) { - // when switching from include to exclude, reset to all included first - // consistent with the facet behaviors of datadog web-ui - return { - type, - facetValues: {} + 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] }, + } } } diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts index 50f9ac65cc..a781dddd14 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts @@ -1,7 +1,36 @@ -import { isIE, isSafari } from '@datadog/browser-core' -import { parseQuery, matchWithWildcard } from './eventFilters' +import type { RumEvent } from '../../../../../packages/rum-core/src/rumEvent.types' +import { isIE, isSafari } from '../../../../../packages/core/src/tools/utils/browserDetection' +import { parseQuery, matchWithWildcard, filterExcludedFacets } 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 if (!isIE() && !isSafari()) { + describe('filterExcludedFacets', () => { + const facetRegistry = new FacetRegistry() + facetRegistry.addEvent(RUM_ACTION_EVENT) + facetRegistry.addEvent(RUM_ERROR_EVENT) + it('should exclude selected facets when in exclusion mode', () => { + expect( + filterExcludedFacets( + [RUM_ERROR_EVENT, RUM_ACTION_EVENT], + { type: 'exclude', facetValues: { type: ['error'] } } as FacetValuesFilter, + facetRegistry + ) + ).toEqual([RUM_ACTION_EVENT]) + }) + it('should exclude unselected facets when in inclusion mode', () => { + expect( + filterExcludedFacets( + [RUM_ERROR_EVENT, RUM_ACTION_EVENT], + { type: 'include', facetValues: { type: ['error'] } } as 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 8b4ce3fc7d..d68b961727 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts @@ -44,7 +44,7 @@ export function applyEventFilters(filters: EventFilters, events: SdkEvent[], fac return filteredEvents } -function filterExcludedFacets( +export function filterExcludedFacets( events: SdkEvent[], facetValuesFilter: FacetValuesFilter, facetRegistry: FacetRegistry From da95a7b1176b38826c5aabe1e4d708a17749c5fe Mon Sep 17 00:00:00 2001 From: zcy Date: Thu, 2 Jan 2025 11:14:37 +0100 Subject: [PATCH 12/17] Improve event filters test --- .../hooks/useEvents/eventFilters.spec.ts | 8 ++++-- .../src/panel/hooks/useEvents/eventFilters.ts | 28 +++++-------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts index a781dddd14..675fcf1a17 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts @@ -11,10 +11,12 @@ if (!isIE() && !isSafari()) { const facetRegistry = new FacetRegistry() facetRegistry.addEvent(RUM_ACTION_EVENT) facetRegistry.addEvent(RUM_ERROR_EVENT) + facetRegistry.addEvent(RUM_ERROR_EVENT) + it('should exclude selected facets when in exclusion mode', () => { expect( filterExcludedFacets( - [RUM_ERROR_EVENT, RUM_ACTION_EVENT], + [RUM_ACTION_EVENT, RUM_ERROR_EVENT, RUM_ERROR_EVENT], { type: 'exclude', facetValues: { type: ['error'] } } as FacetValuesFilter, facetRegistry ) @@ -23,11 +25,11 @@ if (!isIE() && !isSafari()) { it('should exclude unselected facets when in inclusion mode', () => { expect( filterExcludedFacets( - [RUM_ERROR_EVENT, RUM_ACTION_EVENT], + [RUM_ACTION_EVENT, RUM_ERROR_EVENT, RUM_ERROR_EVENT], { type: 'include', facetValues: { type: ['error'] } } as FacetValuesFilter, facetRegistry ) - ).toEqual([RUM_ERROR_EVENT]) + ).toEqual([RUM_ERROR_EVENT, RUM_ERROR_EVENT]) }) }) diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts index d68b961727..108736146c 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts @@ -49,27 +49,13 @@ export function filterExcludedFacets( facetValuesFilter: FacetValuesFilter, facetRegistry: FacetRegistry ): SdkEvent[] { - if (facetValuesFilter.type === 'exclude') { - const excludedFacetValues = facetValuesFilter.facetValues - return events.filter( - (event) => - !Object.entries(excludedFacetValues).some(([facetPath, excludedValues]) => - (excludedValues as Array).includes( - facetRegistry.getFieldValueForEvent(event, facetPath) - ) - ) - ) - } else if (facetValuesFilter.type === 'include') { - const includedFacetValues = facetValuesFilter.facetValues - return events.filter((event) => - Object.entries(includedFacetValues).every(([facetPath, includedValues]) => - (includedValues as Array).includes( - facetRegistry.getFieldValueForEvent(event, facetPath) - ) - ) - ) - } - return events + return events.filter((event: SdkEvent): boolean => + Object.entries(facetValuesFilter.facetValues).every(([facetPath, filteredValues]) => { + const eventValue = facetRegistry.getFieldValueForEvent(event, facetPath) + const values = filteredValues as Array + return facetValuesFilter.type === 'include' ? values.includes(eventValue) : !values.includes(eventValue) + }) + ) } function filterOutdatedVersions(events: SdkEvent[]): SdkEvent[] { From 9cf24e97449805a0e7d21ee942d78c98fe71f562 Mon Sep 17 00:00:00 2001 From: zcy Date: Tue, 7 Jan 2025 12:19:50 +0100 Subject: [PATCH 13/17] Fix filter facets in include mode --- .../hooks/useEvents/eventFilters.spec.ts | 25 ++++++++++++++++--- .../src/panel/hooks/useEvents/eventFilters.ts | 25 +++++++++++++------ 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts index 675fcf1a17..0714455821 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.spec.ts @@ -1,21 +1,23 @@ import type { RumEvent } from '../../../../../packages/rum-core/src/rumEvent.types' import { isIE, isSafari } from '../../../../../packages/core/src/tools/utils/browserDetection' -import { parseQuery, matchWithWildcard, filterExcludedFacets } 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 RUM_XHR_RESOURCE_EVENT = { type: 'resource', resource: { type: 'xhr' } } as RumEvent if (!isIE() && !isSafari()) { - describe('filterExcludedFacets', () => { + 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) it('should exclude selected facets when in exclusion mode', () => { expect( - filterExcludedFacets( + filterFacets( [RUM_ACTION_EVENT, RUM_ERROR_EVENT, RUM_ERROR_EVENT], { type: 'exclude', facetValues: { type: ['error'] } } as FacetValuesFilter, facetRegistry @@ -24,13 +26,28 @@ if (!isIE() && !isSafari()) { }) it('should exclude unselected facets when in inclusion mode', () => { expect( - filterExcludedFacets( + 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]) + }) }) describe('parseQuery', () => { diff --git a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts index 108736146c..4345046f93 100644 --- a/developer-extension/src/panel/hooks/useEvents/eventFilters.ts +++ b/developer-extension/src/panel/hooks/useEvents/eventFilters.ts @@ -24,7 +24,7 @@ export const DEFAULT_FILTERS: EventFilters = { export function applyEventFilters(filters: EventFilters, events: SdkEvent[], facetRegistry: FacetRegistry) { let filteredEvents = events - filteredEvents = filterExcludedFacets(filteredEvents, filters.facetValuesFilter, facetRegistry) + filteredEvents = filterFacets(filteredEvents, filters.facetValuesFilter, facetRegistry) if (filters.query) { const queryParts: string[][] = parseQuery(filters.query) @@ -44,18 +44,29 @@ export function applyEventFilters(filters: EventFilters, events: SdkEvent[], fac return filteredEvents } -export function filterExcludedFacets( +export function filterFacets( events: SdkEvent[], facetValuesFilter: FacetValuesFilter, facetRegistry: FacetRegistry ): SdkEvent[] { - return events.filter((event: SdkEvent): boolean => - Object.entries(facetValuesFilter.facetValues).every(([facetPath, filteredValues]) => { + const filteredEvents: SdkEvent[] = [] + const filteredFacetValueEntries = Object.entries(facetValuesFilter.facetValues) + if (filteredFacetValueEntries.length === 0) { + return events + } + for (const event of events) { + for (const [facetPath, filteredValues] of filteredFacetValueEntries) { const eventValue = facetRegistry.getFieldValueForEvent(event, facetPath) const values = filteredValues as Array - return facetValuesFilter.type === 'include' ? values.includes(eventValue) : !values.includes(eventValue) - }) - ) + + if (facetValuesFilter.type === 'include' && values.includes(eventValue)) { + filteredEvents.push(event) + } else if (facetValuesFilter.type === 'exclude' && !values.includes(eventValue)) { + filteredEvents.push(event) + } + } + } + return filteredEvents } function filterOutdatedVersions(events: SdkEvent[]): SdkEvent[] { From de675262e3403b0c8ad0c7db9647d38142c1ff1c Mon Sep 17 00:00:00 2001 From: zcy Date: Mon, 3 Feb 2025 19:47:16 +0100 Subject: [PATCH 14/17] Reset when unchecking children in Only mode --- .../src/panel/components/tabs/eventsTab/facetList.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index cee7953a38..06138802bb 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -104,7 +104,8 @@ function FacetValue({ checked={facetSelectState === 'selected'} indeterminate={facetSelectState === 'partial-selected'} // can only populate direct parents onChange={() => { - onExcludedFacetValuesChange(toggleFacetValue(facetValuesFilter.type, facet, facetValuesFilter, facetValue)) + const filterType = facetSelectState === 'selected' ? 'exclude' : 'include' + onExcludedFacetValuesChange(toggleFacetValue(filterType, facet, facetValuesFilter, facetValue)) }} /> {facetValueCount} From 148746b2e7265dbbd39eded864e2b995d2c009fc Mon Sep 17 00:00:00 2001 From: zcy Date: Tue, 4 Feb 2025 11:28:33 +0100 Subject: [PATCH 15/17] Polish UI and code --- .../components/tabs/eventsTab/facetList.tsx | 2 +- .../src/panel/hooks/useEvents/eventFilters.ts | 19 ++++++------------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx index 06138802bb..b91ff7bd0b 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/facetList.tsx @@ -110,7 +110,7 @@ function FacetValue({ /> {facetValueCount}