diff --git a/desktop/flipper-plugin-core/src/data-source/DataSource.tsx b/desktop/flipper-plugin-core/src/data-source/DataSource.tsx index 9b5bb860ce2..771ca5ac6fa 100644 --- a/desktop/flipper-plugin-core/src/data-source/DataSource.tsx +++ b/desktop/flipper-plugin-core/src/data-source/DataSource.tsx @@ -664,6 +664,7 @@ export class DataSourceView { private sortBy: undefined | ((a: T) => Primitive) = undefined; private reverse: boolean = false; private filter?: (value: T) => boolean = undefined; + private filterExceptions?: Set = undefined; /** * @readonly @@ -760,10 +761,22 @@ export class DataSourceView { public setFilter(filter: undefined | ((value: T) => boolean)) { if (this.filter !== filter) { this.filter = filter; + // Filter exceptions are relevant for one filter only + this.filterExceptions = undefined; 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() { this.setReversed(!this.reverse); } @@ -782,6 +795,7 @@ export class DataSourceView { this.sortBy = undefined; this.reverse = false; this.filter = undefined; + this.filterExceptions = undefined; this.windowStart = 0; this.windowEnd = 0; this.rebuild(); @@ -891,6 +905,7 @@ export class DataSourceView { case 'append': { const {entry} = event; entry.visible[this.viewId] = filter ? filter(entry.value) : true; + this.applyFilterExceptions(entry); if (!entry.visible[this.viewId]) { // not in filter? skip this entry return; @@ -908,6 +923,7 @@ export class DataSourceView { case 'update': { const {entry} = event; entry.visible[this.viewId] = filter ? filter(entry.value) : true; + this.applyFilterExceptions(entry); // short circuit; no view active so update straight away if (!filter && !sortBy) { output[event.index].approxIndex[this.viewId] = event.index; @@ -1015,6 +1031,7 @@ export class DataSourceView { let output = filter ? records.filter((entry) => { entry.visible[this.viewId] = filter(entry.value); + this.applyFilterExceptions(entry); return entry.visible[this.viewId]; }) : records.slice(); @@ -1082,4 +1099,16 @@ export class DataSourceView { this._output.splice(insertionIndex, 0, entry); this.notifyItemShift(insertionIndex, 1); } + + private applyFilterExceptions(entry: Entry) { + 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); + } + } } diff --git a/desktop/flipper-plugin-core/src/data-source/__tests__/datasource-basics.node.tsx b/desktop/flipper-plugin-core/src/data-source/__tests__/datasource-basics.node.tsx index c9bedac4665..20088bf10b8 100644 --- a/desktop/flipper-plugin-core/src/data-source/__tests__/datasource-basics.node.tsx +++ b/desktop/flipper-plugin-core/src/data-source/__tests__/datasource-basics.node.tsx @@ -356,6 +356,21 @@ test('filter', () => { expect(rawOutput(ds)).toEqual([newCookie, newCoffee, submitBug, a, b]); }); +test('filter + filterExceptions', () => { + const ds = createDataSource([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', () => { const ds = createDataSource([eatCookie, drinkCoffee]); ds.view.setWindow(0, 100); @@ -452,8 +467,12 @@ test('reset', () => { key: 'id', }); ds.view.setSortBy('title'); - ds.view.setFilter((v) => v.id !== 'cookie'); - expect(rawOutput(ds)).toEqual([drinkCoffee, submitBug]); + ds.view.setFilter((v) => v.id === 'cookie'); + 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']); ds.view.reset();