[Security Solution][Fix] Empty Alert Table when upgrading from 8.8.x -> 8.9 (#162063)

## Summary

When users upgrade from `8.8.x` -> `8.9` version, users observe empty
table as shown below.


![image](20549edb-07b9-4124-a0ac-7515cf0e2796)


Below are steps to reproduce this issue and test it:

1. Boot Kibana@v8.8.1
2. Clear Local storage.
3. Go to Security -> Alerts
4. Add Columns `_id` or any other column 
5. Upgrade to `8.9`
6. The table will empty as shown in above screenshot.


## Fix

This fix saperates out the migraton from 8.7 -> 8.8 and add a new
migration for upgrading from 8.8 -> 8.9

`migrateAlertTableStateToTriggerActionsState` migrates table from `v8.7
-> v8.8`,

`migrateTriggerActionsVisibleColumnsAlertTable88xTo89` migrates from
`v8.8.x` -> `v8.9`

Combining both of them may lead to issues when users are migrating from
`v8.7` -> `v8.9` or `v8.8` -> `v8.9`
This commit is contained in:
Jatin Kathuria 2023-07-18 01:57:12 -07:00 committed by GitHub
parent 303575544a
commit 0516caed1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 798 additions and 26 deletions

View file

@ -15,6 +15,7 @@ import {
getAllDataTablesInStorage,
addTableInStorage,
migrateAlertTableStateToTriggerActionsState,
migrateTriggerActionsVisibleColumnsAlertTable88xTo89,
} from '.';
import { mockDataTableModel, createSecuritySolutionStorageMock } from '../../../common/mock';
@ -22,6 +23,7 @@ import { useKibana } from '../../../common/lib/kibana';
import { VIEW_SELECTION } from '../../../../common/constants';
import type { DataTableModel, DataTableState } from '@kbn/securitysolution-data-table';
import { TableId } from '@kbn/securitysolution-data-table';
import { v88xAlertOrignalData, v89xAlertsOriginalData } from './test.data';
jest.mock('../../../common/lib/kibana');
@ -788,7 +790,7 @@ describe('SiemLocalStorage', () => {
});
});
describe('Trigger Actions Alert Table Migration', () => {
describe('Trigger Actions Alert Table Migration -> Migration from 8.7', () => {
const legacyDataTableState: DataTableState['dataTable']['tableById'] = {
'alerts-page': {
queryFields: [],
@ -1261,17 +1263,66 @@ describe('SiemLocalStorage', () => {
],
sort: [{ '@timestamp': { order: 'desc' } }],
visibleColumns: [
'@timestamp',
'kibana.alert.rule.name',
'kibana.alert.severity',
'kibana.alert.risk_score',
'kibana.alert.reason',
'host.name',
'user.name',
'process.name',
'file.name',
'source.ip',
'destination.ip',
{
columnHeaderType: 'not-filtered',
id: '@timestamp',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
initialWidth: 180,
linkField: 'kibana.alert.rule.uuid',
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'host.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'user.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'process.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'file.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'source.ip',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'destination.ip',
initialWidth: 180,
},
],
},
},
@ -1344,17 +1395,66 @@ describe('SiemLocalStorage', () => {
{ 'kibana.alert.rule.name': { order: 'desc' } },
],
visibleColumns: [
'@timestamp',
'kibana.alert.rule.name',
'kibana.alert.severity',
'kibana.alert.risk_score',
'kibana.alert.reason',
'host.name',
'user.name',
'process.name',
'file.name',
'source.ip',
'destination.ip',
{
columnHeaderType: 'not-filtered',
id: '@timestamp',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
initialWidth: 180,
linkField: 'kibana.alert.rule.uuid',
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'host.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'user.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'process.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'file.name',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'source.ip',
initialWidth: 180,
},
{
columnHeaderType: 'not-filtered',
id: 'destination.ip',
initialWidth: 180,
},
],
},
},
@ -1387,4 +1487,44 @@ describe('SiemLocalStorage', () => {
}
});
});
describe('should migrate Alert Table visible columns from v8.8.x', () => {
// PR: https://github.com/elastic/kibana/pull/161054
beforeEach(() => storage.clear());
it('should migrate correctly when upgrading from 8.8.x -> 8.9', () => {
Object.keys(v88xAlertOrignalData).forEach((k) => {
storage.set(k, v88xAlertOrignalData[k as keyof typeof v88xAlertOrignalData]);
});
migrateTriggerActionsVisibleColumnsAlertTable88xTo89(storage);
Object.keys(v89xAlertsOriginalData).forEach((k) => {
const expectedResult = v89xAlertsOriginalData[k as keyof typeof v89xAlertsOriginalData];
expect(storage.get(k)).toMatchObject(expectedResult);
});
});
it('should be a no-op when reinstalling from 8.9 when data is already present.', () => {
Object.keys(v89xAlertsOriginalData).forEach((k) => {
storage.set(k, v89xAlertsOriginalData[k as keyof typeof v89xAlertsOriginalData]);
});
migrateTriggerActionsVisibleColumnsAlertTable88xTo89(storage);
Object.keys(v89xAlertsOriginalData).forEach((k) => {
const expectedResult = v89xAlertsOriginalData[k as keyof typeof v89xAlertsOriginalData];
expect(storage.get(k)).toMatchObject(expectedResult);
});
});
it('should be a no-op when installing 8.9 for the first time', () => {
migrateTriggerActionsVisibleColumnsAlertTable88xTo89(storage);
expect(
storage.get('detection-engine-alert-table-securitySolution-alerts-page-gridView')
).toBeNull();
expect(
storage.get('detection-engine-alert-table-securitySolution-rule-details-gridView')
).toBeNull();
});
});
});

View file

@ -77,6 +77,18 @@ export const migrateLegacyTimelinesToSecurityDataTable = (legacyTimelineTables:
}, {} as { [K in TableIdLiteral]: DataTableModel });
};
/*
*
* This migraiton only works for upgrading from
* 8.7 -> 8.8. Please do not edit this migration for any
* future release.
*
* If there is a migration that is required to be done for
* any future release. It should written as a saperate piece of code
* and should be called after this migration
*
* **/
export const migrateAlertTableStateToTriggerActionsState = (
storage: Storage,
legacyDataTableState: DataTableState['dataTable']['tableById']
@ -100,7 +112,7 @@ export const migrateAlertTableStateToTriggerActionsState = (
sort: legacyDataTableState[tableKey].sort.map((sortCandidate) => ({
[sortCandidate.columnId]: { order: sortCandidate.sortDirection },
})),
visibleColumns: legacyDataTableState[tableKey].columns.map((c) => c.id),
visibleColumns: legacyDataTableState[tableKey].columns,
},
};
});
@ -110,7 +122,47 @@ export const migrateAlertTableStateToTriggerActionsState = (
storage.set(key, stateObj[key]);
})
);
return Object.assign(legacyDataTableState, triggersActionsState);
};
/*
*
* Used for migrating Alert Table from 8.8 => 8.9
* */
export const migrateTriggerActionsVisibleColumnsAlertTable88xTo89 = (storage: Storage) => {
const localStorageKeys = [
`detection-engine-alert-table-${ALERTS_TABLE_REGISTRY_CONFIG_IDS.ALERTS_PAGE}-gridView`,
`detection-engine-alert-table-${ALERTS_TABLE_REGISTRY_CONFIG_IDS.RULE_DETAILS}-gridView`,
];
localStorageKeys.forEach((key) => {
const alertTableData = storage.get(key);
if (!alertTableData) {
return;
}
if ('visibleColumns' in alertTableData) {
const visibleColumns =
alertTableData.visibleColumns as DataTableState['dataTable']['tableById'][string]['columns'];
const v89CompliantFormat = visibleColumns.every((val: unknown) => typeof val === 'string');
if (v89CompliantFormat) {
return;
}
const newVisibleColumns = visibleColumns
.map((visibleCol) => {
if (typeof visibleCol === 'string') {
// if column format is 8.9 compliant already
return visibleCol;
}
// if column format is 8.8
return visibleCol.id;
})
.filter(Boolean);
storage.set(key, {
...alertTableData,
visibleColumns: newVisibleColumns,
});
}
});
};
/**
@ -155,7 +207,8 @@ export const getDataTablesInStorageByIds = (storage: Storage, tableIds: TableIdL
}
}
allDataTables = migrateAlertTableStateToTriggerActionsState(storage, allDataTables);
migrateAlertTableStateToTriggerActionsState(storage, allDataTables);
migrateTriggerActionsVisibleColumnsAlertTable88xTo89(storage);
return tableIds.reduce((acc, tableId) => {
const tableModel = allDataTables[tableId];

View file

@ -0,0 +1,579 @@
/*
* 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 const v88xAlertOrignalData = {
'detection-engine-alert-table-securitySolution-rule-details-gridView': {
columns: [
{
initialWidth: 200,
columnHeaderType: 'not-filtered',
id: '@timestamp',
schema: 'datetime',
},
{
id: '_id',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
linkField: 'kibana.alert.rule.uuid',
schema: 'string',
},
{
initialWidth: 105,
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
schema: 'string',
},
{
initialWidth: 100,
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
schema: 'numeric',
},
{
initialWidth: 450,
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'process.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'file.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'source.ip',
schema: 'ip',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'destination.ip',
schema: 'ip',
},
],
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
visibleColumns: [
{
initialWidth: 200,
columnHeaderType: 'not-filtered',
id: '@timestamp',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
linkField: 'kibana.alert.rule.uuid',
},
{
initialWidth: 105,
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
},
{
initialWidth: 100,
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
},
{
initialWidth: 450,
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.risk.calculated_level',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.risk.calculated_level',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'process.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'file.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'source.ip',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'destination.ip',
},
],
},
'detection-engine-alert-table-securitySolution-alerts-page-gridView': {
columns: [
{
initialWidth: 200,
columnHeaderType: 'not-filtered',
id: '@timestamp',
schema: 'datetime',
},
{
id: '_id',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
linkField: 'kibana.alert.rule.uuid',
schema: 'string',
},
{
initialWidth: 105,
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
schema: 'string',
},
{
initialWidth: 100,
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
schema: 'numeric',
},
{
initialWidth: 450,
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'process.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'file.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'source.ip',
schema: 'ip',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'destination.ip',
schema: 'ip',
},
],
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
visibleColumns: [
{
initialWidth: 200,
columnHeaderType: 'not-filtered',
id: '@timestamp',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
linkField: 'kibana.alert.rule.uuid',
},
{
initialWidth: 105,
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
},
{
initialWidth: 100,
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
},
{
initialWidth: 450,
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.risk.calculated_level',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.risk.calculated_level',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'process.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'file.name',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'source.ip',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'destination.ip',
},
],
},
};
// when upgrading from 8.9.x => 8.9.y
export const v89xAlertsOriginalData = {
'detection-engine-alert-table-securitySolution-rule-details-gridView': {
columns: [
{
initialWidth: 200,
columnHeaderType: 'not-filtered',
id: '@timestamp',
schema: 'datetime',
},
{
id: '_id',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
linkField: 'kibana.alert.rule.uuid',
schema: 'string',
},
{
initialWidth: 105,
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
schema: 'string',
},
{
initialWidth: 100,
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
schema: 'numeric',
},
{
initialWidth: 450,
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'process.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'file.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'source.ip',
schema: 'ip',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'destination.ip',
schema: 'ip',
},
],
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
visibleColumns: [
'@timestamp',
'kibana.alert.rule.name',
'kibana.alert.severity',
'kibana.alert.risk_score',
'kibana.alert.reason',
'host.name',
'host.risk.calculated_level',
'user.name',
'user.risk.calculated_level',
'process.name',
'file.name',
'source.ip',
'destination.ip',
],
},
'detection-engine-alert-table-securitySolution-alerts-page-gridView': {
columns: [
{
initialWidth: 200,
columnHeaderType: 'not-filtered',
id: '@timestamp',
schema: 'datetime',
},
{
id: '_id',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
displayAsText: 'Rule',
id: 'kibana.alert.rule.name',
linkField: 'kibana.alert.rule.uuid',
schema: 'string',
},
{
initialWidth: 105,
columnHeaderType: 'not-filtered',
displayAsText: 'Severity',
id: 'kibana.alert.severity',
schema: 'string',
},
{
initialWidth: 100,
columnHeaderType: 'not-filtered',
displayAsText: 'Risk Score',
id: 'kibana.alert.risk_score',
schema: 'numeric',
},
{
initialWidth: 450,
columnHeaderType: 'not-filtered',
displayAsText: 'Reason',
id: 'kibana.alert.reason',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'host.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'user.risk.calculated_level',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'process.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'file.name',
schema: 'string',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'source.ip',
schema: 'ip',
},
{
initialWidth: 180,
columnHeaderType: 'not-filtered',
id: 'destination.ip',
schema: 'ip',
},
],
sort: [
{
'@timestamp': {
order: 'desc',
},
},
],
visibleColumns: [
'@timestamp',
'kibana.alert.rule.name',
'kibana.alert.severity',
'kibana.alert.risk_score',
'kibana.alert.reason',
'host.name',
'host.risk.calculated_level',
'user.name',
'user.risk.calculated_level',
'process.name',
'file.name',
'source.ip',
'destination.ip',
],
},
};