Skip to content

Commit

Permalink
[8.x] [Rules migration] Add possibility to navigate to a specific mig…
Browse files Browse the repository at this point in the history
…ration (#11264) (#201597) (#201735)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Rules migration] Add possibility to navigate to a specific migration
(#11264) (#201597)](#201597)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Ievgen
Sorokopud","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-26T09:39:17Z","message":"[Rules
migration] Add possibility to navigate to a specific migration (#11264)
(#201597)\n\n## Summary\r\n\r\n[Internal
link](https://github.com/elastic/security-team/issues/10820)\r\nto the
feature details\r\n\r\nWith these changes we:\r\n* allow user to
navigate to a specific migration by its id\r\n* handle different
possible states on migrations rules page:\r\n* `no migrations`: if there
are no existing migrations we will redirect\r\nuser to the landing
page\r\n* `unknown selected migration`: if unknown migration id is
specified in\r\nthe URL, then \"Unknown Migration\" page will be
shown\r\n* `no selected migration`: if user lands on the root \"SIEM
migrations\r\nrules\" page, then most recent migration will be shown\r\n
* `show existing migration`: selected migration will be shown\r\n\r\n###
Screenshots\r\n\r\n**Unknown migration**\r\n\r\n<img width=\"1312\"
alt=\"Screenshot 2024-11-25 at 14 46
56\"\r\nsrc=\"https://github.com/user-attachments/assets/45f51489-e4f8-496f-86e6-d19130bd6769\">\r\n\r\n**Show
existing migration**\r\n\r\n<img width=\"1312\" alt=\"Screenshot
2024-11-25 at 15 03
53\"\r\nsrc=\"https://github.com/user-attachments/assets/ca866432-5a61-44c7-8bec-7aa95ba73156\">","sha":"17410c39279fcca551d3af7299b04d1ccf8ceefa","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:Threat
Hunting","Team: SecuritySolution","backport:prev-minor"],"title":"[Rules
migration] Add possibility to navigate to a specific migration
(#11264)","number":201597,"url":"https://github.com/elastic/kibana/pull/201597","mergeCommit":{"message":"[Rules
migration] Add possibility to navigate to a specific migration (#11264)
(#201597)\n\n## Summary\r\n\r\n[Internal
link](https://github.com/elastic/security-team/issues/10820)\r\nto the
feature details\r\n\r\nWith these changes we:\r\n* allow user to
navigate to a specific migration by its id\r\n* handle different
possible states on migrations rules page:\r\n* `no migrations`: if there
are no existing migrations we will redirect\r\nuser to the landing
page\r\n* `unknown selected migration`: if unknown migration id is
specified in\r\nthe URL, then \"Unknown Migration\" page will be
shown\r\n* `no selected migration`: if user lands on the root \"SIEM
migrations\r\nrules\" page, then most recent migration will be shown\r\n
* `show existing migration`: selected migration will be shown\r\n\r\n###
Screenshots\r\n\r\n**Unknown migration**\r\n\r\n<img width=\"1312\"
alt=\"Screenshot 2024-11-25 at 14 46
56\"\r\nsrc=\"https://github.com/user-attachments/assets/45f51489-e4f8-496f-86e6-d19130bd6769\">\r\n\r\n**Show
existing migration**\r\n\r\n<img width=\"1312\" alt=\"Screenshot
2024-11-25 at 15 03
53\"\r\nsrc=\"https://github.com/user-attachments/assets/ca866432-5a61-44c7-8bec-7aa95ba73156\">","sha":"17410c39279fcca551d3af7299b04d1ccf8ceefa"}},"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/201597","number":201597,"mergeCommit":{"message":"[Rules
migration] Add possibility to navigate to a specific migration (#11264)
(#201597)\n\n## Summary\r\n\r\n[Internal
link](https://github.com/elastic/security-team/issues/10820)\r\nto the
feature details\r\n\r\nWith these changes we:\r\n* allow user to
navigate to a specific migration by its id\r\n* handle different
possible states on migrations rules page:\r\n* `no migrations`: if there
are no existing migrations we will redirect\r\nuser to the landing
page\r\n* `unknown selected migration`: if unknown migration id is
specified in\r\nthe URL, then \"Unknown Migration\" page will be
shown\r\n* `no selected migration`: if user lands on the root \"SIEM
migrations\r\nrules\" page, then most recent migration will be shown\r\n
* `show existing migration`: selected migration will be shown\r\n\r\n###
Screenshots\r\n\r\n**Unknown migration**\r\n\r\n<img width=\"1312\"
alt=\"Screenshot 2024-11-25 at 14 46
56\"\r\nsrc=\"https://github.com/user-attachments/assets/45f51489-e4f8-496f-86e6-d19130bd6769\">\r\n\r\n**Show
existing migration**\r\n\r\n<img width=\"1312\" alt=\"Screenshot
2024-11-25 at 15 03
53\"\r\nsrc=\"https://github.com/user-attachments/assets/ca866432-5a61-44c7-8bec-7aa95ba73156\">","sha":"17410c39279fcca551d3af7299b04d1ccf8ceefa"}}]}]
BACKPORT-->

Co-authored-by: Ievgen Sorokopud <[email protected]>
  • Loading branch information
kibanamachine and e40pud authored Nov 26, 2024
1 parent ce65421 commit 5ebf435
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 156 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -17,7 +18,9 @@ export const RulesRoutes = () => {
return (
<PluginTemplateWrapper>
<SecurityRoutePageWrapper pageName={SecurityPageName.siemMigrationsRules}>
<RulesPage />
<Routes>
<Route path={`${SIEM_MIGRATIONS_RULES_PATH}/:migrationId?`} component={RulesPage} />
</Routes>
</SecurityRoutePageWrapper>
</PluginTemplateWrapper>
);
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 (
<EuiFlexGroup
alignItems="center"
gutterSize="s"
responsive={false}
direction="column"
wrap={true}
>
<EuiFlexItem grow={false}>
<EuiEmptyPrompt
title={<h2>{i18n.UNKNOWN_MIGRATION}</h2>}
titleSize="s"
body={i18n.UNKNOWN_MIGRATION_BODY}
data-test-subj="noMigrationsAvailable"
/>
</EuiFlexItem>
</EuiFlexGroup>
);
};

export const UnknownMigration = React.memo(UnknownMigrationComponent);
UnknownMigration.displayName = 'UnknownMigration';
Original file line number Diff line number Diff line change
@@ -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',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,93 +5,113 @@
* 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';
import { NeedAdminForUpdateRulesCallOut } from '../../../detections/components/callouts/need_admin_for_update_callout';
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<string | undefined>();
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<RulesMigrationPageProps> = React.memo(
({
match: {
params: { migrationId },
},
[]
);

const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({
ruleActionsFactory,
});

return (
<>
<NeedAdminForUpdateRulesCallOut />
<MissingPrivilegesCallOut />

<SecuritySolutionPageWrapper>
<HeaderPage title={i18n.PAGE_TITLE}>
<HeaderButtons
migrationsIds={migrationsIds}
selectedMigrationId={selectedMigrationId}
onMigrationIdChange={onMigrationIdChange}
}) => {
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 <UnknownMigration />;
}
return <RulesTable migrationId={migrationId} openRulePreview={openRulePreview} />;
}, [migrationId, migrationsIds, openRulePreview]);

return (
<>
<NeedAdminForUpdateRulesCallOut />
<MissingPrivilegesCallOut />

<SecuritySolutionPageWrapper>
<HeaderPage title={i18n.PAGE_TITLE}>
<HeaderButtons
migrationsIds={migrationsIds}
selectedMigrationId={migrationId}
onMigrationIdChange={onMigrationIdChange}
/>
</HeaderPage>
<EuiSkeletonLoading
isLoading={isLoadingMigrationsStats}
loadingContent={
<>
<EuiSkeletonTitle />
<EuiSkeletonText />
</>
}
loadedContent={content}
/>
</HeaderPage>
<EuiSkeletonLoading
isLoading={isLoadingMigrationsStats}
loadingContent={
<>
<EuiSkeletonTitle />
<EuiSkeletonText />
</>
}
loadedContent={
selectedMigrationId ? (
<RulesTable migrationId={selectedMigrationId} openRulePreview={openRulePreview} />
) : (
<NoMigrations />
)
}
/>
{rulePreviewFlyout}
</SecuritySolutionPageWrapper>
</>
);
});
{rulePreviewFlyout}
</SecuritySolutionPageWrapper>
</>
);
}
);
RulesPage.displayName = 'RulesPage';

0 comments on commit 5ebf435

Please sign in to comment.