Skip to content

Commit

Permalink
[Discover] Fix document explorer cell popover rendering (elastic#123194)
Browse files Browse the repository at this point in the history
  • Loading branch information
kertal authored and awahab07 committed Jan 31, 2022
1 parent 567b567 commit ebcc0ab
Show file tree
Hide file tree
Showing 2 changed files with 181 additions and 110 deletions.
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) {
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)}</>;
}

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) {
if (fieldsToShow.includes(displayKey)) {
pairs.push([displayKey, formatted]);
}
} else {
pairs.push([key, formatted]);
}
});
return [...highlightPairs, ...sourcePairs];
}

0 comments on commit ebcc0ab

Please sign in to comment.