Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Discover] Fix document explorer cell popover rendering #123194

Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,50 @@ describe('Discover grid cell rendering', function () {
expect(component.html()).toMatchInlineSnapshot(`"<span>100</span>"`);
});

it('renders bytes column correctly using _source when details is true', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
indexPatternMock,
rowsSource,
rowsSource.map(flatten),
false,
[],
100
);
const component = shallow(
<DiscoverGridCellValue
rowIndex={0}
columnId="bytes"
isDetails={true}
isExpanded={false}
isExpandable={true}
setCellProps={jest.fn()}
/>
);
expect(component.html()).toMatchInlineSnapshot(`"<span>100</span>"`);
});

it('renders bytes column correctly using fields when details is true', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
indexPatternMock,
rowsFields,
rowsFields.map(flatten),
false,
[],
100
);
const component = shallow(
<DiscoverGridCellValue
rowIndex={0}
columnId="bytes"
isDetails={true}
isExpanded={false}
isExpandable={true}
setCellProps={jest.fn()}
/>
);
expect(component.html()).toMatchInlineSnapshot(`"<span>100</span>"`);
});

it('renders _source column correctly', () => {
const DiscoverGridCellValue = getRenderCellValueFn(
indexPatternMock,
Expand Down Expand Up @@ -514,13 +558,16 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component).toMatchInlineSnapshot(`
<span>
{
"object.value": [
100
]
}
</span>
<JsonCodeEditor
json={
Object {
"object.value": Array [
100,
],
}
}
width={370}
/>
`);
});

Expand Down Expand Up @@ -634,9 +681,15 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(component).toMatchInlineSnapshot(`
<Fragment>
.gz
</Fragment>
<span
dangerouslySetInnerHTML={
Object {
"__html": Array [
".gz",
],
}
}
/>
`);

const componentWithDetails = shallow(
Expand All @@ -650,13 +703,14 @@ describe('Discover grid cell rendering', function () {
/>
);
expect(componentWithDetails).toMatchInlineSnapshot(`
<JsonCodeEditor
json={
Array [
".gz",
]
<span
dangerouslySetInnerHTML={
Object {
"__html": Array [
".gz",
],
}
}
width={370}
/>
`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

import React, { Fragment, useContext, useEffect } from 'react';
import { euiLightVars as themeLight, euiDarkVars as themeDark } from '@kbn/ui-theme';
import type { DataView } from 'src/plugins/data/common';

import type { DataView, DataViewField } from 'src/plugins/data/common';
import {
EuiDataGridCellValueElementProps,
EuiDescriptionList,
Expand Down Expand Up @@ -64,89 +63,35 @@ export const getRenderCellValueFn =
return <span>-</span>;
}

if (
/**
* when using the fields api this code is used to show top level objects
* this is used for legacy stuff like displaying products of our ecommerce dataset
*/
const useTopLevelObjectColumns = Boolean(
useNewFieldsApi &&
!field &&
row &&
row.fields &&
!(row.fields as Record<string, unknown[]>)[columnId]
) {
const innerColumns = Object.fromEntries(
Object.entries(row.fields as Record<string, unknown[]>).filter(([key]) => {
return key.indexOf(`${columnId}.`) === 0;
})
);
if (isDetails) {
// nicely formatted JSON for the expanded view
return <span>{JSON.stringify(innerColumns, null, 2)}</span>;
}

// Put the most important fields first
const highlights: Record<string, unknown> = (row.highlight as Record<string, unknown>) ?? {};
const highlightPairs: Array<[string, string]> = [];
const sourcePairs: Array<[string, string]> = [];
Object.entries(innerColumns).forEach(([key, values]) => {
const subField = indexPattern.getFieldByName(key);
const displayKey = indexPattern.fields.getByName
? indexPattern.fields.getByName(key)?.displayName
: undefined;
const formatter = subField
? indexPattern.getFormatterForField(subField)
: { convert: (v: unknown, ...rest: unknown[]) => String(v) };
const formatted = (values as unknown[])
.map((val: unknown) =>
formatter.convert(val, 'html', {
field: subField,
hit: row,
indexPattern,
})
)
.join(', ');
const pairs = highlights[key] ? highlightPairs : sourcePairs;
if (displayKey) {
if (fieldsToShow.includes(displayKey)) {
pairs.push([displayKey, formatted]);
}
} else {
pairs.push([key, formatted]);
}
});

return (
// If you change the styling of this list (specifically something that will change the line-height)
// make sure to adjust the img overwrites attached to dscDiscoverGrid__descriptionListDescription
// in discover_grid.scss
<EuiDescriptionList type="inline" compressed className="dscDiscoverGrid__descriptionList">
{[...highlightPairs, ...sourcePairs]
.slice(0, maxDocFieldsDisplayed)
.map(([key, value]) => (
<Fragment key={key}>
<EuiDescriptionListTitle>{key}</EuiDescriptionListTitle>
<EuiDescriptionListDescription
dangerouslySetInnerHTML={{ __html: value }}
className="dscDiscoverGrid__descriptionListDescription"
/>
</Fragment>
))}
</EuiDescriptionList>
);
}
!field &&
row?.fields &&
!(row.fields as Record<string, unknown[]>)[columnId]
);

if (typeof rowFlattened[columnId] === 'object' && isDetails) {
return (
<JsonCodeEditor
json={rowFlattened[columnId] as Record<string, unknown>}
width={defaultMonacoEditorWidth}
/>
if (isDetails) {
return renderPopoverContent(
row,
rowFlattened,
field,
columnId,
indexPattern,
useTopLevelObjectColumns
);
}

if (field && field.type === '_source') {
if (isDetails) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return <JsonCodeEditor json={row as any} width={defaultMonacoEditorWidth} />;
}
const pairs = formatHit(row, indexPattern, fieldsToShow);
if (field?.type === '_source' || useTopLevelObjectColumns) {
kertal marked this conversation as resolved.
Show resolved Hide resolved
const pairs = useTopLevelObjectColumns
? getTopLevelObjectPairs(row, columnId, indexPattern, fieldsToShow).slice(
0,
maxDocFieldsDisplayed
)
: formatHit(row, indexPattern, fieldsToShow);

return (
<EuiDescriptionList type="inline" compressed className="dscDiscoverGrid__descriptionList">
Expand All @@ -163,20 +108,6 @@ export const getRenderCellValueFn =
);
}

if (!field?.type && rowFlattened && typeof rowFlattened[columnId] === 'object') {
if (isDetails) {
// nicely formatted JSON for the expanded view
return (
<JsonCodeEditor
json={rowFlattened[columnId] as Record<string, unknown>}
width={defaultMonacoEditorWidth}
/>
);
}

return <>{formatFieldValue(rowFlattened[columnId], row, indexPattern, field)}</>;
kertal marked this conversation as resolved.
Show resolved Hide resolved
}

return (
<span
// formatFieldValue guarantees sanitized values
Expand All @@ -187,3 +118,89 @@ export const getRenderCellValueFn =
/>
);
};

/**
* Helper function to show top level objects
* this is used for legacy stuff like displaying products of our ecommerce dataset
*/
function getInnerColumns(fields: Record<string, unknown[]>, columnId: string) {
return Object.fromEntries(
Object.entries(fields).filter(([key]) => {
return key.indexOf(`${columnId}.`) === 0;
})
);
}

/**
* Helper function for the cell popover
*/
function renderPopoverContent(
rowRaw: ElasticSearchHit,
rowFlattened: Record<string, unknown>,
field: DataViewField | undefined,
columnId: string,
dataView: DataView,
useTopLevelObjectColumns: boolean
) {
if (useTopLevelObjectColumns || field?.type === '_source') {
const json = useTopLevelObjectColumns
? getInnerColumns(rowRaw.fields as Record<string, unknown[]>, columnId)
: rowRaw;
return (
<JsonCodeEditor json={json as Record<string, unknown>} width={defaultMonacoEditorWidth} />
);
}

return (
<span
// formatFieldValue guarantees sanitized values
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: formatFieldValue(rowFlattened[columnId], rowRaw, dataView, field),
}}
/>
);
}
/**
* Helper function to show top level objects
* this is used for legacy stuff like displaying products of our ecommerce dataset
*/
function getTopLevelObjectPairs(
row: ElasticSearchHit,
columnId: string,
dataView: DataView,
fieldsToShow: string[]
) {
const innerColumns = getInnerColumns(row.fields as Record<string, unknown[]>, columnId);
// Put the most important fields first
const highlights: Record<string, unknown> = (row.highlight as Record<string, unknown>) ?? {};
const highlightPairs: Array<[string, string]> = [];
const sourcePairs: Array<[string, string]> = [];
Object.entries(innerColumns).forEach(([key, values]) => {
const subField = dataView.getFieldByName(key);
const displayKey = dataView.fields.getByName
? dataView.fields.getByName(key)?.displayName
: undefined;
const formatter = subField
? dataView.getFormatterForField(subField)
: { convert: (v: unknown, ...rest: unknown[]) => String(v) };
const formatted = (values as unknown[])
.map((val: unknown) =>
formatter.convert(val, 'html', {
field: subField,
hit: row,
indexPattern: dataView,
})
)
.join(', ');
const pairs = highlights[key] ? highlightPairs : sourcePairs;
if (displayKey) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I wrote this code, but omg is it horrible 😅

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so I've got good news for your, since I refactured the structure of the file, but did not touch the function's logic ... now technically: I wrote this code :)

if (fieldsToShow.includes(displayKey)) {
pairs.push([displayKey, formatted]);
}
} else {
pairs.push([key, formatted]);
}
});
return [...highlightPairs, ...sourcePairs];
}