immutable data structures in tables 5/n: create table plugin
Summary: Migrating tables' row collection to Immutable.js List: 1. Migrate createTablePlugin to ManagedTable_immutable ----- Current implementation of tables forces to copy arrays on new data arrival which causes O(N^2) complexity -> tables can't handle a lot of new rows in short period of time -> tables freeze and become unresponsive for a few seconds. Immutable data structures will bring us to O(N) complexity Reviewed By: jknoxville Differential Revision: D16416867 fbshipit-source-id: 20890aa851cd2e34e33fd2ed69c5d6048af14cbb
This commit is contained in:
committed by
Facebook Github Bot
parent
d3658f7d31
commit
294d158869
@@ -6,15 +6,30 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {createTablePlugin} from '../createTablePlugin.js';
|
import {createTablePlugin} from '../createTablePlugin.js';
|
||||||
|
|
||||||
|
import type {TableRows_immutable} from 'flipper';
|
||||||
|
|
||||||
|
//import type {PersistedState, RowData} from '../createTablePlugin.js';
|
||||||
import {FlipperPlugin} from '../plugin.js';
|
import {FlipperPlugin} from '../plugin.js';
|
||||||
|
|
||||||
|
import {List, Map as ImmutableMap} from 'immutable';
|
||||||
|
|
||||||
const PROPS = {
|
const PROPS = {
|
||||||
method: 'method',
|
method: 'method',
|
||||||
resetMethod: 'resetMethod',
|
resetMethod: 'resetMethod',
|
||||||
columns: {},
|
columns: {},
|
||||||
columnSizes: {},
|
columnSizes: {},
|
||||||
renderSidebar: () => {},
|
renderSidebar: (r: {id: string}) => {},
|
||||||
buildRow: () => {},
|
buildRow: (r: {id: string}) => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
type PersistedState<T> = {|
|
||||||
|
rows: TableRows_immutable,
|
||||||
|
datas: ImmutableMap<string, T>,
|
||||||
|
|};
|
||||||
|
|
||||||
|
type RowData = {
|
||||||
|
id: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
test('createTablePlugin returns FlipperPlugin', () => {
|
test('createTablePlugin returns FlipperPlugin', () => {
|
||||||
@@ -24,13 +39,11 @@ test('createTablePlugin returns FlipperPlugin', () => {
|
|||||||
|
|
||||||
test('persistedStateReducer is resetting data', () => {
|
test('persistedStateReducer is resetting data', () => {
|
||||||
const resetMethod = 'resetMethod';
|
const resetMethod = 'resetMethod';
|
||||||
const tablePlugin = createTablePlugin({...PROPS, resetMethod});
|
const tablePlugin = createTablePlugin<RowData>({...PROPS, resetMethod});
|
||||||
|
|
||||||
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
const ps: PersistedState<RowData> = {
|
||||||
const {rows, datas} = tablePlugin.persistedStateReducer(
|
datas: ImmutableMap({'1': {id: '1'}}),
|
||||||
{
|
rows: List([
|
||||||
datas: {'1': {id: '1'}},
|
|
||||||
rows: [
|
|
||||||
{
|
{
|
||||||
key: '1',
|
key: '1',
|
||||||
columns: {
|
columns: {
|
||||||
@@ -39,14 +52,14 @@ test('persistedStateReducer is resetting data', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
]),
|
||||||
},
|
};
|
||||||
resetMethod,
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(datas).toEqual({});
|
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
||||||
expect(rows).toEqual([]);
|
const {rows, datas} = tablePlugin.persistedStateReducer(ps, resetMethod, {});
|
||||||
|
|
||||||
|
expect(datas.toJSON()).toEqual({});
|
||||||
|
expect(rows.size).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('persistedStateReducer is adding data', () => {
|
test('persistedStateReducer is adding data', () => {
|
||||||
@@ -56,14 +69,11 @@ test('persistedStateReducer is adding data', () => {
|
|||||||
|
|
||||||
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
||||||
const {rows, datas} = tablePlugin.persistedStateReducer(
|
const {rows, datas} = tablePlugin.persistedStateReducer(
|
||||||
{
|
tablePlugin.defaultPersistedState,
|
||||||
datas: {},
|
|
||||||
rows: [],
|
|
||||||
},
|
|
||||||
method,
|
method,
|
||||||
{id},
|
{id},
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(rows.length).toBe(1);
|
expect(rows.size).toBe(1);
|
||||||
expect(Object.keys(datas)).toEqual([id]);
|
expect([...datas.keys()]).toEqual([id]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
TableHighlightedRows,
|
TableHighlightedRows,
|
||||||
TableRows,
|
TableRows_immutable,
|
||||||
TableColumnSizes,
|
TableColumnSizes,
|
||||||
TableColumns,
|
TableColumns,
|
||||||
} from 'flipper';
|
} from 'flipper';
|
||||||
@@ -16,13 +16,15 @@ import FlexColumn from './ui/components/FlexColumn';
|
|||||||
import Button from './ui/components/Button';
|
import Button from './ui/components/Button';
|
||||||
import DetailSidebar from './chrome/DetailSidebar';
|
import DetailSidebar from './chrome/DetailSidebar';
|
||||||
import {FlipperPlugin} from './plugin';
|
import {FlipperPlugin} from './plugin';
|
||||||
import SearchableTable from './ui/components/searchable/SearchableTable';
|
import SearchableTable_immutable from './ui/components/searchable/SearchableTable_immutable';
|
||||||
import textContent from './utils/textContent.js';
|
import textContent from './utils/textContent.js';
|
||||||
import createPaste from './fb-stubs/createPaste.js';
|
import createPaste from './fb-stubs/createPaste.js';
|
||||||
|
|
||||||
|
import {List, Map as ImmutableMap} from 'immutable';
|
||||||
|
|
||||||
type ID = string;
|
type ID = string;
|
||||||
|
|
||||||
type RowData = {
|
export type RowData = {
|
||||||
id: ID,
|
id: ID,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,9 +37,9 @@ type Props<T> = {|
|
|||||||
buildRow: (row: T) => any,
|
buildRow: (row: T) => any,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
type PersistedState<T> = {|
|
export type PersistedState<T> = {|
|
||||||
rows: TableRows,
|
rows: TableRows_immutable,
|
||||||
datas: {[key: ID]: T},
|
datas: ImmutableMap<ID, T>,
|
||||||
|};
|
|};
|
||||||
|
|
||||||
type State = {|
|
type State = {|
|
||||||
@@ -58,41 +60,36 @@ type State = {|
|
|||||||
* the client in an unknown state.
|
* the client in an unknown state.
|
||||||
*/
|
*/
|
||||||
export function createTablePlugin<T: RowData>(props: Props<T>) {
|
export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||||
return class extends FlipperPlugin<State, *, PersistedState<T>> {
|
return class extends FlipperPlugin<State, *, PersistedState<*>> {
|
||||||
static keyboardActions = ['clear', 'createPaste'];
|
static keyboardActions = ['clear', 'createPaste'];
|
||||||
|
|
||||||
static defaultPersistedState: PersistedState<T> = {
|
static defaultPersistedState = {
|
||||||
rows: [],
|
rows: List(),
|
||||||
datas: {},
|
datas: ImmutableMap<ID, T>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
static persistedStateReducer = (
|
static persistedStateReducer = (
|
||||||
persistedState: PersistedState<T>,
|
persistedState: PersistedState<*>,
|
||||||
method: string,
|
method: string,
|
||||||
payload: T | Array<T>,
|
payload: T | Array<T>,
|
||||||
): $Shape<PersistedState<RowData>> => {
|
): $Shape<PersistedState<T>> => {
|
||||||
if (method === props.method) {
|
if (method === props.method) {
|
||||||
const newRows = [];
|
return List(Array.isArray(payload) ? payload : [payload]).reduce(
|
||||||
const newData = {};
|
(ps: PersistedState<*>, data: T) => {
|
||||||
const datas = Array.isArray(payload) ? payload : [payload];
|
|
||||||
|
|
||||||
for (const data of datas.reverse()) {
|
|
||||||
if (data.id == null) {
|
if (data.id == null) {
|
||||||
console.warn('The data sent did not contain an ID.', data);
|
console.warn('The data sent did not contain an ID.', data);
|
||||||
}
|
}
|
||||||
if (persistedState.datas[data.id] == null) {
|
|
||||||
newData[data.id] = data;
|
|
||||||
newRows.push(props.buildRow(data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
datas: {...persistedState.datas, ...newData},
|
datas: ps.datas.set(data.id, data),
|
||||||
rows: [...persistedState.rows, ...newRows],
|
rows: ps.rows.push(props.buildRow(data)),
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
persistedState,
|
||||||
|
);
|
||||||
} else if (method === props.resetMethod) {
|
} else if (method === props.resetMethod) {
|
||||||
return {
|
return {
|
||||||
rows: [],
|
rows: List(),
|
||||||
datas: {},
|
datas: ImmutableMap(),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
@@ -113,8 +110,8 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
|
|
||||||
clear = () => {
|
clear = () => {
|
||||||
this.props.setPersistedState({
|
this.props.setPersistedState({
|
||||||
rows: [],
|
rows: List(),
|
||||||
datas: {},
|
datas: ImmutableMap(),
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedIds: [],
|
selectedIds: [],
|
||||||
@@ -154,7 +151,8 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
const selectedId = selectedIds.length !== 1 ? null : selectedIds[0];
|
const selectedId = selectedIds.length !== 1 ? null : selectedIds[0];
|
||||||
|
|
||||||
if (selectedId != null) {
|
if (selectedId != null) {
|
||||||
return renderSidebar(datas[selectedId]);
|
const data = datas.get(selectedId);
|
||||||
|
return data != null ? renderSidebar(data) : null;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -166,7 +164,7 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FlexColumn grow={true}>
|
<FlexColumn grow={true}>
|
||||||
<SearchableTable
|
<SearchableTable_immutable
|
||||||
key={this.constructor.id}
|
key={this.constructor.id}
|
||||||
rowLineHeight={28}
|
rowLineHeight={28}
|
||||||
floating={false}
|
floating={false}
|
||||||
|
|||||||
Reference in New Issue
Block a user