[Security Solution][Tech Debt] Decoupled TGrid state part from Timelines under the security_solution store (#141010)

* [Security Solution][Tech Debt] Decoupled TGrid state part from Timelines under the security_solution store

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Unified usage of data table get by id selector

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Cleanup - removed not used code

* -

* -

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Fixed add to timeline

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Fixed filter manager for useHoverActions by proper context usage for defining the scopeId

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Fixed es lint

* -

* TableIds to TableId

* Fixed unit tests

* Fixed tests

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* -

* fixed garphevent component

* FIxed details tests

* Added mock for cases test

* Fixed store tests

* fixed mocks

* fixed mocks

* Cleaned up tgrid store from the timeline actions

* Set back reduceReducers to handle ability addToTimelineButton, need to change this later when timelines data will live in the timeline plugin

* fixed merge

* fixed check types

* Fixed type checks

* Fixed tests

* Added snapshot

* Fixed toggleDetails for user and host

* fixed tests

* Fixed timelines tests

* FIxed tests

* Fixed tests

* Fixed tests

* Fixed Jest tests

* Fixed resolver bug

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* FIxed miissing filterManager

* moved tgrid store

* Reduced bundle size!

* Fixed names

* Fixed tests

* Removed test

* New securitySolution bundle size

* Cleanup the store

* More cleanup

* Removed footer

* removed excludedRowRendererIds

* Fixed typecheck

* remove tests changes

* Cleaned up unused selectors

* Removed savedObjectId from tgrid state

* fixed type check

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Resolved the comments

* Fixed due to comments

* Fixed type checks

* Fixed tests

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* fixed merge issue

* Move suricata-sid-db to lazy loaded modules

* Fixed test

* moved mitre helpers to async chunk

* Fixed due to comments

* Fixed tests

* Renamed TableId.detectionsRulesDetailsPage -> TableId.alertsOnRuleDetailsPage
TableId.detectionsPage -> TableId.alertsOnAlertsPage

* [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix'

* Fixed typecheck

* Fixed test

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yuliia Naumenko 2022-10-18 15:43:41 -07:00 committed by GitHub
parent 2b11ca723e
commit ade016bad5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
381 changed files with 5524 additions and 9651 deletions

View file

@ -105,7 +105,7 @@ pageLoadAssetSize:
screenshotting: 22870
searchprofiler: 67080
security: 65433
securitySolution: 273763
securitySolution: 66738
sessionView: 77750
share: 71239
snapshotRestore: 79032

View file

@ -75,5 +75,5 @@ const requiredProperties: CellValueElementProps = {
},
isDraggable: false,
linkValues: [],
timelineId: '',
scopeId: '',
};

View file

@ -221,7 +221,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
const onStateChange = useCallback(
(state: TGridState) => {
const pickedState = pick(state.timelineById['standalone-t-grid'], [
const pickedState = pick(state.tableById['standalone-t-grid'], [
'columns',
'sort',
'selectedEventIds',
@ -259,7 +259,6 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
itemsPerPage,
itemsPerPageOptions: [10, 25, 50],
loadingText: translations.alertsTable.loadingTextLabel,
footerText: translations.alertsTable.footerTextLabel,
onStateChange,
query: {
query: kuery ?? '',

View file

@ -313,34 +313,36 @@ export type TimelineWithoutExternalRefs = Omit<SavedTimeline, 'dataViewId' | 'sa
*/
export enum TimelineId {
active = 'timeline-1',
casePage = 'timeline-case',
test = 'timeline-test', // Reserved for testing purposes
}
export enum TableId {
usersPageEvents = 'users-page-events',
hostsPageEvents = 'hosts-page-events',
networkPageEvents = 'network-page-events',
hostsPageSessions = 'hosts-page-sessions-v2', // the v2 is to cache bust localstorage settings as default columns were reworked.
detectionsRulesDetailsPage = 'detections-rules-details-page',
detectionsPage = 'detections-page',
active = 'timeline-1',
casePage = 'timeline-case',
test = 'test', // Reserved for testing purposes
alertsOnRuleDetailsPage = 'alerts-rules-details-page',
alertsOnAlertsPage = 'alerts-page',
test = 'table-test', // Reserved for testing purposes
alternateTest = 'alternateTest',
rulePreview = 'rule-preview',
kubernetesPageSessions = 'kubernetes-page-sessions',
}
export const TimelineIdLiteralRt = runtimeTypes.union([
runtimeTypes.literal(TimelineId.usersPageEvents),
runtimeTypes.literal(TimelineId.hostsPageEvents),
runtimeTypes.literal(TimelineId.networkPageEvents),
runtimeTypes.literal(TimelineId.hostsPageSessions),
runtimeTypes.literal(TimelineId.detectionsRulesDetailsPage),
runtimeTypes.literal(TimelineId.detectionsPage),
runtimeTypes.literal(TimelineId.active),
runtimeTypes.literal(TimelineId.test),
runtimeTypes.literal(TimelineId.rulePreview),
runtimeTypes.literal(TimelineId.kubernetesPageSessions),
export const TableIdLiteralRt = runtimeTypes.union([
runtimeTypes.literal(TableId.usersPageEvents),
runtimeTypes.literal(TableId.hostsPageEvents),
runtimeTypes.literal(TableId.networkPageEvents),
runtimeTypes.literal(TableId.hostsPageSessions),
runtimeTypes.literal(TableId.alertsOnRuleDetailsPage),
runtimeTypes.literal(TableId.alertsOnAlertsPage),
runtimeTypes.literal(TableId.test),
runtimeTypes.literal(TableId.rulePreview),
runtimeTypes.literal(TableId.kubernetesPageSessions),
]);
export type TimelineIdLiteral = runtimeTypes.TypeOf<typeof TimelineIdLiteralRt>;
export type TableIdLiteral = runtimeTypes.TypeOf<typeof TableIdLiteralRt>;
export const TimelineSavedToReturnObjectRuntimeType = runtimeTypes.intersection([
SavedTimelineRuntimeType,
@ -528,7 +530,7 @@ export type TimelineExpandedDetail = {
export type ToggleDetailPanel = TimelineExpandedDetailType & {
tabType?: TimelineTabs;
timelineId: string;
id: string;
};
export const pageInfoTimeline = runtimeTypes.type({

View file

@ -26,6 +26,7 @@ import { TimelineId } from '../../../../common/types/timeline';
import { createStore } from '../../../common/store';
import { kibanaObservable } from '@kbn/timelines-plugin/public/mock';
import { sourcererPaths } from '../../../common/containers/sourcerer';
import { tGridReducer } from '@kbn/timelines-plugin/public';
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
@ -72,7 +73,13 @@ describe('global header', () => {
},
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
beforeEach(() => {
useVariationMock.mockReset();
@ -169,7 +176,13 @@ describe('global header', () => {
},
},
};
const mockStore = createStore(mockstate, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mockStore = createStore(
mockstate,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
(useLocation as jest.Mock).mockReturnValue({ pathname: sourcererPaths[2] });

View file

@ -33,6 +33,7 @@ import type { TimelineUrl } from '../../timelines/store/timeline/model';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import { URL_PARAM_KEY } from '../../common/hooks/use_url_state';
import { InputsModelId } from '../../common/store/inputs/constants';
import { tGridReducer } from '@kbn/timelines-plugin/public';
jest.mock('../../common/store/inputs/actions');
@ -298,7 +299,13 @@ describe('HomePage', () => {
},
};
const mockStore = createStore(mockstate, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mockStore = createStore(
mockstate,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={mockStore}>
@ -452,7 +459,13 @@ describe('HomePage', () => {
};
const { storage } = createSecuritySolutionStorageMock();
const mockStore = createStore(mockstate, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mockStore = createStore(
mockstate,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const TestComponent = () => (
<TestProviders store={mockStore}>
@ -509,7 +522,13 @@ describe('HomePage', () => {
};
const { storage } = createSecuritySolutionStorageMock();
const mockStore = createStore(mockstate, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const mockStore = createStore(
mockstate,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const TestComponent = () => (
<TestProviders store={mockStore}>
@ -569,7 +588,13 @@ describe('HomePage', () => {
it('it removes empty timeline state from URL', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
mockUseInitializeUrlParam(URL_PARAM_KEY.timeline, {
id: 'testSavedTimelineId',
@ -596,7 +621,13 @@ describe('HomePage', () => {
it('it updates URL when timeline store changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const savedObjectId = 'testTimelineId';
mockUseInitializeUrlParam(URL_PARAM_KEY.timeline, {

View file

@ -18,6 +18,8 @@ import type {
import type { RouteProps } from 'react-router-dom';
import type { AppMountParameters } from '@kbn/core/public';
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import type { TableState } from '@kbn/timelines-plugin/public';
import type { StartServices } from '../types';
/**
@ -33,7 +35,6 @@ export interface RenderAppProps extends AppMountParameters {
import type { State, SubPluginsInitReducer } from '../common/store';
import type { Immutable } from '../../common/endpoint/types';
import type { AppAction } from '../common/store/actions';
import type { TimelineState } from '../timelines/store/timeline/types';
export { SecurityPageName } from '../../common/constants';
@ -47,7 +48,7 @@ export type SecuritySubPluginRoutes = RouteProps[];
export interface SecuritySubPlugin {
routes: SecuritySubPluginRoutes;
storageTimelines?: Pick<TimelineState, 'timelineById'>;
storageDataTables?: Pick<TableState, 'tableById'>;
}
export type SecuritySubPluginKeyStore =

View file

@ -34,7 +34,7 @@ const TimelineDetailsPanel = () => {
entityType="events"
isFlyoutView
runtimeMappings={runtimeMappings}
timelineId={TimelineId.casePage}
scopeId={TimelineId.casePage}
/>
);
};
@ -58,7 +58,7 @@ const CaseContainerComponent: React.FC = () => {
dispatch(
timelineActions.toggleDetailPanel({
panelView: 'eventDetail',
timelineId: TimelineId.casePage,
id: TimelineId.casePage,
params: {
eventId: alertId,
indexName: index,

View file

@ -19,6 +19,7 @@ import type { State } from '../../../../store';
import { createStore } from '../../../../store';
import * as i18n from './translations';
import { useChartSettingsPopoverConfiguration } from '.';
import { tGridReducer } from '@kbn/timelines-plugin/public';
const mockHandleClick = jest.fn();
@ -32,7 +33,13 @@ describe('useChartSettingsPopoverConfiguration', () => {
const state: State = mockGlobalState;
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = ({ children }: { children: React.ReactNode }) => (
<TestProviders store={store}>{children}</TestProviders>
);

View file

@ -168,7 +168,7 @@ export interface BarChartComponentProps {
barChart: ChartSeriesData[] | null | undefined;
configs?: ChartSeriesConfigs | undefined;
stackByField?: string;
timelineId?: string;
scopeId?: string;
visualizationActionsOptions?: VisualizationActionsProps;
}
@ -178,7 +178,7 @@ export const BarChartComponent: React.FC<BarChartComponentProps> = ({
barChart,
configs,
stackByField,
timelineId,
scopeId,
visualizationActionsOptions,
}) => {
const { ref: measureRef, width, height } = useThrottledResizeObserver();
@ -190,12 +190,12 @@ export const BarChartComponent: React.FC<BarChartComponentProps> = ({
dataProviderId: escapeDataProviderId(
`draggable-legend-item-${uuid.v4()}-${stackByField}-${d.key}`
),
timelineId,
scopeId,
field: stackByField,
value: d.key,
}))
: NO_LEGEND_DATA,
[barChart, stackByField, timelineId]
[barChart, stackByField, scopeId]
);
const yAxisTitle = get('yAxisTitle', configs);
@ -243,7 +243,7 @@ export const BarChart = React.memo(
BarChartComponent,
(prevProps, nextProps) =>
prevProps.stackByField === nextProps.stackByField &&
prevProps.timelineId === nextProps.timelineId &&
prevProps.scopeId === nextProps.scopeId &&
deepEqual(prevProps.configs, nextProps.configs) &&
deepEqual(prevProps.barChart, nextProps.barChart)
);

View file

@ -25,7 +25,7 @@ export interface LegendItem {
dataProviderId: string;
render?: (fieldValuePair?: { field: string; value: string | number }) => React.ReactNode;
field: string;
timelineId?: string;
scopeId?: string;
value: string | number;
count?: number;
}
@ -47,7 +47,7 @@ const DraggableLegendItemComponent: React.FC<{
legendItem: LegendItem;
}> = ({ legendItem }) => {
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
const { color, count, dataProviderId, field, timelineId, value } = legendItem;
const { color, count, dataProviderId, field, scopeId, value } = legendItem;
return (
<EuiText size="xs">
@ -72,7 +72,7 @@ const DraggableLegendItemComponent: React.FC<{
hideTopN={true}
id={dataProviderId}
isDraggable={false}
timelineId={timelineId}
scopeId={scopeId}
value={value}
>
{legendItem.render == null ? (

View file

@ -11,7 +11,7 @@ import React from 'react';
import type { DraggableStateSnapshot, DraggingStyle } from 'react-beautiful-dnd';
import '../../mock/match_media';
import { TimelineId } from '../../../../common/types';
import { TableId, TimelineId } from '../../../../common/types';
import { mockBrowserFields } from '../../containers/source/mock';
import { TestProviders } from '../../mock';
import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers';
@ -35,25 +35,22 @@ jest.mock('@elastic/eui', () => {
};
});
const timelineIdsWithHoverActions = [
const scopeIdsWithHoverActions = [
undefined,
TimelineId.active,
TimelineId.alternateTest,
TableId.alternateTest,
TimelineId.casePage,
TimelineId.detectionsPage,
TimelineId.detectionsRulesDetailsPage,
TimelineId.hostsPageEvents,
TimelineId.hostsPageSessions,
TimelineId.kubernetesPageSessions,
TimelineId.networkPageEvents,
TableId.alertsOnAlertsPage,
TableId.alertsOnRuleDetailsPage,
TableId.hostsPageEvents,
TableId.hostsPageSessions,
TableId.kubernetesPageSessions,
TableId.networkPageEvents,
TimelineId.test,
TimelineId.usersPageEvents,
TableId.usersPageEvents,
];
const timelineIdsNoHoverActions = [
TimelineId.rulePreview,
ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
];
const scopeIdsNoHoverActions = [TableId.rulePreview, ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID];
describe('DraggableWrapper', () => {
const dataProvider = mockDataProviders[0];
@ -141,8 +138,8 @@ describe('DraggableWrapper', () => {
});
});
timelineIdsWithHoverActions.forEach((timelineId) => {
test(`it renders hover actions (by default) when 'isDraggable' is false and timelineId is '${timelineId}'`, async () => {
scopeIdsWithHoverActions.forEach((scopeId) => {
test(`it renders hover actions (by default) when 'isDraggable' is false and timelineId is '${scopeId}'`, async () => {
const isDraggable = false;
const { container } = render(
@ -152,7 +149,7 @@ describe('DraggableWrapper', () => {
dataProvider={dataProvider}
isDraggable={isDraggable}
render={() => message}
timelineId={timelineId}
scopeId={scopeId}
/>
</DragDropContextWrapper>
</TestProviders>
@ -166,8 +163,8 @@ describe('DraggableWrapper', () => {
});
});
timelineIdsNoHoverActions.forEach((timelineId) => {
test(`it does NOT render hover actions when 'isDraggable' is false and timelineId is '${timelineId}'`, async () => {
scopeIdsNoHoverActions.forEach((scopeId) => {
test(`it does NOT render hover actions when 'isDraggable' is false and timelineId is '${scopeId}'`, async () => {
const isDraggable = false;
const { container } = render(
@ -177,7 +174,7 @@ describe('DraggableWrapper', () => {
dataProvider={dataProvider}
isDraggable={isDraggable}
render={() => message}
timelineId={timelineId}
scopeId={scopeId}
/>
</DragDropContextWrapper>
</TestProviders>
@ -281,15 +278,15 @@ describe('ConditionalPortal', () => {
});
describe('disableHoverActions', () => {
timelineIdsNoHoverActions.forEach((timelineId) =>
test(`it returns true when timelineId is ${timelineId}`, () => {
expect(disableHoverActions(timelineId)).toBe(true);
scopeIdsNoHoverActions.forEach((scopeId) =>
test(`it returns true when timelineId is ${scopeId}`, () => {
expect(disableHoverActions(scopeId)).toBe(true);
})
);
timelineIdsWithHoverActions.forEach((timelineId) =>
test(`it returns false when timelineId is ${timelineId}`, () => {
expect(disableHoverActions(timelineId)).toBe(false);
scopeIdsWithHoverActions.forEach((scopeId) =>
test(`it returns false when timelineId is ${scopeId}`, () => {
expect(disableHoverActions(scopeId)).toBe(false);
})
);
});

View file

@ -18,7 +18,7 @@ import { Draggable, Droppable } from 'react-beautiful-dnd';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { TimelineId } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import { dragAndDropActions } from '../../store/drag_and_drop';
import type { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../../../timelines/components/row_renderers_browser/constants';
@ -104,13 +104,13 @@ interface Props {
render: RenderFunctionProp;
isAggregatable?: boolean;
fieldType?: string;
timelineId?: string;
scopeId?: string;
truncate?: boolean;
onFilterAdded?: () => void;
}
export const disableHoverActions = (timelineId: string | undefined): boolean =>
[TimelineId.rulePreview, ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID].includes(timelineId ?? '');
[TableId.rulePreview, ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID].includes(timelineId ?? '');
/**
* Wraps a draggable component to handle registration / unregistration of the
@ -138,7 +138,7 @@ const DraggableOnWrapperComponent: React.FC<Props> = ({
render,
fieldType = '',
isAggregatable = false,
timelineId,
scopeId,
truncate,
}) => {
const [providerRegistered, setProviderRegistered] = useState(false);
@ -163,7 +163,7 @@ const DraggableOnWrapperComponent: React.FC<Props> = ({
render,
fieldType,
isAggregatable,
timelineId,
scopeId,
truncate,
});
@ -324,7 +324,7 @@ const DraggableWrapperComponent: React.FC<Props> = ({
render,
isAggregatable = false,
fieldType = '',
timelineId,
scopeId,
truncate,
}) => {
const {
@ -342,7 +342,7 @@ const DraggableWrapperComponent: React.FC<Props> = ({
fieldType,
onFilterAdded,
render,
timelineId,
scopeId,
truncate,
});
const renderContent = useCallback(
@ -374,7 +374,7 @@ const DraggableWrapperComponent: React.FC<Props> = ({
<WithHoverActions
alwaysShow={showTopN || hoverActionsOwnFocus}
closePopOverTrigger={closePopOverTrigger}
hoverContent={disableHoverActions(timelineId) ? undefined : hoverContent}
hoverContent={disableHoverActions(scopeId) ? undefined : hoverContent}
onCloseRequested={onCloseRequested}
render={renderContent}
/>
@ -388,7 +388,7 @@ const DraggableWrapperComponent: React.FC<Props> = ({
fieldType={fieldType}
isAggregatable={isAggregatable}
render={render}
timelineId={timelineId}
scopeId={scopeId}
truncate={truncate}
/>
);

View file

@ -28,7 +28,7 @@ export interface DefaultDraggableType {
name?: string | null;
queryValue?: string | null;
children?: React.ReactNode;
timelineId?: string;
scopeId?: string;
tooltipContent?: React.ReactNode;
tooltipPosition?: ToolTipPositions;
}
@ -107,7 +107,7 @@ export const DefaultDraggable = React.memo<DefaultDraggableType>(
value,
name,
children,
timelineId,
scopeId,
tooltipContent,
tooltipPosition,
queryValue,
@ -158,7 +158,7 @@ export const DefaultDraggable = React.memo<DefaultDraggableType>(
hideTopN={hideTopN}
isDraggable={isDraggable}
render={renderCallback}
timelineId={timelineId}
scopeId={scopeId}
/>
);
}
@ -207,7 +207,7 @@ const DraggableBadgeComponent: React.FC<BadgeDraggableType> = ({
name,
color = 'hollow',
children,
timelineId,
scopeId,
tooltipContent,
queryValue,
}) =>
@ -220,7 +220,7 @@ const DraggableBadgeComponent: React.FC<BadgeDraggableType> = ({
field={field}
name={name}
value={value}
timelineId={timelineId}
scopeId={scopeId}
tooltipContent={tooltipContent}
queryValue={queryValue}
>

View file

@ -8,7 +8,7 @@
import React, { memo, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { useDispatch } from 'react-redux';
import { TimelineId } from '@kbn/timelines-plugin/common';
import { TimelineId } from '../../../../common/types';
import type { AppLocation } from '../../../../common/endpoint/types';
import { timelineActions } from '../../../timelines/store/timeline';

View file

@ -19,14 +19,27 @@ import { createStore } from '../../store/store';
import { ErrorToastDispatcher } from '.';
import type { State } from '../../store/types';
import { tGridReducer } from '@kbn/timelines-plugin/public';
describe('Error Toast Dispatcher', () => {
const state: State = mockGlobalState;
const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
let store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
beforeEach(() => {
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
});
describe('rendering', () => {

View file

@ -30,7 +30,7 @@ const props = {
data: mockAlertDetailsData as TimelineEventsDetailsItem[],
browserFields: mockBrowserFields,
eventId: '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31',
timelineId: 'detections-page',
scopeId: 'alerts-page',
title: '',
goToTable: jest.fn(),
};
@ -84,7 +84,7 @@ describe('AlertSummaryView', () => {
await act(async () => {
const { queryAllByTestId } = render(
<TestProviders>
<AlertSummaryView {...props} timelineId={TimelineId.active} />
<AlertSummaryView {...props} scopeId={TimelineId.active} />
</TestProviders>
);
expect(queryAllByTestId('hover-actions-filter-for').length).toEqual(0);

View file

@ -19,14 +19,14 @@ const AlertSummaryViewComponent: React.FC<{
data: TimelineEventsDetailsItem[];
eventId: string;
isDraggable?: boolean;
timelineId: string;
scopeId: string;
title: string;
goToTable: () => void;
isReadOnly?: boolean;
}> = ({ browserFields, data, eventId, isDraggable, timelineId, title, goToTable, isReadOnly }) => {
}> = ({ browserFields, data, eventId, isDraggable, scopeId, title, goToTable, isReadOnly }) => {
const summaryRows = useMemo(
() => getSummaryRows({ browserFields, data, eventId, isDraggable, timelineId, isReadOnly }),
[browserFields, data, eventId, isDraggable, timelineId, isReadOnly]
() => getSummaryRows({ browserFields, data, eventId, isDraggable, scopeId, isReadOnly }),
[browserFields, data, eventId, isDraggable, scopeId, isReadOnly]
);
return (

View file

@ -30,7 +30,7 @@ describe('getColumns', () => {
eventId: 'some-event',
getLinkValue: jest.fn(),
onUpdateColumns: jest.fn(),
timelineId: 'some-timeline',
scopeId: 'some-timeline',
toggleColumn: jest.fn(),
};

View file

@ -44,7 +44,7 @@ export const getColumns = ({
eventId,
onUpdateColumns,
contextId,
timelineId,
scopeId,
toggleColumn,
getLinkValue,
isDraggable,
@ -55,7 +55,7 @@ export const getColumns = ({
eventId: string;
onUpdateColumns: OnUpdateColumns;
contextId: string;
timelineId: string;
scopeId: string;
toggleColumn: (column: ColumnHeaderOptions) => void;
getLinkValue: (field: string) => string | null;
isDraggable?: boolean;
@ -90,7 +90,7 @@ export const getColumns = ({
fieldFromBrowserField={fieldFromBrowserField}
getLinkValue={getLinkValue}
toggleColumn={toggleColumn}
timelineId={timelineId}
scopeId={scopeId}
values={values}
/>
);

View file

@ -30,7 +30,7 @@ export interface ThreatSummaryDescription {
eventId: string;
index: number;
feedName: string | undefined;
timelineId: string;
scopeId: string;
value: string | undefined;
isDraggable?: boolean;
isReadOnly?: boolean;
@ -63,19 +63,19 @@ const EnrichmentDescription: React.FC<ThreatSummaryDescription> = ({
eventId,
index,
feedName,
timelineId,
scopeId,
value,
isDraggable,
isReadOnly,
}) => {
if (!data || !value) return null;
const key = `alert-details-value-formatted-field-value-${timelineId}-${eventId}-${data.field}-${value}-${index}-${feedName}`;
const key = `alert-details-value-formatted-field-value-${scopeId}-${eventId}-${data.field}-${value}-${index}-${feedName}`;
return (
<StyledEuiFlexGroup key={key} direction="row" gutterSize="xs" alignItems="center">
<EuiFlexItem grow={false}>
<div>
<FormattedFieldValue
contextId={timelineId}
contextId={scopeId}
eventId={key}
fieldFormat={data.format}
fieldName={data.field}
@ -97,10 +97,10 @@ const EnrichmentDescription: React.FC<ThreatSummaryDescription> = ({
{value && !isReadOnly && (
<ActionCell
data={data}
contextId={timelineId}
contextId={scopeId}
eventId={key}
fieldFromBrowserField={browserField}
timelineId={timelineId}
scopeId={scopeId}
values={[value]}
applyWidthAndPadding={false}
/>
@ -114,11 +114,11 @@ const EnrichmentSummaryComponent: React.FC<{
browserFields: BrowserFields;
data: TimelineEventsDetailsItem[];
enrichments: CtiEnrichment[];
timelineId: string;
scopeId: string;
eventId: string;
isDraggable?: boolean;
isReadOnly?: boolean;
}> = ({ browserFields, data, enrichments, timelineId, eventId, isDraggable, isReadOnly }) => {
}> = ({ browserFields, data, enrichments, scopeId, eventId, isDraggable, isReadOnly }) => {
const parsedEnrichments = enrichments.map((enrichment, index) => {
const { field, type, feedName, value } = getEnrichmentIdentifiers(enrichment);
const eventData = data.find((item) => item.field === field);
@ -166,7 +166,7 @@ const EnrichmentSummaryComponent: React.FC<{
eventId={eventId}
index={index}
feedName={feedName}
timelineId={timelineId}
scopeId={scopeId}
value={value}
data={fieldsData}
browserField={browserField}
@ -197,7 +197,7 @@ const EnrichmentSummaryComponent: React.FC<{
eventId={eventId}
index={index}
feedName={feedName}
timelineId={timelineId}
scopeId={scopeId}
value={value}
data={fieldsData}
browserField={browserField}

View file

@ -37,7 +37,7 @@ const EMPTY_RISK_SCORE = {
describe('ThreatSummaryView', () => {
const eventId = '5d1d53da502f56aacc14c3cb5c669363d102b31f99822e5d369d4804ed370a31';
const timelineId = 'detections-page';
const scopeId = 'alerts-page';
const data = mockAlertDetailsData as TimelineEventsDetailsItem[];
const browserFields = mockBrowserFields;
@ -54,7 +54,7 @@ describe('ThreatSummaryView', () => {
browserFields={browserFields}
enrichments={enrichments}
eventId={eventId}
timelineId={timelineId}
scopeId={scopeId}
hostRisk={EMPTY_RISK_SCORE}
userRisk={EMPTY_RISK_SCORE}
/>

View file

@ -126,7 +126,7 @@ const ThreatSummaryViewComponent: React.FC<{
data: TimelineEventsDetailsItem[];
enrichments: CtiEnrichment[];
eventId: string;
timelineId: string;
scopeId: string;
hostRisk: HostRisk;
userRisk: UserRisk;
isDraggable?: boolean;
@ -136,7 +136,7 @@ const ThreatSummaryViewComponent: React.FC<{
data,
enrichments,
eventId,
timelineId,
scopeId,
hostRisk,
userRisk,
isDraggable,
@ -180,7 +180,7 @@ const ThreatSummaryViewComponent: React.FC<{
browserFields={browserFields}
data={data}
enrichments={enrichments}
timelineId={timelineId}
scopeId={scopeId}
eventId={eventId}
isDraggable={isDraggable}
isReadOnly={isReadOnly}

View file

@ -72,7 +72,7 @@ describe('EventDetails', () => {
onEventViewSelected: jest.fn(),
onThreatViewSelected: jest.fn(),
timelineTabType: TimelineTabs.query,
timelineId: 'test',
scopeId: 'table-test',
eventView: EventsViewType.summaryView,
hostRisk: { fields: [], loading: true },
indexName: 'test',

View file

@ -77,7 +77,7 @@ interface Props {
isDraggable?: boolean;
rawEventData: object | undefined;
timelineTabType: TimelineTabs | 'flyout';
timelineId: string;
scopeId: string;
handleOnEventClosed: () => void;
isReadOnly?: boolean;
}
@ -131,7 +131,7 @@ const EventDetailsComponent: React.FC<Props> = ({
isAlert,
isDraggable,
rawEventData,
timelineId,
scopeId,
timelineTabType,
handleOnEventClosed,
isReadOnly,
@ -197,11 +197,11 @@ const EventDetailsComponent: React.FC<Props> = ({
<EuiSpacer size="m" />
<Overview
browserFields={browserFields}
contextId={timelineId}
contextId={scopeId}
data={data}
eventId={id}
indexName={indexName}
timelineId={timelineId}
scopeId={scopeId}
handleOnEventClosed={handleOnEventClosed}
isReadOnly={isReadOnly}
/>
@ -214,7 +214,7 @@ const EventDetailsComponent: React.FC<Props> = ({
contextId: EVENT_DETAILS_CONTEXT_ID,
data: detailsEcsData,
isDraggable: isDraggable ?? false,
timelineId,
scopeId,
})}
</RendererContainer>
</div>
@ -227,7 +227,7 @@ const EventDetailsComponent: React.FC<Props> = ({
eventId: id,
browserFields,
isDraggable,
timelineId,
scopeId,
title: i18n.HIGHLIGHTED_FIELDS,
isReadOnly,
}}
@ -239,7 +239,7 @@ const EventDetailsComponent: React.FC<Props> = ({
browserFields={browserFields}
eventId={id}
data={data}
timelineId={timelineId}
scopeId={scopeId}
isReadOnly={isReadOnly}
/>
@ -251,7 +251,7 @@ const EventDetailsComponent: React.FC<Props> = ({
browserFields={browserFields}
data={data}
eventId={id}
timelineId={timelineId}
scopeId={scopeId}
enrichments={allEnrichments}
isReadOnly={isReadOnly}
/>
@ -280,11 +280,11 @@ const EventDetailsComponent: React.FC<Props> = ({
indexName,
isAlert,
isDraggable,
scopeId,
isEnrichmentsLoading,
showThreatSummary,
isReadOnly,
renderer,
timelineId,
userRisk,
]
);
@ -359,14 +359,14 @@ const EventDetailsComponent: React.FC<Props> = ({
data={data}
eventId={id}
isDraggable={isDraggable}
timelineId={timelineId}
scopeId={scopeId}
timelineTabType={timelineTabType}
isReadOnly={isReadOnly}
/>
</>
),
}),
[browserFields, data, id, isDraggable, timelineId, timelineTabType, isReadOnly]
[browserFields, data, id, isDraggable, scopeId, timelineTabType, isReadOnly]
);
const jsonTab = useMemo(

View file

@ -50,7 +50,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -69,7 +69,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -91,7 +91,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -107,7 +107,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -123,7 +123,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -139,7 +139,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
isReadOnly
/>
@ -158,7 +158,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -176,7 +176,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -194,7 +194,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={eventId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -214,7 +214,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -239,7 +239,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -254,7 +254,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -279,7 +279,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>
@ -298,7 +298,7 @@ describe('EventFieldsBrowser', () => {
browserFields={mockBrowserFields}
data={mockDetailItemData}
eventId={mockDetailItemDataId}
timelineId="test"
scopeId="timeline-test"
timelineTabType={TimelineTabs.query}
/>
</TestProviders>

View file

@ -19,8 +19,11 @@ import {
onKeyDownFocusHandler,
} from '@kbn/timelines-plugin/public';
import { getScopedActions, isInTableScope, isTimelineScope } from '../../../helpers';
import { tableDefaults } from '../../store/data_table/defaults';
import { dataTableSelectors } from '../../store/data_table';
import { ADD_TIMELINE_BUTTON_CLASS_NAME } from '../../../timelines/components/flyout/add_timeline_button';
import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline';
import { timelineSelectors } from '../../../timelines/store/timeline';
import type { BrowserFields } from '../../containers/source';
import { getAllFieldsByName } from '../../containers/source';
import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy/timeline';
@ -36,7 +39,7 @@ interface Props {
data: TimelineEventsDetailsItem[];
eventId: string;
isDraggable?: boolean;
timelineId: string;
scopeId: string;
timelineTabType: TimelineTabs | 'flyout';
isReadOnly?: boolean;
}
@ -166,10 +169,22 @@ const useFieldBrowserPagination = () => {
/** Renders a table view or JSON view of the `ECS` `data` */
export const EventFieldsBrowser = React.memo<Props>(
({ browserFields, data, eventId, isDraggable, timelineTabType, timelineId, isReadOnly }) => {
({ browserFields, data, eventId, isDraggable, timelineTabType, scopeId, isReadOnly }) => {
const containerElement = useRef<HTMLDivElement | null>(null);
const dispatch = useDispatch();
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const getScope = useMemo(() => {
if (isTimelineScope(scopeId)) {
return timelineSelectors.getTimelineByIdSelector();
} else if (isInTableScope(scopeId)) {
return dataTableSelectors.getTableByIdSelector();
}
}, [scopeId]);
const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults;
const columnHeaders = useDeepEqualSelector((state) => {
const { columns } = (getScope && getScope(state, scopeId)) ?? defaults;
return getColumnHeaders(columns, browserFields);
});
const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]);
const items = useMemo(
() =>
@ -182,11 +197,6 @@ export const EventFieldsBrowser = React.memo<Props>(
[data, fieldsByName]
);
const columnHeaders = useDeepEqualSelector((state) => {
const { columns } = getTimeline(state, timelineId) ?? timelineDefaults;
return getColumnHeaders(columns, browserFields);
});
const getLinkValue = useCallback(
(field: string) => {
const linkField = (columnHeaders.find((col) => col.id === field) ?? {}).linkField;
@ -199,27 +209,30 @@ export const EventFieldsBrowser = React.memo<Props>(
},
[data, columnHeaders]
);
const scopedActions = getScopedActions(scopeId);
const toggleColumn = useCallback(
(column: ColumnHeaderOptions) => {
if (!scopedActions) {
return;
}
if (columnHeaders.some((c) => c.id === column.id)) {
dispatch(
timelineActions.removeColumn({
scopedActions.removeColumn({
columnId: column.id,
id: timelineId,
id: scopeId,
})
);
} else {
dispatch(
timelineActions.upsertColumn({
scopedActions.upsertColumn({
column,
id: timelineId,
id: scopeId,
index: 1,
})
);
}
},
[columnHeaders, dispatch, timelineId]
[columnHeaders, dispatch, scopeId, scopedActions]
);
const onSetRowProps = useCallback(({ ariaRowindex, field }: TimelineEventsDetailsItem) => {
@ -232,8 +245,12 @@ export const EventFieldsBrowser = React.memo<Props>(
}, []);
const onUpdateColumns = useCallback(
(columns) => dispatch(timelineActions.updateColumns({ id: timelineId, columns })),
[dispatch, timelineId]
(columns) => {
if (scopedActions) {
dispatch(scopedActions.updateColumns({ id: scopeId, columns }));
}
},
[dispatch, scopeId, scopedActions]
);
const columns = useMemo(
@ -243,8 +260,8 @@ export const EventFieldsBrowser = React.memo<Props>(
columnHeaders,
eventId,
onUpdateColumns,
contextId: `event-fields-browser-for-${timelineId}-${timelineTabType}`,
timelineId,
contextId: `event-fields-browser-for-${scopeId}-${timelineTabType}`,
scopeId,
toggleColumn,
getLinkValue,
isDraggable,
@ -255,7 +272,7 @@ export const EventFieldsBrowser = React.memo<Props>(
columnHeaders,
eventId,
onUpdateColumns,
timelineId,
scopeId,
timelineTabType,
toggleColumn,
getLinkValue,

View file

@ -237,14 +237,14 @@ function getEventCategoriesFromData(data: TimelineEventsDetailsItem[]): EventCat
export const getSummaryRows = ({
data,
browserFields,
timelineId,
scopeId,
eventId,
isDraggable = false,
isReadOnly = false,
}: {
data: TimelineEventsDetailsItem[];
browserFields: BrowserFields;
timelineId: string;
scopeId: string;
eventId: string;
isDraggable?: boolean;
isReadOnly?: boolean;
@ -288,8 +288,8 @@ export const getSummaryRows = ({
...getEnrichedFieldInfo({
item,
linkValueField: linkValueField || undefined,
contextId: timelineId,
timelineId,
contextId: scopeId,
scopeId,
browserFields,
eventId,
field,

View file

@ -134,14 +134,14 @@ export function getEnrichedFieldInfo({
field,
item,
linkValueField,
timelineId,
scopeId,
}: {
browserFields: BrowserFields;
contextId: string;
item: TimelineEventsDetailsItem;
eventId: string;
field?: EventSummaryField;
timelineId: string;
scopeId: string;
linkValueField?: TimelineEventsDetailsItem;
}): EnrichedFieldInfo {
const fieldInfo = {
@ -149,7 +149,7 @@ export function getEnrichedFieldInfo({
eventId,
fieldType: 'string',
linkValue: undefined,
timelineId,
scopeId,
};
const linkValue = getOr(null, 'originalValue.0', linkValueField);
const category = item.category ?? '';

View file

@ -100,7 +100,7 @@ describe('Insights', () => {
it('does not render when there is no content to show', () => {
render(
<TestProviders>
<Insights browserFields={{}} eventId="test" data={[]} timelineId="" />
<Insights browserFields={{}} eventId="test" data={[]} scopeId="" />
</TestProviders>
);
@ -120,7 +120,7 @@ describe('Insights', () => {
render(
<TestProviders>
<Insights browserFields={{}} eventId="test" data={[]} timelineId="" />
<Insights browserFields={{}} eventId="test" data={[]} scopeId="" />
</TestProviders>
);
@ -140,7 +140,7 @@ describe('Insights', () => {
it('should show insights for related alerts by process ancestry', () => {
render(
<TestProviders>
<Insights browserFields={{}} eventId="test" data={data} timelineId="" />
<Insights browserFields={{}} eventId="test" data={data} scopeId="" />
</TestProviders>
);
@ -154,12 +154,7 @@ describe('Insights', () => {
it('should not show the related alerts by process ancestry insights module', () => {
render(
<TestProviders>
<Insights
browserFields={{}}
eventId="test"
data={dataWithoutAgentType}
timelineId=""
/>
<Insights browserFields={{}} eventId="test" data={dataWithoutAgentType} scopeId="" />
</TestProviders>
);
@ -174,7 +169,7 @@ describe('Insights', () => {
render(
<TestProviders>
<Insights browserFields={{}} eventId="test" data={data} timelineId="" />
<Insights browserFields={{}} eventId="test" data={data} scopeId="" />
</TestProviders>
);
@ -192,7 +187,7 @@ describe('Insights', () => {
render(
<TestProviders>
<Insights browserFields={{}} eventId="test" data={data} timelineId="" />
<Insights browserFields={{}} eventId="test" data={data} scopeId="" />
</TestProviders>
);

View file

@ -27,7 +27,7 @@ interface Props {
browserFields: BrowserFields;
eventId: string;
data: TimelineEventsDetailsItem[];
timelineId: string;
scopeId: string;
isReadOnly?: boolean;
}
@ -35,7 +35,7 @@ interface Props {
* Displays several key insights for the associated alert.
*/
export const Insights = React.memo<Props>(
({ browserFields, eventId, data, isReadOnly, timelineId }) => {
({ browserFields, eventId, data, isReadOnly, scopeId }) => {
const isRelatedAlertsByProcessAncestryEnabled = useIsExperimentalFeatureEnabled(
'insightsRelatedAlertsByProcessAncestry'
);
@ -113,7 +113,7 @@ export const Insights = React.memo<Props>(
browserFields={browserFields}
data={sourceEventField}
eventId={eventId}
timelineId={timelineId}
scopeId={scopeId}
/>
</EuiFlexItem>
)}
@ -124,7 +124,7 @@ export const Insights = React.memo<Props>(
browserFields={browserFields}
data={processSessionField}
eventId={eventId}
timelineId={timelineId}
scopeId={scopeId}
/>
</EuiFlexItem>
)}
@ -137,7 +137,7 @@ export const Insights = React.memo<Props>(
originalDocumentId={originalDocumentId}
index={originalDocumentIndex}
eventId={eventId}
timelineId={timelineId}
scopeId={scopeId}
/>
</EuiFlexItem>
) : (

View file

@ -43,6 +43,8 @@ const props = {
values: ['original'],
isObjectArray: false,
},
scopeId: 'table-test',
isActiveTimelines: false,
};
describe('RelatedAlertsByProcessAncestry', () => {
beforeEach(() => {

View file

@ -9,8 +9,8 @@ import React, { useMemo, useCallback, useEffect, useState } from 'react';
import { EuiBetaBadge, EuiSpacer, EuiLoadingSpinner } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import { isActiveTimeline } from '../../../../helpers';
import type { DataProvider } from '../../../../../common/types';
import { TimelineId } from '../../../../../common/types/timeline';
import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline';
import { getDataProvider } from '../table/use_action_cell_data_provider';
import { useAlertPrevalenceFromProcessTree } from '../../../containers/alerts/use_alert_prevalence_from_process_tree';
@ -32,7 +32,7 @@ interface Props {
eventId: string;
index: TimelineEventsDetailsItem;
originalDocumentId: TimelineEventsDetailsItem;
timelineId?: string;
scopeId?: string;
}
interface Cache {
@ -70,7 +70,7 @@ const dataProviderLimit = 5;
* state inside the component rather than to add it to Redux.
*/
export const RelatedAlertsByProcessAncestry = React.memo<Props>(
({ data, originalDocumentId, index, eventId, timelineId }) => {
({ data, originalDocumentId, index, eventId, scopeId }) => {
const [showContent, setShowContent] = useState(false);
const [cache, setCache] = useState<Partial<Cache>>({});
@ -85,7 +85,7 @@ export const RelatedAlertsByProcessAncestry = React.memo<Props>(
return (
<ActualRelatedAlertsByProcessAncestry
eventId={eventId}
timelineId={timelineId}
scopeId={scopeId}
alertIds={cache.alertIds}
/>
);
@ -96,11 +96,11 @@ export const RelatedAlertsByProcessAncestry = React.memo<Props>(
index={index}
originalDocumentId={originalDocumentId}
eventId={eventId}
timelineId={timelineId}
isActiveTimelines={isActiveTimeline(scopeId ?? '')}
onCacheLoad={setCache}
/>
);
}, [showContent, cache, data, eventId, timelineId, index, originalDocumentId]);
}, [showContent, cache.alertIds, data, index, originalDocumentId, eventId, scopeId]);
const betaBadge = useMemo(() => <EuiBetaBadge size="s" label={BETA} color="subdued" />, []);
@ -132,9 +132,9 @@ const FetchAndNotifyCachedAlertsByProcessAncestry: React.FC<{
eventId: string;
index: TimelineEventsDetailsItem;
originalDocumentId: TimelineEventsDetailsItem;
timelineId?: string;
isActiveTimelines: boolean;
onCacheLoad: (cache: Cache) => void;
}> = ({ data, originalDocumentId, index, timelineId, onCacheLoad, eventId }) => {
}> = ({ data, originalDocumentId, index, isActiveTimelines, onCacheLoad, eventId }) => {
const { values: wrappedProcessEntityId } = data;
const { values: indices } = index;
const { values: wrappedDocumentId } = originalDocumentId;
@ -142,7 +142,7 @@ const FetchAndNotifyCachedAlertsByProcessAncestry: React.FC<{
const processEntityId = Array.isArray(wrappedProcessEntityId) ? wrappedProcessEntityId[0] : '';
const { loading, error, alertIds } = useAlertPrevalenceFromProcessTree({
processEntityId,
timelineId: timelineId ?? TimelineId.active,
isActiveTimeline: isActiveTimelines,
documentId,
indices: indices ?? [],
});
@ -173,8 +173,8 @@ FetchAndNotifyCachedAlertsByProcessAncestry.displayName =
const ActualRelatedAlertsByProcessAncestry: React.FC<{
alertIds: string[];
eventId: string;
timelineId?: string;
}> = ({ alertIds, eventId, timelineId }) => {
scopeId?: string;
}> = ({ alertIds, eventId, scopeId }) => {
const shouldUseFilters = alertIds && alertIds.length && alertIds.length >= dataProviderLimit;
const dataProviders = useMemo(() => {
if (alertIds && alertIds.length) {
@ -182,14 +182,14 @@ const ActualRelatedAlertsByProcessAncestry: React.FC<{
return null;
} else {
return alertIds.reduce<DataProvider[]>((result, alertId, index) => {
const id = `${timelineId}-${eventId}-event.id-${index}-${alertId}`;
const id = `${scopeId}-${eventId}-event.id-${index}-${alertId}`;
result.push(getDataProvider('_id', id, alertId));
return result;
}, []);
}
}
return null;
}, [alertIds, eventId, timelineId, shouldUseFilters]);
}, [alertIds, shouldUseFilters, scopeId, eventId]);
const filters: Filter[] | null = useMemo(() => {
if (shouldUseFilters) {

View file

@ -45,7 +45,7 @@ describe('RelatedAlertsBySession', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);
@ -65,7 +65,7 @@ describe('RelatedAlertsBySession', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);
@ -85,7 +85,7 @@ describe('RelatedAlertsBySession', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);
@ -109,7 +109,7 @@ describe('RelatedAlertsBySession', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);

View file

@ -8,6 +8,7 @@
import React, { useCallback } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { isActiveTimeline } from '../../../../helpers';
import type { BrowserFields } from '../../../containers/source';
import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline';
import { useActionCellDataProvider } from '../table/use_action_cell_data_provider';
@ -24,7 +25,7 @@ interface Props {
browserFields: BrowserFields;
data: TimelineEventsDetailsItem;
eventId: string;
timelineId: string;
scopeId: string;
}
/**
@ -35,12 +36,12 @@ interface Props {
* the related alerts in a timeline investigation.
*/
export const RelatedAlertsBySession = React.memo<Props>(
({ browserFields, data, eventId, timelineId }) => {
({ browserFields, data, eventId, scopeId }) => {
const { field, values } = data;
const { error, count, alertIds } = useAlertPrevalence({
field,
value: values,
timelineId: timelineId ?? '',
isActiveTimelines: isActiveTimeline(scopeId),
signalIndexName: null,
includeAlertIds: true,
ignoreTimerange: true,
@ -48,17 +49,17 @@ export const RelatedAlertsBySession = React.memo<Props>(
const { fieldFromBrowserField } = getEnrichedFieldInfo({
browserFields,
contextId: timelineId,
contextId: scopeId,
eventId,
field: { id: data.field },
timelineId,
scopeId,
item: data,
});
const cellData = useActionCellDataProvider({
field,
values,
contextId: timelineId,
contextId: scopeId,
eventId,
fieldFromBrowserField,
fieldFormat: fieldFromBrowserField?.format,

View file

@ -45,7 +45,7 @@ describe('RelatedAlertsBySourceEvent', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);
@ -65,7 +65,7 @@ describe('RelatedAlertsBySourceEvent', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);
@ -85,7 +85,7 @@ describe('RelatedAlertsBySourceEvent', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);
@ -109,7 +109,7 @@ describe('RelatedAlertsBySourceEvent', () => {
browserFields={{}}
data={testData}
eventId={testEventId}
timelineId=""
scopeId=""
/>
</TestProviders>
);

View file

@ -8,6 +8,7 @@
import React, { useCallback } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { isActiveTimeline } from '../../../../helpers';
import type { BrowserFields } from '../../../containers/source';
import type { TimelineEventsDetailsItem } from '../../../../../common/search_strategy/timeline';
import { useActionCellDataProvider } from '../table/use_action_cell_data_provider';
@ -29,7 +30,7 @@ interface Props {
browserFields: BrowserFields;
data: TimelineEventsDetailsItem;
eventId: string;
timelineId: string;
scopeId: string;
}
/**
@ -40,29 +41,29 @@ interface Props {
* the related alerts in a timeline investigation.
*/
export const RelatedAlertsBySourceEvent = React.memo<Props>(
({ browserFields, data, eventId, timelineId }) => {
({ browserFields, data, eventId, scopeId }) => {
const { field, values } = data;
const { error, count, alertIds } = useAlertPrevalence({
field,
value: values,
timelineId: timelineId ?? '',
isActiveTimelines: isActiveTimeline(scopeId),
signalIndexName: null,
includeAlertIds: true,
});
const { fieldFromBrowserField } = getEnrichedFieldInfo({
browserFields,
contextId: timelineId,
contextId: scopeId,
eventId,
field: { id: data.field },
timelineId,
scopeId,
item: data,
});
const cellData = useActionCellDataProvider({
field,
values,
contextId: timelineId,
contextId: scopeId,
eventId,
fieldFromBrowserField,
fieldFormat: fieldFromBrowserField?.format,

View file

@ -77,10 +77,10 @@ describe('Event Details Overview Cards', () => {
const props = {
handleOnEventClosed: jest.fn(),
contextId: 'detections-page',
contextId: 'alerts-page',
eventId: 'testId',
indexName: 'testIndex',
timelineId: 'page',
scopeId: 'page',
data: [
{
category: 'kibana',

View file

@ -43,7 +43,7 @@ interface Props {
eventId: string;
handleOnEventClosed: () => void;
indexName: string;
timelineId: string;
scopeId: string;
isReadOnly?: boolean;
}
@ -55,7 +55,7 @@ export const Overview = React.memo<Props>(
eventId,
handleOnEventClosed,
indexName,
timelineId,
scopeId,
isReadOnly,
}) => {
const statusData = useMemo(() => {
@ -65,12 +65,12 @@ export const Overview = React.memo<Props>(
getEnrichedFieldInfo({
eventId,
contextId,
timelineId,
scopeId,
browserFields,
item,
})
);
}, [browserFields, contextId, data, eventId, timelineId]);
}, [browserFields, contextId, data, eventId, scopeId]);
const severityData = useMemo(() => {
const item = find({ field: 'kibana.alert.severity', category: 'kibana' }, data);
@ -79,12 +79,12 @@ export const Overview = React.memo<Props>(
getEnrichedFieldInfo({
eventId,
contextId,
timelineId,
scopeId,
browserFields,
item,
})
);
}, [browserFields, contextId, data, eventId, timelineId]);
}, [browserFields, contextId, data, eventId, scopeId]);
const riskScoreData = useMemo(() => {
const item = find({ field: 'kibana.alert.risk_score', category: 'kibana' }, data);
@ -93,12 +93,12 @@ export const Overview = React.memo<Props>(
getEnrichedFieldInfo({
eventId,
contextId,
timelineId,
scopeId,
browserFields,
item,
})
);
}, [browserFields, contextId, data, eventId, timelineId]);
}, [browserFields, contextId, data, eventId, scopeId]);
const ruleNameData = useMemo(() => {
const item = find({ field: SIGNAL_RULE_NAME_FIELD_NAME, category: 'kibana' }, data);
@ -108,13 +108,13 @@ export const Overview = React.memo<Props>(
getEnrichedFieldInfo({
eventId,
contextId,
timelineId,
scopeId,
browserFields,
item,
linkValueField,
})
);
}, [browserFields, contextId, data, eventId, timelineId]);
}, [browserFields, contextId, data, eventId, scopeId]);
const signalCard =
hasData(statusData) && !isReadOnly ? (
@ -129,7 +129,7 @@ export const Overview = React.memo<Props>(
contextId={contextId}
enrichedFieldInfo={statusData}
indexName={indexName}
timelineId={timelineId}
scopeId={scopeId}
handleOnEventClosed={handleOnEventClosed}
/>
</OverviewCardWithActions>

View file

@ -8,8 +8,40 @@
import React from 'react';
import { render } from '@testing-library/react';
import { OverviewCardWithActions } from './overview_card';
import { TestProviders } from '../../../mock';
import {
createSecuritySolutionStorageMock,
kibanaObservable,
mockGlobalState,
SUB_PLUGINS_REDUCER,
TestProviders,
} from '../../../mock';
import { SeverityBadge } from '../../../../detections/components/rules/severity_badge';
import type { State } from '../../../store';
import { createStore } from '../../../store';
import { TimelineId } from '../../../../../common/types';
import { tGridReducer } from '@kbn/timelines-plugin/public';
const state: State = {
...mockGlobalState,
timeline: {
...mockGlobalState.timeline,
timelineById: {
[TimelineId.casePage]: {
...mockGlobalState.timeline.timelineById[TimelineId.test],
id: TimelineId.casePage,
},
},
},
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const props = {
title: 'Severity',
@ -18,7 +50,6 @@ const props = {
contextId: 'timeline-case',
eventId: 'testid',
fieldType: 'string',
timelineId: 'timeline-case',
data: {
field: 'kibana.alert.rule.severity',
format: 'string',
@ -44,6 +75,7 @@ const props = {
example: '',
fields: {},
},
scopeId: 'timeline-case',
},
};
@ -52,7 +84,7 @@ jest.mock('../../../lib/kibana');
describe('OverviewCardWithActions', () => {
test('it renders correctly', () => {
const { getByText } = render(
<TestProviders>
<TestProviders store={store}>
<OverviewCardWithActions {...props}>
<SeverityBadge value="medium" />
</OverviewCardWithActions>

View file

@ -13,12 +13,12 @@ import { TestProviders } from '../../../mock';
import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';
const props = {
eventId: 'testid',
contextId: 'detections-page',
contextId: 'alerts-page',
enrichedFieldInfo: {
contextId: 'detections-page',
contextId: 'alerts-page',
eventId: 'testid',
fieldType: 'string',
timelineId: 'detections-page',
scopeId: 'alerts-page',
data: {
field: 'kibana.alert.workflow_status',
format: 'string',
@ -46,7 +46,7 @@ const props = {
},
},
indexName: '.internal.alerts-security.alerts-default-000001',
timelineId: 'detections-page',
scopeId: 'alerts-page',
handleOnEventClosed: jest.fn(),
};

View file

@ -22,12 +22,12 @@ interface StatusPopoverButtonProps {
contextId: string;
enrichedFieldInfo: EnrichedFieldInfoWithValues;
indexName: string;
timelineId: string;
scopeId: string;
handleOnEventClosed: () => void;
}
export const StatusPopoverButton = React.memo<StatusPopoverButtonProps>(
({ eventId, contextId, enrichedFieldInfo, indexName, timelineId, handleOnEventClosed }) => {
({ eventId, contextId, enrichedFieldInfo, indexName, scopeId, handleOnEventClosed }) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const togglePopover = useCallback(() => setIsPopoverOpen(!isPopoverOpen), [isPopoverOpen]);
const closePopover = useCallback(() => setIsPopoverOpen(false), []);
@ -39,7 +39,7 @@ export const StatusPopoverButton = React.memo<StatusPopoverButtonProps>(
const { actionItems } = useAlertsActions({
closePopover: closeAfterAction,
eventId,
timelineId,
scopeId,
indexName,
alertStatus: enrichedFieldInfo.values[0] as Status,
});

View file

@ -48,7 +48,7 @@ const enrichedHostIpData: AlertSummaryRow['description'] = {
eventId,
fieldFromBrowserField: { ...hostIpFieldFromBrowserField },
isDraggable: false,
timelineId: TimelineId.test,
scopeId: TimelineId.test,
values: [...hostIpValues],
};

View file

@ -6,7 +6,7 @@
*/
import React, { useCallback, useState, useContext } from 'react';
import { TimelineContext } from '@kbn/timelines-plugin/public';
import { TimelineContext } from '../../../../timelines/components/timeline';
import { HoverActions } from '../../hover_actions';
import { useActionCellDataProvider } from './use_action_cell_data_provider';
import type { EnrichedFieldInfo } from '../types';
@ -34,7 +34,7 @@ export const ActionCell: React.FC<Props> = React.memo(
linkValue,
onFilterAdded,
setIsPopoverVisible,
timelineId,
scopeId,
toggleColumn,
values,
hideAddToTimeline,
@ -83,7 +83,7 @@ export const ActionCell: React.FC<Props> = React.memo(
onFilterAdded={onFilterAdded}
ownFocus={hoverActionsOwnFocus}
showTopN={showTopN}
timelineId={timelineId ?? timelineIdFind}
scopeId={scopeId ?? timelineIdFind}
toggleColumn={toggleColumn}
toggleTopN={toggleTopN}
values={actionCellConfig?.values}

View file

@ -54,7 +54,7 @@ const enrichedHostIpData: AlertSummaryRow['description'] = {
eventId,
fieldFromBrowserField: { ...hostIpFieldFromBrowserField },
isDraggable: false,
timelineId: TimelineId.test,
scopeId: TimelineId.test,
values: [...hostIpValues],
};

View file

@ -8,6 +8,7 @@
import React from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { TimelineId } from '../../../../../common/types';
import type { AlertSummaryRow } from '../helpers';
import { getEmptyTagValue } from '../../empty_value';
import { InvestigateInTimelineButton } from './investigate_in_timeline_button';
@ -22,18 +23,18 @@ const PrevalenceCell: React.FC<AlertSummaryRow['description']> = ({
eventId,
fieldFromBrowserField,
linkValue,
timelineId,
scopeId,
values,
}) => {
const { loading, count } = useAlertPrevalence({
field: data.field,
timelineId,
isActiveTimelines: scopeId === TimelineId.active,
value: values,
signalIndexName: null,
});
const cellDataProviders = useActionCellDataProvider({
contextId: timelineId,
contextId: scopeId,
eventId,
field: data.field,
fieldFormat: data.format,

View file

@ -49,7 +49,7 @@ const enrichedHostIpData: AlertSummaryRow['description'] = {
eventId,
fieldFromBrowserField: { ...hostIpFieldFromBrowserField },
isDraggable: false,
timelineId: TimelineId.test,
scopeId: TimelineId.test,
values: [...hostIpValues],
};
@ -71,7 +71,7 @@ const enrichedAgentStatusData: AlertSummaryRow['description'] = {
},
eventId,
values: [],
timelineId: TimelineId.test,
scopeId: TimelineId.test,
};
describe('SummaryValueCell', () => {
@ -90,7 +90,7 @@ describe('SummaryValueCell', () => {
test('When in the timeline flyout with timelineId active', async () => {
render(
<TestProviders>
<SummaryValueCell {...enrichedHostIpData} timelineId={TimelineId.active} />
<SummaryValueCell {...enrichedHostIpData} scopeId={TimelineId.active} />
</TestProviders>
);
hostIpValues.forEach((ipValue) => expect(screen.getByText(ipValue)).toBeInTheDocument());

View file

@ -21,7 +21,7 @@ export const SummaryValueCell: React.FC<AlertSummaryRow['description']> = ({
fieldFromBrowserField,
isDraggable,
linkValue,
timelineId,
scopeId,
values,
isReadOnly,
}) => {
@ -30,7 +30,7 @@ export const SummaryValueCell: React.FC<AlertSummaryRow['description']> = ({
return (
<>
<FieldValueCell
contextId={timelineId}
contextId={scopeId}
data={data}
eventId={eventId}
fieldFromBrowserField={fieldFromBrowserField}
@ -39,14 +39,14 @@ export const SummaryValueCell: React.FC<AlertSummaryRow['description']> = ({
style={style}
values={values}
/>
{timelineId !== TimelineId.active && !isReadOnly && hoverActionsEnabled && (
{scopeId !== TimelineId.active && !isReadOnly && hoverActionsEnabled && (
<ActionCell
contextId={timelineId}
contextId={scopeId}
data={data}
eventId={eventId}
fieldFromBrowserField={fieldFromBrowserField}
linkValue={linkValue}
timelineId={timelineId}
scopeId={scopeId}
values={values}
applyWidthAndPadding={false}
hideAddToTimeline={false}

View file

@ -21,7 +21,7 @@ export interface EnrichedFieldInfo {
data: FieldsData | EventFieldsData;
eventId: string;
fieldFromBrowserField?: BrowserField;
timelineId: string;
scopeId: string;
values: string[] | null | undefined;
linkValue?: string;
}

View file

@ -7,7 +7,7 @@
import { render } from '@testing-library/react';
import React from 'react';
import { TimelineId } from '../../../../common/types';
import { TableId } from '../../../../common/types';
import { HostsType } from '../../../hosts/store/model';
import { TestProviders } from '../../mock';
import type { EventsQueryTabBodyComponentProps } from './events_query_tab_body';
@ -76,7 +76,7 @@ describe('EventsQueryTabBody', () => {
const commonProps: EventsQueryTabBodyComponentProps = {
indexNames: ['test-index'],
setQuery: jest.fn(),
timelineId: TimelineId.test,
tableId: TableId.test,
type: HostsType.page,
endDate: new Date('2000').toISOString(),
startDate: new Date('2000').toISOString(),

View file

@ -12,10 +12,10 @@ import { EuiCheckbox } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import type { EntityType } from '@kbn/timelines-plugin/common';
import type { TimelineId } from '../../../../common/types/timeline';
import { dataTableActions } from '../../store/data_table';
import type { TableId } from '../../../../common/types/timeline';
import { RowRendererId } from '../../../../common/types/timeline';
import { StatefulEventsViewer } from '../events_viewer';
import { timelineActions } from '../../../timelines/store/timeline';
import { eventsDefaultModel } from '../events_viewer/default_model';
import { MatrixHistogram } from '../matrix_histogram';
import { useGlobalFullScreen } from '../../containers/use_full_screen';
@ -57,7 +57,7 @@ export type EventsQueryTabBodyComponentProps = QueryTabBodyProps & {
pageFilters?: Filter[];
externalAlertPageFilters?: Filter[];
setQuery: GlobalTimeArgs['setQuery'];
timelineId: TimelineId;
tableId: TableId;
};
const EXTERNAL_ALERTS_URL_PARAM = 'onlyExternalAlerts';
@ -71,7 +71,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
pageFilters = [],
setQuery,
startDate,
timelineId,
tableId,
}) => {
const dispatch = useDispatch();
const { globalFullScreen } = useGlobalFullScreen();
@ -100,8 +100,8 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
useEffect(() => {
dispatch(
timelineActions.initializeTGridSettings({
id: timelineId,
dataTableActions.initializeTGridSettings({
id: tableId,
defaultColumns: eventsDefaultModel.columns.map((c) =>
!tGridEnabled && c.initialWidth == null
? {
@ -110,10 +110,9 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
}
: c
),
excludedRowRendererIds: showExternalAlerts ? Object.values(RowRendererId) : undefined,
})
);
}, [dispatch, showExternalAlerts, tGridEnabled, timelineId]);
}, [dispatch, showExternalAlerts, tGridEnabled, tableId]);
useEffect(() => {
return () => {
@ -178,7 +177,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
renderCellValue={DefaultCellRenderer}
rowRenderers={defaultRowRenderers}
scopeId={SourcererScopeName.default}
id={timelineId}
tableId={tableId}
unit={showExternalAlerts ? i18n.ALERTS_UNIT : i18n.EVENTS_UNIT}
defaultModel={defaultModel}
pageFilters={composedPageFilters}

View file

@ -5,11 +5,11 @@
* 2.0.
*/
import { tableDefaults } from '../../store/data_table/defaults';
import type { SubsetTGridModel } from '../../store/data_table/model';
import { defaultEventHeaders } from './default_event_headers';
import type { SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
export const eventsDefaultModel: SubsetTimelineModel = {
...timelineDefaults,
export const eventsDefaultModel: SubsetTGridModel = {
...tableDefaults,
columns: defaultEventHeaders,
};

View file

@ -16,7 +16,7 @@ import { mockEventViewerResponse } from './mock';
import { StatefulEventsViewer } from '.';
import { eventsDefaultModel } from './default_model';
import { EntityType } from '@kbn/timelines-plugin/common';
import { TimelineId } from '../../../../common/types/timeline';
import { TableId } from '../../../../common/types/timeline';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import { useTimelineEvents } from '../../../timelines/containers';
@ -60,7 +60,7 @@ const testProps = {
end: to,
entityType: EntityType.ALERTS,
indexNames: [],
id: TimelineId.test,
tableId: TableId.test,
leadingControlColumns: getDefaultControlColumn(ACTION_BUTTON_COUNT),
renderCellValue: DefaultCellRenderer,
rowRenderers: defaultRowRenderers,

View file

@ -9,17 +9,15 @@ import React, { useRef, useCallback, useMemo, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import styled from 'styled-components';
import type { Filter } from '@kbn/es-query';
import type { EntityType } from '@kbn/timelines-plugin/common';
import type { EntityType, RowRenderer } from '@kbn/timelines-plugin/common';
import type { TGridCellAction } from '@kbn/timelines-plugin/common/types';
import type { ControlColumnProps, TableId } from '../../../../common/types';
import { dataTableActions } from '../../store/data_table';
import { InputsModelId } from '../../store/inputs/constants';
import { useBulkAddToCaseActions } from '../../../detections/components/alerts_table/timeline_actions/use_bulk_add_to_case_actions';
import type { inputsModel, State } from '../../store';
import { inputsActions } from '../../store/actions';
import type { ControlColumnProps, RowRenderer } from '../../../../common/types/timeline';
import { TimelineId } from '../../../../common/types/timeline';
import { APP_UI_ID } from '../../../../common/constants';
import { timelineActions } from '../../../timelines/store/timeline';
import type { SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import type { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { InspectButtonContainer } from '../inspect';
import { useGlobalFullScreen } from '../../containers/use_full_screen';
@ -38,6 +36,7 @@ import {
useSessionViewNavigation,
useSessionView,
} from '../../../timelines/components/timeline/session_tab_content/use_session_view';
import type { SubsetTGridModel } from '../../store/data_table/model';
const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
@ -50,10 +49,10 @@ const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>`
export interface Props {
defaultCellActions?: TGridCellAction[];
defaultModel: SubsetTimelineModel;
defaultModel: SubsetTGridModel;
end: string;
entityType: EntityType;
id: TimelineId;
tableId: TableId;
leadingControlColumns: ControlColumnProps[];
scopeId: SourcererScopeName;
start: string;
@ -78,7 +77,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
defaultModel,
end,
entityType,
id,
tableId,
leadingControlColumns,
pageFilters,
currentFilter,
@ -97,22 +96,18 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
input,
query,
globalQueries,
timelineQuery,
timeline: {
dataTable: {
columns,
dataProviders,
defaultColumns,
deletedEventIds,
excludedRowRendererIds,
graphEventId, // If truthy, the graph viewer (Resolver) is showing
itemsPerPage,
itemsPerPageOptions,
kqlMode,
sessionViewConfig,
showCheckboxes,
sort,
} = defaultModel,
} = useSelector((state: State) => eventsViewerSelector(state, id));
} = useSelector((state: State) => eventsViewerSelector(state, tableId));
const { timelines: timelinesUi } = useKibana().services;
const {
@ -133,12 +128,11 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
useEffect(() => {
dispatch(
timelineActions.createTimeline({
dataTableActions.createTGrid({
columns,
dataViewId: selectedDataViewId,
defaultColumns,
excludedRowRendererIds,
id,
id: tableId,
indexNames: selectedPatterns,
itemsPerPage,
showCheckboxes,
@ -147,7 +141,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
);
return () => {
dispatch(inputsActions.deleteOneQuery({ id, inputId: InputsModelId.global }));
dispatch(inputsActions.deleteOneQuery({ id: tableId, inputId: InputsModelId.global }));
if (editorActionsRef.current) {
// eslint-disable-next-line react-hooks/exhaustive-deps
editorActionsRef.current.closeEditor();
@ -160,28 +154,34 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
const trailingControlColumns: ControlColumnProps[] = EMPTY_CONTROL_COLUMNS;
const { Navigation } = useSessionViewNavigation({
timelineId: id,
scopeId: tableId,
});
const { DetailsPanel, SessionView } = useSessionView({
entityType,
timelineId: id,
scopeId: tableId,
});
const graphOverlay = useMemo(() => {
const shouldShowOverlay =
(graphEventId != null && graphEventId.length > 0) || sessionViewConfig != null;
return shouldShowOverlay ? (
<GraphOverlay timelineId={id} SessionView={SessionView} Navigation={Navigation} />
<GraphOverlay scopeId={tableId} SessionView={SessionView} Navigation={Navigation} />
) : null;
}, [graphEventId, id, sessionViewConfig, SessionView, Navigation]);
}, [graphEventId, tableId, sessionViewConfig, SessionView, Navigation]);
const setQuery = useCallback(
(inspect, loading, refetch) => {
dispatch(
inputsActions.setQuery({ id, inputId: InputsModelId.global, inspect, loading, refetch })
inputsActions.setQuery({
id: tableId,
inputId: InputsModelId.global,
inspect,
loading,
refetch,
})
);
},
[dispatch, id]
[dispatch, tableId]
);
const refetchQuery = (newQueries: inputsModel.GlobalQuery[]) => {
@ -192,25 +192,22 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
const bulkActions = useMemo(
() => ({
onAlertStatusActionSuccess: () => {
if (id === TimelineId.active) {
refetchQuery([timelineQuery]);
} else {
refetchQuery(globalQueries);
}
refetchQuery(globalQueries);
},
customBulkActions: addToCaseBulkActions,
}),
[addToCaseBulkActions, globalQueries, id, timelineQuery]
[addToCaseBulkActions, globalQueries]
);
const fieldBrowserOptions = useFieldBrowserOptions({
sourcererScope: scopeId,
timelineId: id,
editorActionsRef,
upsertColumn: (column, index) =>
dispatch(dataTableActions.upsertColumn({ column, id: tableId, index })),
removeColumn: (columnId) => dispatch(dataTableActions.removeColumn({ columnId, id: tableId })),
});
const isLive = input.policy.kind === 'interval';
return (
<>
<FullScreenContainer $isFullScreen={globalFullScreen}>
@ -221,7 +218,6 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
browserFields,
bulkActions,
columns,
dataProviders,
dataViewId,
defaultCellActions,
deletedEventIds,
@ -236,14 +232,13 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
graphEventId,
graphOverlay,
hasAlertsCrud,
id,
id: tableId,
indexNames: selectedPatterns,
indexPattern,
isLive,
isLoadingIndexPattern,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
leadingControlColumns,
onRuleChange,
query,

View file

@ -12,7 +12,7 @@ import { eventsViewerSelector } from '.';
describe('selectors', () => {
describe('eventsViewerSelector', () => {
it('returns the expected results', () => {
const id = 'detections-page';
const id = 'alerts-page';
expect(eventsViewerSelector(mockState, id)).toEqual({
filters: mockState.inputs.global.filters,
@ -20,7 +20,7 @@ describe('selectors', () => {
query: mockState.inputs.global.query,
globalQueries: mockState.inputs.global.queries,
timelineQuery: mockState.inputs.timeline.queries[0],
timeline: mockState.timeline.timelineById[id],
dataTable: mockState.dataTable.tableById[id],
});
});
});

View file

@ -7,6 +7,7 @@
import { createSelector } from 'reselect';
import { getTableByIdSelector } from '../../../store/data_table/selectors';
import {
getTimelineSelector,
globalFiltersQuerySelector,
@ -14,12 +15,11 @@ import {
globalQuerySelector,
timelineQueryByIdSelector,
} from '../../../store/inputs/selectors';
import { getTimelineByIdSelector } from '../../../../timelines/store/timeline/selectors';
/**
* This selector is invoked with two arguments:
* @param state - the state of the store as defined by State in common/store/types.ts
* @param id - a timeline id e.g. `detections-page`
* @param id - a timeline id e.g. `alerts-page`
*
* Example:
* `useSelector((state: State) => eventsViewerSelector(state, id))`
@ -30,8 +30,8 @@ export const eventsViewerSelector = createSelector(
globalQuerySelector(),
globalQuery(),
timelineQueryByIdSelector(),
getTimelineByIdSelector(),
(filters, input, query, globalQueries, timelineQuery, timeline) => ({
getTableByIdSelector(),
(filters, input, query, globalQueries, timelineQuery, dataTable) => ({
/** an array representing filters added to the search bar */
filters,
/** an object containing the timerange set in the global date picker, and other page level state */
@ -42,7 +42,7 @@ export const eventsViewerSelector = createSelector(
globalQueries,
/** an object with metadata and actions related to the table query */
timelineQuery,
/** a specific timeline from the state's timelineById collection, or undefined */
timeline,
/** a specific data table from the state's tableById collection, or undefined */
dataTable,
})
);

View file

@ -54,7 +54,7 @@ const query = { query: 'host.ip: *', language: 'kuery' };
const globalQueries = [
{
id: 'detections-page',
id: 'alerts-page',
inspect: {
dsl: ['{\n "allow_no_indices": ...}'],
},
@ -86,7 +86,7 @@ const globalQueries = [
const timelineQueries = [
{
id: 'detections-page',
id: 'alerts-page',
inspect: {
dsl: ['{\n "allow_no_indices": ...}'],
},
@ -97,7 +97,7 @@ const timelineQueries = [
];
const timeline = {
id: 'detections-page',
id: 'alerts-page',
columns: [
{ columnHeaderType: 'not-filtered', id: '@timestamp', initialWidth: 200 },
{
@ -201,7 +201,6 @@ const timeline = {
sort: [{ columnId: '@timestamp', columnType: 'date', sortDirection: 'desc' }],
savedObjectId: null,
version: null,
footerText: 'alerts',
title: '',
initialized: true,
activeTab: 'query',
@ -244,5 +243,5 @@ export const mockState = pipe(
(state) => set(state, 'inputs.global.query', query),
(state) => set(state, 'inputs.global.queries', globalQueries),
(state) => set(state, 'inputs.timeline.queries', timelineQueries),
(state) => set(state, 'timeline.timelineById.detections-page', timeline)
(state) => set(state, 'timeline.timelineById.alerts-page', timeline)
)(mockGlobalState);

View file

@ -10,6 +10,7 @@ import React from 'react';
import { mockCasesContext } from '@kbn/cases-plugin/public/mocks/mock_cases_context';
import { TestProviders } from '../../../mock';
import { ShowTopNButton } from './show_top_n';
import { TimelineId } from '../../../../../common/types';
jest.mock('../../visualization_actions', () => ({
VisualizationActions: jest.fn(() => <div data-test-subj="mock-viz-actions" />),
@ -39,7 +40,7 @@ describe('show topN button', () => {
onClick: jest.fn(),
ownFocus: false,
showTopN: false,
timelineId: 'timeline-1',
scopeId: TimelineId.active,
value: ['rule_name'],
};
@ -178,9 +179,7 @@ describe('show topN button', () => {
expect(wrapper.find('[data-test-subj="top-n"]').prop('toggleTopN')).toEqual(
testProps.onClick
);
expect(wrapper.find('[data-test-subj="top-n"]').prop('timelineId')).toEqual(
testProps.timelineId
);
expect(wrapper.find('[data-test-subj="top-n"]').prop('scopeId')).toEqual(testProps.scopeId);
expect(wrapper.find('[data-test-subj="top-n"]').prop('onFilterAdded')).toEqual(
testProps.onFilterAdded
);

View file

@ -10,13 +10,14 @@ import type { EuiButtonEmpty, EuiContextMenuItem } from '@elastic/eui';
import { EuiPopover, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import type { Filter } from '@kbn/es-query';
import { isActiveTimeline } from '../../../../helpers';
import { StatefulTopN } from '../../top_n';
import { TimelineId } from '../../../../../common/types/timeline';
import { SourcererScopeName } from '../../../store/sourcerer/model';
import { useSourcererDataView } from '../../../containers/sourcerer';
import { TooltipWithKeyboardShortcut } from '../../accessibility';
import { getAdditionalScreenReaderOnlyContext } from '../utils';
import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants';
import { isDetectionsAlertsTable } from '../../top_n/helpers';
const SHOW_TOP = (fieldName: string) =>
i18n.translate('xpack.securitySolution.hoverActions.showTopTooltip', {
@ -44,7 +45,7 @@ interface Props {
showTooltip?: boolean;
showTopN: boolean;
showLegend?: boolean;
timelineId?: string | null;
scopeId?: string | null;
title?: string;
value?: string[] | string | null;
}
@ -66,20 +67,16 @@ export const ShowTopNButton: React.FC<Props> = React.memo(
showLegend,
showTooltip = true,
showTopN,
timelineId,
scopeId,
title,
value,
globalFilters,
}) => {
const activeScope: SourcererScopeName =
timelineId === TimelineId.active
? SourcererScopeName.timeline
: timelineId != null &&
[TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
timelineId as TimelineId
)
? SourcererScopeName.detections
: SourcererScopeName.default;
const activeScope: SourcererScopeName = isActiveTimeline(scopeId ?? '')
? SourcererScopeName.timeline
: scopeId != null && isDetectionsAlertsTable(scopeId)
? SourcererScopeName.detections
: SourcererScopeName.default;
const { browserFields, indexPattern } = useSourcererDataView(activeScope);
const icon = iconType ?? 'visBarVertical';
@ -147,7 +144,7 @@ export const ShowTopNButton: React.FC<Props> = React.memo(
onFilterAdded={onFilterAdded}
paddingSize={paddingSize}
showLegend={showLegend}
timelineId={timelineId ?? undefined}
scopeId={scopeId ?? undefined}
toggleTopN={onClick}
value={value}
globalFilters={globalFilters}
@ -160,7 +157,7 @@ export const ShowTopNButton: React.FC<Props> = React.memo(
onFilterAdded,
paddingSize,
showLegend,
timelineId,
scopeId,
onClick,
value,
globalFilters,

View file

@ -107,7 +107,7 @@ interface Props {
ownFocus: boolean;
showOwnFocus?: boolean;
showTopN: boolean;
timelineId?: string | null;
scopeId?: string | null;
toggleColumn?: (column: ColumnHeaderOptions) => void;
toggleTopN: () => void;
values?: string[] | string | null;
@ -149,7 +149,7 @@ export const HoverActions: React.FC<Props> = React.memo(
ownFocus,
showOwnFocus = true,
showTopN,
timelineId,
scopeId,
toggleColumn,
toggleTopN,
values,
@ -178,11 +178,11 @@ export const HoverActions: React.FC<Props> = React.memo(
const defaultFocusedButtonRef = useRef<HTMLButtonElement | null>(null);
useEffect(() => {
if (isInit.current && goGetTimelineId != null && timelineId == null) {
if (isInit.current && goGetTimelineId != null && scopeId == null) {
isInit.current = false;
goGetTimelineId(true);
}
}, [goGetTimelineId, timelineId]);
}, [goGetTimelineId, scopeId]);
useEffect(() => {
if (ownFocus) {
@ -215,7 +215,7 @@ export const HoverActions: React.FC<Props> = React.memo(
[ownFocus, toggleTopN]
);
const isCaseView = timelineId === TimelineId.casePage;
const isCaseView = scopeId === TimelineId.casePage;
const { overflowActionItems, allActionItems } = useHoverActionItems({
dataProvider,
@ -237,10 +237,10 @@ export const HoverActions: React.FC<Props> = React.memo(
ownFocus,
showTopN,
stKeyboardEvent,
timelineId,
toggleColumn,
toggleTopN,
values,
scopeId,
});
const Container = applyWidthAndPadding

View file

@ -13,15 +13,15 @@ import { isEmpty } from 'lodash';
import { FilterManager } from '@kbn/data-plugin/public';
import { useDispatch } from 'react-redux';
import { isActiveTimeline } from '../../../helpers';
import { timelineSelectors } from '../../../timelines/store/timeline';
import { useKibana } from '../../lib/kibana';
import { allowTopN } from '../drag_and_drop/helpers';
import { useDeepEqualSelector } from '../../hooks/use_selector';
import type { ColumnHeaderOptions, DataProvider } from '../../../../common/types/timeline';
import { TimelineId } from '../../../../common/types/timeline';
import { timelineSelectors } from '../../../timelines/store/timeline';
import { ShowTopNButton } from './actions/show_top_n';
import { addProvider } from '../../../timelines/store/timeline/actions';
import { useDeepEqualSelector } from '../../hooks/use_selector';
export interface UseHoverActionItemsProps {
dataProvider?: DataProvider | DataProvider[];
dataType?: string;
@ -43,7 +43,7 @@ export interface UseHoverActionItemsProps {
ownFocus: boolean;
showTopN: boolean;
stKeyboardEvent: React.KeyboardEvent<Element> | undefined;
timelineId?: string | null;
scopeId?: string | null;
toggleColumn?: (column: ColumnHeaderOptions) => void;
toggleTopN: () => void;
values?: string[] | string | null;
@ -75,7 +75,7 @@ export const useHoverActionItems = ({
ownFocus,
showTopN,
stKeyboardEvent,
timelineId,
scopeId,
toggleColumn,
toggleTopN,
values,
@ -96,16 +96,17 @@ export const useHoverActionItems = ({
() => kibana.services.data.query.filterManager,
[kibana.services.data.query.filterManager]
);
const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
const { filterManager: activeFilterManager } = useDeepEqualSelector((state) =>
getManageTimeline(state, timelineId ?? '')
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
const activeFilterManager = useDeepEqualSelector((state) =>
isActiveTimeline(scopeId ?? '') ? getTimeline(state, scopeId ?? '')?.filterManager : undefined
);
const filterManager = useMemo(
() =>
timelineId === TimelineId.active
isActiveTimeline(scopeId ?? '')
? activeFilterManager ?? new FilterManager(uiSettings)
: filterManagerBackup,
[uiSettings, timelineId, activeFilterManager, filterManagerBackup]
[scopeId, activeFilterManager, uiSettings, filterManagerBackup]
);
/*
@ -152,19 +153,19 @@ export const useHoverActionItems = ({
ownFocus={ownFocus}
showTopN={showTopN}
showTooltip={enableOverflowButton ? false : true}
timelineId={timelineId}
scopeId={scopeId}
value={values}
/>
),
[
enableOverflowButton,
field,
isCaseView,
field,
toggleTopN,
onFilterAdded,
ownFocus,
showTopN,
timelineId,
toggleTopN,
scopeId,
values,
]
);

View file

@ -7,7 +7,8 @@
import React, { useCallback, useMemo, useState, useRef, useContext } from 'react';
import type { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd';
import { TimelineContext } from '@kbn/timelines-plugin/public';
import { TableContext } from '@kbn/timelines-plugin/public';
import { TimelineContext } from '../../../timelines/components/timeline';
import { HoverActions } from '.';
import type { DataProvider } from '../../../../common/types';
@ -34,7 +35,7 @@ interface Props {
isDraggable?: boolean;
inline?: boolean;
render: RenderFunctionProp;
timelineId?: string;
scopeId?: string;
truncate?: boolean;
onFilterAdded?: () => void;
}
@ -47,14 +48,19 @@ export const useHoverActions = ({
isDraggable,
onFilterAdded,
render,
timelineId,
scopeId,
}: Props) => {
const { timelineId: timelineIdFind } = useContext(TimelineContext);
const { tableId: tableIdFind } = useContext(TableContext);
const containerRef = useRef<HTMLDivElement | null>(null);
const keyboardHandlerRef = useRef<HTMLDivElement | null>(null);
const [closePopOverTrigger, setClosePopOverTrigger] = useState(false);
const [showTopN, setShowTopN] = useState<boolean>(false);
const [hoverActionsOwnFocus, setHoverActionsOwnFocus] = useState<boolean>(false);
const { timelineId: timelineIdFind } = useContext(TimelineContext);
const id = useMemo(
() => (!scopeId ? timelineIdFind ?? tableIdFind : scopeId),
[scopeId, tableIdFind, timelineIdFind]
);
const handleClosePopOverTrigger = useCallback(() => {
setClosePopOverTrigger((prevClosePopOverTrigger) => !prevClosePopOverTrigger);
@ -114,7 +120,7 @@ export const useHoverActions = ({
ownFocus={hoverActionsOwnFocus}
showOwnFocus={false}
showTopN={showTopN}
timelineId={timelineId ?? timelineIdFind}
scopeId={id}
toggleTopN={toggleTopN}
values={
typeof dataProvider.queryMatch.value !== 'number'
@ -124,19 +130,18 @@ export const useHoverActions = ({
/>
);
}, [
closeTopN,
dataProvider,
fieldType,
handleClosePopOverTrigger,
hideTopN,
hoverActionsOwnFocus,
isAggregatable,
isDraggable,
onFilterAdded,
render,
showTopN,
timelineId,
timelineIdFind,
dataProvider,
render,
closeTopN,
handleClosePopOverTrigger,
isDraggable,
isAggregatable,
fieldType,
hideTopN,
onFilterAdded,
id,
toggleTopN,
]);

View file

@ -23,6 +23,7 @@ import { upsertQuery } from '../../store/inputs/helpers';
import { InspectButton } from '.';
import { cloneDeep } from 'lodash/fp';
import { InputsModelId } from '../../store/inputs/constants';
import { tGridReducer } from '@kbn/timelines-plugin/public';
jest.mock('./modal', () => ({
ModalInspectQuery: jest.fn(() => <div data-test-subj="mocker-modal" />),
@ -41,13 +42,25 @@ describe('Inspect Button', () => {
state: state.inputs,
};
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
let store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
describe('Render', () => {
beforeEach(() => {
const myState = cloneDeep(state);
myState.inputs = upsertQuery(newQuery);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
});
test('Eui Empty Button', () => {
const wrapper = mount(
@ -153,7 +166,13 @@ describe('Inspect Button', () => {
const myQuery = cloneDeep(newQuery);
myQuery.inspect = null;
myState.inputs = upsertQuery(myQuery);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />
@ -170,7 +189,13 @@ describe('Inspect Button', () => {
response: ['my response'],
};
myState.inputs = upsertQuery(myQuery);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />
@ -187,7 +212,13 @@ describe('Inspect Button', () => {
response: [],
};
myState.inputs = upsertQuery(myQuery);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />
@ -206,7 +237,13 @@ describe('Inspect Button', () => {
response: ['my response'],
};
myState.inputs = upsertQuery(myQuery);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
});
test('Open Inspect Modal', () => {
const wrapper = mount(
@ -251,7 +288,13 @@ describe('Inspect Button', () => {
};
myState.inputs = upsertQuery(myQuery);
myState.inputs.global.queries[0].isInspected = true;
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />
@ -270,7 +313,13 @@ describe('Inspect Button', () => {
};
myState.inputs = upsertQuery(myQuery);
myState.inputs.global.queries[0].isInspected = false;
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />
@ -289,7 +338,13 @@ describe('Inspect Button', () => {
};
myState.inputs = upsertQuery(myQuery);
myState.inputs.global.queries[0].isInspected = true;
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />
@ -308,7 +363,13 @@ describe('Inspect Button', () => {
};
myState.inputs = upsertQuery(myQuery);
myState.inputs.global.queries[0].isInspected = true;
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<InspectButton queryId={newQuery.id} title="My title" />

View file

@ -57,7 +57,7 @@ export type MatrixHistogramComponentProps = MatrixHistogramProps &
showLegend?: boolean;
stackByOptions: MatrixHistogramOption[];
subtitle?: string | GetSubTitle;
timelineId?: string;
scopeId?: string;
title: string | GetTitle;
};
@ -97,7 +97,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
stackByOptions,
startDate,
subtitle,
timelineId,
scopeId,
title,
titleSize,
yTickFormatter,
@ -300,7 +300,7 @@ export const MatrixHistogramComponent: React.FC<MatrixHistogramComponentProps> =
barChart={barChartData}
configs={barchartConfigs}
stackByField={selectedStackByOption.value}
timelineId={timelineId}
scopeId={scopeId}
/>
)
) : null}

View file

@ -21,6 +21,7 @@ import { coreMock } from '@kbn/core/public/mocks';
import { createStore } from '../../store';
import { inputsActions } from '../../store/inputs';
import { InputsModelId } from '../../store/inputs/constants';
import { tGridReducer } from '@kbn/timelines-plugin/public';
const mockSetAppFilters = jest.fn();
const mockFilterManager = new FilterManager(coreMock.createStart().uiSettings);
@ -137,7 +138,13 @@ describe('SearchBarComponent', () => {
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -173,7 +180,13 @@ describe('SearchBarComponent', () => {
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -188,7 +201,13 @@ describe('SearchBarComponent', () => {
it('calls useUpdateUrlParam when query query changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -213,7 +232,13 @@ describe('SearchBarComponent', () => {
it('calls useUpdateUrlParam when filters change', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -249,7 +274,13 @@ describe('SearchBarComponent', () => {
it('calls useUpdateUrlParam when savedQuery changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -282,7 +313,13 @@ describe('SearchBarComponent', () => {
describe('Timerange', () => {
it('calls useUpdateUrlParam when global timerange changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -318,7 +355,13 @@ describe('SearchBarComponent', () => {
it('calls useUpdateUrlParam when timeline timerange changes', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
render(
<TestProviders store={store}>
@ -357,7 +400,13 @@ describe('SearchBarComponent', () => {
it('initializes timerange URL param with redux date on mount', async () => {
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
jest.clearAllMocks();
render(
<TestProviders store={store}>

View file

@ -5,12 +5,11 @@
* 2.0.
*/
import { tableDefaults } from '../../store/data_table/defaults';
import type { SubsetTGridModel } from '../../store/data_table/model';
import type { ColumnHeaderOptions } from '../../../../common/types/timeline';
import { RowRendererId } from '../../../../common/types/timeline';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
import type { SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import {
COLUMN_SESSION_START,
COLUMN_EXECUTABLE,
@ -63,11 +62,10 @@ export const sessionsHeaders: ColumnHeaderOptions[] = [
export const getSessionsDefaultModel = (
columns: ColumnHeaderOptions[],
defaultColumns: ColumnHeaderOptions[]
): SubsetTimelineModel => ({
...timelineDefaults,
): SubsetTGridModel => ({
...tableDefaults,
columns,
defaultColumns,
excludedRowRendererIds: Object.values(RowRendererId),
sort: [
{
columnId: 'process.entry_leader.start',

View file

@ -10,10 +10,10 @@ import { waitFor, render } from '@testing-library/react';
import { TestProviders } from '../../mock';
import { TEST_ID, SessionsView, defaultSessionsFilter } from '.';
import type { EntityType } from '@kbn/timelines-plugin/common';
import { TimelineId } from '@kbn/timelines-plugin/common';
import type { SessionsComponentsProps } from './types';
import type { TimelineModel } from '../../../timelines/store/timeline/model';
import { useGetUserCasesPermissions } from '../../lib/kibana';
import { TableId } from '../../../../common/types';
import { licenseService } from '../../hooks/use_license';
jest.mock('../../lib/kibana');
@ -34,7 +34,7 @@ const filterQuery =
'{"bool":{"must":[],"filter":[{"match_phrase":{"host.name":{"query":"ubuntu-impish"}}}],"should":[],"must_not":[]}}';
const testProps: SessionsComponentsProps = {
timelineId: TimelineId.hostsPageSessions,
tableId: TableId.hostsPageSessions,
entityType: 'sessions',
pageFilters: [],
startDate,
@ -193,7 +193,7 @@ describe('SessionsView', () => {
it('Action tab should have 5 columns when accessed via K8S dahsboard', async () => {
render(
<TestProviders>
<SessionsView {...testProps} timelineId={TimelineId.kubernetesPageSessions} />
<SessionsView {...testProps} tableId={TableId.kubernetesPageSessions} />
</TestProviders>
);

View file

@ -19,7 +19,7 @@ import * as i18n from './translations';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
import { useLicense } from '../../hooks/use_license';
import { TimelineId } from '../../../../common/types/timeline';
import { TableId } from '../../../../common/types/timeline';
export const TEST_ID = 'security_solution:sessions_viewer:sessions_view';
export const defaultSessionsFilter: Required<Pick<Filter, 'meta' | 'query'>> = {
@ -59,7 +59,7 @@ export const defaultSessionsFilter: Required<Pick<Filter, 'meta' | 'query'>> = {
};
const SessionsViewComponent: React.FC<SessionsComponentsProps> = ({
timelineId,
tableId,
endDate,
entityType = 'sessions',
pageFilters,
@ -93,7 +93,7 @@ const SessionsViewComponent: React.FC<SessionsComponentsProps> = ({
);
const isEnterprisePlus = useLicense().isEnterprise();
const ACTION_BUTTON_COUNT =
isEnterprisePlus || timelineId === TimelineId.kubernetesPageSessions ? 5 : 4;
isEnterprisePlus || tableId === TableId.kubernetesPageSessions ? 5 : 4;
const leadingControlColumns = useMemo(
() => getDefaultControlColumn(ACTION_BUTTON_COUNT),
[ACTION_BUTTON_COUNT]
@ -109,7 +109,7 @@ const SessionsViewComponent: React.FC<SessionsComponentsProps> = ({
defaultModel={getSessionsDefaultModel(columns, defaultColumns)}
end={endDate}
entityType={entityType}
id={timelineId}
tableId={tableId}
leadingControlColumns={leadingControlColumns}
renderCellValue={DefaultCellRenderer}
rowRenderers={defaultRowRenderers}

View file

@ -7,10 +7,10 @@
import type { Filter } from '@kbn/es-query';
import type { EntityType } from '@kbn/timelines-plugin/common';
import type { QueryTabBodyProps } from '../../../hosts/pages/navigation/types';
import type { TimelineIdLiteral, ColumnHeaderOptions } from '../../../../common/types/timeline';
import type { ColumnHeaderOptions, TableIdLiteral } from '../../../../common/types/timeline';
export interface SessionsComponentsProps extends Pick<QueryTabBodyProps, 'endDate' | 'startDate'> {
timelineId: TimelineIdLiteral;
tableId: TableIdLiteral;
pageFilters: Filter[];
defaultFilters?: Filter[];
entityType?: EntityType;

View file

@ -28,6 +28,7 @@ import { useSignalHelpers } from '../../containers/sourcerer/use_signal_helpers'
import { TimelineId, TimelineType } from '../../../../common/types';
import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
import { sortWithExcludesAtEnd } from '../../../../common/utils/sourcerer';
import { tGridReducer } from '@kbn/timelines-plugin/public';
const mockDispatch = jest.fn();
@ -94,7 +95,13 @@ describe('Sourcerer component', () => {
const pollForSignalIndexMock = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
(useSourcererDataView as jest.Mock).mockReturnValue(sourcererDataView);
(useSignalHelpers as jest.Mock).mockReturnValue({ signalIndexNeedsInit: false });
});
@ -206,6 +213,7 @@ describe('Sourcerer component', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -256,6 +264,7 @@ describe('Sourcerer component', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -307,7 +316,13 @@ describe('Sourcerer component', () => {
},
};
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<Sourcerer {...defaultProps} />
@ -352,7 +367,13 @@ describe('Sourcerer component', () => {
},
};
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<Sourcerer scope={sourcererModel.SourcererScopeName.timeline} />
@ -392,6 +413,7 @@ describe('Sourcerer component', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -449,6 +471,7 @@ describe('Sourcerer component', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -517,6 +540,7 @@ describe('Sourcerer component', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -561,7 +585,13 @@ describe('Sourcerer component', () => {
},
};
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<Sourcerer scope={sourcererModel.SourcererScopeName.timeline} />
@ -605,7 +635,13 @@ describe('Sourcerer component', () => {
},
};
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const wrapper = mount(
<TestProviders store={store}>
<Sourcerer {...defaultProps} />
@ -684,7 +720,13 @@ describe('Sourcerer component', () => {
describe('sourcerer on alerts page or rules details page', () => {
let wrapper: ReactWrapper;
const { storage } = createSecuritySolutionStorageMock();
store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const testProps = {
scope: sourcererModel.SourcererScopeName.detections,
};
@ -753,7 +795,13 @@ describe('sourcerer on alerts page or rules details page', () => {
describe('timeline sourcerer', () => {
let wrapper: ReactWrapper;
const { storage } = createSecuritySolutionStorageMock();
store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const testProps = {
scope: sourcererModel.SourcererScopeName.timeline,
};
@ -843,7 +891,13 @@ describe('timeline sourcerer', () => {
},
};
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
wrapper = mount(
<TestProviders store={store}>
@ -887,7 +941,13 @@ describe('Sourcerer integration tests', () => {
beforeEach(() => {
(useSourcererDataView as jest.Mock).mockReturnValue(sourcererDataView);
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
jest.clearAllMocks();
jest.restoreAllMocks();
});
@ -932,7 +992,13 @@ describe('No data', () => {
...sourcererDataView,
indicesExist: false,
});
store = createStore(mockNoIndicesState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
mockNoIndicesState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
jest.clearAllMocks();
jest.restoreAllMocks();
});
@ -1007,7 +1073,13 @@ describe('Update available', () => {
...sourcererDataView,
activePatterns: ['myFakebeat-*'],
});
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
wrapper = mount(
<TestProviders store={store}>
@ -1136,7 +1208,13 @@ describe('Update available for timeline template', () => {
...sourcererDataView,
activePatterns: ['myFakebeat-*'],
});
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
wrapper = mount(
<TestProviders store={store}>
@ -1221,7 +1299,13 @@ describe('Missing index patterns', () => {
});
const state3 = cloneDeep(state2);
state3.timeline.timelineById[TimelineId.active].timelineType = TimelineType.default;
store = createStore(state3, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state3,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
wrapper = mount(
<TestProviders store={store}>
@ -1257,7 +1341,13 @@ describe('Missing index patterns', () => {
...sourcererDataView,
activePatterns: ['myFakebeat-*'],
});
store = createStore(state2, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state2,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
wrapper = mount(
<TestProviders store={store}>

View file

@ -43,6 +43,7 @@ import type {
} from '../../../../common/search_strategy';
import { getMockTheme } from '../../lib/kibana/kibana_react.mock';
import * as module from '../../containers/query_toggle';
import { tGridReducer } from '@kbn/timelines-plugin/public';
const from = '2019-06-15T06:00:00.000Z';
const to = '2019-06-18T06:00:00.000Z';
@ -65,7 +66,13 @@ describe('Stat Items Component', () => {
const mockTheme = getMockTheme({ eui: { euiColorMediumShade: '#ece' } });
const state: State = mockGlobalState;
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const testProps = {
description: 'HOSTS',
fields: [{ key: 'hosts', value: null, color: '#6092C0', icon: 'cross' }],

View file

@ -24,6 +24,7 @@ import { createStore } from '../../store';
import { SuperDatePicker, makeMapStateToProps } from '.';
import { cloneDeep } from 'lodash/fp';
import { InputsModelId } from '../../store/inputs/constants';
import { tGridReducer } from '@kbn/timelines-plugin/public';
jest.mock('../../lib/kibana');
const mockUseUiSetting$ = useUiSetting$ as jest.Mock;
@ -84,11 +85,23 @@ describe('SIEM Super Date Picker', () => {
describe('#SuperDatePicker', () => {
const state: State = mockGlobalState;
const { storage } = createSecuritySolutionStorageMock();
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
let store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
beforeEach(() => {
jest.clearAllMocks();
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
mockUseUiSetting$.mockImplementation((key, defaultValue) => {
const useUiSetting$Mock = createUseUiSetting$Mock();

View file

@ -7,7 +7,7 @@
import type { Filter } from '@kbn/es-query';
import { TimelineId } from '../../../../common/types/timeline';
import { TableId, TimelineId } from '../../../../common/types/timeline';
import {
alertEvents,
allEvents,
@ -22,18 +22,18 @@ import {
import { SourcererScopeName } from '../../store/sourcerer/model';
/** the following `TimelineId`s are detection alert tables */
const detectionAlertsTimelines = [TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage];
const detectionAlertsTimelines = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage];
/** the following `TimelineId`s are NOT detection alert tables */
const otherTimelines = [
TimelineId.hostsPageEvents,
TimelineId.hostsPageSessions,
TimelineId.networkPageEvents,
TableId.hostsPageEvents,
TableId.hostsPageSessions,
TableId.networkPageEvents,
TimelineId.active,
TimelineId.casePage,
TimelineId.test,
TimelineId.alternateTest,
TimelineId.kubernetesPageSessions,
TableId.alternateTest,
TableId.kubernetesPageSessions,
];
const othersWithoutActive = otherTimelines.filter((x) => x !== TimelineId.active);
@ -169,121 +169,103 @@ describe('getOptions', () => {
});
describe('isDetectionsAlertsTable', () => {
detectionAlertsTimelines.forEach((timelineId) =>
test(`it returns true for detections alerts table '${timelineId}'`, () => {
expect(isDetectionsAlertsTable(timelineId)).toEqual(true);
detectionAlertsTimelines.forEach((tableId) =>
test(`it returns true for detections alerts table '${tableId}'`, () => {
expect(isDetectionsAlertsTable(tableId)).toEqual(true);
})
);
otherTimelines.forEach((timelineId) =>
test(`it returns false for (NON alert table) timeline '${timelineId}'`, () => {
expect(isDetectionsAlertsTable(timelineId)).toEqual(false);
otherTimelines.forEach((tableId) =>
test(`it returns false for (NON alert table) timeline '${tableId}'`, () => {
expect(isDetectionsAlertsTable(tableId)).toEqual(false);
})
);
});
describe('shouldIgnoreAlertFilters', () => {
detectionAlertsTimelines.forEach((timelineId) => {
test(`it returns true when the view is 'raw' for detections alerts table '${timelineId}'`, () => {
detectionAlertsTimelines.forEach((tableId) => {
test(`it returns true when the view is 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'raw';
expect(shouldIgnoreAlertFilters({ timelineId, view })).toEqual(true);
expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(true);
});
test(`it returns false when the view is NOT 'raw' for detections alerts table '${timelineId}'`, () => {
test(`it returns false when the view is NOT 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'alert'; // the default selection for detection alert tables
expect(shouldIgnoreAlertFilters({ timelineId, view })).toEqual(false);
expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(false);
});
});
otherTimelines.forEach((timelineId) => {
test(`it returns false when the view is 'raw' for (NON alert table) timeline'${timelineId}'`, () => {
otherTimelines.forEach((tableId) => {
test(`it returns false when the view is 'raw' for (NON alert table) timeline'${tableId}'`, () => {
const view = 'raw';
expect(shouldIgnoreAlertFilters({ timelineId, view })).toEqual(false);
expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(false);
});
test(`it returns false when the view is NOT 'raw' for (NON alert table) timeline '${timelineId}'`, () => {
test(`it returns false when the view is NOT 'raw' for (NON alert table) timeline '${tableId}'`, () => {
const view = 'alert';
expect(shouldIgnoreAlertFilters({ timelineId, view })).toEqual(false);
expect(shouldIgnoreAlertFilters({ tableId, view })).toEqual(false);
});
});
});
describe('removeIgnoredAlertFilters', () => {
detectionAlertsTimelines.forEach((timelineId) => {
test(`it removes the ignored alert filters when the view is 'raw' for detections alerts table '${timelineId}'`, () => {
detectionAlertsTimelines.forEach((tableId) => {
test(`it removes the ignored alert filters when the view is 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'raw';
expect(removeIgnoredAlertFilters({ filters: allFilters, timelineId, view })).toEqual([
expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual([
hostNameFilter,
]);
});
test(`it does NOT remove any filters when the view is NOT 'raw' for detections alerts table '${timelineId}'`, () => {
test(`it does NOT remove any filters when the view is NOT 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'alert';
expect(removeIgnoredAlertFilters({ filters: allFilters, timelineId, view })).toEqual(
allFilters
);
expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual(allFilters);
});
});
otherTimelines.forEach((timelineId) => {
test(`it does NOT remove any filters when the view is 'raw' for (NON alert table) '${timelineId}'`, () => {
otherTimelines.forEach((tableId) => {
test(`it does NOT remove any filters when the view is 'raw' for (NON alert table) '${tableId}'`, () => {
const view = 'alert';
expect(removeIgnoredAlertFilters({ filters: allFilters, timelineId, view })).toEqual(
allFilters
);
expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual(allFilters);
});
test(`it does NOT remove any filters when the view is NOT 'raw' for (NON alert table '${timelineId}'`, () => {
test(`it does NOT remove any filters when the view is NOT 'raw' for (NON alert table '${tableId}'`, () => {
const view = 'alert';
expect(removeIgnoredAlertFilters({ filters: allFilters, timelineId, view })).toEqual(
allFilters
);
expect(removeIgnoredAlertFilters({ filters: allFilters, tableId, view })).toEqual(allFilters);
});
});
});
describe('getSourcererScopeName', () => {
detectionAlertsTimelines.forEach((timelineId) => {
test(`it returns the 'default' SourcererScopeName when the view is 'raw' for detections alerts table '${timelineId}'`, () => {
detectionAlertsTimelines.forEach((tableId) => {
test(`it returns the 'default' SourcererScopeName when the view is 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'raw';
expect(getSourcererScopeName({ timelineId, view })).toEqual(SourcererScopeName.default);
expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(SourcererScopeName.default);
});
test(`it returns the 'detections' SourcererScopeName when the view is NOT 'raw' for detections alerts table '${timelineId}'`, () => {
test(`it returns the 'detections' SourcererScopeName when the view is NOT 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'alert';
expect(getSourcererScopeName({ timelineId, view })).toEqual(SourcererScopeName.detections);
expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(
SourcererScopeName.detections
);
});
});
test(`it returns the 'default' SourcererScopeName when timelineId is undefined'`, () => {
const timelineId = undefined;
const tableId = undefined;
const view = 'raw';
expect(getSourcererScopeName({ timelineId, view })).toEqual(SourcererScopeName.default);
expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(SourcererScopeName.default);
});
test(`it returns the 'timeline' SourcererScopeName when the view is 'raw' for the active timeline '${TimelineId.active}'`, () => {
const view = 'raw';
expect(getSourcererScopeName({ timelineId: TimelineId.active, view })).toEqual(
SourcererScopeName.timeline
);
});
test(`it returns the 'timeline' SourcererScopeName when the view is NOT 'raw' for the active timeline '${TimelineId.active}'`, () => {
const view = 'all';
expect(getSourcererScopeName({ timelineId: TimelineId.active, view })).toEqual(
SourcererScopeName.timeline
);
});
othersWithoutActive.forEach((timelineId) => {
test(`it returns the 'default' SourcererScopeName when the view is 'raw' for (NON alert table) timeline '${timelineId}'`, () => {
othersWithoutActive.forEach((tableId) => {
test(`it returns the 'default' SourcererScopeName when the view is 'raw' for (NON alert table) timeline '${tableId}'`, () => {
const view = 'raw';
expect(getSourcererScopeName({ timelineId, view })).toEqual(SourcererScopeName.default);
expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(SourcererScopeName.default);
});
test(`it returns the 'default' SourcererScopeName when the view is NOT 'raw' for detections alerts table '${timelineId}'`, () => {
test(`it returns the 'default' SourcererScopeName when the view is NOT 'raw' for detections alerts table '${tableId}'`, () => {
const view = 'alert';
expect(getSourcererScopeName({ timelineId, view })).toEqual(SourcererScopeName.default);
expect(getSourcererScopeName({ scopeId: tableId, view })).toEqual(SourcererScopeName.default);
});
});
});

View file

@ -54,7 +54,7 @@ import {
} from '@kbn/rule-data-utils';
import type { TimelineEventsType } from '../../../../common/types/timeline';
import { TimelineId } from '../../../../common/types/timeline';
import { TimelineId, TableId } from '../../../../common/types/timeline';
import { SourcererScopeName } from '../../store/sourcerer/model';
import * as i18n from './translations';
@ -117,8 +117,8 @@ export const getOptions = (activeTimelineEventsType?: TimelineEventsType): TopNO
};
/** returns true if the specified timelineId is a detections alert table */
export const isDetectionsAlertsTable = (timelineId: string | undefined): boolean =>
timelineId === TimelineId.detectionsPage || timelineId === TimelineId.detectionsRulesDetailsPage;
export const isDetectionsAlertsTable = (tableId: string | undefined): boolean =>
tableId === TableId.alertsOnAlertsPage || tableId === TableId.alertsOnRuleDetailsPage;
/**
* The following fields are used to filter alerts tables, (i.e. tables in the
@ -185,12 +185,12 @@ export const IGNORED_ALERT_FILTERS = [
* @see IGNORED_ALERT_FILTERS
*/
export const shouldIgnoreAlertFilters = ({
timelineId,
tableId,
view,
}: {
timelineId: string | undefined;
tableId: string | undefined;
view: TimelineEventsType;
}): boolean => view === 'raw' && isDetectionsAlertsTable(timelineId);
}): boolean => view === 'raw' && isDetectionsAlertsTable(tableId);
/**
* returns a new set of `filters` that don't contain the fields specified in
@ -200,14 +200,14 @@ export const shouldIgnoreAlertFilters = ({
*/
export const removeIgnoredAlertFilters = ({
filters,
timelineId,
tableId,
view,
}: {
filters: Filter[];
timelineId: string | undefined;
tableId: string | undefined;
view: TimelineEventsType;
}): Filter[] => {
if (!shouldIgnoreAlertFilters({ timelineId, view })) {
if (!shouldIgnoreAlertFilters({ tableId, view })) {
return filters; // unmodified filters
}
@ -216,23 +216,23 @@ export const removeIgnoredAlertFilters = ({
/** returns the SourcererScopeName applicable to the specified timelineId and view */
export const getSourcererScopeName = ({
timelineId,
scopeId,
view,
}: {
timelineId: string | undefined;
scopeId: string | undefined;
view: TimelineEventsType;
}): SourcererScopeName => {
// When alerts should be ignored, use the `default` Sourcerer scope,
// because it does NOT include alert indexes:
if (shouldIgnoreAlertFilters({ timelineId, view })) {
if (shouldIgnoreAlertFilters({ tableId: scopeId, view })) {
return SourcererScopeName.default; // no alerts in this scope
}
if (isDetectionsAlertsTable(timelineId)) {
if (isDetectionsAlertsTable(scopeId)) {
return SourcererScopeName.detections;
}
if (timelineId === TimelineId.active) {
if (scopeId === TimelineId.active) {
return SourcererScopeName.timeline;
}

View file

@ -24,7 +24,8 @@ import { createStore } from '../../store';
import type { Props } from './top_n';
import { StatefulTopN } from '.';
import { TimelineId } from '../../../../common/types/timeline';
import { TableId, TimelineId } from '../../../../common/types/timeline';
import { tGridReducer } from '@kbn/timelines-plugin/public';
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
@ -93,7 +94,7 @@ const state: State = {
...mockGlobalState.timeline,
timelineById: {
[TimelineId.active]: {
...mockGlobalState.timeline.timelineById.test,
...mockGlobalState.timeline.timelineById['timeline-test'],
id: TimelineId.active,
dataProviders: [
{
@ -149,13 +150,19 @@ const state: State = {
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
let testProps = {
browserFields: mockBrowserFields,
field,
indexPattern: mockIndexPattern,
timelineId: TimelineId.hostsPageEvents,
scopeId: TableId.hostsPageEvents,
toggleTopN: jest.fn(),
onFilterAdded: jest.fn(),
value,
@ -292,13 +299,14 @@ describe('StatefulTopN', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
testProps = {
...testProps,
timelineId: TimelineId.active,
};
wrapper = mount(
<TestProviders store={store}>
<StatefulTopN {...testProps} />
<StatefulTopN
{...{
...testProps,
scopeId: TimelineId.active,
}}
/>
</TestProviders>
);
});
@ -357,7 +365,7 @@ describe('StatefulTopN', () => {
test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, async () => {
testProps = {
...testProps,
timelineId: TimelineId.detectionsPage,
scopeId: TableId.alertsOnAlertsPage,
};
const wrapper = mount(
<TestProviders store={store}>

View file

@ -11,6 +11,7 @@ import { connect } from 'react-redux';
import type { DataViewBase, Filter, Query } from '@kbn/es-query';
import { getEsQueryConfig } from '@kbn/data-plugin/common';
import { isActiveTimeline } from '../../../helpers';
import { InputsModelId } from '../../store/inputs/constants';
import { useGlobalTime } from '../../containers/use_global_time';
import type { BrowserFields } from '../../containers/source';
@ -22,7 +23,7 @@ import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { timelineSelectors } from '../../../timelines/store/timeline';
import type { TimelineModel } from '../../../timelines/store/timeline/model';
import { getOptions } from './helpers';
import { getOptions, isDetectionsAlertsTable } from './helpers';
import { TopN } from './top_n';
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
import type { AlertsStackByField } from '../../../detections/components/alerts_kpis/common/types';
@ -77,7 +78,7 @@ export interface OwnProps {
browserFields: BrowserFields;
field: string;
indexPattern: DataViewBase;
timelineId?: string;
scopeId?: string;
toggleTopN: () => void;
onFilterAdded?: () => void;
paddingSize?: 's' | 'm' | 'l' | 'none';
@ -104,20 +105,18 @@ const StatefulTopNComponent: React.FC<Props> = ({
onFilterAdded,
paddingSize,
showLegend,
timelineId,
scopeId,
toggleTopN,
value,
}) => {
const { uiSettings } = useKibana().services;
const { from, deleteQuery, setQuery, to } = useGlobalTime(false);
const options = getOptions(
timelineId === TimelineId.active ? activeTimelineEventType : undefined
);
const options = getOptions(isActiveTimeline(scopeId ?? '') ? activeTimelineEventType : undefined);
const combinedQueries = useMemo(
() =>
timelineId === TimelineId.active
isActiveTimeline(scopeId ?? '')
? combineQueries({
browserFields,
config: getEsQueryConfig(uiSettings),
@ -132,24 +131,20 @@ const StatefulTopNComponent: React.FC<Props> = ({
})?.filterQuery
: undefined,
[
activeTimelineFilters,
activeTimelineKqlQueryExpression,
scopeId,
browserFields,
uiSettings,
dataProviders,
activeTimelineFilters,
indexPattern,
kqlMode,
timelineId,
uiSettings,
activeTimelineKqlQueryExpression,
]
);
const defaultView = useMemo(
() =>
timelineId === TimelineId.detectionsPage ||
timelineId === TimelineId.detectionsRulesDetailsPage
? 'alert'
: options[0].value,
[options, timelineId]
() => (isDetectionsAlertsTable(scopeId) ? 'alert' : options[0].value),
[options, scopeId]
);
return (
@ -157,21 +152,21 @@ const StatefulTopNComponent: React.FC<Props> = ({
combinedQueries={combinedQueries}
data-test-subj="top-n"
defaultView={defaultView}
deleteQuery={timelineId === TimelineId.active ? undefined : deleteQuery}
deleteQuery={isActiveTimeline(scopeId ?? '') ? undefined : deleteQuery}
field={field as AlertsStackByField}
filters={timelineId === TimelineId.active ? EMPTY_FILTERS : globalFilters}
from={timelineId === TimelineId.active ? activeTimelineFrom : from}
filters={isActiveTimeline(scopeId ?? '') ? EMPTY_FILTERS : globalFilters}
from={isActiveTimeline(scopeId ?? '') ? activeTimelineFrom : from}
indexPattern={indexPattern}
options={options}
paddingSize={paddingSize}
query={timelineId === TimelineId.active ? EMPTY_QUERY : globalQuery}
query={isActiveTimeline(scopeId ?? '') ? EMPTY_QUERY : globalQuery}
showLegend={showLegend}
setAbsoluteRangeDatePickerTarget={
timelineId === TimelineId.active ? InputsModelId.timeline : InputsModelId.global
isActiveTimeline(scopeId ?? '') ? InputsModelId.timeline : InputsModelId.global
}
setQuery={setQuery}
timelineId={timelineId}
to={timelineId === TimelineId.active ? activeTimelineTo : to}
scopeId={scopeId}
to={isActiveTimeline(scopeId ?? '') ? activeTimelineTo : to}
toggleTopN={toggleTopN}
onFilterAdded={onFilterAdded}
value={value}

View file

@ -10,7 +10,7 @@ import { mount } from 'enzyme';
import React from 'react';
import { waitFor } from '@testing-library/react';
import { TimelineId } from '../../../../common/types';
import { TableId, TimelineId } from '../../../../common/types';
import '../../mock/match_media';
import { TestProviders, mockIndexPattern } from '../../mock';
@ -137,21 +137,18 @@ describe('TopN', () => {
});
describe('view selection', () => {
const detectionAlertsTimelines = [
TimelineId.detectionsPage,
TimelineId.detectionsRulesDetailsPage,
];
const detectionAlertsTimelines = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage];
const nonDetectionAlertTables = [
TimelineId.hostsPageEvents,
TimelineId.networkPageEvents,
TableId.hostsPageEvents,
TableId.networkPageEvents,
TimelineId.casePage,
];
test('it disables view selection when timelineId is undefined', () => {
test('it disables view selection when scopeId is undefined', () => {
const wrapper = mount(
<TestProviders>
<TopN {...testProps} timelineId={undefined} />
<TopN {...testProps} scopeId={undefined} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="view-select"]').first().props().disabled).toBe(true);
@ -160,28 +157,28 @@ describe('TopN', () => {
test('it disables view selection when timelineId is `active`', () => {
const wrapper = mount(
<TestProviders>
<TopN {...testProps} timelineId={TimelineId.active} />
<TopN {...testProps} scopeId={TimelineId.active} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="view-select"]').first().props().disabled).toBe(true);
});
detectionAlertsTimelines.forEach((timelineId) => {
test(`it enables view selection for detection alert table '${timelineId}'`, () => {
detectionAlertsTimelines.forEach((tableId) => {
test(`it enables view selection for detection alert table '${tableId}'`, () => {
const wrapper = mount(
<TestProviders>
<TopN {...testProps} timelineId={timelineId} />
<TopN {...testProps} scopeId={tableId} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="view-select"]').first().props().disabled).toBe(false);
});
});
nonDetectionAlertTables.forEach((timelineId) => {
test(`it disables view selection for NON detection alert table '${timelineId}'`, () => {
nonDetectionAlertTables.forEach((tableId) => {
test(`it disables view selection for NON detection alert table '${tableId}'`, () => {
const wrapper = mount(
<TestProviders>
<TopN {...testProps} timelineId={timelineId} />
<TopN {...testProps} scopeId={tableId} />
</TestProviders>
);
expect(wrapper.find('[data-test-subj="view-select"]').first().props().disabled).toBe(true);

View file

@ -60,7 +60,7 @@ export interface Props extends Pick<GlobalTimeArgs, 'from' | 'to' | 'deleteQuery
query: Query;
setAbsoluteRangeDatePickerTarget: InputsModelId;
showLegend?: boolean;
timelineId?: string;
scopeId?: string;
toggleTopN: () => void;
onFilterAdded?: () => void;
value?: string[] | string | null;
@ -80,7 +80,7 @@ const TopNComponent: React.FC<Props> = ({
showLegend,
setAbsoluteRangeDatePickerTarget,
setQuery,
timelineId,
scopeId,
to,
toggleTopN,
}) => {
@ -90,7 +90,7 @@ const TopNComponent: React.FC<Props> = ({
[setView]
);
const { selectedPatterns, runtimeMappings } = useSourcererDataView(
getSourcererScopeName({ timelineId, view })
getSourcererScopeName({ scopeId, view })
);
useEffect(() => {
@ -101,20 +101,20 @@ const TopNComponent: React.FC<Props> = ({
() => (
<ViewSelect
data-test-subj="view-select"
disabled={!isDetectionsAlertsTable(timelineId)}
disabled={!isDetectionsAlertsTable(scopeId)}
onChange={onViewSelected}
options={options}
valueOfSelected={view}
/>
),
[onViewSelected, options, timelineId, view]
[onViewSelected, options, scopeId, view]
);
// alert workflow statuses (e.g. open | closed) and other alert-specific
// filters must be ignored when viewing raw alerts
const applicableFilters = useMemo(
() => removeIgnoredAlertFilters({ filters, timelineId, view }),
[filters, timelineId, view]
() => removeIgnoredAlertFilters({ filters, tableId: scopeId, view }),
[filters, scopeId, view]
);
return (
@ -146,7 +146,7 @@ const TopNComponent: React.FC<Props> = ({
setQuery={setQuery}
showSpacer={false}
toggleTopN={toggleTopN}
timelineId={timelineId}
scopeId={scopeId}
to={to}
/>
) : (
@ -159,7 +159,6 @@ const TopNComponent: React.FC<Props> = ({
query={query}
showLegend={showLegend}
setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget}
timelineId={timelineId}
runtimeMappings={runtimeMappings}
/>
)}

View file

@ -25,6 +25,7 @@ import { CASES_FEATURE_ID } from '../../../../common/constants';
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
import { allCasesCapabilities, allCasesPermissions } from '../../../cases_test_utils';
import { InputsModelId } from '../../store/inputs/constants';
import { tGridReducer } from '@kbn/timelines-plugin/public';
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
return {
@ -56,7 +57,13 @@ describe('VisualizationActions', () => {
state: state.inputs,
};
let store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
let store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const props = {
lensAttributes: dnsTopDomainsLensAttributes,
queryId: 'networkDnsHistogramQuery',
@ -117,7 +124,13 @@ describe('VisualizationActions', () => {
});
const myState = cloneDeep(state);
myState.inputs = upsertQuery(newQuery);
store = createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
});
test('Should render VisualizationActions button', () => {

View file

@ -7,6 +7,7 @@
import React from 'react';
import { cloneDeep } from 'lodash/fp';
import { tGridReducer } from '@kbn/timelines-plugin/public';
import {
TestProviders,
mockGlobalState,
@ -57,7 +58,13 @@ export const mockCreateStoreWithQueryFilters = () => {
filters: filterFromSearchBar,
},
};
return createStore(myState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
return createStore(
myState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
};
export const wrapper = ({ children }: { children: React.ReactElement }) => (

View file

@ -32,7 +32,7 @@ describe('useAlertPrevalence', () => {
useAlertPrevalence({
field: 'host.name',
value: ['Host-byc3w6qlpo'],
timelineId: 'detections-page',
isActiveTimelines: false,
signalIndexName: null,
includeAlertIds: false,
}),

View file

@ -12,7 +12,6 @@ import { useGlobalTime } from '../use_global_time';
import type { GenericBuckets } from '../../../../common/search_strategy';
import { useQueryAlerts } from '../../../detections/containers/detection_engine/alerts/use_query';
import { ALERTS_QUERY_NAMES } from '../../../detections/containers/detection_engine/alerts/constants';
import { TimelineId } from '../../../../common/types';
import { useDeepEqualSelector } from '../../hooks/use_selector';
import { inputsSelectors } from '../../store';
@ -21,7 +20,7 @@ const ALERT_PREVALENCE_AGG = 'countOfAlertsWithSameFieldAndValue';
interface UseAlertPrevalenceOptions {
field: string;
value: string | string[] | undefined | null;
timelineId: string;
isActiveTimelines: boolean;
signalIndexName: string | null;
includeAlertIds?: boolean;
ignoreTimerange?: boolean;
@ -37,7 +36,7 @@ interface UserAlertPrevalenceResult {
export const useAlertPrevalence = ({
field,
value,
timelineId,
isActiveTimelines,
signalIndexName,
includeAlertIds = false,
ignoreTimerange = false,
@ -49,7 +48,7 @@ export const useAlertPrevalence = ({
let to: string | undefined;
let from: string | undefined;
if (ignoreTimerange === false) {
({ to, from } = timelineId === TimelineId.active ? timelineTime : globalTime);
({ to, from } = isActiveTimelines ? timelineTime : globalTime);
}
const [initialQuery] = useState(() =>
generateAlertPrevalenceQuery(field, value, from, to, includeAlertIds)

View file

@ -30,7 +30,7 @@ interface EntityResponse {
interface UseAlertPrevalenceFromProcessTree {
processEntityId: string;
documentId: string;
timelineId: string;
isActiveTimeline: boolean;
indices: string[];
}
@ -94,12 +94,12 @@ function useAlertDocumentAnalyzerSchema({ documentId, indices }: UseAlertDocumen
export function useAlertPrevalenceFromProcessTree({
processEntityId,
documentId,
timelineId,
isActiveTimeline,
indices,
}: UseAlertPrevalenceFromProcessTree): UserAlertPrevalenceFromProcessTreeResult {
const http = useHttp();
const { selectedPatterns } = useTimelineDataFilters(timelineId);
const { selectedPatterns } = useTimelineDataFilters(isActiveTimeline);
const alertAndOriginalIndices = [...new Set(selectedPatterns.concat(indices))];
const { loading, id, schema } = useAlertDocumentAnalyzerSchema({
documentId,

View file

@ -37,6 +37,7 @@ import { postSourcererDataView } from './api';
import * as source from '../source/use_data_view';
import { sourcererActions } from '../../store/sourcerer';
import { useInitializeUrlParam, useUpdateUrlParam } from '../../utils/global_query_string';
import { tGridReducer } from '@kbn/timelines-plugin/public';
const mockRouteSpy: RouteSpyState = {
pageName: SecurityPageName.overview,
@ -111,7 +112,13 @@ describe('Sourcerer Hooks', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
store = createStore(
mockGlobalState,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
mockUseUserInfo.mockImplementation(() => userInfoState);
});
it('initializes loading default and timeline index patterns', async () => {
@ -157,6 +164,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -259,6 +267,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -329,6 +338,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -387,6 +397,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -428,6 +439,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -470,6 +482,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -516,6 +529,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
@ -564,6 +578,7 @@ describe('Sourcerer Hooks', () => {
},
},
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);

View file

@ -17,6 +17,7 @@ import { act, renderHook } from '@testing-library/react-hooks';
import { useSignalHelpers } from './use_signal_helpers';
import type { State } from '../../store';
import { createStore } from '../../store';
import { tGridReducer } from '@kbn/timelines-plugin/public';
describe('useSignalHelpers', () => {
const wrapperContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
@ -53,7 +54,13 @@ describe('useSignalHelpers', () => {
},
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), {
wrapper: ({ children }) => <TestProviders store={store}>{children}</TestProviders>,
@ -83,7 +90,13 @@ describe('useSignalHelpers', () => {
},
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
const store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
await act(async () => {
const { result, waitForNextUpdate } = renderHook(() => useSignalHelpers(), {
wrapper: ({ children }) => <TestProviders store={store}>{children}</TestProviders>,

View file

@ -107,6 +107,7 @@ export const getAddToTimelineCellAction = ({
ownFocus: false,
showTooltip: false,
onClick: handleAddToTimelineAction,
timelineType: 'default',
};
}, [Component, columnId, dataProvider, handleAddToTimelineAction]);

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { EuiDataGridColumn } from '@elastic/eui';
import { TableId } from '../../../../common/types';
import type {
BrowserFields,
TimelineNonEcsData,
@ -20,7 +21,7 @@ describe('default cell actions', () => {
const browserFields: BrowserFields = {};
const data: TimelineNonEcsData[][] = [[]];
const ecsData: Ecs[] = [];
const timelineId = 'mockTimelineId';
const tableId = TableId.test;
const pageSize = 10;
test('columns without any link action (e.g.: signal.status) should return an empty component (not null or data grid would crash)', () => {
@ -43,7 +44,7 @@ describe('default cell actions', () => {
ecsData,
header: columnHeaders.find((h) => h.id === header.id),
pageSize,
timelineId,
scopeId: tableId,
});
return {
@ -75,7 +76,7 @@ describe('default cell actions', () => {
ecsData,
header: [columnHeaders].find((h) => h.id === header.id),
pageSize,
timelineId,
scopeId: tableId,
});
return {

View file

@ -22,7 +22,7 @@ describe('ExpandedCellValueActions', () => {
},
globalFilters: [],
onFilterAdded: () => {},
timelineId: 'mockTimelineId',
scopeId: 'mockTimelineId',
value: ['mock value'],
};
const wrapper = shallow(<ExpandedCellValueActions {...props} />);

View file

@ -19,7 +19,7 @@ import { SHOW_TOP_VALUES, HIDE_TOP_VALUES } from './translations';
interface Props {
field: ColumnHeaderOptions;
globalFilters?: Filter[];
timelineId: string;
scopeId: string;
value: string[] | undefined;
onFilterAdded?: () => void;
}
@ -38,7 +38,7 @@ const ExpandedCellValueActionsComponent: React.FC<Props> = ({
field,
globalFilters,
onFilterAdded,
timelineId,
scopeId,
value,
}) => {
const {
@ -82,7 +82,7 @@ const ExpandedCellValueActionsComponent: React.FC<Props> = ({
showLegend
showTopN={showTopN}
showTooltip={false}
timelineId={timelineId}
scopeId={scopeId}
title={showTopN ? HIDE_TOP_VALUES : SHOW_TOP_VALUES}
value={value}
/>

View file

@ -92,14 +92,14 @@ export const FieldValueCell = ({
data,
ecsData,
header,
timelineId,
scopeId,
pageSize,
closeCellPopover,
}: {
data: TimelineNonEcsData[][];
ecsData: Ecs[];
header?: ColumnHeaderOptions;
timelineId: string;
scopeId: string;
pageSize: number;
closeCellPopover?: () => void;
}) => {
@ -134,7 +134,7 @@ export const FieldValueCell = ({
return showEmpty === false ? (
<FormattedFieldValue
Component={Component}
contextId={`expanded-value-${columnId}-row-${pageRowIndex}-${timelineId}`}
contextId={`expanded-value-${columnId}-row-${pageRowIndex}-${scopeId}`}
eventId={eventId}
fieldFormat={fieldFormat}
isAggregatable={header.aggregatable ?? false}

View file

@ -1,72 +0,0 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { scaleLog } from 'd3-scale';
/**
* Polyfill is from: https://developers.google.com/web/updates/2015/08/using-requestidlecallback
* This is for Safari 12.1.2 and IE-11
*/
// do not delete
export const polyFillRequestIdleCallback = (callback: IdleRequestCallback) => {
const start = Date.now();
return setTimeout(() => {
callback({
didTimeout: false,
timeRemaining: () => {
return Math.max(0, 50 - (Date.now() - start));
},
});
}, 1);
};
/**
* This is a polyfill for "requestIdleCallback" but since it is an
* experimental web API and TypeScript does not even support the API
* properly, I left it as is within this file as a utility for us to change
* tune as needed instead of pushing the experimental API to all users
* of Kibana.
*
* NOTE: This might become obsolete once React releases its own
* scheduler with fibers (Concurrent React) and we would then remove
* this and all usages. Otherwise, just remove this note
*/
// do not delete
export const requestIdleCallbackViaScheduler = (
callback: IdleRequestCallback,
opts?: IdleRequestOptions
) => {
if ('requestIdleCallback' in window) {
window.requestIdleCallback(callback, opts);
} else {
polyFillRequestIdleCallback(callback);
}
};
/**
* Use this with any maxDelay such as
*
* data.map((event, i) => {
* const maxDelay = maxDelay(i);
* }
*
* where i is within a loop of elements. You can start at 0 and go to Infinity and it
* will clamp you at 100 milliseconds through 2000 milliseconds (2 seconds)
* to delay a render.
*
* This function guarantees that your first element gets a chance to run up to
* 100 milliseconds through the range of .range([100, 2000]).
*
* NOTE: ScaleLog cannot be given a zero, so the domain starting at 1
* like so: domain([1, 100]) is intentional.
*
* NOTE: If you go above 25 elements this will at most return 2000 milliseconds for a
* delayMax setting value meaning at most beyond 25 elements to display, they will take at most
* 2 seconds to delay before show up.
*/
// do not delete
export const maxDelay = scaleLog().domain([1, 25]).range([100, 2000]).clamp(true);

View file

@ -12,8 +12,8 @@ import type {
} from '@kbn/triggers-actions-ui-plugin/public';
import { APP_ID, CASES_FEATURE_ID } from '../../../../common/constants';
import { getTimelinesInStorageByIds } from '../../../timelines/containers/local_storage';
import { TimelineId } from '../../../../common/types';
import { getDataTablesInStorageByIds } from '../../../timelines/containers/local_storage';
import { TableId } from '../../../../common/types';
import { getColumns } from '../../../detections/configurations/security_solution_detections';
import { useRenderCellValue } from '../../../detections/configurations/security_solution_detections/render_cell_value';
import { useToGetInternalFlyout } from '../../../timelines/components/side_panel/event_details/flyout';
@ -25,8 +25,8 @@ const registerAlertsTableConfiguration = (
if (registry.has(APP_ID)) {
return;
}
const timelineStorage = getTimelinesInStorageByIds(storage, [TimelineId.detectionsPage]);
const columnsFormStorage = timelineStorage?.[TimelineId.detectionsPage]?.columns ?? [];
const dataTableStorage = getDataTablesInStorageByIds(storage, [TableId.alertsOnAlertsPage]);
const columnsFormStorage = dataTableStorage?.[TableId.alertsOnAlertsPage]?.columns ?? [];
const alertColumns = columnsFormStorage.length ? columnsFormStorage : getColumns();
registry.register({

View file

@ -24,6 +24,7 @@ import type {
} from '@testing-library/react-hooks/src/types/react';
import type { UseBaseQueryResult } from '@tanstack/react-query';
import ReactDOM from 'react-dom';
import { tGridReducer } from '@kbn/timelines-plugin/public';
import { ConsoleManager } from '../../../management/components/console';
import type { StartPlugins, StartServices } from '../../../types';
import { depsStartMock } from './dependencies_start_mock';
@ -204,10 +205,14 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
app: experimentalFeaturesReducer,
};
const store = createStore(mockGlobalState, storeReducer, kibanaObservable, storage, [
...managementMiddlewareFactory(coreStart, depsStart),
middlewareSpy.actionSpyMiddleware,
]);
const store = createStore(
mockGlobalState,
storeReducer,
{ dataTable: tGridReducer },
kibanaObservable,
storage,
[...managementMiddlewareFactory(coreStart, depsStart), middlewareSpy.actionSpyMiddleware]
);
const queryClient = new QueryClient({
defaultOptions: {

View file

@ -29,7 +29,13 @@ import {
DEFAULT_SIGNALS_INDEX,
} from '../../../common/constants';
import { networkModel } from '../../network/store';
import { TimelineType, TimelineStatus, TimelineTabs } from '../../../common/types/timeline';
import {
TimelineType,
TimelineStatus,
TimelineTabs,
TableId,
TimelineId,
} from '../../../common/types/timeline';
import { mockManagementState } from '../../management/store/reducer';
import type { ManagementState } from '../../management/types';
import { initialSourcererState, SourcererScopeName } from '../store/sourcerer/model';
@ -300,15 +306,14 @@ export const mockGlobalState: State = {
newTimelineModel: null,
},
timelineById: {
test: {
[TimelineId.test]: {
activeTab: TimelineTabs.query,
prevActiveTab: TimelineTabs.notes,
dataViewId: DEFAULT_DATA_VIEW_ID,
deletedEventIds: [],
documentType: '',
queryFields: [],
selectAll: false,
id: 'test',
id: TimelineId.test,
savedObjectId: null,
columns: defaultHeaders,
defaultColumns: defaultHeaders,
@ -328,7 +333,6 @@ export const mockGlobalState: State = {
historyIds: [],
isFavorite: false,
isLive: false,
isSelectAllChecked: false,
isLoading: false,
kqlMode: 'filter',
kqlQuery: { filterQuery: null },
@ -342,10 +346,8 @@ export const mockGlobalState: State = {
start: '2020-07-07T08:20:18.966Z',
end: '2020-07-08T08:20:18.966Z',
},
selectedEventIds: {},
sessionViewConfig: null,
show: false,
showCheckboxes: false,
pinnedEventIds: {},
pinnedEventsSaveObject: {},
itemsPerPageOptions: [5, 10, 20],
@ -360,10 +362,48 @@ export const mockGlobalState: State = {
isSaving: false,
version: null,
status: TimelineStatus.active,
isSelectAllChecked: false,
selectedEventIds: {},
},
},
insertTimeline: null,
},
dataTable: {
tableById: {
[TableId.test]: {
columns: defaultHeaders,
defaultColumns: defaultHeaders,
dataViewId: 'security-solution-default',
deletedEventIds: [],
expandedDetail: {},
filters: [],
indexNames: ['.alerts-security.alerts-default'],
isSelectAllChecked: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
loadingEventIds: [],
selectedEventIds: {},
showCheckboxes: false,
sort: [
{
columnId: '@timestamp',
columnType: 'date',
esTypes: ['date'],
sortDirection: 'desc',
},
],
graphEventId: '',
sessionViewConfig: null,
selectAll: false,
id: TableId.test,
title: '',
initialized: true,
updated: 1663882629000,
isLoading: false,
queryFields: [],
},
},
},
sourcerer: {
...mockSourcererState,
defaultDataView: {

View file

@ -18,6 +18,7 @@ import { ThemeProvider } from 'styled-components';
import type { Capabilities } from '@kbn/core/public';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { tGridReducer } from '@kbn/timelines-plugin/public';
import { ConsoleManager } from '../../management/components/console';
import type { State } from '../store';
import { createStore } from '../store';
@ -52,7 +53,13 @@ const { storage } = createSecuritySolutionStorageMock();
/** A utility for wrapping children in the providers required to run most tests */
export const TestProvidersComponent: React.FC<Props> = ({
children,
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage),
store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
),
onDragEnd = jest.fn(),
}) => {
const queryClient = new QueryClient();
@ -79,7 +86,13 @@ export const TestProvidersComponent: React.FC<Props> = ({
*/
const TestProvidersWithPrivilegesComponent: React.FC<Props> = ({
children,
store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage),
store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
),
onDragEnd = jest.fn(),
}) => (
<I18nProvider>

View file

@ -21,6 +21,7 @@ import { Direction } from '../../../common/search_strategy';
import type { CreateTimelineProps } from '../../detections/components/alerts_table/types';
import type { TimelineModel } from '../../timelines/store/timeline/model';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import type { TGridModel } from '../store/data_table/model';
export const mockOpenTimelineQueryResults = {
totalCount: 11,
@ -2010,11 +2011,9 @@ export const mockTimelineModel: TimelineModel = {
pinnedEventIds: {},
pinnedEventsSaveObject: {},
savedObjectId: 'ef579e40-jibber-jabber',
selectAll: false,
selectedEventIds: {},
sessionViewConfig: null,
show: false,
showCheckboxes: false,
sort: [
{
columnId: '@timestamp',
@ -2031,6 +2030,51 @@ export const mockTimelineModel: TimelineModel = {
version: '1',
};
export const mockTGridModel: TGridModel = {
columns: mockTimelineModelColumns,
defaultColumns: mockTimelineModelColumns,
dataViewId: null,
deletedEventIds: [],
expandedDetail: {},
filters: [
{
$state: {
store: FilterStateStore.APP_STATE,
},
meta: {
alias: null,
disabled: true,
key: 'host.name',
negate: false,
params: '"{"query":"placeholder"}"',
type: 'phrase',
},
query: { match_phrase: { 'host.name': 'placeholder' } },
},
],
id: 'ef579e40-jibber-jabber',
indexNames: [],
isLoading: false,
isSelectAllChecked: false,
queryFields: [],
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
loadingEventIds: [],
selectedEventIds: {},
sessionViewConfig: null,
sort: [
{
columnId: '@timestamp',
columnType: 'date',
esTypes: ['date'],
sortDirection: Direction.desc,
},
],
title: 'Test rule',
showCheckboxes: false,
selectAll: false,
};
export const mockGetOneTimelineResult: TimelineResult = {
savedObjectId: 'ef579e40-jibber-jabber',
columns: timelineDefaults.columns.filter((column) => column.id !== 'event.action'),
@ -2139,11 +2183,9 @@ export const defaultTimelineProps: CreateTimelineProps = {
pinnedEventsSaveObject: {},
queryFields: [],
savedObjectId: null,
selectAll: false,
selectedEventIds: {},
sessionViewConfig: null,
show: false,
showCheckboxes: false,
sort: [
{
columnId: '@timestamp',

View file

@ -5,11 +5,6 @@
* 2.0.
*/
import type { AnyAction, Reducer } from 'redux';
import reduceReducers from 'reduce-reducers';
import { tGridReducer } from '@kbn/timelines-plugin/public';
import { hostsReducer } from '../../hosts/store';
import { networkReducer } from '../../network/store';
import { usersReducer } from '../../users/store';
@ -17,9 +12,6 @@ import { timelineReducer } from '../../timelines/store/timeline/reducer';
import { managementReducer } from '../../management/store/reducer';
import type { ManagementPluginReducer } from '../../management';
import type { SubPluginsInitReducer } from '../store';
import { mockGlobalState } from './global_state';
import type { TimelineState } from '../../timelines/store/timeline/types';
import { defaultHeaders } from '../../timelines/components/timeline/body/column_headers/default_headers';
type GlobalThis = typeof globalThis;
interface Global extends GlobalThis {
@ -31,33 +23,11 @@ interface Global extends GlobalThis {
export const globalNode: Global = global;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const combineTimelineReducer = reduceReducers<any>(
{
...mockGlobalState.timeline,
timelineById: {
...mockGlobalState.timeline.timelineById,
test: {
...mockGlobalState.timeline.timelineById.test,
defaultColumns: defaultHeaders,
loadingText: 'events',
footerText: 'events',
documentType: '',
selectAll: false,
queryFields: [],
unit: (n: number) => n,
},
},
},
tGridReducer,
timelineReducer
) as Reducer<TimelineState, AnyAction>;
export const SUB_PLUGINS_REDUCER: SubPluginsInitReducer = {
hosts: hostsReducer,
network: networkReducer,
users: usersReducer,
timeline: combineTimelineReducer,
timeline: timelineReducer,
/**
* These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture,
* they are cast to mutable versions here.

View file

@ -0,0 +1,31 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export {
applyDeltaToColumnWidth,
clearEventsDeleted,
clearEventsLoading,
clearSelected,
initializeTGridSettings,
removeColumn,
setEventsDeleted,
setEventsLoading,
setSelected,
setTGridSelectAll,
toggleDetailPanel,
updateColumnOrder,
updateColumns,
updateColumnWidth,
updateIsLoading,
updateItemsPerPage,
updateItemsPerPageOptions,
updateSort,
upsertColumn,
updateGraphEventId,
updateSessionViewConfig,
createTGrid,
} from '@kbn/timelines-plugin/public';

View file

@ -0,0 +1,36 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import type { SubsetTGridModel } from './model';
export const tableDefaults: SubsetTGridModel = {
defaultColumns: defaultHeaders,
dataViewId: null,
deletedEventIds: [],
expandedDetail: {},
filters: [],
indexNames: [],
isSelectAllChecked: false,
isLoading: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
loadingEventIds: [],
selectedEventIds: {},
showCheckboxes: false,
sort: [
{
columnId: '@timestamp',
columnType: 'date',
esTypes: ['date'],
sortDirection: 'desc',
},
],
graphEventId: '',
sessionViewConfig: null,
columns: defaultHeaders,
queryFields: [],
};

View file

@ -0,0 +1,197 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { shallow } from 'enzyme';
// we don't have the types for waitFor just yet, so using "as waitFor" for when we do
import { waitFor } from '@testing-library/react';
import '../../mock/match_media';
import {
mockGlobalState,
SUB_PLUGINS_REDUCER,
TestProviders,
defaultHeaders,
createSecuritySolutionStorageMock,
kibanaObservable,
} from '../../mock';
import type { State } from '..';
import { createStore } from '..';
import {
removeColumn,
upsertColumn,
applyDeltaToColumnWidth,
updateColumnOrder,
updateColumns,
updateColumnWidth,
updateItemsPerPage,
updateSort,
} from './actions';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import type { Props as StatefulEventsViewerProps } from '../../components/events_viewer';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { addTableInStorage } from '../../../timelines/containers/local_storage';
import { Direction } from '../../../../common/search_strategy';
import { tGridReducer } from '@kbn/timelines-plugin/public';
import { StatefulEventsViewer } from '../../components/events_viewer';
import { eventsDefaultModel } from '../../components/events_viewer/default_model';
import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions';
import { EntityType } from '@kbn/timelines-plugin/common';
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
import { SourcererScopeName } from '../sourcerer/model';
import { TableId } from '../../../../common/types';
jest.mock('../../../timelines/containers/local_storage');
const addTableInStorageMock = addTableInStorage as jest.Mock;
describe('epicLocalStorage', () => {
const state: State = mockGlobalState;
const { storage } = createSecuritySolutionStorageMock();
let store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
let testProps = {} as StatefulEventsViewerProps;
beforeEach(() => {
store = createStore(
state,
SUB_PLUGINS_REDUCER,
{ dataTable: tGridReducer },
kibanaObservable,
storage
);
const from = '2019-08-27T22:10:56.794Z';
const to = '2019-08-26T22:10:56.791Z';
const ACTION_BUTTON_COUNT = 4;
testProps = {
defaultCellActions,
defaultModel: eventsDefaultModel,
end: to,
entityType: EntityType.ALERTS,
tableId: TableId.test,
leadingControlColumns: getDefaultControlColumn(ACTION_BUTTON_COUNT),
renderCellValue: DefaultCellRenderer,
rowRenderers: defaultRowRenderers,
scopeId: SourcererScopeName.default,
start: from,
};
});
it('persist adding / reordering of a column correctly', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(upsertColumn({ id: TableId.test, index: 1, column: defaultHeaders[0] }));
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persist timeline when removing a column ', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(removeColumn({ id: TableId.test, columnId: '@timestamp' }));
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persists resizing of a column', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(
applyDeltaToColumnWidth({ id: TableId.test, columnId: '@timestamp', delta: 80 })
);
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persist the resetting of the fields', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(updateColumns({ id: TableId.test, columns: defaultHeaders }));
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persist items per page', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(updateItemsPerPage({ id: TableId.test, itemsPerPage: 50 }));
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persist the sorting of a column', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(
updateSort({
id: TableId.test,
sort: [
{
columnId: 'event.severity',
columnType: 'number',
esTypes: ['long'],
sortDirection: Direction.desc,
},
],
})
);
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persists updates to the column order to local storage', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(
updateColumnOrder({
columnIds: ['event.severity', '@timestamp', 'event.category'],
id: TableId.test,
})
);
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
it('persists updates to the column width to local storage', async () => {
shallow(
<TestProviders store={store}>
<StatefulEventsViewer {...testProps} />
</TestProviders>
);
store.dispatch(
updateColumnWidth({
columnId: 'event.severity',
id: TableId.test,
width: 123,
})
);
await waitFor(() => expect(addTableInStorageMock).toHaveBeenCalled());
});
});

Some files were not shown because too many files have changed in this diff Show more