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:
Feiyu Wong
2022-06-29 10:36:52 -07:00
committed by Facebook GitHub Bot
parent 24a314054e
commit 2f39ede6f7
6 changed files with 201 additions and 88 deletions

View File

@@ -20,6 +20,7 @@ import {safeStringify} from '../utils/safeStringify';
import {urlRegex} from '../utils/urlRegex';
import {useTableRedraw} from '../data-source/index';
import {theme} from './theme';
import {HighlightManager} from './Highlight';
/**
* 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.
*/
export type Formatter = (value: any) => string | React.ReactElement | any;
export type Formatter = (
value: any,
highlighter?: HighlightManager,
) => string | React.ReactElement | any;
export const DataFormatter = {
defaultFormatter(value: any) {
defaultFormatter(value: any, highlighter?: HighlightManager) {
if (isValidElement(value)) {
return value;
}
let res = '';
switch (typeof value) {
case 'boolean':
return value ? 'true' : 'false';
res = value ? 'true' : 'false';
break;
case 'number':
return '' + value;
res = '' + value;
break;
case 'undefined':
return '';
break;
case 'string':
return value;
res = value;
break;
case 'object': {
if (value === null) return '';
if (value === null) break;
if (value instanceof Date) {
return (
res =
value.toTimeString().split(' ')[0] +
'.' +
pad('' + value.getMilliseconds(), 3, '0')
);
pad('' + value.getMilliseconds(), 3, '0');
break;
}
if (value instanceof Map) {
return safeStringify(Array.from(value.entries()));
res = safeStringify(Array.from(value.entries()));
break;
}
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:
return '<unrenderable value>';
res = '<unrenderable value>';
}
return highlighter?.render(res) ?? res;
},
truncate(maxLength: number) {
return (value: any) => {
return (value: any, highlighter?: HighlightManager) => {
if (typeof value === 'string' && value.length > maxLength) {
return <TruncateHelper value={value} maxLength={maxLength} />;
return (
<TruncateHelper
value={value}
maxLength={maxLength}
textWrapper={highlighter}
/>
);
}
return value;
};
@@ -131,16 +149,20 @@ export const DataFormatter = {
return value;
},
format(value: any, formatters?: Formatter[] | Formatter): any {
format(
value: any,
formatters?: Formatter[] | Formatter,
highlighter?: HighlightManager,
): any {
let res = value;
if (Array.isArray(formatters)) {
for (const formatter of formatters) {
res = formatter(res);
res = formatter(res, highlighter);
}
} 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({
value,
maxLength,
textWrapper,
}: {
value: string;
maxLength: number;
textWrapper?: HighlightManager; //Could be a generic type
}) {
const [collapsed, setCollapsed] = useState(true);
const redrawRow = useTableRedraw();
const message = collapsed ? value.substr(0, maxLength) : value;
return (
<>
{collapsed ? value.substr(0, maxLength) : value}
{textWrapper ? textWrapper.render(message) : message}
<Button
onClick={() => {
setCollapsed((c) => !c);

View File

@@ -53,6 +53,7 @@ import {usePluginInstanceMaybe} from '../../plugin/PluginContext';
import {debounce} from 'lodash';
import {useInUnitTest} from '../../utils/useInUnitTest';
import {createDataSource} from '../../state/createDataSource';
import {HighlightProvider} from '../Highlight';
type DataTableBaseProps<T = any> = {
columns: DataTableColumn<T>[];
@@ -603,7 +604,14 @@ export function DataTable<T extends object>(
return (
<Layout.Container grow={props.scrollable}>
{mainSection}
<HighlightProvider
text={
tableState.highlightSearchSetting.highlightEnabled
? tableState.searchValue
: ''
}>
{mainSection}
</HighlightProvider>
{props.enableAutoScroll && (
<AutoScroller>
<PushpinFilled

View File

@@ -16,6 +16,7 @@ import {DataFormatter} from '../DataFormatter';
import {Dropdown} from 'antd';
import {contextMenuTrigger} from '../data-inspector/DataInspectorNode';
import {getValueAtPath} from './DataTableManager';
import {HighlightManager, useHighlighter} from '../Highlight';
// heuristic for row estimation, should match any future styling updates
export const DEFAULT_ROW_HEIGHT = 24;
@@ -109,6 +110,7 @@ export const TableRow = memo(function TableRow<T>({
highlighted,
config,
}: TableRowProps<T>) {
const highlighter = useHighlighter();
const row = (
<TableBodyRowContainer
highlighted={highlighted}
@@ -127,6 +129,7 @@ export const TableRow = memo(function TableRow<T>({
record,
highlighted,
itemIndex,
highlighter,
);
return (
@@ -157,8 +160,13 @@ export function renderColumnValue<T>(
record: T,
highlighted: boolean,
itemIndex: number,
highlighter?: HighlightManager,
) {
return col.onRender
? col.onRender(record, highlighted, itemIndex)
: DataFormatter.format(getValueAtPath(record, col.key), col.formatters);
: DataFormatter.format(
getValueAtPath(record, col.key),
col.formatters,
highlighter,
);
}

View File

@@ -49,24 +49,34 @@ test('update and append', async () => {
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
</div>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
true
</div>
</div>
`);
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
test DataTable
</span>
</div>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
true
</span>
</div>
</div>
`);
}
act(() => {
@@ -103,24 +113,34 @@ test('column visibility', async () => {
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
</div>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
true
</div>
</div>
`);
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
test DataTable
</span>
</div>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
true
</span>
</div>
</div>
`);
}
// hide done
@@ -130,18 +150,23 @@ test('column visibility', async () => {
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
</div>
</div>
`);
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
test DataTable
</span>
</div>
</div>
`);
}
// reset
@@ -151,7 +176,7 @@ test('column visibility', async () => {
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement?.children.length).toBe(2);
expect(elem[0].parentElement?.parentElement?.children.length).toBe(2);
}
});

View File

@@ -42,7 +42,7 @@ test('update and append', async () => {
{
const elem = await rendering.findAllByText('test DataTable');
expect(elem.length).toBe(1);
expect(elem[0].parentElement).toMatchInlineSnapshot(`
expect(elem[0].parentElement?.parentElement).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
@@ -50,13 +50,23 @@ test('update and append', async () => {
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
test DataTable
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
test DataTable
</span>
</div>
<div
class="css-1baxqcf-TableBodyColumnContainer e1luu51r0"
width="50%"
>
true
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
true
</span>
</div>
</div>
`);

View File

@@ -117,8 +117,9 @@ test('It can render rows', async () => {
});
});
expect((await renderer.findByText('unique-string')).parentElement)
.toMatchInlineSnapshot(`
expect(
(await renderer.findByText('unique-string')).parentElement?.parentElement,
).toMatchInlineSnapshot(`
<div
class="ant-dropdown-trigger css-1k3kr6b-TableBodyRowContainer e1luu51r1"
>
@@ -126,39 +127,76 @@ test('It can render rows', async () => {
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
>
00:00:00.000
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
00:00:00.000
</span>
</div>
<div
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
>
Android Phone
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
Android Phone
</span>
</div>
<div
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
>
FB4A
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
FB4A
</span>
</div>
<div
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
>
unique-string
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
unique-string
</span>
</div>
<div
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
/>
<div
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
/>
>
<span>
<span
class="css-1cfwmd7-Highlighted eiud9hg0"
/>
</span>
</div>
<div
class="css-12luweq-TableBodyColumnContainer e1luu51r0"
width="14%"
>
toClient:send
<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
</span>
</div>
</div>
`);