Summary: Listen to framework events and store in a map based on node id Added UI to allow for monitoring framework event types. The event type is a string separated by : Each segment of this string represents a level in the dialog hierachy. For example Litho:Layout:StateUpdateSync would have levels, Litho Layout StateUpdateSync When event type monitored and event comes in for a node flash the visualiser node briefly Reviewed By: lblasa Differential Revision: D42074988 fbshipit-source-id: 52458ad87ab84bf7b1749e87be516ed73106a6c0
266 lines
6.9 KiB
TypeScript
266 lines
6.9 KiB
TypeScript
/**
|
|
* 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 React, {useState} from 'react';
|
|
import {plugin} from '../index';
|
|
import {
|
|
Button,
|
|
Input,
|
|
Modal,
|
|
Tooltip,
|
|
Dropdown,
|
|
Menu,
|
|
Typography,
|
|
TreeSelect,
|
|
Space,
|
|
} from 'antd';
|
|
import {
|
|
MoreOutlined,
|
|
PauseCircleOutlined,
|
|
PlayCircleOutlined,
|
|
} from '@ant-design/icons';
|
|
import {usePlugin, useValue, Layout} from 'flipper-plugin';
|
|
import {FrameworkEventType} from '../types';
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
|
|
export const Controls: React.FC = () => {
|
|
const instance = usePlugin(plugin);
|
|
const searchTerm = useValue(instance.uiState.searchTerm);
|
|
const isPaused = useValue(instance.uiState.isPaused);
|
|
|
|
const frameworkEventMonitoring: Map<FrameworkEventType, boolean> = useValue(
|
|
instance.uiState.frameworkEventMonitoring,
|
|
);
|
|
|
|
const onSetEventMonitored: (
|
|
eventType: FrameworkEventType,
|
|
monitored: boolean,
|
|
) => void = (eventType: FrameworkEventType, monitored: boolean) => {
|
|
instance.uiState.frameworkEventMonitoring.update((draft) =>
|
|
draft.set(eventType, monitored),
|
|
);
|
|
};
|
|
|
|
return (
|
|
<Layout.Horizontal pad="small" gap="small">
|
|
<Input
|
|
value={searchTerm}
|
|
onChange={(e) => instance.uiState.searchTerm.set(e.target.value)}
|
|
/>
|
|
<Button
|
|
type="default"
|
|
shape="circle"
|
|
onClick={() => instance.setPlayPause(!instance.uiState.isPaused.get())}
|
|
icon={
|
|
<Tooltip
|
|
title={isPaused ? 'Resume live updates' : 'Pause incoming updates'}>
|
|
{isPaused ? <PlayCircleOutlined /> : <PauseCircleOutlined />}
|
|
</Tooltip>
|
|
}></Button>
|
|
<MoreOptionsMenu
|
|
onSetEventMonitored={onSetEventMonitored}
|
|
frameworkEventTypes={[...frameworkEventMonitoring.entries()]}
|
|
/>
|
|
</Layout.Horizontal>
|
|
);
|
|
};
|
|
|
|
function MoreOptionsMenu({
|
|
onSetEventMonitored,
|
|
frameworkEventTypes,
|
|
}: {
|
|
onSetEventMonitored: (
|
|
eventType: FrameworkEventType,
|
|
monitored: boolean,
|
|
) => void;
|
|
frameworkEventTypes: [FrameworkEventType, boolean][];
|
|
}) {
|
|
const [showFrameworkEventsModal, setShowFrameworkEventsModal] =
|
|
useState(false);
|
|
|
|
const moreOptionsMenu = (
|
|
<Menu>
|
|
<Menu.Item
|
|
onClick={() => {
|
|
setShowFrameworkEventsModal(true);
|
|
}}>
|
|
Framework event monitoring
|
|
</Menu.Item>
|
|
</Menu>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<Dropdown
|
|
key="more"
|
|
mouseLeaveDelay={0.7}
|
|
overlay={moreOptionsMenu}
|
|
placement="bottomRight">
|
|
<Button type="text" icon={<MoreOutlined style={{fontSize: 20}} />} />
|
|
</Dropdown>
|
|
|
|
{/*invisible until shown*/}
|
|
<FrameworkEventsMonitoringModal
|
|
frameworkEventTypes={frameworkEventTypes}
|
|
onSetEventMonitored={onSetEventMonitored}
|
|
visible={showFrameworkEventsModal}
|
|
onCancel={() => setShowFrameworkEventsModal(false)}
|
|
/>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function FrameworkEventsMonitoringModal({
|
|
visible,
|
|
onCancel,
|
|
onSetEventMonitored,
|
|
frameworkEventTypes,
|
|
}: {
|
|
visible: boolean;
|
|
onCancel: () => void;
|
|
onSetEventMonitored: (
|
|
eventType: FrameworkEventType,
|
|
monitored: boolean,
|
|
) => void;
|
|
frameworkEventTypes: [FrameworkEventType, boolean][];
|
|
}) {
|
|
const selectedFrameworkEvents = frameworkEventTypes
|
|
.filter(([, selected]) => selected)
|
|
.map(([eventType]) => eventType);
|
|
|
|
const treeData = buildTreeSelectData(
|
|
frameworkEventTypes.map(([type]) => type),
|
|
);
|
|
|
|
return (
|
|
<Modal
|
|
title="Framework event monitoring"
|
|
visible={visible}
|
|
footer={null}
|
|
onCancel={onCancel}>
|
|
<Space direction="vertical" size="large">
|
|
<Typography.Text>
|
|
Monitoring an event will cause the relevant node in the visualizer and
|
|
tree to highlight briefly. Additionally a running total of the number
|
|
of each event will be show in the tree inline
|
|
</Typography.Text>
|
|
|
|
<TreeSelect
|
|
treeCheckable
|
|
showSearch={false}
|
|
showCheckedStrategy={TreeSelect.SHOW_PARENT}
|
|
placeholder="Select nodes to monitor"
|
|
virtual={false} //for scrollbar
|
|
style={{
|
|
width: '100%',
|
|
}}
|
|
treeData={treeData}
|
|
treeDefaultExpandAll
|
|
value={selectedFrameworkEvents}
|
|
onSelect={(_: any, node: any) => {
|
|
for (const leaf of getAllLeaves(node)) {
|
|
onSetEventMonitored(leaf, true);
|
|
}
|
|
}}
|
|
onDeselect={(_: any, node: any) => {
|
|
for (const leaf of getAllLeaves(node)) {
|
|
onSetEventMonitored(leaf, false);
|
|
}
|
|
}}
|
|
/>
|
|
</Space>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
type TreeSelectNode = {
|
|
title: string;
|
|
key: string;
|
|
value: string;
|
|
children: TreeSelectNode[];
|
|
};
|
|
|
|
/**
|
|
* In tree select you can select a parent which implicitly selects all children, we find them all here as the real state
|
|
* is in terms of the leaf nodes
|
|
*/
|
|
function getAllLeaves(treeSelectNode: TreeSelectNode) {
|
|
const result: string[] = [];
|
|
function getAllLeavesRec(node: TreeSelectNode) {
|
|
console.log(node);
|
|
if (node.children.length > 0) {
|
|
for (const child of node.children) {
|
|
getAllLeavesRec(child);
|
|
}
|
|
} else {
|
|
result.push(node.key);
|
|
}
|
|
}
|
|
getAllLeavesRec(treeSelectNode);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* transformed flat event type data structure into tree
|
|
*/
|
|
function buildTreeSelectData(eventTypes: string[]): TreeSelectNode[] {
|
|
const root: TreeSelectNode = buildTreeSelectNode('root', 'root');
|
|
|
|
eventTypes.forEach((eventType) => {
|
|
const eventSubtypes = eventType.split(':');
|
|
let currentNode = root;
|
|
|
|
// Find the parent node for the current id
|
|
for (let i = 0; i < eventSubtypes.length - 1; i++) {
|
|
let foundChild = false;
|
|
for (const child of currentNode.children) {
|
|
if (child.title === eventSubtypes[i]) {
|
|
currentNode = child;
|
|
foundChild = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!foundChild) {
|
|
const newNode: TreeSelectNode = buildTreeSelectNode(
|
|
eventSubtypes[i],
|
|
eventSubtypes.slice(0, i + 1).join(':'),
|
|
);
|
|
|
|
currentNode.children.push(newNode);
|
|
currentNode = newNode;
|
|
}
|
|
}
|
|
// Add the current id as a child of the parent node
|
|
currentNode.children.push(
|
|
buildTreeSelectNode(
|
|
eventSubtypes[eventSubtypes.length - 1],
|
|
eventSubtypes.slice(0, eventSubtypes.length).join(':'),
|
|
),
|
|
);
|
|
});
|
|
|
|
return root.children;
|
|
}
|
|
|
|
function buildTreeSelectNode(title: string, fullValue: string): TreeSelectNode {
|
|
return {
|
|
title: title,
|
|
key: fullValue,
|
|
value: fullValue,
|
|
children: [],
|
|
};
|
|
}
|