Skip to content

Commit

Permalink
[Security Solution] User Alert Assignment feature: Assignee column in…
Browse files Browse the repository at this point in the history
… Alerts Table not displaying after upgrading to 8.12 (elastic#173695) (elastic#174370)

## Summary

Addresses elastic#173695

This PR fixes the issue with the missing "Assignees" column specs in the
alert's table after user upgrades to `v8.12+`.

This happens because we store table mode specs in the local storage and
do not update it after the upgrade. With this changes we add missing
column specs if needed.

To easily reproduce the bug (without actual upgrades):
1. Run latest main
2. Open web inspector > local storage data
3. Find and update next items: `securityDataTable`,
`detection-engine-alert-table-securitySolution-alerts-page-gridView` and
`detection-engine-alert-table-securitySolution-rule-details-gridView`.
You need to remove "Assignees" column specs which look like this:
`{"columnHeaderType":"not-filtered","displayAsText":"Assignees","id":"kibana.alert.workflow_assignee_ids","initialWidth":190,"schema":"string"}`.
Also, for the last to items remove
`"kibana.alert.workflow_assignee_ids"` from `visibleColumns`.

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
e40pud and kibanamachine authored Jan 22, 2024
1 parent 7a85e8d commit 586c358
Show file tree
Hide file tree
Showing 6 changed files with 376 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,11 @@ const mockTimelineModelColumns: TimelineModel['columns'] = [
id: 'user.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 180,
},
];
export const mockTimelineModel: TimelineModel = {
activeTab: TimelineTabs.query,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,78 @@
*/

import type { ExistsFilter, Filter } from '@kbn/es-query';
import { tableDefaults } from '@kbn/securitysolution-data-table';
import { createLicenseServiceMock } from '../../../../common/license/mocks';
import {
buildAlertAssigneesFilter,
buildAlertsFilter,
buildAlertStatusesFilter,
buildAlertStatusFilter,
buildThreatMatchFilter,
getAlertsDefaultModel,
getAlertsPreviewDefaultModel,
} from './default_config';

jest.mock('./actions');

const basicBaseColumns = [
{
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
initialWidth: 105,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
initialWidth: 100,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
initialWidth: 450,
},
{ columnHeaderType: 'not-filtered', id: 'host.name' },
{ columnHeaderType: 'not-filtered', id: 'user.name' },
{ columnHeaderType: 'not-filtered', id: 'process.name' },
{ columnHeaderType: 'not-filtered', id: 'file.name' },
{ columnHeaderType: 'not-filtered', id: 'source.ip' },
{ columnHeaderType: 'not-filtered', id: 'destination.ip' },
];

const platinumBaseColumns = [
{
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
initialWidth: 105,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
initialWidth: 100,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
initialWidth: 450,
},
{ columnHeaderType: 'not-filtered', id: 'host.name' },
{ columnHeaderType: 'not-filtered', id: 'host.risk.calculated_level' },
{ columnHeaderType: 'not-filtered', id: 'user.name' },
{ columnHeaderType: 'not-filtered', id: 'user.risk.calculated_level' },
{ columnHeaderType: 'not-filtered', id: 'kibana.alert.host.criticality_level' },
{ columnHeaderType: 'not-filtered', id: 'kibana.alert.user.criticality_level' },
{ columnHeaderType: 'not-filtered', id: 'process.name' },
{ columnHeaderType: 'not-filtered', id: 'file.name' },
{ columnHeaderType: 'not-filtered', id: 'source.ip' },
{ columnHeaderType: 'not-filtered', id: 'destination.ip' },
];

describe('alerts default_config', () => {
describe('buildAlertsRuleIdFilter', () => {
test('given a rule id this will return an array with a single filter', () => {
Expand Down Expand Up @@ -200,6 +262,122 @@ describe('alerts default_config', () => {
});
});

describe('getAlertsDefaultModel', () => {
test('returns correct model for Basic license', () => {
const licenseServiceMock = createLicenseServiceMock();
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
const model = getAlertsDefaultModel(licenseServiceMock);

const expected = {
...tableDefaults,
showCheckboxes: true,
columns: [
{ columnHeaderType: 'not-filtered', id: '@timestamp', initialWidth: 200 },
{
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
initialWidth: 180,
linkField: 'kibana.alert.rule.uuid',
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Assignees',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 190,
},
...basicBaseColumns,
],
};
expect(model).toEqual(expected);
});

test('returns correct model for Platinum license', () => {
const licenseServiceMock = createLicenseServiceMock();
const model = getAlertsDefaultModel(licenseServiceMock);

const expected = {
...tableDefaults,
showCheckboxes: true,
columns: [
{ columnHeaderType: 'not-filtered', id: '@timestamp', initialWidth: 200 },
{
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
initialWidth: 180,
linkField: 'kibana.alert.rule.uuid',
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Assignees',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 190,
},
...platinumBaseColumns,
],
};
expect(model).toEqual(expected);
});
});

describe('getAlertsPreviewDefaultModel', () => {
test('returns correct model for Basic license', () => {
const licenseServiceMock = createLicenseServiceMock();
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
const model = getAlertsPreviewDefaultModel(licenseServiceMock);

const expected = {
...tableDefaults,
showCheckboxes: false,
defaultColumns: [
{ columnHeaderType: 'not-filtered', id: 'kibana.alert.original_time', initialWidth: 200 },
...basicBaseColumns,
],
columns: [
{ columnHeaderType: 'not-filtered', id: 'kibana.alert.original_time', initialWidth: 200 },
...basicBaseColumns,
],
sort: [
{
columnId: 'kibana.alert.original_time',
columnType: 'date',
esTypes: ['date'],
sortDirection: 'desc',
},
],
};
expect(model).toEqual(expected);
});

test('returns correct model for Platinum license', () => {
const licenseServiceMock = createLicenseServiceMock();
const model = getAlertsPreviewDefaultModel(licenseServiceMock);

const expected = {
...tableDefaults,
showCheckboxes: false,
defaultColumns: [
{ columnHeaderType: 'not-filtered', id: 'kibana.alert.original_time', initialWidth: 200 },
...platinumBaseColumns,
],
columns: [
{ columnHeaderType: 'not-filtered', id: 'kibana.alert.original_time', initialWidth: 200 },
...platinumBaseColumns,
],
sort: [
{
columnId: 'kibana.alert.original_time',
columnType: 'date',
esTypes: ['date'],
sortDirection: 'desc',
},
],
};
expect(model).toEqual(expected);
});
});

// TODO: move these tests to ../timelines/components/timeline/body/events/event_column_view.tsx
// describe.skip('getAlertActions', () => {
// let setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const getAlertsDefaultModel = (license?: LicenseService): SubsetDataTable

export const getAlertsPreviewDefaultModel = (license?: LicenseService): SubsetDataTableModel => ({
...getAlertsDefaultModel(license),
columns: getColumns(license),
columns: getRulePreviewColumns(license),
defaultColumns: getRulePreviewColumns(license),
sort: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,20 @@ import {
DEFAULT_TABLE_DATE_COLUMN_MIN_WIDTH,
} from './translations';

export const assigneesColumn: ColumnHeaderOptions = {
columnHeaderType: defaultColumnHeaderType,
displayAsText: i18n.ALERTS_HEADERS_ASSIGNEES,
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
};

const getBaseColumns = (
license?: LicenseService
): Array<
Pick<EuiDataGridColumn, 'display' | 'displayAsText' | 'id' | 'initialWidth'> & ColumnHeaderOptions
> => {
const isPlatinumPlus = license?.isPlatinumPlus?.() ?? false;
return [
{
columnHeaderType: defaultColumnHeaderType,
displayAsText: i18n.ALERTS_HEADERS_ASSIGNEES,
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH,
},
{
columnHeaderType: defaultColumnHeaderType,
displayAsText: i18n.ALERTS_HEADERS_SEVERITY,
Expand Down Expand Up @@ -131,6 +132,7 @@ export const getColumns = (
initialWidth: DEFAULT_COLUMN_MIN_WIDTH,
linkField: 'kibana.alert.rule.uuid',
},
assigneesColumn,
...getBaseColumns(license),
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
addTableInStorage,
migrateAlertTableStateToTriggerActionsState,
migrateTriggerActionsVisibleColumnsAlertTable88xTo89,
addAssigneesSpecsToSecurityDataTableIfNeeded,
} from '.';

import { mockDataTableModel, createSecuritySolutionStorageMock } from '../../../common/mock';
Expand Down Expand Up @@ -566,6 +567,12 @@ describe('SiemLocalStorage', () => {
{ columnHeaderType: 'not-filtered', id: 'file.name' },
{ columnHeaderType: 'not-filtered', id: 'source.ip' },
{ columnHeaderType: 'not-filtered', id: 'destination.ip' },
{
columnHeaderType: 'not-filtered',
displayAsText: 'Assignees',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 190,
},
],
defaultColumns: [
{ columnHeaderType: 'not-filtered', id: '@timestamp', initialWidth: 200 },
Expand Down Expand Up @@ -600,6 +607,12 @@ describe('SiemLocalStorage', () => {
{ columnHeaderType: 'not-filtered', id: 'file.name' },
{ columnHeaderType: 'not-filtered', id: 'source.ip' },
{ columnHeaderType: 'not-filtered', id: 'destination.ip' },
{
columnHeaderType: 'not-filtered',
displayAsText: 'Assignees',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 190,
},
],
dataViewId: 'security-solution-default',
deletedEventIds: [],
Expand Down Expand Up @@ -1527,4 +1540,88 @@ describe('SiemLocalStorage', () => {
).toBeNull();
});
});

describe('addMissingColumnsToSecurityDataTable', () => {
it('should add missing "Assignees" column specs', () => {
const dataTableState: DataTableState['dataTable']['tableById'] = {
'alerts-page': {
columns: [{ columnHeaderType: 'not-filtered', id: '@timestamp', initialWidth: 200 }],
defaultColumns: [
{ columnHeaderType: 'not-filtered', id: 'host.name' },
{ columnHeaderType: 'not-filtered', id: 'user.name' },
{ columnHeaderType: 'not-filtered', id: 'process.name' },
],
isLoading: false,
queryFields: [],
dataViewId: 'security-solution-default',
deletedEventIds: [],
expandedDetail: {},
filters: [],
indexNames: ['.alerts-security.alerts-default'],
isSelectAllChecked: false,
itemsPerPage: 25,
itemsPerPageOptions: [10, 25, 50, 100],
loadingEventIds: [],
showCheckboxes: true,
sort: [
{
columnId: '@timestamp',
columnType: 'date',
esTypes: ['date'],
sortDirection: 'desc',
},
],
graphEventId: undefined,
selectedEventIds: {},
sessionViewConfig: null,
selectAll: false,
id: 'alerts-page',
title: '',
initialized: true,
updated: 1665943295913,
totalCount: 0,
viewMode: VIEW_SELECTION.gridView,
additionalFilters: {
showBuildingBlockAlerts: false,
showOnlyThreatIndicatorAlerts: false,
},
},
};
storage.set(LOCAL_STORAGE_TABLE_KEY, dataTableState);
migrateAlertTableStateToTriggerActionsState(storage, dataTableState);
migrateTriggerActionsVisibleColumnsAlertTable88xTo89(storage);

const expectedColumns = [
{ columnHeaderType: 'not-filtered', id: '@timestamp', initialWidth: 200 },
{
columnHeaderType: 'not-filtered',
displayAsText: 'Assignees',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 190,
},
];
const expectedDefaultColumns = [
{ columnHeaderType: 'not-filtered', id: 'host.name' },
{ columnHeaderType: 'not-filtered', id: 'user.name' },
{ columnHeaderType: 'not-filtered', id: 'process.name' },
{
columnHeaderType: 'not-filtered',
displayAsText: 'Assignees',
id: 'kibana.alert.workflow_assignee_ids',
initialWidth: 190,
},
];

addAssigneesSpecsToSecurityDataTableIfNeeded(storage, dataTableState);

expect(dataTableState['alerts-page'].columns).toMatchObject(expectedColumns);
expect(dataTableState['alerts-page'].defaultColumns).toMatchObject(expectedDefaultColumns);

const tableKey = 'detection-engine-alert-table-securitySolution-alerts-page-gridView';
expect(storage.get(tableKey)).toMatchObject({
columns: expectedColumns,
visibleColumns: expectedColumns.map((col) => col.id),
});
});
});
});
Loading

0 comments on commit 586c358

Please sign in to comment.