mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Restores Alerts table local storage persistence and the Remove Column action (#114742)
## [Security Solution] Restores Alerts table local storage persistence and the Remove Column action This PR implements the following changes summarized below to address <https://github.com/elastic/kibana/issues/113090>, as proposed [here](https://github.com/elastic/kibana/issues/113090#issuecomment-935143690): - Configures the `Columns` popover to be consistent with `Discover` - Changes the `Hide column` action to `Remove column`, to be consistent with `Discover` - Persists updates to the `Columns` popover order in `local storage` - Restores the feature to persist column widths in `local storage` ### Configures the `Columns` popover to be consistent with `Discover` - We now pass `false` to the `allowHide` [EuiDataGrid API](https://elastic.github.io/eui/#/tabular-content/data-grid):  This makes all `EuiDataGrid`-based views in the Security Solution consistent with `Discover`'s use of the `EuiDataGrid` `Columns` popover. In `7.15`, the `Columns` popover includes the _hide column_ toggle, as shown in the screenshot below:  _Above: The `Columns` popover in the `7.15` `Alerts` table_ The `Columns` popover in `Discover`'s `EuiDataGrid`-based table does not display the hide column toggle, as shown the screenshot below:  _Above: The `EuiDataGrid` `Columns` popover in `Discover`, in `master`_ Passing `false` to the `allowHide` [EuiDataGrid API](https://elastic.github.io/eui/#/tabular-content/data-grid) API makes the `Columns` popover in all `EuiDataGrid`-based views in the Security Solution consistent with `Discover`, as illustrated by the screenshot below:  _Above: The `Columns` popover is now consistent with `Discover`_ ## Changes the `Hide column` action to `Remove column`, to be consistent with `Discover` - The `Hide column` action shown in the `7.15` alerts table is changed to `Remove column`, making it consistent with `Discover`'s use of `EuiDataGrid` In `7.15`, the `Alerts` table has a `Hide column` action, as shown in the screenshot below:  _Above: The `Hide Column` action in the `7.15` `Alerts` table_ In `7.15`, clicking the `Hide Column` action shown in the screenshot above hides the column, but does not remove it. In `7.15`, columns may only be removed by un-checking them in the `Fields` browser, or by un-toggling them in the Alerts / Events details popover. Both of those methods require multiple clicks, and require uses to re-find the field in the modal or popover before it may be toggled for removal. In `Discover`, users don't hide columns. In `Discover`, users directly remove columns by clicking the `Remove column` action, shown in the screenshot below:  _Above: The `Remove column` action in `Discover`'s use of `EuiDataGrid` in `master`_ All `EuiDataGrid`-based views in the Security Solution were made consistent with `Discover` by replacing the `Hide column` action with `Remove column`, per the screenshot below:  _Above: The `Remove column` action in the Alerts table_ Note: the `Remove column` action shown above appears as the last item in the popover because it's specified via the `EuiDataGrid` `EuiDataGridColumnActions` > `additonal` API, which appends additonal actions to the end of popover, after the built-in actions:  ## Persists updates to the `Columns` popover order in `local storage` - Persist column order updates to `local storage` when users update the order of columns via the `Columns` popover The following PR <https://github.com/elastic/kibana/pull/110685> restored partial support for persisting columns across page refreshes via `local storage`, but the Redux store was not updated when users sort columns via the `Columns` popover, an shown in the animated gif below:  _Above: Ordering via the `Columns` popover is not persisted to `local storage` in `7.15`_ This PR utilizes the `setVisibleColumns` [EuiDataGrid API](https://elastic.github.io/eui/#/tabular-content/data-grid) API as a callback to update Redux when the columns are sorted, which will in-turn update `local storage` to persist the new order across page refreshes:  ## Restores the feature to persist column widths in `local storage` In previous releases, resized column widths were peristed in `local storage` to persist across page refreshes, as documented in <https://github.com/elastic/kibana/issues/110524> : ``` { "detections-page":{ "id":"detections-page", "activeTab":"query", "prevActiveTab":"query", "columns":[ { "category":"base", "columnHeaderType":"not-filtered", "description":"Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events.", "example":"2016-05-23T08:05:34.853Z", "id":"@timestamp", "type":"date", "aggregatable":true, "width":190 }, { "category":"cloud", "columnHeaderType":"not-filtered", "description":"The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.", "example":"666777888999", "id":"cloud.account.id", "type":"string", "aggregatable":true, "width":180 }, { "category":"cloud", "columnHeaderType":"not-filtered", "description":"Availability zone in which this host is running.", "example":"us-east-1c", "id":"cloud.availability_zone", "type":"string", "aggregatable":true, "width":180 }, // ... } ], // ... } } ``` _Above: column widths were persisted to `local storage` in previous release, (going at least back to `7.12`)_ In this PR, we utilize the `onColumnResize` [EuiDataGrid API](https://elastic.github.io/eui/#/tabular-content/data-grid) API as a callback to update Redux when the columns are sorted via the `Columns` popover. Updating Redux will in-turn update `local storage`, so resized columns widths will persist across page refreshes:  ### Other changes The Alerts page `Trend` chart and table were updated to include the following additional `Stack by` fields (CC @paulewing): ``` process.name file.name hash.sha256 ``` per the before / after screenshots below:  _Above: The Alerts `Trend` Stack by fields in `7.15` (before)_  _Above: The Alerts `Trend` `Stack by` fields (after the addition of the `process.name`, `file.name`, and `hash.sha256` fields)_ CC: @monina-n @paulewing
This commit is contained in:
parent
d98bf0c245
commit
16320cc249
17 changed files with 352 additions and 19 deletions
|
@ -19,6 +19,9 @@ export const alertsStackByOptions: AlertsStackByOption[] = [
|
|||
{ text: 'signal.rule.name', value: 'signal.rule.name' },
|
||||
{ text: 'source.ip', value: 'source.ip' },
|
||||
{ text: 'user.name', value: 'user.name' },
|
||||
{ text: 'process.name', value: 'process.name' },
|
||||
{ text: 'file.name', value: 'file.name' },
|
||||
{ text: 'hash.sha256', value: 'hash.sha256' },
|
||||
];
|
||||
|
||||
export const DEFAULT_STACK_BY_FIELD = 'signal.rule.name';
|
||||
|
|
|
@ -21,4 +21,7 @@ export type AlertsStackByField =
|
|||
| 'signal.rule.type'
|
||||
| 'signal.rule.name'
|
||||
| 'source.ip'
|
||||
| 'user.name';
|
||||
| 'user.name'
|
||||
| 'process.name'
|
||||
| 'file.name'
|
||||
| 'hash.sha256';
|
||||
|
|
|
@ -37,7 +37,9 @@ export const {
|
|||
setSelected,
|
||||
setTGridSelectAll,
|
||||
toggleDetailPanel,
|
||||
updateColumnOrder,
|
||||
updateColumns,
|
||||
updateColumnWidth,
|
||||
updateIsLoading,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
|
|
|
@ -25,7 +25,9 @@ import {
|
|||
removeColumn,
|
||||
upsertColumn,
|
||||
applyDeltaToColumnWidth,
|
||||
updateColumnOrder,
|
||||
updateColumns,
|
||||
updateColumnWidth,
|
||||
updateItemsPerPage,
|
||||
updateSort,
|
||||
} from './actions';
|
||||
|
@ -168,4 +170,35 @@ describe('epicLocalStorage', () => {
|
|||
);
|
||||
await waitFor(() => expect(addTimelineInStorageMock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('persists updates to the column order to local storage', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<QueryTabContentComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(
|
||||
updateColumnOrder({
|
||||
columnIds: ['event.severity', '@timestamp', 'event.category'],
|
||||
id: 'test',
|
||||
})
|
||||
);
|
||||
await waitFor(() => expect(addTimelineInStorageMock).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('persists updates to the column width to local storage', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<QueryTabContentComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(
|
||||
updateColumnWidth({
|
||||
columnId: 'event.severity',
|
||||
id: 'test',
|
||||
width: 123,
|
||||
})
|
||||
);
|
||||
await waitFor(() => expect(addTimelineInStorageMock).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
|
|
|
@ -19,6 +19,8 @@ import {
|
|||
applyDeltaToColumnWidth,
|
||||
setExcludedRowRendererIds,
|
||||
updateColumns,
|
||||
updateColumnOrder,
|
||||
updateColumnWidth,
|
||||
updateItemsPerPage,
|
||||
updateSort,
|
||||
} from './actions';
|
||||
|
@ -30,6 +32,8 @@ const timelineActionTypes = [
|
|||
upsertColumn.type,
|
||||
applyDeltaToColumnWidth.type,
|
||||
updateColumns.type,
|
||||
updateColumnOrder.type,
|
||||
updateColumnWidth.type,
|
||||
updateItemsPerPage.type,
|
||||
updateSort.type,
|
||||
setExcludedRowRendererIds.type,
|
||||
|
|
|
@ -161,8 +161,8 @@ const ColumnHeaderComponent: React.FC<ColumneHeaderProps> = ({
|
|||
id: 0,
|
||||
items: [
|
||||
{
|
||||
icon: <EuiIcon type="eyeClosed" size="s" />,
|
||||
name: i18n.HIDE_COLUMN,
|
||||
icon: <EuiIcon type="cross" size="s" />,
|
||||
name: i18n.REMOVE_COLUMN,
|
||||
onClick: () => {
|
||||
dispatch(tGridActions.removeColumn({ id: timelineId, columnId: header.id }));
|
||||
handleClosePopOverTrigger();
|
||||
|
|
|
@ -98,6 +98,7 @@ describe('helpers', () => {
|
|||
describe('getColumnHeaders', () => {
|
||||
// additional properties used by `EuiDataGrid`:
|
||||
const actions = {
|
||||
showHide: false,
|
||||
showSortAsc: true,
|
||||
showSortDesc: true,
|
||||
};
|
||||
|
|
|
@ -27,6 +27,7 @@ import { allowSorting } from '../helpers';
|
|||
const defaultActions: EuiDataGridColumnActions = {
|
||||
showSortAsc: true,
|
||||
showSortDesc: true,
|
||||
showHide: false,
|
||||
};
|
||||
|
||||
const getAllBrowserFields = (browserFields: BrowserFields): Array<Partial<BrowserField>> =>
|
||||
|
|
|
@ -23,10 +23,6 @@ export const FULL_SCREEN = i18n.translate('xpack.timelines.timeline.fullScreenBu
|
|||
defaultMessage: 'Full screen',
|
||||
});
|
||||
|
||||
export const HIDE_COLUMN = i18n.translate('xpack.timelines.timeline.hideColumnLabel', {
|
||||
defaultMessage: 'Hide column',
|
||||
});
|
||||
|
||||
export const SORT_AZ = i18n.translate('xpack.timelines.timeline.sortAZLabel', {
|
||||
defaultMessage: 'Sort A-Z',
|
||||
});
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
import { BodyComponent, StatefulBodyProps } from '.';
|
||||
import { Sort } from './sort';
|
||||
import { REMOVE_COLUMN } from './column_headers/translations';
|
||||
import { Direction } from '../../../../common/search_strategy';
|
||||
import { useMountAppended } from '../../utils/use_mount_appended';
|
||||
import { defaultHeaders, mockBrowserFields, mockTimelineData, TestProviders } from '../../../mock';
|
||||
|
@ -273,4 +275,57 @@ describe('Body', () => {
|
|||
.find((c) => c.id === 'signal.rule.risk_score')?.cellActions
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('it does NOT render switches for hiding columns in the `EuiDataGrid` `Columns` popover', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<BodyComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
// Click the `EuidDataGrid` `Columns` button to open the popover:
|
||||
fireEvent.click(screen.getByTestId('dataGridColumnSelectorButton'));
|
||||
|
||||
// `EuiDataGrid` renders switches for hiding in the `Columns` popover when `showColumnSelector.allowHide` is `true`
|
||||
const switches = await screen.queryAllByRole('switch');
|
||||
|
||||
expect(switches.length).toBe(0); // no switches are rendered, because `allowHide` is `false`
|
||||
});
|
||||
|
||||
test('it dispatches the `REMOVE_COLUMN` action when a user clicks `Remove column` in the column header popover', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<BodyComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
// click the `@timestamp` column header to display the popover
|
||||
fireEvent.click(screen.getByText('@timestamp'));
|
||||
|
||||
// click the `Remove column` action in the popover
|
||||
fireEvent.click(await screen.getByText(REMOVE_COLUMN));
|
||||
|
||||
expect(mockDispatch).toBeCalledWith({
|
||||
payload: { columnId: '@timestamp', id: 'timeline-test' },
|
||||
type: 'x-pack/timelines/t-grid/REMOVE_COLUMN',
|
||||
});
|
||||
});
|
||||
|
||||
test('it dispatches the `UPDATE_COLUMN_WIDTH` action when a user resizes a column', async () => {
|
||||
render(
|
||||
<TestProviders>
|
||||
<BodyComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
// simulate resizing the column
|
||||
fireEvent.mouseDown(screen.getAllByTestId('dataGridColumnResizer')[0]);
|
||||
fireEvent.mouseMove(screen.getAllByTestId('dataGridColumnResizer')[0]);
|
||||
fireEvent.mouseUp(screen.getAllByTestId('dataGridColumnResizer')[0]);
|
||||
|
||||
expect(mockDispatch).toBeCalledWith({
|
||||
payload: { columnId: '@timestamp', id: 'timeline-test', width: NaN },
|
||||
type: 'x-pack/timelines/t-grid/UPDATE_COLUMN_WIDTH',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -75,6 +75,7 @@ import { ViewSelection } from '../event_rendered_view/selector';
|
|||
import { EventRenderedView } from '../event_rendered_view';
|
||||
import { useDataGridHeightHack } from './height_hack';
|
||||
import { Filter } from '../../../../../../../src/plugins/data/public';
|
||||
import { REMOVE_COLUMN } from './column_headers/translations';
|
||||
|
||||
const StatefulAlertStatusBulkActions = lazy(
|
||||
() => import('../toolbar/bulk_actions/alert_status_bulk_actions')
|
||||
|
@ -497,7 +498,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
showFullScreenSelector: false,
|
||||
}
|
||||
: {
|
||||
showColumnSelector: { allowHide: true, allowReorder: true },
|
||||
showColumnSelector: { allowHide: false, allowReorder: true },
|
||||
showSortSelector: true,
|
||||
showFullScreenSelector: true,
|
||||
}),
|
||||
|
@ -559,13 +560,32 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
[columnHeaders, dispatch, id, loadPage]
|
||||
);
|
||||
|
||||
const [visibleColumns, setVisibleColumns] = useState(() =>
|
||||
columnHeaders.map(({ id: cid }) => cid)
|
||||
); // initializes to the full set of columns
|
||||
const visibleColumns = useMemo(() => columnHeaders.map(({ id: cid }) => cid), [columnHeaders]); // the full set of columns
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleColumns(columnHeaders.map(({ id: cid }) => cid));
|
||||
}, [columnHeaders]);
|
||||
const onColumnResize = useCallback(
|
||||
({ columnId, width }: { columnId: string; width: number }) => {
|
||||
dispatch(
|
||||
tGridActions.updateColumnWidth({
|
||||
columnId,
|
||||
id,
|
||||
width,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const onSetVisibleColumns = useCallback(
|
||||
(newVisibleColumns: string[]) => {
|
||||
dispatch(
|
||||
tGridActions.updateColumnOrder({
|
||||
columnIds: newVisibleColumns,
|
||||
id,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, id]
|
||||
);
|
||||
|
||||
const setEventsLoading = useCallback<SetEventsLoading>(
|
||||
({ eventIds, isLoading: loading }) => {
|
||||
|
@ -654,6 +674,19 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
|
||||
return {
|
||||
...header,
|
||||
actions: {
|
||||
...header.actions,
|
||||
additional: [
|
||||
{
|
||||
iconType: 'cross',
|
||||
label: REMOVE_COLUMN,
|
||||
onClick: () => {
|
||||
dispatch(tGridActions.removeColumn({ id, columnId: header.id }));
|
||||
},
|
||||
size: 'xs',
|
||||
},
|
||||
],
|
||||
},
|
||||
...(hasCellActions(header.id)
|
||||
? {
|
||||
cellActions:
|
||||
|
@ -663,7 +696,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
: {}),
|
||||
};
|
||||
}),
|
||||
[columnHeaders, defaultCellActions, browserFields, data, pageSize, id]
|
||||
[columnHeaders, defaultCellActions, browserFields, data, pageSize, id, dispatch]
|
||||
);
|
||||
|
||||
const renderTGridCellValue = useMemo(() => {
|
||||
|
@ -761,7 +794,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
data-test-subj="body-data-grid"
|
||||
aria-label={i18n.TGRID_BODY_ARIA_LABEL}
|
||||
columns={columnsWithCellActions}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns }}
|
||||
columnVisibility={{ visibleColumns, setVisibleColumns: onSetVisibleColumns }}
|
||||
gridStyle={gridStyle}
|
||||
leadingControlColumns={leadingTGridControlColumns}
|
||||
trailingControlColumns={trailingTGridControlColumns}
|
||||
|
@ -769,6 +802,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
rowCount={totalItems}
|
||||
renderCellValue={renderTGridCellValue}
|
||||
sorting={{ columns: sortingColumns, onSort }}
|
||||
onColumnResize={onColumnResize}
|
||||
pagination={{
|
||||
pageIndex: activePage,
|
||||
pageSize,
|
||||
|
|
|
@ -32,6 +32,17 @@ export const applyDeltaToColumnWidth = actionCreator<{
|
|||
delta: number;
|
||||
}>('APPLY_DELTA_TO_COLUMN_WIDTH');
|
||||
|
||||
export const updateColumnOrder = actionCreator<{
|
||||
columnIds: string[];
|
||||
id: string;
|
||||
}>('UPDATE_COLUMN_ORDER');
|
||||
|
||||
export const updateColumnWidth = actionCreator<{
|
||||
columnId: string;
|
||||
id: string;
|
||||
width: number;
|
||||
}>('UPDATE_COLUMN_WIDTH');
|
||||
|
||||
export type ToggleDetailPanel = TimelineExpandedDetailType & {
|
||||
tabType?: TimelineTabs;
|
||||
timelineId: string;
|
||||
|
|
|
@ -7,7 +7,11 @@
|
|||
|
||||
import { SortColumnTimeline } from '../../../common';
|
||||
import { tGridDefaults } from './defaults';
|
||||
import { setInitializeTgridSettings } from './helpers';
|
||||
import {
|
||||
setInitializeTgridSettings,
|
||||
updateTGridColumnOrder,
|
||||
updateTGridColumnWidth,
|
||||
} from './helpers';
|
||||
import { mockGlobalState } from '../../mock/global_state';
|
||||
|
||||
import { TGridModelSettings } from '.';
|
||||
|
@ -57,3 +61,112 @@ describe('setInitializeTgridSettings', () => {
|
|||
expect(result).toBe(timelineById);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTGridColumnOrder', () => {
|
||||
test('it returns the columns in the new expected order', () => {
|
||||
const originalIdOrder = defaultTimelineById.test.columns.map((x) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...']
|
||||
|
||||
// the new order swaps the positions of the first and second columns:
|
||||
const newIdOrder = [originalIdOrder[1], originalIdOrder[0], ...originalIdOrder.slice(2)]; // ['event.severity', '@timestamp', 'event.category', '...']
|
||||
|
||||
expect(
|
||||
updateTGridColumnOrder({
|
||||
columnIds: newIdOrder,
|
||||
id: 'test',
|
||||
timelineById: defaultTimelineById,
|
||||
})
|
||||
).toEqual({
|
||||
...defaultTimelineById,
|
||||
test: {
|
||||
...defaultTimelineById.test,
|
||||
columns: [
|
||||
defaultTimelineById.test.columns[1], // event.severity
|
||||
defaultTimelineById.test.columns[0], // @timestamp
|
||||
...defaultTimelineById.test.columns.slice(2), // all remaining columns
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it omits unknown column IDs when re-ordering columns', () => {
|
||||
const originalIdOrder = defaultTimelineById.test.columns.map((x) => x.id); // ['@timestamp', 'event.severity', 'event.category', '...']
|
||||
const unknownColumId = 'does.not.exist';
|
||||
const newIdOrder = [originalIdOrder[0], unknownColumId, ...originalIdOrder.slice(1)]; // ['@timestamp', 'does.not.exist', 'event.severity', 'event.category', '...']
|
||||
|
||||
expect(
|
||||
updateTGridColumnOrder({
|
||||
columnIds: newIdOrder,
|
||||
id: 'test',
|
||||
timelineById: defaultTimelineById,
|
||||
})
|
||||
).toEqual({
|
||||
...defaultTimelineById,
|
||||
test: {
|
||||
...defaultTimelineById.test,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns an empty collection of columns if none of the new column IDs are found', () => {
|
||||
const newIdOrder = ['this.id.does.NOT.exist', 'this.id.also.does.NOT.exist']; // all unknown IDs
|
||||
|
||||
expect(
|
||||
updateTGridColumnOrder({
|
||||
columnIds: newIdOrder,
|
||||
id: 'test',
|
||||
timelineById: defaultTimelineById,
|
||||
})
|
||||
).toEqual({
|
||||
...defaultTimelineById,
|
||||
test: {
|
||||
...defaultTimelineById.test,
|
||||
columns: [], // <-- empty, because none of the new column IDs match the old IDs
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateTGridColumnWidth', () => {
|
||||
test("it updates (only) the specified column's width", () => {
|
||||
const columnId = '@timestamp';
|
||||
const width = 1234;
|
||||
|
||||
const expectedUpdatedColumn = {
|
||||
...defaultTimelineById.test.columns[0], // @timestamp
|
||||
initialWidth: width,
|
||||
};
|
||||
|
||||
expect(
|
||||
updateTGridColumnWidth({
|
||||
columnId,
|
||||
id: 'test',
|
||||
timelineById: defaultTimelineById,
|
||||
width,
|
||||
})
|
||||
).toEqual({
|
||||
...defaultTimelineById,
|
||||
test: {
|
||||
...defaultTimelineById.test,
|
||||
columns: [expectedUpdatedColumn, ...defaultTimelineById.test.columns.slice(1)],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it is a noop if the the specified column is unknown', () => {
|
||||
const unknownColumId = 'does.not.exist';
|
||||
|
||||
expect(
|
||||
updateTGridColumnWidth({
|
||||
columnId: unknownColumId,
|
||||
id: 'test',
|
||||
timelineById: defaultTimelineById,
|
||||
width: 90210,
|
||||
})
|
||||
).toEqual({
|
||||
...defaultTimelineById,
|
||||
test: {
|
||||
...defaultTimelineById.test,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { omit, union } from 'lodash/fp';
|
||||
|
||||
import { isEmpty } from 'lodash';
|
||||
import { EuiDataGridColumn } from '@elastic/eui';
|
||||
import type { ToggleDetailPanel } from './actions';
|
||||
import { TGridPersistInput, TimelineById, TimelineId } from './types';
|
||||
import type { TGridModel, TGridModelSettings } from './model';
|
||||
|
@ -232,6 +233,63 @@ export const applyDeltaToTimelineColumnWidth = ({
|
|||
};
|
||||
};
|
||||
|
||||
type Columns = Array<
|
||||
Pick<EuiDataGridColumn, 'display' | 'displayAsText' | 'id' | 'initialWidth'> & ColumnHeaderOptions
|
||||
>;
|
||||
|
||||
export const updateTGridColumnOrder = ({
|
||||
columnIds,
|
||||
id,
|
||||
timelineById,
|
||||
}: {
|
||||
columnIds: string[];
|
||||
id: string;
|
||||
timelineById: TimelineById;
|
||||
}): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
const columns = columnIds.reduce<Columns>((acc, cid) => {
|
||||
const columnIndex = timeline.columns.findIndex((c) => c.id === cid);
|
||||
|
||||
return columnIndex !== -1 ? [...acc, timeline.columns[columnIndex]] : acc;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
columns,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTGridColumnWidth = ({
|
||||
columnId,
|
||||
id,
|
||||
timelineById,
|
||||
width,
|
||||
}: {
|
||||
columnId: string;
|
||||
id: string;
|
||||
timelineById: TimelineById;
|
||||
width: number;
|
||||
}): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
const columns = timeline.columns.map((x) => ({
|
||||
...x,
|
||||
initialWidth: x.id === columnId ? width : x.initialWidth,
|
||||
}));
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
columns,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateTimelineColumnsParams {
|
||||
id: string;
|
||||
columns: ColumnHeaderOptions[];
|
||||
|
|
|
@ -23,7 +23,9 @@ import {
|
|||
setSelected,
|
||||
setTimelineUpdatedAt,
|
||||
toggleDetailPanel,
|
||||
updateColumnOrder,
|
||||
updateColumns,
|
||||
updateColumnWidth,
|
||||
updateIsLoading,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
|
@ -40,6 +42,8 @@ import {
|
|||
setDeletedTimelineEvents,
|
||||
setLoadingTimelineEvents,
|
||||
setSelectedTimelineEvents,
|
||||
updateTGridColumnOrder,
|
||||
updateTGridColumnWidth,
|
||||
updateTimelineColumns,
|
||||
updateTimelineItemsPerPage,
|
||||
updateTimelinePerPageOptions,
|
||||
|
@ -91,6 +95,23 @@ export const tGridReducer = reducerWithInitialState(initialTGridState)
|
|||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(updateColumnOrder, (state, { id, columnIds }) => ({
|
||||
...state,
|
||||
timelineById: updateTGridColumnOrder({
|
||||
columnIds,
|
||||
id,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(updateColumnWidth, (state, { id, columnId, width }) => ({
|
||||
...state,
|
||||
timelineById: updateTGridColumnWidth({
|
||||
columnId,
|
||||
id,
|
||||
timelineById: state.timelineById,
|
||||
width,
|
||||
}),
|
||||
}))
|
||||
.case(removeColumn, (state, { id, columnId }) => ({
|
||||
...state,
|
||||
timelineById: removeTimelineColumn({
|
||||
|
|
|
@ -24458,7 +24458,6 @@
|
|||
"xpack.timelines.timeline.fieldTooltip": "フィールド",
|
||||
"xpack.timelines.timeline.flyout.pane.removeColumnButtonLabel": "列を削除",
|
||||
"xpack.timelines.timeline.fullScreenButton": "全画面",
|
||||
"xpack.timelines.timeline.hideColumnLabel": "列を非表示",
|
||||
"xpack.timelines.timeline.openedAlertFailedToastMessage": "アラートを開けませんでした",
|
||||
"xpack.timelines.timeline.openSelectedTitle": "選択した項目を開く",
|
||||
"xpack.timelines.timeline.properties.timelineToggleButtonAriaLabel": "タイムライン {title} を{isOpen, select, false {開く} true {閉じる} other {切り替える}}",
|
||||
|
|
|
@ -24874,7 +24874,6 @@
|
|||
"xpack.timelines.timeline.fieldTooltip": "字段",
|
||||
"xpack.timelines.timeline.flyout.pane.removeColumnButtonLabel": "移除列",
|
||||
"xpack.timelines.timeline.fullScreenButton": "全屏",
|
||||
"xpack.timelines.timeline.hideColumnLabel": "隐藏列",
|
||||
"xpack.timelines.timeline.openedAlertFailedToastMessage": "无法打开告警",
|
||||
"xpack.timelines.timeline.openedAlertSuccessToastMessage": "已成功打开 {totalAlerts} 个{totalAlerts, plural, other {告警}}。",
|
||||
"xpack.timelines.timeline.openSelectedTitle": "打开所选",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue