diff --git a/packages/kbn-esql-utils/src/utils/append_to_query.test.ts b/packages/kbn-esql-utils/src/utils/append_to_query.test.ts index c9db52076222f..9dc15454cbbdf 100644 --- a/packages/kbn-esql-utils/src/utils/append_to_query.test.ts +++ b/packages/kbn-esql-utils/src/utils/append_to_query.test.ts @@ -168,5 +168,11 @@ AND \`dest\`=="Crete"` and \`ip\`::string!="127.0.0.2/32"` ); }); + + it('returns undefined for multivalue fields', () => { + expect( + appendWhereClauseToESQLQuery('from logstash-*', 'dest', ['meow'], '+', 'string') + ).toBeUndefined(); + }); }); }); diff --git a/packages/kbn-esql-utils/src/utils/append_to_query.ts b/packages/kbn-esql-utils/src/utils/append_to_query.ts index f4161be073a8d..2820881810387 100644 --- a/packages/kbn-esql-utils/src/utils/append_to_query.ts +++ b/packages/kbn-esql-utils/src/utils/append_to_query.ts @@ -21,7 +21,11 @@ export function appendWhereClauseToESQLQuery( value: unknown, operation: '+' | '-' | 'is_not_null' | 'is_null', fieldType?: string -): string { +): string | undefined { + // multivalues filtering is not supported yet + if (Array.isArray(value)) { + return undefined; + } let operator; switch (operation) { case 'is_not_null': diff --git a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx index c5bba429ed934..876db4b0e7149 100644 --- a/packages/kbn-unified-data-table/src/components/data_table_columns.tsx +++ b/packages/kbn-unified-data-table/src/components/data_table_columns.tsx @@ -189,7 +189,13 @@ function buildEuiGridColumn({ cellActions = columnCellActions; } else { cellActions = dataViewField - ? buildCellActions(dataViewField, toastNotifications, valueToStringConverter, onFilter) + ? buildCellActions( + dataViewField, + isPlainRecord, + toastNotifications, + valueToStringConverter, + onFilter + ) : []; if (columnCellActions?.length && cellActionsHandling === 'append') { diff --git a/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx b/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx index 628d3b5a29697..d097a2becd035 100644 --- a/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx +++ b/packages/kbn-unified-data-table/src/components/default_cell_actions.test.tsx @@ -45,6 +45,7 @@ describe('Default cell actions ', function () { it('should not show cell actions for unfilterable fields', async () => { const cellActions = buildCellActions( { name: 'foo', filterable: false } as DataViewField, + false, servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter ); @@ -61,6 +62,7 @@ describe('Default cell actions ', function () { it('should show filter actions for filterable fields', async () => { const cellActions = buildCellActions( { name: 'foo', filterable: true } as DataViewField, + false, servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter, jest.fn() @@ -71,6 +73,7 @@ describe('Default cell actions ', function () { it('should show Copy action for _source field', async () => { const cellActions = buildCellActions( { name: '_source', type: '_source', filterable: false } as DataViewField, + false, servicesMock.toastNotifications, dataTableContextMock.valueToStringConverter ); @@ -87,65 +90,97 @@ describe('Default cell actions ', function () { const component = mountWithIntl( } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} + cellActionProps={{ + Component: (props: any) => , + rowIndex: 1, + colIndex: 1, + columnId: 'extension', + isExpanded: false, + }} + field={{ name: 'extension', filterable: true } as DataViewField} + isPlainRecord={false} /> ); const button = findTestSubject(component, 'filterForButton'); await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith({}, 'jpg', '+'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + { name: 'extension', filterable: true }, + 'jpg', + '+' + ); }); it('triggers filter function when FilterInBtn is clicked for a non-provided value', async () => { const component = mountWithIntl( } - rowIndex={0} - colIndex={1} - columnId="extension" - isExpanded={false} + cellActionProps={{ + Component: (props: any) => , + rowIndex: 0, + colIndex: 1, + columnId: 'extension', + isExpanded: false, + }} + field={{ name: 'extension', filterable: true } as DataViewField} + isPlainRecord={false} /> ); const button = findTestSubject(component, 'filterForButton'); await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith({}, undefined, '+'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + { name: 'extension', filterable: true }, + undefined, + '+' + ); }); it('triggers filter function when FilterInBtn is clicked for an empty string value', async () => { const component = mountWithIntl( } - rowIndex={4} - colIndex={1} - columnId="message" - isExpanded={false} + cellActionProps={{ + Component: (props: any) => , + rowIndex: 4, + colIndex: 1, + columnId: 'message', + isExpanded: false, + }} + field={{ name: 'message', filterable: true } as DataViewField} + isPlainRecord={false} /> ); const button = findTestSubject(component, 'filterForButton'); await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith({}, '', '+'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + { name: 'message', filterable: true }, + '', + '+' + ); }); it('triggers filter function when FilterOutBtn is clicked', async () => { const component = mountWithIntl( } - rowIndex={1} - colIndex={1} - columnId="extension" - isExpanded={false} + cellActionProps={{ + Component: (props: any) => , + rowIndex: 1, + colIndex: 1, + columnId: 'extension', + isExpanded: false, + }} + field={{ name: 'extension', filterable: true } as DataViewField} + isPlainRecord={false} /> ); const button = findTestSubject(component, 'filterOutButton'); await button.simulate('click'); - expect(dataTableContextMock.onFilter).toHaveBeenCalledWith({}, 'jpg', '-'); + expect(dataTableContextMock.onFilter).toHaveBeenCalledWith( + { name: 'extension', filterable: true }, + 'jpg', + '-' + ); }); it('triggers clipboard copy when CopyBtn is clicked', async () => { const component = mountWithIntl( diff --git a/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx b/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx index 0be9803633825..51730aef44349 100644 --- a/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx +++ b/packages/kbn-unified-data-table/src/components/default_cell_actions.tsx @@ -32,11 +32,25 @@ function onFilterCell( } } -export const FilterInBtn = ( - { Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps, - field: DataViewField -) => { +const esqlMultivalueFilteringDisabled = i18n.translate( + 'unifiedDataTable.grid.esqlMultivalueFilteringDisabled', + { + defaultMessage: 'Multivalue filtering is not supported in ES|QL', + } +); + +export const FilterInBtn = ({ + cellActionProps: { Component, rowIndex, columnId }, + field, + isPlainRecord, +}: { + cellActionProps: EuiDataGridColumnCellActionProps; + field: DataViewField; + isPlainRecord: boolean | undefined; +}) => { const context = useContext(UnifiedDataTableContext); + const filteringDisabled = + isPlainRecord && Array.isArray(context.rows[rowIndex]?.flattened[columnId]); const buttonTitle = i18n.translate('unifiedDataTable.grid.filterForAria', { defaultMessage: 'Filter for this {value}', values: { value: columnId }, @@ -49,7 +63,8 @@ export const FilterInBtn = ( }} iconType="plusInCircle" aria-label={buttonTitle} - title={buttonTitle} + title={filteringDisabled ? esqlMultivalueFilteringDisabled : buttonTitle} + disabled={filteringDisabled} data-test-subj="filterForButton" > {i18n.translate('unifiedDataTable.grid.filterFor', { @@ -59,11 +74,18 @@ export const FilterInBtn = ( ); }; -export const FilterOutBtn = ( - { Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps, - field: DataViewField -) => { +export const FilterOutBtn = ({ + cellActionProps: { Component, rowIndex, columnId }, + field, + isPlainRecord, +}: { + cellActionProps: EuiDataGridColumnCellActionProps; + field: DataViewField; + isPlainRecord: boolean | undefined; +}) => { const context = useContext(UnifiedDataTableContext); + const filteringDisabled = + isPlainRecord && Array.isArray(context.rows[rowIndex]?.flattened[columnId]); const buttonTitle = i18n.translate('unifiedDataTable.grid.filterOutAria', { defaultMessage: 'Filter out this {value}', values: { value: columnId }, @@ -76,7 +98,8 @@ export const FilterOutBtn = ( }} iconType="minusInCircle" aria-label={buttonTitle} - title={buttonTitle} + title={filteringDisabled ? esqlMultivalueFilteringDisabled : buttonTitle} + disabled={filteringDisabled} data-test-subj="filterOutButton" > {i18n.translate('unifiedDataTable.grid.filterOut', { @@ -120,6 +143,7 @@ export function buildCopyValueButton( export function buildCellActions( field: DataViewField, + isPlainRecord: boolean | undefined, toastNotifications: ToastsStart, valueToStringConverter: ValueToStringConverter, onFilter?: DocViewFilterFn @@ -127,16 +151,18 @@ export function buildCellActions( return [ ...(onFilter && field.filterable ? [ - ({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) => - FilterInBtn( - { Component, rowIndex, columnId } as EuiDataGridColumnCellActionProps, - field - ), - ({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) => - FilterOutBtn( - { Component, rowIndex, columnId } as EuiDataGridColumnCellActionProps, - field - ), + (cellActionProps: EuiDataGridColumnCellActionProps) => + FilterInBtn({ + cellActionProps, + field, + isPlainRecord, + }), + (cellActionProps: EuiDataGridColumnCellActionProps) => + FilterOutBtn({ + cellActionProps, + field, + isPlainRecord, + }), ] : []), ({ Component, rowIndex, columnId }: EuiDataGridColumnCellActionProps) => diff --git a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx index 97e1a34f85caf..601338333226f 100644 --- a/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx +++ b/src/plugins/discover/public/application/main/components/layout/discover_layout.tsx @@ -211,6 +211,9 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) { getOperator(fieldName, values, operation), fieldType ); + if (!updatedQuery) { + return; + } data.query.queryString.setQuery({ esql: updatedQuery, });