Skip to content

Commit

Permalink
[Rules migration] UI updates (elastic#207789)
Browse files Browse the repository at this point in the history
## Summary

[Internal link](elastic/security-team#10820)
to the feature details

This PR includes next improvements and fixes

### Improvements

1. Updated copies
2. Added `Last updated` label in Translation Rule details flyout

<img width="1274" alt="Screenshot 2025-01-22 at 14 15 24 copy"
src="https://github.com/user-attachments/assets/6974698f-3a26-48f1-96fc-d5458aa81a0a"
/>

3. Added rule installation information callout in Translation Rule
details flyout

<img width="1274" alt="Screenshot 2025-01-22 at 14 15 24"
src="https://github.com/user-attachments/assets/c350f0c2-8acf-4821-99a8-f6b510ae8dc5"
/>

4. Added horizontal line underneath the Translation Rules header

> [!NOTE]  
> This feature needs `siemMigrationsEnabled` experimental flag enabled
to work.
  • Loading branch information
e40pud authored Jan 23, 2025
1 parent 0a577be commit 8351418
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export const siemMigrationsLinks: LinkItem = {
id: SecurityPageName.siemMigrationsRules,
title: SIEM_MIGRATIONS_RULES,
description: i18n.translate('xpack.securitySolution.appLinks.siemMigrationsRulesDescription', {
defaultMessage: 'SIEM Rule Migrations.',
defaultMessage:
'Our generative AI powered SIEM migration tool automates some of the most time consuming migrations tasks and processed.',
}),
landingIcon: SiemMigrationsIcon,
path: SIEM_MIGRATIONS_RULES_PATH,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
isMigrationCustomRule,
} from '../../../../../common/siem_migrations/rules/utils';
import { useUpdateMigrationRules } from '../../logic/use_update_migration_rules';
import { UpdatedByLabel } from './updated_by';

/*
* Fixes tabs to the top and allows the content to scroll.
Expand Down Expand Up @@ -244,7 +245,8 @@ export const MigrationRuleDetailsFlyout: React.FC<MigrationRuleDetailsFlyoutProp
i18n.UNKNOWN_MIGRATION_RULE_TITLE}
</h2>
</EuiTitle>
<EuiSpacer size="l" />
<EuiSpacer size="s" />
<UpdatedByLabel ruleMigration={ruleMigration} />
</EuiFlyoutHeader>
<EuiFlyoutBody>
<EuiSkeletonLoading
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,37 @@ import React from 'react';
import type { IconType } from '@elastic/eui';
import { EuiCallOut } from '@elastic/eui';
import {
RuleMigrationTranslationResultEnum,
type RuleMigration,
type RuleMigrationTranslationResult,
} from '../../../../../../../common/siem_migrations/model/rule_migration.gen';
import * as i18n from './translations';

type RuleMigrationTranslationCallOutMode = RuleMigrationTranslationResult | 'mapped';

const getCallOutInfo = (
translationResult: RuleMigrationTranslationResult
mode: RuleMigrationTranslationCallOutMode
): { title: string; message?: string; icon: IconType; color: 'success' | 'warning' | 'danger' } => {
switch (translationResult) {
case RuleMigrationTranslationResultEnum.full:
switch (mode) {
case 'mapped':
return {
title: i18n.CALLOUT_MAPPED_TRANSLATED_RULE_TITLE,
icon: 'checkInCircleFilled',
color: 'success',
};
case 'full':
return {
title: i18n.CALLOUT_TRANSLATED_RULE_TITLE,
icon: 'checkInCircleFilled',
color: 'success',
};
case RuleMigrationTranslationResultEnum.partial:
case 'partial':
return {
title: i18n.CALLOUT_PARTIALLY_TRANSLATED_RULE_TITLE,
message: i18n.CALLOUT_PARTIALLY_TRANSLATED_RULE_DESCRIPTION,
icon: 'warningFilled',
color: 'warning',
};
case RuleMigrationTranslationResultEnum.untranslatable:
case 'untranslatable':
return {
title: i18n.CALLOUT_NOT_TRANSLATED_RULE_TITLE,
message: i18n.CALLOUT_NOT_TRANSLATED_RULE_DESCRIPTION,
Expand All @@ -43,23 +51,29 @@ const getCallOutInfo = (
};

export interface TranslationCallOutProps {
translationResult: RuleMigrationTranslationResult;
ruleMigration: RuleMigration;
}

export const TranslationCallOut: FC<TranslationCallOutProps> = React.memo(
({ translationResult }) => {
const { title, message, icon, color } = getCallOutInfo(translationResult);

return (
<EuiCallOut
color={color}
title={title}
iconType={icon}
data-test-subj={`ruleMigrationCallOut-${translationResult}`}
>
{message}
</EuiCallOut>
);
export const TranslationCallOut: FC<TranslationCallOutProps> = React.memo(({ ruleMigration }) => {
if (!ruleMigration.translation_result) {
return null;
}
);

const mode = ruleMigration.elastic_rule?.prebuilt_rule_id
? 'mapped'
: ruleMigration.translation_result;
const { title, message, icon, color } = getCallOutInfo(mode);

return (
<EuiCallOut
color={color}
title={title}
iconType={icon}
size={'s'}
data-test-subj={`ruleMigrationCallOut-${mode}`}
>
{message}
</EuiCallOut>
);
});
TranslationCallOut.displayName = 'TranslationCallOut';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import React, { useMemo } from 'react';
import {
EuiAccordion,
EuiBadge,
EuiCallOut,
EuiFlexGroup,
EuiFlexItem,
EuiSpacer,
Expand All @@ -18,6 +19,7 @@ import {
} from '@elastic/eui';
import { css } from '@emotion/css';
import { FormattedMessage } from '@kbn/i18n-react';
import { RuleTranslationResult } from '../../../../../../../common/siem_migrations/constants';
import type { RuleResponse } from '../../../../../../../common/api/detection_engine';
import type { RuleMigration } from '../../../../../../../common/siem_migrations/model/rule_migration.gen';
import { TranslationTabHeader } from './header';
Expand Down Expand Up @@ -78,7 +80,7 @@ export const TranslationTab: React.FC<TranslationTabProps> = React.memo(
<EuiSpacer size="m" />
{ruleMigration.translation_result && !isInstalled && (
<>
<TranslationCallOut translationResult={ruleMigration.translation_result} />
<TranslationCallOut ruleMigration={ruleMigration} />
<EuiSpacer size="m" />
</>
)}
Expand Down Expand Up @@ -131,6 +133,19 @@ export const TranslationTab: React.FC<TranslationTabProps> = React.memo(
</EuiSplitPanel.Outer>
</EuiFlexItem>
</EuiAccordion>
{ruleMigration.translation_result === RuleTranslationResult.FULL && (
<>
<EuiSpacer size="m" />
<EuiCallOut
color={'primary'}
title={i18n.CALLOUT_TRANSLATED_RULE_INFO_TITLE}
iconType={'iInCircle'}
size={'s'}
>
{i18n.CALLOUT_TRANSLATED_RULE_INFO_DESCRIPTION}
</EuiCallOut>
</>
)}
</>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ export const CALLOUT_TRANSLATED_RULE_TITLE = i18n.translate(
}
);

export const CALLOUT_MAPPED_TRANSLATED_RULE_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.mappedTranslatedRuleCalloutTitle',
{
defaultMessage:
'This rule was mapped to an Elastic authored rule. Click Install & enable rule to complete migration. You can fine-tune it later.',
}
);

export const CALLOUT_PARTIALLY_TRANSLATED_RULE_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.partiallyTranslatedRuleCalloutTitle',
{
Expand Down Expand Up @@ -114,3 +122,18 @@ export const CALLOUT_NOT_TRANSLATED_RULE_DESCRIPTION = i18n.translate(
'This might be caused by feature differences between SIEM products. If possible, update the rule manually.',
}
);

export const CALLOUT_TRANSLATED_RULE_INFO_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.partiallyTranslatedRuleCalloutTitle',
{
defaultMessage: 'Translation successful. Install the rule to customize it.',
}
);

export const CALLOUT_TRANSLATED_RULE_INFO_DESCRIPTION = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.translationDetails.translationTab.partiallyTranslatedRuleCalloutDescription',
{
defaultMessage:
'After you install the rule, you can modify or update it with full access to all features.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ export const CLOSE_BUTTON_LABEL = i18n.translate(
defaultMessage: 'Close',
}
);

export const LAST_UPDATED_LABEL = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.translationDetails.lastUpdatedLabel',
{
defaultMessage: 'Last updated',
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiText } from '@elastic/eui';

import { FormattedDate } from '../../../../../common/components/formatted_date';
import { useBulkGetUserProfiles } from '../../../../../common/components/user_profiles/use_bulk_get_user_profiles';
import type { RuleMigration } from '../../../../../../common/siem_migrations/model/rule_migration.gen';

import * as i18n from './translations';

interface UpdatedByLabelProps {
ruleMigration: RuleMigration;
}

export const UpdatedByLabel: React.FC<UpdatedByLabelProps> = React.memo(
({ ruleMigration }: UpdatedByLabelProps) => {
const userProfileId = useMemo(
() => new Set([ruleMigration.updated_by ?? ruleMigration.created_by]),
[ruleMigration.created_by, ruleMigration.updated_by]
);
const { isLoading: isLoadingUserProfiles, data: userProfiles } = useBulkGetUserProfiles({
uids: userProfileId,
});

if (isLoadingUserProfiles || !userProfiles?.length) {
return null;
}

const userProfile = userProfiles[0];
const updatedBy = userProfile.user.full_name ?? userProfile.user.username;
const updatedAt = ruleMigration.updated_at ?? ruleMigration['@timestamp'];
return (
<EuiText size="xs">
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.translationDetails.updatedByLabel"
defaultMessage="{updated}: {by} on {date}"
values={{
updated: <b>{i18n.LAST_UPDATED_LABEL}</b>,
by: updatedBy,
date: <FormattedDate value={updatedAt} fieldName="updated_at" />,
}}
/>
</EuiText>
);
}
);
UpdatedByLabel.displayName = 'UpdatedByLabel';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 LAST_UPDATED_LABEL = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.translationDetails.lastUpdated.label',
{
defaultMessage: 'Last updated',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export const MigrationRulesPage: React.FC<MigrationRulesPageProps> = React.memo(
<MissingPrivilegesCallOut />

<SecuritySolutionPageWrapper>
<HeaderPage title={pageTitle}>
<HeaderPage title={pageTitle} border>
<HeaderButtons
ruleMigrationsStats={ruleMigrationsStats}
selectedMigrationId={migrationId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export const PageTitle: React.FC = React.memo(() => {

<EuiFlexItem
css={css`
margin: ${euiTheme.size.m} 0 0 ${euiTheme.size.m};
margin: ${euiTheme.size.s} 0 0 ${euiTheme.size.m};
`}
grow={false}
>
Expand Down

0 comments on commit 8351418

Please sign in to comment.