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 => ({
...r,
columns: {
...r.columns,
time: {value: '00:13:37'},
},
timestamp: 0,
time: new Date(Date.UTC(0, 0, 0, 0, 0, 0)),
});
test('It can store rows', () => {
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
expect(instance.state.get().messageRows).toEqual([]);
expect(instance.rows.records()).toEqual([]);
expect(instance.highlightedRow.get()).toBeUndefined();
plugin.sendEvent('newMessage', {
@@ -39,78 +35,21 @@ test('It can store rows', () => {
payload: {hello: 'world'},
});
const newRows = instance.state.get().messageRows.map(fixRowTimestamps);
expect(newRows).toMatchInlineSnapshot(`
expect(instance.rows.records().map(fixRowTimestamps)).toMatchInlineSnapshot(`
Array [
Object {
"columns": Object {
"app": Object {
"isFilterable": true,
"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,
"app": "Flipper",
"direction": "toFlipper",
"time": 1899-12-31T00:00:00.000Z,
},
Object {
"columns": Object {
"app": Object {
"isFilterable": true,
"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",
"app": "FB4A",
"device": "Android Phone",
"direction": "toClient",
"payload": Object {
"hello": "world",
},
"timestamp": 0,
"time": 1899-12-31T00:00:00.000Z,
},
]
`);
@@ -119,7 +58,7 @@ test('It can store rows', () => {
test('It can clear', () => {
const {instance, ...plugin} = TestUtils.startPlugin(Plugin);
expect(instance.state.get().messageRows).toEqual([]);
expect(instance.rows.records()).toEqual([]);
expect(instance.highlightedRow.get()).toBeUndefined();
plugin.sendEvent('newMessage', {
@@ -129,7 +68,7 @@ test('It can clear', () => {
instance.clear();
const newRows = instance.state.get().messageRows.map(fixRowTimestamps);
const newRows = instance.rows.records().map(fixRowTimestamps);
expect(newRows).toEqual([]);
});
@@ -141,9 +80,10 @@ test('It can highlight a row', () => {
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 () => {
@@ -155,4 +95,60 @@ test('It can render empty', async () => {
).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 {
Button,
colors,
DataInspector,
DataTable,
DataTableColumn,
Layout,
createState,
PluginClient,
usePlugin,
useValue,
createDataSource,
DetailSidebar,
FlexCenter,
FlexColumn,
ManagedDataInspector,
Panel,
SearchableTable,
theme,
styled,
TableHighlightedRows,
} from 'flipper';
import {createState, PluginClient, usePlugin, useValue} from 'flipper-plugin';
} from 'flipper-plugin';
import {Button} from 'antd';
import {DeleteOutlined} from '@ant-design/icons';
import React from 'react';
export type MessageInfo = {
export interface MessageInfo {
time?: Date;
device?: string;
app: string;
flipperInternalMethod?: string;
@@ -30,122 +35,22 @@ export type MessageInfo = {
pluginMethod?: string;
payload?: any;
direction: 'toClient' | 'toFlipper';
};
}
export type MessageRow = {
columns: {
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;
};
export interface MessageRow extends MessageInfo {
time: Date;
}
type PersistedState = {
messageRows: Array<MessageRow>;
};
const Placeholder = styled(FlexCenter)({
const Placeholder = styled(Layout.Container)({
center: true,
color: theme.textColorPlaceholder,
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 {
return {
columns: {
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++,
...message,
time: message.time == null ? new Date() : message.time,
};
}
@@ -153,31 +58,59 @@ type Events = {
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, {}>) {
const state = createState<PersistedState>({
messageRows: [],
const highlightedRow = createState<MessageRow>();
const rows = createDataSource<MessageRow>([], {
limit: 1024 * 10,
persist: 'messages',
});
const highlightedRow = createState<string | null>();
const setHighlightedRow = (keys: TableHighlightedRows) => {
if (keys.length > 0) {
highlightedRow.set(keys[0]);
}
const setHighlightedRow = (record: MessageRow) => {
highlightedRow.set(record);
};
const clear = () => {
state.set({messageRows: []});
highlightedRow.set(null);
highlightedRow.set(undefined);
rows.clear();
};
client.onMessage('newMessage', (payload) => {
state.update((draft) => {
draft.messageRows = [...draft.messageRows, createRow(payload)].filter(
(row) => Date.now() - row.timestamp < 5 * 60 * 1000,
);
});
rows.append(createRow(payload));
});
return {
state,
rows,
highlightedRow,
setHighlightedRow,
clear,
@@ -186,53 +119,45 @@ export function plugin(client: PluginClient<Events, {}>) {
function Sidebar() {
const instance = usePlugin(plugin);
const rows = useValue(instance.state).messageRows;
const highlightedRow = useValue(instance.highlightedRow);
const message = rows.find((row) => row.key === highlightedRow);
const message = useValue(instance.highlightedRow);
const renderExtra = (extra: any) => (
<Panel floating={false} grow={false} heading={'Payload'}>
<ManagedDataInspector data={extra} expandRoot={false} />
<Panel title={'Payload'} collapsible={false}>
<DataInspector data={extra} expandRoot={false} />
</Panel>
);
return (
<>
<DetailSidebar>
{message != null ? (
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() {
const instance = usePlugin(plugin);
const rows = useValue(instance.state).messageRows;
const clearTableButton = (
<Button onClick={instance.clear} key="clear">
Clear Table
<Button title="Clear logs" onClick={instance.clear}>
<DeleteOutlined />
</Button>
);
return (
<FlexColumn grow={true}>
<SearchableTable
rowLineHeight={28}
floating={false}
multiline={true}
columnSizes={COLUMN_SIZES}
columns={COLUMNS}
onRowHighlighted={instance.setHighlightedRow}
rows={rows}
stickyBottom={true}
actions={[clearTableButton]}
<Layout.Container grow>
<DataTable<MessageRow>
dataSource={instance.rows}
columns={COLUMN_CONFIG}
onSelect={instance.setHighlightedRow}
extraActions={clearTableButton}
/>
<DetailSidebar>
<Sidebar />
</DetailSidebar>
</FlexColumn>
</Layout.Container>
);
}