Convert flipper-messages to ant.design
Summary: The main change is the move to DataTable here. Reviewed By: mweststrate Differential Revision: D28006097 fbshipit-source-id: 7564276a1177a7835612db08857862cb81942bce
This commit is contained in:
committed by
Facebook GitHub Bot
parent
fbcc5f98a4
commit
cf2405a466
@@ -14,17 +14,13 @@ import {MessageRow} from '../';
|
|||||||
|
|
||||||
const fixRowTimestamps = (r: MessageRow): MessageRow => ({
|
const fixRowTimestamps = (r: MessageRow): MessageRow => ({
|
||||||
...r,
|
...r,
|
||||||
columns: {
|
time: new Date(Date.UTC(0, 0, 0, 0, 0, 0)),
|
||||||
...r.columns,
|
|
||||||
time: {value: '00:13:37'},
|
|
||||||
},
|
|
||||||
timestamp: 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('It can store rows', () => {
|
test('It can store rows', () => {
|
||||||
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
||||||
|
|
||||||
expect(instance.state.get().messageRows).toEqual([]);
|
expect(instance.rows.records()).toEqual([]);
|
||||||
expect(instance.highlightedRow.get()).toBeUndefined();
|
expect(instance.highlightedRow.get()).toBeUndefined();
|
||||||
|
|
||||||
plugin.sendEvent('newMessage', {
|
plugin.sendEvent('newMessage', {
|
||||||
@@ -39,78 +35,21 @@ test('It can store rows', () => {
|
|||||||
payload: {hello: 'world'},
|
payload: {hello: 'world'},
|
||||||
});
|
});
|
||||||
|
|
||||||
const newRows = instance.state.get().messageRows.map(fixRowTimestamps);
|
expect(instance.rows.records().map(fixRowTimestamps)).toMatchInlineSnapshot(`
|
||||||
expect(newRows).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
Array [
|
||||||
Object {
|
Object {
|
||||||
"columns": Object {
|
"app": "Flipper",
|
||||||
"app": Object {
|
"direction": "toFlipper",
|
||||||
"isFilterable": true,
|
"time": 1899-12-31T00:00:00.000Z,
|
||||||
"value": "Flipper",
|
|
||||||
},
|
|
||||||
"device": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"direction": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": "toFlipper",
|
|
||||||
},
|
|
||||||
"internalMethod": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"plugin": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"pluginMethod": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"time": Object {
|
|
||||||
"value": "00:13:37",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"key": "0",
|
|
||||||
"payload": undefined,
|
|
||||||
"timestamp": 0,
|
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"columns": Object {
|
"app": "FB4A",
|
||||||
"app": Object {
|
"device": "Android Phone",
|
||||||
"isFilterable": true,
|
"direction": "toClient",
|
||||||
"value": "FB4A",
|
|
||||||
},
|
|
||||||
"device": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": "Android Phone",
|
|
||||||
},
|
|
||||||
"direction": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": "toClient",
|
|
||||||
},
|
|
||||||
"internalMethod": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"plugin": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"pluginMethod": Object {
|
|
||||||
"isFilterable": true,
|
|
||||||
"value": undefined,
|
|
||||||
},
|
|
||||||
"time": Object {
|
|
||||||
"value": "00:13:37",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"key": "1",
|
|
||||||
"payload": Object {
|
"payload": Object {
|
||||||
"hello": "world",
|
"hello": "world",
|
||||||
},
|
},
|
||||||
"timestamp": 0,
|
"time": 1899-12-31T00:00:00.000Z,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`);
|
`);
|
||||||
@@ -119,7 +58,7 @@ test('It can store rows', () => {
|
|||||||
test('It can clear', () => {
|
test('It can clear', () => {
|
||||||
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
|
||||||
|
|
||||||
expect(instance.state.get().messageRows).toEqual([]);
|
expect(instance.rows.records()).toEqual([]);
|
||||||
expect(instance.highlightedRow.get()).toBeUndefined();
|
expect(instance.highlightedRow.get()).toBeUndefined();
|
||||||
|
|
||||||
plugin.sendEvent('newMessage', {
|
plugin.sendEvent('newMessage', {
|
||||||
@@ -129,7 +68,7 @@ test('It can clear', () => {
|
|||||||
|
|
||||||
instance.clear();
|
instance.clear();
|
||||||
|
|
||||||
const newRows = instance.state.get().messageRows.map(fixRowTimestamps);
|
const newRows = instance.rows.records().map(fixRowTimestamps);
|
||||||
expect(newRows).toEqual([]);
|
expect(newRows).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,9 +80,10 @@ test('It can highlight a row', () => {
|
|||||||
direction: 'toFlipper',
|
direction: 'toFlipper',
|
||||||
});
|
});
|
||||||
|
|
||||||
instance.setHighlightedRow(['0', '1', '2']);
|
instance.setHighlightedRow(instance.rows.records()[0]);
|
||||||
|
|
||||||
expect(instance.highlightedRow.get()).toEqual('0');
|
expect(instance.rows.records()).toHaveLength(1);
|
||||||
|
expect(instance.highlightedRow.get()?.app).toEqual('Flipper');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('It can render empty', async () => {
|
test('It can render empty', async () => {
|
||||||
@@ -155,4 +95,60 @@ test('It can render empty', async () => {
|
|||||||
).not.toBeNull();
|
).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(T82512981): Can't test much more right now until UI conversion has happened.
|
test('It can render rows', async () => {
|
||||||
|
const {renderer, ...plugin} = TestUtils.renderPlugin(Plugin);
|
||||||
|
|
||||||
|
plugin.sendEvent('newMessage', {
|
||||||
|
time: new Date(0, 0, 0, 0, 0, 0),
|
||||||
|
app: 'Flipper',
|
||||||
|
direction: 'toFlipper',
|
||||||
|
});
|
||||||
|
|
||||||
|
plugin.sendEvent('newMessage', {
|
||||||
|
time: new Date(0, 0, 0, 0, 0, 0),
|
||||||
|
app: 'FB4A',
|
||||||
|
direction: 'toClient',
|
||||||
|
device: 'Android Phone',
|
||||||
|
flipperInternalMethod: 'unique-string',
|
||||||
|
payload: {hello: 'world'},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect((await renderer.findByText('unique-string')).parentElement)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
class="css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
>
|
||||||
|
00:00:00.000
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
>
|
||||||
|
Android Phone
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
>
|
||||||
|
FB4A
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
>
|
||||||
|
unique-string
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
class="css-esqhnb-TableBodyColumnContainer e1luu51r0"
|
||||||
|
>
|
||||||
|
toClient
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,21 +8,26 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
DataInspector,
|
||||||
colors,
|
DataTable,
|
||||||
|
DataTableColumn,
|
||||||
|
Layout,
|
||||||
|
createState,
|
||||||
|
PluginClient,
|
||||||
|
usePlugin,
|
||||||
|
useValue,
|
||||||
|
createDataSource,
|
||||||
DetailSidebar,
|
DetailSidebar,
|
||||||
FlexCenter,
|
|
||||||
FlexColumn,
|
|
||||||
ManagedDataInspector,
|
|
||||||
Panel,
|
Panel,
|
||||||
SearchableTable,
|
theme,
|
||||||
styled,
|
styled,
|
||||||
TableHighlightedRows,
|
} from 'flipper-plugin';
|
||||||
} from 'flipper';
|
import {Button} from 'antd';
|
||||||
import {createState, PluginClient, usePlugin, useValue} from 'flipper-plugin';
|
import {DeleteOutlined} from '@ant-design/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export type MessageInfo = {
|
export interface MessageInfo {
|
||||||
|
time?: Date;
|
||||||
device?: string;
|
device?: string;
|
||||||
app: string;
|
app: string;
|
||||||
flipperInternalMethod?: string;
|
flipperInternalMethod?: string;
|
||||||
@@ -30,122 +35,22 @@ export type MessageInfo = {
|
|||||||
pluginMethod?: string;
|
pluginMethod?: string;
|
||||||
payload?: any;
|
payload?: any;
|
||||||
direction: 'toClient' | 'toFlipper';
|
direction: 'toClient' | 'toFlipper';
|
||||||
};
|
}
|
||||||
|
|
||||||
export type MessageRow = {
|
export interface MessageRow extends MessageInfo {
|
||||||
columns: {
|
time: Date;
|
||||||
time: {
|
}
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
device: {
|
|
||||||
value?: string;
|
|
||||||
isFilterable: true;
|
|
||||||
};
|
|
||||||
app: {
|
|
||||||
value: string;
|
|
||||||
isFilterable: true;
|
|
||||||
};
|
|
||||||
internalMethod: {
|
|
||||||
value?: string;
|
|
||||||
isFilterable: true;
|
|
||||||
};
|
|
||||||
plugin: {
|
|
||||||
value?: string;
|
|
||||||
isFilterable: true;
|
|
||||||
};
|
|
||||||
pluginMethod: {
|
|
||||||
value?: string;
|
|
||||||
isFilterable: true;
|
|
||||||
};
|
|
||||||
direction: {
|
|
||||||
value: string;
|
|
||||||
isFilterable: true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
timestamp: number;
|
|
||||||
payload?: any;
|
|
||||||
key: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type PersistedState = {
|
const Placeholder = styled(Layout.Container)({
|
||||||
messageRows: Array<MessageRow>;
|
center: true,
|
||||||
};
|
color: theme.textColorPlaceholder,
|
||||||
|
|
||||||
const Placeholder = styled(FlexCenter)({
|
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: colors.macOSTitleBarIcon,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const COLUMNS = {
|
|
||||||
time: {
|
|
||||||
value: 'Time',
|
|
||||||
},
|
|
||||||
device: {
|
|
||||||
value: 'Device',
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
value: 'App',
|
|
||||||
},
|
|
||||||
internalMethod: {
|
|
||||||
value: 'Flipper internal method',
|
|
||||||
},
|
|
||||||
plugin: {
|
|
||||||
value: 'Plugin',
|
|
||||||
},
|
|
||||||
pluginMethod: {
|
|
||||||
value: 'Method',
|
|
||||||
},
|
|
||||||
direction: {
|
|
||||||
value: 'Direction',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const COLUMN_SIZES = {
|
|
||||||
time: 'flex',
|
|
||||||
device: 'flex',
|
|
||||||
app: 'flex',
|
|
||||||
internalMethod: 'flex',
|
|
||||||
plugin: 'flex',
|
|
||||||
pluginMethod: 'flex',
|
|
||||||
direction: 'flex',
|
|
||||||
};
|
|
||||||
|
|
||||||
let rowId = 0;
|
|
||||||
|
|
||||||
function createRow(message: MessageInfo): MessageRow {
|
function createRow(message: MessageInfo): MessageRow {
|
||||||
return {
|
return {
|
||||||
columns: {
|
...message,
|
||||||
time: {
|
time: message.time == null ? new Date() : message.time,
|
||||||
value: new Date().toLocaleTimeString(),
|
|
||||||
},
|
|
||||||
device: {
|
|
||||||
value: message.device,
|
|
||||||
isFilterable: true,
|
|
||||||
},
|
|
||||||
app: {
|
|
||||||
value: message.app,
|
|
||||||
isFilterable: true,
|
|
||||||
},
|
|
||||||
internalMethod: {
|
|
||||||
value: message.flipperInternalMethod,
|
|
||||||
isFilterable: true,
|
|
||||||
},
|
|
||||||
plugin: {
|
|
||||||
value: message.plugin,
|
|
||||||
isFilterable: true,
|
|
||||||
},
|
|
||||||
pluginMethod: {
|
|
||||||
value: message.pluginMethod,
|
|
||||||
isFilterable: true,
|
|
||||||
},
|
|
||||||
direction: {
|
|
||||||
value: message.direction,
|
|
||||||
isFilterable: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
payload: message.payload,
|
|
||||||
key: '' + rowId++,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,31 +58,59 @@ type Events = {
|
|||||||
newMessage: MessageInfo;
|
newMessage: MessageInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const COLUMN_CONFIG: DataTableColumn<MessageRow>[] = [
|
||||||
|
{
|
||||||
|
key: 'time',
|
||||||
|
title: 'Time',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'device',
|
||||||
|
title: 'Device',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'app',
|
||||||
|
title: 'App',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'flipperInternalMethod',
|
||||||
|
title: 'Flipper Internal Method',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'plugin',
|
||||||
|
title: 'Plugin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'pluginMethod',
|
||||||
|
title: 'Method',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'direction',
|
||||||
|
title: 'Direction',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function plugin(client: PluginClient<Events, {}>) {
|
export function plugin(client: PluginClient<Events, {}>) {
|
||||||
const state = createState<PersistedState>({
|
const highlightedRow = createState<MessageRow>();
|
||||||
messageRows: [],
|
const rows = createDataSource<MessageRow>([], {
|
||||||
|
limit: 1024 * 10,
|
||||||
|
persist: 'messages',
|
||||||
});
|
});
|
||||||
const highlightedRow = createState<string | null>();
|
|
||||||
const setHighlightedRow = (keys: TableHighlightedRows) => {
|
const setHighlightedRow = (record: MessageRow) => {
|
||||||
if (keys.length > 0) {
|
highlightedRow.set(record);
|
||||||
highlightedRow.set(keys[0]);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
state.set({messageRows: []});
|
highlightedRow.set(undefined);
|
||||||
highlightedRow.set(null);
|
rows.clear();
|
||||||
};
|
};
|
||||||
|
|
||||||
client.onMessage('newMessage', (payload) => {
|
client.onMessage('newMessage', (payload) => {
|
||||||
state.update((draft) => {
|
rows.append(createRow(payload));
|
||||||
draft.messageRows = [...draft.messageRows, createRow(payload)].filter(
|
|
||||||
(row) => Date.now() - row.timestamp < 5 * 60 * 1000,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state,
|
rows,
|
||||||
highlightedRow,
|
highlightedRow,
|
||||||
setHighlightedRow,
|
setHighlightedRow,
|
||||||
clear,
|
clear,
|
||||||
@@ -186,53 +119,45 @@ export function plugin(client: PluginClient<Events, {}>) {
|
|||||||
|
|
||||||
function Sidebar() {
|
function Sidebar() {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const rows = useValue(instance.state).messageRows;
|
const message = useValue(instance.highlightedRow);
|
||||||
const highlightedRow = useValue(instance.highlightedRow);
|
|
||||||
const message = rows.find((row) => row.key === highlightedRow);
|
|
||||||
|
|
||||||
const renderExtra = (extra: any) => (
|
const renderExtra = (extra: any) => (
|
||||||
<Panel floating={false} grow={false} heading={'Payload'}>
|
<Panel title={'Payload'} collapsible={false}>
|
||||||
<ManagedDataInspector data={extra} expandRoot={false} />
|
<DataInspector data={extra} expandRoot={false} />
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DetailSidebar>
|
||||||
{message != null ? (
|
{message != null ? (
|
||||||
renderExtra(message.payload)
|
renderExtra(message.payload)
|
||||||
) : (
|
) : (
|
||||||
<Placeholder grow>Select a message to view details</Placeholder>
|
<Placeholder grow pad="large">
|
||||||
|
Select a message to view details
|
||||||
|
</Placeholder>
|
||||||
)}
|
)}
|
||||||
</>
|
</DetailSidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Component() {
|
export function Component() {
|
||||||
const instance = usePlugin(plugin);
|
const instance = usePlugin(plugin);
|
||||||
const rows = useValue(instance.state).messageRows;
|
|
||||||
|
|
||||||
const clearTableButton = (
|
const clearTableButton = (
|
||||||
<Button onClick={instance.clear} key="clear">
|
<Button title="Clear logs" onClick={instance.clear}>
|
||||||
Clear Table
|
<DeleteOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlexColumn grow={true}>
|
<Layout.Container grow>
|
||||||
<SearchableTable
|
<DataTable<MessageRow>
|
||||||
rowLineHeight={28}
|
dataSource={instance.rows}
|
||||||
floating={false}
|
columns={COLUMN_CONFIG}
|
||||||
multiline={true}
|
onSelect={instance.setHighlightedRow}
|
||||||
columnSizes={COLUMN_SIZES}
|
extraActions={clearTableButton}
|
||||||
columns={COLUMNS}
|
|
||||||
onRowHighlighted={instance.setHighlightedRow}
|
|
||||||
rows={rows}
|
|
||||||
stickyBottom={true}
|
|
||||||
actions={[clearTableButton]}
|
|
||||||
/>
|
/>
|
||||||
<DetailSidebar>
|
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</DetailSidebar>
|
</Layout.Container>
|
||||||
</FlexColumn>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user