Skip to content

Commit

Permalink
[OneDiscover][UnifiedDocViewer] Allow filtering by field type (#189981)
Browse files Browse the repository at this point in the history
- Closes #188733

## Summary

This PR adds Field type filter to Doc Viewer (same as in
UnifiedFieldList as discussed with @MichaelMarcialis).

The selected field types would be persisted in Local Storage under
`unifiedDocViewer:selectedFieldTypes` key.

<img width="685" alt="Screenshot 2024-08-07 at 16 52 46"
src="https://github.com/user-attachments/assets/7591aa69-c1b4-4485-ad9f-baac809d7fe5">



### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

---------

Co-authored-by: kibanamachine <[email protected]>
  • Loading branch information
jughosta and kibanamachine authored Aug 9, 2024
1 parent eaf674d commit a8aa215
Show file tree
Hide file tree
Showing 9 changed files with 463 additions and 76 deletions.
2 changes: 2 additions & 0 deletions src/plugins/unified_doc_viewer/public/__mocks__/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import { analyticsServiceMock } from '@kbn/core-analytics-browser-mocks';
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
import { coreMock } from '@kbn/core/public/mocks';
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
import { fieldsMetadataPluginPublicMock } from '@kbn/fields-metadata-plugin/public/mocks';
import { uiSettingsServiceMock } from '@kbn/core-ui-settings-browser-mocks';
Expand All @@ -29,4 +30,5 @@ export const mockUnifiedDocViewerServices: jest.Mocked<UnifiedDocViewerServices>
uiSettings: uiSettingsServiceMock.createStartContract(),
unifiedDocViewer: mockUnifiedDocViewer,
share: sharePluginMock.createStartContract(),
core: coreMock.createStart(),
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import useLocalStorage from 'react-use/lib/useLocalStorage';
import {
EuiFlexGroup,
EuiFlexItem,
EuiFieldSearch,
EuiSpacer,
EuiSelectableMessage,
EuiDataGrid,
Expand All @@ -28,7 +27,6 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { debounce } from 'lodash';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { getFieldIconType } from '@kbn/field-utils/src/utils/get_field_icon_type';
import {
Expand All @@ -41,7 +39,6 @@ import {
} from '@kbn/discover-utils';
import {
FieldDescription,
fieldNameWildcardMatcher,
getFieldSearchMatchingHighlight,
getTextBasedColumnIconType,
} from '@kbn/field-utils';
Expand All @@ -60,12 +57,14 @@ import {
DEFAULT_MARGIN_BOTTOM,
getTabContentAvailableHeight,
} from '../doc_viewer_source/get_height';
import { TableFilters, TableFiltersProps, useTableFilters } from './table_filters';

export type FieldRecord = TableRow;

interface ItemsEntry {
pinnedItems: FieldRecord[];
restItems: FieldRecord[];
allFields: TableFiltersProps['allFields'];
}

const MIN_NAME_COLUMN_WIDTH = 150;
Expand All @@ -74,7 +73,6 @@ const PAGE_SIZE_OPTIONS = [25, 50, 100, 250, 500];
const DEFAULT_PAGE_SIZE = 25;
const PINNED_FIELDS_KEY = 'discover:pinnedFields';
const PAGE_SIZE = 'discover:pageSize';
const SEARCH_TEXT = 'discover:searchText';
const HIDE_NULL_VALUES = 'unifiedDocViewer:hideNullValues';

const GRID_COLUMN_FIELD_NAME = 'name';
Expand Down Expand Up @@ -126,14 +124,6 @@ const updatePageSize = (newPageSize: number, storage: Storage) => {
storage.set(PAGE_SIZE, newPageSize);
};

const getSearchText = (storage: Storage) => {
return storage.get(SEARCH_TEXT) || '';
};
const updateSearchText = debounce(
(newSearchText: string, storage: Storage) => storage.set(SEARCH_TEXT, newSearchText),
500
);

export const DocViewerTable = ({
columns,
columnsMeta,
Expand All @@ -151,7 +141,6 @@ export const DocViewerTable = ({
const showMultiFields = uiSettings.get(SHOW_MULTIFIELDS);
const currentDataViewId = dataView.id!;

const [searchText, setSearchText] = useState(getSearchText(storage));
const [pinnedFields, setPinnedFields] = useState<string[]>(
getPinnedFields(currentDataViewId, storage)
);
Expand All @@ -165,10 +154,6 @@ export const DocViewerTable = ({
[flattened, dataView, showMultiFields]
);

const searchPlaceholder = i18n.translate('unifiedDocViewer.docView.table.searchPlaceHolder', {
defaultMessage: 'Search field names',
});

const mapping = useCallback((name: string) => dataView.fields.getByName(name), [dataView.fields]);

const onToggleColumn = useMemo(() => {
Expand Down Expand Up @@ -196,14 +181,7 @@ export const DocViewerTable = ({
[currentDataViewId, pinnedFields, storage]
);

const onSearch = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const newSearchText = event.currentTarget.value;
updateSearchText(newSearchText, storage);
setSearchText(newSearchText);
},
[storage]
);
const { onFilterField, ...tableFiltersProps } = useTableFilters(storage);

const fieldToItem = useCallback(
(field: string, isPinned: boolean) => {
Expand Down Expand Up @@ -261,47 +239,64 @@ export const DocViewerTable = ({
]
);

const { pinnedItems, restItems } = Object.keys(flattened)
.sort((fieldA, fieldB) => {
const mappingA = mapping(fieldA);
const mappingB = mapping(fieldB);
const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName;
const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName;
return nameA.localeCompare(nameB);
})
.reduce<ItemsEntry>(
(acc, curFieldName) => {
if (!shouldShowFieldHandler(curFieldName)) {
return acc;
}
const shouldHideNullValue =
areNullValuesHidden && flattened[curFieldName] == null && isEsqlMode;
if (shouldHideNullValue) {
return acc;
}
if (pinnedFields.includes(curFieldName)) {
acc.pinnedItems.push(fieldToItem(curFieldName, true));
} else {
const fieldMapping = mapping(curFieldName);
if (
!searchText?.trim() ||
fieldNameWildcardMatcher(
{ name: curFieldName, displayName: fieldMapping?.displayName },
searchText
)
) {
// filter only unpinned fields
acc.restItems.push(fieldToItem(curFieldName, false));
const { pinnedItems, restItems, allFields } = useMemo(
() =>
Object.keys(flattened)
.sort((fieldA, fieldB) => {
const mappingA = mapping(fieldA);
const mappingB = mapping(fieldB);
const nameA = !mappingA || !mappingA.displayName ? fieldA : mappingA.displayName;
const nameB = !mappingB || !mappingB.displayName ? fieldB : mappingB.displayName;
return nameA.localeCompare(nameB);
})
.reduce<ItemsEntry>(
(acc, curFieldName) => {
if (!shouldShowFieldHandler(curFieldName)) {
return acc;
}
const shouldHideNullValue =
areNullValuesHidden && flattened[curFieldName] == null && isEsqlMode;
if (shouldHideNullValue) {
return acc;
}

const isPinned = pinnedFields.includes(curFieldName);
const row = fieldToItem(curFieldName, isPinned);

if (isPinned) {
acc.pinnedItems.push(row);
} else {
if (onFilterField(curFieldName, row.field.displayName, row.field.fieldType)) {
// filter only unpinned fields
acc.restItems.push(row);
}
}

acc.allFields.push({
name: curFieldName,
displayName: row.field.displayName,
type: row.field.fieldType,
});

return acc;
},
{
pinnedItems: [],
restItems: [],
allFields: [],
}
}

return acc;
},
{
pinnedItems: [],
restItems: [],
}
);
),
[
areNullValuesHidden,
fieldToItem,
flattened,
isEsqlMode,
mapping,
onFilterField,
pinnedFields,
shouldShowFieldHandler,
]
);

const rows = useMemo(() => [...pinnedItems, ...restItems], [pinnedItems, restItems]);

Expand Down Expand Up @@ -402,7 +397,7 @@ export const DocViewerTable = ({
scripted={scripted}
highlight={getFieldSearchMatchingHighlight(
fieldMapping?.displayName ?? field,
searchText
tableFiltersProps.searchTerm
)}
isPinned={pinned}
/>
Expand Down Expand Up @@ -433,7 +428,7 @@ export const DocViewerTable = ({

return null;
},
[rows, searchText, fieldsMetadata]
[rows, tableFiltersProps.searchTerm, fieldsMetadata]
);

const renderCellPopover = useCallback(
Expand Down Expand Up @@ -489,14 +484,7 @@ export const DocViewerTable = ({
</EuiFlexItem>

<EuiFlexItem grow={false}>
<EuiFieldSearch
aria-label={searchPlaceholder}
fullWidth
onChange={onSearch}
placeholder={searchPlaceholder}
value={searchText}
data-test-subj="unifiedDocViewerFieldsSearchInput"
/>
<TableFilters {...tableFiltersProps} allFields={allFields} />
</EuiFlexItem>

{rows.length === 0 ? (
Expand Down
Loading

0 comments on commit a8aa215

Please sign in to comment.