Skip to content

Commit

Permalink
[Discover] Grouping multifields in a doc table (#88560) (#89546)
Browse files Browse the repository at this point in the history
* [Discover] Grouping multifields in a doc table

* Fixing scss selector

* Remove unnecessary comment

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
Maja Grubic and kibanamachine authored Jan 28, 2021
1 parent c7b900b commit 34269be
Show file tree
Hide file tree
Showing 4 changed files with 280 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@
padding-top: $euiSizeS;
}

.kbnDocViewer__multifield_row:hover td {
background-color: transparent;
}

.kbnDocViewer__multifield_title {
font-family: $euiFontFamily;
}

.dscFieldName {
color: $euiColorDarkShade;
}
Expand Down
193 changes: 169 additions & 24 deletions src/plugins/discover/public/application/components/table/table.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -167,30 +167,6 @@ describe('DocViewTable at Discover', () => {
});
});

describe('DocViewTable at Discover Doc', () => {
const hit = {
_index: 'logstash-2014.09.09',
_score: 1,
_type: 'doc',
_id: 'id123',
_source: {
extension: 'html',
not_mapped: 'yes',
},
};
// here no action buttons are rendered
const props = {
hit,
indexPattern,
};
const component = mount(<DocViewTable {...props} />);
const foundLength = findTestSubject(component, 'addInclusiveFilterButton').length;

it(`renders no action buttons`, () => {
expect(foundLength).toBe(0);
});
});

describe('DocViewTable at Discover Context', () => {
// here no toggleColumnButtons are rendered
const hit = {
Expand Down Expand Up @@ -243,3 +219,172 @@ describe('DocViewTable at Discover Context', () => {
expect(component.html() !== html).toBeTruthy();
});
});

describe('DocViewTable at Discover Doc', () => {
const hit = {
_index: 'logstash-2014.09.09',
_score: 1,
_type: 'doc',
_id: 'id123',
_source: {
extension: 'html',
not_mapped: 'yes',
},
};
// here no action buttons are rendered
const props = {
hit,
indexPattern,
};
const component = mount(<DocViewTable {...props} />);
const foundLength = findTestSubject(component, 'addInclusiveFilterButton').length;

it(`renders no action buttons`, () => {
expect(foundLength).toBe(0);
});
});

describe('DocViewTable at Discover Doc with Fields API', () => {
const indexPatterneCommerce = ({
fields: {
getAll: () => [
{
name: '_index',
type: 'string',
scripted: false,
filterable: true,
},
{
name: 'category',
type: 'string',
scripted: false,
filterable: true,
},
{
name: 'category.keyword',
displayName: 'category.keyword',
type: 'string',
scripted: false,
filterable: true,
spec: {
subType: {
multi: {
parent: 'category',
},
},
},
},
{
name: 'customer_first_name',
type: 'string',
scripted: false,
filterable: true,
},
{
name: 'customer_first_name.keyword',
displayName: 'customer_first_name.keyword',
type: 'string',
scripted: false,
filterable: false,
spec: {
subType: {
multi: {
parent: 'customer_first_name',
},
},
},
},
{
name: 'customer_first_name.nickname',
displayName: 'customer_first_name.nickname',
type: 'string',
scripted: false,
filterable: false,
spec: {
subType: {
multi: {
parent: 'customer_first_name',
},
},
},
},
],
},
metaFields: ['_index', '_type', '_score', '_id'],
flattenHit: jest.fn((hit) => {
const result = {} as Record<string, any>;
Object.keys(hit).forEach((key) => {
if (key !== 'fields') {
result[key] = hit[key];
} else {
Object.keys(hit.fields).forEach((field) => {
result[field] = hit.fields[field];
});
}
});
return result;
}),
formatHit: jest.fn((hit) => {
const result = {} as Record<string, any>;
Object.keys(hit).forEach((key) => {
if (key !== 'fields') {
result[key] = hit[key];
} else {
Object.keys(hit.fields).forEach((field) => {
result[field] = hit.fields[field];
});
}
});
return result;
}),
} as unknown) as IndexPattern;

indexPatterneCommerce.fields.getByName = (name: string) => {
return indexPatterneCommerce.fields.getAll().find((field) => field.name === name);
};

const fieldsHit = {
_index: 'logstash-2014.09.09',
_type: 'doc',
_id: 'id123',
_score: null,
fields: {
category: "Women's Clothing",
'category.keyword': "Women's Clothing",
customer_first_name: 'Betty',
'customer_first_name.keyword': 'Betty',
'customer_first_name.nickname': 'Betsy',
},
};
const props = {
hit: fieldsHit,
columns: ['Document'],
indexPattern: indexPatterneCommerce,
filter: jest.fn(),
onAddColumn: jest.fn(),
onRemoveColumn: jest.fn(),
};
// @ts-ignore
const component = mount(<DocViewTable {...props} />);
it('renders multifield rows', () => {
const categoryMultifieldRow = findTestSubject(
component,
'tableDocViewRow-multifieldsTitle-category'
);
expect(categoryMultifieldRow.length).toBe(1);
const categoryKeywordRow = findTestSubject(component, 'tableDocViewRow-category.keyword');
expect(categoryKeywordRow.length).toBe(1);

const customerNameMultiFieldRow = findTestSubject(
component,
'tableDocViewRow-multifieldsTitle-customer_first_name'
);
expect(customerNameMultiFieldRow.length).toBe(1);
expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.keyword').length).toBe(
1
);
expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname').length).toBe(
1
);
});
});
107 changes: 88 additions & 19 deletions src/plugins/discover/public/application/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/

import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { DocViewTableRow } from './table_row';
import { trimAngularSpan } from './table_helper';
import { isNestedFieldParent } from '../../helpers/nested_fields';
Expand All @@ -23,6 +23,36 @@ export function DocViewTable({
onRemoveColumn,
}: DocViewRenderProps) {
const [fieldRowOpen, setFieldRowOpen] = useState({} as Record<string, boolean>);
const [multiFields, setMultiFields] = useState({} as Record<string, string[]>);
const [fieldsWithParents, setFieldsWithParents] = useState([] as string[]);

useEffect(() => {
if (!indexPattern) {
return;
}
const mapping = indexPattern.fields.getByName;
const flattened = indexPattern.flattenHit(hit);
const map: Record<string, string[]> = {};
const arr: string[] = [];

Object.keys(flattened).forEach((key) => {
const field = mapping(key);

if (field && field.spec?.subType?.multi?.parent) {
const parent = field.spec.subType.multi.parent;
if (!map[parent]) {
map[parent] = [] as string[];
}
const value = map[parent];
value.push(key);
map[parent] = value;
arr.push(key);
}
});
setMultiFields(map);
setFieldsWithParents(arr);
}, [indexPattern, hit]);

if (!indexPattern) {
return null;
}
Expand All @@ -34,11 +64,13 @@ export function DocViewTable({
fieldRowOpen[field] = !fieldRowOpen[field];
setFieldRowOpen({ ...fieldRowOpen });
}

return (
<table className="table table-condensed kbnDocViewerTable">
<tbody>
{Object.keys(flattened)
.filter((field) => {
return !fieldsWithParents.includes(field);
})
.sort((fieldA, fieldB) => {
const mappingA = mapping(fieldA);
const mappingB = mapping(fieldB);
Expand Down Expand Up @@ -67,23 +99,60 @@ export function DocViewTable({
const fieldType = isNestedFieldParent(field, indexPattern)
? 'nested'
: indexPattern.fields.getByName(field)?.type;

return (
<DocViewTableRow
key={field}
field={field}
fieldMapping={mapping(field)}
fieldType={String(fieldType)}
displayUnderscoreWarning={displayUnderscoreWarning}
isCollapsed={isCollapsed}
isCollapsible={isCollapsible}
isColumnActive={Array.isArray(columns) && columns.includes(field)}
onFilter={filter}
onToggleCollapse={() => toggleValueCollapse(field)}
onToggleColumn={toggleColumn}
value={value}
valueRaw={valueRaw}
/>
<React.Fragment>
<DocViewTableRow
key={field}
field={field}
fieldMapping={mapping(field)}
fieldType={String(fieldType)}
displayUnderscoreWarning={displayUnderscoreWarning}
isCollapsed={isCollapsed}
isCollapsible={isCollapsible}
isColumnActive={Array.isArray(columns) && columns.includes(field)}
onFilter={filter}
onToggleCollapse={() => toggleValueCollapse(field)}
onToggleColumn={toggleColumn}
value={value}
valueRaw={valueRaw}
/>
{multiFields[field] ? (
<tr
key={`tableDocViewRow-multifieldsTitle-${field}`}
className="kbnDocViewer__multifield_row"
data-test-subj={`tableDocViewRow-multifieldsTitle-${field}`}
>
<td className="kbnDocViewer__field">&nbsp;</td>
<td className="kbnDocViewer__multifield_title">
<b>
{i18n.translate('discover.fieldChooser.discoverField.multiFields', {
defaultMessage: 'Multi fields',
})}
</b>
</td>
</tr>
) : null}
{multiFields[field]
? multiFields[field].map((multiField) => {
return (
<DocViewTableRow
key={multiField}
fieldMapping={mapping(multiField)}
fieldType={String(fieldType)}
displayUnderscoreWarning={displayUnderscoreWarning}
isCollapsed={isCollapsed}
isCollapsible={isCollapsible}
isColumnActive={Array.isArray(columns) && columns.includes(field)}
onFilter={filter}
onToggleCollapse={() => toggleValueCollapse(field)}
onToggleColumn={toggleColumn}
value={value}
valueRaw={valueRaw}
/>
);
})
: null}
</React.Fragment>
);
})}
</tbody>
Expand Down
Loading

0 comments on commit 34269be

Please sign in to comment.