From fae31f24e4ea226cacd54f3a06467a4036f2cdb3 Mon Sep 17 00:00:00 2001 From: Ievgen Sorokopud Date: Wed, 4 Dec 2024 17:06:10 +0100 Subject: [PATCH] [Rules migration] Post merge feedback followups (#202815) ## Summary These are the followup updated to address feedback from my previous PRs: * Make sure to use descriptive names specific to the `siem_migrations` subdomain ([comment](https://github.com/elastic/kibana/pull/200978#pullrequestreview-2454582368)): > Make sure you use descriptive names specific to the siem_migrations subdomain. Names like RulesPage, RulesTable, useRulesColumns etc are way too generic and conflict with the rule management terminology, which would make code search more difficult. * Export the memo component directly everywhere ([comment](https://github.com/elastic/kibana/pull/201597#discussion_r1858069127)): > Could we export the memo component directly everywhere? It's shorter and it makes it easier to find the references in the IDE. * Use one hook to access APIs instead of two ([comment](https://github.com/elastic/kibana/pull/202494#discussion_r1867967135)): > I see that for every API request we have to implement 2 separate hooks. Why don't we add error handling to the same hook that does the useQuery? so we have everything in one hook. Or is there a reason to have them separate? --- .../common/siem_migrations/constants.ts | 8 + .../public/siem_migrations/routes.tsx | 11 +- .../hooks/use_get_migration_rules_query.ts | 78 ----- ...e_get_migration_translation_stats_query.ts | 60 ---- .../use_install_migration_rules_mutation.ts | 40 --- ...all_translated_migration_rules_mutation.ts | 43 --- .../rules/api/{api.ts => index.ts} | 12 +- .../rules/components/header_buttons/index.tsx | 99 +++--- .../constants.ts | 0 .../components/rule_details_flyout/index.tsx | 194 ++++++++++++ .../translation_tab/header.tsx | 5 +- .../translation_tab/index.tsx | 11 +- .../translation_tab/migration_rule_query.tsx | 73 +++++ .../translation_tab/translations.ts | 0 .../translations.ts | 0 ..._items_message.tsx => empty_migration.tsx} | 7 +- .../rules/components/rules_table/index.tsx | 293 +++++++++--------- .../components/rules_table/search_field.tsx | 14 +- .../rules_table_columns/actions.tsx | 12 +- .../components/rules_table_columns/name.tsx | 12 +- .../rules/components/status_badge/index.tsx | 38 +-- .../translation_details_flyout/index.tsx | 246 --------------- .../translation_tab/rule_query.tsx | 49 --- .../components/unknown_migration/index.tsx | 8 +- ... => use_migration_rule_preview_flyout.tsx} | 36 +-- ... => use_migration_rules_table_columns.tsx} | 12 +- .../rules/{api/hooks => logic}/constants.ts | 0 .../rules/logic/use_get_migration_rules.ts | 51 ++- .../use_get_migration_translation_stats.ts | 52 +++- .../logic/use_install_migration_rules.ts | 31 +- .../use_install_translated_migration_rules.ts | 34 +- .../siem_migrations/rules/pages/index.tsx | 34 +- .../rules/service/rule_migrations_service.ts | 2 +- .../public/siem_migrations/rules/types.ts | 11 - .../rules/api/util/installation.ts | 2 +- 35 files changed, 730 insertions(+), 848 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_translation_stats_query.ts delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_translated_migration_rules_mutation.ts rename x-pack/plugins/security_solution/public/siem_migrations/rules/api/{api.ts => index.ts} (95%) rename x-pack/plugins/security_solution/public/siem_migrations/rules/components/{translation_details_flyout => rule_details_flyout}/constants.ts (100%) create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx rename x-pack/plugins/security_solution/public/siem_migrations/rules/components/{translation_details_flyout => rule_details_flyout}/translation_tab/header.tsx (82%) rename x-pack/plugins/security_solution/public/siem_migrations/rules/components/{translation_details_flyout => rule_details_flyout}/translation_tab/index.tsx (92%) create mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/migration_rule_query.tsx rename x-pack/plugins/security_solution/public/siem_migrations/rules/components/{translation_details_flyout => rule_details_flyout}/translation_tab/translations.ts (100%) rename x-pack/plugins/security_solution/public/siem_migrations/rules/components/{translation_details_flyout => rule_details_flyout}/translations.ts (100%) rename x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/{no_items_message.tsx => empty_migration.tsx} (93%) delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx delete mode 100644 x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx rename x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/{use_rule_preview_flyout.tsx => use_migration_rule_preview_flyout.tsx} (55%) rename x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/{use_rules_table_columns.tsx => use_migration_rules_table_columns.tsx} (78%) rename x-pack/plugins/security_solution/public/siem_migrations/rules/{api/hooks => logic}/constants.ts (100%) diff --git a/x-pack/plugins/security_solution/common/siem_migrations/constants.ts b/x-pack/plugins/security_solution/common/siem_migrations/constants.ts index a910cb90f1664..789947150a67e 100644 --- a/x-pack/plugins/security_solution/common/siem_migrations/constants.ts +++ b/x-pack/plugins/security_solution/common/siem_migrations/constants.ts @@ -46,3 +46,11 @@ export enum SiemMigrationRuleTranslationResult { export const DEFAULT_TRANSLATION_RISK_SCORE = 21; export const DEFAULT_TRANSLATION_SEVERITY: Severity = 'low'; + +export const DEFAULT_TRANSLATION_FIELDS = { + risk_score: DEFAULT_TRANSLATION_RISK_SCORE, + severity: DEFAULT_TRANSLATION_SEVERITY, + from: 'now-360s', + to: 'now', + interval: '5m', +} as const; 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 cc2b9fbad3451..179cf064c4926 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/routes.tsx @@ -10,16 +10,19 @@ import { Routes, Route } from '@kbn/shared-ux-router'; import type { SecuritySubPluginRoutes } from '../app/types'; import { SIEM_MIGRATIONS_RULES_PATH, SecurityPageName } from '../../common/constants'; -import { RulesPage } from './rules/pages'; +import { MigrationRulesPage } from './rules/pages'; import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper'; -export const RulesRoutes = () => { +export const SiemMigrationsRoutes = () => { return ( - + @@ -29,6 +32,6 @@ export const RulesRoutes = () => { export const routes: SecuritySubPluginRoutes = [ { path: SIEM_MIGRATIONS_RULES_PATH, - component: RulesRoutes, + component: SiemMigrationsRoutes, }, ]; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts deleted file mode 100644 index 138b2a31e9727..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_rules_query.ts +++ /dev/null @@ -1,78 +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 type { UseQueryOptions } from '@tanstack/react-query'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { replaceParams } from '@kbn/openapi-common/shared'; -import { useCallback } from 'react'; -import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; -import { DEFAULT_QUERY_OPTIONS } from './constants'; -import { getRuleMigrations } from '../api'; -import { SIEM_RULE_MIGRATION_PATH } from '../../../../../common/siem_migrations/constants'; - -interface UseGetMigrationRulesQueryProps { - migrationId: string; - page?: number; - perPage?: number; - searchTerm?: string; -} - -export interface MigrationRulesQueryResponse { - ruleMigrations: RuleMigration[]; - total: number; -} - -export const useGetMigrationRulesQuery = ( - queryArgs: UseGetMigrationRulesQueryProps, - queryOptions?: UseQueryOptions< - MigrationRulesQueryResponse, - Error, - MigrationRulesQueryResponse, - [...string[], UseGetMigrationRulesQueryProps] - > -) => { - const { migrationId } = queryArgs; - const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, { - migration_id: migrationId, - }); - return useQuery( - ['GET', SPECIFIC_MIGRATION_PATH, queryArgs], - async ({ signal }) => { - const response = await getRuleMigrations({ signal, ...queryArgs }); - - return { ruleMigrations: response.data, total: response.total }; - }, - { - ...DEFAULT_QUERY_OPTIONS, - ...queryOptions, - } - ); -}; - -/** - * We should use this hook to invalidate the rule migrations cache. For - * example, rule migrations mutations, like installing a rule, should lead to cache invalidation. - * - * @returns A rule migrations cache invalidation callback - */ -export const useInvalidateGetMigrationRulesQuery = (migrationId: string) => { - const queryClient = useQueryClient(); - - const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, { - migration_id: migrationId, - }); - - return useCallback(() => { - /** - * Invalidate all queries that start with SPECIFIC_MIGRATION_PATH. This - * includes the in-memory query cache and paged query cache. - */ - queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_PATH], { - refetchType: 'active', - }); - }, [SPECIFIC_MIGRATION_PATH, queryClient]); -}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_translation_stats_query.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_translation_stats_query.ts deleted file mode 100644 index e58b9be20d19c..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_get_migration_translation_stats_query.ts +++ /dev/null @@ -1,60 +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 type { UseQueryOptions } from '@tanstack/react-query'; -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { replaceParams } from '@kbn/openapi-common/shared'; -import { useCallback } from 'react'; -import { DEFAULT_QUERY_OPTIONS } from './constants'; -import { getRuleMigrationTranslationStats } from '../api'; -import type { GetRuleMigrationTranslationStatsResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; -import { SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH } from '../../../../../common/siem_migrations/constants'; - -export const useGetMigrationTranslationStatsQuery = ( - migrationId: string, - options?: UseQueryOptions -) => { - const SPECIFIC_MIGRATION_TRANSLATION_PATH = replaceParams( - SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, - { - migration_id: migrationId, - } - ); - return useQuery( - ['GET', SPECIFIC_MIGRATION_TRANSLATION_PATH], - async ({ signal }) => { - return getRuleMigrationTranslationStats({ migrationId, signal }); - }, - { - ...DEFAULT_QUERY_OPTIONS, - ...options, - } - ); -}; - -/** - * We should use this hook to invalidate the translation stats cache. For - * example, rule migrations mutations, like installing a rule, should lead to cache invalidation. - * - * @returns A translation stats cache invalidation callback - */ -export const useInvalidateGetMigrationTranslationStatsQuery = (migrationId: string) => { - const queryClient = useQueryClient(); - - const SPECIFIC_MIGRATION_TRANSLATION_PATH = replaceParams( - SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, - { - migration_id: migrationId, - } - ); - - return useCallback(() => { - queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_TRANSLATION_PATH], { - refetchType: 'active', - }); - }, [SPECIFIC_MIGRATION_TRANSLATION_PATH, queryClient]); -}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts deleted file mode 100644 index 536f9e772e5d8..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_migration_rules_mutation.ts +++ /dev/null @@ -1,40 +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 type { UseMutationOptions } from '@tanstack/react-query'; -import { useMutation } from '@tanstack/react-query'; -import type { InstallMigrationRulesResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; -import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../../common/siem_migrations/constants'; -import { installMigrationRules } from '../api'; -import { useInvalidateGetMigrationRulesQuery } from './use_get_migration_rules_query'; -import { useInvalidateGetMigrationTranslationStatsQuery } from './use_get_migration_translation_stats_query'; - -export const INSTALL_MIGRATION_RULES_MUTATION_KEY = ['POST', SIEM_RULE_MIGRATION_INSTALL_PATH]; - -export const useInstallMigrationRulesMutation = ( - migrationId: string, - options?: UseMutationOptions -) => { - const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId); - const invalidateGetMigrationTranslationStatsQuery = - useInvalidateGetMigrationTranslationStatsQuery(migrationId); - - return useMutation( - (ids: string[]) => installMigrationRules({ migrationId, ids }), - { - ...options, - mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY, - onSettled: (...args) => { - invalidateGetRuleMigrationsQuery(); - invalidateGetMigrationTranslationStatsQuery(); - - if (options?.onSettled) { - options.onSettled(...args); - } - }, - } - ); -}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_translated_migration_rules_mutation.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_translated_migration_rules_mutation.ts deleted file mode 100644 index 3c1dda0b457c4..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/use_install_translated_migration_rules_mutation.ts +++ /dev/null @@ -1,43 +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 type { UseMutationOptions } from '@tanstack/react-query'; -import { useMutation } from '@tanstack/react-query'; -import type { InstallTranslatedMigrationRulesResponse } from '../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; -import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../../common/siem_migrations/constants'; -import { installTranslatedMigrationRules } from '../api'; -import { useInvalidateGetMigrationRulesQuery } from './use_get_migration_rules_query'; -import { useInvalidateGetMigrationTranslationStatsQuery } from './use_get_migration_translation_stats_query'; - -export const INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY = [ - 'POST', - SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, -]; - -export const useInstallTranslatedMigrationRulesMutation = ( - migrationId: string, - options?: UseMutationOptions -) => { - const invalidateGetRuleMigrationsQuery = useInvalidateGetMigrationRulesQuery(migrationId); - const invalidateGetMigrationTranslationStatsQuery = - useInvalidateGetMigrationTranslationStatsQuery(migrationId); - - return useMutation( - () => installTranslatedMigrationRules({ migrationId }), - { - ...options, - mutationKey: INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY, - onSettled: (...args) => { - invalidateGetRuleMigrationsQuery(); - invalidateGetMigrationTranslationStatsQuery(); - - if (options?.onSettled) { - options.onSettled(...args); - } - }, - } - ); -}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts similarity index 95% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts index 160d78f1edbb6..592b93f438197 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/api.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/api/index.ts @@ -25,7 +25,6 @@ import type { InstallMigrationRulesResponse, StartRuleMigrationRequestBody, } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; -import type { InstallTranslatedRulesProps, InstallRulesProps } from '../types'; /** * Retrieves the stats for all the existing migrations, aggregated by `migration_id`. @@ -134,7 +133,11 @@ export const installMigrationRules = async ({ migrationId, ids, signal, -}: InstallRulesProps): Promise => { +}: { + migrationId: string; + ids: string[]; + signal?: AbortSignal; +}): Promise => { return KibanaServices.get().http.fetch( replaceParams(SIEM_RULE_MIGRATION_INSTALL_PATH, { migration_id: migrationId }), { @@ -149,7 +152,10 @@ export const installMigrationRules = async ({ export const installTranslatedMigrationRules = async ({ migrationId, signal, -}: InstallTranslatedRulesProps): Promise => { +}: { + migrationId: string; + signal?: AbortSignal; +}): Promise => { return KibanaServices.get().http.fetch( replaceParams(SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, { migration_id: migrationId }), { diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx index ba73bd9c84946..728873f046d2e 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/header_buttons/index.tsx @@ -10,12 +10,13 @@ import React, { useMemo } from 'react'; import type { EuiComboBoxOptionOption } from '@elastic/eui'; import { EuiComboBox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import * as i18n from './translations'; +import type { RuleMigrationTask } from '../../types'; export interface HeaderButtonsProps { /** - * Available rule migrations ids + * Available rule migrations stats */ - migrationsIds: string[]; + ruleMigrationsStats: RuleMigrationTask[]; /** * Selected rule migration id @@ -30,55 +31,53 @@ export interface HeaderButtonsProps { onMigrationIdChange: (selectedId?: string) => void; } -const HeaderButtonsComponent: React.FC = ({ - migrationsIds, - selectedMigrationId, - onMigrationIdChange, -}) => { - const migrationOptions = useMemo(() => { - const options: Array> = migrationsIds.map((id, index) => ({ - value: id, - 'data-test-subj': `migrationSelectionOption-${index}`, - label: i18n.SIEM_MIGRATIONS_OPTION_LABEL(index + 1), - })); - return options; - }, [migrationsIds]); - const selectedMigrationOption = useMemo>>(() => { - const index = migrationsIds.findIndex((id) => id === selectedMigrationId); - return index !== -1 - ? [ - { - value: selectedMigrationId, - 'data-test-subj': `migrationSelectionOption-${index}`, - label: i18n.SIEM_MIGRATIONS_OPTION_LABEL(index + 1), - }, - ] - : []; - }, [migrationsIds, selectedMigrationId]); +export const HeaderButtons: React.FC = React.memo( + ({ ruleMigrationsStats, selectedMigrationId, onMigrationIdChange }) => { + const migrationOptions = useMemo(() => { + const options: Array> = ruleMigrationsStats.map( + ({ id, number }) => ({ + value: id, + 'data-test-subj': `migrationSelectionOption-${number}`, + label: i18n.SIEM_MIGRATIONS_OPTION_LABEL(number), + }) + ); + return options; + }, [ruleMigrationsStats]); + const selectedMigrationOption = useMemo>>(() => { + const stats = ruleMigrationsStats.find(({ id }) => id === selectedMigrationId); + return stats + ? [ + { + value: selectedMigrationId, + 'data-test-subj': `migrationSelectionOption-${stats.number}`, + label: i18n.SIEM_MIGRATIONS_OPTION_LABEL(stats.number), + }, + ] + : []; + }, [ruleMigrationsStats, selectedMigrationId]); - const onChange = (selected: Array>) => { - onMigrationIdChange(selected[0].value); - }; + const onChange = (selected: Array>) => { + onMigrationIdChange(selected[0].value); + }; - if (!migrationsIds.length) { - return null; - } - - return ( - - - - - - ); -}; + if (!ruleMigrationsStats.length) { + return null; + } -export const HeaderButtons = React.memo(HeaderButtonsComponent); + return ( + + + + + + ); + } +); HeaderButtons.displayName = 'HeaderButtons'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/constants.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/constants.ts similarity index 100% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/constants.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/constants.ts diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx new file mode 100644 index 0000000000000..9353d0063b8ab --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/index.tsx @@ -0,0 +1,194 @@ +/* + * 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 type { FC, PropsWithChildren } from 'react'; +import React, { useMemo, useState, useEffect } from 'react'; +import { css } from '@emotion/css'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { + EuiButtonEmpty, + EuiTitle, + EuiFlyout, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiTabbedContent, + EuiSpacer, + EuiFlexGroup, + EuiFlexItem, + useGeneratedHtmlId, +} from '@elastic/eui'; +import type { EuiTabbedContentTab, EuiTabbedContentProps, EuiFlyoutProps } from '@elastic/eui'; + +import { + DEFAULT_TRANSLATION_SEVERITY, + DEFAULT_TRANSLATION_FIELDS, +} from '../../../../../common/siem_migrations/constants'; +import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; +import { + RuleOverviewTab, + useOverviewTabSections, +} from '../../../../detection_engine/rule_management/components/rule_details/rule_overview_tab'; +import { + type RuleResponse, + type Severity, +} from '../../../../../common/api/detection_engine/model/rule_schema'; + +import * as i18n from './translations'; +import { + DEFAULT_DESCRIPTION_LIST_COLUMN_WIDTHS, + LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS, +} from './constants'; +import { TranslationTab } from './translation_tab'; + +/* + * Fixes tabs to the top and allows the content to scroll. + */ +const ScrollableFlyoutTabbedContent = (props: EuiTabbedContentProps) => ( + + + + + +); + +const tabPaddingClassName = css` + padding: 0 ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeXL} ${euiThemeVars.euiSizeM}; +`; + +export const TabContentPadding: FC> = ({ children }) => ( +
{children}
+); + +interface MigrationRuleDetailsFlyoutProps { + ruleActions?: React.ReactNode; + ruleMigration: RuleMigration; + size?: EuiFlyoutProps['size']; + extraTabs?: EuiTabbedContentTab[]; + closeFlyout: () => void; +} + +export const MigrationRuleDetailsFlyout: React.FC = React.memo( + ({ + ruleActions, + ruleMigration, + size = 'm', + extraTabs = [], + closeFlyout, + }: MigrationRuleDetailsFlyoutProps) => { + const { expandedOverviewSections, toggleOverviewSection } = useOverviewTabSections(); + + const rule: RuleResponse = useMemo(() => { + const esqlLanguage = ruleMigration.elastic_rule?.query_language ?? 'esql'; + return { + type: esqlLanguage, + language: esqlLanguage, + name: ruleMigration.elastic_rule?.title, + description: ruleMigration.elastic_rule?.description, + query: ruleMigration.elastic_rule?.query, + + ...DEFAULT_TRANSLATION_FIELDS, + severity: + (ruleMigration.elastic_rule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY, + } as RuleResponse; // TODO: we need to adjust RuleOverviewTab to allow partial RuleResponse as a parameter + }, [ruleMigration]); + + const translationTab: EuiTabbedContentTab = useMemo( + () => ({ + id: 'translation', + name: i18n.TRANSLATION_TAB_LABEL, + content: ( + + + + ), + }), + [ruleMigration] + ); + + const overviewTab: EuiTabbedContentTab = useMemo( + () => ({ + id: 'overview', + name: i18n.OVERVIEW_TAB_LABEL, + content: ( + + + + ), + }), + [rule, size, expandedOverviewSections, toggleOverviewSection] + ); + + const tabs = useMemo(() => { + return [...extraTabs, translationTab, overviewTab]; + }, [extraTabs, translationTab, overviewTab]); + + const [selectedTabId, setSelectedTabId] = useState(tabs[0].id); + const selectedTab = tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0]; + + useEffect(() => { + if (!tabs.find((tab) => tab.id === selectedTabId)) { + // Switch to first tab if currently selected tab is not available for this rule + setSelectedTabId(tabs[0].id); + } + }, [tabs, selectedTabId]); + + const onTabClick = (tab: EuiTabbedContentTab) => { + setSelectedTabId(tab.id); + }; + + const migrationsRulesFlyoutTitleId = useGeneratedHtmlId({ + prefix: 'migrationRulesFlyoutTitle', + }); + + return ( + + + +

{rule.name}

+
+ +
+ + + + + + + + {i18n.DISMISS_BUTTON_LABEL} + + + {ruleActions} + + +
+ ); + } +); +MigrationRuleDetailsFlyout.displayName = 'MigrationRuleDetailsFlyout'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/header.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/header.tsx similarity index 82% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/header.tsx rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/header.tsx index 57e99440e60a1..2775c9569917a 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/header.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/header.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiFlexGroup, EuiTitle } from '@elastic/eui'; import * as i18n from './translations'; -export function TranslationTabHeader(): JSX.Element { +export const TranslationTabHeader: React.FC = React.memo(() => { return ( @@ -17,4 +17,5 @@ export function TranslationTabHeader(): JSX.Element { ); -} +}); +TranslationTabHeader.displayName = 'TranslationTabHeader'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/index.tsx similarity index 92% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/index.tsx rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/index.tsx index f2ac76c78434b..a2e590b85ac09 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/index.tsx @@ -22,7 +22,7 @@ import { css } from '@emotion/css'; import { FormattedMessage } from '@kbn/i18n-react'; import type { RuleMigration } from '../../../../../../common/siem_migrations/model/rule_migration.gen'; import { TranslationTabHeader } from './header'; -import { RuleQueryComponent } from './rule_query'; +import { MigrationRuleQuery } from './migration_rule_query'; import * as i18n from './translations'; import { convertTranslationResultIntoColor, @@ -33,7 +33,7 @@ interface TranslationTabProps { ruleMigration: RuleMigration; } -export const TranslationTab = ({ ruleMigration }: TranslationTabProps) => { +export const TranslationTab: React.FC = React.memo(({ ruleMigration }) => { const { euiTheme } = useEuiTheme(); const name = ruleMigration.elastic_rule?.title ?? ruleMigration.original_rule.title; @@ -81,7 +81,7 @@ export const TranslationTab = ({ ruleMigration }: TranslationTabProps) => { - { `} /> - { ); -}; +}); +TranslationTab.displayName = 'TranslationTab'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/migration_rule_query.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/migration_rule_query.tsx new file mode 100644 index 0000000000000..534f765da97bc --- /dev/null +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/migration_rule_query.tsx @@ -0,0 +1,73 @@ +/* + * 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, { useMemo } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiMarkdownEditor, + EuiMarkdownFormat, + EuiTitle, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import * as i18n from './translations'; + +interface MigrationRuleQueryProps { + title: string; + query: string; + canEdit?: boolean; +} + +export const MigrationRuleQuery: React.FC = React.memo( + ({ title, query, canEdit }) => { + const { euiTheme } = useEuiTheme(); + + const headerComponent = useMemo(() => { + return ( + + + +

{title}

+
+
+
+ ); + }, [euiTheme, title]); + + const queryTextComponent = useMemo(() => { + if (canEdit) { + return ( + {}} + height={400} + initialViewMode={'viewing'} + /> + ); + } else { + return {query}; + } + }, [canEdit, query]); + + return ( + <> + {headerComponent} + + {queryTextComponent} + + ); + } +); +MigrationRuleQuery.displayName = 'MigrationRuleQuery'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/translations.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translation_tab/translations.ts diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translations.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translations.ts similarity index 100% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translations.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rule_details_flyout/translations.ts diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/no_items_message.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/empty_migration.tsx similarity index 93% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/no_items_message.tsx rename to x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/empty_migration.tsx index 7aeaac7ab2f6b..a8bd20d4a557e 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/no_items_message.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/empty_migration.tsx @@ -11,7 +11,7 @@ import { SecurityPageName } from '../../../../../common'; import { useGetSecuritySolutionLinkProps } from '../../../../common/components/links'; import * as i18n from './translations'; -const NoItemsMessageComponent = () => { +export const EmptyMigration: React.FC = React.memo(() => { const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); const { onClick: onClickLink } = getSecuritySolutionLinkProps({ deepLinkId: SecurityPageName.landing, @@ -47,6 +47,5 @@ const NoItemsMessageComponent = () => {
); -}; - -export const NoItemsMessage = React.memo(NoItemsMessageComponent); +}); +EmptyMigration.displayName = 'EmptyMigration'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx index 82793d3e1fd8c..e7af1af93e2ba 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/index.tsx @@ -17,20 +17,22 @@ import { } from '@elastic/eui'; import React, { useCallback, useMemo, useState } from 'react'; +import { useAppToasts } from '../../../../common/hooks/use_app_toasts'; import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; -import { NoItemsMessage } from './no_items_message'; -import { useRulesTableColumns } from '../../hooks/use_rules_table_columns'; -import { useRulePreviewFlyout } from '../../hooks/use_rule_preview_flyout'; +import { EmptyMigration } from './empty_migration'; +import { useMigrationRulesTableColumns } from '../../hooks/use_migration_rules_table_columns'; +import { useMigrationRuleDetailsFlyout } from '../../hooks/use_migration_rule_preview_flyout'; import { useInstallMigrationRules } from '../../logic/use_install_migration_rules'; import { useGetMigrationRules } from '../../logic/use_get_migration_rules'; import { useInstallTranslatedMigrationRules } from '../../logic/use_install_translated_migration_rules'; -import { BulkActions } from './bulk_actions'; import { useGetMigrationTranslationStats } from '../../logic/use_get_migration_translation_stats'; +import * as logicI18n from '../../logic/translations'; +import { BulkActions } from './bulk_actions'; import { SearchField } from './search_field'; const DEFAULT_PAGE_SIZE = 10; -export interface RulesTableComponentProps { +export interface MigrationRulesTableProps { /** * Selected rule migration id */ @@ -40,143 +42,152 @@ export interface RulesTableComponentProps { /** * Table Component for displaying SIEM rules migrations */ -const RulesTableComponent: React.FC = ({ migrationId }) => { - const [pageIndex, setPageIndex] = useState(0); - const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); - const [searchTerm, setSearchTerm] = useState(); - - const { data: translationStats, isLoading: isStatsLoading } = - useGetMigrationTranslationStats(migrationId); - - const { - data: { ruleMigrations, total } = { ruleMigrations: [], total: 0 }, - isLoading: isDataLoading, - } = useGetMigrationRules({ - migrationId, - page: pageIndex, - perPage: pageSize, - searchTerm, - }); - - const [selectedRuleMigrations, setSelectedRuleMigrations] = useState([]); - - const pagination = useMemo(() => { - return { - pageIndex, - pageSize, - totalItemCount: total, - }; - }, [pageIndex, pageSize, total]); - - const onTableChange = useCallback(({ page, sort }: CriteriaWithPagination) => { - if (page) { - setPageIndex(page.index); - setPageSize(page.size); - } - }, []); - - const handleOnSearch = useCallback((value: string) => { - setSearchTerm(value.trim()); - }, []); - - const { mutateAsync: installMigrationRules } = useInstallMigrationRules(migrationId); - const { mutateAsync: installTranslatedMigrationRules } = - useInstallTranslatedMigrationRules(migrationId); - - const [isTableLoading, setTableLoading] = useState(false); - const installSingleRule = useCallback( - async (migrationRule: RuleMigration, enable?: boolean) => { - setTableLoading(true); - try { - await installMigrationRules([migrationRule.id]); - } finally { - setTableLoading(false); - } - }, - [installMigrationRules] - ); - - const installTranslatedRules = useCallback( - async (enable?: boolean) => { - setTableLoading(true); - try { - await installTranslatedMigrationRules(); - } finally { - setTableLoading(false); +export const MigrationRulesTable: React.FC = React.memo( + ({ migrationId }) => { + const { addError } = useAppToasts(); + + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [searchTerm, setSearchTerm] = useState(); + + const { data: translationStats, isLoading: isStatsLoading } = + useGetMigrationTranslationStats(migrationId); + + const { + data: { ruleMigrations, total } = { ruleMigrations: [], total: 0 }, + isLoading: isDataLoading, + } = useGetMigrationRules({ + migrationId, + page: pageIndex, + perPage: pageSize, + searchTerm, + }); + + const [selectedRuleMigrations, setSelectedRuleMigrations] = useState([]); + + const pagination = useMemo(() => { + return { + pageIndex, + pageSize, + totalItemCount: total, + }; + }, [pageIndex, pageSize, total]); + + const onTableChange = useCallback(({ page, sort }: CriteriaWithPagination) => { + if (page) { + setPageIndex(page.index); + setPageSize(page.size); } - }, - [installTranslatedMigrationRules] - ); - - const ruleActionsFactory = useCallback( - (ruleMigration: RuleMigration, closeRulePreview: () => void) => { - // TODO: Add flyout action buttons - return null; - }, - [] - ); - - const { rulePreviewFlyout, openRulePreview } = useRulePreviewFlyout({ - ruleActionsFactory, - }); - - const rulesColumns = useRulesTableColumns({ - disableActions: isTableLoading, - openMigrationRulePreview: openRulePreview, - installMigrationRule: installSingleRule, - }); - - return ( - <> - - - - + }, []); + + const handleOnSearch = useCallback((value: string) => { + setSearchTerm(value.trim()); + }, []); + + const { mutateAsync: installMigrationRules } = useInstallMigrationRules(migrationId); + const { mutateAsync: installTranslatedMigrationRules } = + useInstallTranslatedMigrationRules(migrationId); + + const [isTableLoading, setTableLoading] = useState(false); + const installSingleRule = useCallback( + async (migrationRule: RuleMigration, enable?: boolean) => { + setTableLoading(true); + try { + await installMigrationRules([migrationRule.id]); + } catch (error) { + addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE }); + } finally { + setTableLoading(false); } - loadedContent={ - !translationStats?.rules.total ? ( - - ) : ( + }, + [addError, installMigrationRules] + ); + + const installTranslatedRules = useCallback( + async (enable?: boolean) => { + setTableLoading(true); + try { + await installTranslatedMigrationRules(); + } catch (error) { + addError(error, { title: logicI18n.INSTALL_MIGRATION_RULES_FAILURE }); + } finally { + setTableLoading(false); + } + }, + [addError, installTranslatedMigrationRules] + ); + + const ruleActionsFactory = useCallback( + (ruleMigration: RuleMigration, closeRulePreview: () => void) => { + // TODO: Add flyout action buttons + return null; + }, + [] + ); + + const { + migrationRuleDetailsFlyout: rulePreviewFlyout, + openMigrationRuleDetails: openRulePreview, + } = useMigrationRuleDetailsFlyout({ + ruleActionsFactory, + }); + + const rulesColumns = useMigrationRulesTableColumns({ + disableActions: isTableLoading, + openMigrationRuleDetails: openRulePreview, + installMigrationRule: installSingleRule, + }); + + return ( + <> + - - - - - - - - - - - loading={isTableLoading} - items={ruleMigrations} - pagination={pagination} - onChange={onTableChange} - selection={{ - selectable: () => true, - onSelectionChange: setSelectedRuleMigrations, - initialSelected: selectedRuleMigrations, - }} - itemId={'id'} - data-test-subj={'rules-translation-table'} - columns={rulesColumns} - /> + + - ) - } - /> - {rulePreviewFlyout} - - ); -}; - -export const RulesTable = React.memo(RulesTableComponent); -RulesTable.displayName = 'RulesTable'; + } + loadedContent={ + !translationStats?.rules.total ? ( + + ) : ( + <> + + + + + + + + + + + loading={isTableLoading} + items={ruleMigrations} + pagination={pagination} + onChange={onTableChange} + selection={{ + selectable: () => true, + onSelectionChange: setSelectedRuleMigrations, + initialSelected: selectedRuleMigrations, + }} + itemId={'id'} + data-test-subj={'rules-translation-table'} + columns={rulesColumns} + /> + + ) + } + /> + {rulePreviewFlyout} + + ); + } +); +MigrationRulesTable.displayName = 'MigrationRulesTable'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/search_field.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/search_field.tsx index 5bd18851ba595..7b7a576cb1dd9 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/search_field.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table/search_field.tsx @@ -7,19 +7,9 @@ import type { ChangeEvent } from 'react'; import React, { useCallback, useEffect, useState } from 'react'; -import styled from 'styled-components'; import { EuiFieldSearch, EuiFlexItem } from '@elastic/eui'; import * as i18n from './translations'; -const SearchBarWrapper = styled(EuiFlexItem)` - min-width: 200px; - & .euiPopover { - // This is needed to "cancel" styles passed down from EuiTourStep that - // interfere with EuiFieldSearch and don't allow it to take the full width - display: block; - } -`; - interface SearchFieldProps { initialValue?: string; onSearch: (value: string) => void; @@ -39,7 +29,7 @@ export const SearchField: React.FC = React.memo( }, [initialValue]); return ( - + = React.memo( onSearch={onSearch} data-test-subj="ruleSearchField" /> - + ); } ); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx index 7122949dee907..45de70582d4b1 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/actions.tsx @@ -17,14 +17,14 @@ import type { TableColumn } from './constants'; interface ActionNameProps { disableActions?: boolean; migrationRule: RuleMigration; - openMigrationRulePreview: (migrationRule: RuleMigration) => void; + openMigrationRuleDetails: (migrationRule: RuleMigration) => void; installMigrationRule: (migrationRule: RuleMigration, enable?: boolean) => void; } const ActionName = ({ disableActions, migrationRule, - openMigrationRulePreview, + openMigrationRuleDetails, installMigrationRule, }: ActionNameProps) => { const { navigateToApp } = useKibana().services.application; @@ -72,7 +72,7 @@ const ActionName = ({ { - openMigrationRulePreview(migrationRule); + openMigrationRuleDetails(migrationRule); }} data-test-subj="editRule" > @@ -83,13 +83,13 @@ const ActionName = ({ interface CreateActionsColumnProps { disableActions?: boolean; - openMigrationRulePreview: (migrationRule: RuleMigration) => void; + openMigrationRuleDetails: (migrationRule: RuleMigration) => void; installMigrationRule: (migrationRule: RuleMigration, enable?: boolean) => void; } export const createActionsColumn = ({ disableActions, - openMigrationRulePreview, + openMigrationRuleDetails, installMigrationRule, }: CreateActionsColumnProps): TableColumn => { return { @@ -100,7 +100,7 @@ export const createActionsColumn = ({ ); diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx index 7b7cf228895fc..085a2f5c6a254 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/rules_table_columns/name.tsx @@ -14,14 +14,14 @@ import type { TableColumn } from './constants'; interface NameProps { name: string; rule: RuleMigration; - openMigrationRulePreview: (rule: RuleMigration) => void; + openMigrationRuleDetails: (rule: RuleMigration) => void; } -const Name = ({ name, rule, openMigrationRulePreview }: NameProps) => { +const Name = ({ name, rule, openMigrationRuleDetails }: NameProps) => { return ( { - openMigrationRulePreview(rule); + openMigrationRuleDetails(rule); }} data-test-subj="ruleName" > @@ -31,15 +31,15 @@ const Name = ({ name, rule, openMigrationRulePreview }: NameProps) => { }; export const createNameColumn = ({ - openMigrationRulePreview, + openMigrationRuleDetails, }: { - openMigrationRulePreview: (rule: RuleMigration) => void; + openMigrationRuleDetails: (rule: RuleMigration) => void; }): TableColumn => { return { field: 'original_rule.title', name: i18n.COLUMN_NAME, render: (value: RuleMigration['original_rule']['title'], rule: RuleMigration) => ( - + ), sortable: true, truncateText: true, diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx index 171fe0c451826..60f0ed94862ca 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/status_badge/index.tsx @@ -19,31 +19,27 @@ const statusToColorMap: Record = { untranslatable: euiColorVis9, }; -interface Props { +interface StatusBadgeProps { value?: RuleMigrationTranslationResult; installedRuleId?: string; 'data-test-subj'?: string; } -const StatusBadgeComponent: React.FC = ({ - value, - installedRuleId, - 'data-test-subj': dataTestSubj = 'translation-result', -}) => { - const translationResult = installedRuleId ? 'full' : value ?? 'untranslatable'; - const displayValue = convertTranslationResultIntoText(translationResult); - const color = statusToColorMap[translationResult]; +export const StatusBadge: React.FC = React.memo( + ({ value, installedRuleId, 'data-test-subj': dataTestSubj = 'translation-result' }) => { + const translationResult = installedRuleId ? 'full' : value ?? 'untranslatable'; + const displayValue = convertTranslationResultIntoText(translationResult); + const color = statusToColorMap[translationResult]; - return ( - - {displayValue} - - ); -}; - -export const StatusBadge = React.memo(StatusBadgeComponent); + return ( + + {displayValue} + + ); + } +); StatusBadge.displayName = 'StatusBadge'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx deleted file mode 100644 index b6dce09c311e1..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/index.tsx +++ /dev/null @@ -1,246 +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 type { FC, PropsWithChildren } from 'react'; -import React, { useMemo, useState, useEffect } from 'react'; -import styled from 'styled-components'; -import { css } from '@emotion/css'; -import { euiThemeVars } from '@kbn/ui-theme'; -import { - EuiButtonEmpty, - EuiTitle, - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiTabbedContent, - EuiSpacer, - EuiFlexGroup, - EuiFlexItem, - useGeneratedHtmlId, -} from '@elastic/eui'; -import type { EuiTabbedContentTab, EuiTabbedContentProps, EuiFlyoutProps } from '@elastic/eui'; - -import { - DEFAULT_TRANSLATION_RISK_SCORE, - DEFAULT_TRANSLATION_SEVERITY, -} from '../../../../../common/siem_migrations/constants'; -import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen'; -import { - RuleOverviewTab, - useOverviewTabSections, -} from '../../../../detection_engine/rule_management/components/rule_details/rule_overview_tab'; -import { - type RuleResponse, - type Severity, -} from '../../../../../common/api/detection_engine/model/rule_schema'; - -import * as i18n from './translations'; -import { - DEFAULT_DESCRIPTION_LIST_COLUMN_WIDTHS, - LARGE_DESCRIPTION_LIST_COLUMN_WIDTHS, -} from './constants'; -import { TranslationTab } from './translation_tab'; - -const StyledEuiFlyoutBody = styled(EuiFlyoutBody)` - .euiFlyoutBody__overflow { - display: flex; - flex: 1; - overflow: hidden; - - .euiFlyoutBody__overflowContent { - flex: 1; - overflow: hidden; - padding: ${({ theme }) => `0 ${theme.eui.euiSizeL} 0`}; - } - } -`; - -const StyledFlexGroup = styled(EuiFlexGroup)` - height: 100%; -`; - -const StyledEuiFlexItem = styled(EuiFlexItem)` - &.euiFlexItem { - flex: 1 0 0; - overflow: hidden; - } -`; - -const StyledEuiTabbedContent = styled(EuiTabbedContent)` - display: flex; - flex: 1; - flex-direction: column; - overflow: hidden; - - > [role='tabpanel'] { - display: flex; - flex: 1; - flex-direction: column; - overflow: hidden; - overflow-y: auto; - - ::-webkit-scrollbar { - -webkit-appearance: none; - width: 7px; - } - - ::-webkit-scrollbar-thumb { - border-radius: 4px; - background-color: rgba(0, 0, 0, 0.5); - -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.5); - } - } -`; - -/* - * Fixes tabs to the top and allows the content to scroll. - */ -const ScrollableFlyoutTabbedContent = (props: EuiTabbedContentProps) => ( - - - - - -); - -const tabPaddingClassName = css` - padding: 0 ${euiThemeVars.euiSizeM} ${euiThemeVars.euiSizeXL} ${euiThemeVars.euiSizeM}; -`; - -export const TabContentPadding: FC> = ({ children }) => ( -
{children}
-); - -interface TranslationDetailsFlyoutProps { - ruleActions?: React.ReactNode; - ruleMigration: RuleMigration; - size?: EuiFlyoutProps['size']; - extraTabs?: EuiTabbedContentTab[]; - closeFlyout: () => void; -} - -export const TranslationDetailsFlyout = ({ - ruleActions, - ruleMigration, - size = 'm', - extraTabs = [], - closeFlyout, -}: TranslationDetailsFlyoutProps) => { - const { expandedOverviewSections, toggleOverviewSection } = useOverviewTabSections(); - - const rule: RuleResponse = useMemo(() => { - const esqlLanguage = ruleMigration.elastic_rule?.query_language ?? 'esql'; - return { - type: esqlLanguage, - language: esqlLanguage, - name: ruleMigration.elastic_rule?.title, - description: ruleMigration.elastic_rule?.description, - query: ruleMigration.elastic_rule?.query, - - // Default values - severity: (ruleMigration.elastic_rule?.severity as Severity) ?? DEFAULT_TRANSLATION_SEVERITY, - risk_score: DEFAULT_TRANSLATION_RISK_SCORE, - from: 'now-360s', - to: 'now', - interval: '5m', - } as RuleResponse; // TODO: we need to adjust RuleOverviewTab to allow partial RuleResponse as a parameter - }, [ruleMigration]); - - const translationTab: EuiTabbedContentTab = useMemo( - () => ({ - id: 'translation', - name: i18n.TRANSLATION_TAB_LABEL, - content: ( - - - - ), - }), - [ruleMigration] - ); - - const overviewTab: EuiTabbedContentTab = useMemo( - () => ({ - id: 'overview', - name: i18n.OVERVIEW_TAB_LABEL, - content: ( - - - - ), - }), - [rule, size, expandedOverviewSections, toggleOverviewSection] - ); - - const tabs = useMemo(() => { - return [...extraTabs, translationTab, overviewTab]; - }, [extraTabs, translationTab, overviewTab]); - - const [selectedTabId, setSelectedTabId] = useState(tabs[0].id); - const selectedTab = tabs.find((tab) => tab.id === selectedTabId) ?? tabs[0]; - - useEffect(() => { - if (!tabs.find((tab) => tab.id === selectedTabId)) { - // Switch to first tab if currently selected tab is not available for this rule - setSelectedTabId(tabs[0].id); - } - }, [tabs, selectedTabId]); - - const onTabClick = (tab: EuiTabbedContentTab) => { - setSelectedTabId(tab.id); - }; - - const migrationsRulesFlyoutTitleId = useGeneratedHtmlId({ - prefix: 'migrationRulesFlyoutTitle', - }); - - return ( - - - -

{rule.name}

-
- -
- - - - - - - - {i18n.DISMISS_BUTTON_LABEL} - - - {ruleActions} - - -
- ); -}; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx deleted file mode 100644 index 50977cafb18d0..0000000000000 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/components/translation_details_flyout/translation_tab/rule_query.tsx +++ /dev/null @@ -1,49 +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 React, { useMemo } from 'react'; -import { EuiMarkdownEditor, EuiMarkdownFormat, EuiTitle } from '@elastic/eui'; -import { SideHeader } from '../../../../../detection_engine/rule_management/components/rule_details/three_way_diff/components/side_header'; -import { FinalSideHelpInfo } from '../../../../../detection_engine/rule_management/components/rule_details/three_way_diff/final_side/final_side_help_info'; -import * as i18n from './translations'; - -interface RuleQueryProps { - title: string; - query: string; - canEdit?: boolean; -} - -export const RuleQueryComponent = ({ title, query, canEdit }: RuleQueryProps) => { - const queryTextComponent = useMemo(() => { - if (canEdit) { - return ( - {}} - height={400} - initialViewMode={'viewing'} - /> - ); - } else { - return {query}; - } - }, [canEdit, query]); - return ( - <> - - -

- {title} - -

-
-
- {queryTextComponent} - - ); -}; 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 index 0a33869eff418..dd48d3b357e18 100644 --- 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 @@ -9,7 +9,7 @@ import React from 'react'; import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import * as i18n from './translations'; -const UnknownMigrationComponent = () => { +export const UnknownMigration: React.FC = React.memo(() => { return ( { title={

{i18n.UNKNOWN_MIGRATION}

} titleSize="s" body={i18n.UNKNOWN_MIGRATION_BODY} - data-test-subj="noMigrationsAvailable" + data-test-subj="unknownMigration" />
); -}; - -export const UnknownMigration = React.memo(UnknownMigrationComponent); +}); UnknownMigration.displayName = 'UnknownMigration'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rule_preview_flyout.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rule_preview_flyout.tsx similarity index 55% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rule_preview_flyout.tsx rename to x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rule_preview_flyout.tsx index 1721b4e280aad..4823e48de97c6 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rule_preview_flyout.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rule_preview_flyout.tsx @@ -9,28 +9,28 @@ import type { ReactNode } from 'react'; import React, { useCallback, useState, useMemo } from 'react'; import type { EuiTabbedContentTab } from '@elastic/eui'; import type { RuleMigration } from '../../../../common/siem_migrations/model/rule_migration.gen'; -import { TranslationDetailsFlyout } from '../components/translation_details_flyout'; +import { MigrationRuleDetailsFlyout } from '../components/rule_details_flyout'; -interface UseRulePreviewFlyoutParams { +interface UseMigrationRuleDetailsFlyoutParams { ruleActionsFactory: (ruleMigration: RuleMigration, closeRulePreview: () => void) => ReactNode; extraTabsFactory?: (ruleMigration: RuleMigration) => EuiTabbedContentTab[]; } -interface UseRulePreviewFlyoutResult { - rulePreviewFlyout: ReactNode; - openRulePreview: (rule: RuleMigration) => void; - closeRulePreview: () => void; +interface UseMigrationRuleDetailsFlyoutResult { + migrationRuleDetailsFlyout: ReactNode; + openMigrationRuleDetails: (rule: RuleMigration) => void; + closeMigrationRuleDetails: () => void; } -export function useRulePreviewFlyout({ +export function useMigrationRuleDetailsFlyout({ extraTabsFactory, ruleActionsFactory, -}: UseRulePreviewFlyoutParams): UseRulePreviewFlyoutResult { - const [ruleMigration, setRuleMigrationForPreview] = useState(); - const closeRulePreview = useCallback(() => setRuleMigrationForPreview(undefined), []); +}: UseMigrationRuleDetailsFlyoutParams): UseMigrationRuleDetailsFlyoutResult { + const [ruleMigration, setMigrationRuleForPreview] = useState(); + const closeMigrationRuleDetails = useCallback(() => setMigrationRuleForPreview(undefined), []); const ruleActions = useMemo( - () => ruleMigration && ruleActionsFactory(ruleMigration, closeRulePreview), - [ruleMigration, ruleActionsFactory, closeRulePreview] + () => ruleMigration && ruleActionsFactory(ruleMigration, closeMigrationRuleDetails), + [ruleMigration, ruleActionsFactory, closeMigrationRuleDetails] ); const extraTabs = useMemo( () => (ruleMigration && extraTabsFactory ? extraTabsFactory(ruleMigration) : []), @@ -38,18 +38,18 @@ export function useRulePreviewFlyout({ ); return { - rulePreviewFlyout: ruleMigration && ( - ), - openRulePreview: useCallback((rule: RuleMigration) => { - setRuleMigrationForPreview(rule); + openMigrationRuleDetails: useCallback((rule: RuleMigration) => { + setMigrationRuleForPreview(rule); }, []), - closeRulePreview, + closeMigrationRuleDetails, }; } diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx similarity index 78% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx rename to x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx index 219d2f17de441..b8b37bccaffd3 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_rules_table_columns.tsx +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/hooks/use_migration_rules_table_columns.tsx @@ -17,28 +17,28 @@ import { createUpdatedColumn, } from '../components/rules_table_columns'; -export const useRulesTableColumns = ({ +export const useMigrationRulesTableColumns = ({ disableActions, - openMigrationRulePreview, + openMigrationRuleDetails, installMigrationRule, }: { disableActions?: boolean; - openMigrationRulePreview: (rule: RuleMigration) => void; + openMigrationRuleDetails: (rule: RuleMigration) => void; installMigrationRule: (migrationRule: RuleMigration, enable?: boolean) => void; }): TableColumn[] => { return useMemo( () => [ createUpdatedColumn(), - createNameColumn({ openMigrationRulePreview }), + createNameColumn({ openMigrationRuleDetails }), createStatusColumn(), createRiskScoreColumn(), createSeverityColumn(), createActionsColumn({ disableActions, - openMigrationRulePreview, + openMigrationRuleDetails, installMigrationRule, }), ], - [disableActions, installMigrationRule, openMigrationRulePreview] + [disableActions, installMigrationRule, openMigrationRuleDetails] ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/constants.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/constants.ts similarity index 100% rename from x-pack/plugins/security_solution/public/siem_migrations/rules/api/hooks/constants.ts rename to x-pack/plugins/security_solution/public/siem_migrations/rules/logic/constants.ts diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts index 92f06b2e37428..5f59ceb9f76c2 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_rules.ts @@ -5,9 +5,14 @@ * 2.0. */ +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { replaceParams } from '@kbn/openapi-common/shared'; +import { useCallback } from 'react'; +import { SIEM_RULE_MIGRATION_PATH } from '../../../../common/siem_migrations/constants'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useGetMigrationRulesQuery } from '../api/hooks/use_get_migration_rules_query'; import * as i18n from './translations'; +import { getRuleMigrations } from '../api'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; export const useGetMigrationRules = (params: { migrationId: string; @@ -17,9 +22,47 @@ export const useGetMigrationRules = (params: { }) => { const { addError } = useAppToasts(); - return useGetMigrationRulesQuery(params, { - onError: (error) => { - addError(error, { title: i18n.GET_MIGRATION_RULES_FAILURE }); + const { migrationId } = params; + const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, { + migration_id: migrationId, + }); + + return useQuery( + ['GET', SPECIFIC_MIGRATION_PATH, params], + async ({ signal }) => { + const response = await getRuleMigrations({ signal, ...params }); + + return { ruleMigrations: response.data, total: response.total }; }, + { + ...DEFAULT_QUERY_OPTIONS, + onError: (error) => { + addError(error, { title: i18n.GET_MIGRATION_RULES_FAILURE }); + }, + } + ); +}; + +/** + * We should use this hook to invalidate the rule migrations cache. For + * example, rule migrations mutations, like installing a rule, should lead to cache invalidation. + * + * @returns A rule migrations cache invalidation callback + */ +export const useInvalidateGetMigrationRules = (migrationId: string) => { + const queryClient = useQueryClient(); + + const SPECIFIC_MIGRATION_PATH = replaceParams(SIEM_RULE_MIGRATION_PATH, { + migration_id: migrationId, }); + + return useCallback(() => { + /** + * Invalidate all queries that start with SPECIFIC_MIGRATION_PATH. This + * includes the in-memory query cache and paged query cache. + */ + queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_PATH], { + refetchType: 'active', + }); + }, [SPECIFIC_MIGRATION_PATH, queryClient]); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_translation_stats.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_translation_stats.ts index 081876ba266a9..b19a1133e3061 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_translation_stats.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_get_migration_translation_stats.ts @@ -5,16 +5,58 @@ * 2.0. */ +import { replaceParams } from '@kbn/openapi-common/shared'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCallback } from 'react'; +import type { GetRuleMigrationTranslationStatsResponse } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH } from '../../../../common/siem_migrations/constants'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useGetMigrationTranslationStatsQuery } from '../api/hooks/use_get_migration_translation_stats_query'; import * as i18n from './translations'; +import { getRuleMigrationTranslationStats } from '../api'; +import { DEFAULT_QUERY_OPTIONS } from './constants'; export const useGetMigrationTranslationStats = (migrationId: string) => { const { addError } = useAppToasts(); - return useGetMigrationTranslationStatsQuery(migrationId, { - onError: (error) => { - addError(error, { title: i18n.GET_MIGRATION_TRANSLATION_STATS_FAILURE }); + const SPECIFIC_MIGRATION_TRANSLATION_PATH = replaceParams( + SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, + { + migration_id: migrationId, + } + ); + return useQuery( + ['GET', SPECIFIC_MIGRATION_TRANSLATION_PATH], + async ({ signal }) => { + return getRuleMigrationTranslationStats({ migrationId, signal }); }, - }); + { + ...DEFAULT_QUERY_OPTIONS, + onError: (error) => { + addError(error, { title: i18n.GET_MIGRATION_TRANSLATION_STATS_FAILURE }); + }, + } + ); +}; + +/** + * We should use this hook to invalidate the translation stats cache. For + * example, rule migrations mutations, like installing a rule, should lead to cache invalidation. + * + * @returns A translation stats cache invalidation callback + */ +export const useInvalidateGetMigrationTranslationStats = (migrationId: string) => { + const queryClient = useQueryClient(); + + const SPECIFIC_MIGRATION_TRANSLATION_PATH = replaceParams( + SIEM_RULE_MIGRATION_TRANSLATION_STATS_PATH, + { + migration_id: migrationId, + } + ); + + return useCallback(() => { + queryClient.invalidateQueries(['GET', SPECIFIC_MIGRATION_TRANSLATION_PATH], { + refetchType: 'active', + }); + }, [SPECIFIC_MIGRATION_TRANSLATION_PATH, queryClient]); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts index dcc19f290f87f..755faa03bff14 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_migration_rules.ts @@ -5,16 +5,35 @@ * 2.0. */ +import { useMutation } from '@tanstack/react-query'; +import { SIEM_RULE_MIGRATION_INSTALL_PATH } from '../../../../common/siem_migrations/constants'; +import type { InstallMigrationRulesResponse } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useInstallMigrationRulesMutation } from '../api/hooks/use_install_migration_rules_mutation'; import * as i18n from './translations'; +import { useInvalidateGetMigrationRules } from './use_get_migration_rules'; +import { useInvalidateGetMigrationTranslationStats } from './use_get_migration_translation_stats'; +import { installMigrationRules } from '../api'; + +export const INSTALL_MIGRATION_RULES_MUTATION_KEY = ['POST', SIEM_RULE_MIGRATION_INSTALL_PATH]; export const useInstallMigrationRules = (migrationId: string) => { const { addError } = useAppToasts(); - return useInstallMigrationRulesMutation(migrationId, { - onError: (error) => { - addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE }); - }, - }); + const invalidateGetRuleMigrations = useInvalidateGetMigrationRules(migrationId); + const invalidateGetMigrationTranslationStats = + useInvalidateGetMigrationTranslationStats(migrationId); + + return useMutation( + (ids: string[]) => installMigrationRules({ migrationId, ids }), + { + mutationKey: INSTALL_MIGRATION_RULES_MUTATION_KEY, + onError: (error) => { + addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE }); + }, + onSettled: () => { + invalidateGetRuleMigrations(); + invalidateGetMigrationTranslationStats(); + }, + } + ); }; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_translated_migration_rules.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_translated_migration_rules.ts index 67ee3f099aca0..b0d9e11136396 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_translated_migration_rules.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/logic/use_install_translated_migration_rules.ts @@ -5,16 +5,38 @@ * 2.0. */ +import { useMutation } from '@tanstack/react-query'; +import type { InstallTranslatedMigrationRulesResponse } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; +import { SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH } from '../../../../common/siem_migrations/constants'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; -import { useInstallTranslatedMigrationRulesMutation } from '../api/hooks/use_install_translated_migration_rules_mutation'; import * as i18n from './translations'; +import { useInvalidateGetMigrationRules } from './use_get_migration_rules'; +import { useInvalidateGetMigrationTranslationStats } from './use_get_migration_translation_stats'; +import { installTranslatedMigrationRules } from '../api'; + +export const INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY = [ + 'POST', + SIEM_RULE_MIGRATION_INSTALL_TRANSLATED_PATH, +]; export const useInstallTranslatedMigrationRules = (migrationId: string) => { const { addError } = useAppToasts(); - return useInstallTranslatedMigrationRulesMutation(migrationId, { - onError: (error) => { - addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE }); - }, - }); + const invalidateGetRuleMigrations = useInvalidateGetMigrationRules(migrationId); + const invalidateGetMigrationTranslationStats = + useInvalidateGetMigrationTranslationStats(migrationId); + + return useMutation( + () => installTranslatedMigrationRules({ migrationId }), + { + mutationKey: INSTALL_ALL_MIGRATION_RULES_MUTATION_KEY, + onError: (error) => { + addError(error, { title: i18n.INSTALL_MIGRATION_RULES_FAILURE }); + }, + onSettled: () => { + invalidateGetRuleMigrations(); + invalidateGetMigrationTranslationStats(); + }, + } + ); }; 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 dabdb83cccbab..018aa5d77559e 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 @@ -9,22 +9,23 @@ import React, { useEffect, useMemo } from 'react'; import { EuiSkeletonLoading, EuiSkeletonText, EuiSkeletonTitle } from '@elastic/eui'; import type { RouteComponentProps } from 'react-router-dom'; +import { SiemMigrationTaskStatus } from '../../../../common/siem_migrations/constants'; import { useNavigation } from '../../../common/lib/kibana'; 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 { MigrationRulesTable } 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 { UnknownMigration } from '../components/unknown_migration'; import { useLatestStats } from '../hooks/use_latest_stats'; -type RulesMigrationPageProps = RouteComponentProps<{ migrationId?: string }>; +type MigrationRulesPageProps = RouteComponentProps<{ migrationId?: string }>; -export const RulesPage: React.FC = React.memo( +export const MigrationRulesPage: React.FC = React.memo( ({ match: { params: { migrationId }, @@ -34,13 +35,13 @@ export const RulesPage: React.FC = React.memo( const { data: ruleMigrationsStatsAll, isLoading: isLoadingMigrationsStats } = useLatestStats(); - const migrationsIds = useMemo(() => { + const finishedRuleMigrationsStats = useMemo(() => { if (isLoadingMigrationsStats || !ruleMigrationsStatsAll?.length) { return []; } - return ruleMigrationsStatsAll - .filter((migration) => migration.status === 'finished') - .map((migration) => migration.id); + return ruleMigrationsStatsAll.filter( + (migration) => migration.status === SiemMigrationTaskStatus.FINISHED + ); }, [isLoadingMigrationsStats, ruleMigrationsStatsAll]); useEffect(() => { @@ -49,27 +50,30 @@ export const RulesPage: React.FC = React.memo( } // Navigate to landing page if there are no migrations - if (!migrationsIds.length) { + if (!finishedRuleMigrationsStats.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] }); + navigateTo({ + deepLinkId: SecurityPageName.siemMigrationsRules, + path: finishedRuleMigrationsStats[0].id, + }); } - }, [isLoadingMigrationsStats, migrationId, migrationsIds, navigateTo]); + }, [isLoadingMigrationsStats, migrationId, finishedRuleMigrationsStats, navigateTo]); const onMigrationIdChange = (selectedId?: string) => { navigateTo({ deepLinkId: SecurityPageName.siemMigrationsRules, path: selectedId }); }; const content = useMemo(() => { - if (!migrationId || !migrationsIds.includes(migrationId)) { + if (!migrationId || !finishedRuleMigrationsStats.some((stats) => stats.id === migrationId)) { return ; } - return ; - }, [migrationId, migrationsIds]); + return ; + }, [migrationId, finishedRuleMigrationsStats]); return ( <> @@ -79,7 +83,7 @@ export const RulesPage: React.FC = React.memo( @@ -99,4 +103,4 @@ export const RulesPage: React.FC = React.memo( ); } ); -RulesPage.displayName = 'RulesPage'; +MigrationRulesPage.displayName = 'MigrationRulesPage'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts index a872d79a46027..3162cc3d58e63 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts @@ -12,7 +12,7 @@ import { SiemMigrationTaskStatus } from '../../../../common/siem_migrations/cons import type { StartPluginsDependencies } from '../../../types'; import { ExperimentalFeaturesService } from '../../../common/experimental_features_service'; import { licenseService } from '../../../common/hooks/use_license'; -import { getRuleMigrationsStatsAll, startRuleMigration } from '../api/api'; +import { getRuleMigrationsStatsAll, startRuleMigration } from '../api'; import type { RuleMigrationTask } from '../types'; import { getSuccessToast } from './success_notification'; import { RuleMigrationsStorage } from './storage'; diff --git a/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts b/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts index b395fa0199de8..4c704e97179c0 100644 --- a/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts +++ b/x-pack/plugins/security_solution/public/siem_migrations/rules/types.ts @@ -11,14 +11,3 @@ export interface RuleMigrationTask extends RuleMigrationTaskStats { /** The sequential number of the migration */ number: number; } - -export interface InstallRulesProps { - migrationId: string; - ids: string[]; - signal?: AbortSignal; -} - -export interface InstallTranslatedRulesProps { - migrationId: string; - signal?: AbortSignal; -} diff --git a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts index 756b4b99612c7..df86a1f953656 100644 --- a/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts +++ b/x-pack/plugins/security_solution/server/lib/siem_migrations/rules/api/util/installation.ts @@ -42,7 +42,7 @@ const installPrebuiltRules = async ( const rulesToUpdate: UpdateRuleMigrationInput[] = []; const assetsToInstall: PrebuiltRuleAsset[] = []; rulesToInstall.forEach((ruleToInstall) => { - // If prebuilt rule has already been install, then just update migration rule with the installed rule id + // If prebuilt rule has already been installed, then just update migration rule with the installed rule id const installedRule = currentRules.find( (rule) => rule.rule_id === ruleToInstall.elastic_rule?.prebuilt_rule_id );