diff --git a/desktop/plugins/public/ui-debugger/ClientTypes.tsx b/desktop/plugins/public/ui-debugger/ClientTypes.tsx index 374e7bd6d..9027db84b 100644 --- a/desktop/plugins/public/ui-debugger/ClientTypes.tsx +++ b/desktop/plugins/public/ui-debugger/ClientTypes.tsx @@ -35,6 +35,7 @@ export type SubtreeUpdateEvent = { frameworkEvents?: FrameworkEvent[]; }; +export type NodeMap = Map; export type FrameworkEventType = string; export type FrameworkEventMetadata = { @@ -59,7 +60,7 @@ export type FrameworkEvent = { nodeId: Id; type: FrameworkEventType; timestamp: number; - payload?: JSON; + payload?: JsonObject; duration?: number; attribution?: FrameworkEventAttribution; thread?: 'main' | string; diff --git a/desktop/plugins/public/ui-debugger/DesktopTypes.tsx b/desktop/plugins/public/ui-debugger/DesktopTypes.tsx index c9f708267..b8326b745 100644 --- a/desktop/plugins/public/ui-debugger/DesktopTypes.tsx +++ b/desktop/plugins/public/ui-debugger/DesktopTypes.tsx @@ -11,6 +11,7 @@ import {Atom, _ReadOnlyAtom} from 'flipper-plugin'; import { Id, FrameworkEventType, + FrameworkEvent, Inspectable, Bounds, Tag, @@ -73,6 +74,11 @@ export type NodeSelection = { source: SelectionSource; }; +export type AugmentedFrameworkEvent = FrameworkEvent & { + nodeName?: string; + rootComponentName?: string; +}; + export type OnSelectNode = ( node: Id | undefined, source: SelectionSource, diff --git a/desktop/plugins/public/ui-debugger/components/FrameworkEventsTable.tsx b/desktop/plugins/public/ui-debugger/components/FrameworkEventsTable.tsx index db90e6b72..da283004e 100644 --- a/desktop/plugins/public/ui-debugger/components/FrameworkEventsTable.tsx +++ b/desktop/plugins/public/ui-debugger/components/FrameworkEventsTable.tsx @@ -16,14 +16,20 @@ import { usePlugin, } from 'flipper-plugin'; import React, {useEffect, useRef} from 'react'; -import {FrameworkEvent, Id} from '../ClientTypes'; +import {FrameworkEvent, Id, NodeMap} from '../ClientTypes'; import {plugin} from '../index'; import {Button, Tooltip} from 'antd'; +import {AugmentedFrameworkEvent} from '../DesktopTypes'; +import {formatDuration, formatTimestampMillis} from '../utils/timeUtils'; +import {eventTypeToName} from './sidebar/inspector/FrameworkEventsInspector'; +import {startCase} from 'lodash'; -export function FrameworkEventsTable({nodeId}: {nodeId: Id}) { +export function FrameworkEventsTable({nodeId}: {nodeId: Id; nodes: NodeMap}) { const instance = usePlugin(plugin); - const managerRef = useRef | null>(null); + const managerRef = useRef | null>( + null, + ); useEffect(() => { if (nodeId != null) { @@ -52,23 +58,50 @@ export function FrameworkEventsTable({nodeId}: {nodeId: Id}) { ); } -const columns: DataTableColumn[] = [ +const columns: DataTableColumn[] = [ { key: 'timestamp', - onRender: (row: FrameworkEvent) => { - return new Date(row.timestamp).toLocaleTimeString(); - }, - }, - { - key: 'treeId', - }, - { - key: 'nodeId', + onRender: (row: FrameworkEvent) => formatTimestampMillis(row.timestamp), + title: 'Timestamp', }, { key: 'type', + title: 'Event type', + onRender: (row: FrameworkEvent) => eventTypeToName(row.type), + }, + { + key: 'duration', + title: 'Duration', + onRender: (row: FrameworkEvent) => + row.duration != null ? formatDuration(row.duration) : null, + }, + { + key: 'treeId', + title: 'TreeId', + }, + { + key: 'rootComponentName', + title: 'Root component name', + }, + { + key: 'nodeId', + title: 'Component ID', + }, + { + key: 'nodeName', + title: 'Component name', }, { key: 'thread', + title: 'Thread', + onRender: (row: FrameworkEvent) => startCase(row.thread), + }, + { + key: 'payload', + title: 'Payload', + onRender: (row: FrameworkEvent) => + Object.keys(row.payload ?? {}).length > 0 + ? JSON.stringify(row.payload) + : null, }, ]; diff --git a/desktop/plugins/public/ui-debugger/components/main.tsx b/desktop/plugins/public/ui-debugger/components/main.tsx index 90ed95d4b..2b52a1720 100644 --- a/desktop/plugins/public/ui-debugger/components/main.tsx +++ b/desktop/plugins/public/ui-debugger/components/main.tsx @@ -103,7 +103,7 @@ export function Component() { } if (viewMode.mode === 'frameworkEventsTable') { - return ; + return ; } return ( diff --git a/desktop/plugins/public/ui-debugger/components/sidebar/inspector/FrameworkEventsInspector.tsx b/desktop/plugins/public/ui-debugger/components/sidebar/inspector/FrameworkEventsInspector.tsx index a8abc3596..a19704dde 100644 --- a/desktop/plugins/public/ui-debugger/components/sidebar/inspector/FrameworkEventsInspector.tsx +++ b/desktop/plugins/public/ui-debugger/components/sidebar/inspector/FrameworkEventsInspector.tsx @@ -36,6 +36,7 @@ import {last, startCase, uniqBy} from 'lodash'; import {FilterOutlined, TableOutlined} from '@ant-design/icons'; import {ViewMode} from '../../../DesktopTypes'; import {MultiSelectableDropDownItem} from '../../shared/MultiSelectableDropDownItem'; +import {formatDuration, formatTimestampMillis} from '../../../utils/timeUtils'; type Props = { node: ClientNode; @@ -233,7 +234,7 @@ function EventDetails({ {event.thread} - {formatTimestamp(event.timestamp)} + {formatTimestampMillis(event.timestamp)} {event.duration && ( @@ -257,35 +258,14 @@ function EventDetails({ ); } -const options: Intl.DateTimeFormatOptions = { +export const options: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: false, }; -function formatTimestamp(timestamp: number): string { - const date = new Date(timestamp); - const formattedDate = new Intl.DateTimeFormat('en-US', options).format(date); - const milliseconds = date.getMilliseconds(); - - return `${formattedDate}.${milliseconds.toString().padStart(3, '0')}`; -} - -function formatDuration(nanoseconds: number): string { - if (nanoseconds < 1_000) { - return `${nanoseconds} nanoseconds`; - } else if (nanoseconds < 1_000_000) { - return `${(nanoseconds / 1_000).toFixed(2)} microseconds`; - } else if (nanoseconds < 1_000_000_000) { - return `${(nanoseconds / 1_000_000).toFixed(2)} milliseconds`; - } else if (nanoseconds < 1_000_000_000_000) { - return `${(nanoseconds / 1_000_000_000).toFixed(2)} seconds`; - } else { - return `${(nanoseconds / (1_000_000_000 * 60)).toFixed(2)} minutes`; - } -} -function eventTypeToName(eventType: string) { +export function eventTypeToName(eventType: string) { return eventType.slice(eventType.lastIndexOf(frameworkEventSeparator) + 1); } diff --git a/desktop/plugins/public/ui-debugger/index.tsx b/desktop/plugins/public/ui-debugger/index.tsx index 006013386..5a07a2d05 100644 --- a/desktop/plugins/public/ui-debugger/index.tsx +++ b/desktop/plugins/public/ui-debugger/index.tsx @@ -11,7 +11,6 @@ import {createDataSource, createState, PluginClient} from 'flipper-plugin'; import { Events, FrameScanEvent, - FrameworkEvent, FrameworkEventType, Id, Metadata, @@ -29,11 +28,14 @@ import { ReadOnlyUIState, LiveClientState, WireFrameMode, + AugmentedFrameworkEvent, } from './DesktopTypes'; import {getStreamInterceptor} from './fb-stubs/StreamInterceptor'; import {prefetchSourceFileLocation} from './components/fb-stubs/IDEContextMenu'; import {checkFocusedNodeStillActive} from './plugin/ClientDataUtils'; import {uiActions} from './plugin/uiActions'; +import {first} from 'lodash'; +import {getNode} from './utils/map'; type PendingData = { metadata: Record; @@ -46,7 +48,7 @@ export function plugin(client: PluginClient) { const streamInterceptor = getStreamInterceptor(client.device.os); const snapshot = createState(null); const nodesAtom = createState>(new Map()); - const frameworkEvents = createDataSource([], { + const frameworkEvents = createDataSource([], { indices: [['nodeId']], limit: 10000, }); @@ -223,7 +225,7 @@ export function plugin(client: PluginClient) { lastFrameTime = frameScan.frameTime; } - applyFrameworkEvents(frameScan); + applyFrameworkEvents(frameScan, processedNodes); return true; } catch (error) { @@ -233,9 +235,18 @@ export function plugin(client: PluginClient) { } }; - function applyFrameworkEvents(frameScan: FrameScanEvent) { + function applyFrameworkEvents( + frameScan: FrameScanEvent, + nodes: Map, + ) { for (const frameworkEvent of frameScan.frameworkEvents ?? []) { - frameworkEvents.append(frameworkEvent); + const treeRoot = getNode(frameworkEvent.treeId, nodes); + const treeRootFirstChild = getNode(first(treeRoot?.children), nodes); + frameworkEvents.append({ + ...frameworkEvent, + nodeName: nodes.get(frameworkEvent.nodeId)?.name, + rootComponentName: treeRootFirstChild?.name, + }); } if (uiState.isPaused.get() === true) { diff --git a/desktop/plugins/public/ui-debugger/utils/map.tsx b/desktop/plugins/public/ui-debugger/utils/map.tsx index 5c839f96b..2f66d5b9c 100644 --- a/desktop/plugins/public/ui-debugger/utils/map.tsx +++ b/desktop/plugins/public/ui-debugger/utils/map.tsx @@ -9,7 +9,10 @@ import {ClientNode, Id} from '../ClientTypes'; -export function getNode(id: Id | undefined, nodes: Map) { +export function getNode( + id: Id | undefined, + nodes: Map, +): ClientNode | undefined { //map just returns undefined when you pass null or undefined as a key return nodes.get(id!); } diff --git a/desktop/plugins/public/ui-debugger/utils/timeUtils.tsx b/desktop/plugins/public/ui-debugger/utils/timeUtils.tsx new file mode 100644 index 000000000..b2c6a47f1 --- /dev/null +++ b/desktop/plugins/public/ui-debugger/utils/timeUtils.tsx @@ -0,0 +1,33 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {options} from '../components/sidebar/inspector/FrameworkEventsInspector'; + +export function formatTimestampMillis(timestamp: number): string { + const date = new Date(timestamp); + + const formattedDate = new Intl.DateTimeFormat('en-US', options).format(date); + const milliseconds = date.getMilliseconds(); + + return `${formattedDate}.${milliseconds.toString().padStart(3, '0')}`; +} + +export function formatDuration(nanoseconds: number): string { + if (nanoseconds < 1_000) { + return `${nanoseconds} nanoseconds`; + } else if (nanoseconds < 1_000_000) { + return `${(nanoseconds / 1_000).toFixed(2)} microseconds`; + } else if (nanoseconds < 1_000_000_000) { + return `${(nanoseconds / 1_000_000).toFixed(2)} milliseconds`; + } else if (nanoseconds < 1_000_000_000_000) { + return `${(nanoseconds / 1_000_000_000).toFixed(2)} seconds`; + } else { + return `${(nanoseconds / (1_000_000_000 * 60)).toFixed(2)} minutes`; + } +}