[SIEM][Timeline] Persist timeline to localStorage (#67156)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
This commit is contained in:
parent
ab1270e566
commit
47e50f8102
|
@ -197,6 +197,32 @@ export interface SavedTimeline extends runtimeTypes.TypeOf<typeof SavedTimelineR
|
|||
|
||||
export interface SavedTimelineNote extends runtimeTypes.TypeOf<typeof SavedTimelineRuntimeType> {}
|
||||
|
||||
/*
|
||||
* Timeline IDs
|
||||
*/
|
||||
|
||||
export enum TimelineId {
|
||||
hostsPageEvents = 'hosts-page-events',
|
||||
hostsPageExternalAlerts = 'hosts-page-external-alerts',
|
||||
alertsRulesDetailsPage = 'alerts-rules-details-page',
|
||||
alertsPage = 'alerts-page',
|
||||
networkPageExternalAlerts = 'network-page-external-alerts',
|
||||
active = 'timeline-1',
|
||||
test = 'test', // Reserved for testing purposes
|
||||
}
|
||||
|
||||
export const TimelineIdLiteralRt = runtimeTypes.union([
|
||||
runtimeTypes.literal(TimelineId.hostsPageEvents),
|
||||
runtimeTypes.literal(TimelineId.hostsPageExternalAlerts),
|
||||
runtimeTypes.literal(TimelineId.alertsRulesDetailsPage),
|
||||
runtimeTypes.literal(TimelineId.alertsPage),
|
||||
runtimeTypes.literal(TimelineId.networkPageExternalAlerts),
|
||||
runtimeTypes.literal(TimelineId.active),
|
||||
runtimeTypes.literal(TimelineId.test),
|
||||
]);
|
||||
|
||||
export type TimelineIdLiteral = runtimeTypes.TypeOf<typeof TimelineIdLiteralRt>;
|
||||
|
||||
/**
|
||||
* Timeline Saved object type with metadata
|
||||
*/
|
||||
|
|
|
@ -29,12 +29,12 @@ import {
|
|||
dragAndDropColumn,
|
||||
openEventsViewerFieldsBrowser,
|
||||
opensInspectQueryModal,
|
||||
resetFields,
|
||||
waitsForEventsToBeLoaded,
|
||||
} from '../tasks/hosts/events';
|
||||
import { clearSearchBar, kqlSearch } from '../tasks/security_header';
|
||||
|
||||
import { HOSTS_PAGE } from '../urls/navigation';
|
||||
import { resetFields } from '../tasks/timeline';
|
||||
|
||||
const defaultHeadersInDefaultEcsCategory = [
|
||||
{ id: '@timestamp' },
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { reload } from '../tasks/common';
|
||||
import { loginAndWaitForPage } from '../tasks/login';
|
||||
import { HOSTS_PAGE } from '../urls/navigation';
|
||||
import { openEvents } from '../tasks/hosts/main';
|
||||
import { DRAGGABLE_HEADER } from '../screens/timeline';
|
||||
import { TABLE_COLUMN_EVENTS_MESSAGE } from '../screens/hosts/external_events';
|
||||
import { waitsForEventsToBeLoaded, openEventsViewerFieldsBrowser } from '../tasks/hosts/events';
|
||||
import { removeColumn, resetFields } from '../tasks/timeline';
|
||||
|
||||
describe('persistent timeline', () => {
|
||||
before(() => {
|
||||
loginAndWaitForPage(HOSTS_PAGE);
|
||||
openEvents();
|
||||
waitsForEventsToBeLoaded();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
openEventsViewerFieldsBrowser();
|
||||
resetFields();
|
||||
});
|
||||
|
||||
it('persist the deletion of a column', () => {
|
||||
cy.get(DRAGGABLE_HEADER).then((header) => {
|
||||
const currentNumberOfTimelineColumns = header.length;
|
||||
const expectedNumberOfTimelineColumns = currentNumberOfTimelineColumns - 1;
|
||||
|
||||
cy.wrap(header).eq(TABLE_COLUMN_EVENTS_MESSAGE).invoke('text').should('equal', 'message');
|
||||
removeColumn(TABLE_COLUMN_EVENTS_MESSAGE);
|
||||
|
||||
cy.get(DRAGGABLE_HEADER).should('have.length', expectedNumberOfTimelineColumns);
|
||||
|
||||
reload(waitsForEventsToBeLoaded);
|
||||
|
||||
cy.get(DRAGGABLE_HEADER).should('have.length', expectedNumberOfTimelineColumns);
|
||||
cy.get(DRAGGABLE_HEADER).each(($el) => {
|
||||
expect($el.text()).not.equal('message');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -36,7 +36,4 @@ export const LOCAL_EVENTS_COUNT =
|
|||
export const LOAD_MORE =
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="TimelineMoreButton"';
|
||||
|
||||
export const RESET_FIELDS =
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="reset-fields"]';
|
||||
|
||||
export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]';
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const TABLE_COLUMN_EVENTS_MESSAGE = 1;
|
|
@ -21,6 +21,11 @@ export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]';
|
|||
|
||||
export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]';
|
||||
|
||||
export const REMOVE_COLUMN = '[data-test-subj="remove-column"]';
|
||||
|
||||
export const RESET_FIELDS =
|
||||
'[data-test-subj="events-viewer-panel"] [data-test-subj="reset-fields"]';
|
||||
|
||||
export const SEARCH_OR_FILTER_CONTAINER =
|
||||
'[data-test-subj="timeline-search-or-filter-search-container"]';
|
||||
|
||||
|
@ -30,6 +35,8 @@ export const TIMELINE = (id: string) => {
|
|||
return `[data-test-subj="title-${id}"]`;
|
||||
};
|
||||
|
||||
export const TIMELINE_COLUMN_SPINNER = '[data-test-subj="timeline-loading-spinner"]';
|
||||
|
||||
export const TIMELINE_DATA_PROVIDERS = '[data-test-subj="dataProviders"]';
|
||||
|
||||
export const TIMELINE_DATA_PROVIDERS_EMPTY =
|
||||
|
|
|
@ -48,3 +48,9 @@ export const drop = (dropTarget: JQuery<HTMLElement>) => {
|
|||
.trigger('mouseup', { force: true })
|
||||
.wait(1000);
|
||||
};
|
||||
|
||||
export const reload = (afterReload: () => void) => {
|
||||
cy.reload();
|
||||
cy.contains('a', 'Security');
|
||||
afterReload();
|
||||
};
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
HOST_GEO_COUNTRY_NAME_CHECKBOX,
|
||||
INSPECT_QUERY,
|
||||
LOAD_MORE,
|
||||
RESET_FIELDS,
|
||||
SERVER_SIDE_EVENT_COUNT,
|
||||
} from '../../screens/hosts/events';
|
||||
import { DRAGGABLE_HEADER } from '../../screens/timeline';
|
||||
|
@ -53,10 +52,6 @@ export const opensInspectQueryModal = () => {
|
|||
.click({ force: true });
|
||||
};
|
||||
|
||||
export const resetFields = () => {
|
||||
cy.get(RESET_FIELDS).click({ force: true });
|
||||
};
|
||||
|
||||
export const waitsForEventsToBeLoaded = () => {
|
||||
cy.get(SERVER_SIDE_EVENT_COUNT).should('exist').invoke('text').should('not.equal', '0');
|
||||
};
|
||||
|
|
|
@ -21,6 +21,8 @@ import {
|
|||
TIMELINE_TITLE,
|
||||
TIMESTAMP_TOGGLE_FIELD,
|
||||
TOGGLE_TIMELINE_EXPAND_EVENT,
|
||||
REMOVE_COLUMN,
|
||||
RESET_FIELDS,
|
||||
} from '../screens/timeline';
|
||||
|
||||
import { drag, drop } from '../tasks/common';
|
||||
|
@ -101,3 +103,12 @@ export const dragAndDropIdToggleFieldToTimeline = () => {
|
|||
drop(headersDropArea)
|
||||
);
|
||||
};
|
||||
|
||||
export const removeColumn = (column: number) => {
|
||||
cy.get(REMOVE_COLUMN).first().should('exist');
|
||||
cy.get(REMOVE_COLUMN).eq(column).click({ force: true });
|
||||
};
|
||||
|
||||
export const resetFields = () => {
|
||||
cy.get(RESET_FIELDS).click({ force: true });
|
||||
};
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { AlertsTableComponent } from './index';
|
||||
|
||||
describe('AlertsTableComponent', () => {
|
||||
it('renders correctly', () => {
|
||||
const wrapper = shallow(
|
||||
<AlertsTableComponent
|
||||
timelineId={TimelineId.test}
|
||||
canUserCRUD
|
||||
hasIndexWrite
|
||||
from={0}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Dispatch } from 'redux';
|
|||
|
||||
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
|
||||
import { Filter, esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { TimelineIdLiteral } from '../../../../common/types/timeline';
|
||||
import { useFetchIndexPatterns } from '../../../alerts/containers/detection_engine/rules/fetch_index_patterns';
|
||||
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
|
||||
import { HeaderSection } from '../../../common/components/header_section';
|
||||
|
@ -48,9 +49,8 @@ import {
|
|||
displayErrorToast,
|
||||
} from '../../../common/components/toasters';
|
||||
|
||||
export const ALERTS_TABLE_TIMELINE_ID = 'alerts-table';
|
||||
|
||||
interface OwnProps {
|
||||
timelineId: TimelineIdLiteral;
|
||||
canUserCRUD: boolean;
|
||||
defaultFilters?: Filter[];
|
||||
hasIndexWrite: boolean;
|
||||
|
@ -63,6 +63,7 @@ interface OwnProps {
|
|||
type AlertsTableComponentProps = OwnProps & PropsFromRedux;
|
||||
|
||||
export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
||||
timelineId,
|
||||
canUserCRUD,
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
|
@ -140,18 +141,16 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
|
||||
const setEventsLoadingCallback = useCallback(
|
||||
({ eventIds, isLoading }: SetEventsLoadingProps) => {
|
||||
setEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isLoading });
|
||||
setEventsLoading!({ id: timelineId, eventIds, isLoading });
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[setEventsLoading, ALERTS_TABLE_TIMELINE_ID]
|
||||
[setEventsLoading, timelineId]
|
||||
);
|
||||
|
||||
const setEventsDeletedCallback = useCallback(
|
||||
({ eventIds, isDeleted }: SetEventsDeletedProps) => {
|
||||
setEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID, eventIds, isDeleted });
|
||||
setEventsDeleted!({ id: timelineId, eventIds, isDeleted });
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[setEventsDeleted, ALERTS_TABLE_TIMELINE_ID]
|
||||
[setEventsDeleted, timelineId]
|
||||
);
|
||||
|
||||
const onAlertStatusUpdateSuccess = useCallback(
|
||||
|
@ -202,20 +201,20 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
// Callback for when open/closed filter changes
|
||||
const onFilterGroupChangedCallback = useCallback(
|
||||
(newFilterGroup: Status) => {
|
||||
clearEventsLoading!({ id: ALERTS_TABLE_TIMELINE_ID });
|
||||
clearEventsDeleted!({ id: ALERTS_TABLE_TIMELINE_ID });
|
||||
clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID });
|
||||
clearEventsLoading!({ id: timelineId });
|
||||
clearEventsDeleted!({ id: timelineId });
|
||||
clearSelected!({ id: timelineId });
|
||||
setFilterGroup(newFilterGroup);
|
||||
},
|
||||
[clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup]
|
||||
[clearEventsLoading, clearEventsDeleted, clearSelected, setFilterGroup, timelineId]
|
||||
);
|
||||
|
||||
// Callback for clearing entire selection from utility bar
|
||||
const clearSelectionCallback = useCallback(() => {
|
||||
clearSelected!({ id: ALERTS_TABLE_TIMELINE_ID });
|
||||
clearSelected!({ id: timelineId });
|
||||
setSelectAll(false);
|
||||
setShowClearSelectionAction(false);
|
||||
}, [clearSelected, setSelectAll, setShowClearSelectionAction]);
|
||||
}, [clearSelected, setSelectAll, setShowClearSelectionAction, timelineId]);
|
||||
|
||||
// Callback for selecting all events on all pages from utility bar
|
||||
// Dispatches to stateful_body's selectAll via TimelineTypeContext props
|
||||
|
@ -327,7 +326,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
|
||||
useEffect(() => {
|
||||
initializeTimeline({
|
||||
id: ALERTS_TABLE_TIMELINE_ID,
|
||||
id: timelineId,
|
||||
documentType: i18n.ALERTS_DOCUMENT_TYPE,
|
||||
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
|
||||
loadingText: i18n.LOADING_ALERTS,
|
||||
|
@ -338,7 +337,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
}, []);
|
||||
useEffect(() => {
|
||||
setTimelineRowActions({
|
||||
id: ALERTS_TABLE_TIMELINE_ID,
|
||||
id: timelineId,
|
||||
queryFields: requiredFieldsForActions,
|
||||
timelineRowActions: additionalActions,
|
||||
});
|
||||
|
@ -365,7 +364,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
defaultModel={alertsDefaultModel}
|
||||
end={to}
|
||||
headerFilterGroup={headerFilterGroup}
|
||||
id={ALERTS_TABLE_TIMELINE_ID}
|
||||
id={timelineId}
|
||||
start={from}
|
||||
utilityBar={utilityBarCallback}
|
||||
/>
|
||||
|
@ -375,9 +374,9 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
const makeMapStateToProps = () => {
|
||||
const getTimeline = timelineSelectors.getTimelineByIdSelector();
|
||||
const getGlobalInputs = inputsSelectors.globalSelector();
|
||||
const mapStateToProps = (state: State) => {
|
||||
const timeline: TimelineModel =
|
||||
getTimeline(state, ALERTS_TABLE_TIMELINE_ID) ?? timelineDefaults;
|
||||
const mapStateToProps = (state: State, ownProps: OwnProps) => {
|
||||
const { timelineId } = ownProps;
|
||||
const timeline: TimelineModel = getTimeline(state, timelineId) ?? timelineDefaults;
|
||||
const { deletedEventIds, isSelectAllChecked, loadingEventIds, selectedEventIds } = timeline;
|
||||
|
||||
const globalInputs: inputsModel.InputsRange = getGlobalInputs(state);
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const ALERTS_RULES_DETAILS_PAGE_TIMELINE_ID = 'alerts-rules-details-page';
|
||||
export const ALERTS_TIMELINE_ID = 'alerts-page';
|
|
@ -4,15 +4,26 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage';
|
||||
import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline';
|
||||
import { getAlertsRoutes } from './routes';
|
||||
import { SecuritySubPlugin } from '../app/types';
|
||||
|
||||
const ALERTS_TIMELINE_IDS: TimelineIdLiteral[] = [
|
||||
TimelineId.alertsRulesDetailsPage,
|
||||
TimelineId.alertsPage,
|
||||
];
|
||||
|
||||
export class Alerts {
|
||||
public setup() {}
|
||||
|
||||
public start(): SecuritySubPlugin {
|
||||
public start(storage: Storage): SecuritySubPlugin {
|
||||
return {
|
||||
routes: getAlertsRoutes(),
|
||||
storageTimelines: {
|
||||
timelineById: getTimelinesInStorageByIds(storage, ALERTS_TIMELINE_IDS),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { useCallback, useMemo } from 'react';
|
|||
import { StickyContainer } from 'react-sticky';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { GlobalTime } from '../../../common/containers/global_time';
|
||||
import {
|
||||
indicesExistOrDataTemporarilyUnavailable,
|
||||
|
@ -138,6 +139,7 @@ export const DetectionEnginePageComponent: React.FC<PropsFromRedux> = ({
|
|||
/>
|
||||
<EuiSpacer size="l" />
|
||||
<AlertsTable
|
||||
timelineId={TimelineId.alertsPage}
|
||||
loading={loading}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
canUserCRUD={(canUserCRUD ?? false) && (hasEncryptionKey ?? false)}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { Redirect, useParams } from 'react-router-dom';
|
|||
import { StickyContainer } from 'react-sticky';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
|
||||
import { TimelineId } from '../../../../../../common/types/timeline';
|
||||
import { UpdateDateRange } from '../../../../../common/components/charts/common';
|
||||
import { FiltersGlobal } from '../../../../../common/components/filters_global';
|
||||
import { FormattedDate } from '../../../../../common/components/formatted_date';
|
||||
|
@ -386,6 +387,7 @@ export const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
|
|||
<EuiSpacer />
|
||||
{ruleId != null && (
|
||||
<AlertsTable
|
||||
timelineId={TimelineId.alertsRulesDetailsPage}
|
||||
canUserCRUD={canUserCRUD ?? false}
|
||||
defaultFilters={alertDefaultFilters}
|
||||
hasIndexWrite={hasIndexWrite ?? false}
|
||||
|
|
|
@ -72,9 +72,10 @@ const AppPluginRoot = memo(AppPluginRootComponent);
|
|||
|
||||
interface StartAppComponent extends AppFrontendLibs {
|
||||
subPlugins: SecuritySubPlugins;
|
||||
storage: Storage;
|
||||
}
|
||||
|
||||
const StartAppComponent: FC<StartAppComponent> = ({ subPlugins, ...libs }) => {
|
||||
const StartAppComponent: FC<StartAppComponent> = ({ subPlugins, storage, ...libs }) => {
|
||||
const { routes: subPluginRoutes, store: subPluginsStore } = subPlugins;
|
||||
const { i18n } = useKibana().services;
|
||||
const history = createHashHistory();
|
||||
|
@ -84,6 +85,7 @@ const StartAppComponent: FC<StartAppComponent> = ({ subPlugins, ...libs }) => {
|
|||
createInitialState(subPluginsStore.initialState),
|
||||
subPluginsStore.reducer,
|
||||
libs$.pipe(pluck('apolloClient')),
|
||||
storage,
|
||||
subPluginsStore.middlewares
|
||||
);
|
||||
|
||||
|
@ -118,16 +120,17 @@ interface SiemAppComponentProps {
|
|||
subPlugins: SecuritySubPlugins;
|
||||
}
|
||||
|
||||
const SiemAppComponent: React.FC<SiemAppComponentProps> = ({ services, subPlugins }) => (
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
appName: 'siem',
|
||||
storage: new Storage(localStorage),
|
||||
...services,
|
||||
}}
|
||||
>
|
||||
<StartApp subPlugins={subPlugins} {...compose(services)} />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
const SiemAppComponent: React.FC<SiemAppComponentProps> = ({ services, subPlugins }) => {
|
||||
return (
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
appName: 'siem',
|
||||
...services,
|
||||
}}
|
||||
>
|
||||
<StartApp subPlugins={subPlugins} storage={services.storage} {...compose(services)} />
|
||||
</KibanaContextProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export const SiemApp = memo(SiemAppComponent);
|
||||
|
|
|
@ -18,6 +18,7 @@ import { NavTab } from '../common/components/navigation/types';
|
|||
import { State, SubPluginsInitReducer } from '../common/store';
|
||||
import { Immutable } from '../../common/endpoint/types';
|
||||
import { AppAction } from '../common/store/actions';
|
||||
import { TimelineState } from '../timelines/store/timeline/types';
|
||||
|
||||
export enum SiemPageName {
|
||||
overview = 'overview',
|
||||
|
@ -48,6 +49,7 @@ export interface SecuritySubPluginStore<K extends SecuritySubPluginKeyStore, T>
|
|||
|
||||
export interface SecuritySubPlugin {
|
||||
routes: React.ReactElement[];
|
||||
storageTimelines?: Pick<TimelineState, 'timelineById'>;
|
||||
}
|
||||
|
||||
type SecuritySubPluginKeyStore =
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../mock';
|
||||
import { createStore, State } from '../../store';
|
||||
import { AddFilterToGlobalSearchBar } from '.';
|
||||
|
@ -33,10 +34,11 @@ jest.mock('../../lib/kibana', () => ({
|
|||
|
||||
describe('AddFilterToGlobalSearchBar Component', () => {
|
||||
const state: State = mockGlobalState;
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
mockAddFilters.mockClear();
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React, { useEffect, useMemo } from 'react';
|
||||
|
||||
import { Filter } from '../../../../../../../src/plugins/data/public';
|
||||
import { TimelineIdLiteral } from '../../../../common/types/timeline';
|
||||
import { StatefulEventsViewer } from '../events_viewer';
|
||||
import { alertsDefaultModel } from './default_headers';
|
||||
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
|
||||
|
@ -17,7 +18,6 @@ export interface OwnProps {
|
|||
start: number;
|
||||
}
|
||||
|
||||
const ALERTS_TABLE_ID = 'alerts-table';
|
||||
const defaultAlertsFilters: Filter[] = [
|
||||
{
|
||||
meta: {
|
||||
|
@ -52,18 +52,24 @@ const defaultAlertsFilters: Filter[] = [
|
|||
];
|
||||
|
||||
interface Props {
|
||||
timelineId: TimelineIdLiteral;
|
||||
endDate: number;
|
||||
startDate: number;
|
||||
pageFilters?: Filter[];
|
||||
}
|
||||
|
||||
const AlertsTableComponent: React.FC<Props> = ({ endDate, startDate, pageFilters = [] }) => {
|
||||
const AlertsTableComponent: React.FC<Props> = ({
|
||||
timelineId,
|
||||
endDate,
|
||||
startDate,
|
||||
pageFilters = [],
|
||||
}) => {
|
||||
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
|
||||
const { initializeTimeline } = useManageTimeline();
|
||||
|
||||
useEffect(() => {
|
||||
initializeTimeline({
|
||||
id: ALERTS_TABLE_ID,
|
||||
id: timelineId,
|
||||
documentType: i18n.ALERTS_DOCUMENT_TYPE,
|
||||
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
|
||||
title: i18n.ALERTS_TABLE_TITLE,
|
||||
|
@ -76,7 +82,7 @@ const AlertsTableComponent: React.FC<Props> = ({ endDate, startDate, pageFilters
|
|||
pageFilters={alertsFilter}
|
||||
defaultModel={alertsDefaultModel}
|
||||
end={endDate}
|
||||
id={ALERTS_TABLE_ID}
|
||||
id={timelineId}
|
||||
start={startDate}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@ import React, { useEffect, useCallback, useMemo } from 'react';
|
|||
import numeral from '@elastic/numeral';
|
||||
|
||||
import { DEFAULT_NUMBER_FORMAT } from '../../../../common/constants';
|
||||
import { AlertsComponentsQueryProps } from './types';
|
||||
import { AlertsComponentsProps } from './types';
|
||||
import { AlertsTable } from './alerts_table';
|
||||
import * as i18n from './translations';
|
||||
import { useUiSetting$ } from '../../lib/kibana';
|
||||
|
@ -17,6 +17,7 @@ import { MatrixHisrogramConfigs } from '../matrix_histogram/types';
|
|||
const ID = 'alertsOverTimeQuery';
|
||||
|
||||
export const AlertsView = ({
|
||||
timelineId,
|
||||
deleteQuery,
|
||||
endDate,
|
||||
filterQuery,
|
||||
|
@ -24,7 +25,7 @@ export const AlertsView = ({
|
|||
setQuery,
|
||||
startDate,
|
||||
type,
|
||||
}: AlertsComponentsQueryProps) => {
|
||||
}: AlertsComponentsProps) => {
|
||||
const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT);
|
||||
const getSubtitle = useCallback(
|
||||
(totalCount: number) =>
|
||||
|
@ -61,7 +62,12 @@ export const AlertsView = ({
|
|||
type={type}
|
||||
{...alertsHistogramConfigs}
|
||||
/>
|
||||
<AlertsTable endDate={endDate} startDate={startDate} pageFilters={pageFilters} />
|
||||
<AlertsTable
|
||||
timelineId={timelineId}
|
||||
endDate={endDate}
|
||||
startDate={startDate}
|
||||
pageFilters={pageFilters}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,16 +5,19 @@
|
|||
*/
|
||||
|
||||
import { Filter } from '../../../../../../../src/plugins/data/public';
|
||||
import { TimelineIdLiteral } from '../../../../common/types/timeline';
|
||||
import { HostsComponentsQueryProps } from '../../../hosts/pages/navigation/types';
|
||||
import { NetworkComponentQueryProps } from '../../../network/pages/navigation/types';
|
||||
import { MatrixHistogramOption } from '../matrix_histogram/types';
|
||||
|
||||
type CommonQueryProps = HostsComponentsQueryProps | NetworkComponentQueryProps;
|
||||
export interface AlertsComponentsQueryProps
|
||||
|
||||
export interface AlertsComponentsProps
|
||||
extends Pick<
|
||||
CommonQueryProps,
|
||||
'deleteQuery' | 'endDate' | 'filterQuery' | 'skip' | 'setQuery' | 'startDate' | 'type'
|
||||
> {
|
||||
timelineId: TimelineIdLiteral;
|
||||
pageFilters: Filter[];
|
||||
stackByOptions?: MatrixHistogramOption[];
|
||||
defaultFilters?: Filter[];
|
||||
|
|
|
@ -8,7 +8,12 @@ import { shallow } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../mock';
|
||||
import {
|
||||
apolloClientObservable,
|
||||
mockGlobalState,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../mock';
|
||||
import { createStore } from '../../store/store';
|
||||
|
||||
import { ErrorToastDispatcher } from '.';
|
||||
|
@ -16,10 +21,11 @@ import { State } from '../../store/types';
|
|||
|
||||
describe('Error Toast Dispatcher', () => {
|
||||
const state: State = mockGlobalState;
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
mockGlobalState,
|
||||
apolloClientObservable,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../mock';
|
||||
import { createStore, State } from '../../store';
|
||||
import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers';
|
||||
|
@ -25,6 +26,7 @@ describe('Inspect Button', () => {
|
|||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
const refetch = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const newQuery: UpdateQueryParams = {
|
||||
inputId: 'global',
|
||||
id: 'myQuery',
|
||||
|
@ -34,13 +36,13 @@ describe('Inspect Button', () => {
|
|||
state: state.inputs,
|
||||
};
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
describe('Render', () => {
|
||||
beforeEach(() => {
|
||||
const myState = cloneDeep(state);
|
||||
myState.inputs = upsertQuery(newQuery);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
test('Eui Empty Button', () => {
|
||||
const wrapper = mount(
|
||||
|
@ -144,7 +146,7 @@ describe('Inspect Button', () => {
|
|||
response: ['my response'],
|
||||
};
|
||||
myState.inputs = upsertQuery(myQuery);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
test('Open Inspect Modal', () => {
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -30,7 +30,12 @@ import {
|
|||
mockNoChartMappings,
|
||||
mockNarrowDateRange,
|
||||
} from '../../../network/components/kpi_network/mock';
|
||||
import { mockGlobalState, apolloClientObservable, SUB_PLUGINS_REDUCER } from '../../mock';
|
||||
import {
|
||||
mockGlobalState,
|
||||
apolloClientObservable,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../mock';
|
||||
import { State, createStore } from '../../store';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import { KpiNetworkData, KpiHostsData } from '../../../graphql/types';
|
||||
|
@ -49,7 +54,8 @@ jest.mock('../charts/barchart', () => {
|
|||
describe('Stat Items Component', () => {
|
||||
const theme = () => ({ eui: euiDarkVars, darkMode: true });
|
||||
const state: State = mockGlobalState;
|
||||
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
describe.each([
|
||||
[
|
||||
|
|
|
@ -10,7 +10,12 @@ import { Provider as ReduxStoreProvider } from 'react-redux';
|
|||
|
||||
import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../../common/constants';
|
||||
import { useUiSetting$ } from '../../lib/kibana';
|
||||
import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../mock';
|
||||
import {
|
||||
apolloClientObservable,
|
||||
mockGlobalState,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../mock';
|
||||
import { createUseUiSetting$Mock } from '../../mock/kibana_react';
|
||||
import { createStore, State } from '../../store';
|
||||
|
||||
|
@ -75,11 +80,12 @@ const timepickerRanges = [
|
|||
describe('SIEM Super Date Picker', () => {
|
||||
describe('#SuperDatePicker', () => {
|
||||
const state: State = mockGlobalState;
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
mockUseUiSetting$.mockImplementation((key, defaultValue) => {
|
||||
const useUiSetting$Mock = createUseUiSetting$Mock();
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../mock';
|
||||
import { createKibanaCoreStartMock } from '../../mock/kibana_core';
|
||||
import { FilterManager } from '../../../../../../../src/plugins/data/public';
|
||||
|
@ -141,7 +142,9 @@ const state: State = {
|
|||
},
|
||||
},
|
||||
};
|
||||
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
describe('StatefulTopN', () => {
|
||||
// Suppress warnings about "react-beautiful-dnd"
|
||||
|
|
|
@ -19,7 +19,7 @@ import { alertMiddlewareFactory } from '../../../endpoint_alerts/store/middlewar
|
|||
import { AppRootProvider } from './app_root_provider';
|
||||
import { managementMiddlewareFactory } from '../../../management/store/middleware';
|
||||
import { createKibanaContextProviderMock } from '../kibana_react';
|
||||
import { SUB_PLUGINS_REDUCER, mockGlobalState } from '..';
|
||||
import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock } from '..';
|
||||
|
||||
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
|
||||
|
||||
|
@ -56,7 +56,9 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
const coreStart = coreMock.createStart({ basePath: '/mock' });
|
||||
const depsStart = depsStartMock();
|
||||
const middlewareSpy = createSpyMiddleware();
|
||||
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, [
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
|
||||
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage, [
|
||||
substateMiddlewareFactory(
|
||||
(globalState) => globalState.alertList,
|
||||
alertMiddlewareFactory(coreStart, depsStart)
|
||||
|
@ -64,6 +66,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
...managementMiddlewareFactory(coreStart, depsStart),
|
||||
middlewareSpy.actionSpyMiddleware,
|
||||
]);
|
||||
|
||||
const MockKibanaContextProvider = createKibanaContextProviderMock();
|
||||
|
||||
const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => (
|
||||
|
|
|
@ -10,6 +10,7 @@ export * from './hook_wrapper';
|
|||
export * from './index_pattern';
|
||||
export * from './mock_timeline_data';
|
||||
export * from './mock_detail_item';
|
||||
export * from './mock_local_storage';
|
||||
export * from './netflow';
|
||||
export * from './test_providers';
|
||||
export * from './utils';
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { IStorage, Storage } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
export const localStorageMock = (): IStorage => {
|
||||
let store: Record<string, unknown> = {};
|
||||
|
||||
return {
|
||||
getItem: (key: string) => {
|
||||
return store[key] || null;
|
||||
},
|
||||
setItem: (key: string, value: unknown) => {
|
||||
store[key] = value;
|
||||
},
|
||||
clear() {
|
||||
store = {};
|
||||
},
|
||||
removeItem(key: string) {
|
||||
delete store[key];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const createSecuritySolutionStorageMock = () => {
|
||||
const localStorage = localStorageMock();
|
||||
return {
|
||||
localStorage,
|
||||
storage: new Storage(localStorage),
|
||||
};
|
||||
};
|
|
@ -22,6 +22,7 @@ import { mockGlobalState } from './global_state';
|
|||
import { createKibanaContextProviderMock } from './kibana_react';
|
||||
import { FieldHook, useForm } from '../../shared_imports';
|
||||
import { SUB_PLUGINS_REDUCER } from './utils';
|
||||
import { createSecuritySolutionStorageMock, localStorageMock } from './mock_local_storage';
|
||||
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
|
@ -38,32 +39,17 @@ export const apolloClient = new ApolloClient({
|
|||
|
||||
export const apolloClientObservable = new BehaviorSubject(apolloClient);
|
||||
|
||||
const localStorageMock = () => {
|
||||
let store: Record<string, unknown> = {};
|
||||
|
||||
return {
|
||||
getItem: (key: string) => {
|
||||
return store[key] || null;
|
||||
},
|
||||
setItem: (key: string, value: unknown) => {
|
||||
store[key] = value;
|
||||
},
|
||||
clear() {
|
||||
store = {};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock(),
|
||||
});
|
||||
|
||||
const MockKibanaContextProvider = createKibanaContextProviderMock();
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
|
||||
/** A utility for wrapping children in the providers required to run most tests */
|
||||
const TestProvidersComponent: React.FC<Props> = ({
|
||||
children,
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable),
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage),
|
||||
onDragEnd = jest.fn(),
|
||||
}) => (
|
||||
<I18nProvider>
|
||||
|
@ -83,7 +69,7 @@ export const TestProviders = React.memo(TestProvidersComponent);
|
|||
|
||||
const TestProviderWithoutDragAndDropComponent: React.FC<Props> = ({
|
||||
children,
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable),
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage),
|
||||
}) => (
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>{children}</ReduxStoreProvider>
|
||||
|
|
|
@ -9,11 +9,13 @@ import { createTimelineEpic } from '../../timelines/store/timeline/epic';
|
|||
import { createTimelineFavoriteEpic } from '../../timelines/store/timeline/epic_favorite';
|
||||
import { createTimelineNoteEpic } from '../../timelines/store/timeline/epic_note';
|
||||
import { createTimelinePinnedEventEpic } from '../../timelines/store/timeline/epic_pinned_event';
|
||||
import { createTimelineLocalStorageEpic } from '../../timelines/store/timeline/epic_local_storage';
|
||||
|
||||
export const createRootEpic = <State>() =>
|
||||
combineEpics(
|
||||
createTimelineEpic<State>(),
|
||||
createTimelineFavoriteEpic<State>(),
|
||||
createTimelineNoteEpic<State>(),
|
||||
createTimelinePinnedEventEpic<State>()
|
||||
createTimelinePinnedEventEpic<State>(),
|
||||
createTimelineLocalStorageEpic<State>()
|
||||
);
|
||||
|
|
|
@ -28,6 +28,7 @@ import { AppApolloClient } from '../lib/lib';
|
|||
import { AppAction } from './actions';
|
||||
import { Immutable } from '../../../common/endpoint/types';
|
||||
import { State } from './types';
|
||||
import { Storage } from '../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
type ComposeType = typeof compose;
|
||||
declare global {
|
||||
|
@ -48,6 +49,7 @@ export const createStore = (
|
|||
state: PreloadedState<State>,
|
||||
pluginsReducer: SubPluginsInitReducer,
|
||||
apolloClient: Observable<AppApolloClient>,
|
||||
storage: Storage,
|
||||
additionalMiddleware?: Array<Middleware<{}, State, Dispatch<AppAction | Immutable<AppAction>>>>
|
||||
): Store<State, Action> => {
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||
|
@ -58,6 +60,7 @@ export const createStore = (
|
|||
selectNotesByIdSelector: appSelectors.selectNotesByIdSelector,
|
||||
timelineByIdSelector: timelineSelectors.timelineByIdSelector,
|
||||
timelineTimeRangeSelector: inputsSelectors.timelineTimeRangeSelector,
|
||||
storage,
|
||||
};
|
||||
|
||||
const epicMiddleware = createEpicMiddleware<Action, Action, State, typeof middlewareDependencies>(
|
||||
|
|
|
@ -15,7 +15,12 @@ import { AlertIndex } from '../index';
|
|||
import { RouteCapture } from '../../../common/components/endpoint/route_capture';
|
||||
import { depsStartMock } from '../../../common/mock/endpoint';
|
||||
import { createStore } from '../../../common/store';
|
||||
import { SUB_PLUGINS_REDUCER, mockGlobalState, apolloClientObservable } from '../../../common/mock';
|
||||
import {
|
||||
SUB_PLUGINS_REDUCER,
|
||||
mockGlobalState,
|
||||
apolloClientObservable,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
|
||||
export const alertPageTestRender = () => {
|
||||
/**
|
||||
|
@ -25,7 +30,8 @@ export const alertPageTestRender = () => {
|
|||
/**
|
||||
* Create a store, with the middleware disabled. We don't want side effects being created by our code in this test.
|
||||
*/
|
||||
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const store = createStore(mockGlobalState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
const depsStart = depsStartMock();
|
||||
depsStart.data.ui.SearchBar.mockImplementation(() => <div />);
|
||||
|
|
|
@ -9,7 +9,12 @@ import { getOr } from 'lodash/fp';
|
|||
import React from 'react';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../../common/mock';
|
||||
import {
|
||||
apolloClientObservable,
|
||||
mockGlobalState,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
import { hostsModel } from '../../store';
|
||||
import { mockData } from './mock';
|
||||
|
@ -20,10 +25,11 @@ describe('Authentication Table Component', () => {
|
|||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -35,12 +36,13 @@ jest.mock('../../../common/components/query_bar', () => ({
|
|||
describe('Hosts Table', () => {
|
||||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mount = useMountAppended();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -4,16 +4,27 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { TimelineIdLiteral, TimelineId } from '../../common/types/timeline';
|
||||
import { SecuritySubPluginWithStore } from '../app/types';
|
||||
import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage';
|
||||
import { getHostsRoutes } from './routes';
|
||||
import { initialHostsState, hostsReducer, HostsState } from './store';
|
||||
|
||||
const HOST_TIMELINE_IDS: TimelineIdLiteral[] = [
|
||||
TimelineId.hostsPageEvents,
|
||||
TimelineId.hostsPageExternalAlerts,
|
||||
];
|
||||
|
||||
export class Hosts {
|
||||
public setup() {}
|
||||
|
||||
public start(): SecuritySubPluginWithStore<'hosts', HostsState> {
|
||||
public start(storage: Storage): SecuritySubPluginWithStore<'hosts', HostsState> {
|
||||
return {
|
||||
routes: getHostsRoutes(),
|
||||
storageTimelines: {
|
||||
timelineById: getTimelinesInStorageByIds(storage, HOST_TIMELINE_IDS),
|
||||
},
|
||||
store: {
|
||||
initialState: { hosts: initialHostsState },
|
||||
reducer: { hosts: hostsReducer },
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
TestProviders,
|
||||
mockGlobalState,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../common/mock';
|
||||
import { SiemNavigation } from '../../common/components/navigation';
|
||||
import { inputsActions } from '../../common/store/inputs';
|
||||
|
@ -171,7 +172,8 @@ describe('Hosts - rendering', () => {
|
|||
];
|
||||
localSource[0].result.data.source.status.indicesExist = true;
|
||||
const myState: State = mockGlobalState;
|
||||
const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const wrapper = mount(
|
||||
<TestProviders store={myStore}>
|
||||
<MockedProvider mocks={localSource} addTypename={false}>
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Filter } from '../../../../../../../src/plugins/data/public';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { AlertsView } from '../../../common/components/alerts_viewer';
|
||||
import { AlertsComponentQueryProps } from './types';
|
||||
|
||||
|
@ -48,7 +49,13 @@ export const HostAlertsQueryTabBody = React.memo((alertsProps: AlertsComponentQu
|
|||
[pageFilters]
|
||||
);
|
||||
|
||||
return <AlertsView {...rest} pageFilters={hostPageFilters} />;
|
||||
return (
|
||||
<AlertsView
|
||||
timelineId={TimelineId.hostsPageExternalAlerts}
|
||||
{...rest}
|
||||
pageFilters={hostPageFilters}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
HostAlertsQueryTabBody.displayName = 'HostAlertsQueryTabBody';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
|
||||
import { HostsComponentsQueryProps } from './types';
|
||||
import { hostsModel } from '../../store';
|
||||
|
@ -17,7 +18,6 @@ import { MatrixHistogramContainer } from '../../../common/components/matrix_hist
|
|||
import * as i18n from '../translations';
|
||||
import { HistogramType } from '../../../graphql/types';
|
||||
|
||||
const HOSTS_PAGE_TIMELINE_ID = 'hosts-page';
|
||||
const EVENTS_HISTOGRAM_ID = 'eventsOverTimeQuery';
|
||||
|
||||
export const eventsStackByOptions: MatrixHistogramOption[] = [
|
||||
|
@ -78,7 +78,7 @@ export const EventsQueryTabBody = ({
|
|||
<StatefulEventsViewer
|
||||
defaultModel={eventsDefaultModel}
|
||||
end={endDate}
|
||||
id={HOSTS_PAGE_TIMELINE_ID}
|
||||
id={TimelineId.hostsPageEvents}
|
||||
start={startDate}
|
||||
pageFilters={pageFilters}
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
import { networkModel } from '../../store';
|
||||
|
@ -26,10 +27,11 @@ import { NarrowDateRange } from '../../../common/components/ml/types';
|
|||
describe('IP Overview Component', () => {
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -8,7 +8,12 @@ import { shallow } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
|
||||
import { apolloClientObservable, mockGlobalState, SUB_PLUGINS_REDUCER } from '../../../common/mock';
|
||||
import {
|
||||
apolloClientObservable,
|
||||
mockGlobalState,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
import { KpiNetworkComponent } from '.';
|
||||
import { mockData } from './mock';
|
||||
|
@ -19,10 +24,11 @@ describe('KpiNetwork Component', () => {
|
|||
const to = new Date('2019-06-18T06:00:00.000Z').valueOf();
|
||||
const narrowDateRange = jest.fn();
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { State, createStore } from '../../../common/store';
|
||||
import { networkModel } from '../../store';
|
||||
|
@ -26,11 +27,12 @@ import { mockData } from './mock';
|
|||
describe('NetworkTopNFlow Table Component', () => {
|
||||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mount = useMountAppended();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -27,11 +28,12 @@ describe('NetworkHttp Table Component', () => {
|
|||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mount = useMountAppended();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
mockIndexPattern,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -30,10 +31,11 @@ describe('NetworkTopCountries Table Component', () => {
|
|||
const state: State = mockGlobalState;
|
||||
const mount = useMountAppended();
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -27,11 +28,12 @@ describe('NetworkTopNFlow Table Component', () => {
|
|||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mount = useMountAppended();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -26,11 +27,12 @@ describe('Tls Table Component', () => {
|
|||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mount = useMountAppended();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -28,11 +29,12 @@ describe('Users Table Component', () => {
|
|||
const loadPage = jest.fn();
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mount = useMountAppended();
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
describe('Rendering', () => {
|
||||
|
|
|
@ -4,16 +4,22 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Storage } from '../../../../../src/plugins/kibana_utils/public';
|
||||
import { SecuritySubPluginWithStore } from '../app/types';
|
||||
import { getNetworkRoutes } from './routes';
|
||||
import { initialNetworkState, networkReducer, NetworkState } from './store';
|
||||
import { TimelineId } from '../../common/types/timeline';
|
||||
import { getTimelinesInStorageByIds } from '../timelines/containers/local_storage';
|
||||
|
||||
export class Network {
|
||||
public setup() {}
|
||||
|
||||
public start(): SecuritySubPluginWithStore<'network', NetworkState> {
|
||||
public start(storage: Storage): SecuritySubPluginWithStore<'network', NetworkState> {
|
||||
return {
|
||||
routes: getNetworkRoutes(),
|
||||
storageTimelines: {
|
||||
timelineById: getTimelinesInStorageByIds(storage, [TimelineId.networkPageExternalAlerts]),
|
||||
},
|
||||
store: {
|
||||
initialState: { network: initialNetworkState },
|
||||
reducer: { network: networkReducer },
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { useMountAppended } from '../../../common/utils/use_mount_appended';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
|
@ -118,10 +119,11 @@ describe('Ip Details', () => {
|
|||
});
|
||||
|
||||
const state: State = mockGlobalState;
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
localSource = cloneDeep(mocksSource);
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Filter } from '../../../../../../../src/plugins/data/common/es_query';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { AlertsView } from '../../../common/components/alerts_viewer';
|
||||
import { NetworkComponentQueryProps } from './types';
|
||||
|
||||
|
@ -62,7 +63,11 @@ export const filterNetworkData: Filter[] = [
|
|||
];
|
||||
|
||||
export const NetworkAlertsQueryTabBody = React.memo((alertsProps: NetworkComponentQueryProps) => (
|
||||
<AlertsView {...alertsProps} pageFilters={filterNetworkData} />
|
||||
<AlertsView
|
||||
timelineId={TimelineId.networkPageExternalAlerts}
|
||||
{...alertsProps}
|
||||
pageFilters={filterNetworkData}
|
||||
/>
|
||||
));
|
||||
|
||||
NetworkAlertsQueryTabBody.displayName = 'NetworkAlertsQueryTabBody';
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
mockGlobalState,
|
||||
apolloClientObservable,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../common/mock';
|
||||
import { State, createStore } from '../../common/store';
|
||||
import { inputsActions } from '../../common/store/inputs';
|
||||
|
@ -155,7 +156,8 @@ describe('rendering - rendering', () => {
|
|||
];
|
||||
localSource[0].result.data.source.status.indicesExist = true;
|
||||
const myState: State = mockGlobalState;
|
||||
const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const myStore = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const wrapper = mount(
|
||||
<TestProviders store={myStore}>
|
||||
<MockedProvider mocks={localSource} addTypename={false}>
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
|
||||
import { OverviewHost } from '.';
|
||||
|
@ -92,11 +93,12 @@ const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [
|
|||
describe('OverviewHost', () => {
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
const myState = cloneDeep(state);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
test('it renders the expected widget title', () => {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
|
||||
import { OverviewNetwork } from '.';
|
||||
|
@ -83,11 +84,12 @@ const mockOpenTimelineQueryResults: MockedProvidedQuery[] = [
|
|||
describe('OverviewNetwork', () => {
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
const myState = cloneDeep(state);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(myState, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
});
|
||||
|
||||
test('it renders the expected widget title', () => {
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
Plugin as IPlugin,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
} from '../../../../src/core/public';
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public';
|
||||
import { initTelemetry } from './common/lib/telemetry';
|
||||
import { KibanaServices } from './common/lib/kibana/services';
|
||||
|
@ -50,12 +51,14 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
plugins.triggers_actions_ui.actionTypeRegistry.register(jiraActionType());
|
||||
|
||||
const mountSecurityApp = async (params: AppMountParameters) => {
|
||||
const storage = new Storage(localStorage);
|
||||
const [coreStart, startPlugins] = await core.getStartServices();
|
||||
const { renderApp } = await import('./app');
|
||||
const services = {
|
||||
...coreStart,
|
||||
...startPlugins,
|
||||
security: plugins.security,
|
||||
storage,
|
||||
} as StartServices;
|
||||
|
||||
const alertsSubPlugin = new (await import('./alerts')).Alerts();
|
||||
|
@ -67,15 +70,27 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
const endpointAlertsSubPlugin = new (await import('./endpoint_alerts')).EndpointAlerts();
|
||||
const managementSubPlugin = new (await import('./management')).Management();
|
||||
|
||||
const alertsStart = alertsSubPlugin.start();
|
||||
const alertsStart = alertsSubPlugin.start(storage);
|
||||
const casesStart = casesSubPlugin.start();
|
||||
const hostsStart = hostsSubPlugin.start();
|
||||
const networkStart = networkSubPlugin.start();
|
||||
const hostsStart = hostsSubPlugin.start(storage);
|
||||
const networkStart = networkSubPlugin.start(storage);
|
||||
const overviewStart = overviewSubPlugin.start();
|
||||
const timelinesStart = timelinesSubPlugin.start();
|
||||
const endpointAlertsStart = endpointAlertsSubPlugin.start(coreStart, startPlugins);
|
||||
const managementSubPluginStart = managementSubPlugin.start(coreStart, startPlugins);
|
||||
|
||||
const timelineInitialState = {
|
||||
timeline: {
|
||||
...timelinesStart.store.initialState.timeline!,
|
||||
timelineById: {
|
||||
...timelinesStart.store.initialState.timeline!.timelineById,
|
||||
...alertsStart.storageTimelines!.timelineById,
|
||||
...hostsStart.storageTimelines!.timelineById,
|
||||
...networkStart.storageTimelines!.timelineById,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return renderApp(services, params, {
|
||||
routes: [
|
||||
...alertsStart.routes,
|
||||
|
@ -91,7 +106,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
initialState: {
|
||||
...hostsStart.store.initialState,
|
||||
...networkStart.store.initialState,
|
||||
...timelinesStart.store.initialState,
|
||||
...timelineInitialState,
|
||||
...endpointAlertsStart.store.initialState,
|
||||
...managementSubPluginStart.store.initialState,
|
||||
},
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
mockGlobalState,
|
||||
TestProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../common/mock';
|
||||
import { createStore, State } from '../../../common/store';
|
||||
import { mockDataProviders } from '../timeline/data_providers/mock/mock_data_providers';
|
||||
|
@ -31,6 +32,7 @@ const usersViewing = ['elastic'];
|
|||
|
||||
describe('Flyout', () => {
|
||||
const state: State = mockGlobalState;
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
|
||||
describe('rendering', () => {
|
||||
test('it renders correctly against snapshot', () => {
|
||||
|
@ -59,7 +61,8 @@ describe('Flyout', () => {
|
|||
const storeShowIsTrue = createStore(
|
||||
stateShowIsTrue,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
apolloClientObservable
|
||||
apolloClientObservable,
|
||||
storage
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -82,7 +85,8 @@ describe('Flyout', () => {
|
|||
const storeWithDataProviders = createStore(
|
||||
stateWithDataProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
apolloClientObservable
|
||||
apolloClientObservable,
|
||||
storage
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -103,7 +107,8 @@ describe('Flyout', () => {
|
|||
const storeWithDataProviders = createStore(
|
||||
stateWithDataProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
apolloClientObservable
|
||||
apolloClientObservable,
|
||||
storage
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -136,7 +141,8 @@ describe('Flyout', () => {
|
|||
const storeWithDataProviders = createStore(
|
||||
stateWithDataProviders,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
apolloClientObservable
|
||||
apolloClientObservable,
|
||||
storage
|
||||
);
|
||||
|
||||
const wrapper = mount(
|
||||
|
|
|
@ -48,7 +48,7 @@ export const Actions = React.memo<Props>(({ header, onColumnRemoved, sort, isLoa
|
|||
<>
|
||||
{sort.columnId === header.id && isLoading ? (
|
||||
<EventsHeadingExtra className="siemEventsHeading__extra--loading">
|
||||
<EventsLoading />
|
||||
<EventsLoading data-test-subj="timeline-loading-spinner" />
|
||||
</EventsHeadingExtra>
|
||||
) : (
|
||||
<EventsHeadingExtra className="siemEventsHeading__extra--close">
|
||||
|
|
|
@ -33,24 +33,28 @@ exports[`Footer Timeline Component rendering it renders the default timeline foo
|
|||
items={
|
||||
Array [
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="items-per-page-option-1"
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
1 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="items-per-page-option-5"
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
5 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="items-per-page-option-10"
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
10 rows
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
data-test-subj="items-per-page-option-20"
|
||||
icon="empty"
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
|
|
@ -144,6 +144,7 @@ export const EventsCountComponent = ({
|
|||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={onClick}
|
||||
data-test-subj="local-events-count-button"
|
||||
/>
|
||||
</EuiBadge>
|
||||
{` ${i18n.OF} `}
|
||||
|
@ -289,6 +290,7 @@ export const FooterComponent = ({
|
|||
<EuiContextMenuItem
|
||||
key={item}
|
||||
icon={itemsPerPage === item ? 'check' : 'empty'}
|
||||
data-test-subj={`items-per-page-option-${item}`}
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
onChangeItemsPerPage(item);
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
mockGlobalState,
|
||||
apolloClientObservable,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
TestProviders,
|
||||
} from '../../../../common/mock';
|
||||
import { createStore, State } from '../../../../common/store';
|
||||
|
@ -97,12 +98,14 @@ const defaultProps = {
|
|||
};
|
||||
describe('Properties', () => {
|
||||
const state: State = mockGlobalState;
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let mockedWidth = 1000;
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
(useThrottledResizeObserver as jest.Mock).mockReturnValue({ width: mockedWidth });
|
||||
});
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
mockGlobalState,
|
||||
apolloClientObservable,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
createSecuritySolutionStorageMock,
|
||||
} from '../../../../common/mock';
|
||||
import { createStore, State } from '../../../../common/store';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
@ -24,7 +25,8 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
|
||||
describe('NewTemplateTimeline', () => {
|
||||
const state: State = mockGlobalState;
|
||||
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable);
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
const store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
const mockClosePopover = jest.fn();
|
||||
const mockTitle = 'NEW_TIMELINE';
|
||||
let wrapper: ReactWrapper;
|
||||
|
|
|
@ -27,7 +27,12 @@ import { QueryTemplate, QueryTemplateProps } from '../../common/containers/query
|
|||
import { EventType } from '../../timelines/store/timeline/model';
|
||||
import { timelineQuery } from './index.gql_query';
|
||||
import { timelineActions } from '../../timelines/store/timeline';
|
||||
import { ALERTS_TABLE_TIMELINE_ID } from '../../alerts/components/alerts_table';
|
||||
import {
|
||||
ALERTS_TIMELINE_ID as DETECTION_ENGINE_TIMELINE_ID,
|
||||
ALERTS_RULES_DETAILS_PAGE_TIMELINE_ID as DETECTION_ENGINE_RULES_TIMELINE_ID,
|
||||
} from '../../alerts/constants';
|
||||
|
||||
const timelineIds = [DETECTION_ENGINE_TIMELINE_ID, DETECTION_ENGINE_RULES_TIMELINE_ID];
|
||||
|
||||
export interface TimelineArgs {
|
||||
events: TimelineItem[];
|
||||
|
@ -182,7 +187,7 @@ const makeMapStateToProps = () => {
|
|||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
clearSignalsState: ({ id }: { id?: string }) => {
|
||||
if (id != null && id === ALERTS_TABLE_TIMELINE_ID) {
|
||||
if (id != null && timelineIds.some((timelineId) => timelineId === id)) {
|
||||
dispatch(timelineActions.clearEventsLoading({ id }));
|
||||
dispatch(timelineActions.clearEventsDeleted({ id }));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
LOCAL_STORAGE_TIMELINE_KEY,
|
||||
useTimelinesStorage,
|
||||
getTimelinesInStorageByIds,
|
||||
getAllTimelinesInStorage,
|
||||
addTimelineInStorage,
|
||||
} from '.';
|
||||
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { mockTimelineModel, createSecuritySolutionStorageMock } from '../../../common/mock';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { createUseKibanaMock } from '../../../common/mock/kibana_react';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
|
||||
describe('SiemLocalStorage', () => {
|
||||
const { localStorage, storage } = createSecuritySolutionStorageMock();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
useKibanaMock.mockImplementation(() => ({
|
||||
services: {
|
||||
...createUseKibanaMock()().services,
|
||||
storage,
|
||||
},
|
||||
}));
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
describe('addTimeline', () => {
|
||||
it('adds a timeline when storage is empty', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a timeline when storage contains another timelines', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel);
|
||||
expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
[TimelineId.hostsPageExternalAlerts]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllTimelines', () => {
|
||||
it('gets all timelines correctly', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel);
|
||||
const timelines = timelineStorage.getAllTimelines();
|
||||
expect(timelines).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
[TimelineId.hostsPageExternalAlerts]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty object if there is no timelines', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
const timelines = timelineStorage.getAllTimelines();
|
||||
expect(timelines).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTimelineById', () => {
|
||||
it('gets a timeline by id', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
const timeline = timelineStorage.getTimelineById(TimelineId.hostsPageEvents);
|
||||
expect(timeline).toEqual(mockTimelineModel);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTimelinesInStorageByIds', () => {
|
||||
it('gets timelines correctly', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel);
|
||||
const timelines = getTimelinesInStorageByIds(storage, [
|
||||
TimelineId.hostsPageEvents,
|
||||
TimelineId.hostsPageExternalAlerts,
|
||||
]);
|
||||
expect(timelines).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
[TimelineId.hostsPageExternalAlerts]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
|
||||
it('gets an empty timelime when there is no timelines', () => {
|
||||
const timelines = getTimelinesInStorageByIds(storage, [TimelineId.hostsPageEvents]);
|
||||
expect(timelines).toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty timelime when there is no ids', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
const timelines = getTimelinesInStorageByIds(storage, []);
|
||||
expect(timelines).toEqual({});
|
||||
});
|
||||
|
||||
it('returns empty timelime when a specific timeline does not exists', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
const timelines = getTimelinesInStorageByIds(storage, [TimelineId.hostsPageExternalAlerts]);
|
||||
expect(timelines).toEqual({});
|
||||
});
|
||||
|
||||
it('returns timelines correctly when one exist and another not', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
const timelines = getTimelinesInStorageByIds(storage, [
|
||||
TimelineId.hostsPageEvents,
|
||||
TimelineId.hostsPageExternalAlerts,
|
||||
]);
|
||||
expect(timelines).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllTimelinesInStorage', () => {
|
||||
it('gets timelines correctly', () => {
|
||||
const timelineStorage = useTimelinesStorage();
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
timelineStorage.addTimeline(TimelineId.hostsPageExternalAlerts, mockTimelineModel);
|
||||
const timelines = getAllTimelinesInStorage(storage);
|
||||
expect(timelines).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
[TimelineId.hostsPageExternalAlerts]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
|
||||
it('gets an empty timeline when there is no timelines', () => {
|
||||
const timelines = getAllTimelinesInStorage(storage);
|
||||
expect(timelines).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe('addTimelineInStorage', () => {
|
||||
it('adds a timeline when storage is empty', () => {
|
||||
addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a timeline when storage contains another timelines', () => {
|
||||
addTimelineInStorage(storage, TimelineId.hostsPageEvents, mockTimelineModel);
|
||||
addTimelineInStorage(storage, TimelineId.hostsPageExternalAlerts, mockTimelineModel);
|
||||
expect(JSON.parse(localStorage.getItem(LOCAL_STORAGE_TIMELINE_KEY))).toEqual({
|
||||
[TimelineId.hostsPageEvents]: mockTimelineModel,
|
||||
[TimelineId.hostsPageExternalAlerts]: mockTimelineModel,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { TimelinesStorage } from './types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { TimelineModel } from '../../store/timeline/model';
|
||||
import { TimelineIdLiteral } from '../../../../common/types/timeline';
|
||||
|
||||
export const LOCAL_STORAGE_TIMELINE_KEY = 'timelines';
|
||||
const EMPTY_TIMELINE = {} as {
|
||||
[K in TimelineIdLiteral]: TimelineModel;
|
||||
};
|
||||
|
||||
export const getTimelinesInStorageByIds = (storage: Storage, timelineIds: TimelineIdLiteral[]) => {
|
||||
const allTimelines = storage.get(LOCAL_STORAGE_TIMELINE_KEY);
|
||||
|
||||
if (!allTimelines) {
|
||||
return EMPTY_TIMELINE;
|
||||
}
|
||||
|
||||
return timelineIds.reduce((acc, timelineId) => {
|
||||
const timelineModel = allTimelines[timelineId];
|
||||
if (!timelineModel) {
|
||||
return {
|
||||
...acc,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[timelineId]: timelineModel,
|
||||
};
|
||||
}, {} as { [K in TimelineIdLiteral]: TimelineModel });
|
||||
};
|
||||
|
||||
export const getAllTimelinesInStorage = (storage: Storage) =>
|
||||
storage.get(LOCAL_STORAGE_TIMELINE_KEY) ?? {};
|
||||
|
||||
export const addTimelineInStorage = (
|
||||
storage: Storage,
|
||||
id: TimelineIdLiteral,
|
||||
timeline: TimelineModel
|
||||
) => {
|
||||
const timelines = getAllTimelinesInStorage(storage);
|
||||
storage.set(LOCAL_STORAGE_TIMELINE_KEY, {
|
||||
...timelines,
|
||||
[id]: timeline,
|
||||
});
|
||||
};
|
||||
|
||||
export const useTimelinesStorage = (): TimelinesStorage => {
|
||||
const { storage } = useKibana().services;
|
||||
|
||||
const getAllTimelines: TimelinesStorage['getAllTimelines'] = () =>
|
||||
getAllTimelinesInStorage(storage);
|
||||
|
||||
const getTimelineById: TimelinesStorage['getTimelineById'] = (id: TimelineIdLiteral) =>
|
||||
getTimelinesInStorageByIds(storage, [id])[id] ?? null;
|
||||
|
||||
const addTimeline: TimelinesStorage['addTimeline'] = (id, timeline) =>
|
||||
addTimelineInStorage(storage, id, timeline);
|
||||
|
||||
return { getAllTimelines, getTimelineById, addTimeline };
|
||||
};
|
||||
|
||||
export { TimelinesStorage };
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { TimelineModel } from '../../../timelines/store/timeline/model';
|
||||
import { TimelineIdLiteral } from '../../../../common/types/timeline';
|
||||
|
||||
export interface TimelinesStorage {
|
||||
getAllTimelines: () => Record<TimelineIdLiteral, TimelineModel>;
|
||||
getTimelineById: (id: TimelineIdLiteral) => TimelineModel | null;
|
||||
addTimeline: (id: TimelineIdLiteral, timeline: TimelineModel) => void;
|
||||
}
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from 'lodash/fp';
|
||||
import { Action } from 'redux';
|
||||
import { Epic } from 'redux-observable';
|
||||
import { from, Observable, empty, merge } from 'rxjs';
|
||||
import { from, empty, merge } from 'rxjs';
|
||||
import {
|
||||
filter,
|
||||
map,
|
||||
|
@ -34,16 +34,14 @@ import {
|
|||
MatchAllFilter,
|
||||
} from '../../../../../../.../../../src/plugins/data/public';
|
||||
import { TimelineStatus } from '../../../../common/types/timeline';
|
||||
import { inputsModel } from '../../../common/store/inputs';
|
||||
import {
|
||||
TimelineType,
|
||||
TimelineInput,
|
||||
ResponseTimeline,
|
||||
TimelineResult,
|
||||
} from '../../../graphql/types';
|
||||
import { AppApolloClient } from '../../../common/lib/lib';
|
||||
import { addError } from '../../../common/store/app/actions';
|
||||
import { NotesById } from '../../../common/store/app/model';
|
||||
import { inputsModel } from '../../../common/store/inputs';
|
||||
|
||||
import {
|
||||
applyKqlFilterQuery,
|
||||
|
@ -80,18 +78,10 @@ import { epicPersistTimelineFavorite, timelineFavoriteActionsType } from './epic
|
|||
import { isNotNull } from './helpers';
|
||||
import { dispatcherTimelinePersistQueue } from './epic_dispatcher_timeline_persistence_queue';
|
||||
import { myEpicTimelineId } from './my_epic_timeline_id';
|
||||
import { ActionTimeline, TimelineById } from './types';
|
||||
import { ActionTimeline, TimelineEpicDependencies } from './types';
|
||||
import { persistTimeline } from '../../containers/api';
|
||||
import { ALL_TIMELINE_QUERY_ID } from '../../containers/all';
|
||||
|
||||
interface TimelineEpicDependencies<State> {
|
||||
timelineByIdSelector: (state: State) => TimelineById;
|
||||
timelineTimeRangeSelector: (state: State) => inputsModel.TimeRange;
|
||||
selectAllTimelineQuery: () => (state: State, id: string) => inputsModel.GlobalQuery;
|
||||
selectNotesByIdSelector: (state: State) => NotesById;
|
||||
apolloClient$: Observable<AppApolloClient>;
|
||||
}
|
||||
|
||||
const timelineActionsType = [
|
||||
applyKqlFilterQuery.type,
|
||||
addProvider.type,
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import {
|
||||
mockGlobalState,
|
||||
SUB_PLUGINS_REDUCER,
|
||||
apolloClientObservable,
|
||||
TestProviders,
|
||||
defaultHeaders,
|
||||
createSecuritySolutionStorageMock,
|
||||
mockIndexPattern,
|
||||
} from '../../../common/mock';
|
||||
|
||||
import { createStore, State } from '../../../common/store';
|
||||
import {
|
||||
removeColumn,
|
||||
upsertColumn,
|
||||
applyDeltaToColumnWidth,
|
||||
updateColumns,
|
||||
updateItemsPerPage,
|
||||
updateSort,
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
TimelineComponent,
|
||||
Props as TimelineComponentProps,
|
||||
} from '../../components/timeline/timeline';
|
||||
import { mockBrowserFields } from '../../../common/containers/source/mock';
|
||||
import { mockDataProviders } from '../../components/timeline/data_providers/mock/mock_data_providers';
|
||||
import { Sort } from '../../components/timeline/body/sort';
|
||||
import { Direction } from '../../../graphql/types';
|
||||
|
||||
import { addTimelineInStorage } from '../../containers/local_storage';
|
||||
import { isPageTimeline } from './epic_local_storage';
|
||||
|
||||
jest.mock('../../containers/local_storage');
|
||||
|
||||
const wait = (ms: number = 500): Promise<void> => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
};
|
||||
|
||||
const addTimelineInStorageMock = addTimelineInStorage as jest.Mock;
|
||||
|
||||
describe('epicLocalStorage', () => {
|
||||
const state: State = mockGlobalState;
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
let store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
|
||||
let props = {} as TimelineComponentProps;
|
||||
const sort: Sort = {
|
||||
columnId: '@timestamp',
|
||||
sortDirection: Direction.desc,
|
||||
};
|
||||
const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf();
|
||||
const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf();
|
||||
|
||||
const indexPattern = mockIndexPattern;
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, SUB_PLUGINS_REDUCER, apolloClientObservable, storage);
|
||||
props = {
|
||||
browserFields: mockBrowserFields,
|
||||
columns: defaultHeaders,
|
||||
id: 'foo',
|
||||
dataProviders: mockDataProviders,
|
||||
end: endDate,
|
||||
eventType: 'raw' as TimelineComponentProps['eventType'],
|
||||
filters: [],
|
||||
indexPattern,
|
||||
indexToAdd: [],
|
||||
isLive: false,
|
||||
itemsPerPage: 5,
|
||||
itemsPerPageOptions: [5, 10, 20],
|
||||
kqlMode: 'search' as TimelineComponentProps['kqlMode'],
|
||||
kqlQueryExpression: '',
|
||||
loadingIndexName: false,
|
||||
onChangeItemsPerPage: jest.fn(),
|
||||
onClose: jest.fn(),
|
||||
onDataProviderEdited: jest.fn(),
|
||||
onDataProviderRemoved: jest.fn(),
|
||||
onToggleDataProviderEnabled: jest.fn(),
|
||||
onToggleDataProviderExcluded: jest.fn(),
|
||||
show: true,
|
||||
showCallOutUnauthorizedMsg: false,
|
||||
start: startDate,
|
||||
sort,
|
||||
toggleColumn: jest.fn(),
|
||||
usersViewing: ['elastic'],
|
||||
};
|
||||
});
|
||||
|
||||
it('filters correctly page timelines', () => {
|
||||
expect(isPageTimeline('timeline-1')).toBe(false);
|
||||
expect(isPageTimeline('hosts-page-alerts')).toBe(true);
|
||||
});
|
||||
|
||||
it('persist adding / reordering of a column correctly', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<TimelineComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(upsertColumn({ id: 'test', index: 1, column: defaultHeaders[0] }));
|
||||
await wait();
|
||||
expect(addTimelineInStorageMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('persist timeline when removing a column ', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<TimelineComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(removeColumn({ id: 'test', columnId: '@timestamp' }));
|
||||
await wait();
|
||||
expect(addTimelineInStorageMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('persists resizing of a column', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<TimelineComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(applyDeltaToColumnWidth({ id: 'test', columnId: '@timestamp', delta: 80 }));
|
||||
await wait();
|
||||
expect(addTimelineInStorageMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('persist the resetting of the fields', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<TimelineComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(updateColumns({ id: 'test', columns: defaultHeaders }));
|
||||
await wait();
|
||||
expect(addTimelineInStorageMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('persist items per page', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<TimelineComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(updateItemsPerPage({ id: 'test', itemsPerPage: 50 }));
|
||||
await wait();
|
||||
expect(addTimelineInStorageMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('persist the sorting of a column', async () => {
|
||||
shallow(
|
||||
<TestProviders store={store}>
|
||||
<TimelineComponent {...props} />
|
||||
</TestProviders>
|
||||
);
|
||||
store.dispatch(
|
||||
updateSort({
|
||||
id: 'test',
|
||||
sort: {
|
||||
columnId: 'event.severity',
|
||||
sortDirection: Direction.desc,
|
||||
},
|
||||
})
|
||||
);
|
||||
await wait();
|
||||
expect(addTimelineInStorageMock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Action } from 'redux';
|
||||
import { map, filter, ignoreElements, tap, withLatestFrom, delay } from 'rxjs/operators';
|
||||
import { Epic } from 'redux-observable';
|
||||
import { get } from 'lodash/fp';
|
||||
|
||||
import { TimelineIdLiteral } from '../../../../common/types/timeline';
|
||||
import { addTimelineInStorage } from '../../containers/local_storage';
|
||||
|
||||
import {
|
||||
removeColumn,
|
||||
upsertColumn,
|
||||
applyDeltaToColumnWidth,
|
||||
updateColumns,
|
||||
updateItemsPerPage,
|
||||
updateSort,
|
||||
} from './actions';
|
||||
import { TimelineEpicDependencies } from './types';
|
||||
import { isNotNull } from './helpers';
|
||||
|
||||
const timelineActionTypes = [
|
||||
removeColumn.type,
|
||||
upsertColumn.type,
|
||||
applyDeltaToColumnWidth.type,
|
||||
updateColumns.type,
|
||||
updateItemsPerPage.type,
|
||||
updateSort.type,
|
||||
];
|
||||
|
||||
export const isPageTimeline = (timelineId: string | undefined): boolean =>
|
||||
// Is not a flyout timeline
|
||||
!(timelineId && timelineId.toLowerCase().startsWith('timeline'));
|
||||
|
||||
export const createTimelineLocalStorageEpic = <State>(): Epic<
|
||||
Action,
|
||||
Action,
|
||||
State,
|
||||
TimelineEpicDependencies<State>
|
||||
> => (action$, state$, { timelineByIdSelector, storage }) => {
|
||||
const timeline$ = state$.pipe(map(timelineByIdSelector), filter(isNotNull));
|
||||
return action$.pipe(
|
||||
delay(500),
|
||||
withLatestFrom(timeline$),
|
||||
filter(([action]) => isPageTimeline(get('payload.id', action))),
|
||||
tap(([action, timelineById]) => {
|
||||
if (timelineActionTypes.includes(action.type)) {
|
||||
if (storage) {
|
||||
const timelineId: TimelineIdLiteral = get('payload.id', action);
|
||||
addTimelineInStorage(storage, timelineId, timelineById[timelineId]);
|
||||
}
|
||||
}
|
||||
}),
|
||||
ignoreElements()
|
||||
);
|
||||
};
|
|
@ -23,22 +23,10 @@ import { TimelineTypeLiteral } from '../../../../common/types/timeline';
|
|||
|
||||
import { timelineDefaults } from './defaults';
|
||||
import { ColumnHeaderOptions, KqlMode, TimelineModel, EventType } from './model';
|
||||
import { TimelineById, TimelineState } from './types';
|
||||
|
||||
const EMPTY_TIMELINE_BY_ID: TimelineById = {}; // stable reference
|
||||
import { TimelineById } from './types';
|
||||
|
||||
export const isNotNull = <T>(value: T | null): value is T => value !== null;
|
||||
|
||||
export const initialTimelineState: TimelineState = {
|
||||
timelineById: EMPTY_TIMELINE_BY_ID,
|
||||
autoSavedWarningMsg: {
|
||||
timelineId: null,
|
||||
newTimelineModel: null,
|
||||
},
|
||||
showCallOutUnauthorizedMsg: false,
|
||||
insertTimeline: null,
|
||||
};
|
||||
|
||||
interface AddTimelineHistoryParams {
|
||||
id: string;
|
||||
historyId: string;
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { Action } from 'redux';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { Storage } from '../../../../../../../src/plugins/kibana_utils/public';
|
||||
import { AppApolloClient } from '../../../common/lib/lib';
|
||||
import { inputsModel } from '../../../common/store/inputs';
|
||||
import { NotesById } from '../../../common/store/app/model';
|
||||
import { TimelineModel } from './model';
|
||||
|
||||
export interface AutoSavedWarningMsg {
|
||||
|
@ -39,3 +45,12 @@ export interface ActionTimeline extends Action<string> {
|
|||
noteId: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TimelineEpicDependencies<State> {
|
||||
timelineByIdSelector: (state: State) => TimelineById;
|
||||
timelineTimeRangeSelector: (state: State) => inputsModel.TimeRange;
|
||||
selectAllTimelineQuery: () => (state: State, id: string) => inputsModel.GlobalQuery;
|
||||
selectNotesByIdSelector: (state: State) => NotesById;
|
||||
apolloClient$: Observable<AppApolloClient>;
|
||||
storage: Storage;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Start as NewsfeedStart } from '../../../../src/plugins/newsfeed/public'
|
|||
import { Start as InspectorStart } from '../../../../src/plugins/inspector/public';
|
||||
import { UiActionsStart } from '../../../../src/plugins/ui_actions/public';
|
||||
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import { IngestManagerStart } from '../../ingest_manager/public';
|
||||
import {
|
||||
TriggersAndActionsUIPublicPluginSetup as TriggersActionsSetup,
|
||||
|
@ -39,6 +40,7 @@ export interface StartPlugins {
|
|||
export type StartServices = CoreStart &
|
||||
StartPlugins & {
|
||||
security: SecurityPluginSetup;
|
||||
storage: Storage;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
|
|
Loading…
Reference in a new issue