Improve table view

Summary:
added component name, root component name, duration, event type and better names

changelog: UIDebugger - added event debugger table view and side panel views

Reviewed By: lblasa

Differential Revision: D48559367

fbshipit-source-id: d357ecf654b4e443eac7673731a8be542e76dd48
This commit is contained in:
Luke De Feo
2023-08-23 01:51:31 -07:00
committed by Facebook GitHub Bot
parent 5f9000aa82
commit 206ef79cf9
8 changed files with 112 additions and 45 deletions

View File

@@ -35,6 +35,7 @@ export type SubtreeUpdateEvent = {
frameworkEvents?: FrameworkEvent[]; frameworkEvents?: FrameworkEvent[];
}; };
export type NodeMap = Map<Id, ClientNode>;
export type FrameworkEventType = string; export type FrameworkEventType = string;
export type FrameworkEventMetadata = { export type FrameworkEventMetadata = {
@@ -59,7 +60,7 @@ export type FrameworkEvent = {
nodeId: Id; nodeId: Id;
type: FrameworkEventType; type: FrameworkEventType;
timestamp: number; timestamp: number;
payload?: JSON; payload?: JsonObject;
duration?: number; duration?: number;
attribution?: FrameworkEventAttribution; attribution?: FrameworkEventAttribution;
thread?: 'main' | string; thread?: 'main' | string;

View File

@@ -11,6 +11,7 @@ import {Atom, _ReadOnlyAtom} from 'flipper-plugin';
import { import {
Id, Id,
FrameworkEventType, FrameworkEventType,
FrameworkEvent,
Inspectable, Inspectable,
Bounds, Bounds,
Tag, Tag,
@@ -73,6 +74,11 @@ export type NodeSelection = {
source: SelectionSource; source: SelectionSource;
}; };
export type AugmentedFrameworkEvent = FrameworkEvent & {
nodeName?: string;
rootComponentName?: string;
};
export type OnSelectNode = ( export type OnSelectNode = (
node: Id | undefined, node: Id | undefined,
source: SelectionSource, source: SelectionSource,

View File

@@ -16,14 +16,20 @@ import {
usePlugin, usePlugin,
} from 'flipper-plugin'; } from 'flipper-plugin';
import React, {useEffect, useRef} from 'react'; import React, {useEffect, useRef} from 'react';
import {FrameworkEvent, Id} from '../ClientTypes'; import {FrameworkEvent, Id, NodeMap} from '../ClientTypes';
import {plugin} from '../index'; import {plugin} from '../index';
import {Button, Tooltip} from 'antd'; 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 instance = usePlugin(plugin);
const managerRef = useRef<DataTableManager<FrameworkEvent> | null>(null); const managerRef = useRef<DataTableManager<AugmentedFrameworkEvent> | null>(
null,
);
useEffect(() => { useEffect(() => {
if (nodeId != null) { if (nodeId != null) {
@@ -52,23 +58,50 @@ export function FrameworkEventsTable({nodeId}: {nodeId: Id}) {
); );
} }
const columns: DataTableColumn<FrameworkEvent>[] = [ const columns: DataTableColumn<AugmentedFrameworkEvent>[] = [
{ {
key: 'timestamp', key: 'timestamp',
onRender: (row: FrameworkEvent) => { onRender: (row: FrameworkEvent) => formatTimestampMillis(row.timestamp),
return new Date(row.timestamp).toLocaleTimeString(); title: 'Timestamp',
},
},
{
key: 'treeId',
},
{
key: 'nodeId',
}, },
{ {
key: 'type', 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', 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,
}, },
]; ];

View File

@@ -103,7 +103,7 @@ export function Component() {
} }
if (viewMode.mode === 'frameworkEventsTable') { if (viewMode.mode === 'frameworkEventsTable') {
return <FrameworkEventsTable nodeId={viewMode.nodeId} />; return <FrameworkEventsTable nodeId={viewMode.nodeId} nodes={nodes} />;
} }
return ( return (

View File

@@ -36,6 +36,7 @@ import {last, startCase, uniqBy} from 'lodash';
import {FilterOutlined, TableOutlined} from '@ant-design/icons'; import {FilterOutlined, TableOutlined} from '@ant-design/icons';
import {ViewMode} from '../../../DesktopTypes'; import {ViewMode} from '../../../DesktopTypes';
import {MultiSelectableDropDownItem} from '../../shared/MultiSelectableDropDownItem'; import {MultiSelectableDropDownItem} from '../../shared/MultiSelectableDropDownItem';
import {formatDuration, formatTimestampMillis} from '../../../utils/timeUtils';
type Props = { type Props = {
node: ClientNode; node: ClientNode;
@@ -233,7 +234,7 @@ function EventDetails({
<Tag color={threadToColor(event.thread)}>{event.thread}</Tag> <Tag color={threadToColor(event.thread)}>{event.thread}</Tag>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label="Timestamp"> <Descriptions.Item label="Timestamp">
{formatTimestamp(event.timestamp)} {formatTimestampMillis(event.timestamp)}
</Descriptions.Item> </Descriptions.Item>
{event.duration && ( {event.duration && (
<Descriptions.Item label="Duration"> <Descriptions.Item label="Duration">
@@ -257,35 +258,14 @@ function EventDetails({
); );
} }
const options: Intl.DateTimeFormatOptions = { export const options: Intl.DateTimeFormatOptions = {
hour: 'numeric', hour: 'numeric',
minute: 'numeric', minute: 'numeric',
second: 'numeric', second: 'numeric',
hour12: false, hour12: false,
}; };
function formatTimestamp(timestamp: number): string {
const date = new Date(timestamp);
const formattedDate = new Intl.DateTimeFormat('en-US', options).format(date); export function eventTypeToName(eventType: string) {
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) {
return eventType.slice(eventType.lastIndexOf(frameworkEventSeparator) + 1); return eventType.slice(eventType.lastIndexOf(frameworkEventSeparator) + 1);
} }

View File

@@ -11,7 +11,6 @@ import {createDataSource, createState, PluginClient} from 'flipper-plugin';
import { import {
Events, Events,
FrameScanEvent, FrameScanEvent,
FrameworkEvent,
FrameworkEventType, FrameworkEventType,
Id, Id,
Metadata, Metadata,
@@ -29,11 +28,14 @@ import {
ReadOnlyUIState, ReadOnlyUIState,
LiveClientState, LiveClientState,
WireFrameMode, WireFrameMode,
AugmentedFrameworkEvent,
} from './DesktopTypes'; } from './DesktopTypes';
import {getStreamInterceptor} from './fb-stubs/StreamInterceptor'; import {getStreamInterceptor} from './fb-stubs/StreamInterceptor';
import {prefetchSourceFileLocation} from './components/fb-stubs/IDEContextMenu'; import {prefetchSourceFileLocation} from './components/fb-stubs/IDEContextMenu';
import {checkFocusedNodeStillActive} from './plugin/ClientDataUtils'; import {checkFocusedNodeStillActive} from './plugin/ClientDataUtils';
import {uiActions} from './plugin/uiActions'; import {uiActions} from './plugin/uiActions';
import {first} from 'lodash';
import {getNode} from './utils/map';
type PendingData = { type PendingData = {
metadata: Record<MetadataId, Metadata>; metadata: Record<MetadataId, Metadata>;
@@ -46,7 +48,7 @@ export function plugin(client: PluginClient<Events>) {
const streamInterceptor = getStreamInterceptor(client.device.os); const streamInterceptor = getStreamInterceptor(client.device.os);
const snapshot = createState<SnapshotInfo | null>(null); const snapshot = createState<SnapshotInfo | null>(null);
const nodesAtom = createState<Map<Id, ClientNode>>(new Map()); const nodesAtom = createState<Map<Id, ClientNode>>(new Map());
const frameworkEvents = createDataSource<FrameworkEvent>([], { const frameworkEvents = createDataSource<AugmentedFrameworkEvent>([], {
indices: [['nodeId']], indices: [['nodeId']],
limit: 10000, limit: 10000,
}); });
@@ -223,7 +225,7 @@ export function plugin(client: PluginClient<Events>) {
lastFrameTime = frameScan.frameTime; lastFrameTime = frameScan.frameTime;
} }
applyFrameworkEvents(frameScan); applyFrameworkEvents(frameScan, processedNodes);
return true; return true;
} catch (error) { } catch (error) {
@@ -233,9 +235,18 @@ export function plugin(client: PluginClient<Events>) {
} }
}; };
function applyFrameworkEvents(frameScan: FrameScanEvent) { function applyFrameworkEvents(
frameScan: FrameScanEvent,
nodes: Map<Id, ClientNode>,
) {
for (const frameworkEvent of frameScan.frameworkEvents ?? []) { 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) { if (uiState.isPaused.get() === true) {

View File

@@ -9,7 +9,10 @@
import {ClientNode, Id} from '../ClientTypes'; import {ClientNode, Id} from '../ClientTypes';
export function getNode(id: Id | undefined, nodes: Map<Id, ClientNode>) { export function getNode(
id: Id | undefined,
nodes: Map<Id, ClientNode>,
): ClientNode | undefined {
//map just returns undefined when you pass null or undefined as a key //map just returns undefined when you pass null or undefined as a key
return nodes.get(id!); return nodes.get(id!);
} }

View File

@@ -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`;
}
}