Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -22,4 +22,22 @@ describe('LookupsFileUpload', () => {
const { getByTestId } = render(<LookupsFileUpload {...props} />);
expect(getByTestId('uploadFileButton')).toBeInTheDocument();
});

it('does not render skip button when onSkip is not provided', () => {
const { queryByTestId } = render(<LookupsFileUpload {...props} />);
expect(queryByTestId('lookupsUploadSkipButton')).not.toBeInTheDocument();
});

it('renders skip button when onSkip is provided', () => {
const { getByTestId } = render(<LookupsFileUpload {...props} onSkip={jest.fn()} />);
expect(getByTestId('lookupsUploadSkipButton')).toBeInTheDocument();
expect(getByTestId('lookupsUploadSkipButton')).toHaveTextContent('Skip');
});

it('calls onSkip when skip button is clicked', () => {
const onSkip = jest.fn();
const { getByTestId } = render(<LookupsFileUpload {...props} onSkip={onSkip} />);
fireEvent.click(getByTestId('lookupsUploadSkipButton'));
expect(onSkip).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -23,6 +30,7 @@ export interface LookupsFileUploadProps {
apiError?: string;
isLoading?: boolean;
migrationSource: MigrationSource;
onSkip?: () => void;
}

const CONFIGS: Record<MigrationSource, { prompt: string; label: string }> = {
Expand All @@ -37,7 +45,7 @@ const CONFIGS: Record<MigrationSource, { prompt: string; label: string }> = {
};

export const LookupsFileUpload = React.memo<LookupsFileUploadProps>(
({ createResources, apiError, isLoading, migrationSource }) => {
({ createResources, apiError, isLoading, migrationSource, onSkip }) => {
const [lookupResources, setLookupResources] = useState<SiemMigrationResourceData[]>([]);
const filePickerRef = useRef<EuiFilePickerClass>(null);

Expand Down Expand Up @@ -170,7 +178,18 @@ export const LookupsFileUpload = React.memo<LookupsFileUploadProps>(
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFlexGroup justifyContent="flexEnd" gutterSize="none">
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
{onSkip && (
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={onSkip}
aria-label={i18n.SKIP_BUTTON_ARIA_LABEL}
data-test-subj="lookupsUploadSkipButton"
>
{i18n.SKIP_BUTTON}
</EuiButtonEmpty>
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
<UploadFileButton
onClick={createLookups}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,13 @@ export const REFERENCE_SETS_DATA_INPUT_FILE_UPLOAD_LABEL = i18n.translate(
'xpack.securitySolution.siemMigrations.common.dataInputFlyout.lookups.referenceSetsFileUpload.label',
{ defaultMessage: 'Upload reference set files' }
);

export const SKIP_BUTTON = i18n.translate(
'xpack.securitySolution.siemMigrations.common.dataInputFlyout.lookups.skipButton',
{ defaultMessage: 'Skip' }
);

export const SKIP_BUTTON_ARIA_LABEL = i18n.translate(
'xpack.securitySolution.siemMigrations.common.dataInputFlyout.lookups.skipButtonAriaLabel',
{ defaultMessage: 'Skip this step and continue without uploading all the items' }
);
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const REFERENCE_SETS_QRADAR_APP = i18n.translate(
'xpack.securitySolution.siemMigrations.common.dataInputFlyout.lookups.missingReferenceSetsList.qradarAppSection',
{
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.",
'Below are the reference set found in your rules. Export them from QRadar and upload here.',
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { TestProviders } from '../../../../common/mock/test_providers';
import { SiemMigrationTaskStatus } from '../../../../../common/siem_migrations/constants';
import { getRuleMigrationStatsMock } from '../../__mocks__/migration_rule_stats';
import { useStartRulesMigrationModal } from '../../hooks/use_start_rules_migration_modal';
import { MigrationSource } from '../../../common/types';
import type { HandleMissingResourcesIndexed } from '../../../common/types';

const mockOnClose = jest.fn();
const mockStartMigration = jest.fn();
Expand All @@ -35,6 +37,9 @@ jest.mock('../../../../common/lib/kibana/kibana_react', () => ({
api: {
getMissingResources: jest.fn(),
},
telemetry: {
reportSetupLookupNameCopied: jest.fn(),
},
},
},
notifications: {
Expand Down Expand Up @@ -85,13 +90,36 @@ 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
>;

describe('MigrationDataInputFlyout', () => {
beforeEach(() => {
mockUseMissingResources.mockReturnValue({
missingResourcesIndexed: undefined,
onMissingResourcesFetched: jest.fn(),
missingResourceCount: 0,
});

useStartRulesMigrationModalMock.mockImplementation(({ onStartMigrationWithSettings }) => {
return {
modal: (
Expand Down Expand Up @@ -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(
<TestProviders>
<MigrationDataInputFlyout onClose={jest.fn()} migrationStats={qradarMigrationStats} />
</TestProviders>
);

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();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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__';
Expand Down Expand Up @@ -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`
);
});

Expand Down Expand Up @@ -111,4 +111,33 @@ describe('ReferenceSetDataInput', () => {
);
expect(queryByTestId('referenceSetsUploadDescription')).not.toBeInTheDocument();
});

it('renders skip button when step is current', () => {
const { getByTestId } = render(
<TestProviders>
<ReferenceSetDataInput {...defaultProps} />
</TestProviders>
);
expect(getByTestId('lookupsUploadSkipButton')).toBeInTheDocument();
expect(getByTestId('lookupsUploadSkipButton')).toHaveTextContent('Skip');
});

it('calls setDataInputStep with Enhancements when skip button is clicked', () => {
const { getByTestId } = render(
<TestProviders>
<ReferenceSetDataInput {...defaultProps} />
</TestProviders>
);
fireEvent.click(getByTestId('lookupsUploadSkipButton'));
expect(defaultProps.setDataInputStep).toHaveBeenCalledWith(QradarDataInputStep.Enhancements);
});

it('does not render skip button when step is not current', () => {
const { queryByTestId } = render(
<TestProviders>
<ReferenceSetDataInput {...defaultProps} dataInputStep={SplunkDataInputStep.Upload} />
</TestProviders>
);
expect(queryByTestId('lookupsUploadSkipButton')).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<MigrationStepProps>(
Expand All @@ -41,15 +41,16 @@ export const ReferenceSetDataInput = React.memo<MigrationStepProps>(
() => 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 (
<EuiPanel hasShadow={false} hasBorder>
<EuiFlexGroup direction="column">
Expand Down Expand Up @@ -81,7 +82,7 @@ export const ReferenceSetDataInput = React.memo<MigrationStepProps>(
<ReferenceSetDataInputSubSteps
migrationStats={migrationStats}
missingReferenceSet={missingReferenceSet}
onAllReferenceSetCreated={onAllReferenceSetCreated}
onComplete={onComplete}
/>
</EuiFlexItem>
</>
Expand All @@ -96,7 +97,7 @@ ReferenceSetDataInput.displayName = 'ReferenceSetDataInput';
const END = 10 as const;
type SubStep = 1 | 2 | typeof END;
export const ReferenceSetDataInputSubSteps = React.memo<ReferenceSetDataInputSubStepsProps>(
({ migrationStats, missingReferenceSet, onAllReferenceSetCreated }) => {
({ migrationStats, missingReferenceSet, onComplete }) => {
const { telemetry } = useKibana().services.siemMigrations.rules;
const [subStep, setSubStep] = useState<SubStep>(1);
const [uploadedLookups, setUploadedLookups] = useState<UploadedLookups>({});
Expand All @@ -111,9 +112,9 @@ export const ReferenceSetDataInputSubSteps = React.memo<ReferenceSetDataInputSub
useEffect(() => {
if (missingReferenceSet.every((referenceSet) => uploadedLookups[referenceSet] != null)) {
setSubStep(END);
onAllReferenceSetCreated();
onComplete();
}
}, [uploadedLookups, missingReferenceSet, onAllReferenceSetCreated]);
}, [uploadedLookups, missingReferenceSet, onComplete]);

// Copy query step
const onCopied = useCallback(() => {
Expand All @@ -139,6 +140,7 @@ export const ReferenceSetDataInputSubSteps = React.memo<ReferenceSetDataInputSub
migrationStats,
missingLookups: missingReferenceSet,
addUploadedLookups,
onSkip: onComplete,
});

const steps = useMemo<EuiStepProps[]>(() => [copyStep, uploadStep], [copyStep, uploadStep]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -61,6 +63,7 @@ export const useReferencesFileUploadStep = ({
isLoading={isLoading}
apiError={error?.message}
migrationSource={MigrationSource.QRADAR}
onSkip={onSkip}
/>
),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.`,
}
);
Loading