Introduce column filters

Summary:
Beyond a search across all columns, it is now possible to specific columns for specific values:

* for a row to be visible, all active column filters need to be matched (e.g. both a filter on time and app has to be satisfied)
* if multiple values within a column are filtered for, these are -or-ed.
* if no value at all within a column is checked, even when they are defined, the column won't take part in filtering
* if there is a general search and column filters, a row has to satisfy both

Filters can be preconfigured, pre-configured filters cannot be removed.

Reseting will reset the filters back to their original

Move `useMemoize` to flipper-plugin

Merged the `ui/utils` and `utils` folder inside `flipper-plugin`

Reviewed By: nikoant

Differential Revision: D26450260

fbshipit-source-id: 11693d5d140cea03cad91c1e0f3438d7b129cf29
This commit is contained in:
Michel Weststrate
2021-03-16 14:54:53 -07:00
committed by Facebook GitHub Bot
parent 8aabce477b
commit 11eb19da4c
23 changed files with 529 additions and 93 deletions

View File

@@ -8,10 +8,11 @@
*/
import {DataTableColumn} from 'flipper-plugin/src/ui/datatable/DataTable';
import {Percentage} from '../utils/widthUtils';
import {Percentage} from '../../utils/widthUtils';
import produce from 'immer';
import {useCallback, useEffect, useMemo, useState} from 'react';
import {DataSource} from '../../state/datasource/DataSource';
import {useMemoize} from '../../utils/useMemoize';
export type OnColumnResize = (id: string, size: number | Percentage) => void;
export type Sorting = {
@@ -42,29 +43,50 @@ export function useDataTableManager<T extends object>(
[columns],
);
// filter is computed by useMemo to support adding column filters etc here in the future
const currentFilter = useMemo(
function computeFilter() {
if (searchValue === '') {
// unset
return undefined;
}
const addColumnFilter = useCallback((columnId: string, value: string) => {
// TODO: fix typings
setEffectiveColumns(
produce((draft: DataTableColumn<any>[]) => {
const column = draft.find((c) => c.key === columnId)!;
column.filters!.push({
label: value,
value: value.toLowerCase(),
enabled: true,
});
}),
);
}, []);
const searchString = searchValue.toLowerCase();
return function dataTableFilter(item: object) {
return Object.values(item).some((v) =>
String(v).toLowerCase().includes(searchString),
);
};
},
[searchValue],
const removeColumnFilter = useCallback((columnId: string, index: number) => {
// TODO: fix typings
setEffectiveColumns(
produce((draft: DataTableColumn<any>[]) => {
draft.find((c) => c.key === columnId)!.filters?.splice(index, 1);
}),
);
}, []);
const toggleColumnFilter = useCallback((columnId: string, index: number) => {
// TODO: fix typings
setEffectiveColumns(
produce((draft: DataTableColumn<any>[]) => {
const f = draft.find((c) => c.key === columnId)!.filters![index];
f.enabled = !f.enabled;
}),
);
}, []);
// filter is computed by useMemo to support adding column filters etc here in the future
const currentFilter = useMemoize(
computeDataTableFilter,
[searchValue, columns], // possible optimization: we only need the column filters
);
const reset = useCallback(() => {
setEffectiveColumns(computeInitialColumns(defaultColumns));
setSorting(undefined);
setSearchValue('');
dataSource.reset();
// TODO: local storage
}, [dataSource, defaultColumns]);
const resizeColumn = useCallback((id: string, width: number | Percentage) => {
@@ -152,6 +174,10 @@ export function useDataTableManager<T extends object>(
/** current selection, describes the index index in the datasources's current output (not window) */
selection,
selectItem,
/** Changing column filters */
addColumnFilter,
removeColumnFilter,
toggleColumnFilter,
};
}
@@ -160,6 +186,45 @@ function computeInitialColumns(
): DataTableColumn<any>[] {
return columns.map((c) => ({
...c,
filters:
c.filters?.map((f) => ({
...f,
predefined: true,
})) ?? [],
visible: c.visible !== false,
}));
}
export function computeDataTableFilter(
searchValue: string,
columns: DataTableColumn[],
) {
const searchString = searchValue.toLowerCase();
// the columns with an active filter are those that have filters defined,
// with at least one enabled
const filteringColumns = columns.filter((c) =>
c.filters?.some((f) => f.enabled),
);
if (searchValue === '' && !filteringColumns.length) {
// unset
return undefined;
}
return function dataTableFilter(item: any) {
for (const column of filteringColumns) {
if (
!column.filters!.some(
(f) =>
f.enabled &&
String(item[column.key]).toLowerCase().includes(f.value),
)
) {
return false; // there are filters, but none matches
}
}
return Object.values(item).some((v) =>
String(v).toLowerCase().includes(searchString),
);
};
}