mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Discover] [Unified Data Table] Improve absolute column width handling (#190288)
## Summary This PR improves the handling of columns with absolute widths in Discover, including the following enhancements: - If there are no auto width columns in the profile default app state, set the last column to auto width so the default columns always fill the grid. - If there are no auto width columns remaining when removing a column from the grid, set the last column to auto width so the remaining columns fill the grid. - Add a "Reset width" button to the column header popovers to allow resetting absolute width columns back to auto width. https://github.com/user-attachments/assets/0c588969-5720-40e3-91e2-07a83a93b797 Resolves #189817. Related #188550. ### Checklist - [ ] 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 - [ ] 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) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Julia Rechkunova <julia.rechkunova@elastic.co>
This commit is contained in:
parent
5f19307d26
commit
349fdac456
29 changed files with 708 additions and 153 deletions
|
@ -13,7 +13,7 @@ Props description:
|
|||
| **dataView** | DataView | The used data view. |
|
||||
| **loadingState** | DataLoadingState | Determines if data is currently loaded. |
|
||||
| **onFilter** | DocViewFilterFn | Function to add a filter in the grid cell or document flyout. |
|
||||
| **onResize** | (optional)(colSettings: { columnId: string; width: number }) => void; | Function triggered when a column is resized by the user. |
|
||||
| **onResize** | (optional)(colSettings: { columnId: string; width: number | undefind }) => void; | Function triggered when a column is resized by the user, passes `undefined` for auto-width. |
|
||||
| **onSetColumns** | (columns: string[], hideTimeColumn: boolean) => void; | Function to set all columns. |
|
||||
| **onSort** | (optional)(sort: string[][]) => void; | Function to change sorting of the documents, skipped when isSortEnabled is set to false. |
|
||||
| **rows** | (optional)DataTableRecord[] | Array of documents provided by Elasticsearch. |
|
||||
|
@ -81,7 +81,7 @@ Usage example:
|
|||
onFilter={() => {
|
||||
// Add logic to refetch the data when the filter by field was added/removed. Refetch data.
|
||||
}}
|
||||
onResize={(colSettings: { columnId: string; width: number }) => {
|
||||
onResize={(colSettings: { columnId: string; width: number | undefined }) => {
|
||||
// Update the table state with the new width for the column
|
||||
}}
|
||||
onSetColumns={(columns: string[], hideTimeColumn: boolean) => {
|
||||
|
|
|
@ -24,7 +24,7 @@ export * as columnActions from './src/components/actions/columns';
|
|||
export { getRowsPerPageOptions } from './src/utils/rows_per_page';
|
||||
export { popularizeField } from './src/utils/popularize_field';
|
||||
|
||||
export { useColumns } from './src/hooks/use_data_grid_columns';
|
||||
export { useColumns, type UseColumnsProps } from './src/hooks/use_data_grid_columns';
|
||||
export { OPEN_DETAILS, SELECT_ROW } from './src/components/data_table_columns'; // TODO: deprecate?
|
||||
export { DataTableRowControl } from './src/components/data_table_row_control';
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ import { getStateColumnActions } from './columns';
|
|||
import { dataViewMock } from '@kbn/discover-utils/src/__mocks__';
|
||||
import { Capabilities } from '@kbn/core/types';
|
||||
import { dataViewsMock } from '../../../__mocks__/data_views';
|
||||
import { UnifiedDataTableSettings } from '../../types';
|
||||
|
||||
function getStateColumnAction(
|
||||
state: { columns?: string[]; sort?: string[][] },
|
||||
state: { columns?: string[]; sort?: string[][]; settings?: UnifiedDataTableSettings },
|
||||
setAppState: (state: { columns: string[]; sort?: string[][] }) => void
|
||||
) {
|
||||
return getStateColumnActions({
|
||||
|
@ -28,6 +29,7 @@ function getStateColumnAction(
|
|||
columns: state.columns,
|
||||
sort: state.sort,
|
||||
defaultOrder: 'desc',
|
||||
settings: state.settings,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -41,6 +43,7 @@ describe('Test column actions', () => {
|
|||
actions.onAddColumn('test');
|
||||
expect(setAppState).toHaveBeenCalledWith({ columns: ['test'] });
|
||||
});
|
||||
|
||||
test('getStateColumnActions with columns and sort in state', () => {
|
||||
const setAppState = jest.fn();
|
||||
const actions = getStateColumnAction(
|
||||
|
@ -77,4 +80,95 @@ describe('Test column actions', () => {
|
|||
columns: ['second', 'first'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass settings to setAppState', () => {
|
||||
const setAppState = jest.fn();
|
||||
const settings: UnifiedDataTableSettings = { columns: { first: { width: 100 } } };
|
||||
const actions = getStateColumnAction({ columns: ['first'], settings }, setAppState);
|
||||
actions.onAddColumn('second');
|
||||
expect(setAppState).toHaveBeenCalledWith({ columns: ['first', 'second'], settings });
|
||||
setAppState.mockClear();
|
||||
actions.onRemoveColumn('second');
|
||||
expect(setAppState).toHaveBeenCalledWith({ columns: ['first'], settings, sort: [] });
|
||||
setAppState.mockClear();
|
||||
actions.onMoveColumn('first', 0);
|
||||
expect(setAppState).toHaveBeenCalledWith({ columns: ['first'], settings });
|
||||
setAppState.mockClear();
|
||||
actions.onSetColumns(['first', 'second'], true);
|
||||
expect(setAppState).toHaveBeenCalledWith({ columns: ['first', 'second'], settings });
|
||||
setAppState.mockClear();
|
||||
});
|
||||
|
||||
it('should clean up settings to remove non-existing columns', () => {
|
||||
const setAppState = jest.fn();
|
||||
const actions = getStateColumnAction(
|
||||
{
|
||||
columns: ['first', 'second', 'third'],
|
||||
settings: { columns: { first: { width: 100 }, second: { width: 200 } } },
|
||||
},
|
||||
setAppState
|
||||
);
|
||||
actions.onRemoveColumn('second');
|
||||
expect(setAppState).toHaveBeenCalledWith({
|
||||
columns: ['first', 'third'],
|
||||
settings: { columns: { first: { width: 100 } } },
|
||||
sort: [],
|
||||
});
|
||||
setAppState.mockClear();
|
||||
actions.onSetColumns(['first', 'third'], true);
|
||||
expect(setAppState).toHaveBeenCalledWith({
|
||||
columns: ['first', 'third'],
|
||||
settings: { columns: { first: { width: 100 } } },
|
||||
});
|
||||
});
|
||||
|
||||
it('should reset the last column to auto width if only absolute width columns remain', () => {
|
||||
const setAppState = jest.fn();
|
||||
let actions = getStateColumnAction(
|
||||
{
|
||||
columns: ['first', 'second', 'third'],
|
||||
settings: { columns: { second: { width: 100 }, third: { width: 100, display: 'test' } } },
|
||||
},
|
||||
setAppState
|
||||
);
|
||||
actions.onRemoveColumn('first');
|
||||
expect(setAppState).toHaveBeenCalledWith({
|
||||
columns: ['second', 'third'],
|
||||
settings: { columns: { second: { width: 100 }, third: { display: 'test' } } },
|
||||
sort: [],
|
||||
});
|
||||
setAppState.mockClear();
|
||||
actions = getStateColumnAction(
|
||||
{
|
||||
columns: ['first', 'second', 'third'],
|
||||
settings: { columns: { second: { width: 100 }, third: { width: 100 } } },
|
||||
},
|
||||
setAppState
|
||||
);
|
||||
actions.onSetColumns(['second', 'third'], true);
|
||||
expect(setAppState).toHaveBeenCalledWith({
|
||||
columns: ['second', 'third'],
|
||||
settings: { columns: { second: { width: 100 } } },
|
||||
});
|
||||
});
|
||||
|
||||
it('should not reset the last column to auto width if there are remaining auto width columns', () => {
|
||||
const setAppState = jest.fn();
|
||||
const actions = getStateColumnAction(
|
||||
{ columns: ['first', 'second', 'third'], settings: { columns: { third: { width: 100 } } } },
|
||||
setAppState
|
||||
);
|
||||
actions.onRemoveColumn('first');
|
||||
expect(setAppState).toHaveBeenCalledWith({
|
||||
columns: ['second', 'third'],
|
||||
settings: { columns: { third: { width: 100 } } },
|
||||
sort: [],
|
||||
});
|
||||
setAppState.mockClear();
|
||||
actions.onSetColumns(['second', 'third'], true);
|
||||
expect(setAppState).toHaveBeenCalledWith({
|
||||
columns: ['second', 'third'],
|
||||
settings: { columns: { third: { width: 100 } } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,10 +5,93 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import { Capabilities } from '@kbn/core/public';
|
||||
|
||||
import type { Capabilities } from '@kbn/core/public';
|
||||
import type { DataViewsContract } from '@kbn/data-plugin/public';
|
||||
import type { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { omit } from 'lodash';
|
||||
import { popularizeField } from '../../utils/popularize_field';
|
||||
import type { UnifiedDataTableSettings } from '../../types';
|
||||
|
||||
export function getStateColumnActions({
|
||||
capabilities,
|
||||
dataView,
|
||||
dataViews,
|
||||
useNewFieldsApi,
|
||||
setAppState,
|
||||
columns,
|
||||
sort,
|
||||
defaultOrder,
|
||||
settings,
|
||||
}: {
|
||||
capabilities: Capabilities;
|
||||
dataView: DataView;
|
||||
dataViews: DataViewsContract;
|
||||
useNewFieldsApi: boolean;
|
||||
setAppState: (state: {
|
||||
columns: string[];
|
||||
sort?: string[][];
|
||||
settings?: UnifiedDataTableSettings;
|
||||
}) => void;
|
||||
columns?: string[];
|
||||
sort: string[][] | undefined;
|
||||
defaultOrder: string;
|
||||
settings?: UnifiedDataTableSettings;
|
||||
}) {
|
||||
function onAddColumn(columnName: string) {
|
||||
popularizeField(dataView, columnName, dataViews, capabilities);
|
||||
const nextColumns = addColumn(columns || [], columnName, useNewFieldsApi);
|
||||
const nextSort = columnName === '_score' && !sort?.length ? [['_score', defaultOrder]] : sort;
|
||||
setAppState({ columns: nextColumns, sort: nextSort, settings });
|
||||
}
|
||||
|
||||
function onRemoveColumn(columnName: string) {
|
||||
popularizeField(dataView, columnName, dataViews, capabilities);
|
||||
|
||||
const nextColumns = removeColumn(columns || [], columnName, useNewFieldsApi);
|
||||
// The state's sort property is an array of [sortByColumn,sortDirection]
|
||||
const nextSort = sort && sort.length ? sort.filter((subArr) => subArr[0] !== columnName) : [];
|
||||
|
||||
let nextSettings = cleanColumnSettings(nextColumns, settings);
|
||||
|
||||
// When columns are removed, reset the last column to auto width if only absolute
|
||||
// width columns remain, to ensure the columns fill the available grid space
|
||||
if (nextColumns.length < (columns?.length ?? 0)) {
|
||||
nextSettings = adjustLastColumnWidth(nextColumns, nextSettings);
|
||||
}
|
||||
|
||||
setAppState({ columns: nextColumns, sort: nextSort, settings: nextSettings });
|
||||
}
|
||||
|
||||
function onMoveColumn(columnName: string, newIndex: number) {
|
||||
const nextColumns = moveColumn(columns || [], columnName, newIndex);
|
||||
setAppState({ columns: nextColumns, settings });
|
||||
}
|
||||
|
||||
function onSetColumns(nextColumns: string[], hideTimeColumn: boolean) {
|
||||
// The next line should be gone when classic table will be removed
|
||||
const actualColumns =
|
||||
!hideTimeColumn && dataView.timeFieldName && dataView.timeFieldName === nextColumns[0]
|
||||
? (nextColumns || []).slice(1)
|
||||
: nextColumns;
|
||||
|
||||
let nextSettings = cleanColumnSettings(nextColumns, settings);
|
||||
|
||||
// When columns are removed, reset the last column to auto width if only absolute
|
||||
// width columns remain, to ensure the columns fill the available grid space
|
||||
if (actualColumns.length < (columns?.length ?? 0)) {
|
||||
nextSettings = adjustLastColumnWidth(actualColumns, nextSettings);
|
||||
}
|
||||
|
||||
setAppState({ columns: actualColumns, settings: nextSettings });
|
||||
}
|
||||
return {
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
onMoveColumn,
|
||||
onSetColumns,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to provide a fallback to a single _source column if the given array of columns
|
||||
|
@ -25,14 +108,14 @@ function buildColumns(columns: string[], useNewFieldsApi = false) {
|
|||
return useNewFieldsApi ? [] : ['_source'];
|
||||
}
|
||||
|
||||
export function addColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) {
|
||||
function addColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) {
|
||||
if (columns.includes(columnName)) {
|
||||
return columns;
|
||||
}
|
||||
return buildColumns([...columns, columnName], useNewFieldsApi);
|
||||
}
|
||||
|
||||
export function removeColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) {
|
||||
function removeColumn(columns: string[], columnName: string, useNewFieldsApi?: boolean) {
|
||||
if (!columns.includes(columnName)) {
|
||||
return columns;
|
||||
}
|
||||
|
@ -42,7 +125,7 @@ export function removeColumn(columns: string[], columnName: string, useNewFields
|
|||
);
|
||||
}
|
||||
|
||||
export function moveColumn(columns: string[], columnName: string, newIndex: number) {
|
||||
function moveColumn(columns: string[], columnName: string, newIndex: number) {
|
||||
if (newIndex < 0 || newIndex >= columns.length || !columns.includes(columnName)) {
|
||||
return columns;
|
||||
}
|
||||
|
@ -52,58 +135,51 @@ export function moveColumn(columns: string[], columnName: string, newIndex: numb
|
|||
return modifiedColumns;
|
||||
}
|
||||
|
||||
export function getStateColumnActions({
|
||||
capabilities,
|
||||
dataView,
|
||||
dataViews,
|
||||
useNewFieldsApi,
|
||||
setAppState,
|
||||
columns,
|
||||
sort,
|
||||
defaultOrder,
|
||||
}: {
|
||||
capabilities: Capabilities;
|
||||
dataView: DataView;
|
||||
dataViews: DataViewsContract;
|
||||
useNewFieldsApi: boolean;
|
||||
setAppState: (state: { columns: string[]; sort?: string[][] }) => void;
|
||||
columns?: string[];
|
||||
sort: string[][] | undefined;
|
||||
defaultOrder: string;
|
||||
}) {
|
||||
function onAddColumn(columnName: string) {
|
||||
popularizeField(dataView, columnName, dataViews, capabilities);
|
||||
const nextColumns = addColumn(columns || [], columnName, useNewFieldsApi);
|
||||
const nextSort = columnName === '_score' && !sort?.length ? [['_score', defaultOrder]] : sort;
|
||||
setAppState({ columns: nextColumns, sort: nextSort });
|
||||
function cleanColumnSettings(
|
||||
columns: string[],
|
||||
settings?: UnifiedDataTableSettings
|
||||
): UnifiedDataTableSettings | undefined {
|
||||
const columnSettings = settings?.columns;
|
||||
|
||||
if (!columnSettings) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
function onRemoveColumn(columnName: string) {
|
||||
popularizeField(dataView, columnName, dataViews, capabilities);
|
||||
const nextColumns = removeColumn(columns || [], columnName, useNewFieldsApi);
|
||||
// The state's sort property is an array of [sortByColumn,sortDirection]
|
||||
const nextSort = sort && sort.length ? sort.filter((subArr) => subArr[0] !== columnName) : [];
|
||||
setAppState({ columns: nextColumns, sort: nextSort });
|
||||
const nextColumnSettings = columns.reduce<NonNullable<UnifiedDataTableSettings['columns']>>(
|
||||
(acc, column) => (columnSettings[column] ? { ...acc, [column]: columnSettings[column] } : acc),
|
||||
{}
|
||||
);
|
||||
|
||||
return { ...settings, columns: nextColumnSettings };
|
||||
}
|
||||
|
||||
function adjustLastColumnWidth(
|
||||
columns: string[],
|
||||
settings?: UnifiedDataTableSettings
|
||||
): UnifiedDataTableSettings | undefined {
|
||||
const columnSettings = settings?.columns;
|
||||
|
||||
if (!columns.length || !columnSettings) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
function onMoveColumn(columnName: string, newIndex: number) {
|
||||
const nextColumns = moveColumn(columns || [], columnName, newIndex);
|
||||
setAppState({ columns: nextColumns });
|
||||
const hasAutoWidthColumn = columns.some((colId) => columnSettings[colId]?.width == null);
|
||||
|
||||
if (hasAutoWidthColumn) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
function onSetColumns(nextColumns: string[], hideTimeColumn: boolean) {
|
||||
// The next line should be gone when classic table will be removed
|
||||
const actualColumns =
|
||||
!hideTimeColumn && dataView.timeFieldName && dataView.timeFieldName === nextColumns[0]
|
||||
? (nextColumns || []).slice(1)
|
||||
: nextColumns;
|
||||
const lastColumn = columns[columns.length - 1];
|
||||
const lastColumnSettings = omit(columnSettings[lastColumn] ?? {}, 'width');
|
||||
const lastColumnSettingsOptional = Object.keys(lastColumnSettings).length
|
||||
? { [lastColumn]: lastColumnSettings }
|
||||
: undefined;
|
||||
|
||||
setAppState({ columns: actualColumns });
|
||||
}
|
||||
return {
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
onMoveColumn,
|
||||
onSetColumns,
|
||||
...settings,
|
||||
columns: {
|
||||
...omit(columnSettings, lastColumn),
|
||||
...lastColumnSettingsOptional,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
import React from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import {
|
||||
EuiButton,
|
||||
|
@ -33,6 +33,10 @@ import { DatatableColumnType } from '@kbn/expressions-plugin/common';
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { CELL_CLASS } from '../utils/get_render_cell_value';
|
||||
import { defaultTimeColumnWidth } from '../constants';
|
||||
import { useColumns } from '../hooks/use_data_grid_columns';
|
||||
import { capabilitiesServiceMock } from '@kbn/core-capabilities-browser-mocks';
|
||||
import { dataViewsMock } from '../../__mocks__/data_views';
|
||||
|
||||
const mockUseDataGridColumnsCellActions = jest.fn((prop: unknown) => []);
|
||||
jest.mock('@kbn/cell-actions', () => ({
|
||||
|
@ -90,6 +94,56 @@ const DataTable = (props: Partial<UnifiedDataTableProps>) => (
|
|||
</KibanaContextProvider>
|
||||
);
|
||||
|
||||
const capabilities = capabilitiesServiceMock.createStartContract().capabilities;
|
||||
|
||||
const renderDataTable = (props: Partial<UnifiedDataTableProps>) => {
|
||||
const DataTableWrapped = () => {
|
||||
const [columns, setColumns] = useState(props.columns ?? []);
|
||||
const [settings, setSettings] = useState(props.settings);
|
||||
|
||||
const { onSetColumns } = useColumns({
|
||||
capabilities,
|
||||
dataView: dataViewMock,
|
||||
dataViews: dataViewsMock,
|
||||
setAppState: useCallback((state) => {
|
||||
if (state.columns) {
|
||||
setColumns(state.columns);
|
||||
}
|
||||
if (state.settings) {
|
||||
setSettings(state.settings);
|
||||
}
|
||||
}, []),
|
||||
useNewFieldsApi: true,
|
||||
columns,
|
||||
settings,
|
||||
});
|
||||
|
||||
return (
|
||||
<IntlProvider locale="en">
|
||||
<DataTable
|
||||
{...props}
|
||||
columns={columns}
|
||||
onSetColumns={onSetColumns}
|
||||
settings={settings}
|
||||
onResize={({ columnId, width }) => {
|
||||
setSettings({
|
||||
...settings,
|
||||
columns: {
|
||||
...settings?.columns,
|
||||
[columnId]: {
|
||||
width,
|
||||
},
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
render(<DataTableWrapped />);
|
||||
};
|
||||
|
||||
async function getComponent(props: UnifiedDataTableProps = getProps()) {
|
||||
const component = mountWithIntl(<DataTable {...props} />);
|
||||
await act(async () => {
|
||||
|
@ -265,34 +319,19 @@ describe('UnifiedDataTable', () => {
|
|||
|
||||
describe('edit field button', () => {
|
||||
it('should render the edit field button if onFieldEdited is provided', async () => {
|
||||
const component = await getComponent({
|
||||
...getProps(),
|
||||
columns: ['message'],
|
||||
onFieldEdited: jest.fn(),
|
||||
});
|
||||
expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe(
|
||||
false
|
||||
);
|
||||
findTestSubject(component, 'dataGridHeaderCell-message').find('button').simulate('click');
|
||||
expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe(
|
||||
true
|
||||
);
|
||||
expect(findTestSubject(component, 'gridEditFieldButton').exists()).toBe(true);
|
||||
renderDataTable({ columns: ['message'], onFieldEdited: jest.fn() });
|
||||
expect(screen.queryByTestId('dataGridHeaderCellActionGroup-message')).not.toBeInTheDocument();
|
||||
userEvent.click(screen.getByRole('button', { name: 'message' }));
|
||||
expect(screen.getByTestId('dataGridHeaderCellActionGroup-message')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('gridEditFieldButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the edit field button if onFieldEdited is not provided', async () => {
|
||||
const component = await getComponent({
|
||||
...getProps(),
|
||||
columns: ['message'],
|
||||
});
|
||||
expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe(
|
||||
false
|
||||
);
|
||||
findTestSubject(component, 'dataGridHeaderCell-message').find('button').simulate('click');
|
||||
expect(findTestSubject(component, 'dataGridHeaderCellActionGroup-message').exists()).toBe(
|
||||
true
|
||||
);
|
||||
expect(findTestSubject(component, 'gridEditFieldButton').exists()).toBe(false);
|
||||
renderDataTable({ columns: ['message'] });
|
||||
expect(screen.queryByTestId('dataGridHeaderCellActionGroup-message')).not.toBeInTheDocument();
|
||||
userEvent.click(screen.getByRole('button', { name: 'message' }));
|
||||
expect(screen.getByTestId('dataGridHeaderCellActionGroup-message')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('gridEditFieldButton')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -793,14 +832,6 @@ describe('UnifiedDataTable', () => {
|
|||
});
|
||||
|
||||
describe('document comparison', () => {
|
||||
const renderDataTable = (props: Partial<UnifiedDataTableProps>) => {
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<DataTable {...props} />
|
||||
</IntlProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const getSelectedDocumentsButton = () => screen.queryByTestId('unifiedDataTableSelectionBtn');
|
||||
|
||||
const selectDocument = (document: EsHitRecord) =>
|
||||
|
@ -920,4 +951,75 @@ describe('UnifiedDataTable', () => {
|
|||
expect(findTestSubject(component, 'dataGridHeaderCell-colorIndicator').exists()).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('columns', () => {
|
||||
// Default column width in EUI is hardcoded to 100px for Jest envs
|
||||
const EUI_DEFAULT_COLUMN_WIDTH = '100px';
|
||||
const getColumnHeader = (name: string) => screen.getByRole('columnheader', { name });
|
||||
const queryColumnHeader = (name: string) => screen.queryByRole('columnheader', { name });
|
||||
const getButton = (name: string) => screen.getByRole('button', { name });
|
||||
const queryButton = (name: string) => screen.queryByRole('button', { name });
|
||||
|
||||
it('should reset the last column to auto width if only absolute width columns remain', async () => {
|
||||
renderDataTable({
|
||||
columns: ['message', 'extension', 'bytes'],
|
||||
settings: {
|
||||
columns: {
|
||||
extension: { width: 50 },
|
||||
bytes: { width: 50 },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getColumnHeader('message')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
expect(getColumnHeader('extension')).toHaveStyle({ width: '50px' });
|
||||
expect(getColumnHeader('bytes')).toHaveStyle({ width: '50px' });
|
||||
userEvent.click(getButton('message'));
|
||||
userEvent.click(getButton('Remove column'), undefined, { skipPointerEventsCheck: true });
|
||||
expect(queryColumnHeader('message')).not.toBeInTheDocument();
|
||||
expect(getColumnHeader('extension')).toHaveStyle({ width: '50px' });
|
||||
expect(getColumnHeader('bytes')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
});
|
||||
|
||||
it('should not reset the last column to auto width when there are remaining auto width columns', async () => {
|
||||
renderDataTable({
|
||||
columns: ['message', 'extension', 'bytes'],
|
||||
settings: {
|
||||
columns: {
|
||||
bytes: { width: 50 },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getColumnHeader('message')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
expect(getColumnHeader('extension')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
expect(getColumnHeader('bytes')).toHaveStyle({ width: '50px' });
|
||||
userEvent.click(getButton('message'));
|
||||
userEvent.click(getButton('Remove column'), undefined, { skipPointerEventsCheck: true });
|
||||
expect(queryColumnHeader('message')).not.toBeInTheDocument();
|
||||
expect(getColumnHeader('extension')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
expect(getColumnHeader('bytes')).toHaveStyle({ width: '50px' });
|
||||
});
|
||||
|
||||
it('should show the reset width button only for absolute width columns, and allow resetting to default width', async () => {
|
||||
renderDataTable({
|
||||
columns: ['message', 'extension'],
|
||||
settings: {
|
||||
columns: {
|
||||
'@timestamp': { width: 50 },
|
||||
extension: { width: 50 },
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(getColumnHeader('@timestamp')).toHaveStyle({ width: '50px' });
|
||||
userEvent.click(getButton('@timestamp'));
|
||||
userEvent.click(getButton('Reset width'), undefined, { skipPointerEventsCheck: true });
|
||||
expect(getColumnHeader('@timestamp')).toHaveStyle({ width: `${defaultTimeColumnWidth}px` });
|
||||
expect(getColumnHeader('message')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
userEvent.click(getButton('message'));
|
||||
expect(queryButton('Reset width')).not.toBeInTheDocument();
|
||||
expect(getColumnHeader('extension')).toHaveStyle({ width: '50px' });
|
||||
userEvent.click(getButton('extension'));
|
||||
userEvent.click(getButton('Reset width'), undefined, { skipPointerEventsCheck: true });
|
||||
expect(getColumnHeader('extension')).toHaveStyle({ width: EUI_DEFAULT_COLUMN_WIDTH });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -164,9 +164,9 @@ export interface UnifiedDataTableProps {
|
|||
*/
|
||||
onFilter?: DocViewFilterFn;
|
||||
/**
|
||||
* Function triggered when a column is resized by the user
|
||||
* Function triggered when a column is resized by the user, passes `undefined` for auto-width
|
||||
*/
|
||||
onResize?: (colSettings: { columnId: string; width: number }) => void;
|
||||
onResize?: (colSettings: { columnId: string; width: number | undefined }) => void;
|
||||
/**
|
||||
* Function to set all columns
|
||||
*/
|
||||
|
@ -810,6 +810,7 @@ export const UnifiedDataTable = ({
|
|||
showColumnTokens,
|
||||
headerRowHeightLines,
|
||||
customGridColumnsConfiguration,
|
||||
onResize,
|
||||
}),
|
||||
[
|
||||
columnsMeta,
|
||||
|
@ -824,6 +825,7 @@ export const UnifiedDataTable = ({
|
|||
isPlainRecord,
|
||||
isSortEnabled,
|
||||
onFilter,
|
||||
onResize,
|
||||
settings,
|
||||
showColumnTokens,
|
||||
toastNotifications,
|
||||
|
|
|
@ -50,6 +50,7 @@ describe('Data table columns', function () {
|
|||
hasEditDataViewPermission: () =>
|
||||
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
|
||||
onFilter: () => {},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
@ -72,6 +73,7 @@ describe('Data table columns', function () {
|
|||
hasEditDataViewPermission: () =>
|
||||
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
|
||||
onFilter: () => {},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
@ -99,6 +101,7 @@ describe('Data table columns', function () {
|
|||
message: { type: 'string', esType: 'keyword' },
|
||||
timestamp: { type: 'date', esType: 'dateTime' },
|
||||
},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
@ -297,6 +300,7 @@ describe('Data table columns', function () {
|
|||
hasEditDataViewPermission: () =>
|
||||
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
|
||||
onFilter: () => {},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
@ -324,6 +328,7 @@ describe('Data table columns', function () {
|
|||
hasEditDataViewPermission: () =>
|
||||
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
|
||||
onFilter: () => {},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(actual).toMatchSnapshot();
|
||||
});
|
||||
|
@ -356,6 +361,7 @@ describe('Data table columns', function () {
|
|||
columnsMeta: {
|
||||
extension: { type: 'string' },
|
||||
},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(gridColumns[1].schema).toBe('string');
|
||||
});
|
||||
|
@ -386,6 +392,7 @@ describe('Data table columns', function () {
|
|||
columnsMeta: {
|
||||
var_test: { type: 'number' },
|
||||
},
|
||||
onResize: () => {},
|
||||
});
|
||||
expect(gridColumns[1].schema).toBe('numeric');
|
||||
});
|
||||
|
@ -412,6 +419,7 @@ describe('Data table columns', function () {
|
|||
extension: { type: 'string' },
|
||||
message: { type: 'string', esType: 'keyword' },
|
||||
},
|
||||
onResize: () => {},
|
||||
});
|
||||
|
||||
const extensionGridColumn = gridColumns[0];
|
||||
|
@ -442,6 +450,7 @@ describe('Data table columns', function () {
|
|||
extension: { type: 'string' },
|
||||
message: { type: 'string', esType: 'keyword' },
|
||||
},
|
||||
onResize: () => {},
|
||||
});
|
||||
|
||||
expect(customizedGridColumns).toMatchSnapshot();
|
||||
|
@ -484,6 +493,7 @@ describe('Data table columns', function () {
|
|||
},
|
||||
hasEditDataViewPermission: () =>
|
||||
servicesMock.dataViewFieldEditor.userPermissions.editIndexPattern(),
|
||||
onResize: () => {},
|
||||
});
|
||||
const columnDisplayNames = customizedGridColumns.map((column) => column.displayAsText);
|
||||
expect(columnDisplayNames.includes('test_column_one')).toBeTruthy();
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
type EuiDataGridColumn,
|
||||
type EuiDataGridColumnCellAction,
|
||||
EuiScreenReaderOnly,
|
||||
EuiListGroupItemProps,
|
||||
} from '@elastic/eui';
|
||||
import { type DataView, DataViewField } from '@kbn/data-views-plugin/public';
|
||||
import { ToastsStart, IUiSettingsClient } from '@kbn/core/public';
|
||||
|
@ -30,6 +31,7 @@ import {
|
|||
import { buildCopyColumnNameButton, buildCopyColumnValuesButton } from './build_copy_column_button';
|
||||
import { buildEditFieldButton } from './build_edit_field_button';
|
||||
import { DataTableColumnHeader, DataTableTimeColumnHeader } from './data_table_column_header';
|
||||
import { UnifiedDataTableProps } from './data_table';
|
||||
|
||||
export const getColumnDisplayName = (
|
||||
columnName: string,
|
||||
|
@ -105,6 +107,7 @@ function buildEuiGridColumn({
|
|||
headerRowHeight,
|
||||
customGridColumnsConfiguration,
|
||||
columnDisplay,
|
||||
onResize,
|
||||
}: {
|
||||
numberOfColumns: number;
|
||||
columnName: string;
|
||||
|
@ -126,6 +129,7 @@ function buildEuiGridColumn({
|
|||
headerRowHeight?: number;
|
||||
customGridColumnsConfiguration?: CustomGridColumnsConfiguration;
|
||||
columnDisplay?: string;
|
||||
onResize: UnifiedDataTableProps['onResize'];
|
||||
}) {
|
||||
const dataViewField = !isPlainRecord
|
||||
? dataView.getFieldByName(columnName)
|
||||
|
@ -142,6 +146,26 @@ function buildEuiGridColumn({
|
|||
editField &&
|
||||
dataViewField &&
|
||||
buildEditFieldButton({ hasEditDataViewPermission, dataView, field: dataViewField, editField });
|
||||
const resetWidthButton: EuiListGroupItemProps | undefined =
|
||||
onResize && columnWidth > 0
|
||||
? {
|
||||
// @ts-expect-error
|
||||
// We need to force a key here because EuiListGroup uses the array index as a key by default,
|
||||
// which causes re-render issues with conditional items like this one, and can result in
|
||||
// incorrect attributes (e.g. title) on the HTML element as well as test failures
|
||||
key: 'reset-width',
|
||||
label: i18n.translate('unifiedDataTable.grid.resetColumnWidthButton', {
|
||||
defaultMessage: 'Reset width',
|
||||
}),
|
||||
iconType: 'refresh',
|
||||
size: 'xs',
|
||||
iconProps: { size: 'm' },
|
||||
onClick: () => {
|
||||
onResize({ columnId: columnName, width: undefined });
|
||||
},
|
||||
'data-test-subj': 'unifiedDataTableResetColumnWidth',
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const columnDisplayName = getColumnDisplayName(
|
||||
columnName,
|
||||
|
@ -193,6 +217,7 @@ function buildEuiGridColumn({
|
|||
showMoveLeft: !defaultColumns,
|
||||
showMoveRight: !defaultColumns,
|
||||
additional: [
|
||||
...(resetWidthButton ? [resetWidthButton] : []),
|
||||
...(columnName === '__source'
|
||||
? []
|
||||
: [
|
||||
|
@ -268,6 +293,7 @@ export function getEuiGridColumns({
|
|||
showColumnTokens,
|
||||
headerRowHeightLines,
|
||||
customGridColumnsConfiguration,
|
||||
onResize,
|
||||
}: {
|
||||
columns: string[];
|
||||
columnsCellActions?: EuiDataGridColumnCellAction[][];
|
||||
|
@ -290,6 +316,7 @@ export function getEuiGridColumns({
|
|||
showColumnTokens?: boolean;
|
||||
headerRowHeightLines: number;
|
||||
customGridColumnsConfiguration?: CustomGridColumnsConfiguration;
|
||||
onResize: UnifiedDataTableProps['onResize'];
|
||||
}) {
|
||||
const getColWidth = (column: string) => settings?.columns?.[column]?.width ?? 0;
|
||||
const headerRowHeight = deserializeHeaderRowHeight(headerRowHeightLines);
|
||||
|
@ -317,6 +344,7 @@ export function getEuiGridColumns({
|
|||
headerRowHeight,
|
||||
customGridColumnsConfiguration,
|
||||
columnDisplay: settings?.columns?.[column]?.display,
|
||||
onResize,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,16 +12,22 @@ import type { DataView, DataViewsContract } from '@kbn/data-views-plugin/public'
|
|||
import { Capabilities } from '@kbn/core/public';
|
||||
import { isEqual } from 'lodash';
|
||||
import { getStateColumnActions } from '../components/actions/columns';
|
||||
import { UnifiedDataTableSettings } from '../types';
|
||||
|
||||
interface UseColumnsProps {
|
||||
export interface UseColumnsProps {
|
||||
capabilities: Capabilities;
|
||||
dataView: DataView;
|
||||
dataViews: DataViewsContract;
|
||||
useNewFieldsApi: boolean;
|
||||
setAppState: (state: { columns: string[]; sort?: string[][] }) => void;
|
||||
setAppState: (state: {
|
||||
columns: string[];
|
||||
sort?: string[][];
|
||||
settings?: UnifiedDataTableSettings;
|
||||
}) => void;
|
||||
columns?: string[];
|
||||
sort?: string[][];
|
||||
defaultOrder?: string;
|
||||
settings?: UnifiedDataTableSettings;
|
||||
}
|
||||
|
||||
export const useColumns = ({
|
||||
|
@ -33,6 +39,7 @@ export const useColumns = ({
|
|||
columns,
|
||||
sort,
|
||||
defaultOrder = 'desc',
|
||||
settings,
|
||||
}: UseColumnsProps) => {
|
||||
const [usedColumns, setUsedColumns] = useState(getColumns(columns, useNewFieldsApi));
|
||||
useEffect(() => {
|
||||
|
@ -53,6 +60,7 @@ export const useColumns = ({
|
|||
columns: usedColumns,
|
||||
sort,
|
||||
defaultOrder,
|
||||
settings,
|
||||
}),
|
||||
[
|
||||
capabilities,
|
||||
|
@ -60,6 +68,7 @@ export const useColumns = ({
|
|||
dataViews,
|
||||
defaultOrder,
|
||||
setAppState,
|
||||
settings,
|
||||
sort,
|
||||
useNewFieldsApi,
|
||||
usedColumns,
|
||||
|
|
|
@ -38,5 +38,6 @@
|
|||
"@kbn/shared-ux-utility",
|
||||
"@kbn/unified-field-list",
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-capabilities-browser-mocks",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -23,8 +23,9 @@ import {
|
|||
SEARCH_FIELDS_FROM_SOURCE,
|
||||
SORT_DEFAULT_ORDER_SETTING,
|
||||
} from '@kbn/discover-utils';
|
||||
import { popularizeField, useColumns } from '@kbn/unified-data-table';
|
||||
import { UseColumnsProps, popularizeField, useColumns } from '@kbn/unified-data-table';
|
||||
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import { ContextErrorMessage } from './components/context_error_message';
|
||||
import { LoadingStatus } from './services/context_query_state';
|
||||
import { AppState, GlobalState, isEqualFilters } from './services/context_state';
|
||||
|
@ -69,15 +70,23 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
const prevAppState = useRef<AppState>();
|
||||
const prevGlobalState = useRef<GlobalState>({ filters: [] });
|
||||
|
||||
const setAppState = useCallback<UseColumnsProps['setAppState']>(
|
||||
({ settings, ...rest }) => {
|
||||
stateContainer.setAppState({ ...rest, grid: settings as DiscoverGridSettings });
|
||||
},
|
||||
[stateContainer]
|
||||
);
|
||||
|
||||
const { columns, onAddColumn, onRemoveColumn, onSetColumns } = useColumns({
|
||||
capabilities,
|
||||
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
|
||||
dataView,
|
||||
dataViews,
|
||||
useNewFieldsApi,
|
||||
setAppState: stateContainer.setAppState,
|
||||
setAppState,
|
||||
columns: appState.columns,
|
||||
sort: appState.sort,
|
||||
settings: appState.grid,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -260,6 +269,7 @@ export const ContextApp = ({ dataView, anchorId, referrer }: ContextAppProps) =>
|
|||
useNewFieldsApi={useNewFieldsApi}
|
||||
isLegacy={isLegacy}
|
||||
columns={columns}
|
||||
grid={appState.grid}
|
||||
onAddColumn={onAddColumn}
|
||||
onRemoveColumn={onRemoveColumn}
|
||||
onSetColumns={onSetColumns}
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
ROW_HEIGHT_OPTION,
|
||||
SHOW_MULTIFIELDS,
|
||||
} from '@kbn/discover-utils';
|
||||
import { DataLoadingState } from '@kbn/unified-data-table';
|
||||
import { DataLoadingState, UnifiedDataTableProps } from '@kbn/unified-data-table';
|
||||
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
import { DiscoverGrid } from '../../components/discover_grid';
|
||||
import { getDefaultRowsPerPage } from '../../../common/constants';
|
||||
|
@ -43,6 +43,7 @@ import { onResizeGridColumn } from '../../utils/on_resize_grid_column';
|
|||
|
||||
export interface ContextAppContentProps {
|
||||
columns: string[];
|
||||
grid?: DiscoverGridSettings;
|
||||
onAddColumn: (columnsName: string) => void;
|
||||
onRemoveColumn: (columnsName: string) => void;
|
||||
onSetColumns: (columnsNames: string[], hideTimeColumn: boolean) => void;
|
||||
|
@ -74,6 +75,7 @@ const ActionBarMemoized = React.memo(ActionBar);
|
|||
|
||||
export function ContextAppContent({
|
||||
columns,
|
||||
grid,
|
||||
onAddColumn,
|
||||
onRemoveColumn,
|
||||
onSetColumns,
|
||||
|
@ -94,7 +96,6 @@ export function ContextAppContent({
|
|||
}: ContextAppContentProps) {
|
||||
const { uiSettings: config, uiActions } = useDiscoverServices();
|
||||
const services = useDiscoverServices();
|
||||
const [gridSettings, setGridSettings] = useState<DiscoverGridSettings>();
|
||||
|
||||
const [expandedDoc, setExpandedDoc] = useState<DataTableRecord | undefined>();
|
||||
const isAnchorLoading =
|
||||
|
@ -151,13 +152,11 @@ export function ContextAppContent({
|
|||
[addFilter, dataView, onAddColumn, onRemoveColumn]
|
||||
);
|
||||
|
||||
const onResize = useCallback(
|
||||
const onResize = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
|
||||
(colSettings) => {
|
||||
setGridSettings((currentGridSettings) =>
|
||||
onResizeGridColumn(colSettings, currentGridSettings)
|
||||
);
|
||||
setAppState({ grid: onResizeGridColumn(colSettings, grid) });
|
||||
},
|
||||
[setGridSettings]
|
||||
[grid, setAppState]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -221,7 +220,7 @@ export function ContextAppContent({
|
|||
renderDocumentView={renderDocumentView}
|
||||
services={services}
|
||||
configHeaderRowHeight={3}
|
||||
settings={gridSettings}
|
||||
settings={grid}
|
||||
onResize={onResize}
|
||||
/>
|
||||
</CellActionsProvider>
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
|
||||
import { connectToQueryState, DataPublicPluginStart, FilterManager } from '@kbn/data-plugin/public';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import { getValidFilters } from '../../../utils/get_valid_filters';
|
||||
import { handleSourceColumnState } from '../../../utils/state_helpers';
|
||||
|
||||
|
@ -32,6 +33,10 @@ export interface AppState {
|
|||
* Array of filters
|
||||
*/
|
||||
filters: Filter[];
|
||||
/**
|
||||
* Data Grid related state
|
||||
*/
|
||||
grid?: DiscoverGridSettings;
|
||||
/**
|
||||
* Number of records to be fetched before anchor records (newer records)
|
||||
*/
|
||||
|
|
|
@ -28,6 +28,8 @@ import {
|
|||
getTextBasedColumnsMeta,
|
||||
getRenderCustomToolbarWithElements,
|
||||
type DataGridDensity,
|
||||
UnifiedDataTableProps,
|
||||
UseColumnsProps,
|
||||
} from '@kbn/unified-data-table';
|
||||
import {
|
||||
DOC_HIDE_TIME_COLUMN_SETTING,
|
||||
|
@ -41,6 +43,7 @@ import {
|
|||
} from '@kbn/discover-utils';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import type { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import { DiscoverGrid } from '../../../../components/discover_grid';
|
||||
import { getDefaultRowsPerPage } from '../../../../../common/constants';
|
||||
import { useInternalStateSelector } from '../../state_management/discover_internal_state_container';
|
||||
|
@ -86,7 +89,7 @@ const DiscoverGridMemoized = React.memo(DiscoverGrid);
|
|||
|
||||
// export needs for testing
|
||||
export const onResize = (
|
||||
colSettings: { columnId: string; width: number },
|
||||
colSettings: { columnId: string; width: number | undefined },
|
||||
stateContainer: DiscoverStateContainer
|
||||
) => {
|
||||
const state = stateContainer.appState.getState();
|
||||
|
@ -166,6 +169,13 @@ function DiscoverDocumentsComponent({
|
|||
stateContainer,
|
||||
});
|
||||
|
||||
const setAppState = useCallback<UseColumnsProps['setAppState']>(
|
||||
({ settings, ...rest }) => {
|
||||
stateContainer.appState.update({ ...rest, grid: settings as DiscoverGridSettings });
|
||||
},
|
||||
[stateContainer]
|
||||
);
|
||||
|
||||
const {
|
||||
columns: currentColumns,
|
||||
onAddColumn,
|
||||
|
@ -177,10 +187,11 @@ function DiscoverDocumentsComponent({
|
|||
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
|
||||
dataView,
|
||||
dataViews,
|
||||
setAppState: stateContainer.appState.update,
|
||||
setAppState,
|
||||
useNewFieldsApi,
|
||||
columns,
|
||||
sort,
|
||||
settings: grid,
|
||||
});
|
||||
|
||||
const setExpandedDoc = useCallback(
|
||||
|
@ -190,7 +201,7 @@ function DiscoverDocumentsComponent({
|
|||
[stateContainer]
|
||||
);
|
||||
|
||||
const onResizeDataGrid = useCallback(
|
||||
const onResizeDataGrid = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
|
||||
(colSettings) => onResize(colSettings, stateContainer),
|
||||
[stateContainer]
|
||||
);
|
||||
|
|
|
@ -30,9 +30,10 @@ import {
|
|||
SHOW_FIELD_STATISTICS,
|
||||
SORT_DEFAULT_ORDER_SETTING,
|
||||
} from '@kbn/discover-utils';
|
||||
import { popularizeField, useColumns } from '@kbn/unified-data-table';
|
||||
import { UseColumnsProps, popularizeField, useColumns } from '@kbn/unified-data-table';
|
||||
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import { useSavedSearchInitial } from '../../state_management/discover_state_provider';
|
||||
import { DiscoverStateContainer } from '../../state_management/discover_state';
|
||||
import { VIEW_MODE } from '../../../../../common/constants';
|
||||
|
@ -80,11 +81,12 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
const pageBackgroundColor = useEuiBackgroundColor('plain');
|
||||
const globalQueryState = data.query.getState();
|
||||
const { main$ } = stateContainer.dataState.data$;
|
||||
const [query, savedQuery, columns, sort] = useAppStateSelector((state) => [
|
||||
const [query, savedQuery, columns, sort, grid] = useAppStateSelector((state) => [
|
||||
state.query,
|
||||
state.savedQuery,
|
||||
state.columns,
|
||||
state.sort,
|
||||
state.grid,
|
||||
]);
|
||||
const isEsqlMode = useIsEsqlMode();
|
||||
|
||||
|
@ -126,6 +128,13 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
[dataState.fetchStatus, dataState.foundDocuments]
|
||||
);
|
||||
|
||||
const setAppState = useCallback<UseColumnsProps['setAppState']>(
|
||||
({ settings, ...rest }) => {
|
||||
stateContainer.appState.update({ ...rest, grid: settings as DiscoverGridSettings });
|
||||
},
|
||||
[stateContainer]
|
||||
);
|
||||
|
||||
const {
|
||||
columns: currentColumns,
|
||||
onAddColumn,
|
||||
|
@ -135,10 +144,11 @@ export function DiscoverLayout({ stateContainer }: DiscoverLayoutProps) {
|
|||
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
|
||||
dataView,
|
||||
dataViews,
|
||||
setAppState: stateContainer.appState.update,
|
||||
setAppState,
|
||||
useNewFieldsApi,
|
||||
columns,
|
||||
sort,
|
||||
settings: grid,
|
||||
});
|
||||
|
||||
// The assistant is getting the state from the url correctly
|
||||
|
|
|
@ -53,10 +53,13 @@ describe('getDefaultProfileState', () => {
|
|||
},
|
||||
defaultColumns: ['messsage', 'bytes'],
|
||||
dataView: emptyDataView,
|
||||
esqlQueryColumns: [{ id: '1', name: 'foo', meta: { type: 'string' } }],
|
||||
esqlQueryColumns: [
|
||||
{ id: '1', name: 'foo', meta: { type: 'string' } },
|
||||
{ id: '2', name: 'bar', meta: { type: 'string' } },
|
||||
],
|
||||
});
|
||||
expect(appState).toEqual({
|
||||
columns: ['foo'],
|
||||
columns: ['foo', 'bar'],
|
||||
grid: {
|
||||
columns: {
|
||||
foo: {
|
||||
|
|
|
@ -43,8 +43,13 @@ export const getDefaultProfileState = ({
|
|||
);
|
||||
|
||||
if (validColumns?.length) {
|
||||
const hasAutoWidthColumn = validColumns.some(({ width }) => !width);
|
||||
const columns = validColumns.reduce<DiscoverGridSettings['columns']>(
|
||||
(acc, { name, width }) => (width ? { ...acc, [name]: { width } } : acc),
|
||||
(acc, { name, width }, index) => {
|
||||
// Ensure there's at least one auto width column so the columns fill the grid
|
||||
const skipColumnWidth = !hasAutoWidthColumn && index === validColumns.length - 1;
|
||||
return width && !skipColumnWidth ? { ...acc, [name]: { width } } : acc;
|
||||
},
|
||||
undefined
|
||||
);
|
||||
|
||||
|
|
|
@ -63,6 +63,10 @@ export const createContextAwarenessMocks = ({
|
|||
name: 'foo',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
name: 'bar',
|
||||
width: 400,
|
||||
},
|
||||
],
|
||||
rowHeight: 3,
|
||||
})),
|
||||
|
|
|
@ -13,6 +13,7 @@ import type { DataView } from '@kbn/data-views-plugin/common';
|
|||
import {
|
||||
DOC_HIDE_TIME_COLUMN_SETTING,
|
||||
SEARCH_FIELDS_FROM_SOURCE,
|
||||
SORT_DEFAULT_ORDER_SETTING,
|
||||
isLegacyTableEnabled,
|
||||
} from '@kbn/discover-utils';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
|
@ -22,9 +23,10 @@ import {
|
|||
} from '@kbn/presentation-publishing';
|
||||
import { SortOrder } from '@kbn/saved-search-plugin/public';
|
||||
import { SearchResponseIncompleteWarning } from '@kbn/search-response-warnings/src/types';
|
||||
import { columnActions, DataGridDensity, DataLoadingState } from '@kbn/unified-data-table';
|
||||
import { DataGridDensity, DataLoadingState, useColumns } from '@kbn/unified-data-table';
|
||||
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
|
||||
|
||||
import { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
import { DiscoverDocTableEmbeddable } from '../../components/doc_table/create_doc_table_embeddable';
|
||||
import { useDiscoverServices } from '../../hooks/use_discover_services';
|
||||
import { getSortForEmbeddable } from '../../utils';
|
||||
|
@ -34,6 +36,7 @@ import { isEsqlMode } from '../initialize_fetch';
|
|||
import type { SearchEmbeddableApi, SearchEmbeddableStateManager } from '../types';
|
||||
import { DiscoverGridEmbeddable } from './saved_search_grid';
|
||||
import { getSearchEmbeddableDefaults } from '../get_search_embeddable_defaults';
|
||||
import { onResizeGridColumn } from '../../utils/on_resize_grid_column';
|
||||
|
||||
interface SavedSearchEmbeddableComponentProps {
|
||||
api: SearchEmbeddableApi & { fetchWarnings$: BehaviorSubject<SearchResponseIncompleteWarning[]> };
|
||||
|
@ -60,6 +63,7 @@ export function SearchEmbeddableGridComponent({
|
|||
rows,
|
||||
totalHitCount,
|
||||
columnsMeta,
|
||||
grid,
|
||||
] = useBatchedPublishingSubjects(
|
||||
api.dataLoading,
|
||||
api.savedSearch$,
|
||||
|
@ -67,7 +71,8 @@ export function SearchEmbeddableGridComponent({
|
|||
api.fetchWarnings$,
|
||||
stateManager.rows,
|
||||
stateManager.totalHitCount,
|
||||
stateManager.columnsMeta
|
||||
stateManager.columnsMeta,
|
||||
stateManager.grid
|
||||
);
|
||||
|
||||
const [panelTitle, panelDescription, savedSearchTitle, savedSearchDescription] =
|
||||
|
@ -92,32 +97,37 @@ export function SearchEmbeddableGridComponent({
|
|||
return getSortForEmbeddable(savedSearch.sort, dataView, discoverServices.uiSettings, isEsql);
|
||||
}, [savedSearch.sort, dataView, isEsql, discoverServices.uiSettings]);
|
||||
|
||||
const originalColumns = useMemo(() => savedSearch.columns ?? [], [savedSearch.columns]);
|
||||
const useNewFieldsApi = !discoverServices.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false);
|
||||
|
||||
const { columns, onAddColumn, onRemoveColumn, onMoveColumn, onSetColumns } = useColumns({
|
||||
capabilities: discoverServices.capabilities,
|
||||
defaultOrder: discoverServices.uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
|
||||
dataView,
|
||||
dataViews: discoverServices.dataViews,
|
||||
setAppState: (params) => {
|
||||
if (params.columns) {
|
||||
stateManager.columns.next(params.columns);
|
||||
}
|
||||
if (params.sort) {
|
||||
stateManager.sort.next(params.sort as SortOrder[]);
|
||||
}
|
||||
if (params.settings) {
|
||||
stateManager.grid.next(params.settings as DiscoverGridSettings);
|
||||
}
|
||||
},
|
||||
useNewFieldsApi,
|
||||
columns: originalColumns,
|
||||
sort,
|
||||
settings: grid,
|
||||
});
|
||||
|
||||
const onStateEditedProps = useMemo(
|
||||
() => ({
|
||||
onAddColumn: (columnName: string) => {
|
||||
if (!savedSearch.columns) {
|
||||
return;
|
||||
}
|
||||
const updatedColumns = columnActions.addColumn(savedSearch.columns, columnName, true);
|
||||
stateManager.columns.next(updatedColumns);
|
||||
},
|
||||
onSetColumns: (updatedColumns: string[]) => {
|
||||
stateManager.columns.next(updatedColumns);
|
||||
},
|
||||
onMoveColumn: (columnName: string, newIndex: number) => {
|
||||
if (!savedSearch.columns) {
|
||||
return;
|
||||
}
|
||||
const updatedColumns = columnActions.moveColumn(savedSearch.columns, columnName, newIndex);
|
||||
stateManager.columns.next(updatedColumns);
|
||||
},
|
||||
onRemoveColumn: (columnName: string) => {
|
||||
if (!savedSearch.columns) {
|
||||
return;
|
||||
}
|
||||
const updatedColumns = columnActions.removeColumn(savedSearch.columns, columnName, true);
|
||||
stateManager.columns.next(updatedColumns);
|
||||
},
|
||||
onAddColumn,
|
||||
onSetColumns,
|
||||
onMoveColumn,
|
||||
onRemoveColumn,
|
||||
onUpdateRowsPerPage: (newRowsPerPage: number | undefined) => {
|
||||
stateManager.rowsPerPage.next(newRowsPerPage);
|
||||
},
|
||||
|
@ -140,8 +150,24 @@ export function SearchEmbeddableGridComponent({
|
|||
onUpdateDataGridDensity: (newDensity: DataGridDensity | undefined) => {
|
||||
stateManager.density.next(newDensity);
|
||||
},
|
||||
onResize: (newGridSettings: { columnId: string; width: number | undefined }) => {
|
||||
stateManager.grid.next(onResizeGridColumn(newGridSettings, grid));
|
||||
},
|
||||
}),
|
||||
[stateManager, savedSearch.columns]
|
||||
[
|
||||
onAddColumn,
|
||||
onSetColumns,
|
||||
onMoveColumn,
|
||||
onRemoveColumn,
|
||||
stateManager.rowsPerPage,
|
||||
stateManager.rowHeight,
|
||||
stateManager.headerRowHeight,
|
||||
stateManager.sort,
|
||||
stateManager.sampleSize,
|
||||
stateManager.density,
|
||||
stateManager.grid,
|
||||
grid,
|
||||
]
|
||||
);
|
||||
|
||||
const fetchedSampleSize = useMemo(() => {
|
||||
|
@ -151,7 +177,7 @@ export function SearchEmbeddableGridComponent({
|
|||
const defaults = getSearchEmbeddableDefaults(discoverServices.uiSettings);
|
||||
|
||||
const sharedProps = {
|
||||
columns: savedSearch.columns ?? [],
|
||||
columns,
|
||||
dataView,
|
||||
interceptedWarnings,
|
||||
onFilter: onAddFilter,
|
||||
|
@ -161,7 +187,7 @@ export function SearchEmbeddableGridComponent({
|
|||
searchDescription: panelDescription || savedSearchDescription,
|
||||
sort,
|
||||
totalHitCount,
|
||||
useNewFieldsApi: !discoverServices.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false),
|
||||
useNewFieldsApi,
|
||||
};
|
||||
|
||||
if (useLegacyTable) {
|
||||
|
|
|
@ -32,6 +32,7 @@ export const EDITABLE_SAVED_SEARCH_KEYS: Readonly<Array<keyof SavedSearchAttribu
|
|||
'rowsPerPage',
|
||||
'headerRowHeight',
|
||||
'density',
|
||||
'grid',
|
||||
] as const;
|
||||
|
||||
/** This constant refers to the dashboard panel specific state */
|
||||
|
|
|
@ -171,6 +171,7 @@ export const initializeSearchEmbeddableApi = async (
|
|||
comparators: {
|
||||
sort: [sort$, (value) => sort$.next(value), (a, b) => deepEqual(a, b)],
|
||||
columns: [columns$, (value) => columns$.next(value), (a, b) => deepEqual(a, b)],
|
||||
grid: [grid$, (value) => grid$.next(value), (a, b) => deepEqual(a, b)],
|
||||
sampleSize: [
|
||||
sampleSize$,
|
||||
(value) => sampleSize$.next(value),
|
||||
|
@ -198,7 +199,6 @@ export const initializeSearchEmbeddableApi = async (
|
|||
(value) => serializedSearchSource$.next(value),
|
||||
],
|
||||
viewMode: [savedSearchViewMode$, (value) => savedSearchViewMode$.next(value)],
|
||||
grid: [grid$, (value) => grid$.next(value)],
|
||||
density: [density$, (value) => density$.next(value)],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
import type { DiscoverGridSettings } from '@kbn/saved-search-plugin/common';
|
||||
|
||||
export const onResizeGridColumn = (
|
||||
colSettings: { columnId: string; width: number },
|
||||
colSettings: { columnId: string; width: number | undefined },
|
||||
gridState: DiscoverGridSettings | undefined
|
||||
): DiscoverGridSettings => {
|
||||
const grid = { ...(gridState || {}) };
|
||||
const newColumns = { ...(grid.columns || {}) };
|
||||
newColumns[colSettings.columnId] = {
|
||||
width: Math.round(colSettings.width),
|
||||
};
|
||||
newColumns[colSettings.columnId] = colSettings.width
|
||||
? { width: Math.round(colSettings.width) }
|
||||
: {};
|
||||
return { ...grid, columns: newColumns };
|
||||
};
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export default function ({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const browser = getService('browser');
|
||||
const esArchiver = getService('esArchiver');
|
||||
const kibanaServer = getService('kibanaServer');
|
||||
const dataGrid = getService('dataGrid');
|
||||
const dashboardAddPanel = getService('dashboardAddPanel');
|
||||
const PageObjects = getPageObjects([
|
||||
'common',
|
||||
'discover',
|
||||
'header',
|
||||
'timePicker',
|
||||
'dashboard',
|
||||
'unifiedFieldList',
|
||||
]);
|
||||
const security = getService('security');
|
||||
|
||||
const testResizeColumn = async (field: string) => {
|
||||
const { originalWidth, newWidth } = await dataGrid.resizeColumn(field, -100);
|
||||
expect(newWidth).to.be(originalWidth - 100);
|
||||
await dataGrid.clickResetColumnWidth(field);
|
||||
const resetWidth = (await (await dataGrid.getHeaderElement(field)).getSize()).width;
|
||||
expect(resetWidth).to.be(originalWidth);
|
||||
};
|
||||
|
||||
describe('discover data grid column widths', function describeIndexTests() {
|
||||
before(async () => {
|
||||
await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader']);
|
||||
await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional');
|
||||
await kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/discover');
|
||||
await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/discover');
|
||||
await kibanaServer.uiSettings.replace({});
|
||||
await kibanaServer.savedObjects.cleanStandardList();
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
await PageObjects.common.navigateToApp('discover');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await PageObjects.discover.waitUntilSearchingHasFinished();
|
||||
});
|
||||
|
||||
it('should not show reset width button for auto width column', async () => {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@message');
|
||||
expect(await dataGrid.resetColumnWidthExists('@message')).to.be(false);
|
||||
});
|
||||
|
||||
it('should show reset width button for absolute width column, and allow resetting to auto width', async () => {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@message');
|
||||
await testResizeColumn('@message');
|
||||
});
|
||||
|
||||
it('should reset the last column to auto width if only absolute width columns remain', async () => {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@message');
|
||||
const { originalWidth: messageOriginalWidth, newWidth: messageNewWidth } =
|
||||
await dataGrid.resizeColumn('@message', -300);
|
||||
expect(messageNewWidth).to.be(messageOriginalWidth - 300);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('bytes');
|
||||
const { originalWidth: bytesOriginalWidth, newWidth: bytesNewWidth } =
|
||||
await dataGrid.resizeColumn('bytes', -100);
|
||||
expect(bytesNewWidth).to.be(bytesOriginalWidth - 100);
|
||||
let messageWidth = (await (await dataGrid.getHeaderElement('@message')).getSize()).width;
|
||||
expect(messageWidth).to.be(messageNewWidth);
|
||||
await dataGrid.clickRemoveColumn('bytes');
|
||||
messageWidth = (await (await dataGrid.getHeaderElement('@message')).getSize()).width;
|
||||
expect(messageWidth).to.be(messageOriginalWidth);
|
||||
});
|
||||
|
||||
it('should not reset the last column to auto width when there are remaining auto width columns', async () => {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@message');
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('bytes');
|
||||
const { originalWidth: bytesOriginalWidth, newWidth: bytesNewWidth } =
|
||||
await dataGrid.resizeColumn('bytes', -200);
|
||||
expect(bytesNewWidth).to.be(bytesOriginalWidth - 200);
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('agent');
|
||||
const { originalWidth: agentOriginalWidth, newWidth: agentNewWidth } =
|
||||
await dataGrid.resizeColumn('agent', -100);
|
||||
expect(agentNewWidth).to.be(agentOriginalWidth - 100);
|
||||
await dataGrid.clickRemoveColumn('bytes');
|
||||
const agentWidth = (await (await dataGrid.getHeaderElement('agent')).getSize()).width;
|
||||
expect(agentWidth).to.be(agentNewWidth);
|
||||
});
|
||||
|
||||
it('should allow resetting column width in surrounding docs view', async () => {
|
||||
await PageObjects.unifiedFieldList.clickFieldListItemAdd('@message');
|
||||
await dataGrid.clickRowToggle({ rowIndex: 0 });
|
||||
const [, surroundingActionEl] = await dataGrid.getRowActions({ rowIndex: 0 });
|
||||
await surroundingActionEl.click();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testResizeColumn('@message');
|
||||
});
|
||||
|
||||
it('should allow resetting column width in Dashboard panel', async () => {
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await dashboardAddPanel.clickOpenAddPanel();
|
||||
await dashboardAddPanel.addSavedSearch('A Saved Search');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
await testResizeColumn('_source');
|
||||
});
|
||||
|
||||
it('should use custom column width on Dashboard when specified', async () => {
|
||||
await PageObjects.common.navigateToApp('dashboard');
|
||||
await PageObjects.dashboard.clickNewDashboard();
|
||||
await dashboardAddPanel.clickOpenAddPanel();
|
||||
await dashboardAddPanel.addSavedSearch('A Saved Search');
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const { originalWidth, newWidth } = await dataGrid.resizeColumn('_source', -100);
|
||||
expect(newWidth).to.be(originalWidth - 100);
|
||||
await PageObjects.dashboard.saveDashboard('test');
|
||||
await browser.refresh();
|
||||
await PageObjects.header.waitUntilLoadingHasFinished();
|
||||
const initialWidth = (await (await dataGrid.getHeaderElement('_source')).getSize()).width;
|
||||
expect(initialWidth).to.be(newWidth);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -26,5 +26,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./_data_grid_sample_size'));
|
||||
loadTestFile(require.resolve('./_data_grid_pagination'));
|
||||
loadTestFile(require.resolve('./_data_grid_density'));
|
||||
loadTestFile(require.resolve('./_data_grid_column_widths'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ export class DataGridService extends FtrService {
|
|||
private readonly find = this.ctx.getService('find');
|
||||
private readonly testSubjects = this.ctx.getService('testSubjects');
|
||||
private readonly retry = this.ctx.getService('retry');
|
||||
private readonly browser = this.ctx.getService('browser');
|
||||
|
||||
async getDataGridTableData(): Promise<TabbedGridData> {
|
||||
const table = await this.find.byCssSelector('.euiDataGrid');
|
||||
|
@ -82,6 +83,20 @@ export class DataGridService extends FtrService {
|
|||
.map((cell) => $(cell).text());
|
||||
}
|
||||
|
||||
public getHeaderElement(field: string) {
|
||||
return this.testSubjects.find(`dataGridHeaderCell-${field}`);
|
||||
}
|
||||
|
||||
public async resizeColumn(field: string, delta: number) {
|
||||
const header = await this.getHeaderElement(field);
|
||||
const originalWidth = (await header.getSize()).width;
|
||||
const resizer = await header.findByCssSelector(
|
||||
this.testSubjects.getCssSelector('dataGridColumnResizer')
|
||||
);
|
||||
await this.browser.dragAndDrop({ location: resizer }, { location: { x: delta, y: 0 } });
|
||||
return { originalWidth, newWidth: (await header.getSize()).width };
|
||||
}
|
||||
|
||||
private getCellElementSelector(rowIndex: number = 0, columnIndex: number = 0) {
|
||||
return `[data-test-subj="euiDataGridBody"] [data-test-subj="dataGridRowCell"][data-gridcell-column-index="${columnIndex}"][data-gridcell-visible-row-index="${rowIndex}"]`;
|
||||
}
|
||||
|
@ -465,6 +480,16 @@ export class DataGridService extends FtrService {
|
|||
await this.testSubjects.click('gridEditFieldButton');
|
||||
}
|
||||
|
||||
public async resetColumnWidthExists(field: string) {
|
||||
await this.openColMenuByField(field);
|
||||
return await this.testSubjects.exists('unifiedDataTableResetColumnWidth');
|
||||
}
|
||||
|
||||
public async clickResetColumnWidth(field: string) {
|
||||
await this.openColMenuByField(field);
|
||||
await this.testSubjects.click('unifiedDataTableResetColumnWidth');
|
||||
}
|
||||
|
||||
public async clickGridSettings() {
|
||||
await this.testSubjects.click('dataGridDisplaySelectorButton');
|
||||
}
|
||||
|
|
|
@ -258,12 +258,12 @@ export const CloudSecurityDataTable = ({
|
|||
[dataView, filterManager, setUrlQuery]
|
||||
);
|
||||
|
||||
const onResize = (colSettings: { columnId: string; width: number }) => {
|
||||
const onResize = (colSettings: { columnId: string; width: number | undefined }) => {
|
||||
const grid = persistedSettings || {};
|
||||
const newColumns = { ...(grid.columns || {}) };
|
||||
newColumns[colSettings.columnId] = {
|
||||
width: Math.round(colSettings.width),
|
||||
};
|
||||
newColumns[colSettings.columnId] = colSettings.width
|
||||
? { width: Math.round(colSettings.width) }
|
||||
: {};
|
||||
const newGrid = { ...grid, columns: newColumns };
|
||||
setPersistedSettings(newGrid);
|
||||
};
|
||||
|
|
|
@ -186,7 +186,7 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
|
|||
);
|
||||
|
||||
const onColumnResize = useCallback(
|
||||
({ columnId, width }: { columnId: string; width: number }) => {
|
||||
({ columnId, width }: { columnId: string; width?: number }) => {
|
||||
dispatch(
|
||||
timelineActions.updateColumnWidth({
|
||||
columnId,
|
||||
|
@ -198,9 +198,12 @@ export const TimelineDataTableComponent: React.FC<DataTableProps> = memo(
|
|||
[dispatch, timelineId]
|
||||
);
|
||||
|
||||
const onResizeDataGrid = useCallback(
|
||||
const onResizeDataGrid = useCallback<NonNullable<UnifiedDataTableProps['onResize']>>(
|
||||
(colSettings) => {
|
||||
onColumnResize({ columnId: colSettings.columnId, width: Math.round(colSettings.width) });
|
||||
onColumnResize({
|
||||
columnId: colSettings.columnId,
|
||||
...(colSettings.width ? { width: Math.round(colSettings.width) } : {}),
|
||||
});
|
||||
},
|
||||
[onColumnResize]
|
||||
);
|
||||
|
|
|
@ -291,7 +291,7 @@ export const setChanged = actionCreator<{ id: string; changed: boolean }>('SET_C
|
|||
export const updateColumnWidth = actionCreator<{
|
||||
columnId: string;
|
||||
id: string;
|
||||
width: number;
|
||||
width?: number;
|
||||
}>('UPDATE_COLUMN_WIDTH');
|
||||
|
||||
export const updateRowHeight = actionCreator<{
|
||||
|
|
|
@ -1531,7 +1531,7 @@ export const updateTimelineColumnWidth = ({
|
|||
columnId: string;
|
||||
id: string;
|
||||
timelineById: TimelineById;
|
||||
width: number;
|
||||
width?: number;
|
||||
}): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue