Skip to content

Commit 255086d

Browse files
[8.x] [Security Solution] Migration of Alert Page controls for non-default Spaces. (#200058) (#200093)
# Backport This will backport the following commits from `main` to `8.x`: - [[Security Solution] Migration of Alert Page controls for non-default Spaces. (#200058)](#200058) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Jatin Kathuria","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-13T20:52:00Z","message":"[Security Solution] Migration of Alert Page controls for non-default Spaces. (#200058)\n\n## Summary\r\n\r\nRecently, we created a PR to migrate the alert page filters controls to\r\n`8.16`. Unfortunately, it does not do migration for non-default spaces\r\nso any users upgrading to `8.16` will face the issue where Alert page\r\nerrors out as shown in below screenshot.\r\n\r\n\r\n![grafik](https://github.com/user-attachments/assets/ffee1c2d-4aa2-44a4-96c9-68053fb1cf63)\r\n\r\n\r\n## Desk Testing\r\n\r\n1. Checkout to `v8.15` branch by running `git checkout 8.15`. \r\n2. Create a new space and go to that space.\r\n3. Go to the alert page and do some modifications to the page controls.\r\nThis store `v8.15` page controls in local storage.\r\n - You can, for example, delete one page control.\r\n - Change selected value for one page control.\r\n - Additionally, you can also add a custom control.\r\n4. Checkout `main` now and repeat the above steps.\r\n5. Your changes should be retained on the alert page and there should\r\nnot be any error.","sha":"b7ca7228315393c6672f638982dbac5196c9ad90","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Threat Hunting:Investigations","backport:prev-minor"],"title":"[Security Solution] Migration of Alert Page controls for non-default Spaces.","number":200058,"url":"https://github.com/elastic/kibana/pull/200058","mergeCommit":{"message":"[Security Solution] Migration of Alert Page controls for non-default Spaces. (#200058)\n\n## Summary\r\n\r\nRecently, we created a PR to migrate the alert page filters controls to\r\n`8.16`. Unfortunately, it does not do migration for non-default spaces\r\nso any users upgrading to `8.16` will face the issue where Alert page\r\nerrors out as shown in below screenshot.\r\n\r\n\r\n![grafik](https://github.com/user-attachments/assets/ffee1c2d-4aa2-44a4-96c9-68053fb1cf63)\r\n\r\n\r\n## Desk Testing\r\n\r\n1. Checkout to `v8.15` branch by running `git checkout 8.15`. \r\n2. Create a new space and go to that space.\r\n3. Go to the alert page and do some modifications to the page controls.\r\nThis store `v8.15` page controls in local storage.\r\n - You can, for example, delete one page control.\r\n - Change selected value for one page control.\r\n - Additionally, you can also add a custom control.\r\n4. Checkout `main` now and repeat the above steps.\r\n5. Your changes should be retained on the alert page and there should\r\nnot be any error.","sha":"b7ca7228315393c6672f638982dbac5196c9ad90"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/200058","number":200058,"mergeCommit":{"message":"[Security Solution] Migration of Alert Page controls for non-default Spaces. (#200058)\n\n## Summary\r\n\r\nRecently, we created a PR to migrate the alert page filters controls to\r\n`8.16`. Unfortunately, it does not do migration for non-default spaces\r\nso any users upgrading to `8.16` will face the issue where Alert page\r\nerrors out as shown in below screenshot.\r\n\r\n\r\n![grafik](https://github.com/user-attachments/assets/ffee1c2d-4aa2-44a4-96c9-68053fb1cf63)\r\n\r\n\r\n## Desk Testing\r\n\r\n1. Checkout to `v8.15` branch by running `git checkout 8.15`. \r\n2. Create a new space and go to that space.\r\n3. Go to the alert page and do some modifications to the page controls.\r\nThis store `v8.15` page controls in local storage.\r\n - You can, for example, delete one page control.\r\n - Change selected value for one page control.\r\n - Additionally, you can also add a custom control.\r\n4. Checkout `main` now and repeat the above steps.\r\n5. Your changes should be retained on the alert page and there should\r\nnot be any error.","sha":"b7ca7228315393c6672f638982dbac5196c9ad90"}}]}] BACKPORT--> Co-authored-by: Jatin Kathuria <[email protected]>
1 parent 6b2ebb4 commit 255086d

File tree

6 files changed

+110
-43
lines changed

6 files changed

+110
-43
lines changed

x-pack/plugins/security_solution/public/detections/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { getDataTablesInStorageByIds } from '../timelines/containers/local_stora
1212
import { routes } from './routes';
1313
import type { SecuritySubPlugin } from '../app/types';
1414
import { runDetectionMigrations } from './migrations';
15+
import type { StartPlugins } from '../types';
1516

1617
export const DETECTIONS_TABLE_IDS: TableIdLiteral[] = [
1718
TableId.alertsOnRuleDetailsPage,
@@ -21,8 +22,8 @@ export const DETECTIONS_TABLE_IDS: TableIdLiteral[] = [
2122
export class Detections {
2223
public setup() {}
2324

24-
public start(storage: Storage): SecuritySubPlugin {
25-
runDetectionMigrations();
25+
public async start(storage: Storage, plugins: StartPlugins): Promise<SecuritySubPlugin> {
26+
await runDetectionMigrations(storage, plugins);
2627

2728
return {
2829
storageDataTables: {

x-pack/plugins/security_solution/public/detections/migrations.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55
* 2.0.
66
*/
77

8-
import { Storage } from '@kbn/kibana-utils-plugin/public';
8+
import type { Storage } from '@kbn/kibana-utils-plugin/public';
99
import { migrateAlertPageControlsTo816 } from '../timelines/containers/local_storage/migrate_alert_page_controls';
10+
import type { StartPlugins } from '../types';
1011

11-
type LocalStorageMigrator = (storage: Storage) => void;
12+
/* Migrator could be sync or async */
13+
type LocalStorageMigrator = (storage: Storage, plugins: StartPlugins) => void | Promise<void>;
1214

13-
const runLocalStorageMigration = (fn: LocalStorageMigrator) => {
14-
const storage = new Storage(localStorage);
15-
fn(storage);
15+
const getLocalStorageMigrationRunner = (storage: Storage, plugins: StartPlugins) => {
16+
const runLocalStorageMigration = async (fn: LocalStorageMigrator) => {
17+
await fn(storage, plugins);
18+
};
19+
return runLocalStorageMigration;
1620
};
1721

18-
export const runDetectionMigrations = () => {
19-
runLocalStorageMigration(migrateAlertPageControlsTo816);
22+
export const runDetectionMigrations = async (storage: Storage, plugins: StartPlugins) => {
23+
const runLocalStorageMigration = getLocalStorageMigrationRunner(storage, plugins);
24+
await runLocalStorageMigration(migrateAlertPageControlsTo816);
2025
};

x-pack/plugins/security_solution/public/plugin.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
256256
plugins: StartPlugins
257257
): Promise<StartedSubPlugins> {
258258
const subPlugins = await this.createSubPlugins();
259+
const alerts = await subPlugins.alerts.start(storage, plugins);
259260
return {
260-
alerts: subPlugins.alerts.start(storage),
261+
alerts,
261262
attackDiscovery: subPlugins.attackDiscovery.start(),
262263
cases: subPlugins.cases.start(),
263264
cloudDefend: subPlugins.cloudDefend.start(),

x-pack/plugins/security_solution/public/timelines/containers/local_storage/migrate_alert_page_contorls.test.ts

Lines changed: 83 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77

88
import { Storage } from '@kbn/kibana-utils-plugin/public';
99
import {
10-
PAGE_FILTER_STORAGE_KEY,
10+
GET_PAGE_FILTER_STORAGE_KEY,
1111
migrateAlertPageControlsTo816,
1212
} from './migrate_alert_page_controls';
13+
import type { StartPlugins } from '../../../types';
1314

1415
const OLD_FORMAT = {
1516
viewMode: 'view',
@@ -216,40 +217,94 @@ const NEW_FORMAT = {
216217
};
217218
const storage = new Storage(localStorage);
218219

220+
const mockPlugins = {
221+
spaces: {
222+
getActiveSpace: jest.fn().mockResolvedValue({ id: 'default' }),
223+
},
224+
} as unknown as StartPlugins;
225+
219226
describe('migrateAlertPageControlsTo816', () => {
220227
beforeEach(() => {
221228
storage.clear();
222229
});
223-
it('should migrate the old format to the new format', () => {
224-
storage.set(PAGE_FILTER_STORAGE_KEY, OLD_FORMAT);
225-
migrateAlertPageControlsTo816(storage);
226-
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
227-
expect(migrated).toMatchObject(NEW_FORMAT);
228-
});
230+
describe('Default space', () => {
231+
beforeEach(() => {
232+
if (mockPlugins.spaces?.getActiveSpace) {
233+
mockPlugins.spaces.getActiveSpace = jest.fn().mockResolvedValue({ id: 'default' });
234+
}
235+
});
236+
it('should migrate the old format to the new format', async () => {
237+
storage.set(GET_PAGE_FILTER_STORAGE_KEY(), OLD_FORMAT);
238+
await migrateAlertPageControlsTo816(storage, mockPlugins);
239+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY());
240+
expect(migrated).toMatchObject(NEW_FORMAT);
241+
});
229242

230-
it('should be a no-op if the new format already exists', () => {
231-
storage.set(PAGE_FILTER_STORAGE_KEY, NEW_FORMAT);
232-
migrateAlertPageControlsTo816(storage);
233-
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
234-
expect(migrated).toMatchObject(NEW_FORMAT);
235-
});
243+
it('should be a no-op if the new format already exists', async () => {
244+
storage.set(GET_PAGE_FILTER_STORAGE_KEY(), NEW_FORMAT);
245+
await migrateAlertPageControlsTo816(storage, mockPlugins);
246+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY());
247+
expect(migrated).toMatchObject(NEW_FORMAT);
248+
});
236249

237-
it('should be a no-op if no value is present in localstorage for page filters ', () => {
238-
migrateAlertPageControlsTo816(storage);
239-
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
240-
expect(migrated).toBeNull();
250+
it('should be a no-op if no value is present in localstorage for page filters ', async () => {
251+
await migrateAlertPageControlsTo816(storage, mockPlugins);
252+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY());
253+
expect(migrated).toBeNull();
254+
});
255+
256+
it('should convert custom old format correctly', async () => {
257+
const MODIFIED_OLD_FORMAT = structuredClone(OLD_FORMAT);
258+
MODIFIED_OLD_FORMAT.panels['0'].explicitInput.hideExists = true;
259+
MODIFIED_OLD_FORMAT.chainingSystem = 'NONE';
260+
storage.set(GET_PAGE_FILTER_STORAGE_KEY(), MODIFIED_OLD_FORMAT);
261+
await migrateAlertPageControlsTo816(storage, mockPlugins);
262+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY());
263+
const EXPECTED_NEW_FORMAT = structuredClone(NEW_FORMAT);
264+
EXPECTED_NEW_FORMAT.initialChildControlState['0'].hideExists = true;
265+
EXPECTED_NEW_FORMAT.chainingSystem = 'NONE';
266+
expect(migrated).toMatchObject(EXPECTED_NEW_FORMAT);
267+
});
241268
});
242269

243-
it('should convert custom old format correctly', () => {
244-
const MODIFIED_OLD_FORMAT = structuredClone(OLD_FORMAT);
245-
MODIFIED_OLD_FORMAT.panels['0'].explicitInput.hideExists = true;
246-
MODIFIED_OLD_FORMAT.chainingSystem = 'NONE';
247-
storage.set(PAGE_FILTER_STORAGE_KEY, MODIFIED_OLD_FORMAT);
248-
migrateAlertPageControlsTo816(storage);
249-
const migrated = storage.get(PAGE_FILTER_STORAGE_KEY);
250-
const EXPECTED_NEW_FORMAT = structuredClone(NEW_FORMAT);
251-
EXPECTED_NEW_FORMAT.initialChildControlState['0'].hideExists = true;
252-
EXPECTED_NEW_FORMAT.chainingSystem = 'NONE';
253-
expect(migrated).toMatchObject(EXPECTED_NEW_FORMAT);
270+
describe('Non Default space', () => {
271+
const nonDefaultSpaceId = 'space1';
272+
beforeEach(() => {
273+
if (mockPlugins.spaces?.getActiveSpace) {
274+
mockPlugins.spaces.getActiveSpace = jest.fn().mockResolvedValue({ id: nonDefaultSpaceId });
275+
}
276+
});
277+
it('should migrate the old format to the new format', async () => {
278+
storage.set(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId), OLD_FORMAT);
279+
await migrateAlertPageControlsTo816(storage, mockPlugins);
280+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId));
281+
expect(migrated).toMatchObject(NEW_FORMAT);
282+
});
283+
284+
it('should be a no-op if the new format already exists', async () => {
285+
storage.set(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId), NEW_FORMAT);
286+
await migrateAlertPageControlsTo816(storage, mockPlugins);
287+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId));
288+
expect(migrated).toMatchObject(NEW_FORMAT);
289+
});
290+
291+
it('should be a no-op if no value is present in localstorage for page filters ', async () => {
292+
await migrateAlertPageControlsTo816(storage, mockPlugins);
293+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId));
294+
expect(migrated).toBeNull();
295+
});
296+
297+
it('should convert custom old format correctly', async () => {
298+
const MODIFIED_OLD_FORMAT = structuredClone(OLD_FORMAT);
299+
MODIFIED_OLD_FORMAT.panels['0'].explicitInput.hideExists = true;
300+
MODIFIED_OLD_FORMAT.chainingSystem = 'NONE';
301+
storage.set(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId), MODIFIED_OLD_FORMAT);
302+
await migrateAlertPageControlsTo816(storage, mockPlugins);
303+
const migrated = storage.get(GET_PAGE_FILTER_STORAGE_KEY(nonDefaultSpaceId));
304+
const EXPECTED_NEW_FORMAT = structuredClone(NEW_FORMAT);
305+
EXPECTED_NEW_FORMAT.initialChildControlState['0'].hideExists = true;
306+
EXPECTED_NEW_FORMAT.chainingSystem = 'NONE';
307+
expect(migrated).toMatchObject(EXPECTED_NEW_FORMAT);
308+
});
254309
});
255310
});

x-pack/plugins/security_solution/public/timelines/containers/local_storage/migrate_alert_page_controls.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
import type { DefaultControlState, ControlGroupRuntimeState } from '@kbn/controls-plugin/common';
99
import type { Storage } from '@kbn/kibana-utils-plugin/public';
10+
import type { StartPlugins } from '../../../types';
1011

11-
export const PAGE_FILTER_STORAGE_KEY = 'siem.default.pageFilters';
12+
export const GET_PAGE_FILTER_STORAGE_KEY = (spaceId: string = 'default') =>
13+
`siem.${spaceId}.pageFilters`;
1214

1315
interface OldFormat {
1416
viewMode: string;
@@ -96,8 +98,11 @@ interface NewFormatExplicitInput {
9698
* This migration script is to migrate the old format to the new format.
9799
*
98100
*/
99-
export function migrateAlertPageControlsTo816(storage: Storage) {
100-
const oldFormat: OldFormat = storage.get(PAGE_FILTER_STORAGE_KEY);
101+
export async function migrateAlertPageControlsTo816(storage: Storage, plugins: StartPlugins) {
102+
const space = await plugins.spaces?.getActiveSpace();
103+
const spaceId = space?.id ?? 'default';
104+
const storageKey = GET_PAGE_FILTER_STORAGE_KEY(spaceId);
105+
const oldFormat: OldFormat = storage.get(GET_PAGE_FILTER_STORAGE_KEY(spaceId));
101106
if (oldFormat && Object.keys(oldFormat).includes('panels')) {
102107
// Only run when it is old format
103108
const newFormat: ControlGroupRuntimeState<NewFormatExplicitInput & DefaultControlState> = {
@@ -131,6 +136,6 @@ export function migrateAlertPageControlsTo816(storage: Storage) {
131136
};
132137
}
133138

134-
storage.set(PAGE_FILTER_STORAGE_KEY, newFormat);
139+
storage.set(storageKey, newFormat);
135140
}
136141
}

x-pack/plugins/security_solution/public/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ export interface SubPlugins {
248248
// TODO: find a better way to defined these types
249249
export interface StartedSubPlugins {
250250
[CASES_SUB_PLUGIN_KEY]: ReturnType<Cases['start']>;
251-
alerts: ReturnType<Detections['start']>;
251+
alerts: Awaited<ReturnType<Detections['start']>>;
252252
attackDiscovery: ReturnType<AttackDiscovery['start']>;
253253
cloudDefend: ReturnType<CloudDefend['start']>;
254254
cloudSecurityPosture: ReturnType<CloudSecurityPosture['start']>;

0 commit comments

Comments
 (0)