mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 10:40:07 -04:00
[Security Solution] remove sessionview as overlay (#220596)
## 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. This PR focuses on removing all code related to the Session View component when displayed as an overlay of the Alerts page alerts table and in Timeline. **_As the previous PR had removed the ability to switch back to this overlay mode, this PR does not introduces any visible changes in the UI. If anything looks different or behaves differently, then there is an issue and this PR should not be merged._** Session View in expandable flyout remains unchanged: https://github.com/user-attachments/assets/54107185-ff9b-4090-ac0d-7c4f3f1a421f ### 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
e4e5158aab
commit
ab6b2e85e4
59 changed files with 223 additions and 1221 deletions
|
@ -815,7 +815,7 @@
|
|||
" | null"
|
||||
],
|
||||
"path": "x-pack/solutions/security/packages/data-table/store/data_table/model.ts",
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
|
@ -1273,7 +1273,7 @@
|
|||
" | null; }"
|
||||
],
|
||||
"path": "x-pack/solutions/security/packages/data-table/store/data_table/model.ts",
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"trackAdoption": false,
|
||||
"initialIsOpen": false
|
||||
},
|
||||
|
|
|
@ -1218,7 +1218,7 @@
|
|||
" | null"
|
||||
],
|
||||
"path": "x-pack/solutions/security/plugins/security_solution/public/timelines/store/model.ts",
|
||||
"deprecated": false,
|
||||
"deprecated": true,
|
||||
"trackAdoption": false
|
||||
},
|
||||
{
|
||||
|
|
|
@ -449,7 +449,6 @@ module.exports = {
|
|||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]tabs[\/\\]esql[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]tabs[\/\\]esql[\/\\]styles.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]tabs[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]tabs[\/\\]session[\/\\]index.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]tabs[\/\\]shared[\/\\]layout.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]unified_components[\/\\]data_table[\/\\]custom_timeline_data_grid_body.tsx/,
|
||||
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]timelines[\/\\]components[\/\\]timeline[\/\\]unified_components[\/\\]index.tsx/,
|
||||
|
|
|
@ -41795,9 +41795,6 @@
|
|||
"xpack.sessionView.detailPanelAlertsEmptyMsg": "Aucune alerte n'a été créée pour cette session.",
|
||||
"xpack.sessionView.detailPanelAlertsEmptyTitle": "Aucune alerte",
|
||||
"xpack.sessionView.detailPanelCopy.copyButton": "Copier",
|
||||
"xpack.sessionView.detailsPanel.alerts": "Alertes",
|
||||
"xpack.sessionView.detailsPanel.metadata": "Métadonnées",
|
||||
"xpack.sessionView.detailsPanel.process": "Processus",
|
||||
"xpack.sessionView.emptyDataMessage": "Aucun événement de processus n'a été trouvé pour cette requête.",
|
||||
"xpack.sessionView.emptyDataTitle": "Aucune donnée à rendre",
|
||||
"xpack.sessionView.errorHeading": "Erreur lors du chargement de la vue de session",
|
||||
|
|
|
@ -41756,9 +41756,6 @@
|
|||
"xpack.sessionView.detailPanelAlertsEmptyMsg": "このセッションで作成されたアラートはありません。",
|
||||
"xpack.sessionView.detailPanelAlertsEmptyTitle": "アラートなし",
|
||||
"xpack.sessionView.detailPanelCopy.copyButton": "コピー",
|
||||
"xpack.sessionView.detailsPanel.alerts": "アラート",
|
||||
"xpack.sessionView.detailsPanel.metadata": "メタデータ",
|
||||
"xpack.sessionView.detailsPanel.process": "プロセス",
|
||||
"xpack.sessionView.emptyDataMessage": "このクエリのプロセスイベントが見つかりません。",
|
||||
"xpack.sessionView.emptyDataTitle": "表示するデータがありません",
|
||||
"xpack.sessionView.errorHeading": "セッションビューの読み込みエラー",
|
||||
|
|
|
@ -41836,9 +41836,6 @@
|
|||
"xpack.sessionView.detailPanelAlertsEmptyMsg": "没有为此会话创建告警。",
|
||||
"xpack.sessionView.detailPanelAlertsEmptyTitle": "无告警",
|
||||
"xpack.sessionView.detailPanelCopy.copyButton": "复制",
|
||||
"xpack.sessionView.detailsPanel.alerts": "告警",
|
||||
"xpack.sessionView.detailsPanel.metadata": "元数据",
|
||||
"xpack.sessionView.detailsPanel.process": "进程",
|
||||
"xpack.sessionView.emptyDataMessage": "找不到此查询的进程事件。",
|
||||
"xpack.sessionView.emptyDataTitle": "没有可呈现的数据",
|
||||
"xpack.sessionView.errorHeading": "加载会话视图时出错",
|
||||
|
|
|
@ -16,5 +16,4 @@ export enum TimelineTabs {
|
|||
notes = 'notes',
|
||||
pinned = 'pinned',
|
||||
eql = 'eql',
|
||||
session = 'session',
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
export * from './data_table';
|
||||
export * from './header_actions';
|
||||
export * from './session_view';
|
||||
|
||||
export const FILTER_OPEN = 'open' as const;
|
||||
export const FILTER_CLOSED = 'closed' as const;
|
||||
|
|
|
@ -1,15 +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 interface SessionViewConfig {
|
||||
index: string;
|
||||
sessionEntityId: string;
|
||||
sessionStartTime: string;
|
||||
jumpToEntityId?: string;
|
||||
jumpToCursor?: string;
|
||||
investigatedAlertId?: string;
|
||||
}
|
|
@ -41,7 +41,6 @@ export const mockGlobalState = {
|
|||
},
|
||||
],
|
||||
graphEventId: '',
|
||||
sessionViewConfig: null,
|
||||
selectAll: false,
|
||||
id: TableId.test,
|
||||
title: '',
|
||||
|
|
|
@ -7,13 +7,8 @@
|
|||
|
||||
import actionCreatorFactory from 'typescript-fsa';
|
||||
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import type {
|
||||
ColumnHeaderOptions,
|
||||
SessionViewConfig,
|
||||
SortColumnTable,
|
||||
ViewSelection,
|
||||
} from '../../common/types';
|
||||
import type { InitialyzeDataTableSettings, DataTablePersistInput } from './types';
|
||||
import type { ColumnHeaderOptions, SortColumnTable, ViewSelection } from '../../common/types';
|
||||
import type { DataTablePersistInput, InitialyzeDataTableSettings } from './types';
|
||||
|
||||
const actionCreator = actionCreatorFactory('x-pack/security_solution/data-table');
|
||||
|
||||
|
@ -110,11 +105,6 @@ export const updateGraphEventId = actionCreator<{ id: string; graphEventId: stri
|
|||
'UPDATE_DATA_TABLE_GRAPH_EVENT_ID'
|
||||
);
|
||||
|
||||
export const updateSessionViewConfig = actionCreator<{
|
||||
id: string;
|
||||
sessionViewConfig: SessionViewConfig | null;
|
||||
}>('UPDATE_DATA_TABLE_SESSION_VIEW_CONFIG');
|
||||
|
||||
export const setTableUpdatedAt = actionCreator<{ id: string; updated: number }>(
|
||||
'SET_TABLE_UPDATED_AT'
|
||||
);
|
||||
|
|
|
@ -83,7 +83,6 @@ export const tableDefaults: SubsetDataTableModel = {
|
|||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
sessionViewConfig: null,
|
||||
columns: defaultHeaders,
|
||||
queryFields: [],
|
||||
title: '',
|
||||
|
|
|
@ -9,7 +9,7 @@ import { omit, union } from 'lodash/fp';
|
|||
|
||||
import { isEmpty } from 'lodash';
|
||||
import type { EuiDataGridColumn } from '@elastic/eui';
|
||||
import type { ColumnHeaderOptions, SessionViewConfig, SortColumnTable } from '../../common/types';
|
||||
import type { ColumnHeaderOptions, SortColumnTable } from '../../common/types';
|
||||
import type { DataTablePersistInput, TableById } from './types';
|
||||
import type { DataTableModelSettings } from './model';
|
||||
|
||||
|
@ -454,23 +454,3 @@ export const updateTableGraphEventId = ({
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const updateTableSessionViewConfig = ({
|
||||
id,
|
||||
sessionViewConfig,
|
||||
tableById,
|
||||
}: {
|
||||
id: string;
|
||||
sessionViewConfig: SessionViewConfig | null;
|
||||
tableById: TableById;
|
||||
}): TableById => {
|
||||
const table = tableById[id];
|
||||
|
||||
return {
|
||||
...tableById,
|
||||
[id]: {
|
||||
...table,
|
||||
sessionViewConfig,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -8,12 +8,7 @@
|
|||
import type { EuiDataGridColumn } from '@elastic/eui';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import type {
|
||||
ColumnHeaderOptions,
|
||||
SessionViewConfig,
|
||||
SortColumnTable,
|
||||
ViewSelection,
|
||||
} from '../../common/types';
|
||||
import type { ColumnHeaderOptions, SortColumnTable, ViewSelection } from '../../common/types';
|
||||
|
||||
export interface DataTableModelSettings {
|
||||
defaultColumns: Array<
|
||||
|
@ -61,7 +56,6 @@ export interface DataTableModel extends DataTableModelSettings {
|
|||
/** Events selected on this timeline -- eventId to TimelineNonEcsData[] mapping of data required for bulk actions **/
|
||||
selectedEventIds: Record<string, TimelineNonEcsData[]>;
|
||||
initialized?: boolean;
|
||||
sessionViewConfig: SessionViewConfig | null;
|
||||
/** updated saved object timestamp */
|
||||
updated?: number;
|
||||
/** Total number of fetched events/alerts */
|
||||
|
@ -90,7 +84,6 @@ export type SubsetDataTableModel = Readonly<
|
|||
| 'sort'
|
||||
| 'selectedEventIds'
|
||||
| 'graphEventId'
|
||||
| 'sessionViewConfig'
|
||||
| 'queryFields'
|
||||
| 'title'
|
||||
| 'initialized'
|
||||
|
|
|
@ -9,50 +9,48 @@ import { reducerWithInitialState } from 'typescript-fsa-reducers';
|
|||
|
||||
import {
|
||||
applyDeltaToColumnWidth,
|
||||
changeViewMode,
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
clearSelected,
|
||||
createDataTable,
|
||||
initializeDataTableSettings,
|
||||
removeColumn,
|
||||
setDataTableSelectAll,
|
||||
setEventsDeleted,
|
||||
setEventsLoading,
|
||||
setDataTableSelectAll,
|
||||
setSelected,
|
||||
setTableUpdatedAt,
|
||||
updateColumnOrder,
|
||||
updateColumns,
|
||||
updateColumnWidth,
|
||||
updateGraphEventId,
|
||||
updateIsLoading,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
updateSort,
|
||||
upsertColumn,
|
||||
updateGraphEventId,
|
||||
updateSessionViewConfig,
|
||||
setTableUpdatedAt,
|
||||
updateTotalCount,
|
||||
changeViewMode,
|
||||
updateShowBuildingBlockAlertsFilter,
|
||||
updateShowThreatIndicatorAlertsFilter,
|
||||
updateSort,
|
||||
updateTotalCount,
|
||||
upsertColumn,
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
applyDeltaToTableColumnWidth,
|
||||
createInitDataTable,
|
||||
setInitializeDataTableSettings,
|
||||
removeTableColumn,
|
||||
setDeletedTableEvents,
|
||||
setInitializeDataTableSettings,
|
||||
setLoadingTableEvents,
|
||||
setSelectedTableEvents,
|
||||
updateDataTableColumnOrder,
|
||||
updateDataTableColumnWidth,
|
||||
updateTableColumns,
|
||||
updateTableGraphEventId,
|
||||
updateTableItemsPerPage,
|
||||
updateTablePerPageOptions,
|
||||
updateTableSort,
|
||||
upsertTableColumn,
|
||||
updateTableGraphEventId,
|
||||
updateTableSessionViewConfig,
|
||||
} from './helpers';
|
||||
|
||||
import type { TableState } from './types';
|
||||
|
@ -230,14 +228,6 @@ export const dataTableReducer = reducerWithInitialState(initialDataTableState)
|
|||
...state,
|
||||
tableById: updateTableGraphEventId({ id, graphEventId, tableById: state.tableById }),
|
||||
}))
|
||||
.case(updateSessionViewConfig, (state, { id, sessionViewConfig }) => ({
|
||||
...state,
|
||||
tableById: updateTableSessionViewConfig({
|
||||
id,
|
||||
sessionViewConfig,
|
||||
tableById: state.tableById,
|
||||
}),
|
||||
}))
|
||||
.case(setTableUpdatedAt, (state, { id, updated }) => ({
|
||||
...state,
|
||||
tableById: {
|
||||
|
|
|
@ -9,7 +9,6 @@ import type { Status } from '../api/detection_engine';
|
|||
|
||||
export * from './timeline';
|
||||
export * from './header_actions';
|
||||
export * from './session_view';
|
||||
export * from './bulk_actions';
|
||||
|
||||
export const FILTER_OPEN: Status = 'open';
|
||||
|
|
|
@ -28,7 +28,6 @@ export enum TimelineTabs {
|
|||
notes = 'notes',
|
||||
pinned = 'pinned',
|
||||
eql = 'eql',
|
||||
session = 'session',
|
||||
securityAssistant = 'securityAssistant',
|
||||
esql = 'esql',
|
||||
}
|
||||
|
|
|
@ -55,10 +55,7 @@ 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 {
|
||||
useSessionView,
|
||||
useSessionViewNavigation,
|
||||
} from '../../../timelines/components/timeline/tabs/session/use_session_view';
|
||||
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';
|
||||
|
@ -134,7 +131,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
graphEventId, // If truthy, the graph viewer (Resolver) is showing
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
sessionViewConfig,
|
||||
showCheckboxes,
|
||||
sort,
|
||||
queryFields,
|
||||
|
@ -204,16 +200,10 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
|
|||
const { Navigation } = useSessionViewNavigation({
|
||||
scopeId: tableId,
|
||||
});
|
||||
const { SessionView } = useSessionView({
|
||||
scopeId: tableId,
|
||||
});
|
||||
const graphOverlay = useMemo(() => {
|
||||
const shouldShowOverlay =
|
||||
(graphEventId != null && graphEventId.length > 0) || sessionViewConfig != null;
|
||||
return shouldShowOverlay ? (
|
||||
<GraphOverlay scopeId={tableId} SessionView={SessionView} Navigation={Navigation} />
|
||||
) : null;
|
||||
}, [graphEventId, tableId, sessionViewConfig, SessionView, Navigation]);
|
||||
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) =>
|
||||
|
|
|
@ -15,8 +15,8 @@ import {
|
|||
Direction,
|
||||
FlowTarget,
|
||||
NetworkDnsFields,
|
||||
NetworkTopTablesFields,
|
||||
NetworkTlsFields,
|
||||
NetworkTopTablesFields,
|
||||
NetworkUsersFields,
|
||||
RiskScoreFields,
|
||||
} from '../../../common/search_strategy';
|
||||
|
@ -24,18 +24,18 @@ import type { State } from '../store';
|
|||
|
||||
import { defaultHeaders } from './header';
|
||||
import {
|
||||
DEFAULT_DATA_VIEW_ID,
|
||||
DEFAULT_FROM,
|
||||
DEFAULT_TO,
|
||||
DEFAULT_INDEX_PATTERN,
|
||||
DEFAULT_INTERVAL_TYPE,
|
||||
DEFAULT_INTERVAL_VALUE,
|
||||
DEFAULT_INDEX_PATTERN,
|
||||
DEFAULT_DATA_VIEW_ID,
|
||||
DEFAULT_SIGNALS_INDEX,
|
||||
DEFAULT_TO,
|
||||
VIEW_SELECTION,
|
||||
} from '../../../common/constants';
|
||||
import { networkModel } from '../../explore/network/store';
|
||||
import { TimelineTabs, TimelineId } from '../../../common/types/timeline';
|
||||
import { TimelineTypeEnum, TimelineStatusEnum } from '../../../common/api/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../common/types/timeline';
|
||||
import { TimelineStatusEnum, TimelineTypeEnum } from '../../../common/api/timeline';
|
||||
import { mockManagementState } from '../../management/store/reducer';
|
||||
import type { ManagementState } from '../../management/types';
|
||||
import { initialSourcererState, SourcererScopeName } from '../../sourcerer/store/model';
|
||||
|
@ -374,7 +374,6 @@ export const mockGlobalState: State = {
|
|||
pinnedEventIds: {},
|
||||
pinnedEventsSaveObject: {},
|
||||
selectAll: false,
|
||||
sessionViewConfig: null,
|
||||
show: false,
|
||||
sort: [
|
||||
{
|
||||
|
@ -423,7 +422,6 @@ export const mockGlobalState: State = {
|
|||
},
|
||||
],
|
||||
graphEventId: '',
|
||||
sessionViewConfig: null,
|
||||
selectAll: false,
|
||||
id: TableId.test,
|
||||
title: '',
|
||||
|
|
|
@ -14,8 +14,8 @@ import type { TimelineResponse } from '../../../common/api/timeline';
|
|||
import {
|
||||
type ColumnHeaderResult,
|
||||
RowRendererIdEnum,
|
||||
TimelineTypeEnum,
|
||||
TimelineStatusEnum,
|
||||
TimelineTypeEnum,
|
||||
} from '../../../common/api/timeline';
|
||||
|
||||
import type { OpenTimelineResult } from '../../timelines/components/open_timeline/types';
|
||||
|
@ -1914,7 +1914,6 @@ export const mockTimelineModel: TimelineModel = {
|
|||
pinnedEventsSaveObject: {},
|
||||
savedObjectId: 'ef579e40-jibber-jabber',
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
show: false,
|
||||
sort: [
|
||||
{
|
||||
|
@ -1966,7 +1965,6 @@ export const mockDataTableModel: DataTableModel = {
|
|||
itemsPerPageOptions: [10, 25, 50, 100],
|
||||
loadingEventIds: [],
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
sort: [
|
||||
{
|
||||
columnId: '@timestamp',
|
||||
|
@ -2089,7 +2087,6 @@ export const defaultTimelineProps: CreateTimelineProps = {
|
|||
savedObjectId: null,
|
||||
selectAll: false,
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
show: false,
|
||||
sort: [
|
||||
{
|
||||
|
|
|
@ -15,25 +15,25 @@ import type { Filter } from '@kbn/es-query';
|
|||
import { FilterStateStore } from '@kbn/es-query';
|
||||
|
||||
import {
|
||||
sendAlertToTimelineAction,
|
||||
sendBulkEventsToTimelineAction,
|
||||
determineToAndFrom,
|
||||
getNewTermsData,
|
||||
sendAlertToTimelineAction,
|
||||
sendBulkEventsToTimelineAction,
|
||||
} from './actions';
|
||||
import {
|
||||
defaultTimelineProps,
|
||||
getThresholdDetectionAlertAADMock,
|
||||
mockEcsDataWithAlert,
|
||||
mockTimelineDetails,
|
||||
mockAADEcsDataWithAlert,
|
||||
mockEcsDataWithAlert,
|
||||
mockGetOneTimelineResult,
|
||||
mockTimelineData,
|
||||
mockTimelineDetails,
|
||||
} from '../../../common/mock';
|
||||
import type { CreateTimeline } from './types';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import type { DataProvider } from '../../../../common/types/timeline';
|
||||
import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline';
|
||||
import type { ISearchStart } from '@kbn/data-plugin/public';
|
||||
import { searchServiceMock } from '@kbn/data-plugin/public/search/mocks';
|
||||
import { getTimelineTemplate } from '../../../timelines/containers/api';
|
||||
|
@ -408,7 +408,6 @@ describe('alert actions', () => {
|
|||
savedObjectId: null,
|
||||
selectAll: false,
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
show: true,
|
||||
sort: [
|
||||
{
|
||||
|
|
|
@ -37,10 +37,7 @@ import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline
|
|||
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 {
|
||||
useSessionView,
|
||||
useSessionViewNavigation,
|
||||
} from '../../../timelines/components/timeline/tabs/session/use_session_view';
|
||||
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';
|
||||
|
@ -207,7 +204,6 @@ const DetectionEngineAlertsTableComponent: FC<Omit<DetectionEngineAlertTableProp
|
|||
const {
|
||||
initialized: isDataTableInitialized,
|
||||
graphEventId,
|
||||
sessionViewConfig,
|
||||
viewMode: tableView = eventsDefaultModel.viewMode,
|
||||
columns,
|
||||
totalCount: count,
|
||||
|
@ -391,17 +387,10 @@ const DetectionEngineAlertsTableComponent: FC<Omit<DetectionEngineAlertTableProp
|
|||
scopeId: tableType,
|
||||
});
|
||||
|
||||
const { SessionView } = useSessionView({
|
||||
scopeId: tableType,
|
||||
});
|
||||
|
||||
const graphOverlay = useMemo(() => {
|
||||
const shouldShowOverlay =
|
||||
(graphEventId != null && graphEventId.length > 0) || sessionViewConfig != null;
|
||||
return shouldShowOverlay ? (
|
||||
<GraphOverlay scopeId={tableType} SessionView={SessionView} Navigation={Navigation} />
|
||||
) : null;
|
||||
}, [graphEventId, tableType, sessionViewConfig, SessionView, Navigation]);
|
||||
const shouldShowOverlay = graphEventId != null && graphEventId.length > 0;
|
||||
return shouldShowOverlay ? <GraphOverlay scopeId={tableType} Navigation={Navigation} /> : null;
|
||||
}, [graphEventId, tableType, Navigation]);
|
||||
|
||||
const toolbarVisibility = useMemo(
|
||||
() => ({
|
||||
|
|
|
@ -154,9 +154,8 @@ export const SessionView: FC = memo(() => {
|
|||
...sessionViewConfig,
|
||||
isFullScreen: true,
|
||||
loadAlertDetails: openAlertDetailsPreview,
|
||||
openDetailsInExpandableFlyout: (selectedProcess: Process | null) =>
|
||||
openDetailsInPreview(selectedProcess),
|
||||
closeDetailsInExpandableFlyout: () => closeDetailsInPreview(),
|
||||
openDetails: (selectedProcess: Process | null) => openDetailsInPreview(selectedProcess),
|
||||
closeDetails: () => closeDetailsInPreview(),
|
||||
canReadPolicyManagement,
|
||||
resetJumpToEntityId: jumpToEntityId,
|
||||
resetJumpToCursor: jumpToCursor,
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { RenderHookResult } from '@testing-library/react';
|
|||
import { renderHook } from '@testing-library/react';
|
||||
import type { UseSessionViewConfigParams } from './use_session_view_config';
|
||||
import { useSessionViewConfig } from './use_session_view_config';
|
||||
import type { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types';
|
||||
import type { SessionViewConfig } from '../../../../../common/types/session_view';
|
||||
import type { GetFieldsData } from './use_get_fields_data';
|
||||
import { mockDataFormattedForFieldBrowser } from '../mocks/mock_data_formatted_for_field_browser';
|
||||
import { mockFieldData, mockGetFieldsData } from '../mocks/mock_get_fields_data';
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import type { SessionViewConfig } from '@kbn/securitysolution-data-table/common/types';
|
||||
import type { SessionViewConfig } from '../../../../../common/types/session_view';
|
||||
import type { GetFieldsData } from './use_get_fields_data';
|
||||
import { getField } from '../utils';
|
||||
import { useBasicDataFromDetailsData } from './use_basic_data_from_details_data';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { render, cleanup } from '@testing-library/react';
|
||||
import { cleanup, render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
|
@ -82,7 +82,7 @@ describe('GraphOverlay', () => {
|
|||
test('it has 100% width when NOT in full screen mode', () => {
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay SessionView={<div />} Navigation={<div />} scopeId={TableId.test} />
|
||||
<GraphOverlay Navigation={<div />} scopeId={TableId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -102,7 +102,7 @@ describe('GraphOverlay', () => {
|
|||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay SessionView={<div />} Navigation={<div />} scopeId={TableId.test} />
|
||||
<GraphOverlay Navigation={<div />} scopeId={TableId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -126,7 +126,7 @@ describe('GraphOverlay', () => {
|
|||
},
|
||||
})}
|
||||
>
|
||||
<GraphOverlay SessionView={<div />} Navigation={<div />} scopeId={TableId.test} />
|
||||
<GraphOverlay Navigation={<div />} scopeId={TableId.test} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -145,7 +145,7 @@ describe('GraphOverlay', () => {
|
|||
test('it has 100% width when NOT in full screen mode', () => {
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay SessionView={<div />} Navigation={<div />} scopeId={timelineId} />
|
||||
<GraphOverlay Navigation={<div />} scopeId={timelineId} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -165,7 +165,7 @@ describe('GraphOverlay', () => {
|
|||
|
||||
const wrapper = render(
|
||||
<TestProviders>
|
||||
<GraphOverlay SessionView={<div />} Navigation={<div />} scopeId={timelineId} />
|
||||
<GraphOverlay Navigation={<div />} scopeId={timelineId} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
|
@ -173,46 +173,6 @@ describe('GraphOverlay', () => {
|
|||
expect(overlayContainer).toHaveStyleRule('width', '100%');
|
||||
});
|
||||
|
||||
test('it renders session view controls', () => {
|
||||
(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],
|
||||
sessionViewConfig: {
|
||||
index: 'logs-endpoint.events.process*',
|
||||
sessionEntityId: 'testId',
|
||||
sessionStartTime: '2021-10-14T08:05:34.853Z',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})}
|
||||
>
|
||||
<GraphOverlay
|
||||
SessionView={<div />}
|
||||
Navigation={<div>{'Close Session'}</div>}
|
||||
scopeId={timelineId}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.getByText('Close Session')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('it clears the graph event id on unmount', () => {
|
||||
(useGlobalFullScreen as jest.Mock).mockReturnValue({
|
||||
globalFullScreen: false,
|
||||
|
@ -238,7 +198,7 @@ describe('GraphOverlay', () => {
|
|||
},
|
||||
})}
|
||||
>
|
||||
<GraphOverlay SessionView={<div />} Navigation={<div />} scopeId={timelineId} />
|
||||
<GraphOverlay Navigation={<div />} scopeId={timelineId} />
|
||||
</TestProviders>
|
||||
);
|
||||
wrapper.unmount();
|
||||
|
|
|
@ -5,14 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiLoadingSpinner,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
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';
|
||||
|
@ -35,8 +29,6 @@ import { timelineSelectors } from '../../store';
|
|||
import { timelineDefaults } from '../../store/defaults';
|
||||
import { isFullScreen } from '../timeline/helpers';
|
||||
|
||||
const SESSION_VIEW_FULL_SCREEN = 'sessionViewFullScreen';
|
||||
|
||||
const OverlayStyle = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -66,27 +58,12 @@ const StyledResolver = styled(Resolver)`
|
|||
height: 100%;
|
||||
`;
|
||||
|
||||
const ScrollableFlexItem = styled(EuiFlexItem)`
|
||||
${({ theme }) => `background-color: ${theme.eui.euiColorEmptyShade};`}
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
&.${SESSION_VIEW_FULL_SCREEN} {
|
||||
${({ theme }) => `padding: 0 ${theme.eui.euiSizeM}`}
|
||||
}
|
||||
`;
|
||||
|
||||
interface GraphOverlayProps {
|
||||
scopeId: string;
|
||||
SessionView: JSX.Element | null;
|
||||
Navigation: JSX.Element | null;
|
||||
}
|
||||
|
||||
const GraphOverlayComponent: React.FC<GraphOverlayProps> = ({
|
||||
SessionView,
|
||||
Navigation,
|
||||
scopeId,
|
||||
}) => {
|
||||
const GraphOverlayComponent: React.FC<GraphOverlayProps> = ({ Navigation, scopeId }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const { timelineFullScreen } = useTimelineFullScreen();
|
||||
|
@ -101,7 +78,7 @@ const GraphOverlayComponent: React.FC<GraphOverlayProps> = ({
|
|||
|
||||
const defaults = isInTableScope(scopeId) ? tableDefaults : timelineDefaults;
|
||||
|
||||
const { graphEventId, sessionViewConfig } = useDeepEqualSelector(
|
||||
const { graphEventId } = useDeepEqualSelector(
|
||||
(state) => (getScope && getScope(state, scopeId)) ?? defaults
|
||||
);
|
||||
|
||||
|
@ -136,16 +113,6 @@ const GraphOverlayComponent: React.FC<GraphOverlayProps> = ({
|
|||
return { from, to };
|
||||
}, [from, to]);
|
||||
|
||||
const sessionContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (fullScreen && sessionContainerRef.current) {
|
||||
sessionContainerRef.current.setAttribute('style', FullScreenOverlayStyles.join(''));
|
||||
} else if (sessionContainerRef.current) {
|
||||
sessionContainerRef.current.setAttribute('style', OverlayStyle.join(''));
|
||||
}
|
||||
}, [fullScreen]);
|
||||
|
||||
const resolver = useMemo(
|
||||
() =>
|
||||
graphEventId !== undefined ? (
|
||||
|
@ -157,28 +124,14 @@ const GraphOverlayComponent: React.FC<GraphOverlayProps> = ({
|
|||
filters={filters}
|
||||
/>
|
||||
) : (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" style={{ height: '100%' }}>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="center" css={{ height: '100%' }}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[graphEventId, scopeId, selectedPatterns, shouldUpdate, filters]
|
||||
);
|
||||
|
||||
if (!isActiveTimeline(scopeId) && sessionViewConfig !== null) {
|
||||
return (
|
||||
<OverlayContainer data-test-subj="overlayContainer" ref={sessionContainerRef}>
|
||||
<EuiFlexGroup alignItems="flexStart" gutterSize="none" direction="column">
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiFlexItem grow={false}>{Navigation}</EuiFlexItem>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiSpacer size="m" />
|
||||
<ScrollableFlexItem grow={2} className={fullScreen ? SESSION_VIEW_FULL_SCREEN : ''}>
|
||||
{SessionView}
|
||||
</ScrollableFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</OverlayContainer>
|
||||
);
|
||||
} else if (fullScreen && !isActiveTimeline(scopeId)) {
|
||||
if (fullScreen && !isActiveTimeline(scopeId)) {
|
||||
return (
|
||||
<FullScreenOverlayContainer data-test-subj="overlayContainer">
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
|
|
@ -10,7 +10,7 @@ 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, useSessionView } from '../session/use_session_view';
|
||||
import { useSessionViewNavigation } from '../session/use_session_view';
|
||||
|
||||
interface GraphTabContentProps {
|
||||
timelineId: TimelineId;
|
||||
|
@ -26,17 +26,13 @@ const GraphTabContentComponent: React.FC<GraphTabContentProps> = ({ timelineId }
|
|||
scopeId: timelineId,
|
||||
});
|
||||
|
||||
const { SessionView } = useSessionView({
|
||||
scopeId: timelineId,
|
||||
});
|
||||
|
||||
if (!graphEventId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<GraphOverlay scopeId={timelineId} Navigation={Navigation} SessionView={SessionView} />
|
||||
<GraphOverlay scopeId={timelineId} Navigation={Navigation} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -114,7 +114,6 @@ describe('Timeline', () => {
|
|||
|
||||
describe('analyzer tab and session view tab', () => {
|
||||
const analyzerTabSubj = `timelineTabs-${TimelineTabs.graph}`;
|
||||
const sessionViewTabSubj = `timelineTabs-${TimelineTabs.session}`;
|
||||
|
||||
it('should not show the analyzer tab when the advanced setting is enabled', async () => {
|
||||
mockUseUiSetting.mockReturnValue([true]);
|
||||
|
@ -125,7 +124,6 @@ describe('Timeline', () => {
|
|||
</TestProviders>
|
||||
);
|
||||
expect(screen.queryByTestId(analyzerTabSubj)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(sessionViewTabSubj)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ import type { ComponentType, ReactElement, Ref } from 'react';
|
|||
import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
import type { State } from '../../../../common/store';
|
||||
import { useEsqlAvailability } from '../../../../common/hooks/esql/use_esql_availability';
|
||||
|
@ -82,10 +81,6 @@ const PinnedTab = tabWithSuspense(
|
|||
lazy(() => import('./pinned')),
|
||||
<TimelineTabFallback />
|
||||
);
|
||||
const SessionTab = tabWithSuspense(
|
||||
lazy(() => import('./session')),
|
||||
<TimelineTabFallback />
|
||||
);
|
||||
const EsqlTab = tabWithSuspense(
|
||||
lazy(() => import('./esql')),
|
||||
<TimelineTabFallback />
|
||||
|
@ -130,8 +125,6 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
return <GraphTab timelineId={timelineId} />;
|
||||
case TimelineTabs.notes:
|
||||
return <NotesTab timelineId={timelineId} />;
|
||||
case TimelineTabs.session:
|
||||
return <SessionTab timelineId={timelineId} />;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
@ -140,8 +133,7 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
);
|
||||
|
||||
const isGraphOrNotesTabs = useMemo(
|
||||
() =>
|
||||
[TimelineTabs.graph, TimelineTabs.notes, TimelineTabs.session].includes(activeTimelineTab),
|
||||
() => [TimelineTabs.graph, TimelineTabs.notes].includes(activeTimelineTab),
|
||||
[activeTimelineTab]
|
||||
);
|
||||
|
||||
|
@ -194,7 +186,6 @@ const ActiveTimelineTab = memo<ActiveTimelineTabProps>(
|
|||
<LazyTimelineTabRenderer
|
||||
timelineId={timelineId}
|
||||
shouldShowTab={isGraphOrNotesTabs}
|
||||
isOverflowYScroll={activeTimelineTab === TimelineTabs.session}
|
||||
dataTestSubj={`timeline-tab-content-${TimelineTabs.graph}-${TimelineTabs.notes}`}
|
||||
>
|
||||
{isGraphOrNotesTabs ? getTab(activeTimelineTab) : null}
|
||||
|
@ -362,7 +353,11 @@ const TabsContentComponent: React.FC<BasicTimelineTab> = ({
|
|||
}, [setActiveTab, dispatch, timelineId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!graphEventId && activeTab === TimelineTabs.graph) {
|
||||
// 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)
|
||||
// @ts-ignore
|
||||
if ((!graphEventId && activeTab === TimelineTabs.graph) || activeTab === 'session') {
|
||||
setQueryAsActiveTab();
|
||||
}
|
||||
}, [activeTab, graphEventId, setQueryAsActiveTab]);
|
||||
|
|
|
@ -19,7 +19,6 @@ describe('LazyTimelineTabRenderer', () => {
|
|||
const defaultProps = {
|
||||
dataTestSubj: 'test',
|
||||
shouldShowTab: true,
|
||||
isOverflowYScroll: false,
|
||||
timelineId: TimelineId.test,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiLoadingElastic } from '@elastic/eui';
|
||||
import type { TimelineId } from '../../../../../common/types';
|
||||
|
@ -15,7 +15,6 @@ import { getTimelineShowStatusByIdSelector } from '../../../store/selectors';
|
|||
export interface LazyTimelineTabRendererProps {
|
||||
children: React.ReactElement | null;
|
||||
dataTestSubj: string;
|
||||
isOverflowYScroll?: boolean;
|
||||
shouldShowTab: boolean;
|
||||
timelineId: TimelineId;
|
||||
}
|
||||
|
@ -31,13 +30,7 @@ export interface LazyTimelineTabRendererProps {
|
|||
* every time timeline is closed and re-opened after the first interaction.
|
||||
*/
|
||||
export const LazyTimelineTabRenderer = React.memo(
|
||||
({
|
||||
children,
|
||||
dataTestSubj,
|
||||
shouldShowTab,
|
||||
isOverflowYScroll,
|
||||
timelineId,
|
||||
}: LazyTimelineTabRendererProps) => {
|
||||
({ children, dataTestSubj, shouldShowTab, timelineId }: LazyTimelineTabRendererProps) => {
|
||||
const getTimelineShowStatus = useMemo(() => getTimelineShowStatusByIdSelector(), []);
|
||||
const { show } = useDeepEqualSelector((state) => getTimelineShowStatus(state, timelineId));
|
||||
|
||||
|
@ -54,7 +47,7 @@ export const LazyTimelineTabRenderer = React.memo(
|
|||
// The shouldShowTab check here is necessary for the flex container to accurately size to the modal window when it's opened
|
||||
css={css`
|
||||
display: ${shouldShowTab ? 'flex' : 'none'};
|
||||
overflow: ${isOverflowYScroll ? 'hidden scroll' : 'hidden'};
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
`}
|
||||
data-test-subj={dataTestSubj}
|
||||
|
|
|
@ -1,64 +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, { useState, useCallback } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiSpacer } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import type { TimelineId } from '../../../../../../common/types/timeline';
|
||||
import { useSessionViewNavigation, useSessionView } from './use_session_view';
|
||||
|
||||
const MaxWidthFlexItem = styled(EuiFlexItem)`
|
||||
width: 100%;
|
||||
`;
|
||||
const SessionViewWrapper = styled.div`
|
||||
${({ theme }) => `margin: 0 ${theme.eui.euiSizeM};`}
|
||||
`;
|
||||
|
||||
const MaxWidthPageFlexGroup = styled(EuiFlexGroup)`
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
timelineId: TimelineId;
|
||||
}
|
||||
|
||||
const SessionTabContent: React.FC<Props> = ({ timelineId }) => {
|
||||
const [height, setHeight] = useState(0);
|
||||
const measuredRef = useCallback((node: HTMLDivElement | HTMLSpanElement | null) => {
|
||||
if (node !== null) {
|
||||
setHeight(node.getBoundingClientRect().height);
|
||||
}
|
||||
}, []);
|
||||
const { Navigation } = useSessionViewNavigation({
|
||||
scopeId: timelineId,
|
||||
});
|
||||
const { SessionView } = useSessionView({
|
||||
scopeId: timelineId,
|
||||
height,
|
||||
});
|
||||
|
||||
return (
|
||||
<MaxWidthPageFlexGroup
|
||||
alignItems="flexStart"
|
||||
gutterSize="none"
|
||||
ref={measuredRef}
|
||||
data-test-subj="timeline-session-content"
|
||||
>
|
||||
<MaxWidthFlexItem grow={false}>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
{Navigation}
|
||||
<EuiHorizontalRule margin="none" />
|
||||
<EuiSpacer size="m" />
|
||||
<SessionViewWrapper>{SessionView}</SessionViewWrapper>
|
||||
</MaxWidthFlexItem>
|
||||
</MaxWidthPageFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default SessionTabContent;
|
|
@ -11,13 +11,12 @@ 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 { useKibana } from '../../../../../common/lib/kibana';
|
||||
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import {
|
||||
useGlobalFullScreen,
|
||||
useTimelineFullScreen,
|
||||
} from '../../../../../common/containers/use_full_screen';
|
||||
import { useSessionView, useSessionViewNavigation } from './use_session_view';
|
||||
import { useSessionViewNavigation } from './use_session_view';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
|
@ -46,9 +45,6 @@ jest.mock('../../../../../common/lib/kibana', () => {
|
|||
siemV2: { crud_alerts: true, read_alerts: true },
|
||||
},
|
||||
},
|
||||
sessionView: {
|
||||
getSessionView: jest.fn(() => <div />),
|
||||
},
|
||||
data: {
|
||||
search: jest.fn(),
|
||||
query: jest.fn(),
|
||||
|
@ -67,12 +63,10 @@ jest.mock('../../../../../common/lib/kibana', () => {
|
|||
};
|
||||
});
|
||||
|
||||
describe('useSessionView with active timeline and a session id and graph event id', () => {
|
||||
describe('useSessionView with active timeline and graph event id', () => {
|
||||
let setTimelineFullScreen: jest.Mock;
|
||||
let setGlobalFullScreen: jest.Mock;
|
||||
let kibana: ReturnType<typeof useKibana>;
|
||||
const Wrapper = memo<PropsWithChildren<unknown>>(({ children }) => {
|
||||
kibana = useKibana();
|
||||
return <TestProviders>{children}</TestProviders>;
|
||||
});
|
||||
Wrapper.displayName = 'Wrapper';
|
||||
|
@ -89,32 +83,17 @@ describe('useSessionView with active timeline and a session id and graph event i
|
|||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => {
|
||||
return {
|
||||
...mockTimelineModel,
|
||||
activeTab: TimelineTabs.session,
|
||||
activeTab: TimelineTabs.graph,
|
||||
graphEventId: 'current-graph-event-id',
|
||||
sessionViewConfig: {
|
||||
sessionEntityId: 'test',
|
||||
},
|
||||
show: true,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('removes the full screen class from the overlay', () => {
|
||||
renderHook(
|
||||
() => {
|
||||
const testProps = {
|
||||
scopeId: TimelineId.active,
|
||||
};
|
||||
return useSessionView(testProps);
|
||||
},
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
expect(kibana.services.sessionView.getSessionView).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls setTimelineFullScreen with false when onCloseOverlay is called and the app is not in full screen mode', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
|
@ -127,21 +106,7 @@ describe('useSessionView with active timeline and a session id and graph event i
|
|||
);
|
||||
const navigation = result.current.Navigation;
|
||||
const renderResult = render(<TestProviders>{navigation}</TestProviders>);
|
||||
expect(renderResult.getByText('Close session viewer')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('uses an optional height when passed', () => {
|
||||
renderHook(
|
||||
() => {
|
||||
const testProps = {
|
||||
scopeId: TimelineId.test,
|
||||
height: 1118,
|
||||
};
|
||||
return useSessionView(testProps);
|
||||
},
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
expect(kibana.services.sessionView.getSessionView).toHaveBeenCalled();
|
||||
expect(renderResult.getByText('Close analyzer')).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('useSessionView with non active timeline and graph event id set', () => {
|
||||
|
@ -158,14 +123,15 @@ describe('useSessionView with active timeline and a session id and graph event i
|
|||
return {
|
||||
...mockTimelineModel,
|
||||
graphEventId: 'current-graph-event-id',
|
||||
sessionViewConfig: null,
|
||||
show: true,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
||||
});
|
||||
|
||||
it('renders the navigation component with the correct text for analyzer', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
|
@ -182,7 +148,7 @@ describe('useSessionView with active timeline and a session id and graph event i
|
|||
});
|
||||
});
|
||||
|
||||
describe('useSessionView and useSessionViewNavigation should handle separate parts', () => {
|
||||
describe('useSessionViewNavigation should handle separate parts', () => {
|
||||
beforeEach(() => {
|
||||
setTimelineFullScreen = jest.fn();
|
||||
setGlobalFullScreen = jest.fn();
|
||||
|
@ -195,36 +161,16 @@ describe('useSessionView with active timeline and a session id and graph event i
|
|||
(useDeepEqualSelector as jest.Mock).mockImplementation(() => {
|
||||
return {
|
||||
...mockTimelineModel,
|
||||
activeTab: TimelineTabs.session,
|
||||
activeTab: TimelineTabs.graph,
|
||||
graphEventId: 'current-graph-event-id',
|
||||
sessionViewConfig: {
|
||||
sessionEntityId: 'test',
|
||||
},
|
||||
show: true,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(useDeepEqualSelector as jest.Mock).mockClear();
|
||||
});
|
||||
it('useSessionView should handle session view and details panel', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const testProps = {
|
||||
scopeId: TimelineId.active,
|
||||
};
|
||||
return useSessionView(testProps);
|
||||
},
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
expect(kibana.services.sessionView.getSessionView).toHaveBeenCalled();
|
||||
|
||||
expect(result.current).toHaveProperty('openEventDetailsPanel');
|
||||
expect(result.current).toHaveProperty('SessionView');
|
||||
|
||||
expect(result.current).not.toHaveProperty('Navigation');
|
||||
expect(result.current).not.toHaveProperty('onCloseOverlay');
|
||||
});
|
||||
|
||||
it('useSessionViewNavigation should handle Navigation component and on close callback', () => {
|
||||
const { result } = renderHook(
|
||||
|
|
|
@ -17,33 +17,24 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { dataTableSelectors, tableDefaults } from '@kbn/securitysolution-data-table';
|
||||
import { useExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { useEnableExperimental } from '../../../../../common/hooks/use_experimental_features';
|
||||
import { useSelectedPatterns } from '../../../../../data_view_manager/hooks/use_selected_patterns';
|
||||
import { DocumentDetailsRightPanelKey } from '../../../../../flyout/document_details/shared/constants/panel_keys';
|
||||
import {
|
||||
getScopedActions,
|
||||
isActiveTimeline,
|
||||
isInTableScope,
|
||||
isTimelineScope,
|
||||
} from '../../../../../helpers';
|
||||
import { useKibana } from '../../../../../common/lib/kibana';
|
||||
import * as i18n from './translations';
|
||||
import { TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { SourcererScopeName } from '../../../../../sourcerer/store/model';
|
||||
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 { useUserPrivileges } from '../../../../../common/components/user_privileges';
|
||||
import { timelineActions, timelineSelectors } from '../../../../store';
|
||||
import { timelineSelectors } from '../../../../store';
|
||||
import { timelineDefaults } from '../../../../store/defaults';
|
||||
import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import { DocumentEventTypes } from '../../../../../common/lib/telemetry';
|
||||
import { isFullScreen } from '../../helpers';
|
||||
import { useSourcererDataView } from '../../../../../sourcerer/containers';
|
||||
|
||||
interface NavigationProps {
|
||||
fullScreen: boolean;
|
||||
|
@ -132,13 +123,11 @@ export const useSessionViewNavigation = ({ scopeId }: { scopeId: string }) => {
|
|||
const { timelineFullScreen, setTimelineFullScreen } = useTimelineFullScreen();
|
||||
|
||||
const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults;
|
||||
const { graphEventId, sessionViewConfig, activeTab, prevActiveTab } = useDeepEqualSelector(
|
||||
(state) => ({
|
||||
activeTab: timelineDefaults.activeTab,
|
||||
prevActiveTab: timelineDefaults.prevActiveTab,
|
||||
...((getScope && getScope(state, scopeId)) ?? defaults),
|
||||
})
|
||||
);
|
||||
const { graphEventId, activeTab } = useDeepEqualSelector((state) => ({
|
||||
activeTab: timelineDefaults.activeTab,
|
||||
prevActiveTab: timelineDefaults.prevActiveTab,
|
||||
...((getScope && getScope(state, scopeId)) ?? defaults),
|
||||
}));
|
||||
|
||||
const scopedActions = getScopedActions(scopeId);
|
||||
const onCloseOverlay = useCallback(() => {
|
||||
|
@ -160,51 +149,16 @@ export const useSessionViewNavigation = ({ scopeId }: { scopeId: string }) => {
|
|||
if (isActiveTimeline(scopeId) === false) {
|
||||
if (scopedActions) {
|
||||
dispatch(scopedActions.updateGraphEventId({ id: scopeId, graphEventId: '' }));
|
||||
dispatch(scopedActions.updateSessionViewConfig({ id: scopeId, sessionViewConfig: null }));
|
||||
}
|
||||
} else {
|
||||
if (activeTab === TimelineTabs.graph) {
|
||||
if (scopedActions) {
|
||||
dispatch(scopedActions.updateGraphEventId({ id: scopeId, graphEventId: '' }));
|
||||
}
|
||||
if (prevActiveTab === TimelineTabs.session && !sessionViewConfig) {
|
||||
dispatch(
|
||||
timelineActions.setActiveTabTimeline({ id: scopeId, activeTab: TimelineTabs.query })
|
||||
);
|
||||
}
|
||||
} else if (activeTab === TimelineTabs.session) {
|
||||
if (isTimelineScope(scopeId)) {
|
||||
if (prevActiveTab === TimelineTabs.graph && !graphEventId) {
|
||||
dispatch(
|
||||
timelineActions.setActiveTabTimeline({ id: scopeId, activeTab: TimelineTabs.query })
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
timelineActions.setActiveTabTimeline({ id: scopeId, activeTab: prevActiveTab })
|
||||
);
|
||||
}
|
||||
}
|
||||
if (scopedActions) {
|
||||
dispatch(
|
||||
scopedActions.updateSessionViewConfig({
|
||||
id: scopeId,
|
||||
sessionViewConfig: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [
|
||||
setTimelineFullScreen,
|
||||
setGlobalFullScreen,
|
||||
scopedActions,
|
||||
dispatch,
|
||||
scopeId,
|
||||
activeTab,
|
||||
prevActiveTab,
|
||||
sessionViewConfig,
|
||||
graphEventId,
|
||||
]);
|
||||
}, [setTimelineFullScreen, setGlobalFullScreen, scopedActions, dispatch, scopeId, activeTab]);
|
||||
|
||||
const fullScreen = useMemo(
|
||||
() =>
|
||||
isFullScreen({
|
||||
|
@ -214,6 +168,7 @@ export const useSessionViewNavigation = ({ scopeId }: { scopeId: string }) => {
|
|||
}),
|
||||
[globalFullScreen, scopeId, timelineFullScreen]
|
||||
);
|
||||
|
||||
const toggleFullScreen = useCallback(() => {
|
||||
if (isActiveTimeline(scopeId)) {
|
||||
setTimelineFullScreen(!timelineFullScreen);
|
||||
|
@ -221,6 +176,7 @@ export const useSessionViewNavigation = ({ scopeId }: { scopeId: string }) => {
|
|||
setGlobalFullScreen(!globalFullScreen);
|
||||
}
|
||||
}, [scopeId, setTimelineFullScreen, timelineFullScreen, setGlobalFullScreen, globalFullScreen]);
|
||||
|
||||
const navigation = useMemo(() => {
|
||||
return (
|
||||
<Navigation
|
||||
|
@ -250,92 +206,3 @@ export const useSessionViewNavigation = ({ scopeId }: { scopeId: string }) => {
|
|||
Navigation: navigation,
|
||||
};
|
||||
};
|
||||
|
||||
export const useSessionView = ({ scopeId, height }: { scopeId: string; height?: number }) => {
|
||||
const { sessionView, telemetry } = useKibana().services;
|
||||
const getScope = useMemo(() => {
|
||||
if (isTimelineScope(scopeId)) {
|
||||
return timelineSelectors.getTimelineByIdSelector();
|
||||
} else if (isInTableScope(scopeId)) {
|
||||
return dataTableSelectors.getTableByIdSelector();
|
||||
}
|
||||
}, [scopeId]);
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const { timelineFullScreen } = useTimelineFullScreen();
|
||||
const { canReadPolicyManagement } = useUserPrivileges().endpointPrivileges;
|
||||
|
||||
const defaults = isTimelineScope(scopeId) ? timelineDefaults : tableDefaults;
|
||||
const { sessionViewConfig } = useDeepEqualSelector((state) => ({
|
||||
activeTab: timelineDefaults.activeTab,
|
||||
prevActiveTab: timelineDefaults.prevActiveTab,
|
||||
...((getScope && getScope(state, scopeId)) ?? defaults),
|
||||
}));
|
||||
|
||||
const fullScreen = useMemo(
|
||||
() =>
|
||||
isFullScreen({
|
||||
globalFullScreen,
|
||||
isActiveTimelines: isActiveTimeline(scopeId),
|
||||
timelineFullScreen,
|
||||
}),
|
||||
[globalFullScreen, scopeId, timelineFullScreen]
|
||||
);
|
||||
|
||||
const { newDataViewPickerEnabled } = useEnableExperimental();
|
||||
const { selectedPatterns: oldSelectedPatterns } = useSourcererDataView(
|
||||
SourcererScopeName.detections
|
||||
);
|
||||
|
||||
const experimentalSelectedPatterns = useSelectedPatterns(SourcererScopeName.detections);
|
||||
const selectedPatterns = newDataViewPickerEnabled
|
||||
? experimentalSelectedPatterns
|
||||
: oldSelectedPatterns;
|
||||
const alertsIndex = useMemo(() => selectedPatterns.join(','), [selectedPatterns]);
|
||||
|
||||
const { openFlyout } = useExpandableFlyoutApi();
|
||||
const openAlertDetailsFlyout = useCallback(
|
||||
(eventId?: string, onClose?: () => void) => {
|
||||
openFlyout({
|
||||
right: {
|
||||
id: DocumentDetailsRightPanelKey,
|
||||
params: {
|
||||
id: eventId,
|
||||
indexName: alertsIndex,
|
||||
scopeId,
|
||||
},
|
||||
},
|
||||
});
|
||||
telemetry.reportEvent(DocumentEventTypes.DetailsFlyoutOpened, {
|
||||
location: scopeId,
|
||||
panel: 'right',
|
||||
});
|
||||
},
|
||||
[openFlyout, alertsIndex, scopeId, telemetry]
|
||||
);
|
||||
|
||||
const sessionViewComponent = useMemo(() => {
|
||||
const sessionViewSearchBarHeight = 118;
|
||||
const heightMinusSearchBar = height ? height - sessionViewSearchBarHeight : undefined;
|
||||
return sessionViewConfig !== null
|
||||
? sessionView.getSessionView({
|
||||
...sessionViewConfig,
|
||||
loadAlertDetails: openAlertDetailsFlyout,
|
||||
isFullScreen: fullScreen,
|
||||
height: heightMinusSearchBar,
|
||||
canReadPolicyManagement,
|
||||
})
|
||||
: null;
|
||||
}, [
|
||||
height,
|
||||
sessionViewConfig,
|
||||
sessionView,
|
||||
openAlertDetailsFlyout,
|
||||
fullScreen,
|
||||
canReadPolicyManagement,
|
||||
]);
|
||||
|
||||
return {
|
||||
openEventDetailsPanel: openAlertDetailsFlyout,
|
||||
SessionView: sessionViewComponent,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,19 +7,19 @@
|
|||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import {
|
||||
migrateColumnWidthToInitialWidth,
|
||||
migrateColumnLabelToDisplayAsText,
|
||||
LOCAL_STORAGE_TABLE_KEY,
|
||||
useDataTablesStorage,
|
||||
getDataTablesInStorageByIds,
|
||||
getAllDataTablesInStorage,
|
||||
addTableInStorage,
|
||||
migrateAlertTableStateToTriggerActionsState,
|
||||
migrateTriggerActionsVisibleColumnsAlertTable88xTo89,
|
||||
addAssigneesSpecsToSecurityDataTableIfNeeded,
|
||||
addTableInStorage,
|
||||
getAllDataTablesInStorage,
|
||||
getDataTablesInStorageByIds,
|
||||
LOCAL_STORAGE_TABLE_KEY,
|
||||
migrateAlertTableStateToTriggerActionsState,
|
||||
migrateColumnLabelToDisplayAsText,
|
||||
migrateColumnWidthToInitialWidth,
|
||||
migrateTriggerActionsVisibleColumnsAlertTable88xTo89,
|
||||
useDataTablesStorage,
|
||||
} from '.';
|
||||
|
||||
import { mockDataTableModel, createSecuritySolutionStorageMock } from '../../../common/mock';
|
||||
import { createSecuritySolutionStorageMock, mockDataTableModel } from '../../../common/mock';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { VIEW_SELECTION } from '../../../../common/constants';
|
||||
import type { DataTableModel, DataTableState } from '@kbn/securitysolution-data-table';
|
||||
|
@ -519,7 +519,6 @@ describe('SiemLocalStorage', () => {
|
|||
noteIds: [],
|
||||
pinnedEventIds: {},
|
||||
pinnedEventsSaveObject: {},
|
||||
sessionViewConfig: null,
|
||||
show: false,
|
||||
status: 'draft',
|
||||
initialized: true,
|
||||
|
@ -647,7 +646,6 @@ describe('SiemLocalStorage', () => {
|
|||
],
|
||||
graphEventId: undefined,
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
selectAll: undefined,
|
||||
id: 'alerts-page',
|
||||
title: '',
|
||||
|
@ -888,7 +886,6 @@ describe('SiemLocalStorage', () => {
|
|||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
sessionViewConfig: null,
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
|
@ -1002,7 +999,6 @@ describe('SiemLocalStorage', () => {
|
|||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
sessionViewConfig: null,
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
|
@ -1124,7 +1120,6 @@ describe('SiemLocalStorage', () => {
|
|||
],
|
||||
selectAll: false,
|
||||
graphEventId: '',
|
||||
sessionViewConfig: null,
|
||||
columns: [
|
||||
{
|
||||
columnHeaderType: 'not-filtered',
|
||||
|
@ -1562,7 +1557,6 @@ describe('SiemLocalStorage', () => {
|
|||
],
|
||||
graphEventId: undefined,
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
selectAll: false,
|
||||
id: 'alerts-page',
|
||||
title: '',
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import { isEmpty } from 'lodash/fp';
|
||||
import type { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type {
|
||||
DataTableState,
|
||||
DataTableModel,
|
||||
DataTableState,
|
||||
TableIdLiteral,
|
||||
} from '@kbn/securitysolution-data-table';
|
||||
import { tableEntity, TableEntityType, TableId } from '@kbn/securitysolution-data-table';
|
||||
|
@ -51,7 +51,6 @@ export const migrateLegacyTimelinesToSecurityDataTable = (legacyTimelineTables:
|
|||
itemsPerPageOptions: timelineModel.itemsPerPageOptions,
|
||||
showCheckboxes: timelineModel.showCheckboxes,
|
||||
graphEventId: timelineModel.graphEventId,
|
||||
sessionViewConfig: timelineModel.sessionViewConfig,
|
||||
selectAll: timelineModel.selectAll,
|
||||
id: timelineModel.id,
|
||||
title: timelineModel.title,
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
import actionCreatorFactory from 'typescript-fsa';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import type { SavedSearch } from '@kbn/saved-search-plugin/common';
|
||||
|
||||
import type { SessionViewConfig } from '../../../common/types';
|
||||
import type {
|
||||
DataProvider,
|
||||
QueryOperator,
|
||||
|
@ -22,11 +20,11 @@ import type {
|
|||
TimelineNonEcsData,
|
||||
} from '../../../common/search_strategy/timeline';
|
||||
import type {
|
||||
TimelineTabs,
|
||||
TimelinePersistInput,
|
||||
SerializedFilterQuery,
|
||||
ColumnHeaderOptions,
|
||||
SerializedFilterQuery,
|
||||
SortColumnTimeline,
|
||||
TimelinePersistInput,
|
||||
TimelineTabs,
|
||||
} from '../../../common/types/timeline';
|
||||
import type { DataProviderType, RowRendererId } from '../../../common/api/timeline';
|
||||
import type { ResolveTimelineConfig } from '../components/open_timeline/types';
|
||||
|
@ -68,11 +66,6 @@ export const updateGraphEventId = actionCreator<{ id: string; graphEventId: stri
|
|||
'UPDATE_TIMELINE_GRAPH_EVENT_ID'
|
||||
);
|
||||
|
||||
export const updateSessionViewConfig = actionCreator<{
|
||||
id: string;
|
||||
sessionViewConfig: SessionViewConfig | null;
|
||||
}>('UPDATE_TIMELINE_SESSION_VIEW_CONFIG');
|
||||
|
||||
export const unPinEvent = actionCreator<{ id: string; eventId: string }>('UN_PIN_EVENT');
|
||||
|
||||
export const updateTimeline = actionCreator<{
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
import { TimelineTabs } from '../../../common/types/timeline';
|
||||
import {
|
||||
TimelineTypeEnum,
|
||||
TimelineStatusEnum,
|
||||
RowRendererIdEnum,
|
||||
TimelineStatusEnum,
|
||||
TimelineTypeEnum,
|
||||
} from '../../../common/api/timeline';
|
||||
|
||||
import { normalizeTimeRange } from '../../common/utils/normalize_time_range';
|
||||
|
@ -82,7 +82,6 @@ export const timelineDefaults: SubsetTimelineModel &
|
|||
pinnedEventsSaveObject: {},
|
||||
savedObjectId: null,
|
||||
selectAll: false,
|
||||
sessionViewConfig: null,
|
||||
show: false,
|
||||
sort: [
|
||||
{
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import type { ColumnHeaderOptions } from '../../../common/types/timeline';
|
||||
import { TimelineTabs, TimelineId } from '../../../common/types/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../common/types/timeline';
|
||||
import {
|
||||
DataProviderTypeEnum,
|
||||
TimelineTypeEnum,
|
||||
TimelineStatusEnum,
|
||||
TimelineTypeEnum,
|
||||
} from '../../../common/api/timeline';
|
||||
import type {
|
||||
DataProvider,
|
||||
|
@ -19,8 +19,8 @@ import type {
|
|||
} from '../components/timeline/data_providers/data_provider';
|
||||
import { IS_OPERATOR } from '../components/timeline/data_providers/data_provider';
|
||||
import {
|
||||
defaultUdtHeaders,
|
||||
defaultColumnHeaderType,
|
||||
defaultUdtHeaders,
|
||||
} from '../components/timeline/body/column_headers/default_headers';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH } from '../components/timeline/body/constants';
|
||||
import { defaultHeaders } from '../../common/mock';
|
||||
|
@ -31,18 +31,18 @@ import {
|
|||
removeTimelineColumn,
|
||||
removeTimelineProvider,
|
||||
updateTimelineColumns,
|
||||
updateTimelineColumnWidth,
|
||||
updateTimelineGraphEventId,
|
||||
updateTimelineItemsPerPage,
|
||||
updateTimelinePerPageOptions,
|
||||
updateTimelineProviderEnabled,
|
||||
updateTimelineProviderExcluded,
|
||||
updateTimelineProviderType,
|
||||
updateTimelineProviders,
|
||||
updateTimelineProviderType,
|
||||
updateTimelineRange,
|
||||
updateTimelineShowTimeline,
|
||||
updateTimelineSort,
|
||||
updateTimelineTitleAndDescription,
|
||||
updateTimelineGraphEventId,
|
||||
updateTimelineColumnWidth,
|
||||
upsertTimelineColumn,
|
||||
} from './helpers';
|
||||
import type { TimelineModel } from './model';
|
||||
|
@ -118,7 +118,6 @@ const basicTimeline: TimelineModel = {
|
|||
savedObjectId: null,
|
||||
selectAll: false,
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
show: true,
|
||||
sort: [
|
||||
{
|
||||
|
|
|
@ -5,31 +5,30 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getOr, omit, uniq, isEmpty, isEqualWith, cloneDeep, union } from 'lodash/fp';
|
||||
import { cloneDeep, getOr, isEmpty, isEqualWith, omit, union, uniq } from 'lodash/fp';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import type { SessionViewConfig } from '../../../common/types';
|
||||
import type { TimelineNonEcsData } from '../../../common/search_strategy';
|
||||
import type {
|
||||
DataProvider,
|
||||
QueryOperator,
|
||||
QueryMatch,
|
||||
QueryOperator,
|
||||
} from '../components/timeline/data_providers/data_provider';
|
||||
import { IS_OPERATOR, EXISTS_OPERATOR } from '../components/timeline/data_providers/data_provider';
|
||||
import { EXISTS_OPERATOR, IS_OPERATOR } from '../components/timeline/data_providers/data_provider';
|
||||
import type { RowRendererId, TimelineType } from '../../../common/api/timeline';
|
||||
import {
|
||||
type DataProviderType,
|
||||
DataProviderTypeEnum,
|
||||
TimelineStatusEnum,
|
||||
TimelineTypeEnum,
|
||||
} from '../../../common/api/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../common/types/timeline';
|
||||
import type {
|
||||
ColumnHeaderOptions,
|
||||
SerializedFilterQuery,
|
||||
TimelinePersistInput,
|
||||
SortColumnTimeline,
|
||||
TimelinePersistInput,
|
||||
} from '../../../common/types/timeline';
|
||||
import type { RowRendererId, TimelineType } from '../../../common/api/timeline';
|
||||
import { TimelineId, TimelineTabs } from '../../../common/types/timeline';
|
||||
import { normalizeTimeRange } from '../../common/utils/normalize_time_range';
|
||||
import { getTimelineManageDefaults, timelineDefaults } from './defaults';
|
||||
import type { KqlMode, TimelineModel } from './model';
|
||||
|
@ -273,26 +272,6 @@ export const updateTimelineGraphEventId = ({
|
|||
};
|
||||
};
|
||||
|
||||
export const updateTimelineSessionViewConfig = ({
|
||||
id,
|
||||
sessionViewConfig,
|
||||
timelineById,
|
||||
}: {
|
||||
id: string;
|
||||
sessionViewConfig: SessionViewConfig | null;
|
||||
timelineById: TimelineById;
|
||||
}): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
sessionViewConfig,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const queryMatchCustomizer = (dp1: QueryMatch, dp2: QueryMatch) => {
|
||||
if (dp1.field === dp2.field && dp1.value === dp2.value && dp1.operator === dp2.operator) {
|
||||
return true;
|
||||
|
|
|
@ -9,7 +9,7 @@ import type { Filter } from '@kbn/es-query';
|
|||
import { FilterStateStore } from '@kbn/es-query';
|
||||
import { Direction } from '../../../../common/search_strategy';
|
||||
import { TimelineId, TimelineTabs } from '../../../../common/types/timeline';
|
||||
import { TimelineTypeEnum, TimelineStatusEnum } from '../../../../common/api/timeline';
|
||||
import { TimelineStatusEnum, TimelineTypeEnum } from '../../../../common/api/timeline';
|
||||
import { convertTimelineAsInput } from './timeline_save';
|
||||
import type { TimelineModel } from '../model';
|
||||
import { createMockStore, kibanaMock, mockGlobalState } from '../../../common/mock';
|
||||
|
@ -19,11 +19,11 @@ import { refreshTimelines } from './helpers';
|
|||
import * as i18n from '../../pages/translations';
|
||||
|
||||
import {
|
||||
startTimelineSaving,
|
||||
endTimelineSaving,
|
||||
showCallOutUnauthorizedMsg,
|
||||
saveTimeline,
|
||||
setChanged,
|
||||
showCallOutUnauthorizedMsg,
|
||||
startTimelineSaving,
|
||||
} from '../actions';
|
||||
|
||||
jest.mock('../actions', () => {
|
||||
|
@ -310,7 +310,6 @@ describe('Timeline save middleware', () => {
|
|||
savedObjectId: '11169110-fc22-11e9-8ca9-072f15ce2685',
|
||||
selectAll: false,
|
||||
selectedEventIds: {},
|
||||
sessionViewConfig: null,
|
||||
show: true,
|
||||
sort: [
|
||||
{
|
||||
|
|
|
@ -7,22 +7,21 @@
|
|||
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import type { SavedSearch } from '@kbn/saved-search-plugin/common';
|
||||
import type { SessionViewConfig } from '../../../common/types';
|
||||
import type { EqlOptions, TimelineNonEcsData } from '../../../common/search_strategy/timeline';
|
||||
import type {
|
||||
TimelineTabs,
|
||||
ScrollToTopEvent,
|
||||
SortColumnTimeline,
|
||||
ColumnHeaderOptions,
|
||||
DataProvider,
|
||||
ScrollToTopEvent,
|
||||
SerializedFilterQuery,
|
||||
SortColumnTimeline,
|
||||
TimelineEventsType,
|
||||
TimelineTabs,
|
||||
} from '../../../common/types/timeline';
|
||||
import type {
|
||||
PinnedEvent,
|
||||
RowRendererId,
|
||||
TimelineStatus,
|
||||
TimelineType,
|
||||
PinnedEvent,
|
||||
} from '../../../common/api/timeline';
|
||||
import type { ResolveTimelineConfig } from '../components/open_timeline/types';
|
||||
|
||||
|
@ -70,7 +69,6 @@ export interface TimelineModel {
|
|||
resolveTimelineConfig?: ResolveTimelineConfig;
|
||||
showSaveModal?: boolean;
|
||||
savedQueryId?: string | null;
|
||||
sessionViewConfig: SessionViewConfig | null;
|
||||
/** When true, show the timeline flyover */
|
||||
show: boolean;
|
||||
/** status: active | draft */
|
||||
|
@ -182,7 +180,6 @@ export type SubsetTimelineModel = Readonly<
|
|||
| 'dateRange'
|
||||
| 'selectAll'
|
||||
| 'selectedEventIds'
|
||||
| 'sessionViewConfig'
|
||||
| 'show'
|
||||
| 'sort'
|
||||
| 'isSaving'
|
||||
|
|
|
@ -13,57 +13,56 @@ import {
|
|||
addProvider,
|
||||
addTimeline,
|
||||
applyKqlFilterQuery,
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
clearSelected,
|
||||
createTimeline,
|
||||
dataProviderEdited,
|
||||
deleteNoteFromEvent,
|
||||
endTimelineSaving,
|
||||
initializeSavedSearch,
|
||||
initializeTimelineSettings,
|
||||
pinEvent,
|
||||
removeColumn,
|
||||
removeProvider,
|
||||
setActiveTabTimeline,
|
||||
setChanged,
|
||||
setConfirmingNoteId,
|
||||
setDataProviderVisibility,
|
||||
setEventsDeleted,
|
||||
setEventsLoading,
|
||||
setExcludedRowRendererIds,
|
||||
setFilters,
|
||||
setInsertTimeline,
|
||||
setSavedQueryId,
|
||||
setSelected,
|
||||
showCallOutUnauthorizedMsg,
|
||||
showTimeline,
|
||||
startTimelineSaving,
|
||||
toggleModalSaveTimeline,
|
||||
unPinEvent,
|
||||
updateColumns,
|
||||
updateColumnWidth,
|
||||
updateDataProviderEnabled,
|
||||
updateDataProviderExcluded,
|
||||
updateDataProviderType,
|
||||
updateDataView,
|
||||
updateEqlOptions,
|
||||
updateGraphEventId,
|
||||
updateIsFavorite,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
updateKqlMode,
|
||||
updateProviders,
|
||||
updateRange,
|
||||
updateTimeline,
|
||||
updateGraphEventId,
|
||||
updateTitleAndDescription,
|
||||
updateSessionViewConfig,
|
||||
toggleModalSaveTimeline,
|
||||
updateEqlOptions,
|
||||
setEventsLoading,
|
||||
removeColumn,
|
||||
upsertColumn,
|
||||
updateColumns,
|
||||
updateSort,
|
||||
clearSelected,
|
||||
setSelected,
|
||||
setEventsDeleted,
|
||||
initializeTimelineSettings,
|
||||
updateItemsPerPage,
|
||||
updateItemsPerPageOptions,
|
||||
clearEventsDeleted,
|
||||
clearEventsLoading,
|
||||
updateSavedSearchId,
|
||||
updateSavedSearch,
|
||||
initializeSavedSearch,
|
||||
setDataProviderVisibility,
|
||||
setChanged,
|
||||
updateRowHeight,
|
||||
updateSampleSize,
|
||||
updateColumnWidth,
|
||||
setConfirmingNoteId,
|
||||
deleteNoteFromEvent,
|
||||
updateSavedSearch,
|
||||
updateSavedSearchId,
|
||||
updateSort,
|
||||
updateTimeline,
|
||||
updateTitleAndDescription,
|
||||
upsertColumn,
|
||||
} from './actions';
|
||||
|
||||
import {
|
||||
|
@ -74,34 +73,33 @@ import {
|
|||
addTimelineToStore,
|
||||
applyKqlFilterQueryDraft,
|
||||
pinTimelineEvent,
|
||||
removeTimelineColumn,
|
||||
removeTimelineProvider,
|
||||
setDeletedTableEvents,
|
||||
setInitializeTimelineSettings,
|
||||
setLoadingTableEvents,
|
||||
setSelectedTableEvents,
|
||||
unPinTimelineEvent,
|
||||
updateExcludedRowRenderersIds,
|
||||
updateFilters,
|
||||
updateSavedQuery,
|
||||
updateTimelineColumns,
|
||||
updateTimelineColumnWidth,
|
||||
updateTimelineGraphEventId,
|
||||
updateTimelineIsFavorite,
|
||||
updateTimelineItemsPerPage,
|
||||
updateTimelineKqlMode,
|
||||
updateTimelinePerPageOptions,
|
||||
updateTimelineProviderEnabled,
|
||||
updateTimelineProviderExcluded,
|
||||
updateTimelineProviderProperties,
|
||||
updateTimelineProviderType,
|
||||
updateTimelineProviders,
|
||||
updateTimelineProviderType,
|
||||
updateTimelineRange,
|
||||
updateTimelineShowTimeline,
|
||||
updateTimelineTitleAndDescription,
|
||||
updateSavedQuery,
|
||||
updateTimelineGraphEventId,
|
||||
updateFilters,
|
||||
updateTimelineSessionViewConfig,
|
||||
setLoadingTableEvents,
|
||||
removeTimelineColumn,
|
||||
upsertTimelineColumn,
|
||||
updateTimelineColumns,
|
||||
updateTimelineSort,
|
||||
setSelectedTableEvents,
|
||||
setDeletedTableEvents,
|
||||
setInitializeTimelineSettings,
|
||||
updateTimelinePerPageOptions,
|
||||
updateTimelineItemsPerPage,
|
||||
updateTimelineColumnWidth,
|
||||
updateTimelineTitleAndDescription,
|
||||
upsertTimelineColumn,
|
||||
} from './helpers';
|
||||
|
||||
import type { TimelineState } from './types';
|
||||
|
@ -171,14 +169,6 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(updateSessionViewConfig, (state, { id, sessionViewConfig }) => ({
|
||||
...state,
|
||||
timelineById: updateTimelineSessionViewConfig({
|
||||
id,
|
||||
sessionViewConfig,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(pinEvent, (state, { id, eventId }) => ({
|
||||
...state,
|
||||
timelineById: pinTimelineEvent({ id, eventId, timelineById: state.timelineById }),
|
||||
|
|
|
@ -30,9 +30,6 @@ export const PROCESS_ENTITY_ID_PROPERTY = 'process.entity_id';
|
|||
export const ALERT_UUID_PROPERTY = 'kibana.alert.uuid';
|
||||
export const ALERT_ORIGINAL_TIME_PROPERTY = 'kibana.alert.original_time';
|
||||
export const TOTAL_BYTES_CAPTURED_PROPERTY = 'process.io.total_bytes_captured';
|
||||
export const TTY_CHAR_DEVICE_MAJOR_PROPERTY = 'process.tty.char_device.major';
|
||||
export const TTY_CHAR_DEVICE_MINOR_PROPERTY = 'process.tty.char_device.minor';
|
||||
export const HOST_ID_PROPERTY = 'host.id';
|
||||
export const TIMESTAMP_PROPERTY = '@timestamp';
|
||||
|
||||
// page sizes
|
||||
|
@ -67,16 +64,12 @@ export const DEFAULT_TTY_COLS = 280;
|
|||
// it also creates a more interesting play by play
|
||||
export const TTY_LINE_SPLITTER_REGEX = /(\r?\n|\r\n?|\x1b\[\d+;\d*[Hf]?)/gi;
|
||||
|
||||
// when showing the count of alerts in details panel tab, if the number
|
||||
// exceeds ALERT_COUNT_THRESHOLD we put a + next to it, e.g 999+
|
||||
export const ALERT_COUNT_THRESHOLD = 999;
|
||||
export const ALERT_ICONS: { [key: string]: string } = {
|
||||
process: 'gear',
|
||||
file: 'document',
|
||||
network: 'globe',
|
||||
};
|
||||
export const DEFAULT_ALERT_FILTER_VALUE = 'all';
|
||||
export const ALERT = 'alert';
|
||||
|
||||
// a list of fields to pull in ES queries for process_events_route, io_events_route and alerts.
|
||||
export const PROCESS_EVENT_FIELDS = [
|
||||
|
|
|
@ -4,32 +4,31 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { useEffect, useState, useMemo } from 'react';
|
||||
import { useQuery, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { EuiSearchBarOnChangeArgs } from '@elastic/eui';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
|
||||
import { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type {
|
||||
AlertStatusEventEntityIdMap,
|
||||
EventAction,
|
||||
ProcessEvent,
|
||||
ProcessEventResults,
|
||||
EventAction,
|
||||
} from '../../../common';
|
||||
import {
|
||||
ALERTS_ROUTE,
|
||||
PROCESS_EVENTS_ROUTE,
|
||||
PROCESS_EVENTS_PER_PAGE,
|
||||
ALERTS_PER_PAGE,
|
||||
ALERT_STATUS_ROUTE,
|
||||
GET_TOTAL_IO_BYTES_ROUTE,
|
||||
QUERY_KEY_PROCESS_EVENTS,
|
||||
QUERY_KEY_ALERTS,
|
||||
QUERY_KEY_GET_TOTAL_IO_BYTES,
|
||||
ALERTS_PER_PAGE,
|
||||
ALERTS_ROUTE,
|
||||
CURRENT_API_VERSION,
|
||||
EVENT_ACTION_FORK,
|
||||
EVENT_ACTION_END,
|
||||
EVENT_ACTION_EXEC,
|
||||
EVENT_ACTION_EXECUTED,
|
||||
EVENT_ACTION_END,
|
||||
EVENT_ACTION_FORK,
|
||||
GET_TOTAL_IO_BYTES_ROUTE,
|
||||
PROCESS_EVENTS_PER_PAGE,
|
||||
PROCESS_EVENTS_ROUTE,
|
||||
QUERY_KEY_ALERTS,
|
||||
QUERY_KEY_GET_TOTAL_IO_BYTES,
|
||||
QUERY_KEY_PROCESS_EVENTS,
|
||||
} from '../../../common/constants';
|
||||
|
||||
const isSessionEventAction = (action: EventAction | undefined): boolean => {
|
||||
|
@ -252,19 +251,3 @@ export const useFetchGetTotalIOBytes = (
|
|||
|
||||
return query;
|
||||
};
|
||||
|
||||
export const useSearchQuery = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const onSearch = ({ query }: EuiSearchBarOnChangeArgs) => {
|
||||
if (query) {
|
||||
setSearchQuery(query.text);
|
||||
} else {
|
||||
setSearchQuery('');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
onSearch,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -38,6 +38,8 @@ describe('SessionView component', () => {
|
|||
sessionStartTime={TEST_SESSION_START_TIME}
|
||||
sessionEntityId="test-entity-id"
|
||||
trackEvent={jest.fn()}
|
||||
openDetails={jest.fn()}
|
||||
closeDetails={jest.fn()}
|
||||
/>
|
||||
));
|
||||
mockUseDateFormat.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');
|
||||
|
@ -129,19 +131,6 @@ describe('SessionView component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should toggle detail panel visibilty when detail button clicked', async () => {
|
||||
render();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(renderResult.getByTestId('sessionView:sessionViewDetailPanelToggle')).toBeTruthy();
|
||||
});
|
||||
|
||||
await userEvent.click(renderResult.getByTestId('sessionView:sessionViewDetailPanelToggle'));
|
||||
expect(renderResult.getByText('Process')).toBeTruthy();
|
||||
expect(renderResult.getByText('Metadata')).toBeTruthy();
|
||||
expect(renderResult.getByText('Alerts')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render session view options button and its options when clicked', async () => {
|
||||
render();
|
||||
|
||||
|
|
|
@ -4,17 +4,14 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiEmptyPrompt,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
EuiPanel,
|
||||
EuiResizableContainer,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
@ -26,7 +23,6 @@ import type { AlertStatusEventEntityIdMap, Process, ProcessEvent } from '../../.
|
|||
import type { DisplayOptionsState } from '../session_view_display_options';
|
||||
import { SessionViewDisplayOptions } from '../session_view_display_options';
|
||||
import type { SessionViewDeps, SessionViewIndices, SessionViewTelemetryKey } from '../../types';
|
||||
import { SessionViewDetailPanel } from '../session_view_detail_panel';
|
||||
import { SessionViewSearchBar } from '../session_view_search_bar';
|
||||
import { TTYPlayer } from '../tty_player';
|
||||
import { useStyles } from './styles';
|
||||
|
@ -43,7 +39,7 @@ import {
|
|||
ELASTIC_DEFEND_DATA_SOURCE,
|
||||
ENDPOINT_INDEX,
|
||||
} from '../../methods';
|
||||
import { DETAIL_PANEL, REFRESH_SESSION, TOGGLE_TTY_PLAYER } from './translations';
|
||||
import { REFRESH_SESSION, TOGGLE_TTY_PLAYER } from './translations';
|
||||
|
||||
/**
|
||||
* The main wrapper component for the session view.
|
||||
|
@ -53,15 +49,14 @@ export const SessionView = ({
|
|||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
height,
|
||||
isFullScreen = false,
|
||||
jumpToEntityId,
|
||||
jumpToCursor,
|
||||
investigatedAlertId,
|
||||
loadAlertDetails,
|
||||
canReadPolicyManagement,
|
||||
trackEvent,
|
||||
openDetailsInExpandableFlyout,
|
||||
closeDetailsInExpandableFlyout,
|
||||
openDetails,
|
||||
closeDetails,
|
||||
resetJumpToEntityId,
|
||||
resetJumpToCursor,
|
||||
}: SessionViewDeps & { trackEvent: (name: SessionViewTelemetryKey) => void }) => {
|
||||
|
@ -88,7 +83,6 @@ export const SessionView = ({
|
|||
}, [index, investigatedAlertId, trackEvent]);
|
||||
|
||||
const [showTTY, setShowTTY] = useState(false);
|
||||
const [isDetailOpen, setIsDetailOpen] = useState(false);
|
||||
const [selectedProcess, setSelectedProcess] = useState<Process | null>(null);
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<Process[] | null>(null);
|
||||
|
@ -104,11 +98,8 @@ export const SessionView = ({
|
|||
const [currentJumpToCursor, setCurrentJumpToCursor] = useState(jumpToCursor);
|
||||
const [currentJumpToEntityId, setCurrentJumpToEntityId] = useState(jumpToEntityId);
|
||||
const [currentJumpToOutputEntityId, setCurrentJumpToOutputEntityId] = useState('');
|
||||
const sessionViewId = useMemo(() => `session-view-uuid-${uuidv4()}`, []);
|
||||
|
||||
const styles = useStyles({ height, isFullScreen });
|
||||
|
||||
const detailPanelCollapseFn = useRef(() => {});
|
||||
const styles = useStyles({ height });
|
||||
|
||||
// to give an indication to the user that there may be more search results if they turn on verbose mode.
|
||||
const showVerboseSearchTooltip = useMemo(() => {
|
||||
|
@ -119,14 +110,13 @@ export const SessionView = ({
|
|||
(process: Process | null, isManualSelection = false) => {
|
||||
setSelectedProcess(process);
|
||||
|
||||
// used when SessionView is displayed in the expandable flyout
|
||||
// This refreshes the detailed panel rendered in the flyout preview panel
|
||||
// the isManualSelection prevents the detailed panel to render on first load of the SessionView component
|
||||
if (openDetailsInExpandableFlyout && isManualSelection) {
|
||||
openDetailsInExpandableFlyout(process);
|
||||
if (isManualSelection) {
|
||||
openDetails(process);
|
||||
}
|
||||
},
|
||||
[openDetailsInExpandableFlyout]
|
||||
[openDetails]
|
||||
);
|
||||
|
||||
const onJumpToEvent = useCallback(
|
||||
|
@ -169,14 +159,11 @@ export const SessionView = ({
|
|||
currentJumpToCursor
|
||||
);
|
||||
|
||||
const {
|
||||
data: alertsData,
|
||||
fetchNextPage: fetchNextPageAlerts,
|
||||
isFetching: isFetchingAlerts,
|
||||
hasNextPage: hasNextPageAlerts,
|
||||
error: alertsError,
|
||||
refetch: refetchAlerts,
|
||||
} = useFetchSessionViewAlerts(sessionEntityId, sessionStartTime, investigatedAlertId);
|
||||
const { error: alertsError, refetch: refetchAlerts } = useFetchSessionViewAlerts(
|
||||
sessionEntityId,
|
||||
sessionStartTime,
|
||||
investigatedAlertId
|
||||
);
|
||||
|
||||
const { data: totalTTYOutputBytes, refetch: refetchTotalTTYOutput } = useFetchGetTotalIOBytes(
|
||||
index,
|
||||
|
@ -194,28 +181,20 @@ export const SessionView = ({
|
|||
if (hasTTYOutput) {
|
||||
setShowTTY(!showTTY);
|
||||
|
||||
// used when SessionView is displayed in the expandable flyout
|
||||
// This closes the detailed panel rendered in the flyout preview panel when the user activate the TTY output mode
|
||||
// then reopens the detailed panel to the previously selected process when the user deactivates the TTY output mode
|
||||
if (closeDetailsInExpandableFlyout && !showTTY) {
|
||||
closeDetailsInExpandableFlyout();
|
||||
if (!showTTY) {
|
||||
closeDetails();
|
||||
}
|
||||
if (openDetailsInExpandableFlyout && showTTY) {
|
||||
openDetailsInExpandableFlyout(selectedProcess);
|
||||
if (showTTY) {
|
||||
openDetails(selectedProcess);
|
||||
}
|
||||
|
||||
trackEvent('tty_loaded');
|
||||
} else {
|
||||
trackEvent('disabled_tty_clicked');
|
||||
}
|
||||
}, [
|
||||
closeDetailsInExpandableFlyout,
|
||||
hasTTYOutput,
|
||||
openDetailsInExpandableFlyout,
|
||||
selectedProcess,
|
||||
showTTY,
|
||||
trackEvent,
|
||||
]);
|
||||
}, [closeDetails, hasTTYOutput, openDetails, selectedProcess, showTTY, trackEvent]);
|
||||
|
||||
const handleRefresh = useCallback(() => {
|
||||
refetch({ refetchPage: (_page, i, allPages) => allPages.length - 1 === i });
|
||||
|
@ -224,22 +203,6 @@ export const SessionView = ({
|
|||
trackEvent('refresh_clicked');
|
||||
}, [refetch, refetchAlerts, refetchTotalTTYOutput, trackEvent]);
|
||||
|
||||
const alerts = useMemo(() => {
|
||||
let events: ProcessEvent[] = [];
|
||||
|
||||
if (alertsData) {
|
||||
alertsData.pages.forEach((page) => {
|
||||
events = events.concat(page.events);
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
}, [alertsData]);
|
||||
|
||||
const alertsCount = useMemo(() => {
|
||||
return alertsData?.pages?.[0].total || 0;
|
||||
}, [alertsData]);
|
||||
|
||||
const hasError = error || alertsError;
|
||||
const dataLoaded = data && data.pages?.length > (jumpToCursor ? 1 : 0);
|
||||
const renderIsLoading = isFetching && !dataLoaded;
|
||||
|
@ -292,22 +255,8 @@ export const SessionView = ({
|
|||
}, []);
|
||||
|
||||
const toggleDetailPanel = useCallback(() => {
|
||||
const newValue = !isDetailOpen;
|
||||
detailPanelCollapseFn.current();
|
||||
setIsDetailOpen(newValue);
|
||||
|
||||
if (newValue) {
|
||||
trackEvent('details_opened');
|
||||
} else {
|
||||
trackEvent('details_closed');
|
||||
}
|
||||
}, [isDetailOpen, trackEvent]);
|
||||
|
||||
const toggleDetailPanelInFlyout = useCallback(() => {
|
||||
if (openDetailsInExpandableFlyout) {
|
||||
openDetailsInExpandableFlyout(selectedProcess);
|
||||
}
|
||||
}, [openDetailsInExpandableFlyout, selectedProcess]);
|
||||
openDetails(selectedProcess);
|
||||
}, [openDetails, selectedProcess]);
|
||||
|
||||
const onShowAlertDetails = useCallback(
|
||||
(alertUuid: string) => {
|
||||
|
@ -518,73 +467,21 @@ export const SessionView = ({
|
|||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
{openDetailsInExpandableFlyout ? (
|
||||
<EuiButtonIcon onClick={toggleDetailPanelInFlyout} iconType="list" />
|
||||
) : (
|
||||
<EuiButton
|
||||
onClick={toggleDetailPanel}
|
||||
iconType="list"
|
||||
data-test-subj="sessionView:sessionViewDetailPanelToggle"
|
||||
fill={!isDetailOpen}
|
||||
>
|
||||
{DETAIL_PANEL}
|
||||
</EuiButton>
|
||||
)}
|
||||
<EuiButtonIcon onClick={toggleDetailPanel} iconType="list" />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<EuiHorizontalRule margin="none" />
|
||||
{openDetailsInExpandableFlyout ? (
|
||||
<>
|
||||
{errorEmptyPrompt}
|
||||
{processTree}
|
||||
</>
|
||||
) : (
|
||||
<EuiResizableContainer>
|
||||
{(EuiResizablePanel, EuiResizableButton, { togglePanel }) => {
|
||||
detailPanelCollapseFn.current = () => {
|
||||
togglePanel?.(sessionViewId, { direction: 'left' });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiResizablePanel initialSize={100} minSize="60%" paddingSize="none">
|
||||
{errorEmptyPrompt}
|
||||
{processTree}
|
||||
</EuiResizablePanel>
|
||||
<EuiResizableButton css={styles.resizeHandle} />
|
||||
<EuiResizablePanel
|
||||
id={sessionViewId}
|
||||
initialSize={30}
|
||||
minSize="320px"
|
||||
paddingSize="none"
|
||||
css={styles.detailPanel}
|
||||
>
|
||||
<SessionViewDetailPanel
|
||||
index={index}
|
||||
alerts={alerts}
|
||||
alertsCount={alertsCount}
|
||||
isFetchingAlerts={isFetchingAlerts}
|
||||
hasNextPageAlerts={hasNextPageAlerts}
|
||||
fetchNextPageAlerts={fetchNextPageAlerts}
|
||||
investigatedAlertId={investigatedAlertId}
|
||||
selectedProcess={selectedProcess}
|
||||
onJumpToEvent={onJumpToEvent}
|
||||
onShowAlertDetails={onShowAlertDetails}
|
||||
/>
|
||||
</EuiResizablePanel>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</EuiResizableContainer>
|
||||
)}
|
||||
<>
|
||||
{errorEmptyPrompt}
|
||||
{processTree}
|
||||
</>
|
||||
<TTYPlayer
|
||||
index={index}
|
||||
show={showTTY}
|
||||
sessionEntityId={sessionEntityId}
|
||||
sessionStartTime={sessionStartTime}
|
||||
onClose={onToggleTTY}
|
||||
isFullscreen={isFullScreen}
|
||||
onJumpToEvent={onJumpToEvent}
|
||||
autoSeekToEntityId={currentJumpToOutputEntityId}
|
||||
canReadPolicyManagement={canReadPolicyManagement}
|
||||
|
|
|
@ -14,7 +14,7 @@ interface StylesDeps {
|
|||
isFullScreen?: boolean;
|
||||
}
|
||||
|
||||
export const useStyles = ({ height = 500, isFullScreen }: StylesDeps) => {
|
||||
export const useStyles = ({ height = 500 }: StylesDeps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const cached = useMemo(() => {
|
||||
|
@ -22,7 +22,7 @@ export const useStyles = ({ height = 500, isFullScreen }: StylesDeps) => {
|
|||
|
||||
// 118px = Session View Toolbar height + Close Session button height + spacing margin at the bottom
|
||||
const sessionView: CSSObject = {
|
||||
height: `${isFullScreen ? 'calc(100vh - 118px)' : height + 'px'}`,
|
||||
height: `${height + 'px'}`,
|
||||
};
|
||||
|
||||
const processTree: CSSObject = {
|
||||
|
@ -30,15 +30,6 @@ export const useStyles = ({ height = 500, isFullScreen }: StylesDeps) => {
|
|||
position: 'relative',
|
||||
};
|
||||
|
||||
const detailPanel: CSSObject = {
|
||||
...sessionView,
|
||||
borderRightWidth: '0px',
|
||||
};
|
||||
|
||||
const resizeHandle: CSSObject = {
|
||||
zIndex: 2,
|
||||
};
|
||||
|
||||
const nonGrowGroup: CSSObject = {
|
||||
display: 'flex',
|
||||
flexGrow: 0,
|
||||
|
@ -61,13 +52,11 @@ export const useStyles = ({ height = 500, isFullScreen }: StylesDeps) => {
|
|||
|
||||
return {
|
||||
processTree,
|
||||
detailPanel,
|
||||
nonGrowGroup,
|
||||
resizeHandle,
|
||||
fakeDisabled,
|
||||
sessionViewerComponent,
|
||||
};
|
||||
}, [euiTheme, isFullScreen, height]);
|
||||
}, [euiTheme, height]);
|
||||
|
||||
return cached;
|
||||
};
|
||||
|
|
|
@ -1,38 +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 { getSelectedTabContent } from './helpers';
|
||||
import { EuiTabProps } from '../../types';
|
||||
|
||||
const TABS: EuiTabProps[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Process',
|
||||
content: 'process content',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Host',
|
||||
content: 'host content',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Alert',
|
||||
content: 'alert content',
|
||||
},
|
||||
];
|
||||
|
||||
describe('session view detail panel helpers tests', () => {
|
||||
it('getSelectedTabContent works', () => {
|
||||
const result = getSelectedTabContent(TABS, '1');
|
||||
expect(result).toBe(TABS[0].content);
|
||||
});
|
||||
|
||||
it('getSelectedTabContent returns null if tab id not found', () => {
|
||||
const result = getSelectedTabContent(TABS, 'process');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
|
@ -1,17 +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 { EuiTabProps } from '../../types';
|
||||
|
||||
export const getSelectedTabContent = (tabs: EuiTabProps[], selectedTabId: string) => {
|
||||
const selectedTab = tabs.find((tab) => tab.id === selectedTabId);
|
||||
|
||||
if (selectedTab) {
|
||||
return selectedTab.content;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
|
@ -1,80 +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 from 'react';
|
||||
import {
|
||||
mockAlerts,
|
||||
sessionViewBasicProcessMock,
|
||||
} from '../../../common/mocks/constants/session_view_process.mock';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import { SessionViewDetailPanel } from '.';
|
||||
import { useDateFormat } from '../../hooks';
|
||||
import { ENDPOINT_INDEX } from '../../methods';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
jest.mock('../../hooks/use_date_format');
|
||||
const mockUseDateFormat = useDateFormat as jest.Mock;
|
||||
|
||||
describe('SessionView component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let mockedContext: AppContextTestRender;
|
||||
|
||||
const props = {
|
||||
index: ENDPOINT_INDEX,
|
||||
alerts: [],
|
||||
alertsCount: 0,
|
||||
selectedProcess: sessionViewBasicProcessMock,
|
||||
isFetchingAlerts: false,
|
||||
hasNextPageAlerts: false,
|
||||
fetchNextPageAlerts: jest.fn(() => true),
|
||||
onJumpToEvent: jest.fn((process) => process),
|
||||
onShowAlertDetails: jest.fn((alertId) => alertId),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockedContext = createAppRootMockRenderer();
|
||||
props.onJumpToEvent.mockReset();
|
||||
props.onShowAlertDetails.mockReset();
|
||||
props.fetchNextPageAlerts.mockReset();
|
||||
mockUseDateFormat.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS');
|
||||
});
|
||||
|
||||
describe('When SessionViewDetailPanel is mounted', () => {
|
||||
it('shows process detail by default', async () => {
|
||||
renderResult = mockedContext.render(<SessionViewDetailPanel {...props} />);
|
||||
expect(renderResult.queryByText('8e4daeb2-4a4e-56c4-980e-f0dcfdbc3726')).toBeVisible();
|
||||
});
|
||||
|
||||
it('should should default state with selectedProcess null', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<SessionViewDetailPanel {...props} selectedProcess={null} />
|
||||
);
|
||||
expect(renderResult.queryAllByText('entity_id').length).toBe(5);
|
||||
});
|
||||
|
||||
it('can switch tabs to show host details', async () => {
|
||||
renderResult = mockedContext.render(<SessionViewDetailPanel {...props} />);
|
||||
await userEvent.click(renderResult.queryByText('Metadata')!);
|
||||
expect(renderResult.queryByText('hostname')).toBeVisible();
|
||||
expect(renderResult.queryAllByText('james-fleet-714-2')).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('can switch tabs to show alert details', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<SessionViewDetailPanel {...props} alerts={mockAlerts} alertsCount={mockAlerts.length} />
|
||||
);
|
||||
await userEvent.click(renderResult.queryByText('Alerts')!);
|
||||
expect(renderResult.queryByText('List view')).toBeVisible();
|
||||
});
|
||||
it('alert tab disabled when no alerts', async () => {
|
||||
renderResult = mockedContext.render(<SessionViewDetailPanel {...props} />);
|
||||
await userEvent.click(renderResult.queryByText('Alerts')!);
|
||||
expect(renderResult.queryByText('List view')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,144 +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, { useState, useMemo, useCallback } from 'react';
|
||||
import { EuiTabs, EuiTab, EuiNotificationBadge } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiTabProps } from '../../types';
|
||||
import type { Process, ProcessEvent } from '../../../common';
|
||||
import { getSelectedTabContent } from './helpers';
|
||||
import { DetailPanelProcessTab } from '../detail_panel_process_tab';
|
||||
import { DetailPanelMetadataTab } from '../detail_panel_metadata_tab';
|
||||
import { useStyles } from './styles';
|
||||
import { DetailPanelAlertTab } from '../detail_panel_alert_tab';
|
||||
import { ALERT_COUNT_THRESHOLD } from '../../../common/constants';
|
||||
|
||||
interface SessionViewDetailPanelDeps {
|
||||
index: string;
|
||||
selectedProcess: Process | null;
|
||||
alerts?: ProcessEvent[];
|
||||
alertsCount: number;
|
||||
isFetchingAlerts: boolean;
|
||||
hasNextPageAlerts?: boolean;
|
||||
fetchNextPageAlerts: () => void;
|
||||
investigatedAlertId?: string;
|
||||
onJumpToEvent: (event: ProcessEvent) => void;
|
||||
onShowAlertDetails: (alertId: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detail panel in the session view.
|
||||
*/
|
||||
export const SessionViewDetailPanel = ({
|
||||
index,
|
||||
alerts,
|
||||
alertsCount,
|
||||
isFetchingAlerts,
|
||||
hasNextPageAlerts,
|
||||
fetchNextPageAlerts,
|
||||
selectedProcess,
|
||||
investigatedAlertId,
|
||||
onJumpToEvent,
|
||||
onShowAlertDetails,
|
||||
}: SessionViewDetailPanelDeps) => {
|
||||
const [selectedTabId, setSelectedTabId] = useState('process');
|
||||
|
||||
const alertsCountStr = useMemo(() => {
|
||||
return alertsCount >= ALERT_COUNT_THRESHOLD ? ALERT_COUNT_THRESHOLD + '+' : alertsCount + '';
|
||||
}, [alertsCount]);
|
||||
|
||||
const tabs: EuiTabProps[] = useMemo(() => {
|
||||
const hasAlerts = !!alerts?.length;
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'process',
|
||||
name: i18n.translate('xpack.sessionView.detailsPanel.process', {
|
||||
defaultMessage: 'Process',
|
||||
}),
|
||||
content: <DetailPanelProcessTab index={index} selectedProcess={selectedProcess} />,
|
||||
},
|
||||
{
|
||||
id: 'metadata',
|
||||
name: i18n.translate('xpack.sessionView.detailsPanel.metadata', {
|
||||
defaultMessage: 'Metadata',
|
||||
}),
|
||||
content: (
|
||||
<DetailPanelMetadataTab
|
||||
processHost={selectedProcess?.getDetails()?.host}
|
||||
processContainer={selectedProcess?.getDetails()?.container}
|
||||
processOrchestrator={selectedProcess?.getDetails()?.orchestrator}
|
||||
processCloud={selectedProcess?.getDetails()?.cloud}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'alerts',
|
||||
name: i18n.translate('xpack.sessionView.detailsPanel.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
append: hasAlerts && (
|
||||
<EuiNotificationBadge className="eui-alignCenter" size="m">
|
||||
{alertsCountStr}
|
||||
</EuiNotificationBadge>
|
||||
),
|
||||
content: alerts && (
|
||||
<DetailPanelAlertTab
|
||||
alerts={alerts}
|
||||
isFetchingAlerts={isFetchingAlerts}
|
||||
hasNextPageAlerts={hasNextPageAlerts}
|
||||
fetchNextPageAlerts={fetchNextPageAlerts}
|
||||
onJumpToEvent={onJumpToEvent}
|
||||
onShowAlertDetails={onShowAlertDetails}
|
||||
investigatedAlertId={investigatedAlertId}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [
|
||||
alerts,
|
||||
alertsCountStr,
|
||||
fetchNextPageAlerts,
|
||||
hasNextPageAlerts,
|
||||
isFetchingAlerts,
|
||||
selectedProcess,
|
||||
onJumpToEvent,
|
||||
onShowAlertDetails,
|
||||
investigatedAlertId,
|
||||
index,
|
||||
]);
|
||||
|
||||
const onSelectedTabChanged = useCallback((id: string) => {
|
||||
setSelectedTabId(id);
|
||||
}, []);
|
||||
|
||||
const tabContent = useMemo(
|
||||
() => getSelectedTabContent(tabs, selectedTabId),
|
||||
[tabs, selectedTabId]
|
||||
);
|
||||
|
||||
const styles = useStyles();
|
||||
|
||||
return (
|
||||
<div css={styles.detailsPanel}>
|
||||
<EuiTabs size="l" expand>
|
||||
{tabs.map((tab, i) => (
|
||||
<EuiTab
|
||||
key={`${tab}-${i}`}
|
||||
onClick={() => onSelectedTabChanged(tab.id)}
|
||||
isSelected={tab.id === selectedTabId}
|
||||
disabled={tab.disabled}
|
||||
prepend={tab.prepend}
|
||||
append={tab.append}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
{tabContent}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,28 +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 { useMemo } from 'react';
|
||||
import { useEuiTheme } from '@elastic/eui';
|
||||
import { CSSObject } from '@emotion/react';
|
||||
|
||||
export const useStyles = () => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const cached = useMemo(() => {
|
||||
const { border } = euiTheme;
|
||||
|
||||
const detailsPanel: CSSObject = {
|
||||
borderLeft: border.thin,
|
||||
height: '100%',
|
||||
};
|
||||
|
||||
return {
|
||||
detailsPanel,
|
||||
};
|
||||
}, [euiTheme]);
|
||||
|
||||
return cached;
|
||||
};
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '../../../common/mocks/constants/session_view_process.mock';
|
||||
import { sessionViewIOEventsMock } from '../../../common/mocks/responses/session_view_io_events.mock';
|
||||
import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
||||
import { TTYPlayerDeps, TTYPlayer } from '.';
|
||||
import { TTYPlayer, TTYPlayerDeps } from '.';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
describe('TTYPlayer component', () => {
|
||||
|
@ -40,7 +40,6 @@ describe('TTYPlayer component', () => {
|
|||
sessionStartTime: TEST_SESSION_START_TIME,
|
||||
onClose: jest.fn(),
|
||||
onJumpToEvent: jest.fn(),
|
||||
isFullscreen: false,
|
||||
trackEvent: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import React, { useRef, useState, useCallback, useMemo, useEffect } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiButton,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiButtonIcon,
|
||||
EuiButton,
|
||||
EuiPanel,
|
||||
EuiThemeProvider,
|
||||
} from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
|
@ -22,27 +22,30 @@ import { TTYSearchBar } from '../tty_search_bar';
|
|||
import { TTYTextSizer } from '../tty_text_sizer';
|
||||
import { useStyles } from './styles';
|
||||
import {
|
||||
DEFAULT_TTY_ROWS,
|
||||
DEFAULT_TTY_COLS,
|
||||
DEFAULT_TTY_FONT_SIZE,
|
||||
DEFAULT_TTY_ROWS,
|
||||
POLICIES_PAGE_PATH,
|
||||
SECURITY_APP_ID,
|
||||
} from '../../../common/constants';
|
||||
import { SessionViewTelemetryKey } from '../../types';
|
||||
import { useFetchIOEvents, useIOLines, useXtermPlayer } from './hooks';
|
||||
import { TTYPlayerControls } from '../tty_player_controls';
|
||||
import { TOGGLE_TTY_PLAYER, DETAIL_PANEL } from '../session_view/translations';
|
||||
import { DETAIL_PANEL, TOGGLE_TTY_PLAYER } from '../session_view/translations';
|
||||
|
||||
export interface TTYPlayerDeps {
|
||||
index: string;
|
||||
sessionEntityId: string;
|
||||
sessionStartTime: string;
|
||||
show: boolean;
|
||||
|
||||
onClose(): void;
|
||||
isFullscreen: boolean;
|
||||
|
||||
onJumpToEvent(event: ProcessEvent): void;
|
||||
|
||||
autoSeekToEntityId?: string;
|
||||
canReadPolicyManagement?: boolean;
|
||||
|
||||
trackEvent(name: SessionViewTelemetryKey): void;
|
||||
}
|
||||
|
||||
|
@ -52,7 +55,6 @@ export const TTYPlayer = ({
|
|||
sessionStartTime,
|
||||
show,
|
||||
onClose,
|
||||
isFullscreen,
|
||||
onJumpToEvent,
|
||||
autoSeekToEntityId,
|
||||
canReadPolicyManagement,
|
||||
|
@ -241,7 +243,6 @@ export const TTYPlayer = ({
|
|||
containerHeight={containerHeight}
|
||||
fontSize={fontSize}
|
||||
onFontSizeChanged={setFontSize}
|
||||
isFullscreen={isFullscreen}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -10,8 +10,6 @@ import { AppContextTestRender, createAppRootMockRenderer } from '../../test';
|
|||
import { DEFAULT_TTY_FONT_SIZE } from '../../../common/constants';
|
||||
import { TTYTextSizer, TTYTextSizerDeps } from '.';
|
||||
|
||||
const FULL_SCREEN_FONT_SIZE = 12;
|
||||
|
||||
describe('TTYTextSizer component', () => {
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
|
@ -22,7 +20,6 @@ describe('TTYTextSizer component', () => {
|
|||
mockedContext = createAppRootMockRenderer();
|
||||
|
||||
props = {
|
||||
isFullscreen: false,
|
||||
tty: {
|
||||
rows: 24,
|
||||
columns: 80,
|
||||
|
@ -76,19 +73,4 @@ describe('TTYTextSizer component', () => {
|
|||
expect(props.onFontSizeChanged).toHaveBeenCalledTimes(1);
|
||||
expect(props.onFontSizeChanged).toHaveBeenCalledWith(DEFAULT_TTY_FONT_SIZE - 1);
|
||||
});
|
||||
|
||||
it('emits a font size to fit to full screen, when isFullscreen = true', async () => {
|
||||
renderResult = mockedContext.render(
|
||||
<TTYTextSizer {...props} isFullscreen containerHeight={400} />
|
||||
);
|
||||
|
||||
const zoomFitBtn = renderResult.queryByTestId('sessionView:TTYZoomFit');
|
||||
|
||||
if (zoomFitBtn) {
|
||||
await userEvent.click(zoomFitBtn);
|
||||
}
|
||||
|
||||
expect(props.onFontSizeChanged).toHaveBeenCalledTimes(1);
|
||||
expect(props.onFontSizeChanged).toHaveBeenCalledWith(FULL_SCREEN_FONT_SIZE);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,14 +14,14 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import type { Teletype } from '../../../common';
|
||||
import { DEFAULT_TTY_FONT_SIZE } from '../../../common/constants';
|
||||
import { ZOOM_IN, ZOOM_FIT, ZOOM_OUT } from './translations';
|
||||
import { ZOOM_FIT, ZOOM_IN, ZOOM_OUT } from './translations';
|
||||
import { useStyles } from './styles';
|
||||
|
||||
export interface TTYTextSizerDeps {
|
||||
tty?: Teletype;
|
||||
containerHeight: number;
|
||||
fontSize: number;
|
||||
isFullscreen: boolean;
|
||||
|
||||
onFontSizeChanged(newSize: number): void;
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,6 @@ export const TTYTextSizer = ({
|
|||
tty,
|
||||
containerHeight,
|
||||
fontSize,
|
||||
isFullscreen,
|
||||
onFontSizeChanged,
|
||||
}: TTYTextSizerDeps) => {
|
||||
const styles = useStyles();
|
||||
|
@ -65,7 +64,7 @@ export const TTYTextSizer = ({
|
|||
onFontSizeChanged(newSize);
|
||||
}
|
||||
}
|
||||
}, [isFullscreen, containerHeight, fit, fontSize, onFontSizeChanged, tty?.rows]);
|
||||
}, [containerHeight, fit, fontSize, onFontSizeChanged, tty?.rows]);
|
||||
|
||||
const onToggleFit = useCallback(() => {
|
||||
const newValue = !fit;
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { ReactNode } from 'react';
|
||||
import type {
|
||||
UsageCollectionSetup,
|
||||
UsageCollectionStart,
|
||||
|
@ -13,6 +12,7 @@ import type { Process } from '../common';
|
|||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface SessionViewPluginSetup {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface SessionViewPluginStart {}
|
||||
|
||||
|
@ -84,36 +84,27 @@ export interface SessionViewDeps {
|
|||
) => void;
|
||||
canReadPolicyManagement?: boolean;
|
||||
/**
|
||||
* Allows to open the detailed panel outside of the SessionView component. This is necessary when the session view is rendered in the
|
||||
* Allows to open the detailed panel outside of the SessionView component. This is necessary as the session view is rendered in the
|
||||
* expandable flyout, where the tree and the detailed panel are separated and need to communicate with each other.
|
||||
*/
|
||||
openDetailsInExpandableFlyout?: (selectedProcess: Process | null) => void;
|
||||
openDetails: (selectedProcess: Process | null) => void;
|
||||
/**
|
||||
* Allows to close the detailed panel outside of the SessionView component. This is necessary when the session view is rendered in the
|
||||
* expandable flyout: when the user clicks on the TTY output button we need to close the detailed panel.
|
||||
*/
|
||||
closeDetailsInExpandableFlyout?: () => void;
|
||||
closeDetails: () => void;
|
||||
/**
|
||||
* Allows to reset the view from an external component. This is necessary when the session view is rendered in the
|
||||
* Allows to reset the view from an external component. This is necessary as the session view is rendered in the
|
||||
* expandable flyout, where the tree and the detailed panels are separated and need to communicate with each other.
|
||||
*/
|
||||
resetJumpToEntityId?: string;
|
||||
/**
|
||||
* Allows to reset the view from an external component. This is necessary when the session view is rendered in the
|
||||
* Allows to reset the view from an external component. This is necessary as the session view is rendered in the
|
||||
* expandable flyout, where the tree and the detailed panels are separated and need to communicate with each other.
|
||||
*/
|
||||
resetJumpToCursor?: string;
|
||||
}
|
||||
|
||||
export interface EuiTabProps {
|
||||
id: string;
|
||||
name: string;
|
||||
content: ReactNode;
|
||||
disabled?: boolean;
|
||||
append?: ReactNode;
|
||||
prepend?: ReactNode;
|
||||
}
|
||||
|
||||
export interface DetailPanelProcess {
|
||||
id: string;
|
||||
start: string;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue