Add filter exceptions to DataSource

Summary: Filter exceptions allow us to add singular items to table views. Extremely useful for Bloks Debugger where we have to jump between multiple types of rows that could be filtered out

Reviewed By: LukeDefeo

Differential Revision: D47472006

fbshipit-source-id: 74d21a65d364ec5ab88652effc06aade20ad80b2
This commit is contained in:
Andrey Goncharov
2023-07-18 05:25:59 -07:00
committed by Facebook GitHub Bot
parent cba5af60c8
commit 8397b2bab8
2 changed files with 50 additions and 2 deletions

View File

@@ -664,6 +664,7 @@ export class DataSourceView<T, KeyType> {
private sortBy: undefined | ((a: T) => Primitive) = undefined; private sortBy: undefined | ((a: T) => Primitive) = undefined;
private reverse: boolean = false; private reverse: boolean = false;
private filter?: (value: T) => boolean = undefined; private filter?: (value: T) => boolean = undefined;
private filterExceptions?: Set<KeyType> = undefined;
/** /**
* @readonly * @readonly
@@ -760,10 +761,22 @@ export class DataSourceView<T, KeyType> {
public setFilter(filter: undefined | ((value: T) => boolean)) { public setFilter(filter: undefined | ((value: T) => boolean)) {
if (this.filter !== filter) { if (this.filter !== filter) {
this.filter = filter; this.filter = filter;
// Filter exceptions are relevant for one filter only
this.filterExceptions = undefined;
this.rebuild(); this.rebuild();
} }
} }
/**
* Granular control over filters to add one-off exceptions to them.
* They allow us to add singular items to table views.
* Extremely useful for Bloks Debugger where we have to jump between multiple types of rows that could be filtered out
*/
public setFilterExpections(ids: KeyType[]) {
this.filterExceptions = new Set(ids);
this.rebuild();
}
public toggleReversed() { public toggleReversed() {
this.setReversed(!this.reverse); this.setReversed(!this.reverse);
} }
@@ -782,6 +795,7 @@ export class DataSourceView<T, KeyType> {
this.sortBy = undefined; this.sortBy = undefined;
this.reverse = false; this.reverse = false;
this.filter = undefined; this.filter = undefined;
this.filterExceptions = undefined;
this.windowStart = 0; this.windowStart = 0;
this.windowEnd = 0; this.windowEnd = 0;
this.rebuild(); this.rebuild();
@@ -891,6 +905,7 @@ export class DataSourceView<T, KeyType> {
case 'append': { case 'append': {
const {entry} = event; const {entry} = event;
entry.visible[this.viewId] = filter ? filter(entry.value) : true; entry.visible[this.viewId] = filter ? filter(entry.value) : true;
this.applyFilterExceptions(entry);
if (!entry.visible[this.viewId]) { if (!entry.visible[this.viewId]) {
// not in filter? skip this entry // not in filter? skip this entry
return; return;
@@ -908,6 +923,7 @@ export class DataSourceView<T, KeyType> {
case 'update': { case 'update': {
const {entry} = event; const {entry} = event;
entry.visible[this.viewId] = filter ? filter(entry.value) : true; entry.visible[this.viewId] = filter ? filter(entry.value) : true;
this.applyFilterExceptions(entry);
// short circuit; no view active so update straight away // short circuit; no view active so update straight away
if (!filter && !sortBy) { if (!filter && !sortBy) {
output[event.index].approxIndex[this.viewId] = event.index; output[event.index].approxIndex[this.viewId] = event.index;
@@ -1015,6 +1031,7 @@ export class DataSourceView<T, KeyType> {
let output = filter let output = filter
? records.filter((entry) => { ? records.filter((entry) => {
entry.visible[this.viewId] = filter(entry.value); entry.visible[this.viewId] = filter(entry.value);
this.applyFilterExceptions(entry);
return entry.visible[this.viewId]; return entry.visible[this.viewId];
}) })
: records.slice(); : records.slice();
@@ -1082,4 +1099,16 @@ export class DataSourceView<T, KeyType> {
this._output.splice(insertionIndex, 0, entry); this._output.splice(insertionIndex, 0, entry);
this.notifyItemShift(insertionIndex, 1); this.notifyItemShift(insertionIndex, 1);
} }
private applyFilterExceptions(entry: Entry<T>) {
if (
this.datasource.keyAttribute &&
this.filter &&
this.filterExceptions &&
!entry.visible[this.viewId]
) {
const keyValue = entry.value[this.datasource.keyAttribute] as KeyType;
entry.visible[this.viewId] = this.filterExceptions.has(keyValue);
}
}
} }

View File

@@ -356,6 +356,21 @@ test('filter', () => {
expect(rawOutput(ds)).toEqual([newCookie, newCoffee, submitBug, a, b]); expect(rawOutput(ds)).toEqual([newCookie, newCoffee, submitBug, a, b]);
}); });
test('filter + filterExceptions', () => {
const ds = createDataSource<Todo, 'id'>([eatCookie, drinkCoffee, submitBug], {
key: 'id',
});
ds.view.setFilter((t) => t.title.indexOf('c') === -1);
expect(rawOutput(ds)).toEqual([submitBug]);
// add exception
ds.view.setFilterExpections([drinkCoffee.id]);
expect(rawOutput(ds)).toEqual([drinkCoffee, submitBug]);
});
test('reverse without sorting', () => { test('reverse without sorting', () => {
const ds = createDataSource<Todo>([eatCookie, drinkCoffee]); const ds = createDataSource<Todo>([eatCookie, drinkCoffee]);
ds.view.setWindow(0, 100); ds.view.setWindow(0, 100);
@@ -452,8 +467,12 @@ test('reset', () => {
key: 'id', key: 'id',
}); });
ds.view.setSortBy('title'); ds.view.setSortBy('title');
ds.view.setFilter((v) => v.id !== 'cookie'); ds.view.setFilter((v) => v.id === 'cookie');
expect(rawOutput(ds)).toEqual([drinkCoffee, submitBug]); expect(rawOutput(ds)).toEqual([eatCookie]);
expect([...ds.keys()]).toEqual(['bug', 'coffee', 'cookie']);
ds.view.setFilterExpections([drinkCoffee.id]);
expect(rawOutput(ds)).toEqual([drinkCoffee, eatCookie]);
expect([...ds.keys()]).toEqual(['bug', 'coffee', 'cookie']); expect([...ds.keys()]).toEqual(['bug', 'coffee', 'cookie']);
ds.view.reset(); ds.view.reset();