mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Lens] Add one click filter to Lens table (#139701)
* add one click filter to Lens table * fix tests
This commit is contained in:
parent
5e0615f628
commit
c72e6ee59e
9 changed files with 130 additions and 8 deletions
|
@ -30,6 +30,7 @@ export interface ColumnState {
|
|||
columnId: string;
|
||||
width?: number;
|
||||
hidden?: boolean;
|
||||
oneClickFilter?: boolean;
|
||||
isTransposed?: boolean;
|
||||
// These flags are necessary to transpose columns and map them back later
|
||||
// They are set automatically and are not user-editable
|
||||
|
@ -63,6 +64,7 @@ export const datatableColumn: ExpressionFunctionDefinition<
|
|||
alignment: { types: ['string'], help: '' },
|
||||
sortingHint: { types: ['string'], help: '' },
|
||||
hidden: { types: ['boolean'], help: '' },
|
||||
oneClickFilter: { types: ['boolean'], help: '' },
|
||||
width: { types: ['number'], help: '' },
|
||||
isTransposed: { types: ['boolean'], help: '' },
|
||||
transposable: { types: ['boolean'], help: '' },
|
||||
|
|
|
@ -13,6 +13,7 @@ exports[`DatatableComponent it renders actions column when there are row actions
|
|||
"c": "right",
|
||||
},
|
||||
"getColorForValue": [MockFunction],
|
||||
"handleFilterClick": [Function],
|
||||
"minMaxByColumnId": Object {
|
||||
"c": Object {
|
||||
"max": 3,
|
||||
|
@ -291,6 +292,7 @@ exports[`DatatableComponent it renders custom row height if set to another value
|
|||
"c": "right",
|
||||
},
|
||||
"getColorForValue": [MockFunction],
|
||||
"handleFilterClick": [Function],
|
||||
"minMaxByColumnId": Object {
|
||||
"c": Object {
|
||||
"max": 3,
|
||||
|
@ -558,6 +560,7 @@ exports[`DatatableComponent it renders the title and value 1`] = `
|
|||
"c": "right",
|
||||
},
|
||||
"getColorForValue": [MockFunction],
|
||||
"handleFilterClick": [Function],
|
||||
"minMaxByColumnId": Object {
|
||||
"c": Object {
|
||||
"max": 3,
|
||||
|
@ -823,6 +826,7 @@ exports[`DatatableComponent it should render hide, reset, and sort actions on he
|
|||
"c": "right",
|
||||
},
|
||||
"getColorForValue": [MockFunction],
|
||||
"handleFilterClick": [Function],
|
||||
"minMaxByColumnId": Object {
|
||||
"c": Object {
|
||||
"max": 3,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { ReactWrapper } from 'enzyme';
|
|||
import { DatatableArgs, ColumnConfigArg } from '../../../../common/expressions';
|
||||
import { DataContextType } from './types';
|
||||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
|
||||
describe('datatable cell renderer', () => {
|
||||
const table: Datatable = {
|
||||
|
@ -146,6 +147,50 @@ describe('datatable cell renderer', () => {
|
|||
expect(cell.find('.lnsTableCell--multiline').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders as EuiLink if oneClickFilter is set', () => {
|
||||
const MultiLineCellRenderer = createGridCell(
|
||||
{
|
||||
a: { convert: (x) => `formatted ${x}` } as FieldFormat,
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
columnId: 'a',
|
||||
type: 'lens_datatable_column',
|
||||
oneClickFilter: true,
|
||||
},
|
||||
],
|
||||
sortingColumnId: '',
|
||||
sortingDirection: 'none',
|
||||
},
|
||||
DataContext,
|
||||
{ get: jest.fn() } as unknown as IUiSettingsClient,
|
||||
true
|
||||
);
|
||||
const cell = mountWithIntl(
|
||||
<DataContext.Provider
|
||||
value={{
|
||||
table,
|
||||
alignments: {
|
||||
a: 'right',
|
||||
},
|
||||
handleFilterClick: () => {},
|
||||
}}
|
||||
>
|
||||
<MultiLineCellRenderer
|
||||
rowIndex={0}
|
||||
colIndex={0}
|
||||
columnId="a"
|
||||
setCellProps={() => {}}
|
||||
isExpandable={false}
|
||||
isDetails={false}
|
||||
isExpanded={false}
|
||||
/>
|
||||
</DataContext.Provider>
|
||||
);
|
||||
expect(cell.find(EuiLink).text()).toEqual('formatted 123');
|
||||
});
|
||||
|
||||
describe('dynamic coloring', () => {
|
||||
const paletteRegistry = chartPluginMock.createPaletteRegistry();
|
||||
const customPalette = paletteRegistry.get('custom');
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import type { EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
import { EuiDataGridCellValueElementProps, EuiLink } from '@elastic/eui';
|
||||
import type { IUiSettingsClient } from '@kbn/core/public';
|
||||
import classNames from 'classnames';
|
||||
import type { FormatFactory } from '../../../../common';
|
||||
|
@ -25,13 +25,16 @@ export const createGridCell = (
|
|||
// Changing theme requires a full reload of the page, so we can cache here
|
||||
const IS_DARK_THEME = uiSettings.get('theme:darkMode');
|
||||
return ({ rowIndex, columnId, setCellProps }: EuiDataGridCellValueElementProps) => {
|
||||
const { table, alignments, minMaxByColumnId, getColorForValue } = useContext(DataContext);
|
||||
const { table, alignments, minMaxByColumnId, getColorForValue, handleFilterClick } =
|
||||
useContext(DataContext);
|
||||
const rowValue = table?.rows[rowIndex]?.[columnId];
|
||||
const content = formatters[columnId]?.convert(rowValue, 'html');
|
||||
const currentAlignment = alignments && alignments[columnId];
|
||||
|
||||
const { colorMode, palette } =
|
||||
columnConfig.columns.find(({ columnId: id }) => id === columnId) || {};
|
||||
const colIndex = columnConfig.columns.findIndex(({ columnId: id }) => id === columnId);
|
||||
const { colorMode, palette, oneClickFilter } = columnConfig.columns[colIndex] || {};
|
||||
const filterOnClick = oneClickFilter && handleFilterClick;
|
||||
|
||||
const content = formatters[columnId]?.convert(rowValue, filterOnClick ? 'text' : 'html');
|
||||
const currentAlignment = alignments && alignments[columnId];
|
||||
|
||||
useEffect(() => {
|
||||
const originalId = getOriginalId(columnId);
|
||||
|
@ -68,6 +71,25 @@ export const createGridCell = (
|
|||
};
|
||||
}, [rowValue, columnId, setCellProps, colorMode, palette, minMaxByColumnId, getColorForValue]);
|
||||
|
||||
if (filterOnClick) {
|
||||
return (
|
||||
<div
|
||||
data-test-subj="lnsTableCellContent"
|
||||
className={classNames({
|
||||
'lnsTableCell--multiline': fitRowToContent,
|
||||
[`lnsTableCell--${currentAlignment}`]: true,
|
||||
})}
|
||||
>
|
||||
<EuiLink
|
||||
onClick={() => {
|
||||
handleFilterClick?.(columnId, rowValue, colIndex, rowIndex);
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</EuiLink>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
/*
|
||||
|
|
|
@ -76,8 +76,10 @@ export const createGridColumns = (
|
|||
const filterable = bucketLookup.has(field);
|
||||
const { name, index: colIndex } = columnsReverseLookup[field];
|
||||
|
||||
const columnArgs = columnConfig.columns.find(({ columnId }) => columnId === field);
|
||||
|
||||
const cellActions =
|
||||
filterable && handleFilterClick
|
||||
filterable && handleFilterClick && !columnArgs?.oneClickFilter
|
||||
? [
|
||||
({ rowIndex, columnId, Component }: EuiDataGridColumnCellActionProps) => {
|
||||
const { rowValue, contentsIsDefined, cellContent } = getContentData({
|
||||
|
@ -158,7 +160,6 @@ export const createGridColumns = (
|
|||
]
|
||||
: undefined;
|
||||
|
||||
const columnArgs = columnConfig.columns.find(({ columnId }) => columnId === field);
|
||||
const isTransposed = Boolean(columnArgs?.originalColumnId);
|
||||
const initialWidth = columnArgs?.width;
|
||||
const isHidden = columnArgs?.hidden;
|
||||
|
|
|
@ -318,6 +318,42 @@ export function TableDimensionEditor(
|
|||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{props.groupId === 'rows' && (
|
||||
<EuiFormRow
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.lens.table.columnFilterClickLabel', {
|
||||
defaultMessage: 'Directly filter on click',
|
||||
})}
|
||||
display="columnCompressedSwitch"
|
||||
>
|
||||
<EuiSwitch
|
||||
compressed
|
||||
label={i18n.translate('xpack.lens.table.columnFilterClickLabel', {
|
||||
defaultMessage: 'Directly filter on click',
|
||||
})}
|
||||
showLabel={false}
|
||||
data-test-subj="lns-table-column-one-click-filter"
|
||||
checked={Boolean(column?.oneClickFilter)}
|
||||
disabled={column.hidden}
|
||||
onChange={() => {
|
||||
const newState = {
|
||||
...state,
|
||||
columns: state.columns.map((currentColumn) => {
|
||||
if (currentColumn.columnId === accessor) {
|
||||
return {
|
||||
...currentColumn,
|
||||
oneClickFilter: !column.oneClickFilter,
|
||||
};
|
||||
} else {
|
||||
return currentColumn;
|
||||
}
|
||||
}),
|
||||
};
|
||||
setState(newState);
|
||||
}}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -452,6 +452,7 @@ export const DatatableComponent = (props: DatatableRenderProps) => {
|
|||
alignments,
|
||||
minMaxByColumnId,
|
||||
getColorForValue: props.paletteService.get(CUSTOM_PALETTE).getColorForValue!,
|
||||
handleFilterClick,
|
||||
}}
|
||||
>
|
||||
<EuiDataGrid
|
||||
|
|
|
@ -65,6 +65,13 @@ export interface DataContextType {
|
|||
rowHasRowClickTriggerActions?: boolean[];
|
||||
alignments?: Record<string, 'left' | 'right' | 'center'>;
|
||||
minMaxByColumnId?: Record<string, { min: number; max: number }>;
|
||||
handleFilterClick?: (
|
||||
field: string,
|
||||
value: unknown,
|
||||
colIndex: number,
|
||||
rowIndex: number,
|
||||
negate?: boolean
|
||||
) => void;
|
||||
getColorForValue?: (
|
||||
value: number | undefined,
|
||||
state: CustomPaletteState,
|
||||
|
|
|
@ -449,6 +449,10 @@ export const getDatatableVisualization = ({
|
|||
arguments: {
|
||||
columnId: [column.columnId],
|
||||
hidden: typeof column.hidden === 'undefined' ? [] : [column.hidden],
|
||||
oneClickFilter:
|
||||
typeof column.oneClickFilter === 'undefined'
|
||||
? []
|
||||
: [column.oneClickFilter],
|
||||
width: typeof column.width === 'undefined' ? [] : [column.width],
|
||||
isTransposed:
|
||||
typeof column.isTransposed === 'undefined' ? [] : [column.isTransposed],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue