From 900c5c4f75142886fc47f251cc2dfb8855442bd2 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Tue, 26 Nov 2024 10:39:17 +0100 Subject: [PATCH] [Rules migration] Add possibility to navigate to a specific migration (#11264) (#201597) ## Summary [Internal link](https://github.com/elastic/security-team/issues/10820) to the feature details With these changes we: * allow user to navigate to a specific migration by its id * handle different possible states on migrations rules page: * `no migrations`: if there are no existing migrations we will redirect user to the landing page * `unknown selected migration`: if unknown migration id is specified in the URL, then "Unknown Migration" page will be shown * `no selected migration`: if user lands on the root "SIEM migrations rules" page, then most recent migration will be shown * `show existing migration`: selected migration will be shown ### Screenshots **Unknown migration** Screenshot 2024-11-25 at 14 46 56 **Show existing migration** Screenshot 2024-11-25 at 15 03 53 --- .../public/siem_migrations/routes.tsx | 5 +- .../rules/components/no_migrations/index.tsx | 53 ------ .../components/no_migrations/translations.ts | 29 --- .../components/unknown_migration/index.tsx | 34 ++++ .../unknown_migration/translations.ts | 23 +++ .../siem_migrations/rules/pages/index.tsx | 166 ++++++++++-------- 6 files changed, 154 insertions(+), 156 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/index.tsx create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/translations.ts diff --git a/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx b/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx index 610eb7e2a72d8..cc2b9fbad3451 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx @@ -6,6 +6,7 @@ */ import React from 'react'; +import { Routes, Route } from '@kbn/shared-ux-router'; import type { SecuritySubPluginRoutes } from '../app/types'; import { SIEM_MIGRATIONS_RULES_PATH, SecurityPageName } from '../../common/constants'; @@ -17,7 +18,9 @@ export const RulesRoutes = () => { return ( - + + + ); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx deleted file mode 100644 index e4b3701d94c73..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/index.tsx +++ /dev/null @@ -1,53 +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 { EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React from 'react'; -import { SecurityPageName } from '../../../../../common'; -import { useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; -import * as i18n from './translations'; - -const NoMigrationsComponent = () => { - const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); - const { onClick: onClickLink } = getSecuritySolutionLinkProps({ - deepLinkId: SecurityPageName.landing, - path: 'siem_migrations', - }); - - return ( - - - {i18n.NO_MIGRATIONS_AVAILABLE}} - titleSize="s" - body={i18n.NO_MIGRATIONS_AVAILABLE_BODY} - data-test-subj="noMigrationsAvailable" - /> - - - - {i18n.GO_BACK_TO_RULES_TABLE_BUTTON} - - - - ); -}; - -export const NoMigrations = React.memo(NoMigrationsComponent); -NoMigrations.displayName = 'NoMigrations'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts deleted file mode 100644 index 77ec0454fb5a5..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/no_migrations/translations.ts +++ /dev/null @@ -1,29 +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 { i18n } from '@kbn/i18n'; - -export const NO_MIGRATIONS_AVAILABLE = i18n.translate( - 'xpack.securitySolution.siemMigrations.rules.noMigrationsTitle', - { - defaultMessage: 'No migrations', - } -); - -export const NO_MIGRATIONS_AVAILABLE_BODY = i18n.translate( - 'xpack.securitySolution.siemMigrations.rules.noMigrationsBodyTitle', - { - defaultMessage: 'There are no migrations available', - } -); - -export const GO_BACK_TO_RULES_TABLE_BUTTON = i18n.translate( - 'xpack.securitySolution.siemMigrations.rules.table.goToMigrationsPageButton', - { - defaultMessage: 'Go back to SIEM Migrations', - } -); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/index.tsx new file mode 100644 index 0000000000000..0a33869eff418 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/index.tsx @@ -0,0 +1,34 @@ +/* + * 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 { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import * as i18n from './translations'; + +const UnknownMigrationComponent = () => { + return ( + + + {i18n.UNKNOWN_MIGRATION}} + titleSize="s" + body={i18n.UNKNOWN_MIGRATION_BODY} + data-test-subj="noMigrationsAvailable" + /> + + + ); +}; + +export const UnknownMigration = React.memo(UnknownMigrationComponent); +UnknownMigration.displayName = 'UnknownMigration'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/translations.ts new file mode 100644 index 0000000000000..8720640858b94 --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/unknown_migration/translations.ts @@ -0,0 +1,23 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const UNKNOWN_MIGRATION = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.unknownMigrationTitle', + { + defaultMessage: 'Unknown migration', + } +); + +export const UNKNOWN_MIGRATION_BODY = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.unknownMigrationBodyTitle', + { + defaultMessage: + 'Selected migration does not exist. Please select one of the available migraitons', + } +); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx index 83392f0a70d2a..26199616b3777 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/pages/index.tsx @@ -5,12 +5,15 @@ * 2.0. */ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo } from 'react'; import { EuiSkeletonLoading, EuiSkeletonText, EuiSkeletonTitle } from '@elastic/eui'; +import type { RouteComponentProps } from 'react-router-dom'; +import { useNavigation } from '../../../common/lib/kibana'; import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; import { HeaderPage } from '../../../common/components/header_page'; import { SecuritySolutionPageWrapper } from '../../../common/components/page_wrapper'; +import { SecurityPageName } from '../../../app/types'; import * as i18n from './translations'; import { RulesTable } from '../components/rules_table'; @@ -18,80 +21,97 @@ import { NeedAdminForUpdateRulesCallOut } from '../../../detections/components/c import { MissingPrivilegesCallOut } from '../../../detections/components/callouts/missing_privileges_callout'; import { HeaderButtons } from '../components/header_buttons'; import { useRulePreviewFlyout } from '../hooks/use_rule_preview_flyout'; -import { NoMigrations } from '../components/no_migrations'; +import { UnknownMigration } from '../components/unknown_migration'; import { useLatestStats } from '../hooks/use_latest_stats'; -export const RulesPage = React.memo(() => { - const { data: ruleMigrationsStatsAll, isLoading: isLoadingMigrationsStats } = useLatestStats(); - - const migrationsIds = useMemo(() => { - if (isLoadingMigrationsStats || !ruleMigrationsStatsAll?.length) { - return []; - } - return ruleMigrationsStatsAll - .filter((migration) => migration.status === 'finished') - .map((migration) => migration.id); - }, [isLoadingMigrationsStats, ruleMigrationsStatsAll]); - - const [selectedMigrationId, setSelectedMigrationId] = useState(); - const onMigrationIdChange = (selectedId?: string) => { - setSelectedMigrationId(selectedId); - }; - - useEffect(() => { - if (!migrationsIds.length) { - return; - } - const index = migrationsIds.findIndex((id) => id === selectedMigrationId); - if (index === -1) { - setSelectedMigrationId(migrationsIds[0]); - } - }, [migrationsIds, selectedMigrationId]); - - const ruleActionsFactory = useCallback( - (ruleMigration: RuleMigration, closeRulePreview: () => void) => { - // TODO: Add flyout action buttons - return null; +type RulesMigrationPageProps = RouteComponentProps<{ migrationId?: string }>; + +export const RulesPage: React.FC = React.memo( + ({ + match: { + params: { migrationId }, }, - [] - ); - - const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ - ruleActionsFactory, - }); - - return ( - <> - - - - - - { + const { navigateTo } = useNavigation(); + + const { data: ruleMigrationsStatsAll, isLoading: isLoadingMigrationsStats } = useLatestStats(); + + const migrationsIds = useMemo(() => { + if (isLoadingMigrationsStats || !ruleMigrationsStatsAll?.length) { + return []; + } + return ruleMigrationsStatsAll + .filter((migration) => migration.status === 'finished') + .map((migration) => migration.id); + }, [isLoadingMigrationsStats, ruleMigrationsStatsAll]); + + useEffect(() => { + if (isLoadingMigrationsStats) { + return; + } + + // Navigate to landing page if there are no migrations + if (!migrationsIds.length) { + navigateTo({ deepLinkId: SecurityPageName.landing, path: 'siem_migrations' }); + return; + } + + // Navigate to the most recent migration if none is selected + if (!migrationId) { + navigateTo({ deepLinkId: SecurityPageName.siemMigrationsRules, path: migrationsIds[0] }); + } + }, [isLoadingMigrationsStats, migrationId, migrationsIds, navigateTo]); + + const onMigrationIdChange = (selectedId?: string) => { + navigateTo({ deepLinkId: SecurityPageName.siemMigrationsRules, path: selectedId }); + }; + + const ruleActionsFactory = useCallback( + (ruleMigration: RuleMigration, closeRulePreview: () => void) => { + // TODO: Add flyout action buttons + return null; + }, + [] + ); + + const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ + ruleActionsFactory, + }); + + const content = useMemo(() => { + if (!migrationId || !migrationsIds.includes(migrationId)) { + return ; + } + return ; + }, [migrationId, migrationsIds, openRulePreview]); + + return ( + <> + + + + + + + + + + + + } + loadedContent={content} /> - - - - - - } - loadedContent={ - selectedMigrationId ? ( - - ) : ( - - ) - } - /> - {rulePreviewFlyout} - - - ); -}); + {rulePreviewFlyout} + + + ); + } +); RulesPage.displayName = 'RulesPage';