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';
import React, {ReactNode, useState} from 'react';
import {StackTraceInspector} from './StackTraceInspector';
import {Button, Collapse, Descriptions, Select, Tag} from 'antd';
import {frameworkEventSeparator} from '../../shared/FrameworkEventsTreeSelect';
import {
buildTreeSelectData,
FrameworkEventsTreeSelect,
} from '../../shared/FrameworkEventsTreeSelect';
import {uniqBy} from 'lodash';
import {TableOutlined} from '@ant-design/icons';
Badge,
Button,
Descriptions,
Dropdown,
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 {MultiSelectableDropDownItem} from '../../shared/MultiSelectableDropDownItem';
type Props = {
node: ClientNode;
@@ -39,6 +44,7 @@ type Props = {
frameworkEventMetadata: Map<FrameworkEventType, FrameworkEventMetadata>;
onSetViewMode: (viewMode: ViewMode) => void;
};
export const FrameworkEventsInspector: React.FC<Props> = ({
node,
events,
@@ -68,54 +74,100 @@ export const FrameworkEventsInspector: React.FC<Props> = ({
return (
<Layout.Container gap="small" padv="small">
{node.tags.includes('TreeRoot') && (
<Button
type="ghost"
icon={<TableOutlined />}
size="middle"
onClick={() =>
onSetViewMode({mode: 'frameworkEventsTable', treeRootId: node.id})
}>
Explore all events
</Button>
)}
<Collapse>
<Collapse.Panel header="Filter events" key="1">
<Layout.Container gap="tiny">
<FrameworkEventsTreeSelect
placeholder="Select events types to filter by"
treeData={buildTreeSelectData(
allEventTypes,
frameworkEventMetadata,
)}
width={250}
onSetEventSelected={(eventType, selected) => {
setFilteredEventTypes((cur) =>
produce(cur, (draft) => {
if (selected) {
draft.add(eventType);
} else {
draft.delete(eventType);
}
}),
);
}}
selected={[...filteredEventTypes]}
<Layout.Right center gap>
<Typography.Title level={3}>Event timeline</Typography.Title>
<Layout.Horizontal center gap padh="medium">
{node.tags.includes('TreeRoot') && (
<Tooltip title="Explore all tree events in table">
<Button
shape="circle"
icon={<TableOutlined />}
onClick={() =>
onSetViewMode({
mode: 'frameworkEventsTable',
treeRootId: node.id,
})
}
/>
</Tooltip>
)}
<Dropdown
overlayStyle={{minWidth: 200}}
overlay={
<Layout.Container
gap="small"
pad="small"
style={{
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}
/>
))}
</>
)}
{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
mode="multiple"
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>
</Collapse.Panel>
</Collapse>
</Dropdown>
</Layout.Horizontal>
</Layout.Right>
<TimelineDataDescription
key={node.id}
@@ -228,7 +280,7 @@ function formatDuration(nanoseconds: number): string {
} else if (nanoseconds < 1_000_000_000_000) {
return `${(nanoseconds / 1_000_000_000).toFixed(2)} seconds`;
} 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) {

View File

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

View File

@@ -21,6 +21,7 @@ import {
import {tracker} from '../../utils/tracker';
import {debounce} from 'lodash';
import {WireFrameMode} from '../../DesktopTypes';
import {SelectableDropDownItem} from '../shared/SelectableDropDownItem';
export type TargetModeState =
| {
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(() => {
tracker.track('target-mode-adjusted', {});
}, 500);