Highlight search terms in logs with yellow when highlight search setting is enabled
Summary: Building on the previous diff which added a setting to enable/disable highlighting search terms in the logs. This diff adds the actual highlighting and connects with the setting. The highlighting currently only supports one color, while the next diff will seek to support a preset of a "custom" colors for the highlighting Reviewed By: mweststrate Differential Revision: D37348441 fbshipit-source-id: 7a2b74b16f239d5e36c213e06ccb86f74eaa8df5
This commit is contained in:
committed by
Facebook GitHub Bot
parent
24a314054e
commit
2f39ede6f7
@@ -20,6 +20,7 @@ import {safeStringify} from '../utils/safeStringify';
|
|||||||
import {urlRegex} from '../utils/urlRegex';
|
import {urlRegex} from '../utils/urlRegex';
|
||||||
import {useTableRedraw} from '../data-source/index';
|
import {useTableRedraw} from '../data-source/index';
|
||||||
import {theme} from './theme';
|
import {theme} from './theme';
|
||||||
|
import {HighlightManager} from './Highlight';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Formatter is used to render an arbitrarily value to React. If a formatter returns 'undefined'
|
* A Formatter is used to render an arbitrarily value to React. If a formatter returns 'undefined'
|
||||||
@@ -27,48 +28,65 @@ import {theme} from './theme';
|
|||||||
*
|
*
|
||||||
* In case further processing by the default formatter is to be avoided, make sure a string is returned from any custom formatter.
|
* In case further processing by the default formatter is to be avoided, make sure a string is returned from any custom formatter.
|
||||||
*/
|
*/
|
||||||
export type Formatter = (value: any) => string | React.ReactElement | any;
|
export type Formatter = (
|
||||||
|
value: any,
|
||||||
|
highlighter?: HighlightManager,
|
||||||
|
) => string | React.ReactElement | any;
|
||||||
|
|
||||||
export const DataFormatter = {
|
export const DataFormatter = {
|
||||||
defaultFormatter(value: any) {
|
defaultFormatter(value: any, highlighter?: HighlightManager) {
|
||||||
if (isValidElement(value)) {
|
if (isValidElement(value)) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
let res = '';
|
||||||
switch (typeof value) {
|
switch (typeof value) {
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
return value ? 'true' : 'false';
|
res = value ? 'true' : 'false';
|
||||||
|
break;
|
||||||
case 'number':
|
case 'number':
|
||||||
return '' + value;
|
res = '' + value;
|
||||||
|
break;
|
||||||
case 'undefined':
|
case 'undefined':
|
||||||
return '';
|
break;
|
||||||
case 'string':
|
case 'string':
|
||||||
return value;
|
res = value;
|
||||||
|
break;
|
||||||
case 'object': {
|
case 'object': {
|
||||||
if (value === null) return '';
|
if (value === null) break;
|
||||||
if (value instanceof Date) {
|
if (value instanceof Date) {
|
||||||
return (
|
res =
|
||||||
value.toTimeString().split(' ')[0] +
|
value.toTimeString().split(' ')[0] +
|
||||||
'.' +
|
'.' +
|
||||||
pad('' + value.getMilliseconds(), 3, '0')
|
pad('' + value.getMilliseconds(), 3, '0');
|
||||||
);
|
break;
|
||||||
}
|
}
|
||||||
if (value instanceof Map) {
|
if (value instanceof Map) {
|
||||||
return safeStringify(Array.from(value.entries()));
|
res = safeStringify(Array.from(value.entries()));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (value instanceof Set) {
|
if (value instanceof Set) {
|
||||||
return safeStringify(Array.from(value.values()));
|
res = safeStringify(Array.from(value.values()));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return safeStringify(value);
|
res = safeStringify(value);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return '<unrenderable value>';
|
res = '<unrenderable value>';
|
||||||
}
|
}
|
||||||
|
return highlighter?.render(res) ?? res;
|
||||||
},
|
},
|
||||||
|
|
||||||
truncate(maxLength: number) {
|
truncate(maxLength: number) {
|
||||||
return (value: any) => {
|
return (value: any, highlighter?: HighlightManager) => {
|
||||||
if (typeof value === 'string' && value.length > maxLength) {
|
if (typeof value === 'string' && value.length > maxLength) {
|
||||||
return <TruncateHelper value={value} maxLength={maxLength} />;
|
return (
|
||||||
|
<TruncateHelper
|
||||||
|
value={value}
|
||||||
|
maxLength={maxLength}
|
||||||
|
textWrapper={highlighter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
@@ -131,16 +149,20 @@ export const DataFormatter = {
|
|||||||
return value;
|
return value;
|
||||||
},
|
},
|
||||||
|
|
||||||
format(value: any, formatters?: Formatter[] | Formatter): any {
|
format(
|
||||||
|
value: any,
|
||||||
|
formatters?: Formatter[] | Formatter,
|
||||||
|
highlighter?: HighlightManager,
|
||||||
|
): any {
|
||||||
let res = value;
|
let res = value;
|
||||||
if (Array.isArray(formatters)) {
|
if (Array.isArray(formatters)) {
|
||||||
for (const formatter of formatters) {
|
for (const formatter of formatters) {
|
||||||
res = formatter(res);
|
res = formatter(res, highlighter);
|
||||||
}
|
}
|
||||||
} else if (formatters) {
|
} else if (formatters) {
|
||||||
res = formatters(res);
|
res = formatters(res, highlighter);
|
||||||
}
|
}
|
||||||
return DataFormatter.defaultFormatter(res);
|
return DataFormatter.defaultFormatter(res, highlighter);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,16 +170,18 @@ export const DataFormatter = {
|
|||||||
export function TruncateHelper({
|
export function TruncateHelper({
|
||||||
value,
|
value,
|
||||||
maxLength,
|
maxLength,
|
||||||
|
textWrapper,
|
||||||
}: {
|
}: {
|
||||||
value: string;
|
value: string;
|
||||||
maxLength: number;
|
maxLength: number;
|
||||||
|
textWrapper?: HighlightManager; //Could be a generic type
|
||||||
}) {
|
}) {
|
||||||
const [collapsed, setCollapsed] = useState(true);
|
const [collapsed, setCollapsed] = useState(true);
|
||||||
const redrawRow = useTableRedraw();
|
const redrawRow = useTableRedraw();
|
||||||
|
const message = collapsed ? value.substr(0, maxLength) : value;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{collapsed ? value.substr(0, maxLength) : value}
|
{textWrapper ? textWrapper.render(message) : message}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCollapsed((c) => !c);
|
setCollapsed((c) => !c);
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import {usePluginInstanceMaybe} from '../../plugin/PluginContext';
|
|||||||
import {debounce} from 'lodash';
|
import {debounce} from 'lodash';
|
||||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||||
import {createDataSource} from '../../state/createDataSource';
|
import {createDataSource} from '../../state/createDataSource';
|
||||||
|
import {HighlightProvider} from '../Highlight';
|
||||||
|
|
||||||
type DataTableBaseProps<T = any> = {
|
type DataTableBaseProps<T = any> = {
|
||||||
columns: DataTableColumn<T>[];
|
columns: DataTableColumn<T>[];
|
||||||
@@ -603,7 +604,14 @@ export function DataTable<T extends object>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Container grow={props.scrollable}>
|
<Layout.Container grow={props.scrollable}>
|
||||||
|
<HighlightProvider
|
||||||
|
text={
|
||||||
|
tableState.highlightSearchSetting.highlightEnabled
|
||||||
|
? tableState.searchValue
|
||||||
|
: ''
|
||||||
|
}>
|
||||||
{mainSection}
|
{mainSection}
|
||||||
|
</HighlightProvider>
|
||||||
{props.enableAutoScroll && (
|
{props.enableAutoScroll && (
|
||||||
<AutoScroller>
|
<AutoScroller>
|
||||||
<PushpinFilled
|
<PushpinFilled
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {DataFormatter} from '../DataFormatter';
|
|||||||
import {Dropdown} from 'antd';
|
import {Dropdown} from 'antd';
|
||||||
import {contextMenuTrigger} from '../data-inspector/DataInspectorNode';
|
import {contextMenuTrigger} from '../data-inspector/DataInspectorNode';
|
||||||
import {getValueAtPath} from './DataTableManager';
|
import {getValueAtPath} from './DataTableManager';
|
||||||
|
import {HighlightManager, useHighlighter} from '../Highlight';
|
||||||
|
|
||||||
// heuristic for row estimation, should match any future styling updates
|
// heuristic for row estimation, should match any future styling updates
|
||||||
export const DEFAULT_ROW_HEIGHT = 24;
|
export const DEFAULT_ROW_HEIGHT = 24;
|
||||||
@@ -109,6 +110,7 @@ export const TableRow = memo(function TableRow<T>({
|
|||||||
highlighted,
|
highlighted,
|
||||||
config,
|
config,
|
||||||
}: TableRowProps<T>) {
|
}: TableRowProps<T>) {
|
||||||
|
const highlighter = useHighlighter();
|
||||||
const row = (
|
const row = (
|
||||||
<TableBodyRowContainer
|
<TableBodyRowContainer
|
||||||
highlighted={highlighted}
|
highlighted={highlighted}
|
||||||
@@ -127,6 +129,7 @@ export const TableRow = memo(function TableRow<T>({
|
|||||||
record,
|
record,
|
||||||
highlighted,
|
highlighted,
|
||||||
itemIndex,
|
itemIndex,
|
||||||
|
highlighter,
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -157,8 +160,13 @@ export function renderColumnValue<T>(
|
|||||||
record: T,
|
record: T,
|
||||||
highlighted: boolean,
|
highlighted: boolean,
|
||||||
itemIndex: number,
|
itemIndex: number,
|
||||||
|
highlighter?: HighlightManager,
|
||||||
) {
|
) {
|
||||||
return col.onRender
|
return col.onRender
|
||||||
? col.onRender(record, highlighted, itemIndex)
|
? col.onRender(record, highlighted, itemIndex)
|
||||||
: DataFormatter.format(getValueAtPath(record, col.key), col.formatters);
|
: DataFormatter.format(
|
||||||
|
getValueAtPath(record, col.key),
|
||||||
|
col.formatters,
|
||||||
|
highlighter,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ test('update and append', async () => {
|
|||||||
{
|
{
|
||||||
const elem = await rendering.findAllByText('test DataTable');
|
const elem = await rendering.findAllByText('test DataTable');
|
||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||||
>
|
>
|
||||||
@@ -57,13 +57,23 @@ test('update and append', async () => {
|
|||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
true
|
true
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@@ -103,7 +113,7 @@ test('column visibility', async () => {
|
|||||||
{
|
{
|
||||||
const elem = await rendering.findAllByText('test DataTable');
|
const elem = await rendering.findAllByText('test DataTable');
|
||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||||
>
|
>
|
||||||
@@ -111,13 +121,23 @@ test('column visibility', async () => {
|
|||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
true
|
true
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@@ -130,7 +150,7 @@ test('column visibility', async () => {
|
|||||||
{
|
{
|
||||||
const elem = await rendering.findAllByText('test DataTable');
|
const elem = await rendering.findAllByText('test DataTable');
|
||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||||
>
|
>
|
||||||
@@ -138,7 +158,12 @@ test('column visibility', async () => {
|
|||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
@@ -151,7 +176,7 @@ test('column visibility', async () => {
|
|||||||
{
|
{
|
||||||
const elem = await rendering.findAllByText('test DataTable');
|
const elem = await rendering.findAllByText('test DataTable');
|
||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement?.children.length).toBe(2);
|
expect(elem[0].parentElement?.parentElement?.children.length).toBe(2);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ test('update and append', async () => {
|
|||||||
{
|
{
|
||||||
const elem = await rendering.findAllByText('test DataTable');
|
const elem = await rendering.findAllByText('test DataTable');
|
||||||
expect(elem.length).toBe(1);
|
expect(elem.length).toBe(1);
|
||||||
expect(elem[0].parentElement).toMatchInlineSnapshot(`
|
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||||
>
|
>
|
||||||
@@ -50,13 +50,23 @@ test('update and append', async () => {
|
|||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
test DataTable
|
test DataTable
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
true
|
true
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|||||||
@@ -117,8 +117,9 @@ test('It can render rows', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
expect((await renderer.findByText('unique-string')).parentElement)
|
expect(
|
||||||
.toMatchInlineSnapshot(`
|
(await renderer.findByText('unique-string')).parentElement?.parentElement,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
<div
|
<div
|
||||||
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
|
||||||
>
|
>
|
||||||
@@ -126,39 +127,76 @@ test('It can render rows', async () => {
|
|||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
width="14%"
|
width="14%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
00:00:00.000
|
00:00:00.000
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
width="14%"
|
width="14%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
Android Phone
|
Android Phone
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
width="14%"
|
width="14%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
FB4A
|
FB4A
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
width="14%"
|
width="14%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
unique-string
|
unique-string
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
width="14%"
|
width="14%"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
/>
|
/>
|
||||||
<div
|
</span>
|
||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
</div>
|
||||||
width="14%"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
width="14%"
|
width="14%"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
|
||||||
|
width="14%"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<span
|
||||||
|
class="css-1cfwmd7-Highlighted eiud9hg0"
|
||||||
|
/>
|
||||||
toClient:send
|
toClient:send
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|||||||
Reference in New Issue
Block a user