[Security Solution] Siem migrations copy changes in the upload flyout (#213975)

## Summary

## Summary

2/3 of https://github.com/elastic/security-team/issues/11696

**Done**
- UI changes in the onboarding cards
- UI changes in the upload form

**Pending**
- UI changes in the translated rules page

### Screenshots


![rules](https://github.com/user-attachments/assets/d6db959c-914c-461f-a590-f5d26cf9e0f2)


![macros](https://github.com/user-attachments/assets/5d7b0682-60eb-413e-a902-ab77f86dfea4)


![lookups](https://github.com/user-attachments/assets/95958728-e969-422e-9403-43da5355a1d6)

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Sergi Massaneda 2025-03-12 15:58:33 +01:00 committed by GitHub
parent dd9b5bb1b7
commit 065790e20a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 155 additions and 153 deletions

View file

@ -87,7 +87,7 @@ export const AIConnectorCard: OnboardingCardComponent<AIConnectorCardMetadata> =
{isInferenceConnector ? (
<FormattedMessage
id="xpack.securitySolution.onboarding.aiConnectorCardInferenceDescription"
defaultMessage="The Elastic-provided connector is selected by default. You can configure another connector and model if you prefer. Learn more about {docsLink} and performance with our {llmMatrixLink}"
defaultMessage="The Elastic-provided connector is selected by default. You can configure another connector and model if you prefer. Learn more about {docsLink} and {llmMatrixLink}"
values={{
llmMatrixLink: <LlmPerformanceMatrixDocsLink text={i18n.LLM_MATRIX_LINK} />,
docsLink: <SiemMigrationDocsLink text={i18n.AI_POWERED_MIGRATIONS_LINK} />,

View file

@ -29,7 +29,7 @@ export const AI_CONNECTOR_CARD_DESCRIPTION_INFERENCE_CONNECTOR = i18n.translate(
export const LLM_MATRIX_LINK = i18n.translate(
'xpack.securitySolution.onboarding.aiConnector.llmMatrixLink',
{ defaultMessage: 'LLM performance matrix' }
{ defaultMessage: 'model performance' }
);
export const AI_POWERED_MIGRATIONS_LINK = i18n.translate(

View file

@ -99,7 +99,7 @@ export const MigrationDataInputFlyout = React.memo<MigrationDataInputFlyoutProps
return (
<EuiFlyoutResizable
onClose={onClose}
size="m"
size={850}
maxWidth={1200}
minWidth={500}
data-test-subj="uploadRulesFlyout"

View file

@ -110,7 +110,7 @@ export const LookupsFileUpload = React.memo<LookupsFileUploadProps>(
const isButtonDisabled = showLoader || lookupResources.length === 0;
return (
<EuiFlexGroup direction="column">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiFormRow
helpText={errors.map((error) => (

View file

@ -14,11 +14,11 @@ import {
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
EuiToolTip,
useEuiTheme,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { UploadedLookups } from '../../lookups_data_input';
import * as i18n from './translations';
@ -37,60 +37,70 @@ export const MissingLookupsList = React.memo<MissingLookupsListProps>(
({ missingLookups, uploadedLookups, omitLookup, onCopied }) => {
const { euiTheme } = useEuiTheme();
return (
<>
<EuiPanel hasShadow={false} hasBorder className={scrollPanelCss}>
<EuiFlexGroup direction="column" gutterSize="s">
{missingLookups.map((lookupName) => {
const isOmitted = uploadedLookups[lookupName] === '';
return (
<EuiFlexItem key={lookupName}>
<EuiFlexGroup
direction="row"
gutterSize="s"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
{uploadedLookups[lookupName] != null ? (
<EuiIcon type="checkInCircleFilled" color={euiTheme.colors.success} />
) : (
<EuiIcon type="dot" />
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" style={isOmitted ? { textDecoration: 'line-through' } : {}}>
{lookupName}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={lookupName}>
{(copy) => (
<CopyLookupNameButton
lookupName={lookupName}
onCopied={onCopied}
copy={copy}
/>
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.dataInputFlyout.lookups.copyExportQuery.description"
defaultMessage="Log in to your Splunk admin account, go to the {app}, download the following lookups individually and upload them below. You can also omit lookups that are empty or not needed, and they will be ignored in the translation."
values={{ app: <b>{i18n.LOOKUPS_SPLUNK_APP}</b> }}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
<EuiPanel hasShadow={false} hasBorder className={scrollPanelCss}>
<EuiFlexGroup direction="column" gutterSize="s">
{missingLookups.map((lookupName) => {
const isOmitted = uploadedLookups[lookupName] === '';
return (
<EuiFlexItem key={lookupName}>
<EuiFlexGroup
direction="row"
gutterSize="s"
alignItems="center"
justifyContent="flexStart"
>
<EuiFlexItem grow={false}>
{uploadedLookups[lookupName] != null ? (
<EuiIcon type="checkInCircleFilled" color={euiTheme.colors.success} />
) : (
<EuiIcon type="dot" />
)}
</EuiCopy>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<OmitLookupButton
lookupName={lookupName}
omitLookup={omitLookup}
isDisabled={isOmitted}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
})}
</EuiFlexGroup>
</EuiPanel>
<EuiSpacer size="s" />
<EuiText size="s" color="subdued">
{i18n.MISSING_LOOKUPS_DESCRIPTION}
</EuiText>
</>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText
size="s"
style={isOmitted ? { textDecoration: 'line-through' } : {}}
>
{lookupName}
</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiCopy textToCopy={lookupName}>
{(copy) => (
<CopyLookupNameButton
lookupName={lookupName}
onCopied={onCopied}
copy={copy}
/>
)}
</EuiCopy>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<OmitLookupButton
lookupName={lookupName}
omitLookup={omitLookup}
isDisabled={isOmitted}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
);
})}
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
);
}
);

View file

@ -12,12 +12,9 @@ export const LOOKUPS_DATA_INPUT_COPY_TITLE = i18n.translate(
{ defaultMessage: 'Lookups found in your rules' }
);
export const MISSING_LOOKUPS_DESCRIPTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.lookups.missingLookupsList.description',
{
defaultMessage:
'For your lookups, go to your admin Splunk account and the Search and Reporting app Lookups page. Download the following lookups individually and upload below.',
}
export const LOOKUPS_SPLUNK_APP = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.lookups.missingLookupsList.appSection',
{ defaultMessage: 'Splunk App for Lookup File Editing' }
);
export const COPY_LOOKUP_NAME_TOOLTIP = i18n.translate(

View file

@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
export const LOOKUPS_DATA_INPUT_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.lookups.title',
{ defaultMessage: 'Upload identified lookups' }
{ defaultMessage: 'Upload lookups' }
);
export const LOOKUPS_DATA_INPUT_DESCRIPTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.lookups.description',

View file

@ -45,10 +45,6 @@ export const useCheckResourcesStep = ({
return {
title: i18n.RULES_DATA_INPUT_CHECK_RESOURCES_TITLE,
status: uploadStepStatus,
children: (
<EuiText size="xs" color="subdued">
{i18n.RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION}
</EuiText>
),
children: <EuiText size="s">{i18n.RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION}</EuiText>,
};
};

View file

@ -15,6 +15,6 @@ export const RULES_DATA_INPUT_CHECK_RESOURCES_TITLE = i18n.translate(
export const RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.checkResources.description',
{
defaultMessage: `For best translation results, we will automatically review your rules for macros and lookups and ask you to upload them. Once uploaded, we'll be able to deliver a more complete rule translation for all rules using those macros or lookups.`,
defaultMessage: `For best translation results, we will review the data for macros and lookups. If found, we will ask you to upload them next.`,
}
);

View file

@ -6,7 +6,7 @@
*/
import React, { useCallback } from 'react';
import { EuiCodeBlock, EuiSpacer, EuiText } from '@elastic/eui';
import { EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { MACROS_SPLUNK_QUERY } from '../../../../constants';
import * as i18n from './translations';
@ -26,28 +26,31 @@ export const CopyExportQuery = React.memo<CopyExportQueryProps>(({ onCopied }) =
);
return (
<>
{/* The click event is also dispatched when using the keyboard actions (space or enter) for "copy" button.
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.copyExportQuery.description"
defaultMessage="From you admin Splunk account, go to the {section} app and run the above query. Export your results as {format}."
values={{
section: <b>{i18n.MACROS_DATA_INPUT_COPY_DESCRIPTION_SECTION}</b>,
format: <b>{'JSON'}</b>,
}}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
{/* 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 */}
<div onClick={onClick}>
{/* onCopy react event is dispatched when the user copies text manually */}
<EuiCodeBlock language="text" fontSize="m" paddingSize="m" isCopyable onCopy={onCopied}>
{MACROS_SPLUNK_QUERY}
</EuiCodeBlock>
</div>
<EuiSpacer size="m" />
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description"
defaultMessage="From you admin Splunk account, go to the {section} app and run the above query. Export your results as {format}."
values={{
section: <b>{i18n.MACROS_DATA_INPUT_COPY_DESCRIPTION_SECTION}</b>,
format: <b>{'JSON'}</b>,
}}
/>
</EuiText>
</>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div onClick={onClick}>
{/* onCopy react event is dispatched when the user copies text manually */}
<EuiCodeBlock language="text" fontSize="m" paddingSize="m" isCopyable onCopy={onCopied}>
{MACROS_SPLUNK_QUERY}
</EuiCodeBlock>
</div>
</EuiFlexItem>
</EuiFlexGroup>
);
});
CopyExportQuery.displayName = 'CopyExportQuery';

View file

@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n';
export const MACROS_DATA_INPUT_COPY_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.copyExportQuery.title',
{ defaultMessage: 'Copy macros query' }
{ defaultMessage: 'Export macros' }
);
export const MACROS_DATA_INPUT_COPY_DESCRIPTION_SECTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.copyExportQuery.description.section',
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.copyExportQuery.descriptionSection',
{ defaultMessage: 'Search and Reporting' }
);

View file

@ -62,7 +62,7 @@ export const MacrosFileUpload = React.memo<MacrosFileUploadProps>(
const isButtonDisabled = showLoader || macrosToUpload.length === 0;
return (
<EuiFlexGroup direction="column">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiFormRow
helpText={

View file

@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
export const MACROS_DATA_INPUT_FILE_UPLOAD_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.macrosFileUpload.title',
{ defaultMessage: 'Update your macros export' }
{ defaultMessage: 'Upload exported macros' }
);
export const MACROS_DATA_INPUT_FILE_UPLOAD_PROMPT = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.macrosFileUpload.prompt',

View file

@ -9,5 +9,5 @@ import { i18n } from '@kbn/i18n';
export const MACROS_DATA_INPUT_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.macros.title',
{ defaultMessage: 'Upload identified macros' }
{ defaultMessage: 'Upload macros' }
);

View file

@ -44,10 +44,6 @@ export const useCheckResourcesStep = ({
return {
title: i18n.RULES_DATA_INPUT_CHECK_RESOURCES_TITLE,
status: uploadStepStatus,
children: (
<EuiText size="xs" color="subdued">
{i18n.RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION}
</EuiText>
),
children: <EuiText size="s">{i18n.RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION}</EuiText>,
};
};

View file

@ -15,6 +15,6 @@ export const RULES_DATA_INPUT_CHECK_RESOURCES_TITLE = i18n.translate(
export const RULES_DATA_INPUT_CHECK_RESOURCES_DESCRIPTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.checkResources.description',
{
defaultMessage: `For best translation results, we will automatically review your rules for macros and lookups and ask you to upload them. Once uploaded, we'll be able to deliver a more complete rule translation for all rules using those macros or lookups.`,
defaultMessage: `For best translation results, we will review the data for macros and lookups. If found, we will ask you to upload them next.`,
}
);

View file

@ -6,7 +6,7 @@
*/
import React, { useCallback } from 'react';
import { EuiCodeBlock, EuiSpacer, EuiText } from '@elastic/eui';
import { EuiCode, EuiCodeBlock, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { RULES_SPLUNK_QUERY } from '../../../../constants';
import * as i18n from './translations';
@ -26,28 +26,40 @@ export const CopyExportQuery = React.memo<CopyExportQueryProps>(({ onCopied }) =
);
return (
<>
{/* The click event is also dispatched when using the keyboard actions (space or enter) for "copy" button.
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description"
defaultMessage="Log in to your Splunk admin account, go to the {section} app and run the following query. Export your results as {format}."
values={{
section: <b>{i18n.RULES_DATA_INPUT_COPY_DESCRIPTION_SECTION}</b>,
format: <b>{'JSON'}</b>,
}}
/>
</EuiText>
</EuiFlexItem>
<EuiFlexItem>
{/* 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 */}
<div onClick={onClick}>
{/* onCopy react event is dispatched when the user copies text manually */}
<EuiCodeBlock language="text" fontSize="m" paddingSize="m" isCopyable onCopy={onCopied}>
{RULES_SPLUNK_QUERY}
</EuiCodeBlock>
</div>
<EuiSpacer size="m" />
<EuiText size="s">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.copyExportQuery.description"
defaultMessage="From you admin Splunk account, go to the {section} app and run the above query. Export your results as {format}."
values={{
section: <b>{i18n.RULES_DATA_INPUT_COPY_DESCRIPTION_SECTION}</b>,
format: <b>{'JSON'}</b>,
}}
/>
</EuiText>
</>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div onClick={onClick}>
{/* onCopy react event is dispatched when the user copies text manually */}
<EuiCodeBlock language="text" fontSize="m" paddingSize="m" isCopyable onCopy={onCopied}>
{RULES_SPLUNK_QUERY}
</EuiCodeBlock>
</div>
</EuiFlexItem>
<EuiFlexItem>
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rulesFileUpload.disclaimer"
defaultMessage="Note: To avoid exceeding your LLM API rate limit when translating a large number of queries, consider exporting rules in batches, for example by adding {operator} to the query above"
values={{ operator: <EuiCode>{'| head'}</EuiCode> }}
/>
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
});
CopyExportQuery.displayName = 'CopyExportQuery';

View file

@ -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: 'Copy rules query' }
{ defaultMessage: 'Export Splunk rules' }
);
export const RULES_DATA_INPUT_COPY_DESCRIPTION_SECTION = i18n.translate(

View file

@ -7,19 +7,11 @@
import React, { useCallback, useMemo, useRef, useState } from 'react';
import { isPlainObject } from 'lodash';
import {
EuiCode,
EuiFilePicker,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiText,
} from '@elastic/eui';
import { EuiFilePicker, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiText } from '@elastic/eui';
import type {
EuiFilePickerClass,
EuiFilePickerProps,
} from '@elastic/eui/src/components/form/file_picker/file_picker';
import { FormattedMessage } from '@kbn/i18n-react';
import type { CreateRuleMigrationRequestBody } from '../../../../../../../../../common/siem_migrations/model/api/rules/rule_migration.gen';
import type { OriginalRule } from '../../../../../../../../../common/siem_migrations/model/rule_migration.gen';
import type { CreateMigration } from '../../../../../../service/hooks/use_create_migration';
@ -74,22 +66,9 @@ export const RulesFileUpload = React.memo<RulesFileUploadProps>(
const isButtonDisabled = isDisabled || rulesToUpload.length === 0;
return (
<EuiFlexGroup direction="column">
<EuiFlexGroup direction="column" gutterSize="s">
<EuiFlexItem>
<EuiFormRow
helpText={
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rulesFileUpload.disclaimer"
defaultMessage="Note: To avoid exceeding your LLM API rate limit when translating a large number of queries, consider exporting rules in batches, for example by adding {operator} to the query above"
values={{ operator: <EuiCode>{'| head'}</EuiCode> }}
/>
</EuiText>
}
isInvalid={error != null}
fullWidth
error={error}
>
<EuiFormRow isInvalid={error != null} fullWidth error={error}>
<EuiFilePicker
id="rulesFilePicker"
ref={filePickerRef as React.Ref<Omit<EuiFilePickerProps, 'stylesMemoizer'>>}

View file

@ -9,8 +9,17 @@ import { i18n } from '@kbn/i18n';
export const RULES_DATA_INPUT_FILE_UPLOAD_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.title',
{ defaultMessage: 'Update your rules export' }
{ defaultMessage: 'Update exported rules' }
);
export const RULES_DATA_INPUT_FILE_UPLOAD_DESCRIPTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.description',
{
defaultMessage:
'For best translation results, we will review your rules for macros and lookups. If found, we will ask you to upload them next.',
}
);
export const RULES_DATA_INPUT_FILE_UPLOAD_PROMPT = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.rulesFileUpload.prompt',
{ defaultMessage: 'Select or drag and drop the exported JSON file' }

View file

@ -9,5 +9,5 @@ import { i18n } from '@kbn/i18n';
export const RULES_DATA_INPUT_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.dataInputFlyout.rules.title',
{ defaultMessage: 'Upload rule export and check for macros and lookups' }
{ defaultMessage: 'Upload rules' }
);