From d3658f7d31dcd231809a0155e0b57fbecab5df5c Mon Sep 17 00:00:00 2001 From: Timur Valiev Date: Tue, 23 Jul 2019 07:57:00 -0700 Subject: [PATCH] immutable data structures in tables 3/n: searchable table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Migrating tables' row collection to Immutable.js List: 1. Сopy SearchableTable code to SearchableTable_immutable 2. Use ManagedTable_immutable for SearchableTable_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: D16416732 fbshipit-source-id: 856ba0407bfdd12bb1b90110e130562a07cc5060 --- .../searchable/SearchableTable_immutable.js | 146 ++++++++++++++++++ src/ui/index.js | 3 + 2 files changed, 149 insertions(+) create mode 100644 src/ui/components/searchable/SearchableTable_immutable.js diff --git a/src/ui/components/searchable/SearchableTable_immutable.js b/src/ui/components/searchable/SearchableTable_immutable.js new file mode 100644 index 000000000..1b27bf1bf --- /dev/null +++ b/src/ui/components/searchable/SearchableTable_immutable.js @@ -0,0 +1,146 @@ +/** + * Copyright 2018-present Facebook. + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * @format + */ + +import type {ManagedTableProps_immutable, TableBodyRow, Filter} from 'flipper'; +import type {SearchableProps} from './Searchable.js'; +import {PureComponent} from 'react'; +import ManagedTable_immutable from '../table/ManagedTable_immutable.js'; + +import textContent from '../../../utils/textContent.js'; +import Searchable from './Searchable.js'; +import deepEqual from 'deep-equal'; + +type Props = {| + ...ManagedTableProps_immutable, + ...SearchableProps, + /** Reference to the table */ + innerRef?: (ref: React.ElementRef<*>) => void, + /** Filters that are added to the filterbar by default */ + defaultFilters: Array, +|}; + +type State = { + filterRows: (row: TableBodyRow) => boolean, +}; + +const rowMatchesFilters = (filters: Array, row: TableBodyRow) => + filters + .map((filter: Filter) => { + if (filter.type === 'enum' && row.type != null) { + return filter.value.length === 0 || filter.value.indexOf(row.type) > -1; + } else if (filter.type === 'include') { + return ( + textContent(row.columns[filter.key].value).toLowerCase() === + filter.value.toLowerCase() + ); + } else if (filter.type === 'exclude') { + return ( + textContent(row.columns[filter.key].value).toLowerCase() !== + filter.value.toLowerCase() + ); + } else { + return true; + } + }) + .every(x => x === true); + +function rowMatchesRegex(values: Array, regex: string): boolean { + try { + const re = new RegExp(regex); + return values.some(x => re.test(x)); + } catch (e) { + return false; + } +} + +function rowMatchesSearchTerm( + searchTerm: string, + isRegex: boolean, + row: TableBodyRow, +): boolean { + if (searchTerm == null || searchTerm.length === 0) { + return true; + } + const rowValues = Object.keys(row.columns).map(key => + textContent(row.columns[key].value), + ); + if (isRegex) { + return rowMatchesRegex(rowValues, searchTerm); + } + return rowValues.some(x => + x.toLowerCase().includes(searchTerm.toLowerCase()), + ); +} + +const filterRowsFactory = ( + filters: Array, + searchTerm: string, + regexSearch: boolean, +) => (row: TableBodyRow): boolean => + rowMatchesFilters(filters, row) && + rowMatchesSearchTerm(searchTerm, regexSearch, row); + +class SearchableManagedTable_immutable extends PureComponent { + static defaultProps = { + defaultFilters: [], + }; + + state = { + filterRows: filterRowsFactory( + this.props.filters, + this.props.searchTerm, + this.props.regexEnabled || false, + ), + }; + + componentDidMount() { + this.props.defaultFilters.map(this.props.addFilter); + } + + componentWillReceiveProps(nextProps: Props) { + if ( + nextProps.searchTerm !== this.props.searchTerm || + nextProps.regexEnabled != this.props.regexEnabled || + !deepEqual(this.props.filters, nextProps.filters) + ) { + this.setState({ + filterRows: filterRowsFactory( + nextProps.filters, + nextProps.searchTerm, + nextProps.regexEnabled || false, + ), + }); + } + } + + render() { + const { + addFilter, + searchTerm: _searchTerm, + filters: _filters, + innerRef, + rows, + ...props + } = this.props; + + return ( + + ); + } +} + +/** + * Table with filter and searchbar, supports all properties a ManagedTable + * and Searchable supports. + */ +export default Searchable(SearchableManagedTable_immutable); diff --git a/src/ui/index.js b/src/ui/index.js index 501988cc6..1d1032a0a 100644 --- a/src/ui/index.js +++ b/src/ui/index.js @@ -158,6 +158,9 @@ export { export { default as SearchableTable, } from './components/searchable/SearchableTable.js'; +export { + default as SearchableTable_immutable, +} from './components/searchable/SearchableTable_immutable.js'; export type {SearchableProps} from './components/searchable/Searchable.js'; //