Improve framework event filters

Reviewed By: lblasa

Differential Revision: D48393422

fbshipit-source-id: 18d92b53bd56c100b6d4bb6adc07ede0b4a46732
This commit is contained in:
Luke De Feo
2023-08-21 04:24:16 -07:00
committed by Facebook GitHub Bot
parent 756a289883
commit 1bffe8bc6b
5 changed files with 225 additions and 92 deletions

View File

@@ -0,0 +1,51 @@
/**
* 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 from 'react';
import {Checkbox, Typography} from 'antd';
import {Layout, styled, theme} from 'flipper-plugin';
export function MultiSelectableDropDownItem<T>({
value,
selectedValues,
onSelect,
text,
}: {
value: T;
selectedValues: Set<T>;
onSelect: (value: T, selected: boolean) => void;
text: string;
}) {
const isSelected = selectedValues.has(value);
return (
<StyledMultiSelectDropDownItem
center
padv="small"
gap="small"
onClick={(e) => {
e.stopPropagation();
onSelect(value, !isSelected);
}}>
<Checkbox
checked={isSelected}
onChange={() => {
onSelect(value, !isSelected);
}}
/>
<Typography.Text>{text}</Typography.Text>
</StyledMultiSelectDropDownItem>
);
}
export const StyledMultiSelectDropDownItem = styled(Layout.Horizontal)({
':hover': {
backgroundColor: theme.backgroundWash,
},
height: 32,
});

View File

@@ -0,0 +1,37 @@
/**
* 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 {Menu} from 'antd';
import {theme} from 'flipper-plugin';
import React from 'react';
export function SelectableDropDownItem<T>({
value,
selectedValue,
onSelect,
text,
}: {
value: T;
selectedValue: T;
onSelect: (value: T) => void;
text: string;
}) {
return (
<Menu.Item
style={{
color:
value === selectedValue ? theme.primaryColor : theme.textColorActive,
}}
onClick={() => {
onSelect(value);
}}>
{text}
</Menu.Item>
);
}

View File

@@ -22,15 +22,20 @@ import {
} from '../../../ClientTypes'; } from '../../../ClientTypes';
import React, {ReactNode, useState} from 'react'; import React, {ReactNode, useState} from 'react';
import {StackTraceInspector} from './StackTraceInspector'; import {StackTraceInspector} from './StackTraceInspector';
import {Button, Collapse, Descriptions, Select, Tag} from 'antd';
import {frameworkEventSeparator} from '../../shared/FrameworkEventsTreeSelect';
import { import {
buildTreeSelectData, Badge,
FrameworkEventsTreeSelect, Button,
} from '../../shared/FrameworkEventsTreeSelect'; Descriptions,
import {uniqBy} from 'lodash'; Dropdown,
import {TableOutlined} from '@ant-design/icons'; Tag,
Tooltip,
Typography,
} from 'antd';
import {frameworkEventSeparator} from '../../shared/FrameworkEventsTreeSelect';
import {last, startCase, uniqBy} from 'lodash';
import {FilterOutlined, TableOutlined} from '@ant-design/icons';
import {ViewMode} from '../../../DesktopTypes'; import {ViewMode} from '../../../DesktopTypes';
import {MultiSelectableDropDownItem} from '../../shared/MultiSelectableDropDownItem';
type Props = { type Props = {
node: ClientNode; node: ClientNode;
@@ -39,6 +44,7 @@ type Props = {
frameworkEventMetadata: Map<FrameworkEventType, FrameworkEventMetadata>; frameworkEventMetadata: Map<FrameworkEventType, FrameworkEventMetadata>;
onSetViewMode: (viewMode: ViewMode) => void; onSetViewMode: (viewMode: ViewMode) => void;
}; };
export const FrameworkEventsInspector: React.FC<Props> = ({ export const FrameworkEventsInspector: React.FC<Props> = ({
node, node,
events, events,
@@ -68,54 +74,100 @@ export const FrameworkEventsInspector: React.FC<Props> = ({
return ( return (
<Layout.Container gap="small" padv="small"> <Layout.Container gap="small" padv="small">
{node.tags.includes('TreeRoot') && ( <Layout.Right center gap>
<Button <Typography.Title level={3}>Event timeline</Typography.Title>
type="ghost"
icon={<TableOutlined />} <Layout.Horizontal center gap padh="medium">
size="middle" {node.tags.includes('TreeRoot') && (
onClick={() => <Tooltip title="Explore all tree events in table">
onSetViewMode({mode: 'frameworkEventsTable', treeRootId: node.id}) <Button
}> shape="circle"
Explore all events icon={<TableOutlined />}
</Button> onClick={() =>
)} onSetViewMode({
<Collapse> mode: 'frameworkEventsTable',
<Collapse.Panel header="Filter events" key="1"> treeRootId: node.id,
<Layout.Container gap="tiny"> })
<FrameworkEventsTreeSelect }
placeholder="Select events types to filter by" />
treeData={buildTreeSelectData( </Tooltip>
allEventTypes, )}
frameworkEventMetadata, <Dropdown
)} overlayStyle={{minWidth: 200}}
width={250} overlay={
onSetEventSelected={(eventType, selected) => { <Layout.Container
setFilteredEventTypes((cur) => gap="small"
produce(cur, (draft) => { pad="small"
if (selected) { style={{
draft.add(eventType); backgroundColor: theme.white,
} else { borderRadius: theme.borderRadius,
draft.delete(eventType); boxShadow: `0 0 4px 1px rgba(0,0,0,0.10)`,
} }}>
}), {allThreads.length > 1 && (
); <>
}} <Typography.Text strong>By thread</Typography.Text>
selected={[...filteredEventTypes]} {allThreads.map((thread) => (
<MultiSelectableDropDownItem
onSelect={(thread, selected) => {
setFilteredThreads((cur) =>
produce(cur, (draft) => {
if (selected) {
draft.add(thread);
} else {
draft.delete(thread);
}
}),
);
}}
selectedValues={filteredThreads}
key={thread}
value={thread as string}
text={startCase(thread) as string}
/>
))}
</>
)}
{allEventTypes.length > 1 && (
<>
<Typography.Text strong>By event type</Typography.Text>
{allEventTypes.map((eventType) => (
<MultiSelectableDropDownItem
onSelect={(eventType, selected) => {
setFilteredEventTypes((cur) =>
produce(cur, (draft) => {
if (selected) {
draft.add(eventType);
} else {
draft.delete(eventType);
}
}),
);
}}
selectedValues={filteredEventTypes}
key={eventType}
value={eventType as string}
text={last(eventType.split('.')) as string}
/>
))}
</>
)}
</Layout.Container>
}>
<Button
shape="circle"
icon={
<Badge
offset={[8, -8]}
size="small"
count={filteredEventTypes.size + filteredThreads.size}>
<FilterOutlined style={{}} />
</Badge>
}
/> />
<Select </Dropdown>
mode="multiple" </Layout.Horizontal>
style={{width: '250px'}} </Layout.Right>
placeholder="Select threads to filter by"
defaultValue={[] as string[]}
options={allThreads.map((thread) => ({
value: thread,
label: thread,
}))}
onChange={(value) => setFilteredThreads(new Set(value))}
/>
</Layout.Container>
</Collapse.Panel>
</Collapse>
<TimelineDataDescription <TimelineDataDescription
key={node.id} key={node.id}
@@ -228,7 +280,7 @@ function formatDuration(nanoseconds: number): string {
} else if (nanoseconds < 1_000_000_000_000) { } else if (nanoseconds < 1_000_000_000_000) {
return `${(nanoseconds / 1_000_000_000).toFixed(2)} seconds`; return `${(nanoseconds / 1_000_000_000).toFixed(2)} seconds`;
} else { } else {
return `${(nanoseconds / 1_000_000_000_000).toFixed(2)} minutes`; return `${(nanoseconds / (1_000_000_000 * 60)).toFixed(2)} minutes`;
} }
} }
function eventTypeToName(eventType: string) { function eventTypeToName(eventType: string) {

View File

@@ -9,7 +9,16 @@
import React, {useState} from 'react'; import React, {useState} from 'react';
import {plugin} from '../../index'; import {plugin} from '../../index';
import {Button, Input, Modal, Tooltip, Typography, Space, Switch} from 'antd'; import {
Button,
Input,
Modal,
Tooltip,
Typography,
Space,
Switch,
Badge,
} from 'antd';
import { import {
EyeOutlined, EyeOutlined,
PauseCircleOutlined, PauseCircleOutlined,
@@ -62,17 +71,25 @@ export const TreeControls: React.FC = () => {
}></Button> }></Button>
{frameworkEventMonitoring.size > 0 && ( {frameworkEventMonitoring.size > 0 && (
<> <>
<Button <Badge
type="default" size="small"
shape="circle" count={
onClick={() => { [...frameworkEventMonitoring.values()].filter(
setShowFrameworkEventsModal(true); (val) => val === true,
}} ).length
icon={ }>
<Tooltip title="Framework event monitoring"> <Button
<EyeOutlined /> type="default"
</Tooltip> shape="circle"
}></Button> onClick={() => {
setShowFrameworkEventsModal(true);
}}
icon={
<Tooltip title="Framework event monitoring">
<EyeOutlined />
</Tooltip>
}></Button>
</Badge>
<FrameworkEventsMonitoringModal <FrameworkEventsMonitoringModal
metadata={frameworkEventMetadata} metadata={frameworkEventMetadata}
filterMainThreadMonitoring={filterMainThreadMonitoring} filterMainThreadMonitoring={filterMainThreadMonitoring}

View File

@@ -21,6 +21,7 @@ import {
import {tracker} from '../../utils/tracker'; import {tracker} from '../../utils/tracker';
import {debounce} from 'lodash'; import {debounce} from 'lodash';
import {WireFrameMode} from '../../DesktopTypes'; import {WireFrameMode} from '../../DesktopTypes';
import {SelectableDropDownItem} from '../shared/SelectableDropDownItem';
export type TargetModeState = export type TargetModeState =
| { | {
state: 'selected'; state: 'selected';
@@ -182,31 +183,6 @@ export function VisualiserControls({
); );
} }
function SelectableDropDownItem<T>({
value,
selectedValue,
onSelect,
text,
}: {
value: T;
selectedValue: T;
onSelect: (value: T) => void;
text: string;
}) {
return (
<Menu.Item
style={{
color:
value === selectedValue ? theme.primaryColor : theme.textColorActive,
}}
onClick={() => {
onSelect(value);
}}>
{text}
</Menu.Item>
);
}
const debouncedReportTargetAdjusted = debounce(() => { const debouncedReportTargetAdjusted = debounce(() => {
tracker.track('target-mode-adjusted', {}); tracker.track('target-mode-adjusted', {});
}, 500); }, 500);