diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.test.tsx index d19f28b441f3f..8940741c65887 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { LookupsFileUpload } from '.'; import { MigrationSource } from '../../../../types'; @@ -22,4 +22,22 @@ describe('LookupsFileUpload', () => { const { getByTestId } = render(); expect(getByTestId('uploadFileButton')).toBeInTheDocument(); }); + + it('does not render skip button when onSkip is not provided', () => { + const { queryByTestId } = render(); + expect(queryByTestId('lookupsUploadSkipButton')).not.toBeInTheDocument(); + }); + + it('renders skip button when onSkip is provided', () => { + const { getByTestId } = render(); + expect(getByTestId('lookupsUploadSkipButton')).toBeInTheDocument(); + expect(getByTestId('lookupsUploadSkipButton')).toHaveTextContent('Skip'); + }); + + it('calls onSkip when skip button is clicked', () => { + const onSkip = jest.fn(); + const { getByTestId } = render(); + fireEvent.click(getByTestId('lookupsUploadSkipButton')); + expect(onSkip).toHaveBeenCalledTimes(1); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.tsx index 35f0fda5ef036..80dca4bd93c72 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/common/components/migration_steps/lookups/lookups_file_upload/index.tsx @@ -6,7 +6,14 @@ */ import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { EuiFilePicker, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiFilePicker, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiText, +} from '@elastic/eui'; import type { EuiFilePickerClass, EuiFilePickerProps, @@ -23,6 +30,7 @@ export interface LookupsFileUploadProps { apiError?: string; isLoading?: boolean; migrationSource: MigrationSource; + onSkip?: () => void; } const CONFIGS: Record = { @@ -37,7 +45,7 @@ const CONFIGS: Record = { }; export const LookupsFileUpload = React.memo( - ({ createResources, apiError, isLoading, migrationSource }) => { + ({ createResources, apiError, isLoading, migrationSource, onSkip }) => { const [lookupResources, setLookupResources] = useState([]); const filePickerRef = useRef(null); @@ -170,7 +178,18 @@ export const LookupsFileUpload = React.memo( - + + {onSkip && ( + + + {i18n.SKIP_BUTTON} + + + )} ({ api: { getMissingResources: jest.fn(), }, + telemetry: { + reportSetupLookupNameCopied: jest.fn(), + }, }, }, notifications: { @@ -85,6 +90,23 @@ jest.mock('../../logic/use_start_migration', () => { }; }); +jest.mock('../../../../common/hooks/use_experimental_features', () => ({ + useIsExperimentalFeatureEnabled: jest.fn(() => true), +})); + +const mockUseMissingResources = jest.fn(); +jest.mock('../../../common/hooks/use_missing_resources', () => ({ + useMissingResources: (...args: unknown[]) => mockUseMissingResources(...args), +})); + +jest.mock('../../service/hooks/use_enhance_rules', () => ({ + useEnhanceRules: () => ({ enhanceRules: jest.fn(), isLoading: false, error: null }), +})); + +jest.mock('../../../../common/hooks/use_app_toasts', () => ({ + useAppToasts: () => ({ addError: jest.fn(), addSuccess: jest.fn() }), +})); + jest.mock('../../hooks/use_start_rules_migration_modal'); const useStartRulesMigrationModalMock = useStartRulesMigrationModal as jest.MockedFunction< typeof useStartRulesMigrationModal @@ -92,6 +114,12 @@ const useStartRulesMigrationModalMock = useStartRulesMigrationModal as jest.Mock describe('MigrationDataInputFlyout', () => { beforeEach(() => { + mockUseMissingResources.mockReturnValue({ + missingResourcesIndexed: undefined, + onMissingResourcesFetched: jest.fn(), + missingResourceCount: 0, + }); + useStartRulesMigrationModalMock.mockImplementation(({ onStartMigrationWithSettings }) => { return { modal: ( @@ -271,4 +299,68 @@ describe('MigrationDataInputFlyout', () => { skipPrebuiltRulesMatching: false, }); }); + + describe('QRadar skip reference set step', () => { + const MISSING_LOOKUPS = { macros: [], lookups: ['ref_set_1', 'ref_set_2'] }; + + beforeEach(() => { + const react = jest.requireActual('react'); + mockUseMissingResources.mockImplementation( + ({ + handleMissingResourcesIndexed, + migrationSource, + }: { + handleMissingResourcesIndexed?: HandleMissingResourcesIndexed; + migrationSource: MigrationSource; + }) => { + react.useEffect(() => { + handleMissingResourcesIndexed?.({ + migrationSource, + newMissingResourcesIndexed: MISSING_LOOKUPS, + }); + }, [handleMissingResourcesIndexed, migrationSource]); + + return { + missingResourcesIndexed: MISSING_LOOKUPS, + onMissingResourcesFetched: jest.fn(), + missingResourceCount: MISSING_LOOKUPS.lookups.length, + }; + } + ); + }); + + const qradarMigrationStats = getRuleMigrationStatsMock({ + id: 'qradar-migration-1', + status: SiemMigrationTaskStatus.READY, + vendor: MigrationSource.QRADAR, + }); + + it('advances from Reference Set step to Enhancements step when skip button is clicked', async () => { + const { getByTestId, queryByTestId } = render( + + + + ); + + await waitFor(() => { + expect(getByTestId('lookupsUploadSkipButton')).toBeInTheDocument(); + }); + + expect(getByTestId('referenceSetsUploadStepNumber')).toHaveTextContent('Current step is 2'); + expect(getByTestId('enhancementsStepNumber')).toHaveTextContent('3'); + expect(queryByTestId('enhancementTypeSelect')).not.toBeInTheDocument(); + + fireEvent.click(getByTestId('lookupsUploadSkipButton')); + + await waitFor(() => { + expect(getByTestId('enhancementsStepNumber')).toHaveTextContent('Current step is 3'); + }); + + expect(getByTestId('enhancementTypeSelect')).toBeInTheDocument(); + expect(getByTestId('enhancementFilePicker')).toBeInTheDocument(); + + expect(queryByTestId('lookupsUploadSkipButton')).not.toBeInTheDocument(); + expect(queryByTestId('referenceSetsUploadDescription')).not.toBeInTheDocument(); + }); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.test.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.test.tsx index 305d7011f9a62..7b21ffb409a23 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.test.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { ReferenceSetDataInput } from './reference_set_data_input'; import { getRuleMigrationStatsMock } from '../../../../__mocks__'; @@ -81,7 +81,7 @@ describe('ReferenceSetDataInput', () => { ); expect(getByTestId('referenceSetsUploadDescription')).toBeInTheDocument(); expect(getByTestId('referenceSetsUploadDescription')).toHaveTextContent( - `We've also found reference sets within your rules. To fully translate those rules containing these reference sets, follow the step-by-step guide to export and upload them all.` + `We've also found reference sets within your rules. To fully translate those rules containing these reference sets, upload them all` ); }); @@ -111,4 +111,33 @@ describe('ReferenceSetDataInput', () => { ); expect(queryByTestId('referenceSetsUploadDescription')).not.toBeInTheDocument(); }); + + it('renders skip button when step is current', () => { + const { getByTestId } = render( + + + + ); + expect(getByTestId('lookupsUploadSkipButton')).toBeInTheDocument(); + expect(getByTestId('lookupsUploadSkipButton')).toHaveTextContent('Skip'); + }); + + it('calls setDataInputStep with Enhancements when skip button is clicked', () => { + const { getByTestId } = render( + + + + ); + fireEvent.click(getByTestId('lookupsUploadSkipButton')); + expect(defaultProps.setDataInputStep).toHaveBeenCalledWith(QradarDataInputStep.Enhancements); + }); + + it('does not render skip button when step is not current', () => { + const { queryByTestId } = render( + + + + ); + expect(queryByTestId('lookupsUploadSkipButton')).not.toBeInTheDocument(); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.tsx index fabdbccbade77..baf33d7bdec25 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/reference_set_data_input.tsx @@ -23,7 +23,7 @@ import { SubSteps } from '../../../../../common/components'; import { getEuiStepStatus } from '../../../../../common/utils/get_eui_step_status'; import { useKibana } from '../../../../../../common/lib/kibana/kibana_react'; import type { RuleMigrationTaskStats } from '../../../../../../../common/siem_migrations/model/rule_migration.gen'; -import { QradarDataInputStep, type OnResourcesCreated } from '../../types'; +import { QradarDataInputStep } from '../../types'; import * as i18n from './translations'; import { useMissingReferenceSetsListStep } from './sub_steps/missing_reference_set_list'; import { useReferencesFileUploadStep } from './sub_steps/reference_sets_file_upload'; @@ -32,7 +32,7 @@ import { type MigrationStepProps } from '../../../../../common/types'; interface ReferenceSetDataInputSubStepsProps { migrationStats: RuleMigrationTaskStats; missingReferenceSet: string[]; - onAllReferenceSetCreated: OnResourcesCreated; + onComplete: () => void; } export const ReferenceSetDataInput = React.memo( @@ -41,15 +41,16 @@ export const ReferenceSetDataInput = React.memo( () => missingResourcesIndexed?.lookups, [missingResourcesIndexed] ); - const onAllReferenceSetCreated = useCallback(() => { - setDataInputStep(QradarDataInputStep.Enhancements); - }, [setDataInputStep]); const dataInputStatus = useMemo( () => getEuiStepStatus(QradarDataInputStep.ReferenceSet, dataInputStep), [dataInputStep] ); + const onComplete = useCallback(() => { + setDataInputStep(QradarDataInputStep.Enhancements); + }, [setDataInputStep]); + return ( @@ -81,7 +82,7 @@ export const ReferenceSetDataInput = React.memo( @@ -96,7 +97,7 @@ ReferenceSetDataInput.displayName = 'ReferenceSetDataInput'; const END = 10 as const; type SubStep = 1 | 2 | typeof END; export const ReferenceSetDataInputSubSteps = React.memo( - ({ migrationStats, missingReferenceSet, onAllReferenceSetCreated }) => { + ({ migrationStats, missingReferenceSet, onComplete }) => { const { telemetry } = useKibana().services.siemMigrations.rules; const [subStep, setSubStep] = useState(1); const [uploadedLookups, setUploadedLookups] = useState({}); @@ -111,9 +112,9 @@ export const ReferenceSetDataInputSubSteps = React.memo { if (missingReferenceSet.every((referenceSet) => uploadedLookups[referenceSet] != null)) { setSubStep(END); - onAllReferenceSetCreated(); + onComplete(); } - }, [uploadedLookups, missingReferenceSet, onAllReferenceSetCreated]); + }, [uploadedLookups, missingReferenceSet, onComplete]); // Copy query step const onCopied = useCallback(() => { @@ -139,6 +140,7 @@ export const ReferenceSetDataInputSubSteps = React.memo(() => [copyStep, uploadStep], [copyStep, uploadStep]); diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/sub_steps/reference_sets_file_upload/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/sub_steps/reference_sets_file_upload/index.tsx index be7e6c5976e1d..eb903f66522e8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/sub_steps/reference_sets_file_upload/index.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/sub_steps/reference_sets_file_upload/index.tsx @@ -20,11 +20,13 @@ export interface RulesFileUploadStepProps { migrationStats: RuleMigrationTaskStats; missingLookups: string[]; addUploadedLookups: AddUploadedLookups; + onSkip?: () => void; } export const useReferencesFileUploadStep = ({ status, migrationStats, addUploadedLookups, + onSkip, }: RulesFileUploadStepProps): EuiStepProps => { const { upsertResources, isLoading, error } = useUpsertResources(addUploadedLookups); @@ -61,6 +63,7 @@ export const useReferencesFileUploadStep = ({ isLoading={isLoading} apiError={error?.message} migrationSource={MigrationSource.QRADAR} + onSkip={onSkip} /> ), }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/translations.ts index 6312089ea50c3..8b9e627b57b11 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/siem_migrations/rules/components/data_input_flyout/steps/reference_set/translations.ts @@ -14,6 +14,6 @@ export const REFERENCE_SET_DATA_INPUT_TITLE = i18n.translate( export const REFERENCE_SET_DATA_INPUT_DESCRIPTION = i18n.translate( 'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.referenceSet.description', { - defaultMessage: `We've also found reference sets within your rules. To fully translate those rules containing these reference sets, follow the step-by-step guide to export and upload them all.`, + defaultMessage: `We've also found reference sets within your rules. To fully translate those rules containing these reference sets, upload them all.`, } );