Skip to content

Commit 01d2b96

Browse files
committed
Update alert table columns after adding a new runtime field
1 parent 30bf355 commit 01d2b96

File tree

6 files changed

+73
-30
lines changed

6 files changed

+73
-30
lines changed

x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jest.mock('../../../timelines/containers', () => ({
3232

3333
jest.mock('../../components/url_state/normalize_time_range.ts');
3434

35+
jest.mock('../../../common/containers/source', () => ({ useIndexFields: () => ({}) }));
36+
3537
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
3638
jest.mock('use-resize-observer/polyfilled');
3739
mockUseResizeObserver.mockImplementation(() => ({}));

x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { ControlColumnProps, RowRenderer, TimelineId } from '../../../../common/
1616
import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
1717
import type { SubsetTimelineModel, TimelineModel } from '../../../timelines/store/timeline/model';
1818
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
19-
import { Filter } from '../../../../../../../src/plugins/data/public';
19+
import { Filter, IndexPatternField } from '../../../../../../../src/plugins/data/public';
2020
import { InspectButtonContainer } from '../inspect';
2121
import { useGlobalFullScreen } from '../../containers/use_full_screen';
2222
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
@@ -31,8 +31,11 @@ import { defaultControlColumn } from '../../../timelines/components/timeline/bod
3131
import { EventsViewer } from './events_viewer';
3232
import * as i18n from './translations';
3333
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
34-
import { CreateFieldButton } from '../../../timelines/components/create_field';
35-
import { CreateFieldComponentType } from '../../../../../timelines/public';
34+
import { CreateFieldButton } from '../../../timelines/components/create_field_button';
35+
import { CreateFieldComponentType, tGridActions } from '../../../../../timelines/public';
36+
import { useIndexFields } from '../../containers/source';
37+
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
38+
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
3639

3740
const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
3841
const leadingControlColumns: ControlColumnProps[] = [
@@ -175,18 +178,37 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
175178
}, [id, timelineQuery, globalQuery]);
176179
const bulkActions = useMemo(() => ({ onAlertStatusActionSuccess }), [onAlertStatusActionSuccess]);
177180

181+
const { indexFieldsSearch } = useIndexFields(scopeId, false);
182+
178183
const createFieldComponent = useMemo(() => {
184+
const onCreateField = (field: IndexPatternField) => {
185+
// Fetch updated list of fields
186+
indexFieldsSearch(selectedDataViewId);
187+
// Add the new field to the event table
188+
dispatch(
189+
tGridActions.upsertColumn({
190+
column: {
191+
columnHeaderType: defaultColumnHeaderType,
192+
id: field.name,
193+
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
194+
},
195+
id,
196+
index: 1,
197+
})
198+
);
199+
};
200+
179201
// It receives onClick props from field browser in order to close the modal.
180202
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
181203
<CreateFieldButton
182204
selectedDataViewId={selectedDataViewId}
183205
onClick={onClick}
184-
scopeId={scopeId}
206+
onCreateField={onCreateField}
185207
/>
186208
);
187209

188210
return CreateFieldButtonComponent;
189-
}, [selectedDataViewId, scopeId]);
211+
}, [selectedDataViewId, dispatch, id, indexFieldsSearch]);
190212

191213
return (
192214
<>

x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.test.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import { render, fireEvent, act, screen } from '@testing-library/react';
99
import React from 'react';
10-
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
1110
import { CreateFieldButton } from './index';
1211
import {
1312
indexPatternFieldEditorPluginMock,
@@ -23,8 +22,6 @@ const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
2322
let mockIndexPatternFieldEditor: Start;
2423
jest.mock('../../../common/lib/kibana');
2524

26-
jest.mock('../../../common/containers/source', () => ({ useIndexFields: () => ({}) }));
27-
2825
const runAllPromises = () => new Promise(setImmediate);
2926

3027
describe('CreateFieldButton', () => {
@@ -41,9 +38,11 @@ describe('CreateFieldButton', () => {
4138
<CreateFieldButton
4239
selectedDataViewId={'dataViewId'}
4340
onClick={() => undefined}
44-
scopeId={SourcererScopeName.timeline}
41+
onCreateField={() => undefined}
4542
/>,
46-
{ wrapper: TestProviders }
43+
{
44+
wrapper: TestProviders,
45+
}
4746
);
4847

4948
expect(screen.getByRole('button')).toBeInTheDocument();
@@ -55,9 +54,11 @@ describe('CreateFieldButton', () => {
5554
<CreateFieldButton
5655
selectedDataViewId={'dataViewId'}
5756
onClick={() => undefined}
58-
scopeId={SourcererScopeName.timeline}
57+
onCreateField={() => undefined}
5958
/>,
60-
{ wrapper: TestProviders }
59+
{
60+
wrapper: TestProviders,
61+
}
6162
);
6263

6364
expect(screen.queryByRole('button')).not.toBeInTheDocument();
@@ -73,9 +74,11 @@ describe('CreateFieldButton', () => {
7374
<CreateFieldButton
7475
selectedDataViewId={'dataViewId'}
7576
onClick={onClickParam}
76-
scopeId={SourcererScopeName.timeline}
77+
onCreateField={() => undefined}
7778
/>,
78-
{ wrapper: TestProviders }
79+
{
80+
wrapper: TestProviders,
81+
}
7982
);
8083
await runAllPromises();
8184
});

x-pack/plugins/security_solution/public/timelines/components/create_field_button/index.tsx

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,22 @@ import React, { useCallback, useEffect, useState } from 'react';
99
import { EuiButton } from '@elastic/eui';
1010
import styled from 'styled-components';
1111

12-
import { IndexPattern } from '../../../../../../../src/plugins/data/public';
12+
import { IndexPattern, IndexPatternField } from '../../../../../../../src/plugins/data/public';
1313
import { useKibana } from '../../../common/lib/kibana';
1414

1515
import * as i18n from './translations';
16-
import { useIndexFields } from '../../../common/containers/source';
17-
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
1816

1917
interface CreateFieldButtonProps {
2018
selectedDataViewId: string;
2119
onClick: () => void;
22-
scopeId: SourcererScopeName;
20+
onCreateField: (field: IndexPatternField) => void;
2321
}
2422
const StyledButton = styled(EuiButton)`
2523
margin-left: ${({ theme }) => theme.eui.paddingSizes.m};
2624
`;
2725

2826
export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
29-
({ selectedDataViewId, onClick: onClickParam, scopeId }) => {
30-
const { indexFieldsSearch } = useIndexFields(scopeId, false);
31-
27+
({ selectedDataViewId, onClick: onClickParam, onCreateField }) => {
3228
const {
3329
indexPatternFieldEditor,
3430
data: { dataViews },
@@ -46,13 +42,11 @@ export const CreateFieldButton = React.memo<CreateFieldButtonProps>(
4642
if (dataView) {
4743
indexPatternFieldEditor?.openEditor({
4844
ctx: { indexPattern: dataView },
49-
onSave: () => {
50-
indexFieldsSearch(selectedDataViewId);
51-
},
45+
onSave: onCreateField,
5246
});
5347
}
5448
onClickParam();
55-
}, [indexPatternFieldEditor, dataView, selectedDataViewId, onClickParam, indexFieldsSearch]);
49+
}, [indexPatternFieldEditor, dataView, onClickParam, onCreateField]);
5650

5751
if (!indexPatternFieldEditor?.userPermissions.editIndexPattern()) {
5852
return null;

x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { defaultRowRenderers } from './renderers';
2727

2828
jest.mock('../../../../common/lib/kibana/hooks');
2929
jest.mock('../../../../common/hooks/use_app_toasts');
30-
30+
jest.mock('../../../../common/containers/source', () => ({ useIndexFields: () => ({}) }));
3131
jest.mock('../../../../common/lib/kibana', () => {
3232
const originalModule = jest.requireActual('../../../../common/lib/kibana');
3333
return {

x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { noop } from 'lodash/fp';
99
import memoizeOne from 'memoize-one';
1010
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
11-
import { connect, ConnectedProps } from 'react-redux';
11+
import { connect, ConnectedProps, useDispatch } from 'react-redux';
1212
import deepEqual from 'fast-deep-equal';
1313

1414
import {
@@ -17,6 +17,7 @@ import {
1717
ARIA_ROWINDEX_ATTRIBUTE,
1818
onKeyDownFocusHandler,
1919
CreateFieldComponentType,
20+
tGridActions,
2021
} from '../../../../../../timelines/public';
2122
import { CellValueElementProps } from '../cell_rendering';
2223
import { DEFAULT_COLUMN_MIN_WIDTH } from './constants';
@@ -28,7 +29,7 @@ import {
2829
TimelineId,
2930
TimelineTabs,
3031
} from '../../../../../common/types/timeline';
31-
import { BrowserFields } from '../../../../common/containers/source';
32+
import { BrowserFields, useIndexFields } from '../../../../common/containers/source';
3233
import { TimelineItem } from '../../../../../common/search_strategy/timeline';
3334
import { inputsModel, sourcererSelectors, State } from '../../../../common/store';
3435
import { TimelineModel } from '../../../store/timeline/model';
@@ -47,6 +48,8 @@ import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
4748
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
4849
import { CreateFieldButton } from '../../create_field_button';
4950
import { SelectedDataView } from '../../../../common/store/sourcerer/selectors';
51+
import { IndexPatternField } from '../../../../../../../../src/plugins/data/public';
52+
import { defaultColumnHeaderType } from './column_headers/default_headers';
5053

5154
interface OwnProps {
5255
activePage: number;
@@ -233,18 +236,37 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
233236
getSelectedDataView(state, SourcererScopeName.timeline)
234237
);
235238

239+
const dispatch = useDispatch();
240+
const { indexFieldsSearch } = useIndexFields(SourcererScopeName.timeline, false);
241+
236242
const createFieldComponent = useMemo(() => {
243+
const onCreateField = (field: IndexPatternField) => {
244+
// Fetch updated list of fields
245+
indexFieldsSearch(dataViewId);
246+
// Add the new field to the event table
247+
dispatch(
248+
tGridActions.upsertColumn({
249+
column: {
250+
columnHeaderType: defaultColumnHeaderType,
251+
id: field.name,
252+
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
253+
},
254+
id,
255+
index: 0,
256+
})
257+
);
258+
};
237259
// It receives onClick props from field browser in order to close the modal.
238260
const CreateFieldButtonComponent: CreateFieldComponentType = ({ onClick }) => (
239261
<CreateFieldButton
240262
selectedDataViewId={dataViewId}
241263
onClick={onClick}
242-
scopeId={SourcererScopeName.timeline}
264+
onCreateField={onCreateField}
243265
/>
244266
);
245267

246268
return CreateFieldButtonComponent;
247-
}, [dataViewId]);
269+
}, [dataViewId, dispatch, id, indexFieldsSearch]);
248270

249271
return (
250272
<>

0 commit comments

Comments
 (0)