Add Framework event table

Summary: Very basic framework events table, quite useful for debugging will add more to this soon

Reviewed By: lblasa

Differential Revision: D47520035

fbshipit-source-id: 10f4572dd4ed3529324f03a969773c7e91fde030
This commit is contained in:
Luke De Feo
2023-07-21 07:17:31 -07:00
committed by Facebook GitHub Bot
parent db7aa9eeaf
commit 4df0ad4d35
5 changed files with 166 additions and 43 deletions

View File

@@ -0,0 +1,71 @@
/**
* 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 {PartitionOutlined} from '@ant-design/icons';
import {
DataTable,
DataTableColumn,
DataTableManager,
Layout,
usePlugin,
} from 'flipper-plugin';
import React, {useEffect, useRef} from 'react';
import {FrameworkEvent, Id} from '../types';
import {plugin} from '../index';
import {Button, Tooltip} from 'antd';
export function FrameworkEventsTable({rootTreeId}: {rootTreeId?: Id}) {
const instance = usePlugin(plugin);
const managerRef = useRef<DataTableManager<FrameworkEvent> | null>(null);
useEffect(() => {
if (rootTreeId != null) {
managerRef.current?.resetFilters();
managerRef.current?.addColumnFilter('nodeId', rootTreeId as string);
}
}, [rootTreeId]);
return (
<Layout.Container grow>
<DataTable<FrameworkEvent>
dataSource={instance.frameworkEvents}
tableManagerRef={managerRef}
columns={columns}
extraActions={
<Tooltip title="Back to tree">
<Button
onClick={() => {
instance.uiActions.onSetViewMode({mode: 'default'});
}}
icon={<PartitionOutlined />}></Button>
</Tooltip>
}
/>
</Layout.Container>
);
}
const columns: DataTableColumn<FrameworkEvent>[] = [
{
key: 'timestamp',
onRender: (row: FrameworkEvent) => {
return new Date(row.timestamp).toLocaleTimeString();
},
},
{
key: 'nodeId',
},
{
key: 'type',
},
{
key: 'thread',
},
];

View File

@@ -13,6 +13,7 @@ import {
Id, Id,
OnSelectNode, OnSelectNode,
UINode, UINode,
ViewMode,
} from '../types'; } from '../types';
import React, { import React, {
ReactNode, ReactNode,
@@ -52,6 +53,7 @@ import {
FullscreenExitOutlined, FullscreenExitOutlined,
FullscreenOutlined, FullscreenOutlined,
SnippetsOutlined, SnippetsOutlined,
TableOutlined,
} from '@ant-design/icons'; } from '@ant-design/icons';
const {Text} = Typography; const {Text} = Typography;
@@ -216,9 +218,11 @@ export function Tree2({nodes, rootId}: {nodes: Map<Id, UINode>; rootId: Id}) {
text={searchTerm} text={searchTerm}
highlightColor={theme.searchHighlightBackground.yellow}> highlightColor={theme.searchHighlightBackground.yellow}>
<ContextMenu <ContextMenu
frameworkEvents={instance.frameworkEvents}
focusedNodeId={focusedNode} focusedNodeId={focusedNode}
hoveredNodeId={hoveredNode} hoveredNodeId={hoveredNode}
nodes={nodes} nodes={nodes}
onSetViewMode={instance.uiActions.onSetViewMode}
onContextMenuOpen={instance.uiActions.onContextMenuOpen} onContextMenuOpen={instance.uiActions.onContextMenuOpen}
onFocusNode={instance.uiActions.onFocusNode}> onFocusNode={instance.uiActions.onFocusNode}>
<div <div
@@ -504,18 +508,22 @@ const DecorationImage = styled.img({
const renderDepthOffset = 12; const renderDepthOffset = 12;
const ContextMenu: React.FC<{ const ContextMenu: React.FC<{
frameworkEvents: DataSource<FrameworkEvent>;
nodes: Map<Id, UINode>; nodes: Map<Id, UINode>;
hoveredNodeId?: Id; hoveredNodeId?: Id;
focusedNodeId?: Id; focusedNodeId?: Id;
onFocusNode: (id?: Id) => void; onFocusNode: (id?: Id) => void;
onContextMenuOpen: (open: boolean) => void; onContextMenuOpen: (open: boolean) => void;
onSetViewMode: (viewMode: ViewMode) => void;
}> = ({ }> = ({
nodes, nodes,
frameworkEvents,
hoveredNodeId, hoveredNodeId,
children, children,
focusedNodeId, focusedNodeId,
onFocusNode, onFocusNode,
onContextMenuOpen, onContextMenuOpen,
onSetViewMode,
}) => { }) => {
const copyItems: ReactNode[] = []; const copyItems: ReactNode[] = [];
const hoveredNode = nodes.get(hoveredNodeId ?? Number.MAX_SAFE_INTEGER); const hoveredNode = nodes.get(hoveredNodeId ?? Number.MAX_SAFE_INTEGER);
@@ -579,6 +587,25 @@ const ContextMenu: React.FC<{
}} }}
/> />
); );
const matchingFrameworkEvents =
(hoveredNode &&
frameworkEvents.getAllRecordsByIndex({nodeId: hoveredNode.id})) ??
[];
const frameworkEventsTable = matchingFrameworkEvents.length > 0 && (
<UIDebuggerMenuItem
text="Explore events"
onClick={() => {
onSetViewMode({
mode: 'frameworkEventsTable',
treeRootId: hoveredNode?.id ?? '',
});
}}
icon={<TableOutlined />}
/>
);
return ( return (
<Dropdown <Dropdown
onVisibleChange={(visible) => { onVisibleChange={(visible) => {
@@ -588,8 +615,12 @@ const ContextMenu: React.FC<{
<Menu> <Menu>
{focus} {focus}
{removeFocus} {removeFocus}
{(focus || removeFocus) && <Menu.Divider key="divider-focus" />} {frameworkEventsTable}
{(focus || removeFocus || frameworkEventsTable) && (
<Menu.Divider key="divider-focus" />
)}
{copyItems} {copyItems}
{hoveredNode && <IDEContextMenuItems key="ide" node={hoveredNode} />} {hoveredNode && <IDEContextMenuItems key="ide" node={hoveredNode} />}
</Menu> </Menu>
)} )}

View File

@@ -28,6 +28,7 @@ import {QueryClientProvider} from 'react-query';
import {Tree2} from './Tree'; import {Tree2} from './Tree';
import {StreamInterceptorErrorView} from './StreamInterceptorErrorView'; import {StreamInterceptorErrorView} from './StreamInterceptorErrorView';
import {queryClient} from '../reactQuery'; import {queryClient} from '../reactQuery';
import {FrameworkEventsTable} from './FrameworkEventsTable';
export function Component() { export function Component() {
const instance = usePlugin(plugin); const instance = usePlugin(plugin);
@@ -41,6 +42,7 @@ export function Component() {
useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show)); useHotkeys('ctrl+i', () => setShowPerfStats((show) => !show));
const viewMode = useValue(instance.uiState.viewMode);
const [bottomPanelComponent, setBottomPanelComponent] = useState< const [bottomPanelComponent, setBottomPanelComponent] = useState<
ReactNode | undefined ReactNode | undefined
>(); >();
@@ -96,7 +98,12 @@ export function Component() {
<Spin data-testid="loading-indicator" /> <Spin data-testid="loading-indicator" />
</Centered> </Centered>
); );
} else { }
if (viewMode.mode === 'frameworkEventsTable') {
return <FrameworkEventsTable rootTreeId={viewMode.treeRootId} />;
}
return ( return (
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<Layout.Container grow padh="small" padv="medium"> <Layout.Container grow padh="small" padv="medium">
@@ -139,7 +146,6 @@ export function Component() {
</QueryClientProvider> </QueryClientProvider>
); );
} }
}
export function Centered(props: {children: React.ReactNode}) { export function Centered(props: {children: React.ReactNode}) {
return ( return (

View File

@@ -31,6 +31,7 @@ import {
UIActions, UIActions,
UINode, UINode,
UIState, UIState,
ViewMode,
} from './types'; } from './types';
import {Draft} from 'immer'; import {Draft} from 'immer';
import {tracker} from './tracker'; import {tracker} from './tracker';
@@ -202,6 +203,8 @@ export function plugin(client: PluginClient<Events>) {
const uiState: UIState = { const uiState: UIState = {
isConnected: createState(false), isConnected: createState(false),
viewMode: createState({mode: 'default'}),
//used to disabled hover effects which cause rerenders and mess up the existing context menu //used to disabled hover effects which cause rerenders and mess up the existing context menu
isContextMenuOpen: createState<boolean>(false), isContextMenuOpen: createState<boolean>(false),
@@ -461,6 +464,10 @@ function uiActions(uiState: UIState, nodes: Atom<Map<Id, UINode>>): UIActions {
uiState.filterMainThreadMonitoring.set(toggled); uiState.filterMainThreadMonitoring.set(toggled);
}; };
const onSetViewMode = (viewMode: ViewMode) => {
uiState.viewMode.set(viewMode);
};
return { return {
onExpandNode, onExpandNode,
onCollapseNode, onCollapseNode,
@@ -470,6 +477,7 @@ function uiActions(uiState: UIState, nodes: Atom<Map<Id, UINode>>): UIActions {
onFocusNode, onFocusNode,
setVisualiserWidth, setVisualiserWidth,
onSetFilterMainThreadMonitoring, onSetFilterMainThreadMonitoring,
onSetViewMode,
}; };
} }

View File

@@ -10,6 +10,7 @@
import {Atom} from 'flipper-plugin'; import {Atom} from 'flipper-plugin';
export type UIState = { export type UIState = {
viewMode: Atom<ViewMode>;
isConnected: Atom<boolean>; isConnected: Atom<boolean>;
isPaused: Atom<boolean>; isPaused: Atom<boolean>;
streamState: Atom<StreamState>; streamState: Atom<StreamState>;
@@ -25,6 +26,10 @@ export type UIState = {
filterMainThreadMonitoring: Atom<boolean>; filterMainThreadMonitoring: Atom<boolean>;
}; };
export type ViewMode =
| {mode: 'default'}
| {mode: 'frameworkEventsTable'; treeRootId: Id};
export type NodeSelection = { export type NodeSelection = {
id: Id; id: Id;
source: SelectionSource; source: SelectionSource;
@@ -44,7 +49,9 @@ export type UIActions = {
onCollapseNode: (node: Id) => void; onCollapseNode: (node: Id) => void;
setVisualiserWidth: (width: number) => void; setVisualiserWidth: (width: number) => void;
onSetFilterMainThreadMonitoring: (toggled: boolean) => void; onSetFilterMainThreadMonitoring: (toggled: boolean) => void;
onSetViewMode: (viewMode: ViewMode) => void;
}; };
export type SelectionSource = 'visualiser' | 'tree' | 'keyboard'; export type SelectionSource = 'visualiser' | 'tree' | 'keyboard';
export type StreamState = export type StreamState =