[Discover] [ES|QL] Disable ES|QL multivalue filtering in Unified Doc Viewer (#194232)

## Summary

This PR disables ES|QL multivalue filtering in Unified Doc Viewer,
similar to what we did for Unified Data Table in #193415:

![image](https://github.com/user-attachments/assets/f86c3ba9-05e9-4245-97f6-a8f9413950e9)

Part of https://github.com/elastic/kibana/issues/193015.

### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [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
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [x] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] 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))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
Davis McPhee 2024-09-27 13:28:48 -03:00 committed by GitHub
parent 1ba788620e
commit 6f01d6a260
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 197 additions and 47 deletions

View file

@ -4,12 +4,20 @@ exports[`TableActions getFieldCellActions should render correctly for undefined
Array [
<ToggleColumn
Component={[Function]}
isEsqlMode={false}
onToggleColumn={[MockFunction]}
row={
FieldRow {
"columnsMeta": undefined,
"dataViewField": undefined,
"flattenedValue": "flattenedField",
"dataViewField": Object {
"aggregatable": false,
"displayName": "message",
"filterable": false,
"name": "message",
"scripted": false,
"type": "string",
},
"flattenedValue": "test",
"isPinned": false,
"name": "message",
}
@ -24,12 +32,20 @@ exports[`TableActions getFieldCellActions should render the panels correctly for
Array [
<FilterExist
Component={[Function]}
isEsqlMode={false}
onFilter={[MockFunction]}
row={
FieldRow {
"columnsMeta": undefined,
"dataViewField": undefined,
"flattenedValue": "flattenedField",
"dataViewField": Object {
"aggregatable": false,
"displayName": "message",
"filterable": false,
"name": "message",
"scripted": false,
"type": "string",
},
"flattenedValue": "test",
"isPinned": false,
"name": "message",
}
@ -37,12 +53,20 @@ Array [
/>,
<ToggleColumn
Component={[Function]}
isEsqlMode={false}
onToggleColumn={[MockFunction]}
row={
FieldRow {
"columnsMeta": undefined,
"dataViewField": undefined,
"flattenedValue": "flattenedField",
"dataViewField": Object {
"aggregatable": false,
"displayName": "message",
"filterable": false,
"name": "message",
"scripted": false,
"type": "string",
},
"flattenedValue": "test",
"isPinned": false,
"name": "message",
}
@ -57,12 +81,20 @@ exports[`TableActions getFieldValueCellActions should render the panels correctl
Array [
<FilterIn
Component={[Function]}
isEsqlMode={false}
onFilter={[MockFunction]}
row={
FieldRow {
"columnsMeta": undefined,
"dataViewField": undefined,
"flattenedValue": "flattenedField",
"dataViewField": Object {
"aggregatable": false,
"displayName": "message",
"filterable": false,
"name": "message",
"scripted": false,
"type": "string",
},
"flattenedValue": "test",
"isPinned": false,
"name": "message",
}
@ -70,12 +102,20 @@ Array [
/>,
<FilterOut
Component={[Function]}
isEsqlMode={false}
onFilter={[MockFunction]}
row={
FieldRow {
"columnsMeta": undefined,
"dataViewField": undefined,
"flattenedValue": "flattenedField",
"dataViewField": Object {
"aggregatable": false,
"displayName": "message",
"filterable": false,
"name": "message",
"scripted": false,
"type": "string",
},
"flattenedValue": "test",
"isPinned": false,
"name": "message",
}

View file

@ -324,12 +324,12 @@ export const DocViewerTable = ({
}, [showPagination, curPageIndex, pageSize, onChangePageSize, changePageIndex]);
const fieldCellActions = useMemo(
() => getFieldCellActions({ rows, onFilter: filter, onToggleColumn }),
[rows, filter, onToggleColumn]
() => getFieldCellActions({ rows, isEsqlMode, onFilter: filter, onToggleColumn }),
[rows, isEsqlMode, filter, onToggleColumn]
);
const fieldValueCellActions = useMemo(
() => getFieldValueCellActions({ rows, onFilter: filter }),
[rows, filter]
() => getFieldValueCellActions({ rows, isEsqlMode, onFilter: filter }),
[rows, isEsqlMode, filter]
);
useWindowSize(); // trigger re-render on window resize to recalculate the grid container height

View file

@ -11,26 +11,27 @@ import React from 'react';
import { getFieldCellActions, getFieldValueCellActions } from './table_cell_actions';
import { FieldRow } from './field_row';
import { buildDataTableRecord } from '@kbn/discover-utils';
import { stubLogstashDataView as dataView } from '@kbn/data-views-plugin/common/data_view.stub';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
import { render, screen } from '@testing-library/react';
import { dataViewMockWithTimeField } from '@kbn/discover-utils/src/__mocks__';
describe('TableActions', () => {
const rows: FieldRow[] = [
const getRows = (fieldName = 'message', fieldValue: unknown = 'test'): FieldRow[] => [
new FieldRow({
name: 'message',
flattenedValue: 'flattenedField',
name: fieldName,
flattenedValue: fieldValue,
hit: buildDataTableRecord(
{
_ignored: [],
_index: 'test',
_id: '1',
_source: {
message: 'test',
[fieldName]: fieldValue,
},
},
dataView
dataViewMockWithTimeField
),
dataView,
dataView: dataViewMockWithTimeField,
fieldFormats: {} as FieldFormatsStart,
isPinned: false,
columnsMeta: undefined,
@ -49,23 +50,32 @@ describe('TableActions', () => {
describe('getFieldCellActions', () => {
it('should render correctly for undefined functions', () => {
expect(
getFieldCellActions({ rows, onFilter: undefined, onToggleColumn: jest.fn() }).map((item) =>
item(EuiCellParams)
)
getFieldCellActions({
rows: getRows(),
isEsqlMode: false,
onFilter: undefined,
onToggleColumn: jest.fn(),
}).map((item) => item(EuiCellParams))
).toMatchSnapshot();
expect(
getFieldCellActions({ rows, onFilter: undefined, onToggleColumn: undefined }).map((item) =>
item(EuiCellParams)
)
getFieldCellActions({
rows: getRows(),
isEsqlMode: false,
onFilter: undefined,
onToggleColumn: undefined,
}).map((item) => item(EuiCellParams))
).toMatchSnapshot();
});
it('should render the panels correctly for defined onFilter function', () => {
expect(
getFieldCellActions({ rows, onFilter: jest.fn(), onToggleColumn: jest.fn() }).map((item) =>
item(EuiCellParams)
)
getFieldCellActions({
rows: getRows(),
isEsqlMode: false,
onFilter: jest.fn(),
onToggleColumn: jest.fn(),
}).map((item) => item(EuiCellParams))
).toMatchSnapshot();
});
});
@ -73,14 +83,72 @@ describe('TableActions', () => {
describe('getFieldValueCellActions', () => {
it('should render correctly for undefined functions', () => {
expect(
getFieldValueCellActions({ rows, onFilter: undefined }).map((item) => item(EuiCellParams))
getFieldValueCellActions({ rows: getRows(), isEsqlMode: false, onFilter: undefined }).map(
(item) => item(EuiCellParams)
)
).toMatchSnapshot();
});
it('should render the panels correctly for defined onFilter function', () => {
expect(
getFieldValueCellActions({ rows, onFilter: jest.fn() }).map((item) => item(EuiCellParams))
getFieldValueCellActions({ rows: getRows(), isEsqlMode: false, onFilter: jest.fn() }).map(
(item) => item(EuiCellParams)
)
).toMatchSnapshot();
});
it('should allow filtering in ES|QL mode', () => {
const actions = getFieldValueCellActions({
rows: getRows('extension'),
isEsqlMode: true,
onFilter: jest.fn(),
}).map((Action, i) => (
<Action
key={i}
{...EuiCellParams}
Component={(props: any) => (
<div data-test-subj={props['data-test-subj']}>{JSON.stringify(props)}</div>
)}
/>
));
render(<>{actions}</>);
const filterForProps = JSON.parse(
screen.getByTestId('addFilterForValueButton-extension').innerHTML
);
expect(filterForProps.disabled).toBe(false);
expect(filterForProps.title).toBe('Filter for value');
const filterOutProps = JSON.parse(
screen.getByTestId('addFilterOutValueButton-extension').innerHTML
);
expect(filterOutProps.disabled).toBe(false);
expect(filterOutProps.title).toBe('Filter out value');
});
it('should not allow filtering in ES|QL mode for multivalue fields', () => {
const actions = getFieldValueCellActions({
rows: getRows('extension', ['foo', 'bar']),
isEsqlMode: true,
onFilter: jest.fn(),
}).map((Action, i) => (
<Action
key={i}
{...EuiCellParams}
Component={(props: any) => (
<div data-test-subj={props['data-test-subj']}>{JSON.stringify(props)}</div>
)}
/>
));
render(<>{actions}</>);
const filterForProps = JSON.parse(
screen.getByTestId('addFilterForValueButton-extension').innerHTML
);
expect(filterForProps.disabled).toBe(true);
expect(filterForProps.title).toBe('Multivalue filtering is not supported in ES|QL');
const filterOutProps = JSON.parse(
screen.getByTestId('addFilterOutValueButton-extension').innerHTML
);
expect(filterOutProps.disabled).toBe(true);
expect(filterOutProps.title).toBe('Multivalue filtering is not supported in ES|QL');
});
});
});

View file

@ -16,9 +16,10 @@ import { FieldRow } from './field_row';
interface TableActionsProps {
Component: EuiDataGridColumnCellActionProps['Component'];
row: FieldRow | undefined; // as we pass `rows[rowIndex]` it's safer to assume that `row` prop can be undefined
isEsqlMode: boolean | undefined;
}
export function isFilterInOutPairDisabled(
function isFilterInOutPairDisabled(
row: FieldRow | undefined,
onFilter: DocViewFilterFn | undefined
): boolean {
@ -58,9 +59,17 @@ export function getFilterInOutPairDisabledWarning(
: undefined;
}
export const FilterIn: React.FC<TableActionsProps & { onFilter: DocViewFilterFn | undefined }> = ({
const esqlMultivalueFilteringDisabled = i18n.translate(
'unifiedDocViewer.docViews.table.esqlMultivalueFilteringDisabled',
{
defaultMessage: 'Multivalue filtering is not supported in ES|QL',
}
);
const FilterIn: React.FC<TableActionsProps & { onFilter: DocViewFilterFn | undefined }> = ({
Component,
row,
isEsqlMode,
onFilter,
}) => {
if (!row) {
@ -81,12 +90,14 @@ export const FilterIn: React.FC<TableActionsProps & { onFilter: DocViewFilterFn
return null;
}
const filteringDisabled = isEsqlMode && Array.isArray(flattenedValue);
return (
<Component
data-test-subj={`addFilterForValueButton-${name}`}
iconType="plusInCircle"
disabled={isFilterInOutPairDisabled(row, onFilter)}
title={filterAddLabel}
disabled={filteringDisabled || isFilterInOutPairDisabled(row, onFilter)}
title={filteringDisabled ? esqlMultivalueFilteringDisabled : filterAddLabel}
flush="left"
onClick={() => onFilter(dataViewField, flattenedValue, '+')}
>
@ -95,9 +106,10 @@ export const FilterIn: React.FC<TableActionsProps & { onFilter: DocViewFilterFn
);
};
export const FilterOut: React.FC<TableActionsProps & { onFilter: DocViewFilterFn | undefined }> = ({
const FilterOut: React.FC<TableActionsProps & { onFilter: DocViewFilterFn | undefined }> = ({
Component,
row,
isEsqlMode,
onFilter,
}) => {
if (!row) {
@ -118,12 +130,14 @@ export const FilterOut: React.FC<TableActionsProps & { onFilter: DocViewFilterFn
return null;
}
const filteringDisabled = isEsqlMode && Array.isArray(flattenedValue);
return (
<Component
data-test-subj={`addFilterOutValueButton-${name}`}
iconType="minusInCircle"
disabled={isFilterInOutPairDisabled(row, onFilter)}
title={filterOutLabel}
disabled={filteringDisabled || isFilterInOutPairDisabled(row, onFilter)}
title={filteringDisabled ? esqlMultivalueFilteringDisabled : filterOutLabel}
flush="left"
onClick={() => onFilter(dataViewField, flattenedValue, '-')}
>
@ -132,7 +146,7 @@ export const FilterOut: React.FC<TableActionsProps & { onFilter: DocViewFilterFn
);
};
export function isFilterExistsDisabled(
function isFilterExistsDisabled(
row: FieldRow | undefined,
onFilter: DocViewFilterFn | undefined
): boolean {
@ -165,9 +179,11 @@ export function getFilterExistsDisabledWarning(
: undefined;
}
export const FilterExist: React.FC<
TableActionsProps & { onFilter: DocViewFilterFn | undefined }
> = ({ Component, row, onFilter }) => {
const FilterExist: React.FC<TableActionsProps & { onFilter: DocViewFilterFn | undefined }> = ({
Component,
row,
onFilter,
}) => {
if (!row) {
return null;
}
@ -198,7 +214,7 @@ export const FilterExist: React.FC<
);
};
export const ToggleColumn: React.FC<
const ToggleColumn: React.FC<
TableActionsProps & {
onToggleColumn: ((field: string) => void) | undefined;
}
@ -236,10 +252,12 @@ export const ToggleColumn: React.FC<
export function getFieldCellActions({
rows,
isEsqlMode,
onFilter,
onToggleColumn,
}: {
rows: FieldRow[];
isEsqlMode: boolean | undefined;
onFilter?: DocViewFilterFn;
onToggleColumn: ((field: string) => void) | undefined;
}) {
@ -247,7 +265,14 @@ export function getFieldCellActions({
...(onFilter
? [
({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => {
return <FilterExist row={rows[rowIndex]} Component={Component} onFilter={onFilter} />;
return (
<FilterExist
row={rows[rowIndex]}
Component={Component}
isEsqlMode={isEsqlMode}
onFilter={onFilter}
/>
);
},
]
: []),
@ -258,6 +283,7 @@ export function getFieldCellActions({
<ToggleColumn
row={rows[rowIndex]}
Component={Component}
isEsqlMode={isEsqlMode}
onToggleColumn={onToggleColumn}
/>
);
@ -269,18 +295,34 @@ export function getFieldCellActions({
export function getFieldValueCellActions({
rows,
isEsqlMode,
onFilter,
}: {
rows: FieldRow[];
isEsqlMode: boolean | undefined;
onFilter?: DocViewFilterFn;
}) {
return onFilter
? [
({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => {
return <FilterIn row={rows[rowIndex]} Component={Component} onFilter={onFilter} />;
return (
<FilterIn
row={rows[rowIndex]}
Component={Component}
isEsqlMode={isEsqlMode}
onFilter={onFilter}
/>
);
},
({ Component, rowIndex }: EuiDataGridColumnCellActionProps) => {
return <FilterOut row={rows[rowIndex]} Component={Component} onFilter={onFilter} />;
return (
<FilterOut
row={rows[rowIndex]}
Component={Component}
isEsqlMode={isEsqlMode}
onFilter={onFilter}
/>
);
},
]
: [];