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 => ({
|
||||
...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>
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user