mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Refactor Rule Details page (#166158)
**Resolves: https://github.com/elastic/kibana/issues/164962** **Resolves: https://github.com/elastic/kibana/issues/166650** **Related docs issue: https://github.com/elastic/security-docs/issues/3970** ## Summary Refactors Rule Details page by making use of section components from the rule preview flyout. Namely `RuleAboutSection`, `RuleDefinitionSection` and `RuleScheduleSection`. Also fixes a couple UI copy issues: - #164962. - #166650. ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] Documentation [issue](https://github.com/elastic/security-docs/issues/3970) created. - [x] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [x] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [x] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
92f5a883cf
commit
2d7a084da6
17 changed files with 352 additions and 171 deletions
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 type { Rule } from '../../../rule_management/logic';
|
||||
import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema/rule_schemas';
|
||||
|
||||
/*
|
||||
* This is a temporary workaround to suppress TS errors when using
|
||||
* rule section components on the rule details page.
|
||||
*
|
||||
* The rule details page passes a Rule object to the rule section components,
|
||||
* but section components expect a RuleResponse object. Rule and RuleResponse
|
||||
* are basically same object type with only a few minor differences.
|
||||
* This function casts the Rule object to RuleResponse.
|
||||
*
|
||||
* In the near future we'll start using codegen to generate proper response
|
||||
* types and the rule details page will start passing RuleResponse objects,
|
||||
* so this workaround will no longer be needed.
|
||||
*/
|
||||
export const castRuleAsRuleResponse = (rule: Rule) => rule as Partial<RuleResponse>;
|
|
@ -64,8 +64,6 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes';
|
|||
import { StepAboutRuleToggleDetails } from '../../../../detections/components/rules/step_about_rule_details';
|
||||
import { AlertsHistogramPanel } from '../../../../detections/components/alerts_kpis/alerts_histogram_panel';
|
||||
import { useUserData } from '../../../../detections/components/user_info';
|
||||
import { StepDefineRuleReadOnly } from '../../../../detections/components/rules/step_define_rule';
|
||||
import { StepScheduleRuleReadOnly } from '../../../../detections/components/rules/step_schedule_rule';
|
||||
import { StepRuleActionsReadOnly } from '../../../../detections/components/rules/step_rule_actions';
|
||||
import {
|
||||
buildAlertsFilter,
|
||||
|
@ -120,7 +118,6 @@ import * as ruleI18n from '../../../../detections/pages/detection_engine/rules/t
|
|||
import { RuleDetailsContextProvider } from './rule_details_context';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LegacyUrlConflictCallOut } from './legacy_url_conflict_callout';
|
||||
import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query';
|
||||
import * as i18n from './translations';
|
||||
import { NeedAdminForUpdateRulesCallOut } from '../../../../detections/components/callouts/need_admin_for_update_callout';
|
||||
import { MissingPrivilegesCallOut } from '../../../../detections/components/callouts/missing_privileges_callout';
|
||||
|
@ -138,12 +135,13 @@ import { useBulkDuplicateExceptionsConfirmation } from '../../../rule_management
|
|||
import { BulkActionDuplicateExceptionsConfirmation } from '../../../rule_management_ui/components/rules_table/bulk_actions/bulk_duplicate_exceptions_confirmation';
|
||||
import { useAsyncConfirmation } from '../../../rule_management_ui/components/rules_table/rules_table/use_async_confirmation';
|
||||
import { RuleSnoozeBadge } from '../../../rule_management/components/rule_snooze_badge';
|
||||
import { useRuleIndexPattern } from '../../../rule_creation_ui/pages/form';
|
||||
import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types';
|
||||
import { useBoolState } from '../../../../common/hooks/use_bool_state';
|
||||
import { RuleDefinitionSection } from '../../../rule_management/components/rule_details/rule_definition_section';
|
||||
import { RuleScheduleSection } from '../../../rule_management/components/rule_details/rule_schedule_section';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useLegacyUrlRedirect } from './use_redirect_legacy_url';
|
||||
import { RuleDetailTabs, useRuleDetailsTabs } from './use_rule_details_tabs';
|
||||
import { castRuleAsRuleResponse } from './cast_rule_as_rule_response';
|
||||
|
||||
const RULE_EXCEPTION_LIST_TYPES = [
|
||||
ExceptionListTypeEnum.DETECTION,
|
||||
|
@ -174,7 +172,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
clearSelected,
|
||||
}) => {
|
||||
const {
|
||||
data,
|
||||
application: {
|
||||
navigateToApp,
|
||||
capabilities: { actions },
|
||||
|
@ -259,38 +256,14 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
onFinish: hideDeleteConfirmation,
|
||||
});
|
||||
|
||||
const {
|
||||
aboutRuleData,
|
||||
modifiedAboutRuleDetailsData,
|
||||
defineRuleData,
|
||||
scheduleRuleData,
|
||||
ruleActionsData,
|
||||
} =
|
||||
const { aboutRuleData, modifiedAboutRuleDetailsData, ruleActionsData } =
|
||||
rule != null
|
||||
? getStepsData({ rule, detailsView: true })
|
||||
: {
|
||||
aboutRuleData: null,
|
||||
modifiedAboutRuleDetailsData: null,
|
||||
defineRuleData: null,
|
||||
scheduleRuleData: null,
|
||||
ruleActionsData: null,
|
||||
};
|
||||
const [dataViewTitle, setDataViewTitle] = useState<string>();
|
||||
useEffect(() => {
|
||||
const fetchDataViewTitle = async () => {
|
||||
if (defineRuleData?.dataViewId != null && defineRuleData?.dataViewId !== '') {
|
||||
const dataView = await data.dataViews.get(defineRuleData?.dataViewId);
|
||||
setDataViewTitle(dataView.title);
|
||||
}
|
||||
};
|
||||
fetchDataViewTitle();
|
||||
}, [data.dataViews, defineRuleData?.dataViewId]);
|
||||
|
||||
const { indexPattern: ruleIndexPattern } = useRuleIndexPattern({
|
||||
dataSourceType: defineRuleData?.dataSourceType ?? DataSourceType.IndexPatterns,
|
||||
index: defineRuleData?.index ?? [],
|
||||
dataViewId: defineRuleData?.dataViewId,
|
||||
});
|
||||
|
||||
const { showBuildingBlockAlerts, setShowBuildingBlockAlerts, showOnlyThreatIndicatorAlerts } =
|
||||
useDataTableFilters(TableId.alertsOnRuleDetailsPage);
|
||||
|
@ -299,11 +272,6 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
const [filterGroup, setFilterGroup] = useState<Status>(FILTER_OPEN);
|
||||
|
||||
const { isSavedQueryLoading, savedQueryBar } = useGetSavedQuery({
|
||||
savedQueryId: rule?.saved_id,
|
||||
ruleType: rule?.type,
|
||||
});
|
||||
|
||||
// TODO: Refactor license check + hasMlAdminPermissions to common check
|
||||
const hasMlPermissions = hasMlLicense(mlCapabilities) && hasMlAdminPermissions(mlCapabilities);
|
||||
|
||||
|
@ -666,30 +634,25 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem data-test-subj="aboutRule" component="section" grow={1}>
|
||||
<StepAboutRuleToggleDetails
|
||||
loading={isLoading}
|
||||
stepData={aboutRuleData}
|
||||
stepDataDetails={modifiedAboutRuleDetailsData}
|
||||
/>
|
||||
{rule !== null && (
|
||||
<StepAboutRuleToggleDetails
|
||||
loading={isLoading}
|
||||
stepData={aboutRuleData}
|
||||
stepDataDetails={modifiedAboutRuleDetailsData}
|
||||
rule={rule}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={1}>
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem component="section" grow={1} data-test-subj="defineRule">
|
||||
<StepPanel
|
||||
loading={isLoading || isSavedQueryLoading}
|
||||
title={ruleI18n.DEFINITION}
|
||||
>
|
||||
{defineRuleData != null && !isSavedQueryLoading && !isStartingJobs && (
|
||||
<StepDefineRuleReadOnly
|
||||
addPadding={false}
|
||||
descriptionColumns="singleSplit"
|
||||
defaultValues={{
|
||||
dataViewTitle,
|
||||
...defineRuleData,
|
||||
queryBar: savedQueryBar ?? defineRuleData.queryBar,
|
||||
}}
|
||||
indexPattern={ruleIndexPattern}
|
||||
<StepPanel loading={isLoading} title={ruleI18n.DEFINITION}>
|
||||
{rule !== null && !isStartingJobs && (
|
||||
<RuleDefinitionSection
|
||||
rule={castRuleAsRuleResponse(rule)}
|
||||
isInteractive
|
||||
dataTestSubj="definitionRule"
|
||||
/>
|
||||
)}
|
||||
</StepPanel>
|
||||
|
@ -697,12 +660,8 @@ const RuleDetailsPageComponent: React.FC<DetectionEngineComponentProps> = ({
|
|||
<EuiSpacer />
|
||||
<EuiFlexItem data-test-subj="schedule" component="section" grow={1}>
|
||||
<StepPanel loading={isLoading} title={ruleI18n.SCHEDULE}>
|
||||
{scheduleRuleData != null && (
|
||||
<StepScheduleRuleReadOnly
|
||||
addPadding={false}
|
||||
descriptionColumns="singleSplit"
|
||||
defaultValues={scheduleRuleData}
|
||||
/>
|
||||
{rule != null && (
|
||||
<RuleScheduleSection rule={castRuleAsRuleResponse(rule)} />
|
||||
)}
|
||||
</StepPanel>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -53,11 +53,17 @@ const StyledEuiLink = styled(EuiLink)`
|
|||
word-break: break-word;
|
||||
`;
|
||||
|
||||
interface NameProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const Name = ({ name }: NameProps) => <EuiText size="s">{name}</EuiText>;
|
||||
|
||||
interface DescriptionProps {
|
||||
description: string;
|
||||
}
|
||||
|
||||
const Description = ({ description }: DescriptionProps) => (
|
||||
export const Description = ({ description }: DescriptionProps) => (
|
||||
<EuiText size="s">{description}</EuiText>
|
||||
);
|
||||
|
||||
|
@ -217,10 +223,29 @@ interface TagsProps {
|
|||
|
||||
const Tags = ({ tags }: TagsProps) => <BadgeList badges={tags} />;
|
||||
|
||||
const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListProps['listItems'] => {
|
||||
// eslint-disable-next-line complexity
|
||||
const prepareAboutSectionListItems = (
|
||||
rule: Partial<RuleResponse>,
|
||||
hideName?: boolean,
|
||||
hideDescription?: boolean
|
||||
): EuiDescriptionListProps['listItems'] => {
|
||||
const aboutSectionListItems: EuiDescriptionListProps['listItems'] = [];
|
||||
|
||||
if (rule.author.length > 0) {
|
||||
if (!hideName && rule.name) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.NAME_FIELD_LABEL,
|
||||
description: <Name name={rule.name} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (!hideDescription && rule.description) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.DESCRIPTION_FIELD_LABEL,
|
||||
description: <Description description={rule.description} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.author && rule.author.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.AUTHOR_FIELD_LABEL,
|
||||
description: <Author author={rule.author} />,
|
||||
|
@ -234,12 +259,14 @@ const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListPro
|
|||
});
|
||||
}
|
||||
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.SEVERITY_FIELD_LABEL,
|
||||
description: <SeverityBadge value={rule.severity} />,
|
||||
});
|
||||
if (rule.severity) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.SEVERITY_FIELD_LABEL,
|
||||
description: <SeverityBadge value={rule.severity} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.severity_mapping.length > 0) {
|
||||
if (rule.severity_mapping && rule.severity_mapping.length > 0) {
|
||||
aboutSectionListItems.push(
|
||||
...rule.severity_mapping
|
||||
.filter((severityMappingItem) => severityMappingItem.field !== '')
|
||||
|
@ -252,12 +279,14 @@ const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListPro
|
|||
);
|
||||
}
|
||||
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.RISK_SCORE_FIELD_LABEL,
|
||||
description: <RiskScore riskScore={rule.risk_score} />,
|
||||
});
|
||||
if (rule.risk_score) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.RISK_SCORE_FIELD_LABEL,
|
||||
description: <RiskScore riskScore={rule.risk_score} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.risk_score_mapping.length > 0) {
|
||||
if (rule.risk_score_mapping && rule.risk_score_mapping.length > 0) {
|
||||
aboutSectionListItems.push(
|
||||
...rule.risk_score_mapping
|
||||
.filter((riskScoreMappingItem) => riskScoreMappingItem.field !== '')
|
||||
|
@ -270,14 +299,14 @@ const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListPro
|
|||
);
|
||||
}
|
||||
|
||||
if (rule.references.length > 0) {
|
||||
if (rule.references && rule.references.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.REFERENCES_FIELD_LABEL,
|
||||
description: <References references={rule.references} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.false_positives.length > 0) {
|
||||
if (rule.false_positives && rule.false_positives.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.FALSE_POSITIVES_FIELD_LABEL,
|
||||
description: <FalsePositives falsePositives={rule.false_positives} />,
|
||||
|
@ -307,7 +336,7 @@ const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListPro
|
|||
});
|
||||
}
|
||||
|
||||
if (rule.threat.length > 0) {
|
||||
if (rule.threat && rule.threat.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.THREAT_FIELD_LABEL,
|
||||
description: <Threat threat={rule.threat} />,
|
||||
|
@ -328,7 +357,7 @@ const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListPro
|
|||
});
|
||||
}
|
||||
|
||||
if (rule.tags.length > 0) {
|
||||
if (rule.tags && rule.tags.length > 0) {
|
||||
aboutSectionListItems.push({
|
||||
title: i18n.TAGS_FIELD_LABEL,
|
||||
description: <Tags tags={rule.tags} />,
|
||||
|
@ -339,30 +368,23 @@ const prepareAboutSectionListItems = (rule: RuleResponse): EuiDescriptionListPro
|
|||
};
|
||||
|
||||
export interface RuleAboutSectionProps {
|
||||
rule: RuleResponse;
|
||||
rule: Partial<RuleResponse>;
|
||||
hideName?: boolean;
|
||||
hideDescription?: boolean;
|
||||
}
|
||||
|
||||
export const RuleAboutSection = ({ rule }: RuleAboutSectionProps) => {
|
||||
const aboutSectionListItems = prepareAboutSectionListItems(rule);
|
||||
export const RuleAboutSection = ({ rule, hideName, hideDescription }: RuleAboutSectionProps) => {
|
||||
const aboutSectionListItems = prepareAboutSectionListItems(rule, hideName, hideDescription);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{rule.description && (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: i18n.DESCRIPTION_FIELD_LABEL,
|
||||
description: <Description description={rule.description} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
listItems={aboutSectionListItems}
|
||||
columnWidths={DESCRIPTION_LIST_COLUMN_WIDTHS}
|
||||
rowGutterSize="m"
|
||||
data-test-subj="listItemColumnStepRuleDescription"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -34,12 +34,17 @@ import type { RequiredFieldArray } from '../../../../../common/api/detection_eng
|
|||
import { assertUnreachable } from '../../../../../common/utility_types';
|
||||
import * as descriptionStepI18n from '../../../../detections/components/rules/description_step/translations';
|
||||
import { RelatedIntegrationsDescription } from '../../../../detections/components/rules/related_integrations/integrations_description';
|
||||
import { AlertSuppressionTechnicalPreviewBadge } from '../../../../detections/components/rules/description_step/alert_suppression_technical_preview_badge';
|
||||
import { useGetSavedQuery } from '../../../../detections/pages/detection_engine/rules/use_get_saved_query';
|
||||
import { useLicense } from '../../../../common/hooks/use_license';
|
||||
import * as threatMatchI18n from '../../../../common/components/threat_match/translations';
|
||||
import { AlertSuppressionMissingFieldsStrategy } from '../../../../../common/api/detection_engine/model/rule_schema/specific_attributes/query_attributes';
|
||||
import * as timelinesI18n from '../../../../timelines/components/timeline/translations';
|
||||
import { useRuleIndexPattern } from '../../../rule_creation_ui/pages/form';
|
||||
import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types';
|
||||
import type { Duration } from '../../../../detections/pages/detection_engine/rules/types';
|
||||
import { convertHistoryStartToSize } from '../../../../detections/pages/detection_engine/rules/helpers';
|
||||
import { MlJobsDescription } from '../../../../detections/components/rules/ml_jobs_description/ml_jobs_description';
|
||||
import { MlJobLink } from '../../../../detections/components/rules/ml_job_link/ml_job_link';
|
||||
import { useSecurityJobs } from '../../../../common/components/ml_popover/hooks/use_security_jobs';
|
||||
import { useKibana } from '../../../../common/lib/kibana/kibana_react';
|
||||
|
@ -170,11 +175,16 @@ const AnomalyThreshold = ({ anomalyThreshold }: AnomalyThresholdProps) => (
|
|||
|
||||
interface MachineLearningJobListProps {
|
||||
jobIds: string[];
|
||||
isInteractive: boolean;
|
||||
}
|
||||
|
||||
const MachineLearningJobList = ({ jobIds }: MachineLearningJobListProps) => {
|
||||
const MachineLearningJobList = ({ jobIds, isInteractive }: MachineLearningJobListProps) => {
|
||||
const { jobs } = useSecurityJobs();
|
||||
|
||||
if (isInteractive) {
|
||||
return <MlJobsDescription jobIds={jobIds} />;
|
||||
}
|
||||
|
||||
const relevantJobs = jobs.filter((job) => jobIds.includes(job.id));
|
||||
|
||||
return (
|
||||
|
@ -202,7 +212,7 @@ const getRuleTypeDescription = (ruleType: Type) => {
|
|||
case 'eql':
|
||||
return descriptionStepI18n.EQL_TYPE_DESCRIPTION;
|
||||
case 'esql':
|
||||
return descriptionStepI18n.ESQL_TYPE_DESCRIPTION;
|
||||
return <TitleWithTechnicalPreviewBadge title={descriptionStepI18n.ESQL_TYPE_DESCRIPTION} />;
|
||||
case 'threat_match':
|
||||
return descriptionStepI18n.THREAT_MATCH_TYPE_DESCRIPTION;
|
||||
case 'new_terms':
|
||||
|
@ -301,6 +311,49 @@ const ThreatMapping = ({ threatMapping }: ThreatMappingProps) => {
|
|||
return <EuiText size="s">{description}</EuiText>;
|
||||
};
|
||||
|
||||
interface TitleWithTechnicalPreviewBadgeProps {
|
||||
title: string;
|
||||
}
|
||||
|
||||
const TitleWithTechnicalPreviewBadge = ({ title }: TitleWithTechnicalPreviewBadgeProps) => {
|
||||
const license = useLicense();
|
||||
|
||||
return <AlertSuppressionTechnicalPreviewBadge label={title} license={license} />;
|
||||
};
|
||||
|
||||
interface SuppressAlertsByFieldProps {
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
const SuppressAlertsByField = ({ fields }: SuppressAlertsByFieldProps) => (
|
||||
<BadgeList badges={fields} />
|
||||
);
|
||||
|
||||
interface SuppressAlertsDurationProps {
|
||||
duration?: Duration;
|
||||
}
|
||||
|
||||
const SuppressAlertsDuration = ({ duration }: SuppressAlertsDurationProps) => {
|
||||
const durationDescription = duration
|
||||
? `${duration.value}${duration.unit}`
|
||||
: descriptionStepI18n.ALERT_SUPPRESSION_PER_RULE_EXECUTION;
|
||||
|
||||
return <EuiText size="s">{durationDescription}</EuiText>;
|
||||
};
|
||||
|
||||
interface MissingFieldsStrategyProps {
|
||||
missingFieldsStrategy?: AlertSuppressionMissingFieldsStrategy;
|
||||
}
|
||||
|
||||
const MissingFieldsStrategy = ({ missingFieldsStrategy }: MissingFieldsStrategyProps) => {
|
||||
const missingFieldsDescription =
|
||||
missingFieldsStrategy === AlertSuppressionMissingFieldsStrategy.Suppress
|
||||
? descriptionStepI18n.ALERT_SUPPRESSION_SUPPRESS_ON_MISSING_FIELDS
|
||||
: descriptionStepI18n.ALERT_SUPPRESSION_DO_NOT_SUPPRESS_ON_MISSING_FIELDS;
|
||||
|
||||
return <EuiText size="s">{missingFieldsDescription}</EuiText>;
|
||||
};
|
||||
|
||||
interface NewTermsFieldsProps {
|
||||
newTermsFields: string[];
|
||||
}
|
||||
|
@ -321,7 +374,8 @@ const HistoryWindowSize = ({ historyWindowStart }: HistoryWindowSizeProps) => {
|
|||
|
||||
// eslint-disable-next-line complexity
|
||||
const prepareDefinitionSectionListItems = (
|
||||
rule: RuleResponse,
|
||||
rule: Partial<RuleResponse>,
|
||||
isInteractive: boolean,
|
||||
savedQuery?: SavedQuery
|
||||
): EuiDescriptionListProps['listItems'] => {
|
||||
const definitionSectionListItems: EuiDescriptionListProps['listItems'] = [];
|
||||
|
@ -358,13 +412,18 @@ const prepareDefinitionSectionListItems = (
|
|||
description: <Filters filters={savedQuery.attributes.filters as Filter[]} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof savedQuery.attributes.query.query === 'string') {
|
||||
definitionSectionListItems.push({
|
||||
title: descriptionStepI18n.SAVED_QUERY_LABEL,
|
||||
description: <Query query={savedQuery.attributes.query.query} />,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ('filters' in rule && 'data_view_id' in rule && rule.filters?.length) {
|
||||
if ('filters' in rule && rule.filters?.length) {
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery
|
||||
? descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL
|
||||
: descriptionStepI18n.FILTERS_LABEL,
|
||||
title: descriptionStepI18n.FILTERS_LABEL,
|
||||
description: (
|
||||
<Filters
|
||||
filters={rule.filters as Filter[]}
|
||||
|
@ -376,16 +435,27 @@ const prepareDefinitionSectionListItems = (
|
|||
}
|
||||
|
||||
if ('query' in rule && rule.query) {
|
||||
let title = descriptionStepI18n.QUERY_LABEL;
|
||||
if (rule.type === 'saved_query') {
|
||||
title = descriptionStepI18n.SAVED_QUERY_LABEL;
|
||||
} else if (rule.type === 'eql') {
|
||||
title = descriptionStepI18n.EQL_QUERY_LABEL;
|
||||
} else if (rule.type === 'esql') {
|
||||
title = descriptionStepI18n.ESQL_QUERY_LABEL;
|
||||
}
|
||||
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery ? descriptionStepI18n.SAVED_QUERY_LABEL : descriptionStepI18n.QUERY_LABEL,
|
||||
title,
|
||||
description: <Query query={rule.query} />,
|
||||
});
|
||||
}
|
||||
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.RULE_TYPE_FIELD_LABEL,
|
||||
description: <RuleType type={rule.type} />,
|
||||
});
|
||||
if (rule.type) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.RULE_TYPE_FIELD_LABEL,
|
||||
description: <RuleType type={rule.type} />,
|
||||
});
|
||||
}
|
||||
|
||||
if ('anomaly_threshold' in rule && rule.anomaly_threshold) {
|
||||
definitionSectionListItems.push({
|
||||
|
@ -397,11 +467,16 @@ const prepareDefinitionSectionListItems = (
|
|||
if ('machine_learning_job_id' in rule) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.MACHINE_LEARNING_JOB_ID_FIELD_LABEL,
|
||||
description: <MachineLearningJobList jobIds={rule.machine_learning_job_id as string[]} />,
|
||||
description: (
|
||||
<MachineLearningJobList
|
||||
jobIds={rule.machine_learning_job_id as string[]}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if (rule.related_integrations.length > 0) {
|
||||
if (rule.related_integrations && rule.related_integrations.length > 0) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.RELATED_INTEGRATIONS_FIELD_LABEL,
|
||||
description: (
|
||||
|
@ -410,7 +485,7 @@ const prepareDefinitionSectionListItems = (
|
|||
});
|
||||
}
|
||||
|
||||
if (rule.required_fields.length > 0) {
|
||||
if (rule.required_fields && rule.required_fields.length > 0) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.REQUIRED_FIELDS_FIELD_LABEL,
|
||||
description: <RequiredFields requiredFields={rule.required_fields} />,
|
||||
|
@ -447,9 +522,7 @@ const prepareDefinitionSectionListItems = (
|
|||
|
||||
if ('threat_filters' in rule && rule.threat_filters && rule.threat_filters.length > 0) {
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery
|
||||
? descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL
|
||||
: descriptionStepI18n.FILTERS_LABEL,
|
||||
title: i18n.THREAT_FILTERS_FIELD_LABEL,
|
||||
description: (
|
||||
<Filters
|
||||
filters={rule.threat_filters as Filter[]}
|
||||
|
@ -462,13 +535,32 @@ const prepareDefinitionSectionListItems = (
|
|||
|
||||
if ('threat_query' in rule && rule.threat_query) {
|
||||
definitionSectionListItems.push({
|
||||
title: savedQuery
|
||||
? descriptionStepI18n.SAVED_QUERY_LABEL
|
||||
: descriptionStepI18n.THREAT_QUERY_LABEL,
|
||||
title: descriptionStepI18n.THREAT_QUERY_LABEL,
|
||||
description: <Query query={rule.threat_query} />,
|
||||
});
|
||||
}
|
||||
|
||||
if ('alert_suppression' in rule && rule.alert_suppression) {
|
||||
definitionSectionListItems.push({
|
||||
title: <TitleWithTechnicalPreviewBadge title={i18n.SUPPRESS_ALERTS_BY_FIELD_LABEL} />,
|
||||
description: <SuppressAlertsByField fields={rule.alert_suppression.group_by} />,
|
||||
});
|
||||
|
||||
definitionSectionListItems.push({
|
||||
title: <TitleWithTechnicalPreviewBadge title={i18n.SUPPRESS_ALERTS_DURATION_FIELD_LABEL} />,
|
||||
description: <SuppressAlertsDuration duration={rule.alert_suppression.duration} />,
|
||||
});
|
||||
|
||||
definitionSectionListItems.push({
|
||||
title: <TitleWithTechnicalPreviewBadge title={i18n.SUPPRESSION_FIELD_MISSING_FIELD_LABEL} />,
|
||||
description: (
|
||||
<MissingFieldsStrategy
|
||||
missingFieldsStrategy={rule.alert_suppression.missing_fields_strategy}
|
||||
/>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if ('new_terms_fields' in rule && rule.new_terms_fields && rule.new_terms_fields.length > 0) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.NEW_TERMS_FIELDS_FIELD_LABEL,
|
||||
|
@ -476,7 +568,7 @@ const prepareDefinitionSectionListItems = (
|
|||
});
|
||||
}
|
||||
|
||||
if (rule.type === 'new_terms' || 'history_window_start' in rule) {
|
||||
if ('history_window_start' in rule) {
|
||||
definitionSectionListItems.push({
|
||||
title: i18n.HISTORY_WINDOW_SIZE_FIELD_LABEL,
|
||||
description: <HistoryWindowSize historyWindowStart={rule.history_window_start} />,
|
||||
|
@ -487,24 +579,35 @@ const prepareDefinitionSectionListItems = (
|
|||
};
|
||||
|
||||
export interface RuleDefinitionSectionProps {
|
||||
rule: RuleResponse;
|
||||
rule: Partial<RuleResponse>;
|
||||
isInteractive?: boolean;
|
||||
dataTestSubj?: string;
|
||||
}
|
||||
|
||||
export const RuleDefinitionSection = ({ rule }: RuleDefinitionSectionProps) => {
|
||||
export const RuleDefinitionSection = ({
|
||||
rule,
|
||||
isInteractive = false,
|
||||
dataTestSubj,
|
||||
}: RuleDefinitionSectionProps) => {
|
||||
const { savedQuery } = useGetSavedQuery({
|
||||
savedQueryId: rule.type === 'saved_query' ? rule.saved_id : '',
|
||||
ruleType: rule.type,
|
||||
});
|
||||
|
||||
const definitionSectionListItems = prepareDefinitionSectionListItems(rule, savedQuery);
|
||||
const definitionSectionListItems = prepareDefinitionSectionListItems(
|
||||
rule,
|
||||
isInteractive,
|
||||
savedQuery
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
listItems={definitionSectionListItems}
|
||||
columnWidths={DESCRIPTION_LIST_COLUMN_WIDTHS}
|
||||
rowGutterSize="m"
|
||||
data-test-subj="listItemColumnStepRuleDescription"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema/rule_schemas';
|
||||
import { RuleAboutSection } from './rule_about_section';
|
||||
import { RuleAboutSection, Description } from './rule_about_section';
|
||||
import { RuleDefinitionSection } from './rule_definition_section';
|
||||
import { RuleScheduleSection } from './rule_schedule_section';
|
||||
import { RuleSetupGuideSection } from './rule_setup_guide_section';
|
||||
|
@ -103,7 +103,8 @@ export const RuleOverviewTab = ({
|
|||
isOpen={expandedOverviewSections.about}
|
||||
toggle={toggleOverviewSection.about}
|
||||
>
|
||||
<RuleAboutSection rule={rule} />
|
||||
{rule.description && <Description description={rule.description} />}
|
||||
<RuleAboutSection rule={rule} hideDescription hideName />
|
||||
</ExpandableSection>
|
||||
<EuiHorizontalRule margin="m" />
|
||||
<ExpandableSection
|
||||
|
|
|
@ -28,10 +28,14 @@ const From = ({ from, interval }: FromProps) => (
|
|||
);
|
||||
|
||||
export interface RuleScheduleSectionProps {
|
||||
rule: RuleResponse;
|
||||
rule: Partial<RuleResponse>;
|
||||
}
|
||||
|
||||
export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => {
|
||||
if (!rule.interval || !rule.from) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ruleSectionListItems = [];
|
||||
|
||||
ruleSectionListItems.push(
|
||||
|
@ -46,7 +50,7 @@ export const RuleScheduleSection = ({ rule }: RuleScheduleSectionProps) => {
|
|||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-test-subj="listItemColumnStepRuleDescription">
|
||||
<EuiDescriptionList
|
||||
type="column"
|
||||
listItems={ruleSectionListItems}
|
||||
|
|
|
@ -56,6 +56,13 @@ export const SETUP_GUIDE_SECTION_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const NAME_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.nameFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Name',
|
||||
}
|
||||
);
|
||||
|
||||
export const DESCRIPTION_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.descriptionFieldLabel',
|
||||
{
|
||||
|
@ -185,7 +192,7 @@ export const INDEX_FIELD_LABEL = i18n.translate(
|
|||
export const DATA_VIEW_ID_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.dataViewIdFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Data view',
|
||||
defaultMessage: 'Data view ID',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -269,7 +276,28 @@ export const THREAT_MAPPING_FIELD_LABEL = i18n.translate(
|
|||
export const THREAT_FILTERS_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.threatFiltersFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Filters',
|
||||
defaultMessage: 'Indicator filters',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUPPRESS_ALERTS_BY_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.suppressAlertsByFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Suppress alerts by',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUPPRESS_ALERTS_DURATION_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.suppressAlertsForFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Suppress alerts for',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUPPRESSION_FIELD_MISSING_FIELD_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.suppressionFieldMissingFieldLabel',
|
||||
{
|
||||
defaultMessage: 'If a suppression field is missing',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -15,12 +15,26 @@ export const FILTERS_LABEL = i18n.translate(
|
|||
);
|
||||
|
||||
export const QUERY_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.QueryLabel',
|
||||
'xpack.securitySolution.detectionEngine.createRule.queryLabel',
|
||||
{
|
||||
defaultMessage: 'Custom query',
|
||||
}
|
||||
);
|
||||
|
||||
export const EQL_QUERY_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.eqlQueryLabel',
|
||||
{
|
||||
defaultMessage: 'EQL query',
|
||||
}
|
||||
);
|
||||
|
||||
export const ESQL_QUERY_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.esqlQueryLabel',
|
||||
{
|
||||
defaultMessage: 'ES|QL query',
|
||||
}
|
||||
);
|
||||
|
||||
export const THREAT_QUERY_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.createRule.threatQueryLabel',
|
||||
{
|
||||
|
|
|
@ -11,7 +11,10 @@ import { EuiProgress, EuiButtonGroup } from '@elastic/eui';
|
|||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { StepAboutRuleToggleDetails } from '.';
|
||||
import { mockAboutStepRule } from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock';
|
||||
import {
|
||||
mockRule,
|
||||
mockAboutStepRule,
|
||||
} from '../../../../detection_engine/rule_management_ui/components/rules_table/__mocks__/mock';
|
||||
import { HeaderSection } from '../../../../common/components/header_section';
|
||||
import { StepAboutRule } from '../step_about_rule';
|
||||
import type { AboutStepRule } from '../../../pages/detection_engine/rules/types';
|
||||
|
@ -24,10 +27,10 @@ const mockTheme = getMockTheme({
|
|||
});
|
||||
|
||||
describe('StepAboutRuleToggleDetails', () => {
|
||||
let mockRule: AboutStepRule;
|
||||
let stepDataMock: AboutStepRule;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRule = mockAboutStepRule();
|
||||
stepDataMock = mockAboutStepRule();
|
||||
});
|
||||
|
||||
test('it renders loading component when "loading" is true', () => {
|
||||
|
@ -35,11 +38,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={true}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: '',
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -49,7 +53,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
|
||||
test('it does not render details if stepDataDetails is null', () => {
|
||||
const wrapper = shallow(
|
||||
<StepAboutRuleToggleDetails loading={true} stepDataDetails={null} stepData={mockRule} />
|
||||
<StepAboutRuleToggleDetails
|
||||
loading={true}
|
||||
stepDataDetails={null}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(wrapper.find(StepAboutRule).exists()).toBeFalsy();
|
||||
|
@ -65,6 +74,7 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
setup: '',
|
||||
}}
|
||||
stepData={null}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -74,7 +84,7 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
describe('note value is empty string', () => {
|
||||
test('it does not render toggle buttons', () => {
|
||||
const mockAboutStepWithoutNote = {
|
||||
...mockRule,
|
||||
...stepDataMock,
|
||||
note: '',
|
||||
};
|
||||
const wrapper = shallow(
|
||||
|
@ -82,10 +92,11 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: '',
|
||||
description: mockRule.description,
|
||||
description: stepDataMock.description,
|
||||
setup: '',
|
||||
}}
|
||||
stepData={mockAboutStepWithoutNote}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
);
|
||||
|
||||
|
@ -103,11 +114,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: '',
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -123,11 +135,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: '',
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -151,11 +164,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: '',
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -180,11 +194,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: '',
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -203,11 +218,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
setup: mockRule.note, // TODO: Update to mockRule.setup once supported in UI (and mock can be updated)
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: stepDataMock.note, // TODO: Update to mockRule.setup once supported in UI (and mock can be updated)
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -224,11 +240,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
setup: mockRule.note, // TODO: Update to mockRule.setup once supported in UI (and mock can be updated)
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: stepDataMock.note, // TODO: Update to mockRule.setup once supported in UI (and mock can be updated)
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -254,11 +271,12 @@ describe('StepAboutRuleToggleDetails', () => {
|
|||
<StepAboutRuleToggleDetails
|
||||
loading={false}
|
||||
stepDataDetails={{
|
||||
note: mockRule.note,
|
||||
description: mockRule.description,
|
||||
setup: mockRule.note, // TODO: Update to mockRule.setup once supported in UI (and mock can be updated)
|
||||
note: stepDataMock.note,
|
||||
description: stepDataMock.description,
|
||||
setup: stepDataMock.note, // TODO: Update to mockRule.setup once supported in UI (and mock can be updated)
|
||||
}}
|
||||
stepData={mockRule}
|
||||
stepData={stepDataMock}
|
||||
rule={mockRule('mocked-rule-id')}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
|
|
@ -21,14 +21,16 @@ import type { PropsWithChildren } from 'react';
|
|||
import React, { memo, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { css } from '@emotion/css';
|
||||
import type { Rule } from '../../../../detection_engine/rule_management/logic/types';
|
||||
import { RuleAboutSection } from '../../../../detection_engine/rule_management/components/rule_details/rule_about_section';
|
||||
import { HeaderSection } from '../../../../common/components/header_section';
|
||||
import { MarkdownRenderer } from '../../../../common/components/markdown_editor';
|
||||
import type {
|
||||
AboutStepRule,
|
||||
AboutStepRuleDetails,
|
||||
} from '../../../pages/detection_engine/rules/types';
|
||||
import { castRuleAsRuleResponse } from '../../../../detection_engine/rule_details_ui/pages/rule_details/cast_rule_as_rule_response';
|
||||
import * as i18n from './translations';
|
||||
import { StepAboutRuleReadOnly } from '../step_about_rule';
|
||||
import { fullHeight } from './styles';
|
||||
|
||||
const detailsOption: EuiButtonGroupOptionProps = {
|
||||
|
@ -51,12 +53,14 @@ interface StepPanelProps {
|
|||
stepData: AboutStepRule | null;
|
||||
stepDataDetails: AboutStepRuleDetails | null;
|
||||
loading: boolean;
|
||||
rule: Rule;
|
||||
}
|
||||
|
||||
const StepAboutRuleToggleDetailsComponent: React.FC<StepPanelProps> = ({
|
||||
stepData,
|
||||
stepDataDetails,
|
||||
loading,
|
||||
rule,
|
||||
}) => {
|
||||
const [selectedToggleOption, setToggleOption] = useState('details');
|
||||
const [aboutPanelHeight, setAboutPanelHeight] = useState(0);
|
||||
|
@ -124,10 +128,10 @@ const StepAboutRuleToggleDetailsComponent: React.FC<StepPanelProps> = ({
|
|||
</VerticalOverflowContent>
|
||||
</VerticalOverflowContainer>
|
||||
<EuiSpacer size="m" />
|
||||
<StepAboutRuleReadOnly
|
||||
addPadding={false}
|
||||
descriptionColumns="singleSplit"
|
||||
defaultValues={stepData}
|
||||
<RuleAboutSection
|
||||
rule={castRuleAsRuleResponse(rule)}
|
||||
hideName
|
||||
hideDescription
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -30606,7 +30606,7 @@
|
|||
"xpack.securitySolution.detectionEngine.createRule.mlRuleTypeDescription": "Machine Learning",
|
||||
"xpack.securitySolution.detectionEngine.createRule.newTermsRuleTypeDescription": "Nouveaux termes",
|
||||
"xpack.securitySolution.detectionEngine.createRule.pageTitle": "Créer une nouvelle règle",
|
||||
"xpack.securitySolution.detectionEngine.createRule.QueryLabel": "Requête personnalisée",
|
||||
"xpack.securitySolution.detectionEngine.createRule.queryLabel": "Requête personnalisée",
|
||||
"xpack.securitySolution.detectionEngine.createRule.queryRuleTypeDescription": "Requête",
|
||||
"xpack.securitySolution.detectionEngine.createRule.ruleActionsField.ruleActionsFormErrorsTitle": "Veuillez corriger les problèmes répertoriés ci-dessous",
|
||||
"xpack.securitySolution.detectionEngine.createRule.rulePreviewDescription": "L'aperçu des règles reflète la configuration actuelle de vos paramètres et exceptions de règles. Cliquez sur l'icône d'actualisation pour afficher l'aperçu mis à jour.",
|
||||
|
|
|
@ -30605,7 +30605,7 @@
|
|||
"xpack.securitySolution.detectionEngine.createRule.mlRuleTypeDescription": "機械学習",
|
||||
"xpack.securitySolution.detectionEngine.createRule.newTermsRuleTypeDescription": "新しい用語",
|
||||
"xpack.securitySolution.detectionEngine.createRule.pageTitle": "新規ルールを作成",
|
||||
"xpack.securitySolution.detectionEngine.createRule.QueryLabel": "カスタムクエリ",
|
||||
"xpack.securitySolution.detectionEngine.createRule.queryLabel": "カスタムクエリ",
|
||||
"xpack.securitySolution.detectionEngine.createRule.queryRuleTypeDescription": "クエリ",
|
||||
"xpack.securitySolution.detectionEngine.createRule.ruleActionsField.ruleActionsFormErrorsTitle": "次の一覧の問題を解決してください",
|
||||
"xpack.securitySolution.detectionEngine.createRule.rulePreviewDescription": "ルールプレビューには、ルール設定と例外の現在の構成が反映されます。更新アイコンをクリックすると、更新されたプレビューが表示されます。",
|
||||
|
|
|
@ -30601,7 +30601,7 @@
|
|||
"xpack.securitySolution.detectionEngine.createRule.mlRuleTypeDescription": "Machine Learning",
|
||||
"xpack.securitySolution.detectionEngine.createRule.newTermsRuleTypeDescription": "新字词",
|
||||
"xpack.securitySolution.detectionEngine.createRule.pageTitle": "创建新规则",
|
||||
"xpack.securitySolution.detectionEngine.createRule.QueryLabel": "定制查询",
|
||||
"xpack.securitySolution.detectionEngine.createRule.queryLabel": "定制查询",
|
||||
"xpack.securitySolution.detectionEngine.createRule.queryRuleTypeDescription": "查询",
|
||||
"xpack.securitySolution.detectionEngine.createRule.ruleActionsField.ruleActionsFormErrorsTitle": "请修复下面所列的问题",
|
||||
"xpack.securitySolution.detectionEngine.createRule.rulePreviewDescription": "规则预览反映了您的规则设置和例外的当前配置,单击刷新图标可查看已更新的预览。",
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { getEsqlRule } from '../../../objects/rule';
|
||||
|
||||
import {
|
||||
CUSTOM_QUERY_DETAILS,
|
||||
ESQL_QUERY_DETAILS,
|
||||
DEFINITION_DETAILS,
|
||||
RULE_NAME_HEADER,
|
||||
RULE_TYPE_DETAILS,
|
||||
|
@ -43,7 +43,7 @@ describe('Detection ES|QL rules, details view', { tags: ['@ess'] }, () => {
|
|||
cy.get(RULE_NAME_HEADER).should('contain', `${rule.name}`);
|
||||
|
||||
cy.get(DEFINITION_DETAILS).within(() => {
|
||||
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query);
|
||||
getDetails(ESQL_QUERY_DETAILS).should('have.text', rule.query);
|
||||
|
||||
getDetails(RULE_TYPE_DETAILS).contains('ES|QL');
|
||||
// ensures ES|QL rule in technical preview
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { getEsqlRule } from '../../../objects/rule';
|
||||
|
||||
import { CUSTOM_QUERY_DETAILS, RULE_NAME_OVERRIDE_DETAILS } from '../../../screens/rule_details';
|
||||
import { ESQL_QUERY_DETAILS, RULE_NAME_OVERRIDE_DETAILS } from '../../../screens/rule_details';
|
||||
|
||||
import { ESQL_QUERY_BAR, ESQL_QUERY_BAR_EXPAND_BTN } from '../../../screens/create_new_rule';
|
||||
|
||||
|
@ -60,7 +60,7 @@ describe('Detection ES|QL rules, edit', { tags: ['@ess'] }, () => {
|
|||
saveEditedRule();
|
||||
|
||||
// ensure updated query is displayed on details page
|
||||
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', expectedValidEsqlQuery);
|
||||
getDetails(ESQL_QUERY_DETAILS).should('have.text', expectedValidEsqlQuery);
|
||||
});
|
||||
|
||||
it('edits ES|QL rule query and override rule name with new property', () => {
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
ABOUT_INVESTIGATION_NOTES,
|
||||
ABOUT_RULE_DESCRIPTION,
|
||||
ADDITIONAL_LOOK_BACK_DETAILS,
|
||||
CUSTOM_QUERY_DETAILS,
|
||||
EQL_QUERY_DETAILS,
|
||||
DEFINITION_DETAILS,
|
||||
FALSE_POSITIVES_DETAILS,
|
||||
removeExternalLinkText,
|
||||
|
@ -115,7 +115,7 @@ describe('EQL rules', { tags: ['@ess', '@serverless', '@brokenInServerless'] },
|
|||
cy.get(ABOUT_INVESTIGATION_NOTES).should('have.text', INVESTIGATION_NOTES_MARKDOWN);
|
||||
cy.get(DEFINITION_DETAILS).within(() => {
|
||||
getDetails(INDEX_PATTERNS_DETAILS).should('have.text', getIndexPatterns().join(''));
|
||||
getDetails(CUSTOM_QUERY_DETAILS).should('have.text', rule.query);
|
||||
getDetails(EQL_QUERY_DETAILS).should('have.text', rule.query);
|
||||
getDetails(RULE_TYPE_DETAILS).should('have.text', 'Event Correlation');
|
||||
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', 'None');
|
||||
});
|
||||
|
|
|
@ -23,6 +23,10 @@ export const ANOMALY_SCORE_DETAILS = 'Anomaly score';
|
|||
|
||||
export const CUSTOM_QUERY_DETAILS = 'Custom query';
|
||||
|
||||
export const EQL_QUERY_DETAILS = 'EQL query';
|
||||
|
||||
export const ESQL_QUERY_DETAILS = 'ES|QL query';
|
||||
|
||||
export const SAVED_QUERY_NAME_DETAILS = 'Saved query name';
|
||||
|
||||
export const SAVED_QUERY_DETAILS = /^Saved query$/;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue