Skip to content

Commit

Permalink
[Security Solution] Migration old format of Page Controls to new form…
Browse files Browse the repository at this point in the history
…at (#192555)

## Summary

PR #190561 introduces a breaking change in the format of page controls
data.

Because of this there was conflict between the value of page controls
stored in local storage v/s a new format.

> [!WARNING]
> All new users of `v8.16` will encounter the error on the alerts page
because of this conflict. To resolve this, they will have to clear local
storage which not a great UX.

## Desk Testing

1. Checkout to `v8.15` branch by running `git checkout 8.15`. 
2. Go to the alert page and do some modifications to the page controls.
This store `v8.15` page controls in local storage.
    - You can, for example, delete one page control.
    - Change selected value for one page control.
    - Additionally, you can also add a custom control.
3. Checkout `main` now and repeat the above steps.
4. Your changes should be retained on the alert page and there should
not be any error.



### Checklist

Delete any items that are not applicable to this PR.

- [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

---------

Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
logeekal and elasticmachine authored Sep 11, 2024
1 parent f15e825 commit f8a6f97
Show file tree
Hide file tree
Showing 4 changed files with 415 additions and 0 deletions.
3 changes: 3 additions & 0 deletions x-pack/plugins/security_solution/public/detections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { TableIdLiteral } from '@kbn/securitysolution-data-table';
import { getDataTablesInStorageByIds } from '../timelines/containers/local_storage';
import { routes } from './routes';
import type { SecuritySubPlugin } from '../app/types';
import { runDetectionMigrations } from './migrations';

export const DETECTIONS_TABLE_IDS: TableIdLiteral[] = [
TableId.alertsOnRuleDetailsPage,
Expand All @@ -21,6 +22,8 @@ export class Detections {
public setup() {}

public start(storage: Storage): SecuritySubPlugin {
runDetectionMigrations();

return {
storageDataTables: {
tableById: getDataTablesInStorageByIds(storage, DETECTIONS_TABLE_IDS),
Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/security_solution/public/detections/migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { Storage } from '@kbn/kibana-utils-plugin/public';
import { migrateAlertPageControlsTo816 } from '../timelines/containers/local_storage/migrate_alert_page_controls';

type LocalStorageMigrator = (storage: Storage) => void;

const runLocalStorageMigration = (fn: LocalStorageMigrator) => {
const storage = new Storage(localStorage);
fn(storage);
};

export const runDetectionMigrations = () => {
runLocalStorageMigration(migrateAlertPageControlsTo816);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
/*
* 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 { Storage } from '@kbn/kibana-utils-plugin/public';
import {
PAGE_FILTER_STORAGE_KEY,
migrateAlertPageControlsTo816,
} from './migrate_alert_page_controls';

const OLD_FORMAT = {
viewMode: 'view',
id: '5bc0ef0f-c6a9-4eaf-9fc5-9703fcb85482',
panels: {
'0': {
type: 'optionsListControl',
order: 0,
grow: true,
width: 'small',
explicitInput: {
id: '0',
dataViewId: 'security_solution_alerts_dv',
fieldName: 'kibana.alert.workflow_status',
title: 'Status',
hideExclude: true,
hideSort: true,
hidePanelTitles: true,
placeholder: '',
ignoreParentSettings: {
ignoreValidations: true,
},
selectedOptions: ['open'],
hideActionBar: true,
persist: true,
hideExists: true,
existsSelected: false,
exclude: false,
},
},
'1': {
type: 'optionsListControl',
order: 1,
grow: true,
width: 'small',
explicitInput: {
id: '1',
dataViewId: 'security_solution_alerts_dv',
fieldName: 'kibana.alert.severity',
title: 'Severity',
hideExclude: true,
hideSort: true,
hidePanelTitles: true,
placeholder: '',
ignoreParentSettings: {
ignoreValidations: true,
},
selectedOptions: [],
hideActionBar: true,
hideExists: true,
existsSelected: false,
exclude: false,
},
},
'2': {
type: 'optionsListControl',
order: 2,
grow: true,
width: 'small',
explicitInput: {
id: '2',
dataViewId: 'security_solution_alerts_dv',
fieldName: 'user.name',
title: 'User',
hideExclude: true,
hideSort: true,
hidePanelTitles: true,
placeholder: '',
ignoreParentSettings: {
ignoreValidations: true,
},
selectedOptions: [],
existsSelected: false,
exclude: false,
},
},
'3': {
type: 'optionsListControl',
order: 3,
grow: true,
width: 'small',
explicitInput: {
id: '3',
dataViewId: 'security_solution_alerts_dv',
fieldName: 'host.name',
title: 'Host',
hideExclude: true,
hideSort: true,
hidePanelTitles: true,
placeholder: '',
ignoreParentSettings: {
ignoreValidations: true,
},
selectedOptions: [],
existsSelected: false,
exclude: false,
},
},
},
defaultControlWidth: 'small',
defaultControlGrow: true,
controlStyle: 'oneLine',
chainingSystem: 'HIERARCHICAL',
showApplySelections: false,
ignoreParentSettings: {
ignoreFilters: false,
ignoreQuery: false,
ignoreTimerange: false,
ignoreValidations: false,
},
timeRange: {
from: '2024-09-10T22:00:00.000Z',
to: '2024-09-11T21:59:59.999Z',
mode: 'absolute',
},
filters: [
{
meta: {
alias: null,
negate: true,
disabled: false,
type: 'exists',
key: 'kibana.alert.building_block_type',
index: 'security-solution-default',
},
query: {
exists: {
field: 'kibana.alert.building_block_type',
},
},
},
],
query: {
query: '',
language: 'kuery',
},
};

const NEW_FORMAT = {
initialChildControlState: {
'0': {
type: 'optionsListControl',
order: 0,
hideExclude: true,
hideSort: true,
placeholder: '',
width: 'small',
dataViewId: 'security_solution_alerts_dv',
title: 'Status',
fieldName: 'kibana.alert.workflow_status',
selectedOptions: ['open'],
hideActionBar: true,
persist: true,
hideExists: true,
},
'1': {
type: 'optionsListControl',
order: 1,
hideExclude: true,
hideSort: true,
placeholder: '',
width: 'small',
dataViewId: 'security_solution_alerts_dv',
title: 'Severity',
fieldName: 'kibana.alert.severity',
selectedOptions: [],
hideActionBar: true,
hideExists: true,
},
'2': {
type: 'optionsListControl',
order: 2,
hideExclude: true,
hideSort: true,
placeholder: '',
width: 'small',
dataViewId: 'security_solution_alerts_dv',
title: 'User',
fieldName: 'user.name',
},
'3': {
type: 'optionsListControl',
order: 3,
hideExclude: true,
hideSort: true,
placeholder: '',
width: 'small',
dataViewId: 'security_solution_alerts_dv',
title: 'Host',
fieldName: 'host.name',
},
},
labelPosition: 'oneLine',
chainingSystem: 'HIERARCHICAL',
autoApplySelections: false,
ignoreParentSettings: {
ignoreValidations: false,
},
editorConfig: {
hideWidthSettings: true,
hideDataViewSelector: true,
hideAdditionalSettings: true,
},
};
const storage = new Storage(localStorage);

describe('migrateAlertPageControlsTo816', () => {
beforeEach(() => {
storage.clear();
});
it('should migrate the old format to the new format', () => {
storage.set(PAGE_FILTER_STORAGE_KEY, OLD_FORMAT);
migrateAlertPageControlsTo816(storage);
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
expect(migrated).toMatchObject(NEW_FORMAT);
});

it('should be a no-op if the new format already exists', () => {
storage.set(PAGE_FILTER_STORAGE_KEY, NEW_FORMAT);
migrateAlertPageControlsTo816(storage);
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
expect(migrated).toMatchObject(NEW_FORMAT);
});

it('should be a no-op if no value is present in localstorage for page filters ', () => {
migrateAlertPageControlsTo816(storage);
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
expect(migrated).toBeNull();
});

it('should convert custom old format correctly', () => {
const MODIFIED_OLD_FORMAT = structuredClone(OLD_FORMAT);
MODIFIED_OLD_FORMAT.panels['0'].explicitInput.hideExists = true;
MODIFIED_OLD_FORMAT.chainingSystem = 'NONE';
storage.set(PAGE_FILTER_STORAGE_KEY, MODIFIED_OLD_FORMAT);
migrateAlertPageControlsTo816(storage);
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
const EXPECTED_NEW_FORMAT = structuredClone(NEW_FORMAT);
EXPECTED_NEW_FORMAT.initialChildControlState['0'].hideExists = true;
EXPECTED_NEW_FORMAT.chainingSystem = 'NONE';
expect(migrated).toMatchObject(EXPECTED_NEW_FORMAT);
});
});
Loading

0 comments on commit f8a6f97

Please sign in to comment.