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:
Pascal Hartig
2021-04-28 07:05:19 -07:00
committed by Facebook GitHub Bot
parent fbcc5f98a4
commit cf2405a466
2 changed files with 157 additions and 236 deletions

View File

@@ -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>
`);
});

View File

@@ -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 /> </Layout.Container>
</DetailSidebar>
</FlexColumn>
); );
} }