Sessions tab improvements (#131583)

* session tab query modified query all events, not just entry leaders. solves a few problems wrt to query ability. default columns modified and display names provided for each

* snapshot updated

* readded test

* Default sort set to process.entry_leader.start desc

* sessions tab timeline id changed to cache bust localstorage for table column configs

* missed a couple spots for session tab timeline id update

Co-authored-by: mitodrummer <karlgodard@elastic.co>
This commit is contained in:
Karl Godard 2022-05-06 09:17:16 -07:00 committed by GitHub
parent 37a27384a5
commit 743cce0a65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 99 additions and 82 deletions

View file

@ -318,7 +318,7 @@ export enum TimelineId {
usersPageExternalAlerts = 'users-page-external-alerts',
hostsPageEvents = 'hosts-page-events',
hostsPageExternalAlerts = 'hosts-page-external-alerts',
hostsPageSessions = 'hosts-page-sessions',
hostsPageSessions = 'hosts-page-sessions-v2', // the v2 is to cache bust localstorage settings as default columns were reworked.
detectionsRulesDetailsPage = 'detections-rules-details-page',
detectionsPage = 'detections-page',
networkPageExternalAlerts = 'network-page-external-alerts',

View file

@ -70,34 +70,28 @@ exports[`SessionsView renders correctly against snapshot 1`] = `
<div
data-test-subj="security_solution:sessions_viewer:sessions_view:timelineId"
>
hosts-page-sessions
hosts-page-sessions-v2
</div>
<div>
process.start
Started
</div>
<div>
process.end
Executable
</div>
<div>
process.executable
User
</div>
<div>
user.name
Interactive
</div>
<div>
process.interactive
Hostname
</div>
<div>
process.pid
Type
</div>
<div>
host.hostname
</div>
<div>
process.entry_leader.entry_meta.type
</div>
<div>
process.entry_leader.entry_meta.source.ip
Source IP
</div>
</div>
</div>

View file

@ -1,25 +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 { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import { getEmptyValue } from '../empty_value';
import { MAPPED_PROCESS_END_COLUMN } from './default_headers';
const hasEcsDataEndEventAction = (ecsData: CellValueElementProps['ecsData']) => {
return ecsData?.event?.action?.includes('end');
};
export const CellRenderer: React.FC<CellValueElementProps> = (props: CellValueElementProps) => {
// We only want to render process.end for event.actions of type 'end'
if (props.columnId === MAPPED_PROCESS_END_COLUMN && !hasEcsDataEndEventAction(props.ecsData)) {
return <>{getEmptyValue()}</>;
}
return <DefaultCellRenderer {...props} />;
};

View file

@ -10,50 +10,52 @@ import { defaultColumnHeaderType } from '../../../timelines/components/timeline/
import { DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
import { SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
// Using @timestamp as an way of getting the end time of the process. (Currently endpoint doesn't populate process.end)
// @timestamp of an event.action with value of "end" is what we consider that to be the end time of the process
// Current action are: 'start', 'exec', 'end', so we might have up to three events per process.
export const MAPPED_PROCESS_END_COLUMN = '@timestamp';
import {
COLUMN_SESSION_START,
COLUMN_EXECUTABLE,
COLUMN_ENTRY_USER,
COLUMN_INTERACTIVE,
COLUMN_HOST_NAME,
COLUMN_ENTRY_TYPE,
COLUMN_ENTRY_IP,
} from './translations';
export const sessionsHeaders: ColumnHeaderOptions[] = [
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.start',
id: 'process.entry_leader.start',
initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
display: COLUMN_SESSION_START,
},
{
columnHeaderType: defaultColumnHeaderType,
id: MAPPED_PROCESS_END_COLUMN,
display: 'process.end',
id: 'process.entry_leader.executable',
display: COLUMN_EXECUTABLE,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.executable',
id: 'process.entry_leader.user.name',
display: COLUMN_ENTRY_USER,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'user.name',
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.interactive',
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.pid',
id: 'process.entry_leader.interactive',
display: COLUMN_INTERACTIVE,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'host.hostname',
display: COLUMN_HOST_NAME,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.entry_leader.entry_meta.type',
display: COLUMN_ENTRY_TYPE,
},
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.entry_leader.entry_meta.source.ip',
columnHeaderType: defaultColumnHeaderType,
display: COLUMN_ENTRY_IP,
},
];
@ -62,4 +64,11 @@ export const sessionsDefaultModel: SubsetTimelineModel = {
columns: sessionsHeaders,
defaultColumns: sessionsHeaders,
excludedRowRendererIds: Object.values(RowRendererId),
sort: [
{
columnId: 'process.entry_leader.start',
columnType: 'date',
sortDirection: 'desc',
},
],
};

View file

@ -109,10 +109,11 @@ describe('SessionsView', () => {
expect(wrapper.getByTestId(`${TEST_PREFIX}:startDate`)).toHaveTextContent(startDate);
expect(wrapper.getByTestId(`${TEST_PREFIX}:endDate`)).toHaveTextContent(endDate);
expect(wrapper.getByTestId(`${TEST_PREFIX}:timelineId`)).toHaveTextContent(
'hosts-page-sessions'
'hosts-page-sessions-v2'
);
});
});
it('passes in the right filters to TGrid', async () => {
render(
<TestProviders>

View file

@ -12,7 +12,7 @@ import { ESBoolQuery } from '../../../../common/typed_json';
import { StatefulEventsViewer } from '../events_viewer';
import { sessionsDefaultModel } from './default_headers';
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
import { CellRenderer } from './cell_renderer';
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
import * as i18n from './translations';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
@ -24,15 +24,8 @@ export const defaultSessionsFilter: Required<Pick<Filter, 'meta' | 'query'>> = {
bool: {
filter: [
{
bool: {
should: [
{
match: {
'process.entry_leader.same_as_process': true,
},
},
],
minimum_should_match: 1,
exists: {
field: 'process.entry_leader.entity_id', // to exclude any records which have no entry_leader.entity_id
},
},
],
@ -41,10 +34,10 @@ export const defaultSessionsFilter: Required<Pick<Filter, 'meta' | 'query'>> = {
meta: {
alias: null,
disabled: false,
key: 'process.entry_leader.same_as_process',
key: 'process.entry_leader.entity_id',
negate: false,
params: {},
type: 'boolean',
type: 'string',
},
};
@ -95,7 +88,7 @@ const SessionsViewComponent: React.FC<SessionsComponentsProps> = ({
entityType={entityType}
id={timelineId}
leadingControlColumns={leadingControlColumns}
renderCellValue={CellRenderer}
renderCellValue={DefaultCellRenderer}
rowRenderers={defaultRowRenderers}
scopeId={SourcererScopeName.default}
start={startDate}

View file

@ -20,3 +20,52 @@ export const SINGLE_COUNT_OF_SESSIONS = i18n.translate(
defaultMessage: 'session',
}
);
export const COLUMN_SESSION_START = i18n.translate(
'xpack.securitySolution.sessionsView.columnSessionStart',
{
defaultMessage: 'Started',
}
);
export const COLUMN_EXECUTABLE = i18n.translate(
'xpack.securitySolution.sessionsView.columnExecutable',
{
defaultMessage: 'Executable',
}
);
export const COLUMN_ENTRY_USER = i18n.translate(
'xpack.securitySolution.sessionsView.columnEntryUser',
{
defaultMessage: 'User',
}
);
export const COLUMN_INTERACTIVE = i18n.translate(
'xpack.securitySolution.sessionsView.columnInteractive',
{
defaultMessage: 'Interactive',
}
);
export const COLUMN_HOST_NAME = i18n.translate(
'xpack.securitySolution.sessionsView.columnHostName',
{
defaultMessage: 'Hostname',
}
);
export const COLUMN_ENTRY_TYPE = i18n.translate(
'xpack.securitySolution.sessionsView.columnEntryType',
{
defaultMessage: 'Type',
}
);
export const COLUMN_ENTRY_IP = i18n.translate(
'xpack.securitySolution.sessionsView.columnEntrySourceIp',
{
defaultMessage: 'Source IP',
}
);

View file

@ -314,7 +314,7 @@ export enum TimelineId {
usersPageExternalAlerts = 'users-page-external-alerts',
hostsPageEvents = 'hosts-page-events',
hostsPageExternalAlerts = 'hosts-page-external-alerts',
hostsPageSessions = 'hosts-page-sessions',
hostsPageSessions = 'hosts-page-sessions-v2',
detectionsRulesDetailsPage = 'detections-rules-details-page',
detectionsPage = 'detections-page',
networkPageExternalAlerts = 'network-page-external-alerts',

View file

@ -46,7 +46,7 @@ export enum TimelineId {
usersPageExternalAlerts = 'users-page-external-alerts',
hostsPageEvents = 'hosts-page-events',
hostsPageExternalAlerts = 'hosts-page-external-alerts',
hostsPageSessions = 'hosts-page-sessions',
hostsPageSessions = 'hosts-page-sessions-v2',
detectionsRulesDetailsPage = 'detections-rules-details-page',
detectionsPage = 'detections-page',
networkPageExternalAlerts = 'network-page-external-alerts',

View file

@ -209,17 +209,13 @@ const timelineSessionsSearchStrategy = <T extends TimelineFactoryQueryTypes>({
};
const collapse = {
field: 'process.entity_id',
inner_hits: {
name: 'last_event',
size: 1,
sort: [{ '@timestamp': 'desc' }],
},
field: 'process.entry_leader.entity_id',
};
const aggs = {
total: {
cardinality: {
field: 'process.entity_id',
field: 'process.entry_leader.entity_id',
},
},
};