mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Security Solution] remove analyzer as overlay (#220597)
## Summary This PR is a follow up of [this previous one](https://github.com/elastic/kibana/pull/220590) that removed the advanced setting allowing users to switch between the Session View component rendered in the expandable flyout and as an overlay to the alerts table. After removing related to the [Session View displayed as overlay](https://github.com/elastic/kibana/pull/220596), this PR removes all code related to the Analyzer Graph component when displayed as an overlay. This is applied to the Alerts page alerts table and to Timeline. **_As [this previous PR](https://github.com/elastic/kibana/pull/220590) had removed the ability to switch back to this overlay mode, this PR does not introduces any changes visible in the UI. If anything looks different or behaves differently, then there is an issue and this PR should not be merged._** Analyzer Graph in expandable flyout remains unchanged: https://github.com/user-attachments/assets/eb110ecc-d857-475a-a9e3-1bd0ff4fa5ef ### Notes The timeline adds a `graphEventId` parameter to the url. This was used to reopen the timeline to the Analyzer tab. This functionality was broken a few months back when we enabled the advanced settings by default. This functionality is completely removed in this PR, as we now do not have the `graphEvenId` url parameter anymore. This was run by the team or product and we're ok with it. ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
b974589671
commit
77fe98dcef
73 changed files with 173 additions and 1509 deletions
|
@ -666,7 +666,7 @@
|
|||
"string | undefined"
|
||||
],
|
||||
"path": "x-pack/solutions/security/packages/data-table/store/data_table/model.ts",
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
|
@ -1245,7 +1245,7 @@
|
|||
},
|
||||
" | undefined; type?: string | undefined; })[]; readonly additionalFilters: Record<",
|
||||
"AlertPageFilterType",
|
||||
", boolean>; readonly itemsPerPage: number; readonly queryFields: string[]; readonly selectAll: boolean; readonly showCheckboxes: boolean; readonly deletedEventIds: string[]; readonly graphEventId?: string | undefined; readonly indexNames: string[]; readonly isSelectAllChecked: boolean; readonly itemsPerPageOptions: number[]; readonly loadingEventIds: string[]; readonly selectedEventIds: Record<string, ",
|
||||
", boolean>; readonly itemsPerPage: number; readonly queryFields: string[]; readonly selectAll: boolean; readonly showCheckboxes: boolean; readonly deletedEventIds: string[]; readonly indexNames: string[]; readonly isSelectAllChecked: boolean; readonly itemsPerPageOptions: number[]; readonly loadingEventIds: string[]; readonly selectedEventIds: Record<string, ",
|
||||
{
|
||||
"pluginId": "timelines",
|
||||
"scope": "common",
|
||||
|
@ -1526,7 +1526,7 @@
|
|||
"label": "graphEventId",
|
||||
"description": [],
|
||||
"path": "x-pack/solutions/security/packages/data-table/store/data_table/defaults.ts",
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1527,7 +1527,7 @@
|
|||
"string | undefined"
|
||||
],
|
||||
"path": "x-pack/solutions/security/plugins/security_solution/public/timelines/store/model.ts",
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
|
|
|
@ -186,7 +186,6 @@ module.exports = {
|
|||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]events_viewer[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]events_viewer[\/\\]summary_view_select[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]field_selection[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]filters_global[\/\\]filters_global.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]header_actions[\/\\]actions.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]health_truncate_text[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]hover_visibility_container[\/\\]index.tsx/,
|
||||
|
@ -386,7 +385,6 @@ module.exports = {
|
|||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]fields_browser[\/\\]create_field_button[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]fields_browser[\/\\]field_table_columns[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]formatted_duration[\/\\]tooltip[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]graph_overlay[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]ja3_fingerprint[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]loading[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]modal[\/\\]header[\/\\]index.tsx/,
|
||||
|
|
|
@ -41129,9 +41129,6 @@
|
|||
"xpack.securitySolution.timeline.footer.loadingLabel": "Chargement",
|
||||
"xpack.securitySolution.timeline.footer.of": "de",
|
||||
"xpack.securitySolution.timeline.footer.rows": "lignes",
|
||||
"xpack.securitySolution.timeline.graphOverlay.closeAnalyzerButton": "Fermer l'analyseur",
|
||||
"xpack.securitySolution.timeline.graphOverlay.closeSessionButton": "Fermer le visualiseur de session",
|
||||
"xpack.securitySolution.timeline.graphOverlay.fullScreenButton": "Plein écran",
|
||||
"xpack.securitySolution.timeline.kpiFailDescription": "Une erreur s'est produite",
|
||||
"xpack.securitySolution.timeline.kpiFailSearchDescription": "Échec de chargement des KPI",
|
||||
"xpack.securitySolution.timeline.kpis.destinationKpiTitle": "IP de destination",
|
||||
|
|
|
@ -41093,9 +41093,6 @@
|
|||
"xpack.securitySolution.timeline.footer.loadingLabel": "読み込み中",
|
||||
"xpack.securitySolution.timeline.footer.of": "/",
|
||||
"xpack.securitySolution.timeline.footer.rows": "行",
|
||||
"xpack.securitySolution.timeline.graphOverlay.closeAnalyzerButton": "アナライザーを閉じる",
|
||||
"xpack.securitySolution.timeline.graphOverlay.closeSessionButton": "セッションビューアーの終了",
|
||||
"xpack.securitySolution.timeline.graphOverlay.fullScreenButton": "全画面",
|
||||
"xpack.securitySolution.timeline.kpiFailDescription": "エラーが発生しました",
|
||||
"xpack.securitySolution.timeline.kpiFailSearchDescription": "KPIの読み込みに失敗",
|
||||
"xpack.securitySolution.timeline.kpis.destinationKpiTitle": "デスティネーション IP",
|
||||
|
|
|
@ -41170,9 +41170,6 @@
|
|||
"xpack.securitySolution.timeline.footer.loadingLabel": "正在加载",
|
||||
"xpack.securitySolution.timeline.footer.of": "/",
|
||||
"xpack.securitySolution.timeline.footer.rows": "行",
|
||||
"xpack.securitySolution.timeline.graphOverlay.closeAnalyzerButton": "关闭分析器",
|
||||
"xpack.securitySolution.timeline.graphOverlay.closeSessionButton": "关闭会话查看器",
|
||||
"xpack.securitySolution.timeline.graphOverlay.fullScreenButton": "全屏",
|
||||
"xpack.securitySolution.timeline.kpiFailDescription": "发生错误",
|
||||
"xpack.securitySolution.timeline.kpiFailSearchDescription": "无法加载 KPI",
|
||||
"xpack.securitySolution.timeline.kpis.destinationKpiTitle": "目标 IP",
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import type { EuiMarkdownEditorUiPlugin, EuiMarkdownAstNodePosition } from '@elastic/eui';
|
||||
import type { EuiMarkdownAstNodePosition, EuiMarkdownEditorUiPlugin } from '@elastic/eui';
|
||||
import type { Plugin } from 'unified';
|
||||
|
||||
/**
|
||||
* @description - manage the plugins, hooks, and ui components needed to enable timeline functionality within the cases plugin
|
||||
* @TODO - To better encapsulate the timeline logic needed by cases, we are managing it in this top level context.
|
||||
|
@ -17,14 +18,14 @@ import type { Plugin } from 'unified';
|
|||
|
||||
// TODO: copied from 'use_insert_timeline' in security_solution till timeline moved into it's own plugin.
|
||||
interface UseInsertTimelineReturn {
|
||||
handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void;
|
||||
handleOnTimelineChange: (title: string, id: string | null) => void;
|
||||
}
|
||||
|
||||
interface TimelineProcessingPluginRendererProps {
|
||||
id: string | null;
|
||||
title: string;
|
||||
graphEventId?: string;
|
||||
type: 'timeline';
|
||||
|
||||
[key: string]: string | null | undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export enum FlowTargetSourceDest {
|
||||
destination = 'destination',
|
||||
source = 'source',
|
||||
}
|
||||
|
||||
export enum TimelineTabs {
|
||||
query = 'query',
|
||||
graph = 'graph',
|
||||
notes = 'notes',
|
||||
pinned = 'pinned',
|
||||
eql = 'eql',
|
||||
}
|
|
@ -10,9 +10,6 @@ export * from './header_actions';
|
|||
|
||||
export const FILTER_OPEN = 'open' as const;
|
||||
export const FILTER_CLOSED = 'closed' as const;
|
||||
export const FILTER_ACKNOWLEDGED = 'acknowledged' as const;
|
||||
|
||||
export type SetEventsLoading = (params: { eventIds: string[]; isLoading: boolean }) => void;
|
||||
export type SetEventsDeleted = (params: { eventIds: string[]; isDeleted: boolean }) => void;
|
||||
|
||||
export { TimelineTabs } from './detail_panel';
|
||||
|
|
|
@ -19,14 +19,7 @@ export {
|
|||
export type { TableState, DataTableState, TableById } from './store/data_table/types';
|
||||
export type { DataTableModel, SubsetDataTableModel } from './store/data_table/model';
|
||||
|
||||
export {
|
||||
Direction,
|
||||
tableEntity,
|
||||
FILTER_OPEN,
|
||||
TimelineTabs,
|
||||
TableId,
|
||||
TableEntityType,
|
||||
} from './common/types';
|
||||
export { Direction, tableEntity, FILTER_OPEN, TableId, TableEntityType } from './common/types';
|
||||
export type {
|
||||
TableIdLiteral,
|
||||
ViewSelection,
|
||||
|
|
|
@ -40,7 +40,6 @@ export const mockGlobalState = {
|
|||
sortDirection: 'desc',
|
||||
},
|
||||
],
|
||||
graphEventId: '',
|
||||
selectAll: false,
|
||||
id: TableId.test,
|
||||
title: '',
|
||||
|
|
|
@ -101,10 +101,6 @@ export const setDataTableSelectAll = actionCreator<{ id: string; selectAll: bool
|
|||
'SET_DATA_TABLE_SELECT_ALL'
|
||||
);
|
||||
|
||||
export const updateGraphEventId = actionCreator<{ id: string; graphEventId: string }>(
|
||||
'UPDATE_DATA_TABLE_GRAPH_EVENT_ID'
|
||||
);
|
||||
|
||||
export const setTableUpdatedAt = actionCreator<{ id: string; updated: number }>(
|
||||
'SET_TABLE_UPDATED_AT'
|
||||
);
|
||||
|
|
|
@ -82,7 +82,6 @@ export const tableDefaults: SubsetDataTableModel = {
|
|||
},
|
||||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
columns: defaultHeaders,
|
||||
queryFields: [],
|
||||
title: '',
|
||||
|
@ -104,5 +103,4 @@ export const getDataTableManageDefaults = (id: string) => ({
|
|||
queryFields: [],
|
||||
title: '',
|
||||
unit: (n: number) => i18n.UNIT(n),
|
||||
graphEventId: '',
|
||||
});
|
||||
|
|
|
@ -434,23 +434,3 @@ export const setSelectedTableEvents = ({
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTableGraphEventId = ({
|
||||
id,
|
||||
graphEventId,
|
||||
tableById,
|
||||
}: {
|
||||
id: string;
|
||||
graphEventId: string;
|
||||
tableById: TableById;
|
||||
}): TableById => {
|
||||
const table = tableById[id];
|
||||
|
||||
return {
|
||||
...tableById,
|
||||
[id]: {
|
||||
...table,
|
||||
graphEventId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -39,8 +39,6 @@ export interface DataTableModel extends DataTableModelSettings {
|
|||
/** Events to not be rendered **/
|
||||
deletedEventIds: string[];
|
||||
filters?: Filter[];
|
||||
/** When non-empty, display a graph view for this event */
|
||||
graphEventId?: string;
|
||||
/** Uniquely identifies the data table */
|
||||
id: string;
|
||||
indexNames: string[];
|
||||
|
@ -83,7 +81,6 @@ export type SubsetDataTableModel = Readonly<
|
|||
| 'showCheckboxes'
|
||||
| 'sort'
|
||||
| 'selectedEventIds'
|
||||
| 'graphEventId'
|
||||
| 'queryFields'
|
||||
| 'title'
|
||||
| 'initialized'
|
||||
|
|
|
@ -24,7 +24,6 @@ import {
|
|||
updateColumnOrder,
|
||||
updateColumns,
|
||||
updateColumnWidth,
|
||||
updateGraphEventId,
|
||||
updateIsLoading,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
|
@ -46,7 +45,6 @@ import {
|
|||
updateDataTableColumnOrder,
|
||||
updateDataTableColumnWidth,
|
||||
updateTableColumns,
|
||||
updateTableGraphEventId,
|
||||
updateTableItemsPerPage,
|
||||
updateTablePerPageOptions,
|
||||
updateTableSort,
|
||||
|
@ -224,10 +222,6 @@ export const dataTableReducer = reducerWithInitialState(initialDataTableState)
|
|||
},
|
||||
},
|
||||
}))
|
||||
.case(updateGraphEventId, (state, { id, graphEventId }) => ({
|
||||
...state,
|
||||
tableById: updateTableGraphEventId({ id, graphEventId, tableById: state.tableById }),
|
||||
}))
|
||||
.case(setTableUpdatedAt, (state, { id, updated }) => ({
|
||||
...state,
|
||||
tableById: {
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
import { getOr } from 'lodash/fp';
|
||||
import { createSelector } from 'reselect';
|
||||
import { tableDefaults, getDataTableManageDefaults } from './defaults';
|
||||
import type { DataTableState, TableById, DataTableModel } from './types';
|
||||
import { getDataTableManageDefaults, tableDefaults } from './defaults';
|
||||
import type { DataTableModel, DataTableState, TableById } from './types';
|
||||
|
||||
const selectTableById = (state: DataTableState): TableById => state.dataTable.tableById;
|
||||
|
||||
|
@ -35,7 +35,7 @@ const selectTGridById = (state: unknown, tableId: string): DataTableModel => {
|
|||
export const getManageDataTableById = () =>
|
||||
createSelector(
|
||||
selectTGridById,
|
||||
({
|
||||
({ dataViewId, defaultColumns, isLoading, loadingText, queryFields, title, selectAll }) => ({
|
||||
dataViewId,
|
||||
defaultColumns,
|
||||
isLoading,
|
||||
|
@ -43,15 +43,5 @@ export const getManageDataTableById = () =>
|
|||
queryFields,
|
||||
title,
|
||||
selectAll,
|
||||
graphEventId,
|
||||
}) => ({
|
||||
dataViewId,
|
||||
defaultColumns,
|
||||
isLoading,
|
||||
loadingText,
|
||||
queryFields,
|
||||
title,
|
||||
selectAll,
|
||||
graphEventId,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -24,11 +24,9 @@ export interface ScrollToTopEvent {
|
|||
|
||||
export enum TimelineTabs {
|
||||
query = 'query',
|
||||
graph = 'graph',
|
||||
notes = 'notes',
|
||||
pinned = 'pinned',
|
||||
eql = 'eql',
|
||||
securityAssistant = 'securityAssistant',
|
||||
esql = 'esql',
|
||||
}
|
||||
|
||||
|
|
|
@ -14,9 +14,9 @@ import { FilterManager } from '@kbn/data-plugin/public';
|
|||
import { createMockStore, mockGlobalState, TestProviders } from '../../common/mock';
|
||||
import { inputsActions } from '../../common/store/inputs';
|
||||
import {
|
||||
setSearchBarFilter,
|
||||
setAbsoluteRangeDatePicker,
|
||||
setRelativeRangeDatePicker,
|
||||
setSearchBarFilter,
|
||||
} from '../../common/store/inputs/actions';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
|
@ -641,7 +641,6 @@ describe('HomePage', () => {
|
|||
|
||||
expect(mockUpdateUrlParam).toHaveBeenCalledWith({
|
||||
activeTab: 'query',
|
||||
graphEventId: '',
|
||||
isOpen: false,
|
||||
});
|
||||
});
|
||||
|
|
|
@ -38,7 +38,6 @@ jest.mock('../../../common/hooks/use_selector', () => ({
|
|||
useShallowEqualSelector: jest.fn().mockReturnValue({
|
||||
timelineTitle: mockTimelineModel.title,
|
||||
timelineSavedObjectId: mockTimelineModel.savedObjectId,
|
||||
graphEventId: mockTimelineModel.graphEventId,
|
||||
timelineId: mockTimelineModel.id,
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -11,12 +11,12 @@ import { isEmpty } from 'lodash/fp';
|
|||
|
||||
import { getTimelineUrl, useFormatUrl } from '../../../common/components/link_to';
|
||||
import { useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { timelineSelectors, timelineActions } from '../../../timelines/store';
|
||||
import { timelineActions, timelineSelectors } from '../../../timelines/store';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { setInsertTimeline } from '../../../timelines/store/actions';
|
||||
|
||||
export interface UseInsertTimelineReturn {
|
||||
handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void;
|
||||
handleOnTimelineChange: (title: string, id: string | null) => void;
|
||||
}
|
||||
|
||||
export const useInsertTimeline = (
|
||||
|
@ -29,8 +29,8 @@ export const useInsertTimeline = (
|
|||
const insertTimeline = useShallowEqualSelector(timelineSelectors.selectInsertTimeline);
|
||||
|
||||
const handleOnTimelineChange = useCallback(
|
||||
(title: string, id: string | null, graphEventId?: string) => {
|
||||
const url = formatUrl(getTimelineUrl(id ?? '', graphEventId), {
|
||||
(title: string, id: string | null) => {
|
||||
const url = formatUrl(getTimelineUrl(id ?? ''), {
|
||||
absolute: true,
|
||||
skipSearch: true,
|
||||
});
|
||||
|
@ -49,11 +49,7 @@ export const useInsertTimeline = (
|
|||
useEffect(() => {
|
||||
if (insertTimeline != null && value != null) {
|
||||
dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false }));
|
||||
handleOnTimelineChange(
|
||||
insertTimeline.timelineTitle,
|
||||
insertTimeline.timelineSavedObjectId,
|
||||
insertTimeline.graphEventId
|
||||
);
|
||||
handleOnTimelineChange(insertTimeline.timelineTitle, insertTimeline.timelineSavedObjectId);
|
||||
dispatch(setInsertTimeline(null));
|
||||
}
|
||||
}, [insertTimeline, dispatch, handleOnTimelineChange, value]);
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
/* TODO: (new data view picker) remove this after new picker is enabled */
|
||||
/* eslint-disable complexity */
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import type { SubsetDataTableModel, TableId } from '@kbn/securitysolution-data-table';
|
||||
|
@ -52,10 +51,8 @@ import type { SourcererScopeName } from '../../../sourcerer/store/model';
|
|||
import { useSourcererDataView } from '../../../sourcerer/containers';
|
||||
import type { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
|
||||
import type { FieldEditorActions } from '../../../timelines/components/fields_browser';
|
||||
import { useFieldBrowserOptions } from '../../../timelines/components/fields_browser';
|
||||
import { useSessionViewNavigation } from '../../../timelines/components/timeline/tabs/session/use_session_view';
|
||||
import { getCombinedFilterQuery } from './helpers';
|
||||
import { useTimelineEvents } from './use_timelines_events';
|
||||
import { EmptyTable, TableContext, TableLoading } from './shared';
|
||||
|
@ -128,7 +125,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
columns,
|
||||
defaultColumns,
|
||||
deletedEventIds,
|
||||
graphEventId, // If truthy, the graph viewer (Resolver) is showing
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
showCheckboxes,
|
||||
|
@ -196,15 +192,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
|
||||
const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]);
|
||||
|
||||
// TODO remove this when session view is fully migrated to the flyout and the advanced settings is removed
|
||||
const { Navigation } = useSessionViewNavigation({
|
||||
scopeId: tableId,
|
||||
});
|
||||
const graphOverlay = useMemo(() => {
|
||||
const shouldShowOverlay = graphEventId != null && graphEventId.length > 0;
|
||||
return shouldShowOverlay ? <GraphOverlay scopeId={tableId} Navigation={Navigation} /> : null;
|
||||
}, [graphEventId, tableId, Navigation]);
|
||||
|
||||
const setQuery = useCallback(
|
||||
({ id, inspect, loading, refetch }: SetQuery) =>
|
||||
dispatch(
|
||||
|
@ -505,8 +492,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
<div data-test-subj="events-viewer-panel">
|
||||
{showFullLoading && <TableLoading height="short" />}
|
||||
|
||||
{graphOverlay}
|
||||
|
||||
{canQueryTimeline && (
|
||||
<TableContext.Provider value={tableContext}>
|
||||
<div
|
||||
|
@ -516,7 +501,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
position: relative;
|
||||
`}
|
||||
>
|
||||
{!loading && !graphOverlay && (
|
||||
{!loading && (
|
||||
<div
|
||||
css={css`
|
||||
position: absolute;
|
||||
|
@ -536,12 +521,12 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
</div>
|
||||
)}
|
||||
|
||||
{!hasAlerts && !loading && !graphOverlay && <EmptyTable />}
|
||||
{!hasAlerts && !loading && <EmptyTable />}
|
||||
|
||||
{hasAlerts && (
|
||||
<EuiFlexItem
|
||||
css={css`
|
||||
display: ${!graphEventId && graphOverlay == null ? 'flex' : 'none'};
|
||||
display: flex;
|
||||
overflow: auto;
|
||||
`}
|
||||
>
|
||||
|
|
|
@ -36,6 +36,7 @@ import type { ESQuery } from '../../../../common/typed_json';
|
|||
import type { AlertWorkflowStatus } from '../../types';
|
||||
import { getSearchTransactionName, useStartTransaction } from '../../lib/apm/use_start_transaction';
|
||||
import { useFetchNotes } from '../../../notes/hooks/use_fetch_notes';
|
||||
|
||||
export type InspectResponse = Inspect & { response: string[] };
|
||||
|
||||
export const detectionsTimelineIds = [TableId.alertsOnAlertsPage, TableId.alertsOnRuleDetailsPage];
|
||||
|
@ -164,7 +165,6 @@ export const useTimelineEventsHandler = ({
|
|||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(0);
|
||||
const [timelineRequest, setTimelineRequest] = useState<TimelineRequest | null>(null);
|
||||
const [prevFilterStatus, setFilterStatus] = useState(filterStatus);
|
||||
const prevTimelineRequest = useRef<TimelineRequest | null>(null);
|
||||
|
||||
const clearSignalsState = useCallback(() => {
|
||||
|
@ -262,10 +262,6 @@ export const useTimelineEventsHandler = ({
|
|||
if (onNextHandler) onNextHandler(newTimelineResponse);
|
||||
return newTimelineResponse;
|
||||
});
|
||||
if (prevFilterStatus !== request.filterStatus) {
|
||||
dispatch(dataTableActions.updateGraphEventId({ id, graphEventId: '' }));
|
||||
}
|
||||
setFilterStatus(request.filterStatus);
|
||||
setLoading(false);
|
||||
|
||||
searchSubscription$.current.unsubscribe();
|
||||
|
@ -286,7 +282,7 @@ export const useTimelineEventsHandler = ({
|
|||
asyncSearch();
|
||||
refetch.current = asyncSearch;
|
||||
},
|
||||
[skip, data, entityType, dataViewId, startTracking, dispatch, id, prevFilterStatus]
|
||||
[skip, data, entityType, dataViewId, startTracking]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -18,14 +18,13 @@ exports[`rendering renders correctly 1`] = `
|
|||
color="subdued"
|
||||
paddingSize="none"
|
||||
>
|
||||
<FiltersGlobalContainer
|
||||
<header
|
||||
data-test-subj="filters-global-container"
|
||||
show={true}
|
||||
>
|
||||
<p>
|
||||
Additional filters here.
|
||||
</p>
|
||||
</FiltersGlobalContainer>
|
||||
</header>
|
||||
</EuiPanel>
|
||||
</InPortal>
|
||||
`;
|
||||
|
|
|
@ -44,21 +44,4 @@ describe('rendering', () => {
|
|||
).not.toHaveStyleRule('display', 'none');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when show is false', () => {
|
||||
test('it renders the container with a `display: none` style', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<FiltersGlobal show={false}>
|
||||
<p>{'Filter content'}</p>
|
||||
</FiltersGlobal>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="filters-global-container"]').first()).toHaveStyleRule(
|
||||
'display',
|
||||
'none'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,31 +6,21 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { InPortal } from 'react-reverse-portal';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { useGlobalHeaderPortal } from '../../hooks/use_global_header_portal';
|
||||
|
||||
const FiltersGlobalContainer = styled.header<{ show: boolean }>`
|
||||
display: ${({ show }) => (show ? 'block' : 'none')};
|
||||
`;
|
||||
|
||||
FiltersGlobalContainer.displayName = 'FiltersGlobalContainer';
|
||||
|
||||
export interface FiltersGlobalProps {
|
||||
children: React.ReactNode;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
export const FiltersGlobal = React.memo<FiltersGlobalProps>(({ children, show = true }) => {
|
||||
export const FiltersGlobal = React.memo<FiltersGlobalProps>(({ children }) => {
|
||||
const { globalKQLHeaderPortalNode } = useGlobalHeaderPortal();
|
||||
|
||||
return (
|
||||
<InPortal node={globalKQLHeaderPortalNode}>
|
||||
<EuiPanel borderRadius="none" color="subdued" paddingSize="none">
|
||||
<FiltersGlobalContainer data-test-subj="filters-global-container" show={show}>
|
||||
{children}
|
||||
</FiltersGlobalContainer>
|
||||
<header data-test-subj="filters-global-container">{children}</header>
|
||||
</EuiPanel>
|
||||
</InPortal>
|
||||
);
|
||||
|
|
|
@ -5,14 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import type { TimelineType } from '../../../../common/api/timeline';
|
||||
import { appendSearch } from './helpers';
|
||||
|
||||
export const getTimelineTabsUrl = (tabName: TimelineType, search?: string) =>
|
||||
`/${tabName}${appendSearch(search)}`;
|
||||
|
||||
export const getTimelineUrl = (id: string, graphEventId?: string) =>
|
||||
`?timeline=(id:'${id}',isOpen:!t${
|
||||
isEmpty(graphEventId) ? ')' : `,graphEventId:'${graphEventId}')`
|
||||
}`;
|
||||
export const getTimelineUrl = (id: string) => `?timeline=(id:'${id}',isOpen:!t)`;
|
||||
|
|
|
@ -75,11 +75,9 @@ export const TimelineParser: Plugin = function () {
|
|||
const parseTimelineUrlSearch = parse(timelineSearch[1]) as { timeline: string };
|
||||
const decodedTimeline = safeDecode(parseTimelineUrlSearch.timeline ?? '') as {
|
||||
id?: string;
|
||||
graphEventId?: string;
|
||||
} | null;
|
||||
const { id: timelineId = '', graphEventId = '' } = decodedTimeline ?? {
|
||||
const { id: timelineId = '' } = decodedTimeline ?? {
|
||||
id: null,
|
||||
graphEventId: '',
|
||||
};
|
||||
|
||||
if (!timelineId) {
|
||||
|
@ -94,7 +92,6 @@ export const TimelineParser: Plugin = function () {
|
|||
type: ID,
|
||||
id: timelineId,
|
||||
title: timelineTitle,
|
||||
graphEventId,
|
||||
});
|
||||
} catch {
|
||||
this.file.info(i18n.TIMELINE_URL_IS_NOT_VALID(timelineUrl), {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, memo } from 'react';
|
||||
import type { EuiSelectableOption, EuiMarkdownEditorUiPlugin } from '@elastic/eui';
|
||||
import { EuiModalBody, EuiModalHeader, EuiCodeBlock } from '@elastic/eui';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import type { EuiMarkdownEditorUiPlugin, EuiSelectableOption } from '@elastic/eui';
|
||||
import { EuiCodeBlock, EuiModalBody, EuiModalHeader } from '@elastic/eui';
|
||||
|
||||
import { TimelineTypeEnum } from '../../../../../../common/api/timeline';
|
||||
import { SelectableTimeline } from '../../../../../timelines/components/timeline/selectable_timeline';
|
||||
|
@ -45,8 +45,8 @@ const TimelineEditorComponent: React.FC<TimelineEditorProps> = ({ onClosePopover
|
|||
);
|
||||
|
||||
const handleTimelineChange = useCallback(
|
||||
(timelineTitle: string, timelineId: string | null, graphEventId?: string) => {
|
||||
const url = formatUrl(getTimelineUrl(timelineId ?? '', graphEventId), {
|
||||
(timelineTitle: string, timelineId: string | null) => {
|
||||
const url = formatUrl(getTimelineUrl(timelineId ?? ''), {
|
||||
absolute: true,
|
||||
skipSearch: true,
|
||||
});
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, memo } from 'react';
|
||||
import { EuiToolTip, EuiLink } from '@elastic/eui';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { EuiLink, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { useUpsellingMessage } from '../../../../hooks/use_upselling';
|
||||
import { useTimelineClick } from '../../../../utils/timeline/use_timeline_click';
|
||||
|
@ -15,11 +15,7 @@ import * as i18n from './translations';
|
|||
import { useAppToasts } from '../../../../hooks/use_app_toasts';
|
||||
import { useUserPrivileges } from '../../../user_privileges';
|
||||
|
||||
export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
||||
id,
|
||||
title,
|
||||
graphEventId,
|
||||
}) => {
|
||||
export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({ id, title }) => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||
|
@ -41,8 +37,8 @@ export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
|||
);
|
||||
|
||||
const onClickTimeline = useCallback(
|
||||
() => handleTimelineClick(id ?? '', onError, graphEventId),
|
||||
[id, graphEventId, handleTimelineClick, onError]
|
||||
() => handleTimelineClick(id ?? '', onError),
|
||||
[id, handleTimelineClick, onError]
|
||||
);
|
||||
return (
|
||||
<EuiToolTip content={interactionsUpsellingMessage ?? i18n.TIMELINE_ID(id ?? '')}>
|
||||
|
|
|
@ -10,7 +10,7 @@ import type { ID } from './constants';
|
|||
export interface TimelineConfiguration {
|
||||
id: string | null;
|
||||
title: string;
|
||||
graphEventId?: string;
|
||||
|
||||
[key: string]: string | null | undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ export const useInitTimelineFromUrlParam = () => {
|
|||
queryTimelineById({
|
||||
activeTimelineTab: initialState.activeTab,
|
||||
duplicate: false,
|
||||
graphEventId: initialState.graphEventId,
|
||||
timelineId: initialState.id,
|
||||
openTimeline: initialState.isOpen,
|
||||
savedSearchId: initialState.savedSearchId,
|
||||
|
@ -71,8 +70,6 @@ function hasTimelineStateChanged(
|
|||
return (
|
||||
activeTimeline &&
|
||||
newState &&
|
||||
(activeTimeline.id !== newState.id ||
|
||||
activeTimeline.savedSearchId !== newState.savedSearchId ||
|
||||
activeTimeline.graphEventId !== newState.graphEventId)
|
||||
(activeTimeline.id !== newState.id || activeTimeline.savedSearchId !== newState.savedSearchId)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -42,8 +42,8 @@ jest.mock('react-redux', () => {
|
|||
describe('queryTimelineByIdOnUrlChange', () => {
|
||||
const oldTestTimelineId = '04e8ffb0-2c2a-11ec-949c-39005af91f70';
|
||||
const newTestTimelineId = `${oldTestTimelineId}-newId`;
|
||||
const oldTimelineRisonSearchString = `?timeline=(activeTab:query,graphEventId:%27%27,id:%27${oldTestTimelineId}%27,isOpen:!t)`;
|
||||
const newTimelineRisonSearchString = `?timeline=(activeTab:query,graphEventId:%27%27,id:%27${newTestTimelineId}%27,isOpen:!t)`;
|
||||
const oldTimelineRisonSearchString = `?timeline=(activeTab:query,id:%27${oldTestTimelineId}%27,isOpen:!t)`;
|
||||
const newTimelineRisonSearchString = `?timeline=(activeTab:query,id:%27${newTestTimelineId}%27,isOpen:!t)`;
|
||||
const mockQueryTimelineById = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -66,7 +66,6 @@ describe('queryTimelineByIdOnUrlChange', () => {
|
|||
expect.objectContaining({
|
||||
activeTimelineTab: 'query',
|
||||
duplicate: false,
|
||||
graphEventId: '',
|
||||
timelineId: newTestTimelineId,
|
||||
openTimeline: true,
|
||||
})
|
||||
|
|
|
@ -57,7 +57,7 @@ export const useQueryTimelineByIdOnUrlChange = () => {
|
|||
}, [oldSearch, search]);
|
||||
|
||||
const oldId = previousTimeline?.id;
|
||||
const { id: newId, activeTab, graphEventId } = currentTimeline || {};
|
||||
const { id: newId, activeTab } = currentTimeline || {};
|
||||
|
||||
const queryTimelineById = useQueryTimelineById();
|
||||
|
||||
|
@ -66,12 +66,11 @@ export const useQueryTimelineByIdOnUrlChange = () => {
|
|||
queryTimelineById({
|
||||
activeTimelineTab: activeTab ?? TimelineTabs.query,
|
||||
duplicate: false,
|
||||
graphEventId,
|
||||
timelineId: newId,
|
||||
openTimeline: true,
|
||||
});
|
||||
}
|
||||
}, [timelineIdFromReduxStore, oldId, newId, activeTab, graphEventId, queryTimelineById]);
|
||||
}, [timelineIdFromReduxStore, oldId, newId, activeTab, queryTimelineById]);
|
||||
};
|
||||
|
||||
export const getQueryStringKeyValue = ({ search, urlKey }: { search: string; urlKey: string }) =>
|
||||
|
|
|
@ -17,7 +17,7 @@ import { URL_PARAM_KEY } from '../use_url_state';
|
|||
export const useSyncTimelineUrlParam = () => {
|
||||
const updateUrlParam = useUpdateUrlParam<TimelineUrl>(URL_PARAM_KEY.timeline);
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const { activeTab, graphEventId, show, savedObjectId, savedSearchId } = useShallowEqualSelector(
|
||||
const { activeTab, show, savedObjectId, savedSearchId } = useShallowEqualSelector(
|
||||
(state) => getTimeline(state, TimelineId.active) ?? {}
|
||||
);
|
||||
|
||||
|
@ -26,9 +26,8 @@ export const useSyncTimelineUrlParam = () => {
|
|||
...(savedObjectId ? { id: savedObjectId } : {}),
|
||||
isOpen: show,
|
||||
activeTab,
|
||||
graphEventId: graphEventId ?? '',
|
||||
savedSearchId: savedSearchId ? savedSearchId : undefined,
|
||||
};
|
||||
updateUrlParam(params);
|
||||
}, [activeTab, graphEventId, savedObjectId, show, updateUrlParam, savedSearchId]);
|
||||
}, [activeTab, savedObjectId, show, updateUrlParam, savedSearchId]);
|
||||
};
|
||||
|
|
|
@ -33,8 +33,7 @@ describe('useResolveConflict', () => {
|
|||
// Mock rison format in actual url
|
||||
(useLocation as jest.Mock).mockReturnValue({
|
||||
pathname: 'my/cool/path',
|
||||
search:
|
||||
'timeline=(activeTab:query,graphEventId:%27%27,id:%2704e8ffb0-2c2a-11ec-949c-39005af91f70%27,isOpen:!t)',
|
||||
search: 'timeline=(activeTab:query,id:%2704e8ffb0-2c2a-11ec-949c-39005af91f70%27,isOpen:!t)',
|
||||
});
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: {
|
||||
|
@ -58,7 +57,6 @@ describe('useResolveConflict', () => {
|
|||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => ({
|
||||
savedObjectId: 'current-saved-object-id',
|
||||
activeTab: 'some-tab',
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: false,
|
||||
}));
|
||||
const { result } = renderHook(() => useResolveConflict());
|
||||
|
@ -75,7 +73,6 @@ describe('useResolveConflict', () => {
|
|||
},
|
||||
savedObjectId: 'current-saved-object-id',
|
||||
activeTab: 'some-tab',
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: false,
|
||||
}));
|
||||
const { result } = renderHook(() => useResolveConflict());
|
||||
|
@ -114,7 +111,7 @@ describe('useResolveConflict', () => {
|
|||
currentObjectId: '04e8ffb0-2c2a-11ec-949c-39005af91f70',
|
||||
otherObjectId: 'new-id',
|
||||
otherObjectPath:
|
||||
'my/cool/path?timeline=%28activeTab%3Aquery%2CgraphEventId%3A%27%27%2Cid%3Anew-id%2CisOpen%3A%21t%29',
|
||||
'my/cool/path?timeline=%28activeTab%3Aquery%2Cid%3Anew-id%2CisOpen%3A%21t%29',
|
||||
});
|
||||
expect(result.current).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
|
@ -137,7 +134,6 @@ describe('useResolveConflict', () => {
|
|||
},
|
||||
savedObjectId: 'current-saved-object-id',
|
||||
activeTab: 'some-tab',
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: false,
|
||||
}));
|
||||
mockGetLegacyUrlConflict.mockImplementation(() => mockTextContent);
|
||||
|
@ -148,7 +144,7 @@ describe('useResolveConflict', () => {
|
|||
currentObjectId: 'current-saved-object-id',
|
||||
otherObjectId: 'new-id',
|
||||
otherObjectPath:
|
||||
'my/cool/path?foo=bar&timeline=%28activeTab%3Asome-tab%2CgraphEventId%3Acurrent-graph-event-id%2Cid%3Anew-id%2CisOpen%3A%21f%29',
|
||||
'my/cool/path?foo=bar&timeline=%28activeTab%3Asome-tab%2Cid%3Anew-id%2CisOpen%3A%21f%29',
|
||||
});
|
||||
expect(result.current).toMatchInlineSnapshot(`
|
||||
<React.Fragment>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { safeDecode, encode } from '@kbn/rison';
|
||||
import { encode, safeDecode } from '@kbn/rison';
|
||||
import { useDeepEqualSelector } from './use_selector';
|
||||
import { TimelineId } from '../../../common/types/timeline';
|
||||
import { timelineSelectors } from '../../timelines/store';
|
||||
|
@ -25,8 +25,9 @@ export const useResolveConflict = () => {
|
|||
const { search, pathname } = useLocation();
|
||||
const { spaces } = useKibana().services;
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const { resolveTimelineConfig, savedObjectId, show, graphEventId, activeTab } =
|
||||
useDeepEqualSelector((state) => getTimeline(state, TimelineId.active) ?? timelineDefaults);
|
||||
const { resolveTimelineConfig, savedObjectId, show, activeTab } = useDeepEqualSelector(
|
||||
(state) => getTimeline(state, TimelineId.active) ?? timelineDefaults
|
||||
);
|
||||
|
||||
const getLegacyUrlConflictCallout = useCallback(() => {
|
||||
// This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario
|
||||
|
@ -38,7 +39,6 @@ export const useResolveConflict = () => {
|
|||
id: savedObjectId ?? '',
|
||||
isOpen: !!show,
|
||||
activeTab,
|
||||
graphEventId,
|
||||
};
|
||||
const timelineSearch =
|
||||
(safeDecode(timelineRison ?? '') as TimelineUrl | null) ?? currentTimelineState;
|
||||
|
@ -78,7 +78,6 @@ export const useResolveConflict = () => {
|
|||
);
|
||||
}, [
|
||||
activeTab,
|
||||
graphEventId,
|
||||
pathname,
|
||||
resolveTimelineConfig?.alias_target_id,
|
||||
resolveTimelineConfig?.outcome,
|
||||
|
|
|
@ -34,8 +34,7 @@ describe('useResolveRedirect', () => {
|
|||
// Mock rison format in actual url
|
||||
(useLocation as jest.Mock).mockReturnValue({
|
||||
pathname: 'my/cool/path',
|
||||
search:
|
||||
'timeline=(activeTab:query,graphEventId:%27%27,id:%2704e8ffb0-2c2a-11ec-949c-39005af91f70%27,isOpen:!t)',
|
||||
search: 'timeline=(activeTab:query,id:%2704e8ffb0-2c2a-11ec-949c-39005af91f70%27,isOpen:!t)',
|
||||
});
|
||||
(useKibana as jest.Mock).mockReturnValue({
|
||||
services: {
|
||||
|
@ -57,7 +56,6 @@ describe('useResolveRedirect', () => {
|
|||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => ({
|
||||
savedObjectId: 'current-saved-object-id',
|
||||
activeTab: 'some-tab',
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: false,
|
||||
}));
|
||||
renderHook(() => useResolveRedirect());
|
||||
|
@ -73,7 +71,6 @@ describe('useResolveRedirect', () => {
|
|||
},
|
||||
savedObjectId: 'current-saved-object-id',
|
||||
activeTab: 'some-tab',
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: false,
|
||||
}));
|
||||
renderHook(() => useResolveRedirect());
|
||||
|
@ -92,7 +89,7 @@ describe('useResolveRedirect', () => {
|
|||
}));
|
||||
renderHook(() => useResolveRedirect());
|
||||
expect(mockRedirectLegacyUrl).toHaveBeenCalledWith({
|
||||
path: 'my/cool/path?timeline=%28activeTab%3Aquery%2CgraphEventId%3A%27%27%2Cid%3Anew-id%2CisOpen%3A%21t%29',
|
||||
path: 'my/cool/path?timeline=%28activeTab%3Aquery%2Cid%3Anew-id%2CisOpen%3A%21t%29',
|
||||
aliasPurpose: 'savedObjectConversion',
|
||||
objectNoun: 'timeline',
|
||||
});
|
||||
|
@ -112,12 +109,11 @@ describe('useResolveRedirect', () => {
|
|||
},
|
||||
savedObjectId: 'current-saved-object-id',
|
||||
activeTab: 'some-tab',
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: false,
|
||||
}));
|
||||
renderHook(() => useResolveRedirect());
|
||||
expect(mockRedirectLegacyUrl).toHaveBeenCalledWith({
|
||||
path: 'my/cool/path?foo=bar&timeline=%28activeTab%3Asome-tab%2CgraphEventId%3Acurrent-graph-event-id%2Cid%3Anew-id%2CisOpen%3A%21f%29',
|
||||
path: 'my/cool/path?foo=bar&timeline=%28activeTab%3Asome-tab%2Cid%3Anew-id%2CisOpen%3A%21f%29',
|
||||
aliasPurpose: 'savedObjectConversion',
|
||||
objectNoun: 'timeline',
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { safeDecode, encode } from '@kbn/rison';
|
||||
import { encode, safeDecode } from '@kbn/rison';
|
||||
import { useDeepEqualSelector } from './use_selector';
|
||||
import { TimelineId } from '../../../common/types/timeline';
|
||||
import { timelineSelectors } from '../../timelines/store';
|
||||
|
@ -28,8 +28,9 @@ export const useResolveRedirect = () => {
|
|||
const { spaces } = useKibana().services;
|
||||
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const { resolveTimelineConfig, savedObjectId, show, activeTab, graphEventId } =
|
||||
useDeepEqualSelector((state) => getTimeline(state, TimelineId.active) ?? timelineDefaults);
|
||||
const { resolveTimelineConfig, savedObjectId, show, activeTab } = useDeepEqualSelector(
|
||||
(state) => getTimeline(state, TimelineId.active) ?? timelineDefaults
|
||||
);
|
||||
|
||||
const redirect = useCallback(() => {
|
||||
const searchQuery = new URLSearchParams(search);
|
||||
|
@ -40,7 +41,6 @@ export const useResolveRedirect = () => {
|
|||
id: savedObjectId ?? '',
|
||||
isOpen: !!show,
|
||||
activeTab,
|
||||
graphEventId,
|
||||
};
|
||||
const timelineSearch =
|
||||
(safeDecode(timelineRison ?? '') as TimelineUrl | null) ?? currentTimelineState;
|
||||
|
@ -72,7 +72,6 @@ export const useResolveRedirect = () => {
|
|||
updateHasRedirected(true);
|
||||
}, [
|
||||
activeTab,
|
||||
graphEventId,
|
||||
hasRedirected,
|
||||
pathname,
|
||||
resolveTimelineConfig,
|
||||
|
|
|
@ -421,7 +421,6 @@ export const mockGlobalState: State = {
|
|||
sortDirection: 'desc',
|
||||
},
|
||||
],
|
||||
graphEventId: '',
|
||||
selectAll: false,
|
||||
id: TableId.test,
|
||||
title: '',
|
||||
|
|
|
@ -5,14 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
isDetectionsPages,
|
||||
getQueryStringFromLocation,
|
||||
getParamFromQueryString,
|
||||
getObjectFromQueryString,
|
||||
useGetInitialUrlParamValue,
|
||||
encodeQueryString,
|
||||
useReplaceUrlParams,
|
||||
createHistoryEntry,
|
||||
encodeQueryString,
|
||||
getObjectFromQueryString,
|
||||
getParamFromQueryString,
|
||||
getQueryStringFromLocation,
|
||||
isDetectionsPages,
|
||||
useGetInitialUrlParamValue,
|
||||
useReplaceUrlParams,
|
||||
} from './helpers';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
|
@ -22,7 +22,7 @@ import React from 'react';
|
|||
|
||||
const flyoutString =
|
||||
"(left:(id:document-details-left,params:(id:'04cf6ea970ab702721f17755b718c1296b5f8b5fcf7b74b053eab9bc885ceb9c',indexName:.internal.alerts-security.alerts-default-000001,scopeId:alerts-page)),right:(id:document-details-right,params:(id:'04cf6ea970ab702721f17755b718c1296b5f8b5fcf7b74b053eab9bc885ceb9c',indexName:.internal.alerts-security.alerts-default-000001,scopeId:alerts-page)))";
|
||||
const testString = `sourcerer=(default:(id:security-solution-default,selectedPatterns:!(.alerts-security.alerts-default)))&timerange=(global:(linkTo:!(),timerange:(from:%272024-05-14T23:00:00.000Z%27,fromStr:now%2Fd,kind:relative,to:%272024-05-15T22:59:59.999Z%27,toStr:now%2Fd)),timeline:(linkTo:!(),timerange:(from:%272024-05-14T09:32:36.347Z%27,kind:absolute,to:%272024-05-15T09:32:36.347Z%27)))&timeline=(activeTab:query,graphEventId:%27%27,isOpen:!f)&pageFilters=!((exclude:!f,existsSelected:!f,fieldName:kibana.alert.workflow_status,hideActionBar:!t,selectedOptions:!(open),title:Status),(exclude:!f,existsSelected:!f,fieldName:kibana.alert.severity,hideActionBar:!t,selectedOptions:!(),title:Severity),(exclude:!f,existsSelected:!f,fieldName:user.name,hideActionBar:!f,selectedOptions:!(),title:User),(exclude:!f,existsSelected:!f,fieldName:host.name,hideActionBar:!f,selectedOptions:!(),title:Host))&flyout=${flyoutString}&timelineFlyout=()`;
|
||||
const testString = `sourcerer=(default:(id:security-solution-default,selectedPatterns:!(.alerts-security.alerts-default)))&timerange=(global:(linkTo:!(),timerange:(from:%272024-05-14T23:00:00.000Z%27,fromStr:now%2Fd,kind:relative,to:%272024-05-15T22:59:59.999Z%27,toStr:now%2Fd)),timeline:(linkTo:!(),timerange:(from:%272024-05-14T09:32:36.347Z%27,kind:absolute,to:%272024-05-15T09:32:36.347Z%27)))&timeline=(activeTab:query,isOpen:!f)&pageFilters=!((exclude:!f,existsSelected:!f,fieldName:kibana.alert.workflow_status,hideActionBar:!t,selectedOptions:!(open),title:Status),(exclude:!f,existsSelected:!f,fieldName:kibana.alert.severity,hideActionBar:!t,selectedOptions:!(),title:Severity),(exclude:!f,existsSelected:!f,fieldName:user.name,hideActionBar:!f,selectedOptions:!(),title:User),(exclude:!f,existsSelected:!f,fieldName:host.name,hideActionBar:!f,selectedOptions:!(),title:Host))&flyout=${flyoutString}&timelineFlyout=()`;
|
||||
|
||||
const flyoutObject = {
|
||||
left: {
|
||||
|
|
|
@ -13,9 +13,8 @@ export const useTimelineClick = () => {
|
|||
const queryTimelineById = useQueryTimelineById();
|
||||
|
||||
const handleTimelineClick = useCallback(
|
||||
(timelineId: string, onError: TimelineErrorCallback, graphEventId?: string) => {
|
||||
(timelineId: string, onError: TimelineErrorCallback) => {
|
||||
queryTimelineById({
|
||||
graphEventId,
|
||||
timelineId,
|
||||
onError,
|
||||
});
|
||||
|
|
|
@ -102,7 +102,6 @@ import {
|
|||
focusUtilityBarAction,
|
||||
onTimelineTabKeyPressed,
|
||||
resetKeyboardFocus,
|
||||
showGlobalFilters,
|
||||
} from '../../../../timelines/components/timeline/helpers';
|
||||
import { useSourcererDataView } from '../../../../sourcerer/containers';
|
||||
import { SourcererScopeName } from '../../../../sourcerer/store/model';
|
||||
|
@ -228,9 +227,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.alertsOnRuleDetailsPage) ?? tableDefaults).graphEventId
|
||||
);
|
||||
const updatedAt = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.alertsOnRuleDetailsPage) ?? tableDefaults).updated
|
||||
);
|
||||
|
@ -638,7 +634,7 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
)}
|
||||
<StyledFullHeightContainer onKeyDown={onKeyDown} ref={containerElement}>
|
||||
<EuiWindowEvent event="resize" handler={noop} />
|
||||
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar
|
||||
id={InputsModelId.global}
|
||||
pollForSignalIndex={pollForSignalIndex}
|
||||
|
|
|
@ -36,8 +36,6 @@ import { APP_ID, CASES_FEATURE_ID, VIEW_SELECTION } from '../../../../common/con
|
|||
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
|
||||
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
|
||||
import { eventsDefaultModel } from '../../../common/components/events_viewer/default_model';
|
||||
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
|
||||
import { useSessionViewNavigation } from '../../../timelines/components/timeline/tabs/session/use_session_view';
|
||||
import type { State } from '../../../common/store';
|
||||
import { inputsSelectors } from '../../../common/store';
|
||||
import { combineQueries } from '../../../common/lib/kuery';
|
||||
|
@ -84,10 +82,10 @@ interface GridContainerProps {
|
|||
hideLastPage: boolean;
|
||||
}
|
||||
|
||||
export const FullWidthFlexGroupTable = styled(EuiFlexGroup)<{ $visible: boolean }>`
|
||||
export const FullWidthFlexGroupTable = styled(EuiFlexGroup)`
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
display: ${({ $visible }) => ($visible ? 'flex' : 'none')};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const EuiDataGridContainer = styled.div<GridContainerProps>`
|
||||
|
@ -203,7 +201,6 @@ const DetectionEngineAlertsTableComponent: FC<Omit<DetectionEngineAlertTableProp
|
|||
|
||||
const {
|
||||
initialized: isDataTableInitialized,
|
||||
graphEventId,
|
||||
viewMode: tableView = eventsDefaultModel.viewMode,
|
||||
columns,
|
||||
totalCount: count,
|
||||
|
@ -383,15 +380,6 @@ const DetectionEngineAlertsTableComponent: FC<Omit<DetectionEngineAlertTableProp
|
|||
);
|
||||
}, [dispatch, tableType, finalColumns, isDataTableInitialized]);
|
||||
|
||||
const { Navigation } = useSessionViewNavigation({
|
||||
scopeId: tableType,
|
||||
});
|
||||
|
||||
const graphOverlay = useMemo(() => {
|
||||
const shouldShowOverlay = graphEventId != null && graphEventId.length > 0;
|
||||
return shouldShowOverlay ? <GraphOverlay scopeId={tableType} Navigation={Navigation} /> : null;
|
||||
}, [graphEventId, tableType, Navigation]);
|
||||
|
||||
const toolbarVisibility = useMemo(
|
||||
() => ({
|
||||
showColumnSelector: !isEventRenderedView,
|
||||
|
@ -438,9 +426,7 @@ const DetectionEngineAlertsTableComponent: FC<Omit<DetectionEngineAlertTableProp
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{graphOverlay}
|
||||
<FullWidthFlexGroupTable $visible={!graphEventId && graphOverlay == null} gutterSize="none">
|
||||
<FullWidthFlexGroupTable gutterSize="none">
|
||||
<StatefulEventContext.Provider value={activeStatefulEventContext}>
|
||||
<EuiDataGridContainer hideLastPage={false}>
|
||||
<AlertTableCellContextProvider
|
||||
|
@ -490,7 +476,6 @@ const DetectionEngineAlertsTableComponent: FC<Omit<DetectionEngineAlertTableProp
|
|||
</EuiDataGridContainer>
|
||||
</StatefulEventContext.Provider>
|
||||
</FullWidthFlexGroupTable>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -72,7 +72,6 @@ import {
|
|||
focusUtilityBarAction,
|
||||
onTimelineTabKeyPressed,
|
||||
resetKeyboardFocus,
|
||||
showGlobalFilters,
|
||||
} from '../../../timelines/components/timeline/helpers';
|
||||
import {
|
||||
buildAlertAssigneesFilter,
|
||||
|
@ -111,9 +110,6 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ()
|
|||
const dispatch = useDispatch();
|
||||
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.alertsOnAlertsPage) ?? tableDefaults).graphEventId
|
||||
);
|
||||
|
||||
const isTableLoading = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.alertsOnAlertsPage) ?? tableDefaults).isLoading
|
||||
|
@ -397,7 +393,7 @@ const DetectionEnginePageComponent: React.FC<DetectionEngineComponentProps> = ()
|
|||
) : !signalIndexNeedsInit && hasIndexRead && canUserREAD ? (
|
||||
<StyledFullHeightContainer onKeyDown={onKeyDown} ref={containerElement}>
|
||||
<EuiWindowEvent event="resize" handler={noop} />
|
||||
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar
|
||||
id={InputsModelId.global}
|
||||
pollForSignalIndex={pollForSignalIndex}
|
||||
|
|
|
@ -15,11 +15,9 @@ import {
|
|||
import { noop } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { buildEsQuery } from '@kbn/es-query';
|
||||
import { getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import { dataTableSelectors, tableDefaults, TableId } from '@kbn/securitysolution-data-table';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import type { NarrowDateRange } from '../../../../common/components/ml/types';
|
||||
import { dataViewSpecToViewBase } from '../../../../common/lib/kuery';
|
||||
|
@ -36,7 +34,7 @@ import { AlertsByStatus } from '../../../../overview/components/detection_respon
|
|||
import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index';
|
||||
import { useAlertsPrivileges } from '../../../../detections/containers/detection_engine/alerts/use_alerts_privileges';
|
||||
import { InputsModelId } from '../../../../common/store/inputs/constants';
|
||||
import { LastEventIndexKey, type HostItem } from '../../../../../common/search_strategy';
|
||||
import { type HostItem, LastEventIndexKey } from '../../../../../common/search_strategy';
|
||||
import { EntityType } from '../../../../../common/entity_analytics/types';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
import { FiltersGlobal } from '../../../../common/components/filters_global';
|
||||
|
@ -60,19 +58,14 @@ import { inputsSelectors } from '../../../../common/store';
|
|||
import { setHostDetailsTablesActivePageToZero } from '../../store/actions';
|
||||
import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions';
|
||||
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
|
||||
|
||||
import { HostDetailsTabs } from './details_tabs';
|
||||
import { navTabsHostDetails } from './nav_tabs';
|
||||
import type { HostDetailsProps } from './types';
|
||||
import { HostsType } from '../../store/model';
|
||||
import { getHostDetailsPageFilters } from './helpers';
|
||||
import { showGlobalFilters } from '../../../../timelines/components/timeline/helpers';
|
||||
import { useGlobalFullScreen } from '../../../../common/containers/use_full_screen';
|
||||
import { Display } from '../display';
|
||||
import {
|
||||
useDeepEqualSelector,
|
||||
useShallowEqualSelector,
|
||||
} from '../../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { ID, useHostDetails } from '../../containers/hosts/details';
|
||||
import { manageQuery } from '../../../../common/components/page/manage_query';
|
||||
import { useInvalidFilterQuery } from '../../../../common/hooks/use_invalid_filter_query';
|
||||
|
@ -92,10 +85,6 @@ const HostOverviewManage = manageQuery(HostOverview);
|
|||
|
||||
const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDetailsPagePath }) => {
|
||||
const dispatch = useDispatch();
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.hostsPageEvents) ?? tableDefaults).graphEventId
|
||||
);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
|
@ -230,7 +219,7 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
{indicesExist ? (
|
||||
<>
|
||||
<EuiWindowEvent event="resize" handler={noop} />
|
||||
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar id={InputsModelId.global} sourcererDataView={sourcererDataView} />
|
||||
</FiltersGlobal>
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import { useParams } from 'react-router-dom';
|
|||
import type { Filter } from '@kbn/es-query';
|
||||
import { isTab } from '@kbn/timelines-plugin/public';
|
||||
import { getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import { dataTableSelectors, tableDefaults, TableId } from '@kbn/securitysolution-data-table';
|
||||
import { LastEventIndexKey } from '@kbn/timelines-plugin/common';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||
import { InputsModelId } from '../../../common/store/inputs/constants';
|
||||
|
@ -33,7 +32,6 @@ import { useKibana } from '../../../common/lib/kibana';
|
|||
import { convertToBuildEsQuery } from '../../../common/lib/kuery';
|
||||
import type { State } from '../../../common/store';
|
||||
import { inputsSelectors } from '../../../common/store';
|
||||
|
||||
import { SpyRoute } from '../../../common/utils/route/spy_routes';
|
||||
import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities';
|
||||
import { Display } from './display';
|
||||
|
@ -46,10 +44,9 @@ import { HostsTableType } from '../store/model';
|
|||
import {
|
||||
onTimelineTabKeyPressed,
|
||||
resetKeyboardFocus,
|
||||
showGlobalFilters,
|
||||
} from '../../../timelines/components/timeline/helpers';
|
||||
import { useSourcererDataView } from '../../../sourcerer/containers';
|
||||
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
|
||||
import { ID } from '../containers/hosts';
|
||||
import { EmptyPrompt } from '../../../common/components/empty_prompt';
|
||||
|
@ -70,10 +67,6 @@ const StyledFullHeightContainer = styled.div`
|
|||
|
||||
const HostsComponent = () => {
|
||||
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.hostsPageEvents) ?? tableDefaults).graphEventId
|
||||
);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
|
@ -190,7 +183,7 @@ const HostsComponent = () => {
|
|||
{indicesExist ? (
|
||||
<StyledFullHeightContainer onKeyDown={onKeyDown} ref={containerElement}>
|
||||
<EuiWindowEvent event="resize" handler={noop} />
|
||||
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar id={InputsModelId.global} sourcererDataView={sourcererDataView} />
|
||||
</FiltersGlobal>
|
||||
|
||||
|
|
|
@ -10,10 +10,8 @@ import { noop } from 'lodash/fp';
|
|||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { isTab } from '@kbn/timelines-plugin/public';
|
||||
import { getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import { dataTableSelectors, tableDefaults, TableId } from '@kbn/securitysolution-data-table';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../common/hooks/use_experimental_features';
|
||||
import { InputsModelId } from '../../../common/store/inputs/constants';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
|
@ -22,7 +20,6 @@ import { FiltersGlobal } from '../../../common/components/filters_global';
|
|||
import { HeaderPage } from '../../../common/components/header_page';
|
||||
import { LastEventTime } from '../../../common/components/last_event_time';
|
||||
import { TabNavigation } from '../../../common/components/navigation/tab_navigation';
|
||||
|
||||
import { NetworkKpiComponent } from '../components/kpi_network';
|
||||
import { SiemSearchBar } from '../../../common/components/search_bar';
|
||||
import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper';
|
||||
|
@ -42,16 +39,16 @@ import { NetworkRouteType } from './navigation/types';
|
|||
import {
|
||||
onTimelineTabKeyPressed,
|
||||
resetKeyboardFocus,
|
||||
showGlobalFilters,
|
||||
} from '../../../timelines/components/timeline/helpers';
|
||||
import { useSourcererDataView } from '../../../sourcerer/containers';
|
||||
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { useInvalidFilterQuery } from '../../../common/hooks/use_invalid_filter_query';
|
||||
import { sourceOrDestinationIpExistsFilter } from '../../../common/components/visualization_actions/utils';
|
||||
import { EmptyPrompt } from '../../../common/components/empty_prompt';
|
||||
import { useDataView } from '../../../data_view_manager/hooks/use_data_view';
|
||||
import { useDataViewSpec } from '../../../data_view_manager/hooks/use_data_view_spec';
|
||||
import { useSelectedPatterns } from '../../../data_view_manager/hooks/use_selected_patterns';
|
||||
|
||||
/**
|
||||
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
|
||||
*/
|
||||
|
@ -66,10 +63,6 @@ const ID = 'NetworkQueryId';
|
|||
const NetworkComponent = React.memo<NetworkComponentProps>(
|
||||
({ hasMlUserPermissions, capabilitiesFetched }) => {
|
||||
const containerElement = useRef<HTMLDivElement | null>(null);
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.networkPageEvents) ?? tableDefaults).graphEventId
|
||||
);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
|
@ -157,7 +150,7 @@ const NetworkComponent = React.memo<NetworkComponentProps>(
|
|||
{indicesExist ? (
|
||||
<StyledFullHeightContainer onKeyDown={onKeyDown} ref={containerElement}>
|
||||
<EuiWindowEvent event="resize" handler={noop} />
|
||||
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar sourcererDataView={sourcererDataView} id={InputsModelId.global} />
|
||||
</FiltersGlobal>
|
||||
|
||||
|
|
|
@ -15,11 +15,9 @@ import {
|
|||
import { noop } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { getEsQueryConfig } from '@kbn/data-plugin/common';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { buildEsQuery } from '@kbn/es-query';
|
||||
import { dataTableSelectors, TableId } from '@kbn/securitysolution-data-table';
|
||||
import { LastEventIndexKey } from '@kbn/timelines-plugin/common';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { dataViewSpecToViewBase } from '../../../../common/lib/kuery';
|
||||
|
@ -49,28 +47,21 @@ import { useAlertsPrivileges } from '../../../../detections/containers/detection
|
|||
import { setUsersDetailsTablesActivePageToZero } from '../../store/actions';
|
||||
import { setAbsoluteRangeDatePicker } from '../../../../common/store/inputs/actions';
|
||||
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
|
||||
|
||||
import { UsersDetailsTabs } from './details_tabs';
|
||||
import { navTabsUsersDetails } from './nav_tabs';
|
||||
import type { UsersDetailsProps } from './types';
|
||||
import { getUsersDetailsPageFilters } from './helpers';
|
||||
import { showGlobalFilters } from '../../../../timelines/components/timeline/helpers';
|
||||
import { useGlobalFullScreen } from '../../../../common/containers/use_full_screen';
|
||||
import { timelineDefaults } from '../../../../timelines/store/defaults';
|
||||
import { useSourcererDataView } from '../../../../sourcerer/containers';
|
||||
import {
|
||||
useDeepEqualSelector,
|
||||
useShallowEqualSelector,
|
||||
} from '../../../../common/hooks/use_selector';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { useInvalidFilterQuery } from '../../../../common/hooks/use_invalid_filter_query';
|
||||
import { LastEventTime } from '../../../../common/components/last_event_time';
|
||||
import { EntityType } from '../../../../../common/entity_analytics/types';
|
||||
|
||||
import { AnomalyTableProvider } from '../../../../common/components/ml/anomaly/anomaly_table_provider';
|
||||
import type { UserSummaryProps } from '../../../../overview/components/user_overview';
|
||||
import {
|
||||
UserOverview,
|
||||
USER_OVERVIEW_RISK_SCORE_QUERY_ID,
|
||||
UserOverview,
|
||||
} from '../../../../overview/components/user_overview';
|
||||
import { useObservedUserDetails } from '../../containers/users/observed_details';
|
||||
import { useQueryInspector } from '../../../../common/components/page/manage_query';
|
||||
|
@ -93,10 +84,6 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
usersDetailsPagePath,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.hostsPageEvents) ?? timelineDefaults).graphEventId
|
||||
);
|
||||
const getGlobalFiltersQuerySelector = useMemo(
|
||||
() => inputsSelectors.globalFiltersQuerySelector(),
|
||||
[]
|
||||
|
@ -230,7 +217,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
{indicesExist ? (
|
||||
<>
|
||||
<EuiWindowEvent event="resize" handler={noop} />
|
||||
<FiltersGlobal show={showGlobalFilters({ globalFullScreen, graphEventId })}>
|
||||
<FiltersGlobal>
|
||||
<SiemSearchBar sourcererDataView={sourcererDataView} id={InputsModelId.global} />
|
||||
</FiltersGlobal>
|
||||
|
||||
|
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { cleanup, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
// Necessary until components being tested are migrated of styled-components https://github.com/elastic/kibana/issues/219037
|
||||
import 'jest-styled-components';
|
||||
import {
|
||||
useGlobalFullScreen,
|
||||
useTimelineFullScreen,
|
||||
} from '../../../common/containers/use_full_screen';
|
||||
import { createMockStore, mockGlobalState, TestProviders } from '../../../common/mock';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { GraphOverlay } from '.';
|
||||
import { useStateSyncingActions } from '../../../resolver/view/use_state_syncing_actions';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
|
||||
jest.mock('../../../common/containers/use_full_screen', () => ({
|
||||
useGlobalFullScreen: jest.fn(),
|
||||
useTimelineFullScreen: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
return { ...actual, useLocation: jest.fn().mockReturnValue({ pathname: '' }) };
|
||||
});
|
||||
|
||||
jest.mock('../../../resolver/view/use_resolver_query_params_cleaner');
|
||||
jest.mock('../../../resolver/view/use_state_syncing_actions');
|
||||
const useStateSyncingActionsMock = useStateSyncingActions as jest.Mock;
|
||||
|
||||
jest.mock('../../../resolver/view/use_sync_selected_node');
|
||||
jest.mock('../../../common/lib/kibana', () => {
|
||||
const original = jest.requireActual('../../../common/lib/kibana');
|
||||
return {
|
||||
...original,
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
sessionView: {
|
||||
getSessionView: () => <div />,
|
||||
},
|
||||
data: {
|
||||
search: {
|
||||
search: jest.fn(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
jest.mock('react-redux', () => {
|
||||
const original = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useDispatch: () => mockDispatch,
|
||||
};
|
||||
});
|
||||
|
||||
describe('GraphOverlay', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useGlobalFullScreen as jest.Mock).mockReturnValue({
|
||||
globalFullScreen: false,
|
||||
setGlobalFullScreen: jest.fn(),
|
||||
});
|
||||
(useTimelineFullScreen as jest.Mock).mockReturnValue({
|
||||
timelineFullScreen: false,
|
||||
setTimelineFullScreen: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
describe('when used in an events viewer (i.e. in the Detections view, or the Host > Events view)', () => {
|
||||
test('it has 100% width when NOT in full screen mode', () => {
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay Navigation={<div />} scopeId={TableId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const overlayContainer = wrapper.getByTestId('overlayContainer');
|
||||
expect(overlayContainer).toHaveStyleRule('width', '100%');
|
||||
});
|
||||
|
||||
test('it has a fixed position when in full screen mode', () => {
|
||||
(useGlobalFullScreen as jest.Mock).mockReturnValue({
|
||||
globalFullScreen: true,
|
||||
setGlobalFullScreen: jest.fn(),
|
||||
});
|
||||
(useTimelineFullScreen as jest.Mock).mockReturnValue({
|
||||
timelineFullScreen: false,
|
||||
setTimelineFullScreen: jest.fn(),
|
||||
});
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay Navigation={<div />} scopeId={TableId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const overlayContainer = wrapper.getByTestId('overlayContainer');
|
||||
expect(overlayContainer).toHaveStyleRule('position', 'fixed');
|
||||
});
|
||||
|
||||
test('it gets index pattern from default data view', () => {
|
||||
render(
|
||||
<TestProviders
|
||||
store={createMockStore({
|
||||
...mockGlobalState,
|
||||
timeline: {
|
||||
...mockGlobalState.timeline,
|
||||
timelineById: {
|
||||
[TimelineId.test]: {
|
||||
...mockGlobalState.timeline.timelineById[TimelineId.test],
|
||||
graphEventId: 'definitely-not-null',
|
||||
},
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<GraphOverlay Navigation={<div />} scopeId={TableId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(useStateSyncingActionsMock.mock.calls[0][0].indices).toEqual(
|
||||
mockGlobalState.sourcerer.sourcererScopes.analyzer.selectedPatterns
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when used in the active timeline', () => {
|
||||
const timelineId = TimelineId.active;
|
||||
afterAll(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test('it has 100% width when NOT in full screen mode', () => {
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay Navigation={<div />} scopeId={timelineId} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const overlayContainer = wrapper.getByTestId('overlayContainer');
|
||||
expect(overlayContainer).toHaveStyleRule('width', '100%');
|
||||
});
|
||||
|
||||
test('it has 100% width when the active timeline is in full screen mode', () => {
|
||||
(useGlobalFullScreen as jest.Mock).mockReturnValue({
|
||||
globalFullScreen: false,
|
||||
setGlobalFullScreen: jest.fn(),
|
||||
});
|
||||
(useTimelineFullScreen as jest.Mock).mockReturnValue({
|
||||
timelineFullScreen: true, // <-- true when the active timeline is in full screen mode
|
||||
setTimelineFullScreen: jest.fn(),
|
||||
});
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay Navigation={<div />} scopeId={timelineId} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
const overlayContainer = wrapper.getByTestId('overlayContainer');
|
||||
expect(overlayContainer).toHaveStyleRule('width', '100%');
|
||||
});
|
||||
|
||||
test('it clears the graph event id on unmount', () => {
|
||||
(useGlobalFullScreen as jest.Mock).mockReturnValue({
|
||||
globalFullScreen: false,
|
||||
setGlobalFullScreen: jest.fn(),
|
||||
});
|
||||
(useTimelineFullScreen as jest.Mock).mockReturnValue({
|
||||
timelineFullScreen: true,
|
||||
setTimelineFullScreen: jest.fn(),
|
||||
});
|
||||
|
||||
const wrapper = render(
|
||||
<TestProviders
|
||||
store={createMockStore({
|
||||
...mockGlobalState,
|
||||
timeline: {
|
||||
...mockGlobalState.timeline,
|
||||
timelineById: {
|
||||
[timelineId]: {
|
||||
...mockGlobalState.timeline.timelineById[timelineId],
|
||||
graphEventId: 'test_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<GraphOverlay Navigation={<div />} scopeId={timelineId} />
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper.unmount();
|
||||
expect(mockDispatch).toHaveBeenCalledWith({
|
||||
payload: { id: 'timeline-1', graphEventId: '' },
|
||||
type: 'x-pack/security_solution/local/timeline/UPDATE_TIMELINE_GRAPH_EVENT_ID',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,159 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import styled, { css } from 'styled-components';
|
||||
import { dataTableSelectors, tableDefaults } from '@kbn/securitysolution-data-table';
|
||||
import {
|
||||
getScopedActions,
|
||||
isActiveTimeline,
|
||||
isInTableScope,
|
||||
isTimelineScope,
|
||||
} from '../../../helpers';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
import { InputsModelId } from '../../../common/store/inputs/constants';
|
||||
import {
|
||||
useGlobalFullScreen,
|
||||
useTimelineFullScreen,
|
||||
} from '../../../common/containers/use_full_screen';
|
||||
import { inputsActions } from '../../../common/store/actions';
|
||||
import { Resolver } from '../../../resolver/view';
|
||||
import { useTimelineDataFilters } from '../../containers/use_timeline_data_filters';
|
||||
import { timelineSelectors } from '../../store';
|
||||
import { timelineDefaults } from '../../store/defaults';
|
||||
import { isFullScreen } from '../timeline/helpers';
|
||||
|
||||
const OverlayStyle = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const OverlayContainer = styled.div`
|
||||
${OverlayStyle}
|
||||
`;
|
||||
|
||||
const FullScreenOverlayStyles = css`
|
||||
background-color: ${({ theme }) => `${theme.eui.euiColorEmptyShade};`}
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 2em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 3000;
|
||||
`;
|
||||
|
||||
const FullScreenOverlayContainer = styled.div`
|
||||
${FullScreenOverlayStyles}
|
||||
`;
|
||||
|
||||
const StyledResolver = styled(Resolver)`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
interface GraphOverlayProps {
|
||||
scopeId: string;
|
||||
Navigation: JSX.Element | null;
|
||||
}
|
||||
|
||||
const GraphOverlayComponent: React.FC<GraphOverlayProps> = ({ Navigation, scopeId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const { timelineFullScreen } = useTimelineFullScreen();
|
||||
|
||||
const getScope = useMemo(() => {
|
||||
if (isInTableScope(scopeId)) {
|
||||
return dataTableSelectors.getTableByIdSelector();
|
||||
} else if (isTimelineScope(scopeId)) {
|
||||
return timelineSelectors.getTimelineByIdSelector();
|
||||
}
|
||||
}, [scopeId]);
|
||||
|
||||
const defaults = isInTableScope(scopeId) ? tableDefaults : timelineDefaults;
|
||||
|
||||
const { graphEventId } = useDeepEqualSelector(
|
||||
(state) => (getScope && getScope(state, scopeId)) ?? defaults
|
||||
);
|
||||
|
||||
const fullScreen = useMemo(
|
||||
() =>
|
||||
isFullScreen({
|
||||
globalFullScreen,
|
||||
isActiveTimelines: isActiveTimeline(scopeId),
|
||||
timelineFullScreen,
|
||||
}),
|
||||
[globalFullScreen, scopeId, timelineFullScreen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const scopedActions = getScopedActions(scopeId);
|
||||
if (scopedActions) {
|
||||
dispatch(scopedActions.updateGraphEventId({ id: scopeId, graphEventId: '' }));
|
||||
}
|
||||
if (isActiveTimeline(scopeId)) {
|
||||
dispatch(inputsActions.setFullScreen({ id: InputsModelId.timeline, fullScreen: false }));
|
||||
} else {
|
||||
dispatch(inputsActions.setFullScreen({ id: InputsModelId.global, fullScreen: false }));
|
||||
}
|
||||
};
|
||||
}, [dispatch, scopeId]);
|
||||
|
||||
const { from, to, shouldUpdate, selectedPatterns } = useTimelineDataFilters(
|
||||
isActiveTimeline(scopeId)
|
||||
);
|
||||
const filters = useMemo(() => {
|
||||
return { from, to };
|
||||
}, [from, to]);
|
||||
|
||||
const resolver = useMemo(
|
||||
() =>
|
||||
graphEventId !== undefined ? (
|
||||
<StyledResolver
|
||||
databaseDocumentID={graphEventId}
|
||||
resolverComponentInstanceID={scopeId}
|
||||
indices={selectedPatterns}
|
||||
shouldUpdate={shouldUpdate}
|
||||
filters={filters}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" css={{ height: '100%' }}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[graphEventId, scopeId, selectedPatterns, shouldUpdate, filters]
|
||||
);
|
||||
|
||||
if (fullScreen && !isActiveTimeline(scopeId)) {
|
||||
return (
|
||||
<FullScreenOverlayContainer data-test-subj="overlayContainer">
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{Navigation}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
{resolver}
|
||||
</FullScreenOverlayContainer>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<OverlayContainer data-test-subj="overlayContainer">
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiFlexGroup gutterSize="none" justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{Navigation}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
{resolver}
|
||||
</OverlayContainer>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const GraphOverlay = React.memo(GraphOverlayComponent);
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiContextMenuPanel, EuiContextMenuItem, EuiPopover, EuiButtonEmpty } from '@elastic/eui';
|
||||
import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import type { CaseUI } from '@kbn/cases-plugin/common';
|
||||
|
@ -17,7 +17,7 @@ import { setInsertTimeline, showTimeline } from '../../../store/actions';
|
|||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../../common/api/timeline';
|
||||
import { getCreateCaseUrl, getCaseDetailsUrl } from '../../../../common/components/link_to';
|
||||
import { getCaseDetailsUrl, getCreateCaseUrl } from '../../../../common/components/link_to';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -34,7 +34,6 @@ interface AttachToCaseButtonProps {
|
|||
export const AttachToCaseButton = React.memo<AttachToCaseButtonProps>(({ timelineId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
graphEventId,
|
||||
savedObjectId,
|
||||
status: timelineStatus,
|
||||
title: timelineTitle,
|
||||
|
@ -62,22 +61,13 @@ export const AttachToCaseButton = React.memo<AttachToCaseButtonProps>(({ timelin
|
|||
});
|
||||
dispatch(
|
||||
setInsertTimeline({
|
||||
graphEventId,
|
||||
timelineId,
|
||||
timelineSavedObjectId: savedObjectId,
|
||||
timelineTitle,
|
||||
})
|
||||
);
|
||||
},
|
||||
[
|
||||
closeCaseModal,
|
||||
dispatch,
|
||||
graphEventId,
|
||||
navigateToApp,
|
||||
savedObjectId,
|
||||
timelineId,
|
||||
timelineTitle,
|
||||
]
|
||||
[closeCaseModal, dispatch, navigateToApp, savedObjectId, timelineId, timelineTitle]
|
||||
);
|
||||
|
||||
const attachToNewCase = useCallback(() => {
|
||||
|
@ -89,7 +79,6 @@ export const AttachToCaseButton = React.memo<AttachToCaseButtonProps>(({ timelin
|
|||
}).then(() => {
|
||||
dispatch(
|
||||
setInsertTimeline({
|
||||
graphEventId,
|
||||
timelineId,
|
||||
timelineSavedObjectId: savedObjectId,
|
||||
timelineTitle: timelineTitle.length > 0 ? timelineTitle : UNTITLED_TIMELINE,
|
||||
|
@ -97,15 +86,7 @@ export const AttachToCaseButton = React.memo<AttachToCaseButtonProps>(({ timelin
|
|||
);
|
||||
dispatch(showTimeline({ id: TimelineId.active, show: false }));
|
||||
});
|
||||
}, [
|
||||
dispatch,
|
||||
graphEventId,
|
||||
navigateToApp,
|
||||
savedObjectId,
|
||||
timelineId,
|
||||
timelineTitle,
|
||||
togglePopover,
|
||||
]);
|
||||
}, [dispatch, navigateToApp, savedObjectId, timelineId, timelineTitle, togglePopover]);
|
||||
|
||||
const attachToExistingCase = useCallback(() => {
|
||||
togglePopover();
|
||||
|
|
|
@ -6,30 +6,30 @@
|
|||
*/
|
||||
|
||||
import { cloneDeep, omit } from 'lodash/fp';
|
||||
import { waitFor, renderHook } from '@testing-library/react';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { mockTimelineResults } from '../../../common/mock';
|
||||
import { timelineDefaults } from '../../store/defaults';
|
||||
import type { QueryTimelineById } from './helpers';
|
||||
import {
|
||||
defaultTimelineToTimelineModel,
|
||||
formatTimelineResponseToModel,
|
||||
getNotesCount,
|
||||
getPinnedEventCount,
|
||||
isUntitled,
|
||||
useQueryTimelineById,
|
||||
formatTimelineResponseToModel,
|
||||
} from './helpers';
|
||||
import type { OpenTimelineResult } from './types';
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import {
|
||||
TimelineTypeEnum,
|
||||
TimelineStatusEnum,
|
||||
type ColumnHeaderResult,
|
||||
type RowRendererId,
|
||||
TimelineStatusEnum,
|
||||
TimelineTypeEnum,
|
||||
} from '../../../../common/api/timeline';
|
||||
import {
|
||||
mockTimeline as mockSelectedTimeline,
|
||||
mockTemplate as mockSelectedTemplate,
|
||||
mockTimeline as mockSelectedTimeline,
|
||||
} from './__mocks__';
|
||||
import { resolveTimeline } from '../../containers/api';
|
||||
import { defaultUdtHeaders } from '../timeline/body/column_headers/default_headers';
|
||||
|
@ -623,7 +623,6 @@ describe('helpers', () => {
|
|||
|
||||
const args: QueryTimelineById = {
|
||||
duplicate: false,
|
||||
graphEventId: '',
|
||||
timelineId: '',
|
||||
timelineType: TimelineTypeEnum.default,
|
||||
onError,
|
||||
|
@ -669,7 +668,6 @@ describe('helpers', () => {
|
|||
const onOpenTimeline = jest.fn();
|
||||
const args: QueryTimelineById = {
|
||||
duplicate: false,
|
||||
graphEventId: '',
|
||||
timelineId: '',
|
||||
timelineType: TimelineTypeEnum.default,
|
||||
openTimeline: true,
|
||||
|
@ -699,7 +697,6 @@ describe('helpers', () => {
|
|||
expect(mockUpdateTimeline).toHaveBeenCalledWith({
|
||||
timeline: {
|
||||
...timeline,
|
||||
graphEventId: '',
|
||||
show: true,
|
||||
dateRange: {
|
||||
start: '2020-07-07T08:20:18.966Z',
|
||||
|
@ -724,7 +721,6 @@ describe('helpers', () => {
|
|||
(resolveTimeline as jest.Mock).mockResolvedValue(selectedTimeline);
|
||||
const newArgs: QueryTimelineById = {
|
||||
duplicate: false,
|
||||
graphEventId: '',
|
||||
timelineId: undefined,
|
||||
timelineType: TimelineTypeEnum.default,
|
||||
onOpenTimeline,
|
||||
|
@ -798,7 +794,6 @@ describe('helpers', () => {
|
|||
const onOpenTimeline = jest.fn();
|
||||
const args = {
|
||||
duplicate: false,
|
||||
graphEventId: '',
|
||||
timelineId: '',
|
||||
timelineType: TimelineTypeEnum.template,
|
||||
onOpenTimeline,
|
||||
|
|
|
@ -11,13 +11,14 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
import deepMerge from 'deepmerge';
|
||||
import { useDiscoverInTimelineContext } from '../../../common/components/discover_in_timeline/use_discover_in_timeline_context';
|
||||
import type { ColumnHeaderOptions } from '../../../../common/types/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import type {
|
||||
TimelineResponse,
|
||||
ColumnHeaderResult,
|
||||
FilterTimelineResult,
|
||||
DataProviderResult,
|
||||
PinnedEvent,
|
||||
FilterTimelineResult,
|
||||
Note,
|
||||
PinnedEvent,
|
||||
TimelineResponse,
|
||||
} from '../../../../common/api/timeline';
|
||||
import {
|
||||
DataProviderTypeEnum,
|
||||
|
@ -26,7 +27,6 @@ import {
|
|||
type TimelineType,
|
||||
TimelineTypeEnum,
|
||||
} from '../../../../common/api/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import { useUpdateTimeline } from './use_update_timeline';
|
||||
|
||||
import type { TimelineModel } from '../../store/model';
|
||||
|
@ -292,7 +292,6 @@ export const formatTimelineResponseToModel = (
|
|||
export interface QueryTimelineById {
|
||||
activeTimelineTab?: TimelineTabs;
|
||||
duplicate?: boolean;
|
||||
graphEventId?: string;
|
||||
timelineId?: string;
|
||||
timelineType?: TimelineType;
|
||||
onError?: TimelineErrorCallback;
|
||||
|
@ -308,7 +307,6 @@ export const useQueryTimelineById = () => {
|
|||
return ({
|
||||
activeTimelineTab = TimelineTabs.query,
|
||||
duplicate = false,
|
||||
graphEventId = '',
|
||||
timelineId,
|
||||
timelineType,
|
||||
onError,
|
||||
|
@ -369,7 +367,6 @@ export const useQueryTimelineById = () => {
|
|||
timeline: {
|
||||
...timeline,
|
||||
activeTab: activeTimelineTab,
|
||||
graphEventId,
|
||||
show: openTimeline,
|
||||
dateRange: { start: from, end: to },
|
||||
savedSearchId: timeline.savedSearchId,
|
||||
|
|
|
@ -19,7 +19,6 @@ import {
|
|||
handleIsOperator,
|
||||
isFullScreen,
|
||||
isPrimitiveArray,
|
||||
showGlobalFilters,
|
||||
} from './helpers';
|
||||
|
||||
import { mockBrowserFields } from '../../../common/containers/source/mock';
|
||||
|
@ -246,32 +245,6 @@ describe('Build KQL Query', () => {
|
|||
'(name : "Provider 1" and name : "Provider 3" and name : "Provider 4") or (name : "Provider 2" and name : "Provider 5") or (name : "Provider 3") or (name : "Provider 4") or (name : "Provider 5") or (name : "Provider 6") or (name : "Provider 7") or (name : "Provider 8") or (name : "Provider 9") or (name : "Provider 10")'
|
||||
);
|
||||
});
|
||||
|
||||
describe('showGlobalFilters', () => {
|
||||
test('it returns false when `globalFullScreen` is true and `graphEventId` is NOT an empty string, because Resolver IS showing', () => {
|
||||
expect(showGlobalFilters({ globalFullScreen: true, graphEventId: 'a valid id' })).toBe(false);
|
||||
});
|
||||
|
||||
test('it returns true when `globalFullScreen` is true and `graphEventId` is undefined, because Resolver is NOT showing', () => {
|
||||
expect(showGlobalFilters({ globalFullScreen: true, graphEventId: undefined })).toBe(true);
|
||||
});
|
||||
|
||||
test('it returns true when `globalFullScreen` is true and `graphEventId` is an empty string, because Resolver is NOT showing', () => {
|
||||
expect(showGlobalFilters({ globalFullScreen: true, graphEventId: '' })).toBe(true);
|
||||
});
|
||||
|
||||
test('it returns true when `globalFullScreen` is false and `graphEventId` is NOT an empty string, because Resolver IS showing', () => {
|
||||
expect(showGlobalFilters({ globalFullScreen: false, graphEventId: 'a valid id' })).toBe(true);
|
||||
});
|
||||
|
||||
test('it returns true when `globalFullScreen` is false and `graphEventId` is undefined, because Resolver is NOT showing', () => {
|
||||
expect(showGlobalFilters({ globalFullScreen: false, graphEventId: undefined })).toBe(true);
|
||||
});
|
||||
|
||||
test('it returns true when `globalFullScreen` is false and `graphEventId` is an empty string, because Resolver is NOT showing', () => {
|
||||
expect(showGlobalFilters({ globalFullScreen: false, graphEventId: '' })).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isStringOrNumberArray', () => {
|
||||
|
|
|
@ -20,11 +20,11 @@ import { prepareKQLParam, prepareKQLStringParam } from '../../../../common/utils
|
|||
import { assertUnreachable } from '../../../../common/utility_types';
|
||||
import type { BrowserFields } from '../../../common/containers/source';
|
||||
import {
|
||||
convertDateFieldToQuery,
|
||||
checkIfFieldTypeIsDate,
|
||||
convertNestedFieldToQuery,
|
||||
convertNestedFieldToExistQuery,
|
||||
checkIfFieldTypeIsNested,
|
||||
convertDateFieldToQuery,
|
||||
convertNestedFieldToExistQuery,
|
||||
convertNestedFieldToQuery,
|
||||
type PrimitiveOrArrayOfPrimitives,
|
||||
} from '../../../common/lib/kuery';
|
||||
import type { DataProvider, DataProvidersAnd } from './data_providers/data_provider';
|
||||
|
@ -91,39 +91,8 @@ export const buildGlobalQuery = (dataProviders: DataProvider[], browserFields: B
|
|||
*/
|
||||
export const STATEFUL_EVENT_CSS_CLASS_NAME = 'event-column-view';
|
||||
|
||||
export const resolverIsShowing = (graphEventId: string | undefined): boolean =>
|
||||
graphEventId != null && graphEventId !== '';
|
||||
|
||||
export const showGlobalFilters = ({
|
||||
globalFullScreen,
|
||||
graphEventId,
|
||||
}: {
|
||||
globalFullScreen: boolean;
|
||||
graphEventId: string | undefined;
|
||||
}): boolean => (globalFullScreen && resolverIsShowing(graphEventId) ? false : true);
|
||||
|
||||
/**
|
||||
* The `aria-colindex` of the Timeline actions column
|
||||
*/
|
||||
export const ACTIONS_COLUMN_ARIA_COL_INDEX = '1';
|
||||
|
||||
/**
|
||||
* Every column index offset by `2`, because, per https://www.w3.org/TR/wai-aria-practices-1.1/examples/grid/dataGrids.html
|
||||
* the `aria-colindex` attribute starts at `1`, and the "actions column" is always the first column
|
||||
*/
|
||||
export const ARIA_COLUMN_INDEX_OFFSET = 2;
|
||||
|
||||
export const EVENTS_COUNT_BUTTON_CLASS_NAME = 'local-events-count-button';
|
||||
|
||||
/** Calculates the total number of pages in a (timeline) events view */
|
||||
export const calculateTotalPages = ({
|
||||
itemsCount,
|
||||
itemsPerPage,
|
||||
}: {
|
||||
itemsCount: number;
|
||||
itemsPerPage: number;
|
||||
}): number => (itemsCount === 0 || itemsPerPage === 0 ? 0 : Math.ceil(itemsCount / itemsPerPage));
|
||||
|
||||
/** Returns true if the events table has focus */
|
||||
export const tableHasFocus = (containerElement: HTMLElement | null): boolean =>
|
||||
elementOrChildrenHasFocus(
|
||||
|
|
|
@ -78,7 +78,6 @@ const StatefulTimelineComponent: React.FC<Props> = ({
|
|||
const {
|
||||
dataViewId: selectedDataViewIdTimeline,
|
||||
indexNames: selectedPatternsTimeline,
|
||||
graphEventId,
|
||||
savedObjectId,
|
||||
timelineType,
|
||||
description,
|
||||
|
@ -88,7 +87,6 @@ const StatefulTimelineComponent: React.FC<Props> = ({
|
|||
[
|
||||
'indexNames',
|
||||
'dataViewId',
|
||||
'graphEventId',
|
||||
'savedObjectId',
|
||||
'timelineType',
|
||||
'description',
|
||||
|
@ -230,7 +228,6 @@ const StatefulTimelineComponent: React.FC<Props> = ({
|
|||
</HideShowContainer>
|
||||
|
||||
<TabsContent
|
||||
graphEventId={graphEventId}
|
||||
renderCellValue={renderCellValue}
|
||||
rowRenderers={rowRenderers}
|
||||
timelineId={timelineId}
|
||||
|
|
|
@ -7,20 +7,20 @@
|
|||
|
||||
import type { EuiSelectableOption, EuiSelectableProps } from '@elastic/eui';
|
||||
import {
|
||||
EuiSelectable,
|
||||
EuiHighlight,
|
||||
EuiFilterButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHighlight,
|
||||
EuiIcon,
|
||||
EuiSelectable,
|
||||
EuiTextColor,
|
||||
EuiFilterButton,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty, debounce } from 'lodash/fp';
|
||||
import React, { memo, useCallback, useMemo, useState, useEffect, useRef } from 'react';
|
||||
import { debounce, isEmpty } from 'lodash/fp';
|
||||
import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { type TimelineType, SortFieldTimelineEnum } from '../../../../../common/api/timeline';
|
||||
import { SortFieldTimelineEnum, type TimelineType } from '../../../../../common/api/timeline';
|
||||
|
||||
import { useGetAllTimeline } from '../../../containers/all';
|
||||
import { isUntitled } from '../../open_timeline/helpers';
|
||||
|
@ -52,9 +52,7 @@ const TIMELINE_ITEM_HEIGHT = 50;
|
|||
*/
|
||||
const replaceTitleInOptions = (
|
||||
options: EuiSelectableOption[]
|
||||
): Array<
|
||||
EuiSelectableOption<{ timelineTitle: string; description?: string; graphEveId?: string }>
|
||||
> =>
|
||||
): Array<EuiSelectableOption<{ timelineTitle: string; description?: string }>> =>
|
||||
options.map(({ title, ...props }) => ({
|
||||
...props,
|
||||
title: undefined,
|
||||
|
@ -77,11 +75,7 @@ export interface SelectableTimelineProps {
|
|||
searchTimelineValue,
|
||||
}: GetSelectableOptions) => EuiSelectableOption[];
|
||||
onClosePopover: () => void;
|
||||
onTimelineChange: (
|
||||
timelineTitle: string,
|
||||
timelineId: string | null,
|
||||
graphEventId?: string
|
||||
) => void;
|
||||
onTimelineChange: (timelineTitle: string, timelineId: string | null) => void;
|
||||
timelineType: TimelineType;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
@ -143,7 +137,6 @@ const SelectableTimelineComponent: React.FC<SelectableTimelineProps> = ({
|
|||
EuiSelectableProps<{
|
||||
timelineTitle: string;
|
||||
description?: string;
|
||||
graphEventId?: string;
|
||||
favorite?: FavoriteTimelineResult[];
|
||||
}>['renderOption']
|
||||
>
|
||||
|
@ -198,7 +191,6 @@ const SelectableTimelineComponent: React.FC<SelectableTimelineProps> = ({
|
|||
EuiSelectableProps<{
|
||||
timelineTitle: string;
|
||||
description?: string;
|
||||
graphEventId?: string;
|
||||
}>['onChange']
|
||||
>
|
||||
>(
|
||||
|
@ -211,8 +203,7 @@ const SelectableTimelineComponent: React.FC<SelectableTimelineProps> = ({
|
|||
: selectedTimeline[0].timelineTitle,
|
||||
selectedTimeline[0].id === '-1'
|
||||
? null
|
||||
: (selectedTimeline[0].id as unknown as string | null),
|
||||
selectedTimeline[0].graphEventId ?? ''
|
||||
: (selectedTimeline[0].id as unknown as string | null)
|
||||
);
|
||||
}
|
||||
onClosePopover();
|
||||
|
@ -288,7 +279,6 @@ const SelectableTimelineComponent: React.FC<SelectableTimelineProps> = ({
|
|||
<EuiSelectable<{
|
||||
timelineTitle: string;
|
||||
description?: string;
|
||||
graphEventId?: string;
|
||||
favorite?: FavoriteTimelineResult[];
|
||||
}>
|
||||
data-test-subj="selectable-input"
|
||||
|
|
|
@ -14,7 +14,7 @@ import { TestProviders } from '../../../../../common/mock/test_providers';
|
|||
|
||||
import type { Props as EqlTabContentComponentProps } from '.';
|
||||
import EqlTabContentComponent from '.';
|
||||
import { TimelineId } from '../../../../../../common/types/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { useTimelineEvents } from '../../../../containers';
|
||||
import { useTimelineEventsDetails } from '../../../../containers/details';
|
||||
import { useSourcererDataView } from '../../../../../sourcerer/containers';
|
||||
|
@ -28,7 +28,6 @@ import { timelineActions } from '../../../../store';
|
|||
import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer';
|
||||
import { defaultRowRenderers } from '../../body/renderers';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { TimelineTabs } from '@kbn/securitysolution-data-table';
|
||||
import { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { initialUserPrivilegesState } from '../../../../../common/components/user_privileges/user_privileges_context';
|
||||
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import { timelineSelectors } from '../../../../store';
|
||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import type { TimelineId } from '../../../../../../common/types/timeline';
|
||||
import { GraphOverlay } from '../../../graph_overlay';
|
||||
import { useSessionViewNavigation } from '../session/use_session_view';
|
||||
|
||||
interface GraphTabContentProps {
|
||||
timelineId: TimelineId;
|
||||
}
|
||||
|
||||
const GraphTabContentComponent: React.FC<GraphTabContentProps> = ({ timelineId }) => {
|
||||
const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => getTimeline(state, timelineId)?.graphEventId
|
||||
);
|
||||
|
||||
const { Navigation } = useSessionViewNavigation({
|
||||
scopeId: timelineId,
|
||||
});
|
||||
|
||||
if (!graphEventId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<GraphOverlay scopeId={timelineId} Navigation={Navigation} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
GraphTabContentComponent.displayName = 'GraphTabContentComponent';
|
||||
|
||||
const GraphTabContent = React.memo(GraphTabContentComponent);
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export { GraphTabContent as default };
|
|
@ -15,7 +15,6 @@ import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
|
|||
import { TimelineTypeEnum } from '../../../../../common/api/timeline';
|
||||
import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_availability';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { useLicense } from '../../../../common/hooks/use_license';
|
||||
import { useUserPrivileges } from '../../../../common/components/user_privileges';
|
||||
|
||||
jest.mock('../../../../common/hooks/use_license');
|
||||
|
@ -112,21 +111,6 @@ describe('Timeline', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('analyzer tab and session view tab', () => {
|
||||
const analyzerTabSubj = `timelineTabs-${TimelineTabs.graph}`;
|
||||
|
||||
it('should not show the analyzer tab when the advanced setting is enabled', async () => {
|
||||
mockUseUiSetting.mockReturnValue([true]);
|
||||
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => true });
|
||||
render(
|
||||
<TestProviders>
|
||||
<TabsContent {...defaultProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(screen.queryByTestId(analyzerTabSubj)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('privileges', () => {
|
||||
it('should show notes and pinned tabs for users with the required privileges', () => {
|
||||
(useUserPrivileges as jest.Mock).mockReturnValue({
|
||||
|
|
|
@ -69,10 +69,6 @@ const EqlTab = tabWithSuspense(
|
|||
lazy(() => import('./eql')),
|
||||
<TimelineTabFallback />
|
||||
);
|
||||
const GraphTab = tabWithSuspense(
|
||||
lazy(() => import('./graph')),
|
||||
<TimelineTabFallback />
|
||||
);
|
||||
const NotesTab = tabWithSuspense(
|
||||
lazy(() => import('./notes')),
|
||||
<TimelineTabFallback />
|
||||
|
@ -92,7 +88,6 @@ interface BasicTimelineTab {
|
|||
timelineFullScreen?: boolean;
|
||||
timelineId: TimelineId;
|
||||
timelineType: TimelineType;
|
||||
graphEventId?: string;
|
||||
timelineDescription: string;
|
||||
}
|
||||
|
||||
|
@ -118,24 +113,6 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
() => isEsqlAdvancedSettingEnabled || timelineESQLSavedSearch != null,
|
||||
[isEsqlAdvancedSettingEnabled, timelineESQLSavedSearch]
|
||||
);
|
||||
const getTab = useCallback(
|
||||
(tab: TimelineTabs) => {
|
||||
switch (tab) {
|
||||
case TimelineTabs.graph:
|
||||
return <GraphTab timelineId={timelineId} />;
|
||||
case TimelineTabs.notes:
|
||||
return <NotesTab timelineId={timelineId} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
},
|
||||
[timelineId]
|
||||
);
|
||||
|
||||
const isGraphOrNotesTabs = useMemo(
|
||||
() => [TimelineTabs.graph, TimelineTabs.notes].includes(activeTimelineTab),
|
||||
[activeTimelineTab]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -185,10 +162,10 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
)}
|
||||
<LazyTimelineTabRenderer
|
||||
timelineId={timelineId}
|
||||
shouldShowTab={isGraphOrNotesTabs}
|
||||
dataTestSubj={`timeline-tab-content-${TimelineTabs.graph}-${TimelineTabs.notes}`}
|
||||
shouldShowTab={activeTimelineTab === TimelineTabs.notes}
|
||||
dataTestSubj={`timeline-tab-content-${TimelineTabs.notes}`}
|
||||
>
|
||||
{isGraphOrNotesTabs ? getTab(activeTimelineTab) : null}
|
||||
<NotesTab timelineId={timelineId} />
|
||||
</LazyTimelineTabRenderer>
|
||||
</>
|
||||
);
|
||||
|
@ -228,7 +205,6 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
timelineId,
|
||||
timelineFullScreen,
|
||||
timelineType,
|
||||
graphEventId,
|
||||
timelineDescription,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -353,14 +329,13 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
}, [setActiveTab, dispatch, timelineId]);
|
||||
|
||||
useEffect(() => {
|
||||
// we redirect to the Query tab in the following situations:
|
||||
// - for the Analyzer tab but the graphEventId is missing from the url
|
||||
// - for urls with Timeline saved with the Session tab, we automatically (as Session View is now rendered in the flyout)
|
||||
// we redirect to the Query tab when we try to load Timeline urls with the Session or Analyzer tabs active
|
||||
// Session View and Analyzer graph are now only rendered in the flyout
|
||||
// @ts-ignore
|
||||
if ((!graphEventId && activeTab === TimelineTabs.graph) || activeTab === 'session') {
|
||||
if (activeTab === 'graph' || activeTab === 'session') {
|
||||
setQueryAsActiveTab();
|
||||
}
|
||||
}, [activeTab, graphEventId, setQueryAsActiveTab]);
|
||||
}, [activeTab, setQueryAsActiveTab]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const CLOSE_ANALYZER = i18n.translate(
|
||||
'xpack.securitySolution.timeline.graphOverlay.closeAnalyzerButton',
|
||||
{
|
||||
defaultMessage: 'Close analyzer',
|
||||
}
|
||||
);
|
||||
|
||||
export const CLOSE_SESSION = i18n.translate(
|
||||
'xpack.securitySolution.timeline.graphOverlay.closeSessionButton',
|
||||
{
|
||||
defaultMessage: 'Close session viewer',
|
||||
}
|
||||
);
|
||||
|
||||
export const FULL_SCREEN = i18n.translate(
|
||||
'xpack.securitySolution.timeline.graphOverlay.fullScreenButton',
|
||||
{
|
||||
defaultMessage: 'Full screen',
|
||||
}
|
||||
);
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import { render, renderHook } from '@testing-library/react';
|
||||
import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { mockTimelineModel, TestProviders } from '../../../../../common/mock';
|
||||
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import {
|
||||
useGlobalFullScreen,
|
||||
useTimelineFullScreen,
|
||||
} from '../../../../../common/containers/use_full_screen';
|
||||
import { useSessionViewNavigation } from './use_session_view';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
jest.mock('../../../../../common/hooks/use_selector');
|
||||
|
||||
jest.mock('../../../../../common/containers/use_full_screen');
|
||||
|
||||
jest.mock('react-redux', () => {
|
||||
const original = jest.requireActual('react-redux');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useDispatch: () => mockDispatch,
|
||||
};
|
||||
});
|
||||
jest.mock('../../../../../common/lib/kibana', () => {
|
||||
const originalModule = jest.requireActual('../../../../../common/lib/kibana');
|
||||
return {
|
||||
...originalModule,
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
services: {
|
||||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: jest.fn(),
|
||||
capabilities: {
|
||||
siemV2: { crud_alerts: true, read_alerts: true },
|
||||
},
|
||||
},
|
||||
data: {
|
||||
search: jest.fn(),
|
||||
query: jest.fn(),
|
||||
},
|
||||
uiSettings: {
|
||||
get: jest.fn(),
|
||||
},
|
||||
savedObjects: {
|
||||
client: {},
|
||||
},
|
||||
timelines: {
|
||||
getLastUpdated: jest.fn(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('useSessionView with active timeline and graph event id', () => {
|
||||
let setTimelineFullScreen: jest.Mock;
|
||||
let setGlobalFullScreen: jest.Mock;
|
||||
const Wrapper = memo<PropsWithChildren<unknown>>(({ children }) => {
|
||||
return <TestProviders>{children}</TestProviders>;
|
||||
});
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
||||
beforeEach(() => {
|
||||
setTimelineFullScreen = jest.fn();
|
||||
setGlobalFullScreen = jest.fn();
|
||||
(useTimelineFullScreen as jest.Mock).mockImplementation(() => ({
|
||||
setTimelineFullScreen,
|
||||
}));
|
||||
(useGlobalFullScreen as jest.Mock).mockImplementation(() => ({
|
||||
setGlobalFullScreen,
|
||||
}));
|
||||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => {
|
||||
return {
|
||||
...mockTimelineModel,
|
||||
activeTab: TimelineTabs.graph,
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: true,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('calls setTimelineFullScreen with false when onCloseOverlay is called and the app is not in full screen mode', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const testProps = {
|
||||
scopeId: TimelineId.active,
|
||||
};
|
||||
return useSessionViewNavigation(testProps);
|
||||
},
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
const navigation = result.current.Navigation;
|
||||
const renderResult = render(<TestProviders>{navigation}</TestProviders>);
|
||||
expect(renderResult.getByText('Close analyzer')).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('useSessionView with non active timeline and graph event id set', () => {
|
||||
beforeEach(() => {
|
||||
setTimelineFullScreen = jest.fn();
|
||||
setGlobalFullScreen = jest.fn();
|
||||
(useTimelineFullScreen as jest.Mock).mockImplementation(() => ({
|
||||
setTimelineFullScreen,
|
||||
}));
|
||||
(useGlobalFullScreen as jest.Mock).mockImplementation(() => ({
|
||||
setGlobalFullScreen,
|
||||
}));
|
||||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => {
|
||||
return {
|
||||
...mockTimelineModel,
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: true,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('renders the navigation component with the correct text for analyzer', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const testProps = {
|
||||
scopeId: TableId.hostsPageEvents,
|
||||
};
|
||||
return useSessionViewNavigation(testProps);
|
||||
},
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
const navigation = result.current.Navigation;
|
||||
const renderResult = render(<TestProviders>{navigation}</TestProviders>);
|
||||
expect(renderResult.getByText('Close analyzer')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSessionViewNavigation should handle separate parts', () => {
|
||||
beforeEach(() => {
|
||||
setTimelineFullScreen = jest.fn();
|
||||
setGlobalFullScreen = jest.fn();
|
||||
(useTimelineFullScreen as jest.Mock).mockImplementation(() => ({
|
||||
setTimelineFullScreen,
|
||||
}));
|
||||
(useGlobalFullScreen as jest.Mock).mockImplementation(() => ({
|
||||
setGlobalFullScreen,
|
||||
}));
|
||||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => {
|
||||
return {
|
||||
...mockTimelineModel,
|
||||
activeTab: TimelineTabs.graph,
|
||||
graphEventId: 'current-graph-event-id',
|
||||
show: true,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('useSessionViewNavigation should handle Navigation component and on close callback', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const testProps = {
|
||||
scopeId: TableId.hostsPageEvents,
|
||||
};
|
||||
return useSessionViewNavigation(testProps);
|
||||
},
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
expect(result.current).toHaveProperty('Navigation');
|
||||
expect(result.current).toHaveProperty('onCloseOverlay');
|
||||
|
||||
expect(result.current).not.toHaveProperty('openDetailsPanel');
|
||||
expect(result.current).not.toHaveProperty('shouldShowDetailsPanel');
|
||||
expect(result.current).not.toHaveProperty('SessionView');
|
||||
expect(result.current).not.toHaveProperty('DetailsPanel');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,208 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiToolTip,
|
||||
useEuiTheme,
|
||||
} from '@elastic/eui';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { dataTableSelectors, tableDefaults } from '@kbn/securitysolution-data-table';
|
||||
import {
|
||||
getScopedActions,
|
||||
isActiveTimeline,
|
||||
isInTableScope,
|
||||
isTimelineScope,
|
||||
} from '../../../../../helpers';
|
||||
import * as i18n from './translations';
|
||||
import { TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { SCROLLING_DISABLED_CLASS_NAME } from '../../../../../../common/constants';
|
||||
import { EXIT_FULL_SCREEN } from '../../../../../common/components/exit_full_screen/translations';
|
||||
import {
|
||||
useGlobalFullScreen,
|
||||
useTimelineFullScreen,
|
||||
} from '../../../../../common/containers/use_full_screen';
|
||||
import { timelineSelectors } from '../../../../store';
|
||||
import { timelineDefaults } from '../../../../store/defaults';
|
||||
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import { isFullScreen } from '../../helpers';
|
||||
|
||||
interface NavigationProps {
|
||||
fullScreen: boolean;
|
||||
globalFullScreen: boolean;
|
||||
onCloseOverlay: () => void;
|
||||
isActiveTimelines: boolean;
|
||||
timelineFullScreen: boolean;
|
||||
toggleFullScreen: () => void;
|
||||
graphEventId?: string;
|
||||
activeTab?: TimelineTabs;
|
||||
}
|
||||
|
||||
const NavigationComponent: React.FC<NavigationProps> = ({
|
||||
fullScreen,
|
||||
globalFullScreen,
|
||||
onCloseOverlay,
|
||||
isActiveTimelines,
|
||||
timelineFullScreen,
|
||||
toggleFullScreen,
|
||||
graphEventId,
|
||||
activeTab,
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const title = () => {
|
||||
if (isActiveTimelines) {
|
||||
return activeTab === TimelineTabs.graph ? i18n.CLOSE_ANALYZER : i18n.CLOSE_SESSION;
|
||||
} else {
|
||||
return graphEventId ? i18n.CLOSE_ANALYZER : i18n.CLOSE_SESSION;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
iconType="cross"
|
||||
onClick={onCloseOverlay}
|
||||
size="xs"
|
||||
data-test-subj="close-overlay"
|
||||
>
|
||||
{title()}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
{!isActiveTimelines && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip content={fullScreen ? EXIT_FULL_SCREEN : i18n.FULL_SCREEN}>
|
||||
<EuiButtonIcon
|
||||
aria-label={
|
||||
isFullScreen({
|
||||
globalFullScreen,
|
||||
isActiveTimelines,
|
||||
timelineFullScreen,
|
||||
})
|
||||
? EXIT_FULL_SCREEN
|
||||
: i18n.FULL_SCREEN
|
||||
}
|
||||
display={fullScreen ? 'fill' : 'empty'}
|
||||
color="primary"
|
||||
data-test-subj="full-screen"
|
||||
iconType="fullScreen"
|
||||
onClick={toggleFullScreen}
|
||||
css={css`
|
||||
margin: ${euiTheme.size.xs} 0;
|
||||
`}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
NavigationComponent.displayName = 'NavigationComponent';
|
||||
|
||||
const Navigation = React.memo(NavigationComponent);
|
||||
|
||||
export const useSessionViewNavigation = ({ scopeId }: { scopeId: string }) => {
|
||||
const dispatch = useDispatch();
|
||||
const getScope = useMemo(() => {
|
||||
if (isTimelineScope(scopeId)) {
|
||||
return timelineSelectors.getTimelineByIdSelector();
|
||||
} else if (isInTableScope(scopeId)) {
|
||||
return dataTableSelectors.getTableByIdSelector();
|
||||
}
|
||||
}, [scopeId]);
|
||||
const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
|
||||
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
||||
|
||||
const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults;
|
||||
const { graphEventId, activeTab } = useDeepEqualSelector((state) => ({
|
||||
activeTab: timelineDefaults.activeTab,
|
||||
prevActiveTab: timelineDefaults.prevActiveTab,
|
||||
...((getScope && getScope(state, scopeId)) ?? defaults),
|
||||
}));
|
||||
|
||||
const scopedActions = getScopedActions(scopeId);
|
||||
const onCloseOverlay = useCallback(() => {
|
||||
const isDataGridFullScreen = document.querySelector('.euiDataGrid--fullScreen') !== null;
|
||||
// Since EUI changes these values directly as a side effect, need to add them back on close.
|
||||
if (isDataGridFullScreen) {
|
||||
if (isActiveTimeline(scopeId)) {
|
||||
document.body.classList.add('euiDataGrid__restrictBody');
|
||||
} else {
|
||||
document.body.classList.add(SCROLLING_DISABLED_CLASS_NAME, 'euiDataGrid__restrictBody');
|
||||
}
|
||||
} else {
|
||||
if (isActiveTimeline(scopeId)) {
|
||||
setTimelineFullScreen(false);
|
||||
} else {
|
||||
setGlobalFullScreen(false);
|
||||
}
|
||||
}
|
||||
if (isActiveTimeline(scopeId) === false) {
|
||||
if (scopedActions) {
|
||||
dispatch(scopedActions.updateGraphEventId({ id: scopeId, graphEventId: '' }));
|
||||
}
|
||||
} else {
|
||||
if (activeTab === TimelineTabs.graph) {
|
||||
if (scopedActions) {
|
||||
dispatch(scopedActions.updateGraphEventId({ id: scopeId, graphEventId: '' }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [setTimelineFullScreen, setGlobalFullScreen, scopedActions, dispatch, scopeId, activeTab]);
|
||||
|
||||
const fullScreen = useMemo(
|
||||
() =>
|
||||
isFullScreen({
|
||||
globalFullScreen,
|
||||
isActiveTimelines: isActiveTimeline(scopeId),
|
||||
timelineFullScreen,
|
||||
}),
|
||||
[globalFullScreen, scopeId, timelineFullScreen]
|
||||
);
|
||||
|
||||
const toggleFullScreen = useCallback(() => {
|
||||
if (isActiveTimeline(scopeId)) {
|
||||
setTimelineFullScreen(!timelineFullScreen);
|
||||
} else {
|
||||
setGlobalFullScreen(!globalFullScreen);
|
||||
}
|
||||
}, [scopeId, setTimelineFullScreen, timelineFullScreen, setGlobalFullScreen, globalFullScreen]);
|
||||
|
||||
const navigation = useMemo(() => {
|
||||
return (
|
||||
<Navigation
|
||||
fullScreen={fullScreen}
|
||||
globalFullScreen={globalFullScreen}
|
||||
activeTab={activeTab}
|
||||
onCloseOverlay={onCloseOverlay}
|
||||
isActiveTimelines={isActiveTimeline(scopeId)}
|
||||
timelineFullScreen={timelineFullScreen}
|
||||
toggleFullScreen={toggleFullScreen}
|
||||
graphEventId={graphEventId}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
fullScreen,
|
||||
globalFullScreen,
|
||||
activeTab,
|
||||
onCloseOverlay,
|
||||
scopeId,
|
||||
timelineFullScreen,
|
||||
toggleFullScreen,
|
||||
graphEventId,
|
||||
]);
|
||||
|
||||
return {
|
||||
onCloseOverlay,
|
||||
Navigation: navigation,
|
||||
};
|
||||
};
|
|
@ -9,7 +9,7 @@ import type { ComponentProps, FC, PropsWithChildren } from 'react';
|
|||
import React, { useEffect } from 'react';
|
||||
import type QueryTabContent from '.';
|
||||
import { UnifiedTimeline } from '.';
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
|
||||
import { useTimelineEvents } from '../../../containers';
|
||||
import { useTimelineEventsDetails } from '../../../containers/details';
|
||||
import { useSourcererDataView } from '../../../../sourcerer/containers';
|
||||
|
@ -20,7 +20,7 @@ import {
|
|||
TestProviders,
|
||||
} from '../../../../common/mock';
|
||||
import { createMockStore } from '../../../../common/mock/create_store';
|
||||
import { render, screen, fireEvent, cleanup, waitFor, within } from '@testing-library/react';
|
||||
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
|
||||
import { createStartServicesMock } from '../../../../common/lib/kibana/kibana_react.mock';
|
||||
import type { StartServices } from '../../../../types';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
@ -29,7 +29,6 @@ import { timelineActions } from '../../../store';
|
|||
import type { ExperimentalFeatures } from '../../../../../common';
|
||||
import { allowedExperimentalValues } from '../../../../../common';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import { TimelineTabs } from '@kbn/securitysolution-data-table';
|
||||
import { DataLoadingState } from '@kbn/unified-data-table';
|
||||
import { getColumnHeaders } from '../body/column_headers/helpers';
|
||||
import { defaultUdtHeaders } from '../body/column_headers/default_headers';
|
||||
|
|
|
@ -644,7 +644,6 @@ describe('SiemLocalStorage', () => {
|
|||
sort: [
|
||||
{ columnId: '@timestamp', columnType: 'date', esTypes: ['date'], sortDirection: 'desc' },
|
||||
],
|
||||
graphEventId: undefined,
|
||||
selectedEventIds: {},
|
||||
selectAll: undefined,
|
||||
id: 'alerts-page',
|
||||
|
@ -885,7 +884,6 @@ describe('SiemLocalStorage', () => {
|
|||
},
|
||||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
|
@ -998,7 +996,6 @@ describe('SiemLocalStorage', () => {
|
|||
},
|
||||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
|
@ -1119,7 +1116,6 @@ describe('SiemLocalStorage', () => {
|
|||
},
|
||||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
|
@ -1555,7 +1551,6 @@ describe('SiemLocalStorage', () => {
|
|||
sortDirection: 'desc',
|
||||
},
|
||||
],
|
||||
graphEventId: undefined,
|
||||
selectedEventIds: {},
|
||||
selectAll: false,
|
||||
id: 'alerts-page',
|
||||
|
|
|
@ -50,7 +50,6 @@ export const migrateLegacyTimelinesToSecurityDataTable = (legacyTimelineTables:
|
|||
itemsPerPage: timelineModel.itemsPerPage,
|
||||
itemsPerPageOptions: timelineModel.itemsPerPageOptions,
|
||||
showCheckboxes: timelineModel.showCheckboxes,
|
||||
graphEventId: timelineModel.graphEventId,
|
||||
selectAll: timelineModel.selectAll,
|
||||
id: timelineModel.id,
|
||||
title: timelineModel.title,
|
||||
|
|
|
@ -62,10 +62,6 @@ export const removeProvider = actionCreator<{
|
|||
andProviderId?: string;
|
||||
}>('REMOVE_PROVIDER');
|
||||
|
||||
export const updateGraphEventId = actionCreator<{ id: string; graphEventId: string }>(
|
||||
'UPDATE_TIMELINE_GRAPH_EVENT_ID'
|
||||
);
|
||||
|
||||
export const unPinEvent = actionCreator<{ id: string; eventId: string }>('UN_PIN_EVENT');
|
||||
|
||||
export const updateTimeline = actionCreator<{
|
||||
|
|
|
@ -112,5 +112,4 @@ export const getTimelineManageDefaults = (id: string) => ({
|
|||
isLoading: false,
|
||||
queryFields: [],
|
||||
title: '',
|
||||
graphEventId: '',
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import type { ColumnHeaderOptions } from '../../../common/types/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../common/types/timeline';
|
||||
import { TimelineTabs } from '../../../common/types/timeline';
|
||||
import {
|
||||
DataProviderTypeEnum,
|
||||
TimelineStatusEnum,
|
||||
|
@ -32,7 +32,6 @@ import {
|
|||
removeTimelineProvider,
|
||||
updateTimelineColumns,
|
||||
updateTimelineColumnWidth,
|
||||
updateTimelineGraphEventId,
|
||||
updateTimelineItemsPerPage,
|
||||
updateTimelinePerPageOptions,
|
||||
updateTimelineProviderEnabled,
|
||||
|
@ -79,7 +78,7 @@ const basicDataProvider: DataProvider = {
|
|||
};
|
||||
const basicTimeline: TimelineModel = {
|
||||
activeTab: TimelineTabs.query,
|
||||
prevActiveTab: TimelineTabs.graph,
|
||||
prevActiveTab: TimelineTabs.eql,
|
||||
columns: [],
|
||||
defaultColumns: [],
|
||||
dataProviders: [{ ...basicDataProvider }],
|
||||
|
@ -1818,77 +1817,6 @@ describe('Timeline', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#updateGraphEventId', () => {
|
||||
test('should return a new reference and not the same reference', () => {
|
||||
const update = updateTimelineGraphEventId({
|
||||
id: 'foo',
|
||||
graphEventId: '123',
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
expect(update).not.toBe(timelineByIdMock);
|
||||
});
|
||||
|
||||
test('should empty graphEventId', () => {
|
||||
const update = updateTimelineGraphEventId({
|
||||
id: 'foo',
|
||||
graphEventId: '',
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
expect(update.foo.graphEventId).toEqual('');
|
||||
});
|
||||
|
||||
test('should empty graphEventId and not change activeTab and prevActiveTab because TimelineId !== TimelineId.active', () => {
|
||||
const update = updateTimelineGraphEventId({
|
||||
id: 'foo',
|
||||
graphEventId: '',
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
expect(update.foo.graphEventId).toEqual('');
|
||||
expect(update.foo.activeTab).toEqual(timelineByIdMock.foo.activeTab);
|
||||
expect(update.foo.prevActiveTab).toEqual(timelineByIdMock.foo.prevActiveTab);
|
||||
});
|
||||
|
||||
test('should empty graphEventId if timeline is active and user go to another tab', () => {
|
||||
const mock = cloneDeep(timelineByIdMock);
|
||||
mock[TimelineId.active] = {
|
||||
...timelineByIdMock.foo,
|
||||
activeTab: TimelineTabs.eql,
|
||||
prevActiveTab: TimelineTabs.graph,
|
||||
};
|
||||
delete mock.foo;
|
||||
|
||||
const update = updateTimelineGraphEventId({
|
||||
id: TimelineId.active,
|
||||
graphEventId: '',
|
||||
timelineById: mock,
|
||||
});
|
||||
|
||||
expect(update[TimelineId.active].graphEventId).toEqual('');
|
||||
expect(update[TimelineId.active].activeTab).toEqual(TimelineTabs.eql);
|
||||
expect(update[TimelineId.active].prevActiveTab).toEqual(TimelineTabs.graph);
|
||||
});
|
||||
|
||||
test('should empty graphEventId and return to the previous tab if timeline is active and user close the graph', () => {
|
||||
const mock = cloneDeep(timelineByIdMock);
|
||||
mock[TimelineId.active] = {
|
||||
...timelineByIdMock.foo,
|
||||
activeTab: TimelineTabs.graph,
|
||||
prevActiveTab: TimelineTabs.eql,
|
||||
};
|
||||
delete mock.foo;
|
||||
|
||||
const update = updateTimelineGraphEventId({
|
||||
id: TimelineId.active,
|
||||
graphEventId: '',
|
||||
timelineById: mock,
|
||||
});
|
||||
|
||||
expect(update[TimelineId.active].graphEventId).toEqual('');
|
||||
expect(update[TimelineId.active].activeTab).toEqual(TimelineTabs.eql);
|
||||
expect(update[TimelineId.active].prevActiveTab).toEqual(TimelineTabs.graph);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateTimelineColumnWidth', () => {
|
||||
let mockTimelineById: TimelineById;
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -28,7 +28,7 @@ import type {
|
|||
SortColumnTimeline,
|
||||
TimelinePersistInput,
|
||||
} from '../../../common/types/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../common/types/timeline';
|
||||
import { TimelineId } from '../../../common/types/timeline';
|
||||
import { normalizeTimeRange } from '../../common/utils/normalize_time_range';
|
||||
import { getTimelineManageDefaults, timelineDefaults } from './defaults';
|
||||
import type { KqlMode, TimelineModel } from './model';
|
||||
|
@ -246,32 +246,6 @@ export const updateTimelineShowTimeline = ({
|
|||
};
|
||||
};
|
||||
|
||||
export const updateTimelineGraphEventId = ({
|
||||
id,
|
||||
graphEventId,
|
||||
timelineById,
|
||||
}: {
|
||||
id: string;
|
||||
graphEventId: string;
|
||||
timelineById: TimelineById;
|
||||
}): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
graphEventId,
|
||||
// if use click close analyzer button, it will go back to the previous tab
|
||||
...(graphEventId === '' &&
|
||||
id === TimelineId.active &&
|
||||
timeline.activeTab === TimelineTabs.graph
|
||||
? { activeTab: timeline.prevActiveTab, prevActiveTab: timeline.activeTab }
|
||||
: {}),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const queryMatchCustomizer = (dp1: QueryMatch, dp2: QueryMatch) => {
|
||||
if (dp1.field === dp2.field && dp1.value === dp2.value && dp1.operator === dp2.operator) {
|
||||
return true;
|
||||
|
|
|
@ -48,7 +48,6 @@ import {
|
|||
updateDataProviderType,
|
||||
updateDataView,
|
||||
updateEqlOptions,
|
||||
updateGraphEventId,
|
||||
updateIsFavorite,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
|
@ -85,7 +84,6 @@ import {
|
|||
updateSavedQuery,
|
||||
updateTimelineColumns,
|
||||
updateTimelineColumnWidth,
|
||||
updateTimelineGraphEventId,
|
||||
updateTimelineIsFavorite,
|
||||
updateTimelineItemsPerPage,
|
||||
updateTimelineKqlMode,
|
||||
|
@ -161,14 +159,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
...state,
|
||||
timelineById: updateTimelineShowTimeline({ id, show, timelineById: state.timelineById }),
|
||||
}))
|
||||
.case(updateGraphEventId, (state, { id, graphEventId }) => ({
|
||||
...state,
|
||||
timelineById: updateTimelineGraphEventId({
|
||||
id,
|
||||
graphEventId,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(pinEvent, (state, { id, eventId }) => ({
|
||||
...state,
|
||||
timelineById: pinTimelineEvent({ id, eventId, timelineById: state.timelineById }),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue