Skip to content

Commit

Permalink
[Rules migration] Improvements & fixes (#207177)
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. Add information tooltip for `Status`, `Severity`, `Author`,
`Integrations` and `Actions` column headers. [Figma
link](https://www.figma.com/design/BD9GZZz6y8pfSbubAt5H2W?node-id=2579-182863#1094946220)


/~https://github.com/user-attachments/assets/8de91149-8b47-4dc1-8a6c-853c9e428522

### Fixes

1. Migration rules page flickering/reloading on filter updates. Make
sure that we show loading indicator for the table only when rules data
is being fetched:


/~https://github.com/user-attachments/assets/ff24fd50-c286-46a6-a850-9d12d3a01993

2. Make sure that we split translation tab equally between original and
translated query code block components.


/~https://github.com/user-attachments/assets/c1214f2c-e0a9-4add-82e6-4296458ce7f9

To reproduce this issue you need to add a splunk rule with the long one
line query. For example

> tag=watchlist NOT sourcetype=stash | eval
risk_object=case(isnotnull(user),user,isnotnull(src_user),src_user,isnotnull(dest),dest,isnotnull(src),src,1=1,host)
| eval
risk_object_type=case(isnotnull(user),"user",isnotnull(src_user),"user",isnotnull(dest),"system",isnotnull(src),"system",1=1,"system")
| eval risk_score=if(eventtype="website_watchlist",50,null()) | eval
suppression_value=sourcetype."|".risk_object | `get_event_id` | table
_raw,event_id,host,source,sourcetype,src,dest,dvc,src_user,user

> [!NOTE]  
> This feature needs `siemMigrationsEnabled` experimental flag enabled
to work.
  • Loading branch information
e40pud authored Jan 20, 2025
1 parent bf15ee7 commit 49d1cea
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const TranslationTab: React.FC<TranslationTabProps> = React.memo(
<EuiBadge
color={convertTranslationResultIntoColor(ruleMigration.translation_result)}
onClick={() => {}}
onClickAriaLabel={'Click to update translation status'}
onClickAriaLabel={'Translation status badge'}
>
{isInstalled
? i18n.INSTALLED_LABEL
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,12 @@ export const MigrationRuleQuery: React.FC<MigrationRuleQueryProps> = React.memo(
<h3>{ruleName}</h3>
</EuiTitle>
<EuiSpacer size="m" />
<EuiCodeBlock language={codeBlockLanguage} fontSize="s" paddingSize="s">
<EuiCodeBlock
language={codeBlockLanguage}
fontSize="s"
paddingSize="s"
className="eui-textBreakWord"
>
{query}
</EuiCodeBlock>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,13 +212,13 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
startMigration(migrationId, SiemMigrationRetryFilter.FAILED);
}, [migrationId, startMigration]);

const isLoading =
isStatsLoading || isPrebuiltRulesLoading || isDataLoading || isTableLoading || isRetryLoading;
const isRulesLoading =
isPrebuiltRulesLoading || isDataLoading || isTableLoading || isRetryLoading;

const ruleActionsFactory = useCallback(
(ruleMigration: RuleMigration, closeRulePreview: () => void) => {
const canMigrationRuleBeInstalled =
!isLoading &&
!isRulesLoading &&
!ruleMigration.elastic_rule?.id &&
ruleMigration.translation_result === RuleTranslationResult.FULL;
return (
Expand Down Expand Up @@ -253,12 +253,12 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
</EuiFlexGroup>
);
},
[installSingleRule, isLoading]
[installSingleRule, isRulesLoading]
);

const getMigrationRuleData = useCallback(
(ruleId: string) => {
if (!isLoading && ruleMigrations.length) {
if (!isRulesLoading && ruleMigrations.length) {
const ruleMigration = ruleMigrations.find((item) => item.id === ruleId);
let matchedPrebuiltRule: RuleResponse | undefined;
let relatedIntegrations: RelatedIntegration[] = [];
Expand All @@ -278,14 +278,14 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
return { ruleMigration, matchedPrebuiltRule, relatedIntegrations, isIntegrationsLoading };
}
},
[integrations, isIntegrationsLoading, isLoading, prebuiltRules, ruleMigrations]
[integrations, isIntegrationsLoading, isRulesLoading, prebuiltRules, ruleMigrations]
);

const {
migrationRuleDetailsFlyout: rulePreviewFlyout,
openMigrationRuleDetails: openRulePreview,
} = useMigrationRuleDetailsFlyout({
isLoading,
isLoading: isRulesLoading,
getMigrationRuleData,
ruleActionsFactory,
});
Expand All @@ -300,7 +300,7 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
return (
<>
<EuiSkeletonLoading
isLoading={isDataLoading}
isLoading={isStatsLoading}
loadingContent={
<>
<EuiSkeletonTitle />
Expand All @@ -324,7 +324,7 @@ export const MigrationRulesTable: React.FC<MigrationRulesTableProps> = React.mem
</EuiFlexItem>
<EuiFlexItem grow={false}>
<BulkActions
isTableLoading={isLoading}
isTableLoading={isRulesLoading}
numberOfFailedRules={translationStats.rules.failed}
numberOfTranslatedRules={translationStats.rules.success.installable}
numberOfSelectedRules={selectedRuleMigrations.length}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
*/

import React from 'react';
import { EuiLink } from '@elastic/eui';
import { EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { SecuritySolutionLinkAnchor } from '../../../../common/components/links';
import {
RuleTranslationResult,
Expand All @@ -17,6 +18,7 @@ import { SecurityPageName } from '../../../../../common';
import { type RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
import * as i18n from './translations';
import { type TableColumn } from './constants';
import { TableHeader } from './header';

interface ActionNameProps {
disableActions?: boolean;
Expand Down Expand Up @@ -91,7 +93,34 @@ export const createActionsColumn = ({
}: CreateActionsColumnProps): TableColumn => {
return {
field: 'elastic_rule',
name: i18n.COLUMN_ACTIONS,
name: (
<TableHeader
title={i18n.COLUMN_ACTIONS}
tooltipContent={
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.tableColumn.actionsTooltip"
defaultMessage="{title}
{view} - go to rule installed in the Detection rule (SIEM) page. {lineBreak}
{install} - add rule to your Detection rule (SEIM) without enabling. {lineBreak}
{edit} - Open detail view when a rule has not been fully translated."
values={{
lineBreak: <br />,
title: (
<EuiText size="s">
<p>
<b>{i18n.COLUMN_ACTIONS}</b>
<EuiHorizontalRule margin="s" />
</p>
</EuiText>
),
view: <b>{i18n.ACTIONS_VIEW_LABEL}</b>,
install: <b>{i18n.ACTIONS_INSTALL_LABEL}</b>,
edit: <b>{i18n.ACTIONS_EDIT_LABEL}</b>,
}}
/>
}
/>
),
render: (_, rule: RuleMigration) => {
return (
<ActionName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
*/

import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiIcon, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants';
import { type RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
import * as i18n from './translations';
import { COLUMN_EMPTY_VALUE, type TableColumn } from './constants';
import { TableHeader } from './header';

const Author = ({ isPrebuiltRule }: { isPrebuiltRule: boolean }) => {
return (
Expand All @@ -30,7 +32,32 @@ const Author = ({ isPrebuiltRule }: { isPrebuiltRule: boolean }) => {
export const createAuthorColumn = (): TableColumn => {
return {
field: 'elastic_rule.prebuilt_rule_id',
name: i18n.COLUMN_AUTHOR,
name: (
<TableHeader
title={i18n.COLUMN_AUTHOR}
tooltipContent={
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.tableColumn.authorTooltip"
defaultMessage="{title}
{elastic} authored rules have been created and are maintained by Elastic. {lineBreak}
{custom} rules are any rules that are not authored by Elastic and will not be maintained or updated automatically."
values={{
lineBreak: <br />,
title: (
<EuiText size="s">
<p>
<b>{i18n.COLUMN_AUTHOR}</b>
<EuiHorizontalRule margin="s" />
</p>
</EuiText>
),
elastic: <b>{i18n.ELASTIC_AUTHOR_TITLE}</b>,
custom: <b>{i18n.CUSTOM_AUTHOR_TITLE}</b>,
}}
/>
}
/>
),
render: (_, rule: RuleMigration) => {
return rule.status === SiemMigrationStatus.FAILED ? (
<>{COLUMN_EMPTY_VALUE}</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { EuiToolTip, EuiIcon } from '@elastic/eui';

interface TableHeaderProps {
title: string;
tooltipContent?: React.ReactNode;
}

export const TableHeader: React.FC<TableHeaderProps> = React.memo(({ title, tooltipContent }) => {
return (
<EuiToolTip content={tooltipContent}>
<>
{title}
&nbsp;
<EuiIcon size="s" type="questionInCircle" color="subdued" className="eui-alignTop" />
</>
</EuiToolTip>
);
});
TableHeader.displayName = 'TableHeader';
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
*/

import React from 'react';
import { EuiLoadingSpinner } from '@elastic/eui';
import { EuiHorizontalRule, EuiLoadingSpinner, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { RelatedIntegration } from '../../../../../common/api/detection_engine';
import { IntegrationsPopover } from '../../../../detections/components/rules/related_integrations/integrations_popover';
import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
import * as i18n from './translations';
import type { TableColumn } from './constants';
import { TableHeader } from './header';

export const createIntegrationsColumn = ({
getMigrationRuleData,
Expand All @@ -22,7 +24,27 @@ export const createIntegrationsColumn = ({
}): TableColumn => {
return {
field: 'elastic_rule.integration_ids',
name: i18n.COLUMN_INTEGRATIONS,
name: (
<TableHeader
title={i18n.COLUMN_INTEGRATIONS}
tooltipContent={
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.tableColumn.integrationsTooltip"
defaultMessage="{title} The AI migration process tries to infer integrations from the queries provided, but its possible that a match might not be present."
values={{
title: (
<EuiText size="s">
<p>
<b>{i18n.COLUMN_INTEGRATIONS}</b>
<EuiHorizontalRule margin="s" />
</p>
</EuiText>
),
}}
/>
}
/>
),
render: (_, rule: RuleMigration) => {
const migrationRuleData = getMigrationRuleData(rule.id);
if (migrationRuleData?.isIntegrationsLoading) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,39 @@

import React from 'react';
import type { Severity } from '@kbn/securitysolution-io-ts-alerting-types';
import { EuiHorizontalRule, EuiText } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { type RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
import { SiemMigrationStatus } from '../../../../../common/siem_migrations/constants';
import { SeverityBadge } from '../../../../common/components/severity_badge';
import { COLUMN_EMPTY_VALUE, type TableColumn } from './constants';
import * as i18n from './translations';
import { TableHeader } from './header';

export const createSeverityColumn = (): TableColumn => {
return {
field: 'elastic_rule.severity',
name: i18n.COLUMN_SEVERITY,
name: (
<TableHeader
title={i18n.COLUMN_SEVERITY}
tooltipContent={
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.tableColumn.severityTooltip"
defaultMessage="{title} If the severity cannot be inferred from the rule export data, the rule Severity will be set to the default: Low severity."
values={{
title: (
<EuiText size="s">
<p>
<b>{i18n.COLUMN_SEVERITY}</b>
<EuiHorizontalRule margin="s" />
</p>
</EuiText>
),
}}
/>
}
/>
),
render: (value: Severity, rule: RuleMigration) =>
rule.status === SiemMigrationStatus.FAILED ? (
<>{COLUMN_EMPTY_VALUE}</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,53 @@
*/

import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiHorizontalRule, EuiText } from '@elastic/eui';
import { RuleTranslationResult } from '../../../../../common/siem_migrations/constants';
import type { RuleMigration } from '../../../../../common/siem_migrations/model/rule_migration.gen';
import * as i18n from './translations';
import type { TableColumn } from './constants';
import { StatusBadge } from '../status_badge';
import { TableHeader } from './header';
import { convertTranslationResultIntoText } from '../../utils/translation_results';

export const createStatusColumn = (): TableColumn => {
return {
field: 'translation_result',
name: i18n.COLUMN_STATUS,
name: (
<TableHeader
title={i18n.COLUMN_STATUS}
tooltipContent={
<FormattedMessage
id="xpack.securitySolution.siemMigrations.rules.tableColumn.statusTooltip"
defaultMessage="{title}
{installed} - already added to Elastic SIEM. Click View to manage and enable it. {lineBreak}
{translated} - rule is ready to install. Rules with matching capabilities have been mapped to Elastic Authored rules. If not match was detected, an AI translation was provided. {lineBreak}
{partiallyTranslated} - part of the original query could not be translated. Make sure you’ve uploaded all macros and lookups, and resolved all syntax errors. {lineBreak}
{notTranslated} - none of the original query could be translated."
values={{
lineBreak: <br />,
title: (
<EuiText size="s">
<p>
<b>{i18n.STATUS_TOOLTIP_TITLE}</b>
<EuiHorizontalRule margin="s" />
</p>
</EuiText>
),
installed: <b>{i18n.INSTALLED_STATUS_TITLE}</b>,
translated: <b>{convertTranslationResultIntoText(RuleTranslationResult.FULL)}</b>,
partiallyTranslated: (
<b>{convertTranslationResultIntoText(RuleTranslationResult.PARTIAL)}</b>
),
notTranslated: (
<b>{convertTranslationResultIntoText(RuleTranslationResult.UNTRANSLATABLE)}</b>
),
}}
/>
}
/>
),
render: (_, rule: RuleMigration) => <StatusBadge migrationRule={rule} />,
sortable: true,
truncateText: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,17 @@ export const COLUMN_INTEGRATIONS = i18n.translate(
defaultMessage: 'Integrations',
}
);

export const STATUS_TOOLTIP_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.tableColumn.statusTooltipTitle',
{
defaultMessage: 'Translation Status legend',
}
);

export const INSTALLED_STATUS_TITLE = i18n.translate(
'xpack.securitySolution.siemMigrations.rules.tableColumn.installedStatusTitle',
{
defaultMessage: 'Installed',
}
);

0 comments on commit 49d1cea

Please sign in to comment.