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,28 +74,66 @@ export const FrameworkEventsInspector: React.FC<Props> = ({
return ( return (
<Layout.Container gap="small" padv="small"> <Layout.Container gap="small" padv="small">
<Layout.Right center gap>
<Typography.Title level={3}>Event timeline</Typography.Title>
<Layout.Horizontal center gap padh="medium">
{node.tags.includes('TreeRoot') && ( {node.tags.includes('TreeRoot') && (
<Tooltip title="Explore all tree events in table">
<Button <Button
type="ghost" shape="circle"
icon={<TableOutlined />} icon={<TableOutlined />}
size="middle"
onClick={() => onClick={() =>
onSetViewMode({mode: 'frameworkEventsTable', treeRootId: node.id}) onSetViewMode({
}> mode: 'frameworkEventsTable',
Explore all events treeRootId: node.id,
</Button> })
}
/>
</Tooltip>
)} )}
<Collapse> <Dropdown
<Collapse.Panel header="Filter events" key="1"> overlayStyle={{minWidth: 200}}
<Layout.Container gap="tiny"> overlay={
<FrameworkEventsTreeSelect <Layout.Container
placeholder="Select events types to filter by" gap="small"
treeData={buildTreeSelectData( pad="small"
allEventTypes, style={{
frameworkEventMetadata, backgroundColor: theme.white,
borderRadius: theme.borderRadius,
boxShadow: `0 0 4px 1px rgba(0,0,0,0.10)`,
}}>
{allThreads.length > 1 && (
<>
<Typography.Text strong>By thread</Typography.Text>
{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}
/>
))}
</>
)} )}
width={250}
onSetEventSelected={(eventType, selected) => { {allEventTypes.length > 1 && (
<>
<Typography.Text strong>By event type</Typography.Text>
{allEventTypes.map((eventType) => (
<MultiSelectableDropDownItem
onSelect={(eventType, selected) => {
setFilteredEventTypes((cur) => setFilteredEventTypes((cur) =>
produce(cur, (draft) => { produce(cur, (draft) => {
if (selected) { if (selected) {
@@ -100,22 +144,30 @@ export const FrameworkEventsInspector: React.FC<Props> = ({
}), }),
); );
}} }}
selected={[...filteredEventTypes]} selectedValues={filteredEventTypes}
/> key={eventType}
<Select value={eventType as string}
mode="multiple" text={last(eventType.split('.')) as string}
style={{width: '250px'}}
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> </Layout.Container>
</Collapse.Panel> }>
</Collapse> <Button
shape="circle"
icon={
<Badge
offset={[8, -8]}
size="small"
count={filteredEventTypes.size + filteredThreads.size}>
<FilterOutlined style={{}} />
</Badge>
}
/>
</Dropdown>
</Layout.Horizontal>
</Layout.Right>
<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,6 +71,13 @@ export const TreeControls: React.FC = () => {
}></Button> }></Button>
{frameworkEventMonitoring.size > 0 && ( {frameworkEventMonitoring.size > 0 && (
<> <>
<Badge
size="small"
count={
[...frameworkEventMonitoring.values()].filter(
(val) => val === true,
).length
}>
<Button <Button
type="default" type="default"
shape="circle" shape="circle"
@@ -73,6 +89,7 @@ export const TreeControls: React.FC = () => {
<EyeOutlined /> <EyeOutlined />
</Tooltip> </Tooltip>
}></Button> }></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);