diff --git a/developer-extension/src/panel/components/panel.tsx b/developer-extension/src/panel/components/panel.tsx index ecd5dbdfec..3029db17bd 100644 --- a/developer-extension/src/panel/components/panel.tsx +++ b/developer-extension/src/panel/components/panel.tsx @@ -10,7 +10,7 @@ import { useSettings } from '../hooks/useSettings' import { DEFAULT_PANEL_TAB, PanelTabs } from '../../common/constants' import { SettingsTab } from './tabs/settingsTab' import { InfosTab } from './tabs/infosTab' -import { EventsTab } from './tabs/eventsTab' +import { EventsTab, DEFAULT_COLUMNS } from './tabs/eventsTab' import { ReplayTab } from './tabs/replayTab' export function Panel() { @@ -21,6 +21,8 @@ export function Panel() { const { events, filters, setFilters, clear, facetRegistry } = useEvents(settings) + const [columns, setColumns] = useState(DEFAULT_COLUMNS) + const [activeTab, setActiveTab] = useState(DEFAULT_PANEL_TAB) function updateActiveTab(activeTab: string | null) { setActiveTab(activeTab) @@ -60,7 +62,9 @@ export function Panel() { events={events} facetRegistry={facetRegistry} filters={filters} - onFiltered={setFilters} + onFiltersChange={setFilters} + columns={columns} + onColumnsChange={setColumns} clear={clear} /> diff --git a/developer-extension/src/panel/components/tabs/eventsTab/columnDrag.tsx b/developer-extension/src/panel/components/tabs/eventsTab/columnDrag.tsx new file mode 100644 index 0000000000..a01521d134 --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/columnDrag.tsx @@ -0,0 +1,190 @@ +import type { RefObject } from 'react' +import React, { useState, useEffect } from 'react' +import { Box, Text } from '@mantine/core' +import { BORDER_RADIUS } from '../../../uiUtils' +import type { Coordinates } from './drag' +import { initDrag } from './drag' +import type { EventListColumn } from './columnUtils' +import { getColumnTitle } from './columnUtils' +import { HORIZONTAL_PADDING, VERTICAL_PADDING } from './grid' + +/** Number of pixel to determine if the cursor is close enough of a position to trigger an action */ +const ACTION_DISTANCE_THRESHOLD = 20 + +export function ColumnDrag({ + headerRowRef, + columns, + onColumnsChange, +}: { + headerRowRef: RefObject + columns: EventListColumn[] + onColumnsChange: (columns: EventListColumn[]) => void +}) { + const [drag, setDrag] = useState(null) + + useEffect(() => { + if (columns.length > 1) { + const { stop } = initColumnDrag(headerRowRef.current!, setDrag, columns, onColumnsChange) + return stop + } + }, [columns]) + + return drag && +} + +function DragGhost({ columns, drag }: { columns: EventListColumn[]; drag: DragState }) { + return ( + + {getColumnTitle(columns[drag.columnIndex])} + + ) +} + +function getClosestCell(target: HTMLElement) { + if (target.closest('button, .mantine-Popover-dropdown')) { + return null + } + return target.closest('[data-header-cell]') +} + +interface DragState { + targetRect: DOMRect + startPosition: Coordinates + position: Coordinates + action?: DragAction + moved: boolean + insertPlaces: Place[] + columnIndex: number +} + +interface Place { + index: number + xPosition: number +} + +type DragAction = { type: 'delete' } | { type: 'insert'; place: Place } + +function initColumnDrag( + target: HTMLElement, + onColumnDragStateChanges: (state: DragState | null) => void, + columns: EventListColumn[], + onColumnsChange: (columns: EventListColumn[]) => void +) { + let state: DragState | null = null + + return initDrag({ + target, + + onStart({ target, position }) { + const targetCell = getClosestCell(target) + if (!targetCell) { + return false + } + const siblings = Array.from(targetCell.parentElement!.children) + const columnIndex = siblings.indexOf(targetCell) + + state = { + targetRect: targetCell.getBoundingClientRect(), + insertPlaces: siblings.flatMap((sibling, index) => { + if (sibling === targetCell) { + return [] + } + return { + xPosition: sibling.getBoundingClientRect()[index < columnIndex ? 'left' : 'right'], + index, + } + }), + startPosition: position, + position, + moved: false, + action: undefined, + columnIndex, + } + onColumnDragStateChanges(state) + }, + + onMove({ position }) { + if (!state) { + return + } + let action: DragAction | undefined + if (Math.abs(state.startPosition.y - position.y) > ACTION_DISTANCE_THRESHOLD) { + action = { type: 'delete' } + } else { + const insertPlace = state.insertPlaces.find( + ({ xPosition }) => Math.abs(position.x - xPosition) < ACTION_DISTANCE_THRESHOLD + ) + if (insertPlace) { + action = { type: 'insert', place: insertPlace } + } + } + + state = { ...state, action, position, moved: true } + onColumnDragStateChanges(state) + }, + + onDrop() { + if (!state) { + return + } + + if (state.action) { + switch (state.action.type) { + case 'delete': { + const newColumns = columns.slice() + newColumns.splice(state.columnIndex, 1) + onColumnsChange(newColumns) + break + } + case 'insert': { + const newColumns = columns.slice() + const [column] = newColumns.splice(state.columnIndex, 1) + newColumns.splice(state.action.place.index, 0, column) + onColumnsChange(newColumns) + break + } + } + } + + state = null + onColumnDragStateChanges(state) + }, + + onAbort() { + state = null + onColumnDragStateChanges(state) + }, + }) +} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/columnUtils.ts b/developer-extension/src/panel/components/tabs/eventsTab/columnUtils.ts new file mode 100644 index 0000000000..3c0c046a8f --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/columnUtils.ts @@ -0,0 +1,26 @@ +export type EventListColumn = + | { type: 'date' } + | { type: 'description' } + | { type: 'type' } + | { type: 'field'; path: string } + +export const DEFAULT_COLUMNS: EventListColumn[] = [{ type: 'date' }, { type: 'type' }, { type: 'description' }] + +export function includesColumn(existingColumns: EventListColumn[], newColumn: EventListColumn) { + return existingColumns.some((column) => { + if (column.type === 'field' && newColumn.type === 'field') { + return column.path === newColumn.path + } + return column.type === newColumn.type + }) +} + +export function getColumnTitle(column: EventListColumn) { + return column.type === 'date' + ? 'Date' + : column.type === 'description' + ? 'Description' + : column.type === 'type' + ? 'Type' + : column.path +} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/drag.ts b/developer-extension/src/panel/components/tabs/eventsTab/drag.ts new file mode 100644 index 0000000000..5d2e0dff0e --- /dev/null +++ b/developer-extension/src/panel/components/tabs/eventsTab/drag.ts @@ -0,0 +1,121 @@ +export interface Coordinates { + x: number + y: number +} + +/** + * This is a framework agnostic drag implementation that works as a state machine: + * + * ``` + * (init) + * | + * [onStart] + * | \______________ + * | \ + * + * | ____ | + * | / \ (end) + * [onMove] ) + * | \ \____/ + * | \______________ + * | \ + * | + * | + * | | + * [onDrop] [onAbort] + * | _________________/ + * |/ + * (end) + * ``` + */ +export function initDrag({ + target, + onStart, + onMove, + onAbort, + onDrop, +}: { + target: HTMLElement + onStart: (event: { target: HTMLElement; position: Coordinates }) => boolean | void + onMove: (event: { position: Coordinates }) => void + onDrop: () => void + onAbort: () => void +}) { + type DragState = + | { isDragging: false } + | { + isDragging: true + removeListeners: () => void + } + + let state: DragState = { + isDragging: false, + } + + target.addEventListener('pointerdown', onPointerDown, { capture: true }) + + return { + stop: () => { + endDrag(true) + target.removeEventListener('pointerdown', onPointerDown, { capture: true }) + }, + } + + function onPointerDown(event: PointerEvent) { + if ( + state.isDragging || + event.buttons !== 1 || + onStart({ target: event.target as HTMLElement, position: { x: event.clientX, y: event.clientY } }) === false + ) { + return + } + + event.preventDefault() + + state = { + isDragging: true, + removeListeners: () => { + removeEventListener('pointerup', onPointerUp, { capture: true }) + removeEventListener('pointermove', onPointerMove, { capture: true }) + }, + } + + addEventListener('pointerup', onPointerUp, { capture: true }) + addEventListener('pointermove', onPointerMove, { capture: true }) + } + + function onPointerUp(_event: PointerEvent) { + endDrag(false) + } + + function onPointerMove(event: PointerEvent) { + if (!state.isDragging) { + return + } + + if (event.buttons !== 1) { + // The user might have released the click outside of the window + endDrag(true) + return + } + + onMove({ + position: { + x: event.clientX, + y: event.clientY, + }, + }) + } + + function endDrag(abort: boolean) { + if (state.isDragging) { + if (abort) { + onAbort() + } else { + onDrop() + } + state.removeListeners() + state = { isDragging: false } + } + } +} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx index 4fe039ccf8..bdf6f3e495 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx @@ -1,4 +1,4 @@ -import { Badge, Box } from '@mantine/core' +import { Badge, Box, Menu } from '@mantine/core' import type { ReactNode } from 'react' import React, { useRef, useState } from 'react' import type { TelemetryEvent } from '../../../../../../packages/core/src/domain/telemetry' @@ -15,7 +15,10 @@ import { isTelemetryEvent, isLogEvent, isRumEvent } from '../../../sdkEvent' import { formatDate, formatDuration } from '../../../formatNumber' import { defaultFormatValue, Json } from '../../json' import { LazyCollapse } from '../../lazyCollapse' +import type { FacetRegistry } from '../../../hooks/useEvents' import { Grid } from './grid' +import type { EventListColumn } from './columnUtils' +import { includesColumn } from './columnUtils' const RUM_EVENT_TYPE_COLOR = { action: 'violet', @@ -46,46 +49,106 @@ const RESOURCE_TYPE_LABELS: Record = { other: 'Other', } -export const EventRow = React.memo(({ event }: { event: SdkEvent }) => { - const [isCollapsed, setIsCollapsed] = useState(true) - const jsonRef = useRef(null) +export const EventRow = React.memo( + ({ + event, + columns, + facetRegistry, + onAddColumn, + }: { + event: SdkEvent + columns: EventListColumn[] + facetRegistry: FacetRegistry + onAddColumn: (newColumn: EventListColumn) => void + }) => { + const [isCollapsed, setIsCollapsed] = useState(true) + const jsonRef = useRef(null) - return ( - { - if (jsonRef.current?.contains(event.target as Node)) { - // Ignore clicks on the collapsible area - return - } - setIsCollapsed((previous) => !previous) - }} - > - {formatDate(event.date)} - - {isRumEvent(event) || isTelemetryEvent(event) ? ( - - {event.type} - - ) : ( - - {event.origin as string} {event.status as string} - - )} - - - - - - - - - ) -}) + function getMenuItemsForPath(path: string) { + const newColumn: EventListColumn = { type: 'field', path } + if (!path || includesColumn(columns, newColumn)) { + return null + } + return ( + <> + { + onAddColumn(newColumn) + }} + > + Add column + + + ) + } + + return ( + + {columns.map((column): React.ReactElement => { + switch (column.type) { + case 'date': + return {formatDate(event.date)} + case 'description': + return ( + { + if (jsonRef.current?.contains(event.target as Node)) { + // Ignore clicks on the collapsible area + return + } + setIsCollapsed((previous) => !previous) + }} + > + + + + + + ) + case 'type': + return ( + + {isRumEvent(event) || isTelemetryEvent(event) ? ( + + {event.type} + + ) : ( + + {event.origin as string} {event.status as string} + + )} + + ) + case 'field': { + const value = facetRegistry.getFieldValueForEvent(event, column.path) + return ( + + {value !== undefined && ( + getMenuItemsForPath(path ? `${column.path}.${path}` : column.path)} + formatValue={(path, value) => formatValue(path ? `${column.path}.${path}` : column.path, value)} + /> + )} + + ) + } + } + })} + + ) + } +) function formatValue(path: string, value: unknown) { if (typeof value === 'number') { diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsList.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsList.tsx index 1cfc5b7655..b3f78567de 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventsList.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventsList.tsx @@ -1,33 +1,213 @@ -import { Text, Center } from '@mantine/core' -import React from 'react' -import type { EventFilters } from '../../../hooks/useEvents' +import { Popover, Box, Text, Button, Flex, Autocomplete } from '@mantine/core' +import type { ForwardedRef, ReactNode } from 'react' +import React, { useMemo, useRef, useState, forwardRef, useCallback } from 'react' +import type { EventFilters, FacetRegistry } from '../../../hooks/useEvents' import type { SdkEvent } from '../../../sdkEvent' import { isRumViewEvent } from '../../../sdkEvent' +import type { EventListColumn } from './columnUtils' +import { getColumnTitle, DEFAULT_COLUMNS, includesColumn } from './columnUtils' import { EventRow } from './eventRow' import { Grid } from './grid' +import { ColumnDrag } from './columnDrag' -export function EventsList({ events, filters }: { events: SdkEvent[]; filters: EventFilters }) { - if (!events.length) { - return ( -
- - No events - -
- ) +export function EventsList({ + events, + filters, + facetRegistry, + columns, + onColumnsChange, +}: { + events: SdkEvent[] + filters: EventFilters + facetRegistry: FacetRegistry + columns: EventListColumn[] + onColumnsChange: (columns: EventListColumn[]) => void +}) { + const headerRowRef = useRef(null) + + const onAddColumn = useCallback( + (column: EventListColumn) => { + if (!includesColumn(columns, column)) { + onColumnsChange(columns.concat(column)) + } + }, + [columns] + ) + + return ( + <> + + + {columns.map((column, index) => ( + + + {getColumnTitle(column)} + {index === columns.length - 1 && ( + + )} + + + ))} + + {events.map((event) => ( + + ))} + + + + + ) +} + +function AddColumnPopover({ + columns, + onColumnsChange, + facetRegistry, +}: { + columns: EventListColumn[] + onColumnsChange: (columns: EventListColumn[]) => void + facetRegistry: FacetRegistry +}) { + return ( + + + + + ({ background: theme.colorScheme === 'dark' ? theme.colors.dark[7] : theme.white })} + > + + {DEFAULT_COLUMNS.map((column) => ( + + ))} + + + + + ) +} + +function AddDefaultColumnButton({ + column, + columns, + onColumnsChange, +}: { + column: EventListColumn + columns: EventListColumn[] + onColumnsChange: (columns: EventListColumn[]) => void +}) { + if (includesColumn(columns, column)) { + return null } + return ( + + {getColumnTitle(column)} + + + ) +} + +function AddFieldColumn({ + columns, + onColumnsChange, + facetRegistry, +}: { + columns: EventListColumn[] + onColumnsChange: (columns: EventListColumn[]) => void + facetRegistry: FacetRegistry +}) { + const [input, setInput] = useState('') + + function addFieldColumn(path: string) { + const newColumn: EventListColumn = { path, type: 'field' } + if (!includesColumn(columns, newColumn)) { + onColumnsChange(columns.concat(newColumn)) + } + } + + const allPaths = useMemo( + () => + Array.from(facetRegistry.getAllFieldPaths()).sort((a, b) => { + // Sort private fields last + if (a.startsWith('_dd') !== b.startsWith('_dd')) { + if (a.startsWith('_dd')) { + return 1 + } + if (b.startsWith('_dd')) { + return -1 + } + } + return a < b ? -1 : 1 + }), + [] + ) + + const suggestions = allPaths.filter((path) => path.includes(input)) return ( - - - Date - Type - Description - - {events.map((event) => ( - - ))} - + + { + event.preventDefault() + addFieldColumn(input) + }} + sx={{ display: 'contents' }} + > + ) => { + const inputIndex = value.indexOf(input) + let renderedValue: ReactNode + if (inputIndex < 0) { + renderedValue = value + } else { + renderedValue = ( + <> + {value.slice(0, inputIndex)} + + {value.slice(inputIndex, inputIndex + input.length)} + + {value.slice(inputIndex + input.length)} + + ) + } + + return ( + + {renderedValue} + + ) + })} + onItemSubmit={({ value }) => addFieldColumn(value)} + /> + + + ) } diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsTab.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsTab.tsx index 80820e96f7..600b455768 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventsTab.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventsTab.tsx @@ -1,26 +1,52 @@ import React from 'react' +import { Center, Text } from '@mantine/core' import type { EventFilters, FacetRegistry } from '../../../hooks/useEvents' import { TabBase } from '../../tabBase' import type { SdkEvent } from '../../../sdkEvent' import { EventsTabTop } from './eventsTabTop' import { EventsList } from './eventsList' import { EventsTabSide } from './eventsTabSide' +import type { EventListColumn } from './columnUtils' interface EventsTabProps { events: SdkEvent[] facetRegistry: FacetRegistry | undefined filters: EventFilters - onFiltered: (filters: EventFilters) => void + onFiltersChange: (filters: EventFilters) => void + columns: EventListColumn[] + onColumnsChange: (columns: EventListColumn[]) => void clear: () => void } -export function EventsTab({ events, facetRegistry, filters, onFiltered, clear }: EventsTabProps) { +export function EventsTab({ + events, + facetRegistry, + filters, + onFiltersChange, + columns, + onColumnsChange, + clear, +}: EventsTabProps) { return ( } - leftSide={} + top={} + leftSide={} > - + {events.length === 0 || !facetRegistry ? ( +
+ + No events + +
+ ) : ( + + )}
) } diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx index 09aea3150e..7270173426 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabSide.tsx @@ -6,18 +6,18 @@ import { FacetList } from './facetList' export function EventsTabSide({ facetRegistry, filters, - onFiltered, + onFiltersChange, }: { facetRegistry?: FacetRegistry filters: EventFilters - onFiltered: (filters: EventFilters) => void + onFiltersChange: (filters: EventFilters) => void }) { return ( onFiltered({ ...filters, outdatedVersions: !e.target.checked })} + onChange={(e) => onFiltersChange({ ...filters, outdatedVersions: !e.target.checked })} mb="sm" /> @@ -26,7 +26,7 @@ export function EventsTabSide({ facetRegistry={facetRegistry} excludedFacetValues={filters.excludedFacetValues} onExcludedFacetValuesChange={(newExcludedFacetValues) => - onFiltered({ ...filters, excludedFacetValues: newExcludedFacetValues }) + onFiltersChange({ ...filters, excludedFacetValues: newExcludedFacetValues }) } /> )} diff --git a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabTop.tsx b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabTop.tsx index 578f33ef29..2fa58ed6c9 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/eventsTabTop.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/eventsTabTop.tsx @@ -5,11 +5,11 @@ import type { EventFilters } from '../../../hooks/useEvents' export function EventsTabTop({ filters, - onFiltered, + onFiltersChange, clear, }: { filters: EventFilters - onFiltered: (filters: EventFilters) => void + onFiltersChange: (filters: EventFilters) => void clear: () => void }) { return ( @@ -18,7 +18,7 @@ export function EventsTabTop({ placeholder="Filter your events, syntax: 'type:view application.id:40d8ca4b'" value={filters.query} style={{ flexGrow: 1 }} - onChange={(event) => onFiltered({ ...filters, query: event.currentTarget.value })} + onChange={(event) => onFiltersChange({ ...filters, query: event.currentTarget.value })} className="dd-privacy-mask" /> diff --git a/developer-extension/src/panel/components/tabs/eventsTab/grid.tsx b/developer-extension/src/panel/components/tabs/eventsTab/grid.tsx index 18cd1b58ae..a1efe5785f 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/grid.tsx +++ b/developer-extension/src/panel/components/tabs/eventsTab/grid.tsx @@ -1,55 +1,92 @@ -import { Box, Text, useMantineTheme } from '@mantine/core' -import type { ComponentPropsWithoutRef, ReactNode } from 'react' -import React from 'react' +import type { BoxProps } from '@mantine/core' +import { Box, Text } from '@mantine/core' +import type { ComponentPropsWithoutRef, ForwardedRef, ReactNode } from 'react' +import React, { forwardRef } from 'react' +import { BORDER_RADIUS, separatorBorder } from '../../../uiUtils' -const HORIZONTAL_PADDING = 12 -const VERTICAL_PADDING = 6 +export const HORIZONTAL_PADDING = 16 +export const VERTICAL_PADDING = 6 export function Grid({ children, columnsCount }: { children: ReactNode; columnsCount: number }) { return ( 'auto').join(' ')} 1fr`, + gridTemplateColumns: `${Array.from({ length: columnsCount - 1 }, () => 'auto').join(' ')} minmax(200px, 1fr)`, }} + mx="md" > {children} ) } -Grid.HeaderCell = function ({ children }: { children: ReactNode }) { +Grid.HeaderCell = forwardRef(function ( + { children, ...props }: { children: ReactNode } & BoxProps & ComponentPropsWithoutRef<'div'>, + ref: ForwardedRef +) { return ( - + ({ + borderTop: separatorBorder(theme), + ':first-of-type': { borderTopLeftRadius: BORDER_RADIUS }, + ':last-of-type': { borderTopRightRadius: BORDER_RADIUS }, + cursor: props.onClick ? 'pointer' : 'default', + })} + > {children} ) -} +}) -Grid.Cell = function ({ children, center }: { children: ReactNode; center?: boolean }) { - const theme = useMantineTheme() - const borderColor = theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[3] +Grid.Cell = forwardRef(function ( + { + children, + center, + ...props + }: { children: ReactNode; center?: boolean } & BoxProps & ComponentPropsWithoutRef<'div'>, + ref: ForwardedRef +) { return ( ({ + position: 'relative', + borderBottom: separatorBorder(theme), + paddingLeft: HORIZONTAL_PADDING / 2, + paddingRight: HORIZONTAL_PADDING / 2, + paddingTop: VERTICAL_PADDING, + paddingBottom: VERTICAL_PADDING, + ':first-of-type': { + borderLeft: separatorBorder(theme), + paddingLeft: HORIZONTAL_PADDING, + }, + ':last-of-type': { + borderRight: separatorBorder(theme), + paddingRight: HORIZONTAL_PADDING, + }, + textAlign: center ? 'center' : undefined, + cursor: props.onClick ? 'pointer' : 'default', + }), + ...(Array.isArray(props.sx) ? props.sx : [props.sx]), + ]} > {children} ) -} +}) -Grid.Row = function ({ children, ...props }: { children: ReactNode } & ComponentPropsWithoutRef<'div'>) { +Grid.Row = forwardRef(function ( + { children, ...props }: { children: ReactNode } & ComponentPropsWithoutRef<'div'>, + ref: ForwardedRef +) { return ( ) -} +}) diff --git a/developer-extension/src/panel/components/tabs/eventsTab/index.ts b/developer-extension/src/panel/components/tabs/eventsTab/index.ts index 09950fc976..72bbd35fc9 100644 --- a/developer-extension/src/panel/components/tabs/eventsTab/index.ts +++ b/developer-extension/src/panel/components/tabs/eventsTab/index.ts @@ -1 +1,2 @@ export { EventsTab } from './eventsTab' +export { DEFAULT_COLUMNS } from './columnUtils' diff --git a/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts b/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts index 71ece99f12..91cb9923ae 100644 --- a/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts +++ b/developer-extension/src/panel/hooks/useEvents/facetRegistry.ts @@ -11,6 +11,7 @@ type EventFields = Map export class FacetRegistry { facetValueCounts: FacetValueCounts = new Map() eventFieldsCache: WeakMap = new WeakMap() + allEventFieldPaths: Set = new Set() addEvent(event: SdkEvent) { const fields = getAllFields(event) @@ -18,6 +19,9 @@ export class FacetRegistry { this.eventFieldsCache.set(event, fields) incrementFacetValueCounts(fields, this.facetValueCounts) + for (const fieldPath of fields.keys()) { + this.allEventFieldPaths.add(fieldPath) + } } getFieldValueForEvent(event: SdkEvent, fieldPath: FieldPath): FieldMultiValue | undefined { @@ -28,6 +32,10 @@ export class FacetRegistry { return this.facetValueCounts.get(fieldPath) || new Map() } + getAllFieldPaths() { + return this.allEventFieldPaths + } + clear() { this.facetValueCounts.clear() } @@ -130,7 +138,7 @@ export function getAllFields(event: object) { * Add the value to the fields map. If a value is already defined for the given path, store it as * an array to reflect all possible values for that path. */ - function pushField(path: string, value: FieldValue) { + function pushField(path: FieldPath, value: FieldValue) { const previousValue = fields.get(path) if (Array.isArray(previousValue)) { previousValue.push(value) diff --git a/developer-extension/src/panel/uiUtils.ts b/developer-extension/src/panel/uiUtils.ts index 4aa8f0eb58..f963d10307 100644 --- a/developer-extension/src/panel/uiUtils.ts +++ b/developer-extension/src/panel/uiUtils.ts @@ -4,12 +4,18 @@ export const BORDER_RADIUS = 8 // arbitrary export const CHECKBOX_WIDTH = 20 // the Mantine checkbox component happen to be 20px wide export const TABS_LIST_BORDER_WIDTH = 2 // the Mantine tabs list happen to have a 2px border +export function separatorBorder(theme: MantineTheme) { + return `1px solid ${borderColor(theme)}` +} + /** * Returns the same CSS border as the mantine TabsList */ export function tabsListBorder(theme: MantineTheme) { // https://github.com/mantinedev/mantine/blob/cf0f85faec56615ea5fbd7813e83bac60dbaefb7/src/mantine-core/src/Tabs/TabsList/TabsList.styles.ts#L25-L26 - return `${TABS_LIST_BORDER_WIDTH}px solid ${ - theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[3] - }` + return `${TABS_LIST_BORDER_WIDTH}px solid ${borderColor(theme)}` +} + +function borderColor(theme: MantineTheme) { + return theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[3] }