Skip to content

Commit

Permalink
✨ add columns to the event list
Browse files Browse the repository at this point in the history
  • Loading branch information
BenoitZugmeyer committed Aug 8, 2023
1 parent 78a7b77 commit bfd9f6b
Show file tree
Hide file tree
Showing 8 changed files with 718 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -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
}
121 changes: 121 additions & 0 deletions developer-extension/src/panel/components/tabs/eventsTab/drag.ts
Original file line number Diff line number Diff line change
@@ -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]
* | \______________
* | \
* <returns true> <returns false>
* | ____ |
* | / \ (end)
* [onMove] )
* | \ \____/
* | \______________
* | \
* <drop in the window> |
* | <stop() called or drop out of the window>
* | |
* [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 }
}
}
}
131 changes: 96 additions & 35 deletions developer-extension/src/panel/components/tabs/eventsTab/eventRow.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -15,7 +15,10 @@ import { isTelemetryEvent, isLogEvent, isRumEvent } from '../../../sdkEvent'
import { formatDuration } from '../../../formatNumber'
import { 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',
Expand Down Expand Up @@ -46,41 +49,99 @@ const RESOURCE_TYPE_LABELS: Record<string, string | undefined> = {
other: 'Other',
}

export const EventRow = React.memo(({ event }: { event: SdkEvent }) => {
const [isCollapsed, setIsCollapsed] = useState(true)
const collapseRef = useRef<HTMLDivElement>(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 collapseRef = useRef<HTMLDivElement>(null)

return (
<Grid.Row
onClick={(event) => {
if (collapseRef.current?.contains(event.target as Node)) {
// Ignore clicks on the collapsible area
return
}
setIsCollapsed((previous) => !previous)
}}
>
<Grid.Cell>{new Date(event.date).toLocaleTimeString()}</Grid.Cell>
<Grid.Cell center>
{isRumEvent(event) || isTelemetryEvent(event) ? (
<Badge variant="outline" color={RUM_EVENT_TYPE_COLOR[event.type]}>
{event.type}
</Badge>
) : (
<Badge variant="dot" color={LOG_STATUS_COLOR[event.status]}>
{event.origin as string} {event.status as string}
</Badge>
)}
</Grid.Cell>
<Grid.Cell>
<EventDescription event={event} />
<LazyCollapse in={!isCollapsed} ref={collapseRef}>
<Json value={event} defaultCollapseLevel={0} />
</LazyCollapse>
</Grid.Cell>
</Grid.Row>
)
})
function getMenuItemsForPath(path: string, _value: unknown) {
const newColumn: EventListColumn = { type: 'field', path }
if (!path || includesColumn(columns, newColumn)) {
return null
}
return (
<>
<Menu.Item
onClick={() => {
onAddColumn(newColumn)
}}
>
Add column
</Menu.Item>
</>
)
}

return (
<Grid.Row>
{columns.map((column): React.ReactElement => {
switch (column.type) {
case 'date':
return <Grid.Cell key="date">{new Date(event.date).toLocaleTimeString()}</Grid.Cell>
case 'description':
return (
<Grid.Cell
key="description"
onClick={(event) => {
if (collapseRef.current?.contains(event.target as Node)) {
// Ignore clicks on the collapsible area
return
}
setIsCollapsed((previous) => !previous)
}}
>
<EventDescription event={event} />
<LazyCollapse in={!isCollapsed} ref={collapseRef}>
<Json
value={event}
defaultCollapseLevel={0}
getMenuItemsForPath={getMenuItemsForPath}
mt="xs"
sx={{ display: 'block' }}
/>
</LazyCollapse>
</Grid.Cell>
)
case 'type':
return (
<Grid.Cell key="type" center>
{isRumEvent(event) || isTelemetryEvent(event) ? (
<Badge variant="outline" color={RUM_EVENT_TYPE_COLOR[event.type]}>
{event.type}
</Badge>
) : (
<Badge variant="dot" color={LOG_STATUS_COLOR[event.status]}>
{event.origin as string} {event.status as string}
</Badge>
)}
</Grid.Cell>
)
case 'field': {
const value = facetRegistry.getFieldValueForEvent(event, column.path)
return (
<Grid.Cell key={`field-${column.path}`}>
{value !== undefined && (
<Json value={value} defaultCollapseLevel={0} getMenuItemsForPath={getMenuItemsForPath} />
)}
</Grid.Cell>
)
}
}
})}
</Grid.Row>
)
}
)

export const EventDescription = React.memo(({ event }: { event: SdkEvent }) => {
if (isRumEvent(event)) {
Expand Down
Loading

0 comments on commit bfd9f6b

Please sign in to comment.