Add neighbouring lines feature to Flipper Logs plugin
Summary: Adding a feature to Flipper's Logs plugin where: 1) you search for something, 2) click on a line among the filtered search results, 3) press control, 4) get taken back to unfiltered list of all messages, centered on your selected line This is to help debugging where the user may add a print statement but the error happens after it, and it's difficult to catch without a lot of scrolling. Reviewed By: mweststrate Differential Revision: D33446285 fbshipit-source-id: 19aa472a12de074e561dbe37b44821fc29bf5c91
This commit is contained in:
committed by
Facebook GitHub Bot
parent
30becc1ced
commit
035ba5613c
@@ -308,6 +308,9 @@ export function DataTable<T extends object>(
|
|||||||
case 'Escape':
|
case 'Escape':
|
||||||
tableManager.clearSelection();
|
tableManager.clearSelection();
|
||||||
break;
|
break;
|
||||||
|
case 'Control':
|
||||||
|
tableManager.setSelectedSearchRecord();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
handled = false;
|
handled = false;
|
||||||
}
|
}
|
||||||
@@ -348,7 +351,9 @@ export function DataTable<T extends object>(
|
|||||||
[
|
[
|
||||||
tableState.searchValue,
|
tableState.searchValue,
|
||||||
tableState.useRegex,
|
tableState.useRegex,
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
...tableState.columns.map((c) => c.filters),
|
...tableState.columns.map((c) => c.filters),
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
...tableState.columns.map((c) => c.inversed),
|
...tableState.columns.map((c) => c.inversed),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -473,6 +478,30 @@ export function DataTable<T extends object>(
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
function findMappedIndex() {
|
||||||
|
// Hardcoded delay to give dataSource.view time to update, otherwise
|
||||||
|
// the entries we loop over here won't be the list of unfiltered records
|
||||||
|
// the user sees, so there won't be a match found
|
||||||
|
const delay = 300;
|
||||||
|
|
||||||
|
if (tableState.selectedSearchRecord) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
for (let i = 0; i < dataSource.view.size; i++) {
|
||||||
|
if (dataSource.view.get(i) === tableState.selectedSearchRecord) {
|
||||||
|
tableManager.clearSelectedSearchRecord();
|
||||||
|
tableManager.selectItem(i, false, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dataSource, selection, tableManager, tableState.selectedSearchRecord],
|
||||||
|
);
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<Layout.Container>
|
<Layout.Container>
|
||||||
{props.enableSearchbar && (
|
{props.enableSearchbar && (
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ type PersistedState = {
|
|||||||
columns: Pick<DataTableColumn, 'key' | 'width' | 'filters' | 'visible'>[];
|
columns: Pick<DataTableColumn, 'key' | 'width' | 'filters' | 'visible'>[];
|
||||||
scrollOffset: number;
|
scrollOffset: number;
|
||||||
autoScroll: boolean;
|
autoScroll: boolean;
|
||||||
|
selectedSearchRecord: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Action<Name extends string, Args = {}> = {type: Name} & Args;
|
type Action<Name extends string, Args = {}> = {type: Name} & Args;
|
||||||
@@ -90,7 +91,9 @@ type DataManagerActions<T> =
|
|||||||
| Action<'appliedInitialScroll'>
|
| Action<'appliedInitialScroll'>
|
||||||
| Action<'toggleUseRegex'>
|
| Action<'toggleUseRegex'>
|
||||||
| Action<'toggleAutoScroll'>
|
| Action<'toggleAutoScroll'>
|
||||||
| Action<'setAutoScroll', {autoScroll: boolean}>;
|
| Action<'setAutoScroll', {autoScroll: boolean}>
|
||||||
|
| Action<'clearSelectedSearchRecord'>
|
||||||
|
| Action<'setSelectedSearchRecord'>;
|
||||||
|
|
||||||
type DataManagerConfig<T> = {
|
type DataManagerConfig<T> = {
|
||||||
dataSource: DataSource<T, T[keyof T]>;
|
dataSource: DataSource<T, T[keyof T]>;
|
||||||
@@ -113,6 +116,8 @@ type DataManagerState<T> = {
|
|||||||
searchValue: string;
|
searchValue: string;
|
||||||
useRegex: boolean;
|
useRegex: boolean;
|
||||||
autoScroll: boolean;
|
autoScroll: boolean;
|
||||||
|
/** Used to remember the record entry to lookup when user presses ctrl */
|
||||||
|
selectedSearchRecord: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DataTableReducer<T> = Reducer<
|
export type DataTableReducer<T> = Reducer<
|
||||||
@@ -251,6 +256,24 @@ export const dataTableManagerReducer = produce<
|
|||||||
draft.autoScroll = action.autoScroll;
|
draft.autoScroll = action.autoScroll;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'setSelectedSearchRecord': {
|
||||||
|
if (draft.searchValue !== '') {
|
||||||
|
// Clear search to get unfiltered list of records
|
||||||
|
draft.searchValue = '';
|
||||||
|
|
||||||
|
// Save the record to map back in DataTable
|
||||||
|
draft.selectedSearchRecord = config.dataSource.view.get(
|
||||||
|
draft.selection.current,
|
||||||
|
);
|
||||||
|
|
||||||
|
draft.selection = castDraft(emptySelection);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'clearSelectedSearchRecord': {
|
||||||
|
draft.selectedSearchRecord = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error('Unknown action ' + (action as any).type);
|
throw new Error('Unknown action ' + (action as any).type);
|
||||||
}
|
}
|
||||||
@@ -280,6 +303,8 @@ export type DataTableManager<T> = {
|
|||||||
sortColumn(column: keyof T, direction?: SortDirection): void;
|
sortColumn(column: keyof T, direction?: SortDirection): void;
|
||||||
setSearchValue(value: string): void;
|
setSearchValue(value: string): void;
|
||||||
dataSource: DataSource<T, T[keyof T]>;
|
dataSource: DataSource<T, T[keyof T]>;
|
||||||
|
setSelectedSearchRecord(): void;
|
||||||
|
clearSelectedSearchRecord(): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createDataTableManager<T>(
|
export function createDataTableManager<T>(
|
||||||
@@ -323,6 +348,12 @@ export function createDataTableManager<T>(
|
|||||||
setSearchValue(value) {
|
setSearchValue(value) {
|
||||||
dispatch({type: 'setSearchValue', value});
|
dispatch({type: 'setSearchValue', value});
|
||||||
},
|
},
|
||||||
|
setSelectedSearchRecord() {
|
||||||
|
dispatch({type: 'setSelectedSearchRecord'});
|
||||||
|
},
|
||||||
|
clearSelectedSearchRecord() {
|
||||||
|
dispatch({type: 'clearSelectedSearchRecord'});
|
||||||
|
},
|
||||||
dataSource,
|
dataSource,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -366,6 +397,7 @@ export function createInitialState<T>(
|
|||||||
searchValue: prefs?.search ?? '',
|
searchValue: prefs?.search ?? '',
|
||||||
useRegex: prefs?.useRegex ?? false,
|
useRegex: prefs?.useRegex ?? false,
|
||||||
autoScroll: prefs?.autoScroll ?? config.autoScroll ?? false,
|
autoScroll: prefs?.autoScroll ?? config.autoScroll ?? false,
|
||||||
|
selectedSearchRecord: null,
|
||||||
};
|
};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
res.config[immerable] = false; // optimization: never proxy anything in config
|
res.config[immerable] = false; // optimization: never proxy anything in config
|
||||||
@@ -442,6 +474,7 @@ export function savePreferences(
|
|||||||
})),
|
})),
|
||||||
scrollOffset,
|
scrollOffset,
|
||||||
autoScroll: state.autoScroll,
|
autoScroll: state.autoScroll,
|
||||||
|
selectedSearchRecord: state.selectedSearchRecord,
|
||||||
};
|
};
|
||||||
localStorage.setItem(state.storageKey, JSON.stringify(prefs));
|
localStorage.setItem(state.storageKey, JSON.stringify(prefs));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user