From c8938d708018af0bf612392291675eb757fa84fa Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Mon, 24 Nov 2025 15:41:58 +0000 Subject: [PATCH 1/7] qradar UI --- .../migration_name_input.tsx | 1 - .../migration_source_step/index.tsx | 20 +++ .../migration_source_dropdown.tsx | 71 ++++++++ .../migration_source_options.tsx | 170 ++++++++++++++++++ .../migration_source_step/translations.ts | 23 +++ .../components/migration_source_step/types.ts | 44 +++++ .../public/siem_migrations/common/types.ts | 5 + .../public/siem_migrations/rules/api/index.ts | 14 +- .../data_input_flyout/data_input_flyout.tsx | 114 +++++------- .../data_input_flyout/flyout_description.tsx | 46 +++++ .../data_input_flyout/steps/constants.ts | 7 + .../steps/rules/rules_data_input.tsx | 41 ++++- .../copy_exported_qradar_query.tsx | 41 +++++ ...ery.tsx => copy_exported_splunk_query.tsx} | 6 +- .../sub_steps/copy_export_query/index.tsx | 19 +- .../copy_export_query/translations.ts | 2 +- .../sub_steps/rules_file_upload/index.tsx | 4 + .../rules_file_upload/rules_file_upload.tsx | 48 ++++- .../rules_file_upload/translations.ts | 9 +- .../service/hooks/use_create_migration.ts | 26 ++- .../rules/service/rule_migrations_service.ts | 16 +- 21 files changed, 623 insertions(+), 104 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/flyout_description.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_qradar_query.tsx rename x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/{copy_export_query.tsx => copy_exported_splunk_query.tsx} (92%) diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_name_step/migration_name_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_name_step/migration_name_input.tsx index 27fda2f7dfdcf..072b4e974dbc5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_name_step/migration_name_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_name_step/migration_name_input.tsx @@ -62,7 +62,6 @@ export const MigrationNameInput = React.memo( onBlur={onBlur} onKeyDown={onEnter} fullWidth - autoFocus data-test-subj="migrationNameInput" /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx new file mode 100644 index 0000000000000..f6d24f423e8a8 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { useState } from 'react'; +import type { MigrationSource } from '../../types'; +import { MIGRATIONSOURCE_OPTIONS } from './migration_source_options'; + +export const useMigrationSourceStep = () => { + const [migrationSource, setMigrationSource] = useState( + MIGRATIONSOURCE_OPTIONS[0].value + ); + return { + migrationSource, + setMigrationSource, + }; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx new file mode 100644 index 0000000000000..11fade15c8a5c --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx @@ -0,0 +1,71 @@ +/* + * 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, { useCallback, useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow, EuiSuperSelect } from '@elastic/eui'; +import * as i18n from './translations'; +import type { MigrationSource } from '../../types'; +import { MIGRATIONSOURCE_OPTIONS } from './migration_source_options'; + +export interface MigrationSourceDropdownProps { + migrationSource: MigrationSource; + setMigrationSource: (migrationSource: MigrationSource) => void; +} + +export const MigrationSourceDropdown = React.memo( + ({ migrationSource, setMigrationSource }) => { + const [value, setValue] = useState(migrationSource); + const [isTouched, setIsTouched] = useState(false); + + const checkAndSetMigrationSource = useCallback( + (selected: MigrationSource) => { + if (selected.length > 0) { + setMigrationSource(selected); + } + }, + [setMigrationSource] + ); + + const handleMigrationSourceChange = useCallback( + (selected: MigrationSource) => { + setValue(selected); + checkAndSetMigrationSource(selected); + }, + [checkAndSetMigrationSource] + ); + + const onBlur = useCallback(() => { + setMigrationSource(value); + }, [setMigrationSource, value]); + return ( + + + + { + setIsTouched(true); + }} + label={i18n.MIGRATION_SOURCE_DROPDOWN_TITLE} + fullWidth + > + + + + + + ); + } +); + +MigrationSourceDropdown.displayName = 'MigrationSourceDropdown'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx new file mode 100644 index 0000000000000..a29969efa8bc6 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx @@ -0,0 +1,170 @@ +/* + * 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, { useCallback, useMemo, useState } from 'react'; +import type { EuiSuperSelectOption } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { MigrationSource } from '../../types'; +import * as i18n from './translations'; +import { + DataInputStep, + DataInputStepId, +} from '../../../rules/components/data_input_flyout/steps/constants'; +import { RulesDataInput } from '../../../rules/components/data_input_flyout/steps/rules/rules_data_input'; +import type { SiemMigrationResourceBase } from '../../../../../common/siem_migrations/model/common.gen'; +import type { RuleMigrationStats } from '../../../rules/types'; +import { MacrosDataInput } from '../../../rules/components/data_input_flyout/steps/macros/macros_data_input'; +import { LookupsDataInput } from '../../../rules/components/data_input_flyout/steps/lookups/lookups_data_input'; +import type { QradarMigrationSteps, SplunkMigrationSteps } from './types'; + +export const MIGRATIONSOURCE_OPTIONS: Array> = [ + { + value: MigrationSource.SPLUNK, + inputDisplay: {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_SPLUNK}, + }, + { + value: MigrationSource.QRADAR, + inputDisplay: ( + + {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_QRADAR} + + + ), + }, +]; + +interface MissingResourcesIndexed { + macros: string[]; + lookups: string[]; +} + +export const useSplunkMigrationSteps = ({ + setDataInputStep, + dataInputStep, + migrationSource, + migrationStats, + onMigrationCreated, +}: { + setDataInputStep: (step: DataInputStep) => void; + dataInputStep: DataInputStep; + migrationSource: MigrationSource; + migrationStats?: RuleMigrationStats; + onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; +}): SplunkMigrationSteps | null => { + const [missingResourcesIndexed, setMissingResourcesIndexed] = useState< + MissingResourcesIndexed | undefined + >(); + + const onMissingResourcesFetched = useCallback( + (missingResources: SiemMigrationResourceBase[]) => { + const newMissingResourcesIndexed = missingResources.reduce( + (acc, { type, name }) => { + if (type === 'macro') { + acc.macros.push(name); + } else if (type === 'lookup') { + acc.lookups.push(name); + } + return acc; + }, + { macros: [], lookups: [] } + ); + setMissingResourcesIndexed(newMissingResourcesIndexed); + if (newMissingResourcesIndexed.macros.length) { + setDataInputStep(DataInputStep.Macros); + return; + } + if (newMissingResourcesIndexed.lookups.length) { + setDataInputStep(DataInputStep.Lookups); + return; + } + setDataInputStep(DataInputStep.End); + }, + [setDataInputStep] + ); + + const onAllLookupsCreated = useCallback(() => { + setDataInputStep(DataInputStep.End); + }, [setDataInputStep]); + + const SPLUNK_MIGRATION_STEPS: SplunkMigrationSteps = useMemo( + () => [ + { + id: DataInputStepId.SplunkRules, + Component: RulesDataInput, + extraProps: { + dataInputStep, + migrationSource, + migrationStats, + onMigrationCreated, + onMissingResourcesFetched, + }, + }, + { + id: DataInputStepId.SplunkMacros, + Component: MacrosDataInput, + extraProps: { + dataInputStep, + onMissingResourcesFetched, + missingMacros: missingResourcesIndexed?.macros, + migrationStats, + }, + }, + { + id: DataInputStepId.SplunkLookups, + Component: LookupsDataInput, + extraProps: { + dataInputStep, + onAllLookupsCreated, + missingLookups: missingResourcesIndexed?.lookups, + migrationStats, + }, + }, + ], + [ + dataInputStep, + migrationSource, + migrationStats, + onMigrationCreated, + onMissingResourcesFetched, + missingResourcesIndexed, + onAllLookupsCreated, + ] + ); + + return migrationSource === MigrationSource.SPLUNK ? SPLUNK_MIGRATION_STEPS : null; +}; + +export const useQradarMigrationSteps = ({ + onMigrationCreated, + dataInputStep, + migrationSource, + migrationStats, +}: { + onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; + dataInputStep: DataInputStep; + migrationSource: MigrationSource; + migrationStats?: RuleMigrationStats; +}): QradarMigrationSteps | null => { + const onMissingResourcesFetched = useCallback(() => {}, []); + const QRADAR_MIGRATION_STEPS: QradarMigrationSteps = useMemo( + () => [ + { + id: DataInputStepId.QradarRules, + Component: RulesDataInput, + extraProps: { + dataInputStep, + migrationSource, + migrationStats, + onMigrationCreated, + onMissingResourcesFetched, + }, + }, + ], + [dataInputStep, migrationSource, migrationStats, onMigrationCreated, onMissingResourcesFetched] + ); + + return migrationSource === MigrationSource.QRADAR ? QRADAR_MIGRATION_STEPS : null; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts new file mode 100644 index 0000000000000..7f307855d70bc --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/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 MIGRATION_SOURCE_DROPDOWN_TITLE = i18n.translate( + 'xpack.securitySolution.siemMigrations.common.dataInputFlyout.migrationSource.title', + { defaultMessage: 'Select migration source' } +); + +export const MIGRATION_SOURCE_DROPDOWN_OPTION_SPLUNK = i18n.translate( + 'xpack.securitySolution.siemMigrations.common.dataInputFlyout.migrationSource.option.splunk', + { defaultMessage: 'Splunk' } +); + +export const MIGRATION_SOURCE_DROPDOWN_OPTION_QRADAR = i18n.translate( + 'xpack.securitySolution.siemMigrations.common.dataInputFlyout.migrationSource.option.qradar', + { defaultMessage: 'QRadar' } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts new file mode 100644 index 0000000000000..c5550a9933b4a --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts @@ -0,0 +1,44 @@ +/* + * 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 { DataInputStepId } from '../../../rules/components/data_input_flyout/steps/constants'; +import type { LookupsDataInput } from '../../../rules/components/data_input_flyout/steps/lookups/lookups_data_input'; +import type { MacrosDataInput } from '../../../rules/components/data_input_flyout/steps/macros/macros_data_input'; +import type { RulesDataInput } from '../../../rules/components/data_input_flyout/steps/rules/rules_data_input'; + +interface RulesStep { + id: T; + Component: typeof RulesDataInput; + extraProps: React.ComponentProps; +} +interface MacrosStep { + id: DataInputStepId.SplunkMacros; + Component: typeof MacrosDataInput; + extraProps: React.ComponentProps; +} +interface LookupsStep { + id: DataInputStepId.SplunkLookups; + Component: typeof LookupsDataInput; + extraProps: React.ComponentProps; +} + +export type Step = + T extends DataInputStepId.SplunkRules + ? RulesStep + : T extends DataInputStepId.QradarRules + ? RulesStep + : T extends DataInputStepId.SplunkMacros + ? MacrosStep + : LookupsStep; + +export type SplunkMigrationSteps = [ + Step, + Step, + Step +]; + +export type QradarMigrationSteps = [Step]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/types.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/types.ts index 29b8b5335ff89..e8b904f3fe942 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/types.ts @@ -39,3 +39,8 @@ export interface FilterOptionsBase { export interface MigrationStats extends MigrationTaskStats { status: SiemMigrationTaskStatus; // use the native enum instead of the zod enum from the model } + +export enum MigrationSource { + SPLUNK = 'splunk', + QRADAR = 'qradar', +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts index fa5187a76e8a2..d773495744538 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts @@ -25,11 +25,12 @@ import { SIEM_RULE_MIGRATIONS_PREBUILT_RULES_PATH, SIEM_RULE_MIGRATIONS_INTEGRATIONS_PATH, SIEM_RULE_MIGRATION_MISSING_PRIVILEGES_PATH, - SIEM_RULE_MIGRATION_RULES_PATH, SIEM_RULE_MIGRATIONS_INTEGRATIONS_STATS_PATH, SIEM_RULE_MIGRATION_PATH, SIEM_RULE_MIGRATION_STOP_PATH, SIEM_RULE_MIGRATION_UPDATE_INDEX_PATTERN_PATH, + SIEM_RULE_MIGRATION_RULES_PATH, + SIEM_RULE_MIGRATION_QRADAR_RULES_PATH, } from '../../../../common/siem_migrations/constants'; import type { CreateRuleMigrationResponse, @@ -53,6 +54,7 @@ import type { } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; import type { RuleMigrationStats } from '../types'; import type { GetMigrationStatsParams, GetMigrationsStatsAllParams } from '../../common/types'; +import { MigrationSource } from '../../common/types'; /** Retrieves the stats for the specific migration. */ export const getRuleMigrationStats = async ({ @@ -102,6 +104,8 @@ export interface AddRulesToMigrationParams { body: CreateRuleMigrationRulesRequestBody; /** Optional AbortSignal for cancelling request */ signal?: AbortSignal; + /** The source of the migration */ + migrationSource: MigrationSource; } /** Adds provided rules to an existing migration */ @@ -109,9 +113,15 @@ export const addRulesToMigration = async ({ migrationId, body, signal, + migrationSource, }: AddRulesToMigrationParams) => { return KibanaServices.get().http.post( - replaceParams(SIEM_RULE_MIGRATION_RULES_PATH, { migration_id: migrationId }), + replaceParams( + migrationSource === MigrationSource.QRADAR + ? SIEM_RULE_MIGRATION_QRADAR_RULES_PATH + : SIEM_RULE_MIGRATION_RULES_PATH, + { migration_id: migrationId } + ), { body: JSON.stringify(body), version: '1', signal } ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx index 9cddaaaf0afac..1572ecb1d075e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx @@ -19,29 +19,38 @@ import { useGeneratedHtmlId, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { SiemMigrationResourceBase } from '../../../../../common/siem_migrations/model/common.gen'; import { SiemMigrationRetryFilter, SiemMigrationTaskStatus, } from '../../../../../common/siem_migrations/constants'; -import { RulesDataInput } from './steps/rules/rules_data_input'; +import type { DataInputStepId } from './steps/constants'; import { DataInputStep } from './steps/constants'; -import { MacrosDataInput } from './steps/macros/macros_data_input'; -import { LookupsDataInput } from './steps/lookups/lookups_data_input'; import { useStartRulesMigrationModal } from '../../hooks/use_start_rules_migration_modal'; import type { RuleMigrationSettings, RuleMigrationStats } from '../../types'; import { useStartMigration } from '../../logic/use_start_migration'; - -interface MissingResourcesIndexed { - macros: string[]; - lookups: string[]; -} +import { useMigrationSourceStep } from '../../../common/components/migration_source_step'; +import { MigrationSourceDropdown } from '../../../common/components/migration_source_step/migration_source_dropdown'; +import { CenteredLoadingSpinner } from '../../../../common/components/centered_loading_spinner'; +import { + useQradarMigrationSteps, + useSplunkMigrationSteps, +} from '../../../common/components/migration_source_step/migration_source_options'; +import type { + QradarMigrationSteps, + SplunkMigrationSteps, + Step, +} from '../../../common/components/migration_source_step/types'; export interface MigrationDataInputFlyoutProps { onClose: () => void; migrationStats?: RuleMigrationStats; } +function StepRenderer({ step }: { step: Step }) { + const Component = step.Component as React.ComponentType; + return ; +} + const RULES_MIGRATION_DATA_INPUT_FLYOUT_TITLE = 'rulesMigrationDataInputFlyoutTitle'; export const MigrationDataInputFlyout = React.memo( @@ -53,9 +62,7 @@ export const MigrationDataInputFlyout = React.memo( initialMigrationSats ); - const [missingResourcesIndexed, setMissingResourcesIndexed] = useState< - MissingResourcesIndexed | undefined - >(); + const isRetry = migrationStats?.status === SiemMigrationTaskStatus.FINISHED; const [dataInputStep, setDataInputStep] = useState(DataInputStep.Rules); @@ -64,37 +71,6 @@ export const MigrationDataInputFlyout = React.memo { - const newMissingResourcesIndexed = missingResources.reduce( - (acc, { type, name }) => { - if (type === 'macro') { - acc.macros.push(name); - } else if (type === 'lookup') { - acc.lookups.push(name); - } - return acc; - }, - { macros: [], lookups: [] } - ); - setMissingResourcesIndexed(newMissingResourcesIndexed); - if (newMissingResourcesIndexed.macros.length) { - setDataInputStep(DataInputStep.Macros); - return; - } - if (newMissingResourcesIndexed.lookups.length) { - setDataInputStep(DataInputStep.Lookups); - return; - } - setDataInputStep(DataInputStep.End); - }, - [] - ); - - const onAllLookupsCreated = useCallback(() => { - setDataInputStep(DataInputStep.End); - }, []); - const { startMigration, isLoading: isStartLoading } = useStartMigration(onClose); const onStartMigrationWithSettings = useCallback( (settings: RuleMigrationSettings) => { @@ -120,6 +96,25 @@ export const MigrationDataInputFlyout = React.memo {startMigrationModal} @@ -136,7 +131,7 @@ export const MigrationDataInputFlyout = React.memo @@ -144,29 +139,18 @@ export const MigrationDataInputFlyout = React.memo - - - - - - - + <> + {steps?.map((step) => ( + + + + )) ?? } + diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/flyout_description.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/flyout_description.tsx new file mode 100644 index 0000000000000..7b8c56924b037 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/flyout_description.tsx @@ -0,0 +1,46 @@ +/* + * 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 { FormattedMessage } from '@kbn/i18n-react'; +import { EuiText, EuiLink } from '@elastic/eui'; +import { useNavigation } from '../../../../common/lib/kibana'; + +export const FlyoutDescription = React.memo(() => { + const { navigateTo } = useNavigation(); + return ( + + { + navigateTo({ + appId: 'management', + path: '/kibana/dataViews', + }); + }} + target="_blank" + rel="noopener" + external + data-test-subj="siemMigrationsAdvancedSettingsLink" + > + + + ), + }} + /> + + ); +}); + +FlyoutDescription.displayName = 'FlyoutDescription'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts index c0586108b0a19..a8707f95ea54d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts @@ -11,3 +11,10 @@ export enum DataInputStep { Lookups = 3, End = 10, } + +export enum DataInputStepId { + SplunkRules = 'splunk_rules', + SplunkMacros = 'splunk_macros', + SplunkLookups = 'splunk_lookups', + QradarRules = 'qradar_rules', +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx index b10ed3a6df334..f044b0d1baa2f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx @@ -18,17 +18,25 @@ import { useCopyExportQueryStep } from './sub_steps/copy_export_query'; import { useRulesFileUploadStep } from './sub_steps/rules_file_upload'; import { useCheckResourcesStep } from './sub_steps/check_resources'; import type { RuleMigrationStats } from '../../../../types'; +import { MigrationSource } from '../../../../../common/types'; interface RulesDataInputSubStepsProps { migrationStats?: RuleMigrationStats; onMigrationCreated: OnMigrationCreated; onMissingResourcesFetched: OnMissingResourcesFetched; + migrationSource: MigrationSource; } interface RulesDataInputProps extends RulesDataInputSubStepsProps { dataInputStep: DataInputStep; } export const RulesDataInput = React.memo( - ({ dataInputStep, migrationStats, onMigrationCreated, onMissingResourcesFetched }) => { + ({ + dataInputStep, + migrationStats, + migrationSource, + onMigrationCreated, + onMissingResourcesFetched, + }) => { const dataInputStatus = useMemo( () => getEuiStepStatus(DataInputStep.Rules, dataInputStep), [dataInputStep] @@ -57,6 +65,7 @@ export const RulesDataInput = React.memo( {dataInputStatus === 'current' && ( ( - ({ migrationStats, onMigrationCreated, onMissingResourcesFetched }) => { + ({ migrationStats, onMigrationCreated, onMissingResourcesFetched, migrationSource }) => { const { telemetry } = useKibana().services.siemMigrations.rules; const [subStep, setSubStep] = useState(migrationStats ? 4 : 1); @@ -103,16 +112,27 @@ export const RulesDataInputSubSteps = React.memo( setSubStep((currentSubStep) => (currentSubStep !== 1 ? 3 : currentSubStep)); // Move to the next step only if step 1 was completed telemetry.reportSetupQueryCopied({ migrationId: migrationStats?.id }); }, [telemetry, migrationStats?.id]); - const copyStep = useCopyExportQueryStep({ status: getEuiStepStatus(2, subStep), onCopied }); + const copyStep = useCopyExportQueryStep({ + status: getEuiStepStatus(2, subStep), + onCopied, + migrationSource, + }); // Upload rules step - const onMigrationCreatedStep = useCallback( + const onSplunkMigrationCreatedStep = useCallback( (stats) => { onMigrationCreated(stats); setSubStep(4); }, [onMigrationCreated] ); + const onQradarMigrationCreatedStep = useCallback( + (stats) => { + onMigrationCreated(stats); + setSubStep(END); + }, + [onMigrationCreated] + ); const onRulesFileChanged = useCallback((files: FileList | null) => { setIsRuleFileReady(!!files?.length); setSubStep(3); @@ -121,8 +141,12 @@ export const RulesDataInputSubSteps = React.memo( status: getEuiStepStatus(3, subStep), migrationStats, onRulesFileChanged, - onMigrationCreated: onMigrationCreatedStep, + onMigrationCreated: + migrationSource === MigrationSource.SPLUNK + ? onSplunkMigrationCreatedStep + : onQradarMigrationCreatedStep, migrationName, + migrationSource, }); // Check missing resources step @@ -140,8 +164,11 @@ export const RulesDataInputSubSteps = React.memo( }); const steps = useMemo( - () => [nameStep, copyStep, uploadStep, resourcesStep], - [nameStep, copyStep, uploadStep, resourcesStep] + () => + migrationSource === MigrationSource.SPLUNK + ? [nameStep, copyStep, uploadStep, resourcesStep] + : [nameStep, copyStep, uploadStep], + [migrationSource, nameStep, copyStep, uploadStep, resourcesStep] ); return ; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_qradar_query.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_qradar_query.tsx new file mode 100644 index 0000000000000..85e3e2948fede --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_qradar_query.tsx @@ -0,0 +1,41 @@ +/* + * 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 { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; + +export const CopyExportedQradarQuery = React.memo(() => { + return ( + + + + + ), + xmlFileOption: ( + + + + ), + }} + defaultMessage="On the Use Case Explorer page, after clicking on the {download} button, select the second option in the Export window. Only the {xmlFileOption} is supported for export rules and dependencies. Leave the default options for the selected checkboxes regarding MITRE mappings and for custom rule attribute mappings." + /> + + ); +}); + +CopyExportedQradarQuery.displayName = 'CopyExportedQradarQuery'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_export_query.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_splunk_query.tsx similarity index 92% rename from x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_export_query.tsx rename to x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_splunk_query.tsx index 0dad40571ebf8..8b3ac411cce9d 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_export_query.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/copy_exported_splunk_query.tsx @@ -14,7 +14,7 @@ import * as i18n from './translations'; interface CopyExportQueryProps { onCopied: () => void; } -export const CopyExportQuery = React.memo(({ onCopied }) => { +export const CopyExportedSplunkQuery = React.memo(({ onCopied }) => { const onClick: React.MouseEventHandler = useCallback( (ev) => { // The only button inside the element is the "copy" button. @@ -40,7 +40,7 @@ export const CopyExportQuery = React.memo(({ onCopied }) = - {/* The click event is also dispatched when using the keyboard actions (space or enter) for "copy" button. + {/* The click event is also dispatched when using the keyboard actions (space or enter) for "copy" button. No need to use keyboard specific events, disabling the a11y lint rule:*/} {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
@@ -62,4 +62,4 @@ export const CopyExportQuery = React.memo(({ onCopied }) = ); }); -CopyExportQuery.displayName = 'CopyExportQuery'; +CopyExportedSplunkQuery.displayName = 'CopyExportedSplunkQuery'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.tsx index 3d2adcc78857b..91c821c99425c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.tsx @@ -5,22 +5,35 @@ * 2.0. */ -import React from 'react'; +import React, { useMemo } from 'react'; import type { EuiStepProps, EuiStepStatus } from '@elastic/eui'; -import { CopyExportQuery } from './copy_export_query'; +import { CopyExportedSplunkQuery } from './copy_exported_splunk_query'; import * as i18n from './translations'; +import { MigrationSource } from '../../../../../../../common/types'; +import { CopyExportedQradarQuery } from './copy_exported_qradar_query'; export interface CopyExportQueryStepProps { status: EuiStepStatus; + migrationSource: MigrationSource; onCopied: () => void; } + export const useCopyExportQueryStep = ({ + migrationSource, status, onCopied, }: CopyExportQueryStepProps): EuiStepProps => { + const COPY_EXPORT_QUERY_SUPPORTED_SOURCES: Record = useMemo( + () => ({ + [MigrationSource.SPLUNK]: , + [MigrationSource.QRADAR]: , + }), + [onCopied] + ); + return { title: i18n.RULES_DATA_INPUT_COPY_TITLE, status, - children: , + children: COPY_EXPORT_QUERY_SUPPORTED_SOURCES[migrationSource], }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/translations.ts index 274cd3cda2b61..df5045e0e27ad 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/translations.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; export const RULES_DATA_INPUT_COPY_TITLE = i18n.translate( 'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.title', - { defaultMessage: 'Export Splunk rules' } + { defaultMessage: 'Copy rule query' } ); export const RULES_DATA_INPUT_COPY_DESCRIPTION_SECTION = i18n.translate( diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx index 09b5f136eb6db..fda03c35dba66 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx @@ -15,11 +15,13 @@ import { type OnSuccess, } from '../../../../../../service/hooks/use_create_migration'; import * as i18n from './translations'; +import type { MigrationSource } from '../../../../../../../common/types'; export interface RulesFileUploadStepProps { status: EuiStepStatus; migrationStats: RuleMigrationStats | undefined; migrationName: string | undefined; + migrationSource: MigrationSource; onMigrationCreated: OnMigrationCreated; onRulesFileChanged: (files: FileList | null) => void; } @@ -27,6 +29,7 @@ export const useRulesFileUploadStep = ({ status, migrationStats, migrationName, + migrationSource, onMigrationCreated, onRulesFileChanged, }: RulesFileUploadStepProps): EuiStepProps => { @@ -57,6 +60,7 @@ export const useRulesFileUploadStep = ({ >; @@ -32,19 +34,33 @@ export interface RulesFileUploadProps { isCreated: boolean; onRulesFileChanged: (files: FileList | null) => void; migrationName: string | undefined; + migrationSource: MigrationSource; apiError: string | undefined; } + +const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT: Record = { + [MigrationSource.SPLUNK]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_SPLUNK, + [MigrationSource.QRADAR]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_QRADAR, +}; export const RulesFileUpload = React.memo( - ({ createMigration, migrationName, apiError, isLoading, isCreated, onRulesFileChanged }) => { + ({ + createMigration, + migrationName, + migrationSource, + apiError, + isLoading, + isCreated, + onRulesFileChanged, + }) => { const [rulesToUpload, setRulesToUpload] = useState([]); const filePickerRef = useRef(null); const createRules = useCallback(() => { if (migrationName) { filePickerRef.current?.removeFiles(); - createMigration(migrationName, rulesToUpload); + createMigration({ migrationName, rules: rulesToUpload, migrationSource }); } - }, [createMigration, migrationName, rulesToUpload]); + }, [createMigration, migrationName, migrationSource, rulesToUpload]); const onFileParsed = useCallback((content: Array>) => { const rules = content.map(formatRuleRow); @@ -53,13 +69,22 @@ export const RulesFileUpload = React.memo( const { parseFile, isParsing, error: fileError } = useParseFileInput(onFileParsed); + // const onFileChange = useCallback( + // (files: FileList | null) => { + // setRulesToUpload([]); + // onRulesFileChanged(files); + // parseFile(files); + // }, + // [parseFile, onRulesFileChanged] + // ); + const onFileChange = useCallback( (files: FileList | null) => { - setRulesToUpload([]); + console.log('Files changed:', files); + setRulesToUpload(files?.length ? Array.from(files) : []); onRulesFileChanged(files); - parseFile(files); }, - [parseFile, onRulesFileChanged] + [setRulesToUpload, onRulesFileChanged] ); const error = useMemo(() => { @@ -75,6 +100,9 @@ export const RulesFileUpload = React.memo( return ( + + {RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION} + ( fullWidth initialPromptText={ - {i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT} + {RULES_DATA_INPUT_FILE_UPLOAD_PROMPT[migrationSource]} } - accept="application/json, application/x-ndjson" + accept={ + migrationSource === MigrationSource.SPLUNK + ? 'application/json, application/x-ndjson' + : '.xml' + } onChange={onFileChange} display="large" aria-label="Upload rules file" diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/translations.ts index 01e5fd8dc35b3..9251914e99db5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/translations.ts @@ -12,7 +12,12 @@ export const RULES_DATA_INPUT_FILE_UPLOAD_TITLE = i18n.translate( { defaultMessage: 'Update exported rules' } ); -export const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT = i18n.translate( - 'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.prompt', +export const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_SPLUNK = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.promptSplunk', { defaultMessage: 'Select or drag and drop the exported JSON file' } ); + +export const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_QRADAR = i18n.translate( + 'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.promptQradar', + { defaultMessage: 'Select or drag and drop the exported XML file' } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts index 07a449dd891a7..6015db9b75fa6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts @@ -11,6 +11,7 @@ import type { CreateRuleMigrationRulesRequestBody } from '../../../../../common/ import { useKibana } from '../../../../common/lib/kibana/kibana_react'; import { reducer, initialState } from '../../../common/service'; import type { RuleMigrationStats } from '../../types'; +import type { MigrationSource } from '../../../common/types'; export const RULES_DATA_INPUT_CREATE_MIGRATION_SUCCESS_TITLE = i18n.translate( 'xpack.securitySolution.siemMigrations.rules.service.createRuleSuccess.title', @@ -26,10 +27,15 @@ export const RULES_DATA_INPUT_CREATE_MIGRATION_ERROR = i18n.translate( { defaultMessage: 'Failed to upload rules file' } ); -export type CreateMigration = ( - migrationName: string, - rules: CreateRuleMigrationRulesRequestBody -) => void; +export type CreateMigration = ({ + rules, + migrationName, + migrationSource, +}: { + migrationName: string; + migrationSource: MigrationSource; + rules: CreateRuleMigrationRulesRequestBody; +}) => void; export type OnSuccess = (migrationStats: RuleMigrationStats) => void; export const useCreateMigration = (onSuccess: OnSuccess) => { @@ -37,12 +43,18 @@ export const useCreateMigration = (onSuccess: OnSuccess) => { const [state, dispatch] = useReducer(reducer, initialState); const createMigration = useCallback( - (migrationName, rules) => { + ({ migrationName, migrationSource, rules }) => { (async () => { try { dispatch({ type: 'start' }); - const migrationId = await siemMigrations.rules.createRuleMigration(rules, migrationName); - const stats = await siemMigrations.rules.api.getRuleMigrationStats({ migrationId }); + const migrationId = await siemMigrations.rules.createRuleMigration( + rules, + migrationName, + migrationSource + ); + const stats = await siemMigrations.rules.api.getRuleMigrationStats({ + migrationId, + }); notifications.toasts.addSuccess({ title: RULES_DATA_INPUT_CREATE_MIGRATION_SUCCESS_TITLE, diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts index 1de46051cd29c..6bae1d6453aa1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts @@ -25,7 +25,11 @@ import { getMissingCapabilitiesToast, getNoConnectorToast, } from '../../common/service'; -import type { GetMigrationStatsParams, GetMigrationsStatsAllParams } from '../../common/types'; +import type { + GetMigrationStatsParams, + GetMigrationsStatsAllParams, + MigrationSource, +} from '../../common/types'; import { raiseSuccessToast } from './notification/success_notification'; import { START_STOP_POLLING_SLEEP_SECONDS } from '../../common/constants'; @@ -51,7 +55,8 @@ export class SiemRulesMigrationsService extends SiemMigrationsServiceBase { const rulesCount = data.length; if (rulesCount === 0) { @@ -81,7 +87,7 @@ export class SiemRulesMigrationsService extends SiemMigrationsServiceBase Date: Thu, 27 Nov 2025 16:00:01 +0000 Subject: [PATCH 2/7] qradar UI --- .../migration_source_step/index.tsx | 10 +- .../migration_source_dropdown.tsx | 5 +- .../migration_source_options.tsx | 55 ++++-- .../migration_source_step/translations.ts | 5 + .../public/siem_migrations/rules/api/index.ts | 31 ++- .../data_input_flyout/data_input_flyout.tsx | 76 ++++---- .../data_input_flyout/steps/constants.ts | 14 +- .../steps/lookups/lookups_data_input.test.tsx | 6 +- .../steps/lookups/lookups_data_input.tsx | 8 +- .../steps/macros/macros_data_input.test.tsx | 6 +- .../steps/macros/macros_data_input.tsx | 8 +- .../steps/rules/rules_data_input.test.tsx | 27 ++- .../steps/rules/rules_data_input.tsx | 109 ++++++++--- .../sub_steps/rules_file_upload/index.tsx | 29 ++- .../rules_file_upload.test.tsx | 2 + .../rules_file_upload/rules_file_upload.tsx | 19 +- .../rules_xml_file_upload.tsx | 183 ++++++++++++++++++ .../service/hooks/use_create_migration.ts | 10 +- .../rules/service/rule_migrations_service.ts | 31 +-- 19 files changed, 484 insertions(+), 150 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx index f6d24f423e8a8..70a0d12eee2e4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx @@ -7,14 +7,14 @@ import { useState } from 'react'; import type { MigrationSource } from '../../types'; -import { MIGRATIONSOURCE_OPTIONS } from './migration_source_options'; -export const useMigrationSourceStep = () => { - const [migrationSource, setMigrationSource] = useState( - MIGRATIONSOURCE_OPTIONS[0].value - ); +export const useMigrationSourceStep = (initialMigrationSource: MigrationSource) => { + const [migrationSource, setMigrationSource] = useState(initialMigrationSource); + const [migrationSourceDisabled, setMigrationSourceDisabled] = useState(false); return { migrationSource, setMigrationSource, + migrationSourceDisabled, + setMigrationSourceDisabled, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx index 11fade15c8a5c..c06b94364ee2a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx @@ -13,10 +13,11 @@ import { MIGRATIONSOURCE_OPTIONS } from './migration_source_options'; export interface MigrationSourceDropdownProps { migrationSource: MigrationSource; setMigrationSource: (migrationSource: MigrationSource) => void; + disabled: boolean; } export const MigrationSourceDropdown = React.memo( - ({ migrationSource, setMigrationSource }) => { + ({ migrationSource, setMigrationSource, disabled }) => { const [value, setValue] = useState(migrationSource); const [isTouched, setIsTouched] = useState(false); @@ -50,6 +51,7 @@ export const MigrationSourceDropdown = React.memo( }} label={i18n.MIGRATION_SOURCE_DROPDOWN_TITLE} fullWidth + helpText={disabled ? i18n.MIGRATION_SOURCE_DROPDOWN_HELPER_TEXT : undefined} > ( onBlur={onBlur} autoFocus data-test-subj="migrationSourceDropdown" + disabled={disabled} /> diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx index a29969efa8bc6..c9b35b26d8758 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx @@ -9,8 +9,12 @@ import type { EuiSuperSelectOption } from '@elastic/eui'; import { EuiIcon } from '@elastic/eui'; import { MigrationSource } from '../../types'; import * as i18n from './translations'; -import { +import type { DataInputStep, + QradarDataInputStep, +} from '../../../rules/components/data_input_flyout/steps/constants'; +import { + SplunkDataInputStep, DataInputStepId, } from '../../../rules/components/data_input_flyout/steps/constants'; import { RulesDataInput } from '../../../rules/components/data_input_flyout/steps/rules/rules_data_input'; @@ -48,8 +52,8 @@ export const useSplunkMigrationSteps = ({ migrationStats, onMigrationCreated, }: { - setDataInputStep: (step: DataInputStep) => void; - dataInputStep: DataInputStep; + setDataInputStep: (step: SplunkDataInputStep) => void; + dataInputStep: SplunkDataInputStep; migrationSource: MigrationSource; migrationStats?: RuleMigrationStats; onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; @@ -73,20 +77,20 @@ export const useSplunkMigrationSteps = ({ ); setMissingResourcesIndexed(newMissingResourcesIndexed); if (newMissingResourcesIndexed.macros.length) { - setDataInputStep(DataInputStep.Macros); + setDataInputStep(SplunkDataInputStep.Macros); return; } if (newMissingResourcesIndexed.lookups.length) { - setDataInputStep(DataInputStep.Lookups); + setDataInputStep(SplunkDataInputStep.Lookups); return; } - setDataInputStep(DataInputStep.End); + setDataInputStep(SplunkDataInputStep.End); }, [setDataInputStep] ); const onAllLookupsCreated = useCallback(() => { - setDataInputStep(DataInputStep.End); + setDataInputStep(SplunkDataInputStep.End); }, [setDataInputStep]); const SPLUNK_MIGRATION_STEPS: SplunkMigrationSteps = useMemo( @@ -144,11 +148,10 @@ export const useQradarMigrationSteps = ({ migrationStats, }: { onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; - dataInputStep: DataInputStep; + dataInputStep: QradarDataInputStep; migrationSource: MigrationSource; migrationStats?: RuleMigrationStats; }): QradarMigrationSteps | null => { - const onMissingResourcesFetched = useCallback(() => {}, []); const QRADAR_MIGRATION_STEPS: QradarMigrationSteps = useMemo( () => [ { @@ -159,12 +162,42 @@ export const useQradarMigrationSteps = ({ migrationSource, migrationStats, onMigrationCreated, - onMissingResourcesFetched, }, }, ], - [dataInputStep, migrationSource, migrationStats, onMigrationCreated, onMissingResourcesFetched] + [dataInputStep, migrationSource, migrationStats, onMigrationCreated] ); return migrationSource === MigrationSource.QRADAR ? QRADAR_MIGRATION_STEPS : null; }; + +export const useMigrationSteps = ({ + onMigrationCreated, + dataInputStep, + migrationSource, + migrationStats, + setMigrationDataInputStep, +}: { + onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; + dataInputStep: DataInputStep; + migrationSource: MigrationSource; + migrationStats?: RuleMigrationStats; + setMigrationDataInputStep: (step: SplunkDataInputStep | QradarDataInputStep) => void; +}): SplunkMigrationSteps | QradarMigrationSteps | null => { + const splunkMigrationSteps: SplunkMigrationSteps | null = useSplunkMigrationSteps({ + setDataInputStep: setMigrationDataInputStep, + dataInputStep: dataInputStep[MigrationSource.SPLUNK], + migrationSource, + migrationStats, + onMigrationCreated, + }); + + const qradarMigrationSteps: QradarMigrationSteps | null = useQradarMigrationSteps({ + dataInputStep: dataInputStep[MigrationSource.QRADAR], + migrationSource, + migrationStats, + onMigrationCreated, + }); + + return splunkMigrationSteps ?? qradarMigrationSteps; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts index 7f307855d70bc..d5a59900371ef 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/translations.ts @@ -21,3 +21,8 @@ export const MIGRATION_SOURCE_DROPDOWN_OPTION_QRADAR = i18n.translate( 'xpack.securitySolution.siemMigrations.common.dataInputFlyout.migrationSource.option.qradar', { defaultMessage: 'QRadar' } ); + +export const MIGRATION_SOURCE_DROPDOWN_HELPER_TEXT = i18n.translate( + 'xpack.securitySolution.siemMigrations.common.dataInputFlyout.migrationSource.helperText', + { defaultMessage: 'You cannot change the migration source after creating a migration.' } +); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts index d773495744538..28f090d487020 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/api/index.ts @@ -54,7 +54,6 @@ import type { } from '../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; import type { RuleMigrationStats } from '../types'; import type { GetMigrationStatsParams, GetMigrationsStatsAllParams } from '../../common/types'; -import { MigrationSource } from '../../common/types'; /** Retrieves the stats for the specific migration. */ export const getRuleMigrationStats = async ({ @@ -97,6 +96,26 @@ export const createRuleMigration = async ({ }); }; +export interface AddRulesToQradarMigrationParams { + /** `id` of the migration to add the rules to */ + migrationId: string; + /** The body containing the list of rules to be added to the migration */ + body: { xml: string }; + /** Optional AbortSignal for cancelling request */ + signal?: AbortSignal; +} + +export const addRulesToQRadarMigration = async ({ + migrationId, + body, + signal, +}: AddRulesToQradarMigrationParams) => { + return KibanaServices.get().http.post( + replaceParams(SIEM_RULE_MIGRATION_QRADAR_RULES_PATH, { migration_id: migrationId }), + { body: JSON.stringify(body), version: '1', signal } + ); +}; + export interface AddRulesToMigrationParams { /** `id` of the migration to add the rules to */ migrationId: string; @@ -104,8 +123,6 @@ export interface AddRulesToMigrationParams { body: CreateRuleMigrationRulesRequestBody; /** Optional AbortSignal for cancelling request */ signal?: AbortSignal; - /** The source of the migration */ - migrationSource: MigrationSource; } /** Adds provided rules to an existing migration */ @@ -113,15 +130,9 @@ export const addRulesToMigration = async ({ migrationId, body, signal, - migrationSource, }: AddRulesToMigrationParams) => { return KibanaServices.get().http.post( - replaceParams( - migrationSource === MigrationSource.QRADAR - ? SIEM_RULE_MIGRATION_QRADAR_RULES_PATH - : SIEM_RULE_MIGRATION_RULES_PATH, - { migration_id: migrationId } - ), + replaceParams(SIEM_RULE_MIGRATION_RULES_PATH, { migration_id: migrationId }), { body: JSON.stringify(body), version: '1', signal } ); }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx index 1572ecb1d075e..bfa9b1d6ea1ab 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx @@ -23,27 +23,22 @@ import { SiemMigrationRetryFilter, SiemMigrationTaskStatus, } from '../../../../../common/siem_migrations/constants'; -import type { DataInputStepId } from './steps/constants'; -import { DataInputStep } from './steps/constants'; +import type { DataInputStep, DataInputStepId } from './steps/constants'; +import { QradarDataInputStep, SplunkDataInputStep } from './steps/constants'; import { useStartRulesMigrationModal } from '../../hooks/use_start_rules_migration_modal'; import type { RuleMigrationSettings, RuleMigrationStats } from '../../types'; import { useStartMigration } from '../../logic/use_start_migration'; import { useMigrationSourceStep } from '../../../common/components/migration_source_step'; import { MigrationSourceDropdown } from '../../../common/components/migration_source_step/migration_source_dropdown'; import { CenteredLoadingSpinner } from '../../../../common/components/centered_loading_spinner'; -import { - useQradarMigrationSteps, - useSplunkMigrationSteps, -} from '../../../common/components/migration_source_step/migration_source_options'; -import type { - QradarMigrationSteps, - SplunkMigrationSteps, - Step, -} from '../../../common/components/migration_source_step/types'; +import { useMigrationSteps } from '../../../common/components/migration_source_step/migration_source_options'; +import type { Step } from '../../../common/components/migration_source_step/types'; +import { MigrationSource } from '../../../common/types'; export interface MigrationDataInputFlyoutProps { onClose: () => void; migrationStats?: RuleMigrationStats; + migrationSource?: MigrationSource; } function StepRenderer({ step }: { step: Step }) { @@ -54,35 +49,60 @@ function StepRenderer({ step }: { step: Step }) { const RULES_MIGRATION_DATA_INPUT_FLYOUT_TITLE = 'rulesMigrationDataInputFlyoutTitle'; export const MigrationDataInputFlyout = React.memo( - ({ onClose, migrationStats: initialMigrationSats }) => { + ({ + onClose, + migrationStats: initialMigrationSats, + migrationSource: initialMigrationSource = MigrationSource.SPLUNK, + }) => { const modalTitleId = useGeneratedHtmlId({ prefix: RULES_MIGRATION_DATA_INPUT_FLYOUT_TITLE, }); + const { + migrationSource, + setMigrationSource, + migrationSourceDisabled, + setMigrationSourceDisabled, + } = useMigrationSourceStep(initialMigrationSource); + const [migrationStats, setMigrationStats] = useState( initialMigrationSats ); const isRetry = migrationStats?.status === SiemMigrationTaskStatus.FINISHED; - const [dataInputStep, setDataInputStep] = useState(DataInputStep.Rules); + const [dataInputStep, setDataInputStep] = useState({ + [MigrationSource.SPLUNK]: SplunkDataInputStep.Rules, + [MigrationSource.QRADAR]: QradarDataInputStep.Rules, + }); - const onMigrationCreated = useCallback((createdMigrationStats: RuleMigrationStats) => { - setMigrationStats(createdMigrationStats); - }, []); + const setMigrationDataInputStep = useCallback( + (step: DataInputStep[MigrationSource]) => { + setDataInputStep((prev) => ({ ...prev, ...{ [migrationSource]: step } })); + }, + [migrationSource] + ); + + const onMigrationCreated = useCallback( + (createdMigrationStats: RuleMigrationStats) => { + setMigrationStats(createdMigrationStats); + setMigrationSourceDisabled(true); + }, + [setMigrationStats, setMigrationSourceDisabled] + ); const { startMigration, isLoading: isStartLoading } = useStartMigration(onClose); const onStartMigrationWithSettings = useCallback( (settings: RuleMigrationSettings) => { - if (migrationStats?.id) { + if (typeof migrationStats?.id === 'string') { startMigration( - migrationStats.id, + migrationStats?.id as string, isRetry ? SiemMigrationRetryFilter.NOT_FULLY_TRANSLATED : undefined, settings ); } }, - [isRetry, migrationStats?.id, startMigration] + [isRetry, migrationStats, startMigration] ); const { modal: startMigrationModal, showModal: showStartMigrationModal } = useStartRulesMigrationModal({ @@ -94,27 +114,16 @@ export const MigrationDataInputFlyout = React.memo {startMigrationModal} @@ -142,6 +151,7 @@ export const MigrationDataInputFlyout = React.memo <> diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts index a8707f95ea54d..77684ea464f4c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/constants.ts @@ -5,13 +5,25 @@ * 2.0. */ -export enum DataInputStep { +import type { MigrationSource } from '../../../../common/types'; + +export enum SplunkDataInputStep { Rules = 1, Macros = 2, Lookups = 3, End = 10, } +export enum QradarDataInputStep { + Rules = 1, + End = 10, +} + +export interface DataInputStep { + [MigrationSource.SPLUNK]: SplunkDataInputStep; + [MigrationSource.QRADAR]: QradarDataInputStep; +} + export enum DataInputStepId { SplunkRules = 'splunk_rules', SplunkMacros = 'splunk_macros', diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.test.tsx index 5f0278be2f450..73a5748a84e68 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.test.tsx @@ -8,7 +8,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { LookupsDataInput } from './lookups_data_input'; -import { DataInputStep } from '../constants'; +import { SplunkDataInputStep } from '../constants'; import { getRuleMigrationStatsMock } from '../../../../__mocks__'; import { SiemMigrationTaskStatus } from '../../../../../../../common/siem_migrations/constants'; import { TestProviders } from '../../../../../../common/mock'; @@ -40,7 +40,7 @@ jest.mock('../../../../../../common/lib/kibana/kibana_react', () => ({ describe('LookupsDataInput', () => { const defaultProps = { onAllLookupsCreated: jest.fn(), - dataInputStep: DataInputStep.Lookups, + dataInputStep: SplunkDataInputStep.Lookups, migrationStats: getRuleMigrationStatsMock({ status: SiemMigrationTaskStatus.READY }), missingLookups: ['lookup1', 'lookup2'], }; @@ -84,7 +84,7 @@ describe('LookupsDataInput', () => { it('does not render description when dataInputStep is not LookupsUpload', () => { const { queryByTestId } = render( - + ); expect(queryByTestId('lookupsUploadDescription')).not.toBeInTheDocument(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.tsx index 7e85980fd5329..dcab90085c33a 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/lookups/lookups_data_input.tsx @@ -25,7 +25,7 @@ import { useKibana } from '../../../../../../common/lib/kibana/kibana_react'; import type { RuleMigrationTaskStats } from '../../../../../../../common/siem_migrations/model/rule_migration.gen'; import type { OnResourcesCreated } from '../../types'; import * as i18n from './translations'; -import { DataInputStep } from '../constants'; +import { SplunkDataInputStep } from '../constants'; import { useMissingLookupsListStep } from './sub_steps/missing_lookups_list'; import { useLookupsFileUploadStep } from './sub_steps/lookups_file_upload'; @@ -36,14 +36,14 @@ interface LookupsDataInputSubStepsProps { } interface LookupsDataInputProps extends Omit { - dataInputStep: DataInputStep; + dataInputStep: SplunkDataInputStep; migrationStats?: RuleMigrationTaskStats; missingLookups?: string[]; } export const LookupsDataInput = React.memo( ({ dataInputStep, migrationStats, missingLookups, onAllLookupsCreated }) => { const dataInputStatus = useMemo( - () => getEuiStepStatus(DataInputStep.Lookups, dataInputStep), + () => getEuiStepStatus(SplunkDataInputStep.Lookups, dataInputStep), [dataInputStep] ); @@ -56,7 +56,7 @@ export const LookupsDataInput = React.memo( diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.test.tsx index af1622f9e8e1f..454548ebad41e 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.test.tsx @@ -8,7 +8,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import React from 'react'; import { MacrosDataInput } from './macros_data_input'; -import { DataInputStep } from '../constants'; +import { SplunkDataInputStep } from '../constants'; import { getRuleMigrationStatsMock } from '../../../../__mocks__'; import { SiemMigrationTaskStatus } from '../../../../../../../common/siem_migrations/constants'; import { TestProviders } from '../../../../../../common/mock'; @@ -46,7 +46,7 @@ describe('MacrosDataInput', () => { const defaultProps = { onMissingResourcesFetched: jest.fn(), - dataInputStep: DataInputStep.Macros, + dataInputStep: SplunkDataInputStep.Macros, migrationStats: getRuleMigrationStatsMock({ status: SiemMigrationTaskStatus.READY }), missingMacros: ['macro1', 'macro2'], }; @@ -83,7 +83,7 @@ describe('MacrosDataInput', () => { it('does not render sub-steps when dataInputStep is not MacrosUpload', () => { const { queryByTestId } = render( - + ); expect(queryByTestId('migrationsSubSteps')).not.toBeInTheDocument(); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.tsx index 7915e63642ba7..9579cd6ee3681 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/macros_data_input.tsx @@ -14,7 +14,7 @@ import { useKibana } from '../../../../../../common/lib/kibana/kibana_react'; import type { RuleMigrationTaskStats } from '../../../../../../../common/siem_migrations/model/rule_migration.gen'; import type { OnResourcesCreated, OnMissingResourcesFetched } from '../../types'; import * as i18n from './translations'; -import { DataInputStep } from '../constants'; +import { SplunkDataInputStep } from '../constants'; import { useCopyExportQueryStep } from './sub_steps/copy_export_query'; import { useMacrosFileUploadStep } from './sub_steps/macros_file_upload'; import { useCheckResourcesStep } from './sub_steps/check_resources'; @@ -26,14 +26,14 @@ interface MacrosDataInputSubStepsProps { } interface MacrosDataInputProps extends Omit { - dataInputStep: DataInputStep; + dataInputStep: SplunkDataInputStep; migrationStats?: RuleMigrationTaskStats; missingMacros?: string[]; } export const MacrosDataInput = React.memo( ({ dataInputStep, migrationStats, missingMacros, onMissingResourcesFetched }) => { const dataInputStatus = useMemo( - () => getEuiStepStatus(DataInputStep.Macros, dataInputStep), + () => getEuiStepStatus(SplunkDataInputStep.Macros, dataInputStep), [dataInputStep] ); @@ -46,7 +46,7 @@ export const MacrosDataInput = React.memo( diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.test.tsx index 492f2ef32f750..e48b680120647 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.test.tsx @@ -8,8 +8,9 @@ import React from 'react'; import { render } from '@testing-library/react'; import { RulesDataInput } from './rules_data_input'; -import { DataInputStep } from '../constants'; +import { SplunkDataInputStep } from '../constants'; import { TestProviders } from '../../../../../../common/mock/test_providers'; +import { MigrationSource } from '../../../../../common/types'; describe('RulesDataInput', () => { const defaultProps = { @@ -21,7 +22,11 @@ describe('RulesDataInput', () => { it('renders the step number', () => { const { getByTestId } = render( - + ); @@ -32,7 +37,11 @@ describe('RulesDataInput', () => { it('renders the title', () => { const { getByTestId } = render( - + ); @@ -43,7 +52,11 @@ describe('RulesDataInput', () => { it('renders sub-steps when the step is current', () => { const { getByTestId } = render( - + ); @@ -53,7 +66,11 @@ describe('RulesDataInput', () => { it('does not render sub-steps when the step is not current', () => { const { queryByTestId } = render( - + ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx index f044b0d1baa2f..ff6fc442823ee 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx @@ -13,7 +13,7 @@ import { getEuiStepStatus } from '../../../../../common/utils/get_eui_step_statu import { useKibana } from '../../../../../../common/lib/kibana'; import type { OnMigrationCreated, OnMissingResourcesFetched } from '../../types'; import * as i18n from './translations'; -import { DataInputStep } from '../constants'; +import { QradarDataInputStep, SplunkDataInputStep } from '../constants'; import { useCopyExportQueryStep } from './sub_steps/copy_export_query'; import { useRulesFileUploadStep } from './sub_steps/rules_file_upload'; import { useCheckResourcesStep } from './sub_steps/check_resources'; @@ -23,11 +23,11 @@ import { MigrationSource } from '../../../../../common/types'; interface RulesDataInputSubStepsProps { migrationStats?: RuleMigrationStats; onMigrationCreated: OnMigrationCreated; - onMissingResourcesFetched: OnMissingResourcesFetched; + onMissingResourcesFetched?: OnMissingResourcesFetched; migrationSource: MigrationSource; } interface RulesDataInputProps extends RulesDataInputSubStepsProps { - dataInputStep: DataInputStep; + dataInputStep: SplunkDataInputStep | QradarDataInputStep; } export const RulesDataInput = React.memo( ({ @@ -37,9 +37,22 @@ export const RulesDataInput = React.memo( onMigrationCreated, onMissingResourcesFetched, }) => { + const dataInputNumber = useMemo( + () => + migrationSource === MigrationSource.QRADAR + ? QradarDataInputStep.Rules + : SplunkDataInputStep.Rules, + [migrationSource] + ); const dataInputStatus = useMemo( - () => getEuiStepStatus(DataInputStep.Rules, dataInputStep), - [dataInputStep] + () => + getEuiStepStatus( + migrationSource === MigrationSource.QRADAR + ? QradarDataInputStep.Rules + : SplunkDataInputStep.Rules, + dataInputStep + ), + [dataInputStep, migrationSource] ); return ( @@ -50,7 +63,7 @@ export const RulesDataInput = React.memo( @@ -84,36 +97,67 @@ type SubStep = 1 | 2 | 3 | 4 | typeof END; export const RulesDataInputSubSteps = React.memo( ({ migrationStats, onMigrationCreated, onMissingResourcesFetched, migrationSource }) => { const { telemetry } = useKibana().services.siemMigrations.rules; - const [subStep, setSubStep] = useState(migrationStats ? 4 : 1); + const [subStep, setSubStep] = useState<{ + [MigrationSource.SPLUNK]: SubStep; + [MigrationSource.QRADAR]: SubStep; + }>({ + [MigrationSource.QRADAR]: migrationStats ? 4 : 1, + [MigrationSource.SPLUNK]: migrationStats ? 4 : 1, + }); - const [migrationName, setMigrationName] = useState(migrationStats?.name); + const setMigrationSubStep = useCallback( + (step: SubStep) => { + setSubStep((prev) => ({ ...prev, ...{ [migrationSource]: step } })); + }, + [migrationSource] + ); + + const [migrationName, setMigrationName] = useState<{ + [MigrationSource.SPLUNK]: string | undefined; + [MigrationSource.QRADAR]: string | undefined; + }>({ + [MigrationSource.SPLUNK]: migrationStats?.name, + [MigrationSource.QRADAR]: migrationStats?.name, + }); + + const setRulesMigrationName = useCallback( + (name: string) => { + setMigrationName((prev) => ({ ...prev, ...{ [migrationSource]: name } })); + }, + [migrationSource] + ); const [isRulesFileReady, setIsRuleFileReady] = useState(false); // Migration name step const setName = useCallback( (name: string) => { - setMigrationName(name); + setRulesMigrationName(name); if (name) { - setSubStep(isRulesFileReady ? 3 : 2); + setMigrationSubStep(isRulesFileReady ? 3 : 2); } else { - setSubStep(1); + setMigrationSubStep(1); } }, - [isRulesFileReady] + [isRulesFileReady, setMigrationSubStep, setRulesMigrationName] ); const nameStep = useMigrationNameStep({ - status: getEuiStepStatus(1, subStep), + status: getEuiStepStatus(1, subStep[migrationSource]), setMigrationName: setName, - migrationName, + migrationName: migrationName[migrationSource], }); // Copy query step const onCopied = useCallback(() => { - setSubStep((currentSubStep) => (currentSubStep !== 1 ? 3 : currentSubStep)); // Move to the next step only if step 1 was completed + setSubStep((currentSubStep) => + currentSubStep[migrationSource] !== 1 + ? { ...currentSubStep, [migrationSource]: 3 } + : currentSubStep + ); // Move to the next step only if step 1 was completed + telemetry.reportSetupQueryCopied({ migrationId: migrationStats?.id }); - }, [telemetry, migrationStats?.id]); + }, [telemetry, migrationStats?.id, migrationSource]); const copyStep = useCopyExportQueryStep({ - status: getEuiStepStatus(2, subStep), + status: getEuiStepStatus(2, subStep[migrationSource]), onCopied, migrationSource, }); @@ -122,43 +166,46 @@ export const RulesDataInputSubSteps = React.memo( const onSplunkMigrationCreatedStep = useCallback( (stats) => { onMigrationCreated(stats); - setSubStep(4); + setMigrationSubStep(4); }, - [onMigrationCreated] + [onMigrationCreated, setMigrationSubStep] ); const onQradarMigrationCreatedStep = useCallback( (stats) => { onMigrationCreated(stats); - setSubStep(END); + setMigrationSubStep(END); + }, + [onMigrationCreated, setMigrationSubStep] + ); + const onRulesFileChanged = useCallback( + (files: FileList | null) => { + setIsRuleFileReady(!!files?.length); + setMigrationSubStep(3); }, - [onMigrationCreated] + [setMigrationSubStep] ); - const onRulesFileChanged = useCallback((files: FileList | null) => { - setIsRuleFileReady(!!files?.length); - setSubStep(3); - }, []); const uploadStep = useRulesFileUploadStep({ - status: getEuiStepStatus(3, subStep), + status: getEuiStepStatus(3, subStep[migrationSource]), migrationStats, onRulesFileChanged, onMigrationCreated: migrationSource === MigrationSource.SPLUNK ? onSplunkMigrationCreatedStep : onQradarMigrationCreatedStep, - migrationName, + migrationName: migrationName[migrationSource], migrationSource, }); // Check missing resources step const onMissingResourcesFetchedStep = useCallback( (missingResources) => { - onMissingResourcesFetched(missingResources); - setSubStep(END); + onMissingResourcesFetched?.(missingResources); + setMigrationSubStep(END); }, - [onMissingResourcesFetched] + [onMissingResourcesFetched, setMigrationSubStep] ); const resourcesStep = useCheckResourcesStep({ - status: getEuiStepStatus(4, subStep), + status: getEuiStepStatus(4, subStep[migrationSource]), migrationStats, onMissingResourcesFetched: onMissingResourcesFetchedStep, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx index fda03c35dba66..04e80c0760d07 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.tsx @@ -15,7 +15,8 @@ import { type OnSuccess, } from '../../../../../../service/hooks/use_create_migration'; import * as i18n from './translations'; -import type { MigrationSource } from '../../../../../../../common/types'; +import { MigrationSource } from '../../../../../../../common/types'; +import { RulesXMLFileUpload } from './rules_xml_file_upload'; export interface RulesFileUploadStepProps { status: EuiStepStatus; @@ -33,13 +34,26 @@ export const useRulesFileUploadStep = ({ onMigrationCreated, onRulesFileChanged, }: RulesFileUploadStepProps): EuiStepProps => { - const [isCreated, setIsCreated] = useState(!!migrationStats); + const [isCreated, setIsCreated] = useState<{ + [MigrationSource.SPLUNK]: boolean; + [MigrationSource.QRADAR]: boolean; + }>({ + [MigrationSource.SPLUNK]: !!migrationStats, + [MigrationSource.QRADAR]: !!migrationStats, + }); + const setMigrationCreated = useCallback( + (created: boolean) => { + setIsCreated((prev) => ({ ...prev, ...{ [migrationSource]: created } })); + }, + [migrationSource] + ); + const onSuccess = useCallback( (stats) => { - setIsCreated(true); + setMigrationCreated(true); onMigrationCreated(stats); }, - [onMigrationCreated] + [onMigrationCreated, setMigrationCreated] ); const { createMigration, isLoading, error } = useCreateMigration(onSuccess); @@ -53,16 +67,19 @@ export const useRulesFileUploadStep = ({ return status; }, [isLoading, error, status]); + const Component = + migrationSource === MigrationSource.QRADAR ? RulesXMLFileUpload : RulesFileUpload; + return { title: i18n.RULES_DATA_INPUT_FILE_UPLOAD_TITLE, status: uploadStepStatus, children: ( - diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx index 3049c602340fa..f4b34d48b0159 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.test.tsx @@ -17,6 +17,7 @@ import path from 'path'; import os from 'os'; import { splunkTestRules } from './splunk_rules.test.data'; import type { OriginalRule } from '../../../../../../../../../common/siem_migrations/model/rule_migration.gen'; +import { MigrationSource } from '../../../../../../../common/types'; const mockCreateMigration: CreateMigration = jest.fn(); const mockOnRulesFileChanged = jest.fn(); @@ -30,6 +31,7 @@ const defaultProps: RulesFileUploadProps = { isLoading: false, isCreated: false, migrationName, + migrationSource: MigrationSource.SPLUNK, }; const renderTestComponent = (props: Partial = {}) => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx index e44590017edeb..a200948b83222 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx @@ -15,16 +15,16 @@ import type { import { UploadFileButton } from '../../../../../../../common/components'; import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file_upload_error'; import { - useParseFileInput, type SplunkRow, + useParseFileInput, } from '../../../../../../../common/hooks/use_parse_file_input'; import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration'; import type { CreateRuleMigrationRulesRequestBody } from '../../../../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; import type { OriginalRule } from '../../../../../../../../../common/siem_migrations/model/rule_migration.gen'; -import type { SPLUNK_RULES_COLUMNS } from '../../../../constants'; import * as i18n from './translations'; import { MigrationSource } from '../../../../../../../common/types'; import { RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION } from '../check_resources/translations'; +import type { SPLUNK_RULES_COLUMNS } from '../../../../constants'; type SplunkRulesResult = Partial>; @@ -69,22 +69,13 @@ export const RulesFileUpload = React.memo( const { parseFile, isParsing, error: fileError } = useParseFileInput(onFileParsed); - // const onFileChange = useCallback( - // (files: FileList | null) => { - // setRulesToUpload([]); - // onRulesFileChanged(files); - // parseFile(files); - // }, - // [parseFile, onRulesFileChanged] - // ); - const onFileChange = useCallback( (files: FileList | null) => { - console.log('Files changed:', files); - setRulesToUpload(files?.length ? Array.from(files) : []); + setRulesToUpload([]); onRulesFileChanged(files); + parseFile(files); }, - [setRulesToUpload, onRulesFileChanged] + [parseFile, onRulesFileChanged] ); const error = useMemo(() => { diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx new file mode 100644 index 0000000000000..a737c28dfa14f --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx @@ -0,0 +1,183 @@ +/* + * 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, { useCallback, useMemo, useRef, useState } from 'react'; +import { EuiFilePicker, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui'; +import type { + EuiFilePickerClass, + EuiFilePickerProps, +} from '@elastic/eui/src/components/form/file_picker/file_picker'; +import { UploadFileButton } from '../../../../../../../common/components'; +import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration'; +import * as i18n from './translations'; +import { MigrationSource } from '../../../../../../../common/types'; +import { RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION } from '../check_resources/translations'; +import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file_upload_error'; + +export interface RulesXMLFileUploadProps { + createMigration: CreateMigration; + isLoading: boolean; + isCreated: boolean; + onRulesFileChanged: (files: FileList | null) => void; + migrationName: string | undefined; + migrationSource: MigrationSource; + apiError: string | undefined; +} + +const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT: Record = { + [MigrationSource.SPLUNK]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_SPLUNK, + [MigrationSource.QRADAR]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_QRADAR, +}; + +export const RulesXMLFileUpload = React.memo( + ({ + createMigration, + migrationName, + migrationSource, + apiError, + isLoading, + isCreated, + onRulesFileChanged, + }) => { + const [rulesToUpload, setRulesToUpload] = useState(); + const filePickerRef = useRef(null); + + const createRules = useCallback(() => { + if (migrationName && rulesToUpload) { + filePickerRef.current?.removeFiles(); + createMigration({ migrationName, rules: rulesToUpload, migrationSource }); + } + }, [createMigration, migrationName, migrationSource, rulesToUpload]); + + const onXMLFileParsed = useCallback((content: string) => { + setRulesToUpload(content); + }, []); + + const [isParsing, setIsParsing] = useState(false); + const [error, setError] = useState(); + + const parseFile = useCallback( + (files: FileList | null) => { + setError(undefined); + + if (!files || files.length === 0) { + return; + } + + const file = files[0]; + const reader = new FileReader(); + + reader.onloadstart = () => setIsParsing(true); + reader.onloadend = () => setIsParsing(false); + + reader.onload = function (e) { + // We can safely cast to string since we call `readAsText` to load the file. + const fileContent = e.target?.result as string | undefined; + + if (fileContent == null) { + setError(FILE_UPLOAD_ERROR.CAN_NOT_READ); + return; + } + + if (fileContent === '' && e.loaded > 100000) { + // V8-based browsers can't handle large files and return an empty string + // instead of an error; see https://stackoverflow.com/a/61316641 + setError(FILE_UPLOAD_ERROR.TOO_LARGE_TO_PARSE); + return; + } + + try { + onXMLFileParsed(fileContent); + } catch (err) { + setError(err.message); + } + }; + + const handleReaderError = function () { + const message = reader.error?.message; + if (message) { + setError(FILE_UPLOAD_ERROR.CAN_NOT_READ_WITH_REASON(message)); + } else { + setError(FILE_UPLOAD_ERROR.CAN_NOT_READ); + } + }; + + reader.onerror = handleReaderError; + reader.onabort = handleReaderError; + + reader.readAsText(file); + }, + [onXMLFileParsed] + ); + + const validationError = useMemo(() => { + if (apiError) { + return apiError; + } + return error; + }, [apiError, error]); + + const onFileChange = useCallback( + (files: FileList | null) => { + onRulesFileChanged(files); + parseFile(files); + }, + [parseFile, onRulesFileChanged] + ); + + const showLoader = isParsing || isLoading; + const isDisabled = !migrationName || showLoader || isCreated; + const isButtonDisabled = isDisabled || rulesToUpload == null; + + return ( + + + {RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION} + + + + >} + fullWidth + initialPromptText={ + + {RULES_DATA_INPUT_FILE_UPLOAD_PROMPT[migrationSource]} + + } + accept={ + migrationSource === MigrationSource.SPLUNK + ? 'application/json, application/x-ndjson' + : '.xml' + } + onChange={onFileChange} + display="large" + aria-label="Upload rules file" + isLoading={showLoader} + disabled={isDisabled} + data-test-subj="rulesFilePicker" + data-loading={isParsing} + /> + + + + + + + + + + + ); + } +); +RulesXMLFileUpload.displayName = 'RulesXMLFileUpload'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts index 6015db9b75fa6..4e78a3f0bf0f4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts @@ -34,7 +34,7 @@ export type CreateMigration = ({ }: { migrationName: string; migrationSource: MigrationSource; - rules: CreateRuleMigrationRulesRequestBody; + rules: CreateRuleMigrationRulesRequestBody | string; }) => void; export type OnSuccess = (migrationStats: RuleMigrationStats) => void; @@ -43,15 +43,11 @@ export const useCreateMigration = (onSuccess: OnSuccess) => { const [state, dispatch] = useReducer(reducer, initialState); const createMigration = useCallback( - ({ migrationName, migrationSource, rules }) => { + ({ migrationName, rules }) => { (async () => { try { dispatch({ type: 'start' }); - const migrationId = await siemMigrations.rules.createRuleMigration( - rules, - migrationName, - migrationSource - ); + const migrationId = await siemMigrations.rules.createRuleMigration(rules, migrationName); const stats = await siemMigrations.rules.api.getRuleMigrationStats({ migrationId, }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts index 6bae1d6453aa1..a6410db364458 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/rule_migrations_service.ts @@ -25,11 +25,7 @@ import { getMissingCapabilitiesToast, getNoConnectorToast, } from '../../common/service'; -import type { - GetMigrationStatsParams, - GetMigrationsStatsAllParams, - MigrationSource, -} from '../../common/types'; +import type { GetMigrationStatsParams, GetMigrationsStatsAllParams } from '../../common/types'; import { raiseSuccessToast } from './notification/success_notification'; import { START_STOP_POLLING_SLEEP_SECONDS } from '../../common/constants'; @@ -52,11 +48,19 @@ export class SiemRulesMigrationsService extends SiemMigrationsServiceBase { const rulesCount = data.length; if (rulesCount === 0) { @@ -87,7 +90,11 @@ export class SiemRulesMigrationsService extends SiemMigrationsServiceBase Date: Tue, 2 Dec 2025 14:46:56 +0000 Subject: [PATCH 3/7] clean up rules uploader component --- .../common/hooks/use_parse_file_input.ts | 7 +- .../macros_file_upload/macros_file_upload.tsx | 5 +- .../dashboards_file_upload.tsx | 11 ++- .../macros_file_upload/macros_file_upload.tsx | 5 +- .../rules_file_upload/rules_file_upload.tsx | 21 ++--- .../rules_xml_file_upload.tsx | 87 +++---------------- .../service/hooks/use_create_migration.ts | 2 +- 7 files changed, 37 insertions(+), 101 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/hooks/use_parse_file_input.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/hooks/use_parse_file_input.ts index fcc87ed8584c7..553a9338696d2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/hooks/use_parse_file_input.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/hooks/use_parse_file_input.ts @@ -12,7 +12,7 @@ export interface SplunkRow { result: T; } -export type OnFileParsed = (content: SplunkRow[]) => void; +export type OnFileParsed = (content: string) => void; export const useParseFileInput = (onFileParsed: OnFileParsed) => { const [isParsing, setIsParsing] = useState(false); @@ -49,8 +49,7 @@ export const useParseFileInput = (onFileParsed: OnFileParsed) => { } try { - const parsedData = parseContent(fileContent); - onFileParsed(parsedData); + onFileParsed(fileContent); } catch (err) { setError(err.message); } @@ -76,7 +75,7 @@ export const useParseFileInput = (onFileParsed: OnFileParsed) => { return { parseFile, isParsing, error }; }; -const parseContent = (fileContent: string): SplunkRow[] => { +export const parseContent = (fileContent: string): SplunkRow[] => { const trimmedContent = fileContent.trim(); let arrayContent: SplunkRow[]; if (trimmedContent.startsWith('[')) { diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx index bf1b9e9d4d7fe..5ea1a3f623ce1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx @@ -15,6 +15,7 @@ import type { import { UploadFileButton } from '../../../../../../../common/components/migration_steps'; import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file_upload_error'; import { + parseContent, useParseFileInput, type SplunkRow, } from '../../../../../../../common/hooks/use_parse_file_input'; @@ -39,8 +40,8 @@ export const MacrosFileUpload = React.memo( createResources(macrosToUpload); }, [createResources, macrosToUpload]); - const onFileParsed = useCallback((content: Array>) => { - const macros = content.map(formatMacroRow); + const onFileParsed = useCallback((content: string) => { + const macros = parseContent(content).map(formatMacroRow); setMacrosToUpload(macros); }, []); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/upload_dashboards/sub_steps/dashboards_file_upload/dashboards_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/upload_dashboards/sub_steps/dashboards_file_upload/dashboards_file_upload.tsx index fbae6842f093a..25557072e92b1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/upload_dashboards/sub_steps/dashboards_file_upload/dashboards_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/dashboards/components/data_input_flyout/steps/upload_dashboards/sub_steps/dashboards_file_upload/dashboards_file_upload.tsx @@ -15,7 +15,10 @@ import type { import { UploadFileButton } from '../../../../../../../common/components/migration_steps'; import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file_upload_error'; import type { SplunkRow } from '../../../../../../../common/hooks/use_parse_file_input'; -import { useParseFileInput } from '../../../../../../../common/hooks/use_parse_file_input'; +import { + parseContent, + useParseFileInput, +} from '../../../../../../../common/hooks/use_parse_file_input'; import * as i18n from './translations'; import type { SplunkDashboardsResult, OnMigrationCreated } from '../../../../types'; import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration'; @@ -46,8 +49,10 @@ export const DashboardsFileUpload = React.memo( const filePickerRef = useRef(null); const onFileParsed = useCallback( - (content: Array>) => { - const dashboards = content.map(formatDashboardRow) as SplunkDashboardsResult[]; + (content: string) => { + const dashboards = parseContent(content).map( + formatDashboardRow + ) as SplunkDashboardsResult[]; setUploadedDashboards(dashboards); onFileUpload?.(dashboards); }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx index d11b8f0c0d057..f93eab54801fe 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/macros/sub_steps/macros_file_upload/macros_file_upload.tsx @@ -15,6 +15,7 @@ import type { import { UploadFileButton } from '../../../../../../../common/components'; import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file_upload_error'; import { + parseContent, useParseFileInput, type SplunkRow, } from '../../../../../../../common/hooks/use_parse_file_input'; @@ -39,8 +40,8 @@ export const MacrosFileUpload = React.memo( createResources(macrosToUpload); }, [createResources, macrosToUpload]); - const onFileParsed = useCallback((content: Array>) => { - const macros = content.map(formatMacroRow); + const onFileParsed = useCallback((content: string) => { + const macros = parseContent(content).map(formatMacroRow); setMacrosToUpload(macros); }, []); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx index a200948b83222..087383d6895c4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_file_upload.tsx @@ -17,12 +17,13 @@ import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file import { type SplunkRow, useParseFileInput, + parseContent, } from '../../../../../../../common/hooks/use_parse_file_input'; import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration'; import type { CreateRuleMigrationRulesRequestBody } from '../../../../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen'; import type { OriginalRule } from '../../../../../../../../../common/siem_migrations/model/rule_migration.gen'; import * as i18n from './translations'; -import { MigrationSource } from '../../../../../../../common/types'; +import type { MigrationSource } from '../../../../../../../common/types'; import { RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION } from '../check_resources/translations'; import type { SPLUNK_RULES_COLUMNS } from '../../../../constants'; @@ -38,10 +39,6 @@ export interface RulesFileUploadProps { apiError: string | undefined; } -const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT: Record = { - [MigrationSource.SPLUNK]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_SPLUNK, - [MigrationSource.QRADAR]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_QRADAR, -}; export const RulesFileUpload = React.memo( ({ createMigration, @@ -62,8 +59,8 @@ export const RulesFileUpload = React.memo( } }, [createMigration, migrationName, migrationSource, rulesToUpload]); - const onFileParsed = useCallback((content: Array>) => { - const rules = content.map(formatRuleRow); + const onFileParsed = useCallback((content: string) => { + const rules = parseContent(content).map(formatRuleRow); setRulesToUpload(rules); }, []); @@ -103,14 +100,10 @@ export const RulesFileUpload = React.memo( fullWidth initialPromptText={ - {RULES_DATA_INPUT_FILE_UPLOAD_PROMPT[migrationSource]} + {i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_SPLUNK} } - accept={ - migrationSource === MigrationSource.SPLUNK - ? 'application/json, application/x-ndjson' - : '.xml' - } + accept={'application/json, application/x-ndjson'} onChange={onFileChange} display="large" aria-label="Upload rules file" @@ -138,7 +131,7 @@ export const RulesFileUpload = React.memo( ); RulesFileUpload.displayName = 'RulesFileUpload'; -const formatRuleRow = (row: SplunkRow): OriginalRule => { +export const formatRuleRow = (row: SplunkRow): OriginalRule => { if (!isPlainObject(row.result)) { throw new Error(FILE_UPLOAD_ERROR.NOT_OBJECT); } diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx index a737c28dfa14f..dfa146fb5d4d5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/rules_xml_file_upload.tsx @@ -14,9 +14,9 @@ import type { import { UploadFileButton } from '../../../../../../../common/components'; import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration'; import * as i18n from './translations'; -import { MigrationSource } from '../../../../../../../common/types'; +import type { MigrationSource } from '../../../../../../../common/types'; import { RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION } from '../check_resources/translations'; -import { FILE_UPLOAD_ERROR } from '../../../../../../../common/translations/file_upload_error'; +import { useParseFileInput } from '../../../../../../../common/hooks/use_parse_file_input'; export interface RulesXMLFileUploadProps { createMigration: CreateMigration; @@ -28,11 +28,6 @@ export interface RulesXMLFileUploadProps { apiError: string | undefined; } -const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT: Record = { - [MigrationSource.SPLUNK]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_SPLUNK, - [MigrationSource.QRADAR]: i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_QRADAR, -}; - export const RulesXMLFileUpload = React.memo( ({ createMigration, @@ -57,77 +52,23 @@ export const RulesXMLFileUpload = React.memo( setRulesToUpload(content); }, []); - const [isParsing, setIsParsing] = useState(false); - const [error, setError] = useState(); + const { parseFile, isParsing, error: fileError } = useParseFileInput(onXMLFileParsed); - const parseFile = useCallback( + const onFileChange = useCallback( (files: FileList | null) => { - setError(undefined); - - if (!files || files.length === 0) { - return; - } - - const file = files[0]; - const reader = new FileReader(); - - reader.onloadstart = () => setIsParsing(true); - reader.onloadend = () => setIsParsing(false); - - reader.onload = function (e) { - // We can safely cast to string since we call `readAsText` to load the file. - const fileContent = e.target?.result as string | undefined; - - if (fileContent == null) { - setError(FILE_UPLOAD_ERROR.CAN_NOT_READ); - return; - } - - if (fileContent === '' && e.loaded > 100000) { - // V8-based browsers can't handle large files and return an empty string - // instead of an error; see https://stackoverflow.com/a/61316641 - setError(FILE_UPLOAD_ERROR.TOO_LARGE_TO_PARSE); - return; - } - - try { - onXMLFileParsed(fileContent); - } catch (err) { - setError(err.message); - } - }; - - const handleReaderError = function () { - const message = reader.error?.message; - if (message) { - setError(FILE_UPLOAD_ERROR.CAN_NOT_READ_WITH_REASON(message)); - } else { - setError(FILE_UPLOAD_ERROR.CAN_NOT_READ); - } - }; - - reader.onerror = handleReaderError; - reader.onabort = handleReaderError; - - reader.readAsText(file); + setRulesToUpload(undefined); + onRulesFileChanged(files); + parseFile(files); }, - [onXMLFileParsed] + [parseFile, onRulesFileChanged] ); const validationError = useMemo(() => { if (apiError) { return apiError; } - return error; - }, [apiError, error]); - - const onFileChange = useCallback( - (files: FileList | null) => { - onRulesFileChanged(files); - parseFile(files); - }, - [parseFile, onRulesFileChanged] - ); + return fileError; + }, [apiError, fileError]); const showLoader = isParsing || isLoading; const isDisabled = !migrationName || showLoader || isCreated; @@ -147,14 +88,10 @@ export const RulesXMLFileUpload = React.memo( fullWidth initialPromptText={ - {RULES_DATA_INPUT_FILE_UPLOAD_PROMPT[migrationSource]} + {i18n.RULES_DATA_INPUT_FILE_UPLOAD_PROMPT_QRADAR} } - accept={ - migrationSource === MigrationSource.SPLUNK - ? 'application/json, application/x-ndjson' - : '.xml' - } + accept={'.xml'} onChange={onFileChange} display="large" aria-label="Upload rules file" diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts index 4e78a3f0bf0f4..480e061c824a1 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.ts @@ -54,7 +54,7 @@ export const useCreateMigration = (onSuccess: OnSuccess) => { notifications.toasts.addSuccess({ title: RULES_DATA_INPUT_CREATE_MIGRATION_SUCCESS_TITLE, - text: RULES_DATA_INPUT_CREATE_MIGRATION_SUCCESS_DESCRIPTION(rules.length), + text: RULES_DATA_INPUT_CREATE_MIGRATION_SUCCESS_DESCRIPTION(stats.items.total), }); onSuccess(stats); dispatch({ type: 'success' }); From 8d081e42939bc5b9f0d04e6b478deb588ffc93a6 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Tue, 2 Dec 2025 17:20:53 +0000 Subject: [PATCH 4/7] add component config --- .../migration_source_options.tsx | 76 +++++++----------- .../components/migration_source_step/types.ts | 16 ++-- .../components/migration_steps/configs.tsx | 31 +++++++ .../data_input_flyout/data_input_flyout.tsx | 5 +- .../steps/rules/rules_data_input.tsx | 80 ++++++------------- 5 files changed, 92 insertions(+), 116 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/configs.tsx diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx index c9b35b26d8758..4e1c71b9d8499 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx @@ -13,16 +13,11 @@ import type { DataInputStep, QradarDataInputStep, } from '../../../rules/components/data_input_flyout/steps/constants'; -import { - SplunkDataInputStep, - DataInputStepId, -} from '../../../rules/components/data_input_flyout/steps/constants'; -import { RulesDataInput } from '../../../rules/components/data_input_flyout/steps/rules/rules_data_input'; +import { SplunkDataInputStep } from '../../../rules/components/data_input_flyout/steps/constants'; import type { SiemMigrationResourceBase } from '../../../../../common/siem_migrations/model/common.gen'; import type { RuleMigrationStats } from '../../../rules/types'; -import { MacrosDataInput } from '../../../rules/components/data_input_flyout/steps/macros/macros_data_input'; -import { LookupsDataInput } from '../../../rules/components/data_input_flyout/steps/lookups/lookups_data_input'; import type { QradarMigrationSteps, SplunkMigrationSteps } from './types'; +import { STEP_COMPONENTS } from '../migration_steps/configs'; export const MIGRATIONSOURCE_OPTIONS: Array> = [ { @@ -94,47 +89,29 @@ export const useSplunkMigrationSteps = ({ }, [setDataInputStep]); const SPLUNK_MIGRATION_STEPS: SplunkMigrationSteps = useMemo( - () => [ - { - id: DataInputStepId.SplunkRules, - Component: RulesDataInput, + () => + STEP_COMPONENTS[MigrationSource.SPLUNK].map(({ id, Component }) => ({ + id, + Component, extraProps: { dataInputStep, migrationSource, migrationStats, - onMigrationCreated, - onMissingResourcesFetched, - }, - }, - { - id: DataInputStepId.SplunkMacros, - Component: MacrosDataInput, - extraProps: { - dataInputStep, - onMissingResourcesFetched, + missingLookups: missingResourcesIndexed?.lookups, missingMacros: missingResourcesIndexed?.macros, - migrationStats, - }, - }, - { - id: DataInputStepId.SplunkLookups, - Component: LookupsDataInput, - extraProps: { - dataInputStep, onAllLookupsCreated, - missingLookups: missingResourcesIndexed?.lookups, - migrationStats, + onMissingResourcesFetched, + onMigrationCreated, }, - }, - ], + })) as SplunkMigrationSteps, [ dataInputStep, migrationSource, migrationStats, onMigrationCreated, - onMissingResourcesFetched, missingResourcesIndexed, onAllLookupsCreated, + onMissingResourcesFetched, ] ); @@ -153,39 +130,40 @@ export const useQradarMigrationSteps = ({ migrationStats?: RuleMigrationStats; }): QradarMigrationSteps | null => { const QRADAR_MIGRATION_STEPS: QradarMigrationSteps = useMemo( - () => [ - { - id: DataInputStepId.QradarRules, - Component: RulesDataInput, + () => + STEP_COMPONENTS[MigrationSource.QRADAR].map(({ id, Component }) => ({ + id, + Component, extraProps: { dataInputStep, migrationSource, migrationStats, onMigrationCreated, }, - }, - ], + })), [dataInputStep, migrationSource, migrationStats, onMigrationCreated] ); return migrationSource === MigrationSource.QRADAR ? QRADAR_MIGRATION_STEPS : null; }; -export const useMigrationSteps = ({ - onMigrationCreated, - dataInputStep, - migrationSource, - migrationStats, - setMigrationDataInputStep, -}: { +interface UseMigrationStepsParams { onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; dataInputStep: DataInputStep; migrationSource: MigrationSource; migrationStats?: RuleMigrationStats; setMigrationDataInputStep: (step: SplunkDataInputStep | QradarDataInputStep) => void; -}): SplunkMigrationSteps | QradarMigrationSteps | null => { +} + +export const useMigrationSteps = ({ + onMigrationCreated, + dataInputStep, + migrationSource, + migrationStats, + setMigrationDataInputStep: setDataInputStep, +}: UseMigrationStepsParams): SplunkMigrationSteps | QradarMigrationSteps | null => { const splunkMigrationSteps: SplunkMigrationSteps | null = useSplunkMigrationSteps({ - setDataInputStep: setMigrationDataInputStep, + setDataInputStep, dataInputStep: dataInputStep[MigrationSource.SPLUNK], migrationSource, migrationStats, diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts index c5550a9933b4a..150ceaadc9eb7 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts @@ -13,17 +13,17 @@ import type { RulesDataInput } from '../../../rules/components/data_input_flyout interface RulesStep { id: T; Component: typeof RulesDataInput; - extraProps: React.ComponentProps; + extraProps?: React.ComponentProps; } interface MacrosStep { id: DataInputStepId.SplunkMacros; Component: typeof MacrosDataInput; - extraProps: React.ComponentProps; + extraProps?: React.ComponentProps; } interface LookupsStep { id: DataInputStepId.SplunkLookups; Component: typeof LookupsDataInput; - extraProps: React.ComponentProps; + extraProps?: React.ComponentProps; } export type Step = @@ -35,10 +35,8 @@ export type Step = ? MacrosStep : LookupsStep; -export type SplunkMigrationSteps = [ - Step, - Step, - Step -]; +export type SplunkStep = RulesStep | MacrosStep | LookupsStep; -export type QradarMigrationSteps = [Step]; +export type SplunkMigrationSteps = SplunkStep[]; + +export type QradarMigrationSteps = Array>; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/configs.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/configs.tsx new file mode 100644 index 0000000000000..e295f8fee1364 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/configs.tsx @@ -0,0 +1,31 @@ +/* + * 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 { MigrationSource } from '../../types'; +import { DataInputStepId } from '../../../rules/components/data_input_flyout/steps/constants'; +import { RulesDataInput } from '../../../rules/components/data_input_flyout/steps/rules/rules_data_input'; +import { MacrosDataInput } from '../../../rules/components/data_input_flyout/steps/macros/macros_data_input'; +import { LookupsDataInput } from '../../../rules/components/data_input_flyout/steps/lookups/lookups_data_input'; +import type { QradarMigrationSteps, SplunkMigrationSteps } from '../migration_source_step/types'; + +const SPLUNK_MIGRATION_STEPS: SplunkMigrationSteps = [ + { id: DataInputStepId.SplunkRules, Component: RulesDataInput }, + { id: DataInputStepId.SplunkMacros, Component: MacrosDataInput }, + { id: DataInputStepId.SplunkLookups, Component: LookupsDataInput }, +] as const; + +const QRADAR_MIGRATION_STEPS: QradarMigrationSteps = [ + { id: DataInputStepId.QradarRules, Component: RulesDataInput }, +] as const; + +export const STEP_COMPONENTS: { + [MigrationSource.SPLUNK]: SplunkMigrationSteps; + [MigrationSource.QRADAR]: QradarMigrationSteps; +} = { + [MigrationSource.SPLUNK]: SPLUNK_MIGRATION_STEPS, + [MigrationSource.QRADAR]: QRADAR_MIGRATION_STEPS, +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx index bfa9b1d6ea1ab..07e1874033bbd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx @@ -42,8 +42,9 @@ export interface MigrationDataInputFlyoutProps { } function StepRenderer({ step }: { step: Step }) { - const Component = step.Component as React.ComponentType; - return ; + const Component = step.Component as React.ComponentType; + + return step.extraProps ? : ; } const RULES_MIGRATION_DATA_INPUT_FLYOUT_TITLE = 'rulesMigrationDataInputFlyoutTitle'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx index ff6fc442823ee..4e026a5ec8074 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx @@ -97,67 +97,38 @@ type SubStep = 1 | 2 | 3 | 4 | typeof END; export const RulesDataInputSubSteps = React.memo( ({ migrationStats, onMigrationCreated, onMissingResourcesFetched, migrationSource }) => { const { telemetry } = useKibana().services.siemMigrations.rules; - const [subStep, setSubStep] = useState<{ - [MigrationSource.SPLUNK]: SubStep; - [MigrationSource.QRADAR]: SubStep; - }>({ - [MigrationSource.QRADAR]: migrationStats ? 4 : 1, - [MigrationSource.SPLUNK]: migrationStats ? 4 : 1, - }); - - const setMigrationSubStep = useCallback( - (step: SubStep) => { - setSubStep((prev) => ({ ...prev, ...{ [migrationSource]: step } })); - }, - [migrationSource] - ); + const [subStep, setSubStep] = useState(migrationStats ? 4 : 1); - const [migrationName, setMigrationName] = useState<{ - [MigrationSource.SPLUNK]: string | undefined; - [MigrationSource.QRADAR]: string | undefined; - }>({ - [MigrationSource.SPLUNK]: migrationStats?.name, - [MigrationSource.QRADAR]: migrationStats?.name, - }); + const [migrationName, setMigrationName] = useState(migrationStats?.name); - const setRulesMigrationName = useCallback( - (name: string) => { - setMigrationName((prev) => ({ ...prev, ...{ [migrationSource]: name } })); - }, - [migrationSource] - ); const [isRulesFileReady, setIsRuleFileReady] = useState(false); // Migration name step const setName = useCallback( (name: string) => { - setRulesMigrationName(name); + setMigrationName(name); if (name) { - setMigrationSubStep(isRulesFileReady ? 3 : 2); + setSubStep(isRulesFileReady ? 3 : 2); } else { - setMigrationSubStep(1); + setSubStep(1); } }, - [isRulesFileReady, setMigrationSubStep, setRulesMigrationName] + [isRulesFileReady] ); const nameStep = useMigrationNameStep({ - status: getEuiStepStatus(1, subStep[migrationSource]), + status: getEuiStepStatus(1, subStep), setMigrationName: setName, - migrationName: migrationName[migrationSource], + migrationName, }); // Copy query step const onCopied = useCallback(() => { - setSubStep((currentSubStep) => - currentSubStep[migrationSource] !== 1 - ? { ...currentSubStep, [migrationSource]: 3 } - : currentSubStep - ); // Move to the next step only if step 1 was completed + setSubStep((currentSubStep) => (currentSubStep !== 1 ? 3 : currentSubStep)); // Move to the next step only if step 1 was completed telemetry.reportSetupQueryCopied({ migrationId: migrationStats?.id }); - }, [telemetry, migrationStats?.id, migrationSource]); + }, [telemetry, migrationStats?.id]); const copyStep = useCopyExportQueryStep({ - status: getEuiStepStatus(2, subStep[migrationSource]), + status: getEuiStepStatus(2, subStep), onCopied, migrationSource, }); @@ -166,33 +137,30 @@ export const RulesDataInputSubSteps = React.memo( const onSplunkMigrationCreatedStep = useCallback( (stats) => { onMigrationCreated(stats); - setMigrationSubStep(4); + setSubStep(4); }, - [onMigrationCreated, setMigrationSubStep] + [onMigrationCreated] ); const onQradarMigrationCreatedStep = useCallback( (stats) => { onMigrationCreated(stats); - setMigrationSubStep(END); - }, - [onMigrationCreated, setMigrationSubStep] - ); - const onRulesFileChanged = useCallback( - (files: FileList | null) => { - setIsRuleFileReady(!!files?.length); - setMigrationSubStep(3); + setSubStep(END); }, - [setMigrationSubStep] + [onMigrationCreated] ); + const onRulesFileChanged = useCallback((files: FileList | null) => { + setIsRuleFileReady(!!files?.length); + setSubStep(3); + }, []); const uploadStep = useRulesFileUploadStep({ - status: getEuiStepStatus(3, subStep[migrationSource]), + status: getEuiStepStatus(3, subStep), migrationStats, onRulesFileChanged, onMigrationCreated: migrationSource === MigrationSource.SPLUNK ? onSplunkMigrationCreatedStep : onQradarMigrationCreatedStep, - migrationName: migrationName[migrationSource], + migrationName, migrationSource, }); @@ -200,12 +168,12 @@ export const RulesDataInputSubSteps = React.memo( const onMissingResourcesFetchedStep = useCallback( (missingResources) => { onMissingResourcesFetched?.(missingResources); - setMigrationSubStep(END); + setSubStep(END); }, - [onMissingResourcesFetched, setMigrationSubStep] + [onMissingResourcesFetched] ); const resourcesStep = useCheckResourcesStep({ - status: getEuiStepStatus(4, subStep[migrationSource]), + status: getEuiStepStatus(4, subStep), migrationStats, onMissingResourcesFetched: onMissingResourcesFetchedStep, }); From 2382d1992aab29b1c96fbdeda1fb335117f50d16 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Wed, 3 Dec 2025 11:48:10 +0000 Subject: [PATCH 5/7] types --- .../migration_source_options.tsx | 90 ++++++++----------- .../components/migration_source_step/types.ts | 17 +++- .../steps/rules/rules_data_input.tsx | 11 +-- 3 files changed, 58 insertions(+), 60 deletions(-) diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx index 4e1c71b9d8499..9dc4cb76c26b9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx @@ -15,10 +15,31 @@ import type { } from '../../../rules/components/data_input_flyout/steps/constants'; import { SplunkDataInputStep } from '../../../rules/components/data_input_flyout/steps/constants'; import type { SiemMigrationResourceBase } from '../../../../../common/siem_migrations/model/common.gen'; -import type { RuleMigrationStats } from '../../../rules/types'; -import type { QradarMigrationSteps, SplunkMigrationSteps } from './types'; +import type { + QradarMigrationSteps, + RulesDataInputSubStepsProps, + SplunkMigrationSteps, +} from './types'; import { STEP_COMPONENTS } from '../migration_steps/configs'; +interface MissingResourcesIndexed { + macros: string[]; + lookups: string[]; +} + +type UseMigrationStepsProps = Omit & { + dataInputStep: DataInputStep; + setMigrationDataInputStep: (step: SplunkDataInputStep | QradarDataInputStep) => void; +}; + +interface UseSplunkMigrationSteps extends Omit { + dataInputStep: SplunkDataInputStep; +} + +interface UseQradarMigrationSteps extends Omit { + dataInputStep: QradarDataInputStep; +} + export const MIGRATIONSOURCE_OPTIONS: Array> = [ { value: MigrationSource.SPLUNK, @@ -35,24 +56,13 @@ export const MIGRATIONSOURCE_OPTIONS: Array void; - dataInputStep: SplunkDataInputStep; - migrationSource: MigrationSource; - migrationStats?: RuleMigrationStats; - onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; -}): SplunkMigrationSteps | null => { +}: UseSplunkMigrationSteps): SplunkMigrationSteps | null => { const [missingResourcesIndexed, setMissingResourcesIndexed] = useState< MissingResourcesIndexed | undefined >(); @@ -72,21 +82,21 @@ export const useSplunkMigrationSteps = ({ ); setMissingResourcesIndexed(newMissingResourcesIndexed); if (newMissingResourcesIndexed.macros.length) { - setDataInputStep(SplunkDataInputStep.Macros); + setMigrationDataInputStep(SplunkDataInputStep.Macros); return; } if (newMissingResourcesIndexed.lookups.length) { - setDataInputStep(SplunkDataInputStep.Lookups); + setMigrationDataInputStep(SplunkDataInputStep.Lookups); return; } - setDataInputStep(SplunkDataInputStep.End); + setMigrationDataInputStep(SplunkDataInputStep.End); }, - [setDataInputStep] + [setMigrationDataInputStep] ); const onAllLookupsCreated = useCallback(() => { - setDataInputStep(SplunkDataInputStep.End); - }, [setDataInputStep]); + setMigrationDataInputStep(SplunkDataInputStep.End); + }, [setMigrationDataInputStep]); const SPLUNK_MIGRATION_STEPS: SplunkMigrationSteps = useMemo( () => @@ -123,12 +133,7 @@ export const useQradarMigrationSteps = ({ dataInputStep, migrationSource, migrationStats, -}: { - onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; - dataInputStep: QradarDataInputStep; - migrationSource: MigrationSource; - migrationStats?: RuleMigrationStats; -}): QradarMigrationSteps | null => { +}: UseQradarMigrationSteps): QradarMigrationSteps | null => { const QRADAR_MIGRATION_STEPS: QradarMigrationSteps = useMemo( () => STEP_COMPONENTS[MigrationSource.QRADAR].map(({ id, Component }) => ({ @@ -147,34 +152,17 @@ export const useQradarMigrationSteps = ({ return migrationSource === MigrationSource.QRADAR ? QRADAR_MIGRATION_STEPS : null; }; -interface UseMigrationStepsParams { - onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; - dataInputStep: DataInputStep; - migrationSource: MigrationSource; - migrationStats?: RuleMigrationStats; - setMigrationDataInputStep: (step: SplunkDataInputStep | QradarDataInputStep) => void; -} - -export const useMigrationSteps = ({ - onMigrationCreated, - dataInputStep, - migrationSource, - migrationStats, - setMigrationDataInputStep: setDataInputStep, -}: UseMigrationStepsParams): SplunkMigrationSteps | QradarMigrationSteps | null => { +export const useMigrationSteps = ( + props: UseMigrationStepsProps +): SplunkMigrationSteps | QradarMigrationSteps | null => { const splunkMigrationSteps: SplunkMigrationSteps | null = useSplunkMigrationSteps({ - setDataInputStep, - dataInputStep: dataInputStep[MigrationSource.SPLUNK], - migrationSource, - migrationStats, - onMigrationCreated, + ...props, + dataInputStep: props.dataInputStep[MigrationSource.SPLUNK], }); const qradarMigrationSteps: QradarMigrationSteps | null = useQradarMigrationSteps({ - dataInputStep: dataInputStep[MigrationSource.QRADAR], - migrationSource, - migrationStats, - onMigrationCreated, + ...props, + dataInputStep: props.dataInputStep[MigrationSource.QRADAR], }); return splunkMigrationSteps ?? qradarMigrationSteps; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts index 150ceaadc9eb7..ec9c5fa9f45ae 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/types.ts @@ -5,10 +5,17 @@ * 2.0. */ -import type { DataInputStepId } from '../../../rules/components/data_input_flyout/steps/constants'; +import type { + DataInputStepId, + QradarDataInputStep, + SplunkDataInputStep, +} from '../../../rules/components/data_input_flyout/steps/constants'; import type { LookupsDataInput } from '../../../rules/components/data_input_flyout/steps/lookups/lookups_data_input'; import type { MacrosDataInput } from '../../../rules/components/data_input_flyout/steps/macros/macros_data_input'; import type { RulesDataInput } from '../../../rules/components/data_input_flyout/steps/rules/rules_data_input'; +import type { OnMissingResourcesFetched } from '../../../rules/components/data_input_flyout/types'; +import type { RuleMigrationStats } from '../../../rules/types'; +import type { MigrationSource } from '../../types'; interface RulesStep { id: T; @@ -40,3 +47,11 @@ export type SplunkStep = RulesStep | MacrosStep | LookupsStep; export type SplunkMigrationSteps = SplunkStep[]; export type QradarMigrationSteps = Array>; + +export interface RulesDataInputSubStepsProps { + dataInputStep: SplunkDataInputStep | QradarDataInputStep; + migrationSource: MigrationSource; + migrationStats?: RuleMigrationStats; + onMigrationCreated: (createdMigrationStats: RuleMigrationStats) => void; + onMissingResourcesFetched?: OnMissingResourcesFetched; +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx index 4e026a5ec8074..7d959436933c3 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/rules_data_input.tsx @@ -17,16 +17,10 @@ import { QradarDataInputStep, SplunkDataInputStep } from '../constants'; import { useCopyExportQueryStep } from './sub_steps/copy_export_query'; import { useRulesFileUploadStep } from './sub_steps/rules_file_upload'; import { useCheckResourcesStep } from './sub_steps/check_resources'; -import type { RuleMigrationStats } from '../../../../types'; import { MigrationSource } from '../../../../../common/types'; +import type { RulesDataInputSubStepsProps } from '../../../../../common/components/migration_source_step/types'; -interface RulesDataInputSubStepsProps { - migrationStats?: RuleMigrationStats; - onMigrationCreated: OnMigrationCreated; - onMissingResourcesFetched?: OnMissingResourcesFetched; - migrationSource: MigrationSource; -} -interface RulesDataInputProps extends RulesDataInputSubStepsProps { +interface RulesDataInputProps extends Omit { dataInputStep: SplunkDataInputStep | QradarDataInputStep; } export const RulesDataInput = React.memo( @@ -78,6 +72,7 @@ export const RulesDataInput = React.memo( {dataInputStatus === 'current' && ( Date: Wed, 3 Dec 2025 23:46:58 +0000 Subject: [PATCH 6/7] unit tests --- .../migration_source_dropdown.test.tsx | 55 +++++++++++++++++ .../migration_source_dropdown.tsx | 30 ++------- .../components/migration_source_step/types.ts | 6 +- .../use_migration_source_options.test.tsx | 35 +++++++++++ .../use_migration_source_options.tsx | 36 +++++++++++ ...ndex.tsx => use_migration_source_step.tsx} | 15 ++++- .../use_migration_steps.test.tsx | 61 +++++++++++++++++++ .../use_migration_steps.tsx} | 52 ++++++---------- .../data_input_flyout/data_input_flyout.tsx | 10 +-- 9 files changed, 233 insertions(+), 67 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.test.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.tsx rename x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/{index.tsx => use_migration_source_step.tsx} (57%) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.test.tsx rename x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/{migration_source_step/migration_source_options.tsx => migration_steps/use_migration_steps.tsx} (83%) diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.test.tsx new file mode 100644 index 0000000000000..712138ad9c672 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.test.tsx @@ -0,0 +1,55 @@ +/* + * 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 { render, screen } from '@testing-library/react'; +import { MigrationSource } from '../../types'; +import { MigrationSourceDropdown } from './migration_source_dropdown'; +import * as i18n from './translations'; + +describe('MigrationSourceDropdown', () => { + const mockSetMigrationSource = jest.fn(); + + const defaultProps = { + migrationSource: MigrationSource.SPLUNK, + setMigrationSource: mockSetMigrationSource, + disabled: false, + migrationSourceOptions: [ + { + value: MigrationSource.SPLUNK, + inputDisplay: {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_SPLUNK}, + }, + { + value: MigrationSource.QRADAR, + inputDisplay: {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_QRADAR}, + }, + ], + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with initial value', () => { + render(); + + const select = screen.getByTestId('migrationSourceDropdown'); + expect(select.textContent).toContain(i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_SPLUNK); + }); + + it('disables the dropdown when disabled equals true', () => { + render(); + + const select = screen.getByTestId('migrationSourceDropdown'); + expect(select).toBeDisabled(); + }); + + it('shows helper text when disabled', () => { + render(); + + expect(screen.getByText(i18n.MIGRATION_SOURCE_DROPDOWN_HELPER_TEXT)).toBeInTheDocument(); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx index c06b94364ee2a..7d72d1ea6f5c2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_dropdown.tsx @@ -8,34 +8,18 @@ import React, { useCallback, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiForm, EuiFormRow, EuiSuperSelect } from '@elastic/eui'; import * as i18n from './translations'; import type { MigrationSource } from '../../types'; -import { MIGRATIONSOURCE_OPTIONS } from './migration_source_options'; - -export interface MigrationSourceDropdownProps { - migrationSource: MigrationSource; - setMigrationSource: (migrationSource: MigrationSource) => void; - disabled: boolean; -} +import type { MigrationSourceDropdownProps } from './use_migration_source_step'; export const MigrationSourceDropdown = React.memo( - ({ migrationSource, setMigrationSource, disabled }) => { + ({ migrationSource, setMigrationSource, disabled, migrationSourceOptions }) => { const [value, setValue] = useState(migrationSource); - const [isTouched, setIsTouched] = useState(false); - - const checkAndSetMigrationSource = useCallback( - (selected: MigrationSource) => { - if (selected.length > 0) { - setMigrationSource(selected); - } - }, - [setMigrationSource] - ); const handleMigrationSourceChange = useCallback( (selected: MigrationSource) => { setValue(selected); - checkAndSetMigrationSource(selected); + setMigrationSource(selected); }, - [checkAndSetMigrationSource] + [setMigrationSource] ); const onBlur = useCallback(() => { @@ -46,16 +30,12 @@ export const MigrationSourceDropdown = React.memo( { - setIsTouched(true); - }} label={i18n.MIGRATION_SOURCE_DROPDOWN_TITLE} fullWidth helpText={disabled ? i18n.MIGRATION_SOURCE_DROPDOWN_HELPER_TEXT : undefined} > { id: T; Component: typeof RulesDataInput; - extraProps?: React.ComponentProps; + props?: React.ComponentProps; } interface MacrosStep { id: DataInputStepId.SplunkMacros; Component: typeof MacrosDataInput; - extraProps?: React.ComponentProps; + props?: React.ComponentProps; } interface LookupsStep { id: DataInputStepId.SplunkLookups; Component: typeof LookupsDataInput; - extraProps?: React.ComponentProps; + props?: React.ComponentProps; } export type Step = diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.test.tsx new file mode 100644 index 0000000000000..5676cc77a1bd3 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.test.tsx @@ -0,0 +1,35 @@ +/* + * 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 { renderHook } from '@testing-library/react'; +import { useMigrationSourceOptions } from './use_migration_source_options'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; +import { MigrationSource } from '../../types'; + +jest.mock('../../../../common/hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: jest.fn(), +})); + +describe('useMigrationSourceOptions', () => { + it('returns only Splunk option when QRadar feature is disabled', () => { + const { result } = renderHook(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(false); + return useMigrationSourceOptions(); + }); + expect(result.current.length).toEqual(1); + expect(result.current[0].value).toEqual(MigrationSource.SPLUNK); + }); + it('returns Splunk and QRadar options when QRadar feature is enabled', () => { + const { result } = renderHook(() => { + (useIsExperimentalFeatureEnabled as jest.Mock).mockReturnValue(true); + return useMigrationSourceOptions(); + }); + expect(result.current.length).toEqual(2); + expect(result.current[0].value).toEqual(MigrationSource.SPLUNK); + expect(result.current[1].value).toEqual(MigrationSource.QRADAR); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.tsx new file mode 100644 index 0000000000000..b4f79e0ee68bc --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_options.tsx @@ -0,0 +1,36 @@ +/* + * 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 type { EuiSuperSelectOption } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; +import { MigrationSource } from '../../types'; +import * as i18n from './translations'; +import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features'; + +export const useMigrationSourceOptions = () => { + const isQradarEnabled = useIsExperimentalFeatureEnabled('qradarRulesMigration'); + + const options: Array> = [ + { + value: MigrationSource.SPLUNK, + inputDisplay: {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_SPLUNK}, + }, + ]; + + if (isQradarEnabled) { + options.push({ + value: MigrationSource.QRADAR, + inputDisplay: ( + + {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_QRADAR} + + + ), + }); + } + return options; +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_step.tsx similarity index 57% rename from x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx rename to x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_step.tsx index 70a0d12eee2e4..311d60979eceb 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/use_migration_source_step.tsx @@ -6,15 +6,28 @@ */ import { useState } from 'react'; +import type { EuiSuperSelectOption } from '@elastic/eui'; import type { MigrationSource } from '../../types'; +import { useMigrationSourceOptions } from './use_migration_source_options'; + +export interface MigrationSourceDropdownProps { + migrationSource: MigrationSource; + setMigrationSource: (migrationSource: MigrationSource) => void; + disabled: boolean; + migrationSourceOptions: Array>; +} export const useMigrationSourceStep = (initialMigrationSource: MigrationSource) => { + const migrationSourceOptions = useMigrationSourceOptions(); const [migrationSource, setMigrationSource] = useState(initialMigrationSource); - const [migrationSourceDisabled, setMigrationSourceDisabled] = useState(false); + const [migrationSourceDisabled, setMigrationSourceDisabled] = useState( + migrationSourceOptions.length <= 1 + ); return { migrationSource, setMigrationSource, migrationSourceDisabled, setMigrationSourceDisabled, + migrationSourceOptions, }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.test.tsx new file mode 100644 index 0000000000000..3760d13154fe2 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.test.tsx @@ -0,0 +1,61 @@ +/* + * 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 { + DataInputStepId, + QradarDataInputStep, + SplunkDataInputStep, +} from '../../../rules/components/data_input_flyout/steps/constants'; +import { MigrationSource } from '../../types'; +import type { UseMigrationStepsProps } from './use_migration_steps'; +import { useMigrationSteps } from './use_migration_steps'; +import { renderHook } from '@testing-library/react'; +import { SiemMigrationTaskStatus } from '../../../../../common/siem_migrations/constants'; + +describe('useMigrationSteps', () => { + const props: UseMigrationStepsProps = { + dataInputStep: { + [MigrationSource.SPLUNK]: SplunkDataInputStep.Rules, + [MigrationSource.QRADAR]: QradarDataInputStep.Rules, + }, + migrationSource: MigrationSource.SPLUNK, + setMigrationDataInputStep: jest.fn(), + migrationStats: { + id: 'test-id', + name: 'test-name', + status: SiemMigrationTaskStatus.FINISHED, + items: { + total: 1, + pending: 0, + failed: 0, + completed: 1, + processing: 0, + }, + created_at: '2024-01-01T00:00:00Z', + last_updated_at: '2024-01-01T00:00:00Z', + }, + onMigrationCreated: jest.fn(), + onMissingResourcesFetched: jest.fn(), + }; + + it('should return Splunk migration steps when migration source is Splunk', () => { + const { result } = renderHook(() => + useMigrationSteps({ ...props, migrationSource: MigrationSource.SPLUNK }) + ); + expect(result.current?.length).toEqual(3); + expect(result.current?.[0].id).toBe(DataInputStepId.SplunkRules); + expect(result.current?.[1].id).toBe(DataInputStepId.SplunkMacros); + expect(result.current?.[2].id).toBe(DataInputStepId.SplunkLookups); + }); + it('should return Qradar migration steps when migration source is Qradar', () => { + const { result } = renderHook(() => + useMigrationSteps({ ...props, migrationSource: MigrationSource.QRADAR }) + ); + expect(result.current?.length).toEqual(1); + expect(result.current?.[0].id).toBe(DataInputStepId.QradarRules); + }); +}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.tsx similarity index 83% rename from x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx rename to x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.tsx index 9dc4cb76c26b9..caf707643172f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_source_step/migration_source_options.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/use_migration_steps.tsx @@ -4,30 +4,25 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; -import type { EuiSuperSelectOption } from '@elastic/eui'; -import { EuiIcon } from '@elastic/eui'; -import { MigrationSource } from '../../types'; -import * as i18n from './translations'; +import { useCallback, useMemo, useState } from 'react'; + +import type { SiemMigrationResourceBase } from '../../../../../common/siem_migrations/model/common.gen'; + import type { DataInputStep, QradarDataInputStep, } from '../../../rules/components/data_input_flyout/steps/constants'; import { SplunkDataInputStep } from '../../../rules/components/data_input_flyout/steps/constants'; -import type { SiemMigrationResourceBase } from '../../../../../common/siem_migrations/model/common.gen'; + +import { STEP_COMPONENTS } from './configs'; import type { QradarMigrationSteps, RulesDataInputSubStepsProps, SplunkMigrationSteps, -} from './types'; -import { STEP_COMPONENTS } from '../migration_steps/configs'; - -interface MissingResourcesIndexed { - macros: string[]; - lookups: string[]; -} +} from '../migration_source_step/types'; +import { MigrationSource } from '../../types'; -type UseMigrationStepsProps = Omit & { +export type UseMigrationStepsProps = Omit & { dataInputStep: DataInputStep; setMigrationDataInputStep: (step: SplunkDataInputStep | QradarDataInputStep) => void; }; @@ -40,23 +35,12 @@ interface UseQradarMigrationSteps extends Omit> = [ - { - value: MigrationSource.SPLUNK, - inputDisplay: {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_SPLUNK}, - }, - { - value: MigrationSource.QRADAR, - inputDisplay: ( - - {i18n.MIGRATION_SOURCE_DROPDOWN_OPTION_QRADAR} - - - ), - }, -]; - -export const useSplunkMigrationSteps = ({ +interface MissingResourcesIndexed { + macros: string[]; + lookups: string[]; +} + +const useSplunkMigrationSteps = ({ setMigrationDataInputStep, dataInputStep, migrationSource, @@ -103,7 +87,7 @@ export const useSplunkMigrationSteps = ({ STEP_COMPONENTS[MigrationSource.SPLUNK].map(({ id, Component }) => ({ id, Component, - extraProps: { + props: { dataInputStep, migrationSource, migrationStats, @@ -128,7 +112,7 @@ export const useSplunkMigrationSteps = ({ return migrationSource === MigrationSource.SPLUNK ? SPLUNK_MIGRATION_STEPS : null; }; -export const useQradarMigrationSteps = ({ +const useQradarMigrationSteps = ({ onMigrationCreated, dataInputStep, migrationSource, @@ -139,7 +123,7 @@ export const useQradarMigrationSteps = ({ STEP_COMPONENTS[MigrationSource.QRADAR].map(({ id, Component }) => ({ id, Component, - extraProps: { + props: { dataInputStep, migrationSource, migrationStats, diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx index 07e1874033bbd..749ec43276183 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/data_input_flyout.tsx @@ -28,12 +28,12 @@ import { QradarDataInputStep, SplunkDataInputStep } from './steps/constants'; import { useStartRulesMigrationModal } from '../../hooks/use_start_rules_migration_modal'; import type { RuleMigrationSettings, RuleMigrationStats } from '../../types'; import { useStartMigration } from '../../logic/use_start_migration'; -import { useMigrationSourceStep } from '../../../common/components/migration_source_step'; +import { useMigrationSourceStep } from '../../../common/components/migration_source_step/use_migration_source_step'; import { MigrationSourceDropdown } from '../../../common/components/migration_source_step/migration_source_dropdown'; import { CenteredLoadingSpinner } from '../../../../common/components/centered_loading_spinner'; -import { useMigrationSteps } from '../../../common/components/migration_source_step/migration_source_options'; import type { Step } from '../../../common/components/migration_source_step/types'; import { MigrationSource } from '../../../common/types'; +import { useMigrationSteps } from '../../../common/components/migration_steps/use_migration_steps'; export interface MigrationDataInputFlyoutProps { onClose: () => void; @@ -42,9 +42,9 @@ export interface MigrationDataInputFlyoutProps { } function StepRenderer({ step }: { step: Step }) { - const Component = step.Component as React.ComponentType; + const Component = step.Component as React.ComponentType; - return step.extraProps ? : ; + return step.props ? : ; } const RULES_MIGRATION_DATA_INPUT_FLYOUT_TITLE = 'rulesMigrationDataInputFlyoutTitle'; @@ -64,6 +64,7 @@ export const MigrationDataInputFlyout = React.memo( @@ -153,6 +154,7 @@ export const MigrationDataInputFlyout = React.memo <> From ce06e6cbaf3e4e5a418a0786ef7d5727b928a7e1 Mon Sep 17 00:00:00 2001 From: Angela Chuang Date: Thu, 4 Dec 2025 00:50:41 +0000 Subject: [PATCH 7/7] lint --- .../private/translations/translations/de-DE.json | 1 - .../private/translations/translations/fr-FR.json | 1 - .../private/translations/translations/ja-JP.json | 1 - .../private/translations/translations/zh-CN.json | 1 - .../sub_steps/copy_export_query/index.test.tsx | 8 ++++++++ .../sub_steps/rules_file_upload/index.test.tsx | 4 ++++ .../service/hooks/use_create_migration.test.ts | 13 +++++++++++-- 7 files changed, 23 insertions(+), 6 deletions(-) diff --git a/x-pack/platform/plugins/private/translations/translations/de-DE.json b/x-pack/platform/plugins/private/translations/translations/de-DE.json index 26fe986605709..ffb90edda02a3 100644 --- a/x-pack/platform/plugins/private/translations/translations/de-DE.json +++ b/x-pack/platform/plugins/private/translations/translations/de-DE.json @@ -38566,7 +38566,6 @@ "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description": "Loggen Sie sich in Ihr Splunk-Administratorkonto ein, gehen Sie zur {section} App und führen Sie die folgende Abfrage aus. Exportieren Sie Ihre Ergebnisse als {format}.", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description.section": "Search und Berichterstattung", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.title": "Splunk-Regeln exportieren", - "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.prompt": "Wählen Sie die exportierte JSON-Datei aus oder ziehen Sie sie per Drag-and-Drop.", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.title": "Exportierte Regeln aktualisieren", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.title": "Regeln hochladen", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.title": "Splunk SIEM-Regeln hochladen", diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index d86df06fed5ea..5e050aa70ea9f 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -38822,7 +38822,6 @@ "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description": "Connectez-vous à votre compte administrateur Splunk, rendez-vous dans l'application {section} et exécutez la recherche suivante. Exportez vos résultats au format {format}.", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description.section": "Recherche et Rapports", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.title": "Exporter les règles Splunk", - "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.prompt": "Sélectionnez ou faites un cliquer-glisser sur le fichier JSON exporté", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.title": "Mettre à jour les règles exportées", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.title": "Charger les règles", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.title": "Charger les règles SIEM Splunk", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index 044c97ba7631c..13e9aa73ef627 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -38864,7 +38864,6 @@ "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description": "Splunk管理者アカウントにログインし、{section}アプリを開き、次のクエリを実行します。結果を{format}でエクスポートします。", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description.section": "検索とレポート", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.title": "Splunkをエクスポート", - "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.prompt": "エクスポートされたJSONファイルを選択するか、ドラッグしてドロップします", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.title": "エクスポートしたルールを更新", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.title": "ルールのアップロード", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.title": "Splunk SIEMルールをアップロード", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 4376245c5e257..96042d3926191 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -38850,7 +38850,6 @@ "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description": "登录到您的 Splunk 管理员帐户,前往 {section} 应用,然后运行以下查询。以 {format} 格式导出结果。", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description.section": "搜索和报告", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.title": "导出 Splunk 规则", - "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.prompt": "选择或拖放导出的 JSON 文件", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.title": "更新导出的规则", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.title": "上传规则", "xpack.securitySolution.siemMigrations.rules.dataInputFlyout.title": "上传 Splunk SIEM 规则", diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.test.tsx index 79776dba609ac..09147a44f18a8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/copy_export_query/index.test.tsx @@ -9,6 +9,7 @@ import { renderHook } from '@testing-library/react'; import { useCopyExportQueryStep } from '.'; import type { CopyExportQueryStepProps } from '.'; import { TestProviders } from '../../../../../../../../common/mock'; +import { MigrationSource } from '../../../../../../../common/types'; const renderCopyExportQueryStep = (props: CopyExportQueryStepProps) => { const { result } = renderHook(() => useCopyExportQueryStep(props), { @@ -20,6 +21,7 @@ const renderCopyExportQueryStep = (props: CopyExportQueryStepProps) => { describe('useCopyExportQueryStep', () => { it('returns step props with "incomplete" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'incomplete', onCopied: jest.fn(), }); @@ -32,6 +34,7 @@ describe('useCopyExportQueryStep', () => { it('returns step props with "complete" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'complete', onCopied: jest.fn(), }); @@ -44,6 +47,7 @@ describe('useCopyExportQueryStep', () => { it('returns step props with "disabled" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'disabled', onCopied: jest.fn(), }); @@ -56,6 +60,7 @@ describe('useCopyExportQueryStep', () => { it('returns step props with "loading" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'loading', onCopied: jest.fn(), }); @@ -68,6 +73,7 @@ describe('useCopyExportQueryStep', () => { it('returns step props with "warning" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'warning', onCopied: jest.fn(), }); @@ -80,6 +86,7 @@ describe('useCopyExportQueryStep', () => { it('returns step props with "danger" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'danger', onCopied: jest.fn(), }); @@ -92,6 +99,7 @@ describe('useCopyExportQueryStep', () => { it('returns step props with "current" status', () => { const result = renderCopyExportQueryStep({ + migrationSource: MigrationSource.SPLUNK, status: 'current', onCopied: jest.fn(), }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.test.tsx index 97361038d45f3..030ee563d7693 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/rules/sub_steps/rules_file_upload/index.test.tsx @@ -9,6 +9,7 @@ import { renderHook } from '@testing-library/react'; import { useRulesFileUploadStep } from '.'; import { TestProviders } from '../../../../../../../../common/mock'; import { useCreateMigration } from '../../../../../../service/hooks/use_create_migration'; +import { MigrationSource } from '../../../../../../../common/types'; jest.mock('../../../../../../service/hooks/use_create_migration', () => ({ useCreateMigration: jest.fn(), @@ -36,6 +37,7 @@ describe('useRulesFileUploadStep', () => { migrationName: 'test', onMigrationCreated: jest.fn(), onRulesFileChanged: jest.fn(), + migrationSource: MigrationSource.SPLUNK, }), { wrapper: TestProviders } ); @@ -62,6 +64,7 @@ describe('useRulesFileUploadStep', () => { migrationName: 'test', onMigrationCreated: jest.fn(), onRulesFileChanged: jest.fn(), + migrationSource: MigrationSource.SPLUNK, }), { wrapper: TestProviders } ); @@ -88,6 +91,7 @@ describe('useRulesFileUploadStep', () => { migrationName: 'test', onMigrationCreated: jest.fn(), onRulesFileChanged: jest.fn(), + migrationSource: MigrationSource.SPLUNK, }), { wrapper: TestProviders } ); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.test.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.test.ts index 921e86b5f0777..fa649633d6acd 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/service/hooks/use_create_migration.test.ts @@ -8,6 +8,7 @@ import { renderHook, act } from '@testing-library/react'; import { useCreateMigration } from './use_create_migration'; import { useKibana } from '../../../../common/lib/kibana/kibana_react'; +import { MigrationSource } from '../../../common/types'; jest.mock('../../../../common/lib/kibana/kibana_react', () => ({ useKibana: jest.fn(), @@ -51,7 +52,11 @@ describe('useCreateMigration', () => { const { result } = renderHook(() => useCreateMigration(onSuccess)); await act(async () => { - await result.current.createMigration('test-migration', []); + await result.current.createMigration({ + rules: [], + migrationName: 'test-migration', + migrationSource: MigrationSource.SPLUNK, + }); }); expect(createRuleMigration).toHaveBeenCalledWith([], 'test-migration'); @@ -68,7 +73,11 @@ describe('useCreateMigration', () => { const { result } = renderHook(() => useCreateMigration(onSuccess)); await act(async () => { - await result.current.createMigration('test-migration', []); + await result.current.createMigration({ + migrationName: 'test-migration', + rules: [], + migrationSource: MigrationSource.SPLUNK, + }); }); expect(addError).toHaveBeenCalledWith(error, {