[8.x] [Discover] Format JSON messages in Observability Logs profile (#205666) (#206153)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Discover] Format JSON messages in Observability Logs profile
(#205666)](https://github.com/elastic/kibana/pull/205666)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Davis
McPhee","email":"davis.mcphee@elastic.co"},"sourceCommit":{"committedDate":"2025-01-10T00:05:18Z","message":"[Discover]
Format JSON messages in Observability Logs profile (#205666)\n\n##
Summary\r\n\r\nThis PR updates the Observability Logs profile to detect
and auto format\r\nJSON message values within both the Log overview doc
viewer tab and the\r\nSummary cell popover. Additionally, it enables
CTRL/CMD + F find\r\nfunctionality within the doc viewer JSON tab for
all contexts to make it\r\neasier for users to search the JSON
output.\r\n\r\nJSON message
formatting:\r\n\r\n![json](https://github.com/user-attachments/assets/a7c63afd-bef7-4050-b8cf-08e4f469ffa9)\r\n\r\nJSON
tab find
functionality:\r\n\r\n![find](https://github.com/user-attachments/assets/aac51e05-6126-4770-8976-0d9057bad557)\r\n\r\n###
Checklist\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] If a plugin
configuration key changed, check if it needs to be\r\nallowlisted in the
cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\r\nchanges have been approved by the breaking-change committee.
The\r\n`release_note:breaking` label should be applied in these
situations.\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [x] The PR description includes
the appropriate Release Notes section,\r\nand the correct
`release_note:*` label is applied per
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"518e0afbde3f0391d7c99bfbb1ef6252a2623d12","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","Feature:Discover","v9.0.0","Team:DataDiscovery","backport:prev-minor","Team:obs-ux-logs"],"title":"[Discover]
Format JSON messages in Observability Logs
profile","number":205666,"url":"https://github.com/elastic/kibana/pull/205666","mergeCommit":{"message":"[Discover]
Format JSON messages in Observability Logs profile (#205666)\n\n##
Summary\r\n\r\nThis PR updates the Observability Logs profile to detect
and auto format\r\nJSON message values within both the Log overview doc
viewer tab and the\r\nSummary cell popover. Additionally, it enables
CTRL/CMD + F find\r\nfunctionality within the doc viewer JSON tab for
all contexts to make it\r\neasier for users to search the JSON
output.\r\n\r\nJSON message
formatting:\r\n\r\n![json](https://github.com/user-attachments/assets/a7c63afd-bef7-4050-b8cf-08e4f469ffa9)\r\n\r\nJSON
tab find
functionality:\r\n\r\n![find](https://github.com/user-attachments/assets/aac51e05-6126-4770-8976-0d9057bad557)\r\n\r\n###
Checklist\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] If a plugin
configuration key changed, check if it needs to be\r\nallowlisted in the
cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\r\nchanges have been approved by the breaking-change committee.
The\r\n`release_note:breaking` label should be applied in these
situations.\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [x] The PR description includes
the appropriate Release Notes section,\r\nand the correct
`release_note:*` label is applied per
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"518e0afbde3f0391d7c99bfbb1ef6252a2623d12"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/205666","number":205666,"mergeCommit":{"message":"[Discover]
Format JSON messages in Observability Logs profile (#205666)\n\n##
Summary\r\n\r\nThis PR updates the Observability Logs profile to detect
and auto format\r\nJSON message values within both the Log overview doc
viewer tab and the\r\nSummary cell popover. Additionally, it enables
CTRL/CMD + F find\r\nfunctionality within the doc viewer JSON tab for
all contexts to make it\r\neasier for users to search the JSON
output.\r\n\r\nJSON message
formatting:\r\n\r\n![json](https://github.com/user-attachments/assets/a7c63afd-bef7-4050-b8cf-08e4f469ffa9)\r\n\r\nJSON
tab find
functionality:\r\n\r\n![find](https://github.com/user-attachments/assets/aac51e05-6126-4770-8976-0d9057bad557)\r\n\r\n###
Checklist\r\n\r\n- [ ] Any text added follows [EUI's
writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing),
uses\r\nsentence case text and includes
[i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)\r\n-
[
]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas
added for features that require explanation or tutorials\r\n- [x] [Unit
or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n- [ ] If a plugin
configuration key changed, check if it needs to be\r\nallowlisted in the
cloud and added to the
[docker\r\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)\r\n-
[ ] This was checked for breaking HTTP API changes, and any
breaking\r\nchanges have been approved by the breaking-change committee.
The\r\n`release_note:breaking` label should be applied in these
situations.\r\n- [ ] [Flaky
Test\r\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1)
was\r\nused on any tests changed\r\n- [x] The PR description includes
the appropriate Release Notes section,\r\nand the correct
`release_note:*` label is applied per
the\r\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)","sha":"518e0afbde3f0391d7c99bfbb1ef6252a2623d12"}}]}]
BACKPORT-->

Co-authored-by: Davis McPhee <davis.mcphee@elastic.co>
This commit is contained in:
Kibana Machine 2025-01-10 12:48:04 +11:00 committed by GitHub
parent 73e60b1d49
commit 37b4a5c2ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 192 additions and 92 deletions

View file

@ -10,7 +10,12 @@
import React from 'react';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { render, screen } from '@testing-library/react';
import SummaryColumn, { SummaryColumnFactoryDeps, SummaryColumnProps } from './summary_column';
import SummaryColumn, {
AllSummaryColumnProps,
SummaryCellPopover,
SummaryColumnFactoryDeps,
SummaryColumnProps,
} from './summary_column';
import { DataGridDensity, ROWS_HEIGHT_OPTIONS } from '@kbn/unified-data-table';
import * as constants from '@kbn/discover-utils/src/data_types/logs/constants';
import { sharePluginMock } from '@kbn/share-plugin/public/mocks';
@ -18,32 +23,46 @@ import { coreMock as corePluginMock } from '@kbn/core/public/mocks';
import { DataTableRecord, buildDataTableRecord } from '@kbn/discover-utils';
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__/data_view';
jest.mock('@elastic/eui', () => ({
...jest.requireActual('@elastic/eui'),
EuiCodeBlock: ({
children,
dangerouslySetInnerHTML,
}: {
children?: string;
dangerouslySetInnerHTML?: { __html: string };
}) => <code data-test-subj="codeBlock">{children ?? dangerouslySetInnerHTML?.__html ?? ''}</code>,
}));
const getSummaryProps = (
record: DataTableRecord,
opts: Partial<SummaryColumnProps & SummaryColumnFactoryDeps> = {}
): AllSummaryColumnProps => ({
rowIndex: 0,
colIndex: 0,
columnId: '_source',
isExpandable: true,
isExpanded: false,
isDetails: false,
row: record,
dataView: dataViewMock,
fieldFormats: fieldFormatsMock,
setCellProps: () => {},
closePopover: () => {},
density: DataGridDensity.COMPACT,
rowHeight: ROWS_HEIGHT_OPTIONS.single,
onFilter: jest.fn(),
shouldShowFieldHandler: () => true,
core: corePluginMock.createStart(),
share: sharePluginMock.createStartContract(),
...opts,
});
const renderSummary = (
record: DataTableRecord,
opts: Partial<SummaryColumnProps & SummaryColumnFactoryDeps> = {}
) => {
render(
<SummaryColumn
rowIndex={0}
colIndex={0}
columnId="_source"
isExpandable={true}
isExpanded={false}
isDetails={false}
row={record}
dataView={dataViewMock}
fieldFormats={fieldFormatsMock}
setCellProps={() => {}}
closePopover={() => {}}
density={DataGridDensity.COMPACT}
rowHeight={ROWS_HEIGHT_OPTIONS.single}
onFilter={jest.fn()}
shouldShowFieldHandler={() => true}
core={corePluginMock.createStart()}
share={sharePluginMock.createStartContract()}
{...opts}
/>
);
render(<SummaryColumn {...getSummaryProps(record, opts)} />);
};
const getBaseRecord = (overrides: Record<string, unknown> = {}) =>
@ -174,3 +193,18 @@ describe('SummaryColumn', () => {
});
});
});
describe('SummaryCellPopover', () => {
it('should render message value', async () => {
const message = 'This is a message';
render(<SummaryCellPopover {...getSummaryProps(getBaseRecord({ message }))} />);
expect(screen.queryByTestId('codeBlock')?.innerHTML).toBe(message);
});
it('should render formatted JSON message value', async () => {
const json = { foo: { bar: true } };
const message = JSON.stringify(json);
render(<SummaryCellPopover {...getSummaryProps(getBaseRecord({ message }))} />);
expect(screen.queryByTestId('codeBlock')?.innerHTML).toBe(JSON.stringify(json, null, 2));
});
});

View file

@ -98,14 +98,19 @@ const SummaryCell = ({
);
};
const SummaryCellPopover = (props: AllSummaryColumnProps) => {
export const SummaryCellPopover = (props: AllSummaryColumnProps) => {
const { row, dataView, fieldFormats, onFilter, closePopover, share, core } = props;
const resourceFields = createResourceFields(row, core, share);
const shouldRenderResource = resourceFields.length > 0;
const documentOverview = getLogDocumentOverview(row, { dataView, fieldFormats });
const { field, value } = getMessageFieldWithFallbacks(documentOverview);
const { field, value, formattedValue } = getMessageFieldWithFallbacks(documentOverview, {
includeFormattedValue: true,
});
const messageCodeBlockProps = formattedValue
? { language: 'json', children: formattedValue }
: { language: 'txt', dangerouslySetInnerHTML: { __html: value ?? '' } };
const shouldRenderContent = Boolean(field && value);
const shouldRenderSource = !shouldRenderContent;
@ -142,11 +147,9 @@ const SummaryCellPopover = (props: AllSummaryColumnProps) => {
overflowHeight={100}
paddingSize="s"
isCopyable
language="txt"
fontSize="s"
>
{value}
</EuiCodeBlock>
{...messageCodeBlockProps}
/>
</EuiFlexGroup>
)}
{shouldRenderSource && (

View file

@ -7,10 +7,14 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { unescape } from 'lodash';
import { fieldConstants } from '..';
import { LogDocumentOverview } from '../types';
export const getMessageFieldWithFallbacks = (doc: LogDocumentOverview) => {
export const getMessageFieldWithFallbacks = (
doc: LogDocumentOverview,
{ includeFormattedValue = false }: { includeFormattedValue?: boolean } = {}
) => {
const rankingOrder = [
fieldConstants.MESSAGE_FIELD,
fieldConstants.ERROR_MESSAGE_FIELD,
@ -18,8 +22,20 @@ export const getMessageFieldWithFallbacks = (doc: LogDocumentOverview) => {
] as const;
for (const rank of rankingOrder) {
if (doc[rank] !== undefined && doc[rank] !== null) {
return { field: rank, value: doc[rank] };
const value = doc[rank];
if (value !== undefined && value !== null) {
let formattedValue: string | undefined;
if (includeFormattedValue) {
try {
formattedValue = JSON.stringify(JSON.parse(unescape(value)), null, 2);
} catch {
// If the value is not a valid JSON, leave it unformatted
}
}
return { field: rank, value, formattedValue };
}
}

View file

@ -17,6 +17,17 @@ import { setUnifiedDocViewerServices } from '../../plugin';
import { mockUnifiedDocViewerServices } from '../../__mocks__';
import { merge } from 'lodash';
jest.mock('@elastic/eui', () => ({
...jest.requireActual('@elastic/eui'),
EuiCodeBlock: ({
children,
dangerouslySetInnerHTML,
}: {
children?: string;
dangerouslySetInnerHTML?: { __html: string };
}) => <code data-test-subj="codeBlock">{children ?? dangerouslySetInnerHTML?.__html ?? ''}</code>,
}));
const DATASET_NAME = 'logs.overview';
const NAMESPACE = 'default';
const DATA_STREAM_NAME = `logs-${DATASET_NAME}-${NAMESPACE}`;
@ -58,51 +69,55 @@ dataView.fields.getByName = (name: string) => {
return dataView.fields.getAll().find((field) => field.name === name);
};
const fullHit = buildDataTableRecord(
{
_index: DATA_STREAM_NAME,
_id: DATA_STREAM_NAME,
_score: 1,
_source: {
'@timestamp': NOW + 1000,
message: 'full document',
log: { level: 'info', file: { path: '/logs.overview.log' } },
data_stream: {
type: 'logs',
dataset: DATASET_NAME,
namespace: NAMESPACE,
const buildHit = (fields?: Record<string, unknown>) =>
buildDataTableRecord(
{
_index: DATA_STREAM_NAME,
_id: DATA_STREAM_NAME,
_score: 1,
_source: {
'@timestamp': NOW + 1000,
message: 'full document',
log: { level: 'info', file: { path: '/logs.overview.log' } },
data_stream: {
type: 'logs',
dataset: DATASET_NAME,
namespace: NAMESPACE,
},
'service.name': DATASET_NAME,
'host.name': 'gke-edge-oblt-pool',
'trace.id': 'abcdef',
orchestrator: {
cluster: {
id: 'my-cluster-id',
name: 'my-cluster-name',
},
resource: {
id: 'orchestratorResourceId',
},
},
cloud: {
provider: ['gcp'],
region: 'us-central-1',
availability_zone: MORE_THAN_1024_CHARS,
project: {
id: 'elastic-project',
},
instance: {
id: 'BgfderflkjTheUiGuy',
},
},
'agent.name': 'node',
...fields,
},
'service.name': DATASET_NAME,
'host.name': 'gke-edge-oblt-pool',
'trace.id': 'abcdef',
orchestrator: {
cluster: {
id: 'my-cluster-id',
name: 'my-cluster-name',
},
resource: {
id: 'orchestratorResourceId',
},
ignored_field_values: {
'cloud.availability_zone': [MORE_THAN_1024_CHARS],
},
cloud: {
provider: ['gcp'],
region: 'us-central-1',
availability_zone: MORE_THAN_1024_CHARS,
project: {
id: 'elastic-project',
},
instance: {
id: 'BgfderflkjTheUiGuy',
},
},
'agent.name': 'node',
},
ignored_field_values: {
'cloud.availability_zone': [MORE_THAN_1024_CHARS],
},
},
dataView
);
dataView
);
const fullHit = buildHit();
const getCustomUnifedDocViewerServices = (params?: {
showApm: boolean;
@ -306,3 +321,18 @@ describe('LogsOverview with APM links', () => {
});
});
});
describe('LogsOverview content breakdown', () => {
it('should render message value', async () => {
const message = 'This is a message';
renderLogsOverview({ hit: buildHit({ message }) });
expect(screen.queryByTestId('codeBlock')?.innerHTML).toBe(message);
});
it('should render formatted JSON message value', async () => {
const json = { foo: { bar: true } };
const message = JSON.stringify(json);
renderLogsOverview({ hit: buildHit({ message }) });
expect(screen.queryByTestId('codeBlock')?.innerHTML).toBe(JSON.stringify(json, null, 2));
});
});

View file

@ -34,7 +34,12 @@ export const contentLabel = i18n.translate('unifiedDocViewer.docView.logsOvervie
export function LogsOverviewHeader({ doc }: { doc: LogDocumentOverview }) {
const hasLogLevel = Boolean(doc[fieldConstants.LOG_LEVEL_FIELD]);
const hasTimestamp = Boolean(doc[fieldConstants.TIMESTAMP_FIELD]);
const { field, value } = getMessageFieldWithFallbacks(doc);
const { field, value, formattedValue } = getMessageFieldWithFallbacks(doc, {
includeFormattedValue: true,
});
const messageCodeBlockProps = formattedValue
? { language: 'json', children: formattedValue }
: { language: 'txt', dangerouslySetInnerHTML: { __html: value ?? '' } };
const hasBadges = hasTimestamp || hasLogLevel;
const hasMessageField = field && value;
const hasFlyoutHeader = hasMessageField || hasBadges;
@ -80,14 +85,19 @@ export function LogsOverviewHeader({ doc }: { doc: LogDocumentOverview }) {
</EuiText>
<EuiFlexItem grow={false}>{logLevelAndTimestamp}</EuiFlexItem>
</EuiFlexGroup>
<HoverActionPopover value={value} field={field} anchorPosition="downCenter" display="block">
<HoverActionPopover
value={value}
formattedValue={formattedValue}
field={field}
anchorPosition="downCenter"
display="block"
>
<EuiCodeBlock
overflowHeight={100}
paddingSize="s"
isCopyable
language="txt"
fontSize="s"
dangerouslySetInnerHTML={{ __html: value }}
{...messageCodeBlockProps}
/>
</HoverActionPopover>
</EuiFlexGroup>

View file

@ -23,6 +23,7 @@ interface HoverPopoverActionProps {
children: React.ReactChild;
field: string;
value: unknown;
formattedValue?: string;
title?: unknown;
anchorPosition?: PopoverAnchorPosition;
display?: EuiPopoverProps['display'];
@ -33,12 +34,13 @@ export const HoverActionPopover = ({
title,
field,
value,
formattedValue,
anchorPosition = 'upCenter',
display = 'inline-block',
}: HoverPopoverActionProps) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const leaveTimer = useRef<NodeJS.Timeout | null>(null);
const uiFieldActions = useUIFieldActions({ field, value });
const uiFieldActions = useUIFieldActions({ field, value, formattedValue });
// The timeout hack is required because we are using a Popover which ideally should be used with a mouseclick,
// but we are using it as a Tooltip. Which means we now need to manually handle the open and close

View file

@ -42,7 +42,6 @@ describe('Source Viewer component', () => {
index={'index1'}
dataView={mockDataView}
width={123}
hasLineNumbers={true}
onRefresh={() => {}}
/>
);
@ -59,7 +58,6 @@ describe('Source Viewer component', () => {
index={'index1'}
dataView={mockDataView}
width={123}
hasLineNumbers={true}
onRefresh={() => {}}
/>
);
@ -97,7 +95,6 @@ describe('Source Viewer component', () => {
index={'index1'}
dataView={mockDataView}
width={123}
hasLineNumbers={true}
onRefresh={() => {}}
/>
);
@ -105,5 +102,7 @@ describe('Source Viewer component', () => {
expect(jsonCodeEditor).not.toBe(null);
expect(jsonCodeEditor.props().jsonValue).toContain('_source');
expect(jsonCodeEditor.props().jsonValue).not.toContain('_score');
expect(jsonCodeEditor.props().hasLineNumbers).toBe(true);
expect(jsonCodeEditor.props().enableFindAction).toBe(true);
});
});

View file

@ -29,10 +29,8 @@ interface SourceViewerProps {
index: string | undefined;
dataView: DataView;
textBasedHits?: DataTableRecord[];
hasLineNumbers: boolean;
width?: number;
decreaseAvailableHeightBy?: number;
requestState?: ElasticRequestState;
onRefresh: () => void;
}
@ -46,9 +44,8 @@ export const DocViewerSource = ({
id,
index,
dataView,
width,
hasLineNumbers,
textBasedHits,
width,
decreaseAvailableHeightBy,
onRefresh,
}: SourceViewerProps) => {
@ -150,7 +147,8 @@ export const DocViewerSource = ({
jsonValue={jsonValue}
width={width}
height={editorHeight}
hasLineNumbers={hasLineNumbers}
hasLineNumbers
enableFindAction
onEditorDidMount={(editorNode: monaco.editor.IStandaloneCodeEditor) => setEditor(editorNode)}
/>
);

View file

@ -29,6 +29,7 @@ interface JsonCodeEditorCommonProps {
height?: string | number;
hasLineNumbers?: boolean;
hideCopyButton?: boolean;
enableFindAction?: boolean;
}
export const JsonCodeEditorCommon = ({
@ -38,6 +39,7 @@ export const JsonCodeEditorCommon = ({
hasLineNumbers,
onEditorDidMount,
hideCopyButton,
enableFindAction,
}: JsonCodeEditorCommonProps) => {
if (jsonValue === '') {
return null;
@ -66,6 +68,7 @@ export const JsonCodeEditorCommon = ({
wordWrap: 'on',
wrappingIndent: 'indent',
}}
enableFindAction={enableFindAction}
/>
);
if (hideCopyButton) {

View file

@ -21,7 +21,9 @@ interface WithValueParam {
value: unknown;
}
interface TFieldActionParams extends WithFieldParam, WithValueParam {}
interface TFieldActionParams extends WithFieldParam, WithValueParam {
formattedValue?: string;
}
export interface TFieldAction {
id: string;
@ -66,7 +68,11 @@ export const [FieldActionsProvider, useFieldActionsContext] = createContainer(us
/**
* This is a preset of the UI elements and related actions that can be used to build an action bar anywhere in a DocView
*/
export const useUIFieldActions = ({ field, value }: TFieldActionParams): TFieldAction[] => {
export const useUIFieldActions = ({
field,
value,
formattedValue,
}: TFieldActionParams): TFieldAction[] => {
const actions = useFieldActionsContext();
return useMemo(
@ -99,10 +105,10 @@ export const useUIFieldActions = ({ field, value }: TFieldActionParams): TFieldA
id: 'copyToClipboardAction',
iconType: 'copyClipboard',
label: copyToClipboardLabel,
onClick: () => actions.copyToClipboard(value as string),
onClick: () => actions.copyToClipboard(formattedValue ?? (value as string)),
},
],
[actions, field, value]
[actions, field, formattedValue, value]
);
};

View file

@ -92,7 +92,6 @@ export class UnifiedDocViewerPublicPlugin
id={hit.raw._id ?? hit.id}
dataView={dataView}
textBasedHits={textBasedHits}
hasLineNumbers
decreaseAvailableHeightBy={decreaseAvailableHeightBy}
onRefresh={() => {}}
/>