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

[7.x] [Discover] Grouping multifields in a doc table (#88560) #89546

Merged
merged 1 commit into from
Jan 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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