Improve framework event filters
Reviewed By: lblasa Differential Revision: D48393422 fbshipit-source-id: 18d92b53bd56c100b6d4bb6adc07ede0b4a46732
This commit is contained in:
committed by
Facebook GitHub Bot
parent
756a289883
commit
1bffe8bc6b
@@ -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,
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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,28 +74,66 @@ export const FrameworkEventsInspector: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<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') && (
|
||||
<Tooltip title="Explore all tree events in table">
|
||||
<Button
|
||||
type="ghost"
|
||||
shape="circle"
|
||||
icon={<TableOutlined />}
|
||||
size="middle"
|
||||
onClick={() =>
|
||||
onSetViewMode({mode: 'frameworkEventsTable', treeRootId: node.id})
|
||||
}>
|
||||
Explore all events
|
||||
</Button>
|
||||
onSetViewMode({
|
||||
mode: 'frameworkEventsTable',
|
||||
treeRootId: node.id,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Collapse>
|
||||
<Collapse.Panel header="Filter events" key="1">
|
||||
<Layout.Container gap="tiny">
|
||||
<FrameworkEventsTreeSelect
|
||||
placeholder="Select events types to filter by"
|
||||
treeData={buildTreeSelectData(
|
||||
allEventTypes,
|
||||
frameworkEventMetadata,
|
||||
<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}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
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) =>
|
||||
produce(cur, (draft) => {
|
||||
if (selected) {
|
||||
@@ -100,22 +144,30 @@ export const FrameworkEventsInspector: React.FC<Props> = ({
|
||||
}),
|
||||
);
|
||||
}}
|
||||
selected={[...filteredEventTypes]}
|
||||
/>
|
||||
<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))}
|
||||
selectedValues={filteredEventTypes}
|
||||
key={eventType}
|
||||
value={eventType as string}
|
||||
text={last(eventType.split('.')) as string}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</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
|
||||
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) {
|
||||
|
||||
@@ -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,6 +71,13 @@ export const TreeControls: React.FC = () => {
|
||||
}></Button>
|
||||
{frameworkEventMonitoring.size > 0 && (
|
||||
<>
|
||||
<Badge
|
||||
size="small"
|
||||
count={
|
||||
[...frameworkEventMonitoring.values()].filter(
|
||||
(val) => val === true,
|
||||
).length
|
||||
}>
|
||||
<Button
|
||||
type="default"
|
||||
shape="circle"
|
||||
@@ -73,6 +89,7 @@ export const TreeControls: React.FC = () => {
|
||||
<EyeOutlined />
|
||||
</Tooltip>
|
||||
}></Button>
|
||||
</Badge>
|
||||
<FrameworkEventsMonitoringModal
|
||||
metadata={frameworkEventMetadata}
|
||||
filterMainThreadMonitoring={filterMainThreadMonitoring}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user