mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] ThreeWayDiff UI: Add FieldReadOnly
component (#191499)
**Partially addresses: https://github.com/elastic/kibana/issues/171520** **Follow-up PR: https://github.com/elastic/kibana/pull/192342** This is the 1st of the 2 PRs for `FieldReadOnly`. The second PR will add more field components. I split the work into two PRs to keep the number of changed files reasonable. ## Summary This PR adds the `FieldReadOnly` component along with some field components. Field components display a read-only view of a particular `DiffableRule` field, similar to how fields are shown on the Rule Details page. `FieldReadOnly` and field components will be displayed in the right side of the new Diff tab of the Upgrade flyout (see it on the [Miro board](https://miro.com/app/board/uXjVK0gqjjQ=/?moveToWidget=3458764594148126123&cot=14)). They will let the user see how an upgraded version of a rule will look like in a user-friendly way. ### Running `FinalReadOnly` and its field components are not yet integrated into the flyout, but you can view components in Storybook. 1. Run Storybook: `yarn storybook security_solution` 2. Go to `http://localhost:9001` in browser. <img width="1062" alt="Schermafbeelding 2024-09-03 om 13 05 11" src="https://github.com/user-attachments/assets/13b227d4-1321-47d9-a0a7-93868c9f4a15"> ## Changes - `FieldReadOnly` component itself was added. It shows a field component based on a `fieldName` prop. - Field components (like `DataSourceReadOnly`) were added. These components mostly import and reuse components from the Rule Details page. - Each field component has a Storybook story. I had to mock dependencies for some field components to make them work in Storybook. - Rule Details page and Overview tab of the flyout now display query language for Custom Query, Saved Query and Indicator Match rules. Language can be either KQL or Lucene. Since language will be displayed in the new Diff tab, it makes sense to show it in other places as well to keep it consistent.
This commit is contained in:
parent
fa686b9905
commit
e308d17195
39 changed files with 1950 additions and 46 deletions
|
@ -21,7 +21,7 @@ export interface RuleUpgradeStatsForReview {
|
|||
/** Number of installed prebuilt rules available for upgrade (stock + customized) */
|
||||
num_rules_to_upgrade_total: number;
|
||||
|
||||
/** Number of installed prebuilt rules with upgrade conflicts (SOLVABLE or NON_SOLVALBE) */
|
||||
/** Number of installed prebuilt rules with upgrade conflicts (SOLVABLE or NON_SOLVABLE) */
|
||||
num_rules_with_conflicts: number;
|
||||
|
||||
/** Number of installed prebuilt rules with NON_SOLVABLE upgrade conflicts */
|
||||
|
|
|
@ -5,8 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RuleFieldsDiff, ThreeWayDiff } from '../../../../../common/api/detection_engine';
|
||||
import { ThreeWayDiffOutcome } from '../../../../../common/api/detection_engine';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import type {
|
||||
DiffableAllFields,
|
||||
RuleFieldsDiff,
|
||||
ThreeWayDiff,
|
||||
} from '../../../../../common/api/detection_engine';
|
||||
import { DataSourceType, ThreeWayDiffOutcome } from '../../../../../common/api/detection_engine';
|
||||
import type { FieldsGroupDiff } from '../../model/rule_details/rule_field_diff';
|
||||
import {
|
||||
ABOUT_UPGRADE_FIELD_ORDER,
|
||||
|
@ -14,6 +19,8 @@ import {
|
|||
SCHEDULE_UPGRADE_FIELD_ORDER,
|
||||
SETUP_UPGRADE_FIELD_ORDER,
|
||||
} from './constants';
|
||||
import * as i18n from './translations';
|
||||
import { assertUnreachable } from '../../../../../common/utility_types';
|
||||
|
||||
export const getSectionedFieldDiffs = (fields: FieldsGroupDiff[]) => {
|
||||
const aboutFields = [];
|
||||
|
@ -57,3 +64,58 @@ export const filterUnsupportedDiffOutcomes = (
|
|||
);
|
||||
})
|
||||
);
|
||||
|
||||
export function getQueryLanguageLabel(language: string) {
|
||||
switch (language) {
|
||||
case 'kuery':
|
||||
return i18n.KUERY_LANGUAGE_LABEL;
|
||||
case 'lucene':
|
||||
return i18n.LUCENE_LANGUAGE_LABEL;
|
||||
default:
|
||||
return language;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns type `Filter` to items that have a `meta` property. Removes any other items.
|
||||
*/
|
||||
export function typeCheckFilters(filters: unknown[]): Filter[] {
|
||||
return filters.filter((f) => {
|
||||
if (typeof f === 'object' && f !== null && 'meta' in f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}) as Filter[];
|
||||
}
|
||||
|
||||
type DataSourceProps =
|
||||
| {
|
||||
index: undefined;
|
||||
dataViewId: undefined;
|
||||
}
|
||||
| {
|
||||
index: string[];
|
||||
dataViewId: undefined;
|
||||
}
|
||||
| {
|
||||
index: undefined;
|
||||
dataViewId: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts `index` and `dataViewId` from a `data_source` object for use in the `Filters` component.
|
||||
*/
|
||||
export function getDataSourceProps(dataSource: DiffableAllFields['data_source']): DataSourceProps {
|
||||
if (!dataSource) {
|
||||
return { index: undefined, dataViewId: undefined };
|
||||
}
|
||||
|
||||
if (dataSource.type === DataSourceType.index_patterns) {
|
||||
return { index: dataSource.index_patterns, dataViewId: undefined };
|
||||
} else if (dataSource.type === DataSourceType.data_view) {
|
||||
return { index: undefined, dataViewId: dataSource.data_view_id };
|
||||
}
|
||||
|
||||
return assertUnreachable(dataSource);
|
||||
}
|
||||
|
|
|
@ -43,10 +43,10 @@ const OverrideColumn = styled(EuiFlexItem)`
|
|||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const OverrideValueColumn = styled(EuiFlexItem)`
|
||||
width: 30px;
|
||||
max-width: 30px;
|
||||
const OverrideValueColumn = styled.div`
|
||||
width: 50px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
|
@ -86,7 +86,7 @@ interface SeverityMappingItemProps {
|
|||
severityMappingItem: SeverityMappingItemType;
|
||||
}
|
||||
|
||||
const SeverityMappingItem = ({ severityMappingItem }: SeverityMappingItemProps) => (
|
||||
export const SeverityMappingItem = ({ severityMappingItem }: SeverityMappingItemProps) => (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<OverrideColumn>
|
||||
<EuiToolTip
|
||||
|
@ -96,16 +96,18 @@ const SeverityMappingItem = ({ severityMappingItem }: SeverityMappingItemProps)
|
|||
<span data-test-subj="severityOverrideField">{`${severityMappingItem.field}:`}</span>
|
||||
</EuiToolTip>
|
||||
</OverrideColumn>
|
||||
<OverrideValueColumn>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
content={severityMappingItem.value}
|
||||
data-test-subj={`severityOverrideValue-${severityMappingItem.value}`}
|
||||
>
|
||||
<span data-test-subj="severityOverrideValue">
|
||||
{defaultToEmptyTag(severityMappingItem.value)}
|
||||
</span>
|
||||
<OverrideValueColumn>
|
||||
<span data-test-subj="severityOverrideValue">
|
||||
{defaultToEmptyTag(severityMappingItem.value)}
|
||||
</span>
|
||||
</OverrideValueColumn>
|
||||
</EuiToolTip>
|
||||
</OverrideValueColumn>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiIcon type={'sortRight'} />
|
||||
</EuiFlexItem>
|
||||
|
@ -132,7 +134,7 @@ interface RiskScoreMappingItemProps {
|
|||
riskScoreMappingItem: RiskScoreMappingItemType;
|
||||
}
|
||||
|
||||
const RiskScoreMappingItem = ({ riskScoreMappingItem }: RiskScoreMappingItemProps) => (
|
||||
export const RiskScoreMappingItem = ({ riskScoreMappingItem }: RiskScoreMappingItemProps) => (
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<OverrideColumn>
|
||||
<EuiToolTip
|
||||
|
@ -218,7 +220,7 @@ interface ThreatProps {
|
|||
threat: Threats;
|
||||
}
|
||||
|
||||
const Threat = ({ threat }: ThreatProps) => (
|
||||
export const Threat = ({ threat }: ThreatProps) => (
|
||||
<ThreatEuiFlexGroup threat={filterEmptyThreats(threat)} data-test-subj="threatPropertyValue" />
|
||||
);
|
||||
|
||||
|
@ -226,7 +228,7 @@ interface ThreatIndicatorPathProps {
|
|||
threatIndicatorPath: string;
|
||||
}
|
||||
|
||||
const ThreatIndicatorPath = ({ threatIndicatorPath }: ThreatIndicatorPathProps) => (
|
||||
export const ThreatIndicatorPath = ({ threatIndicatorPath }: ThreatIndicatorPathProps) => (
|
||||
<EuiText size="s">{threatIndicatorPath}</EuiText>
|
||||
);
|
||||
|
||||
|
|
|
@ -57,12 +57,14 @@ import {
|
|||
queryStyles,
|
||||
useRequiredFieldsStyles,
|
||||
} from './rule_definition_section.styles';
|
||||
import { getQueryLanguageLabel } from './helpers';
|
||||
import { useDefaultIndexPattern } from './use_default_index_pattern';
|
||||
|
||||
interface SavedQueryNameProps {
|
||||
savedQueryName: string;
|
||||
}
|
||||
|
||||
const SavedQueryName = ({ savedQueryName }: SavedQueryNameProps) => (
|
||||
export const SavedQueryName = ({ savedQueryName }: SavedQueryNameProps) => (
|
||||
<EuiText size="s" data-test-subj="savedQueryNamePropertyValue">
|
||||
{savedQueryName}
|
||||
</EuiText>
|
||||
|
@ -75,12 +77,19 @@ interface FiltersProps {
|
|||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
const Filters = ({ filters, dataViewId, index, 'data-test-subj': dataTestSubj }: FiltersProps) => {
|
||||
export const Filters = ({
|
||||
filters,
|
||||
dataViewId,
|
||||
index,
|
||||
'data-test-subj': dataTestSubj,
|
||||
}: FiltersProps) => {
|
||||
const flattenedFilters = mapAndFlattenFilters(filters);
|
||||
|
||||
const defaultIndexPattern = useDefaultIndexPattern();
|
||||
|
||||
const { indexPattern } = useRuleIndexPattern({
|
||||
dataSourceType: dataViewId ? DataSourceType.DataView : DataSourceType.IndexPatterns,
|
||||
index: index ?? [],
|
||||
index: index ?? defaultIndexPattern,
|
||||
dataViewId,
|
||||
});
|
||||
|
||||
|
@ -104,7 +113,7 @@ interface QueryProps {
|
|||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
const Query = ({ query, 'data-test-subj': dataTestSubj = 'query' }: QueryProps) => {
|
||||
export const Query = ({ query, 'data-test-subj': dataTestSubj = 'query' }: QueryProps) => {
|
||||
const styles = queryStyles;
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj} className={styles.content}>
|
||||
|
@ -117,7 +126,7 @@ interface IndexProps {
|
|||
index: string[];
|
||||
}
|
||||
|
||||
const Index = ({ index }: IndexProps) => (
|
||||
export const Index = ({ index }: IndexProps) => (
|
||||
<BadgeList badges={index} data-test-subj="indexPropertyValue" />
|
||||
);
|
||||
|
||||
|
@ -125,7 +134,7 @@ interface DataViewIdProps {
|
|||
dataViewId: string;
|
||||
}
|
||||
|
||||
const DataViewId = ({ dataViewId }: DataViewIdProps) => (
|
||||
export const DataViewId = ({ dataViewId }: DataViewIdProps) => (
|
||||
<EuiText size="s" data-test-subj="dataViewIdPropertyValue">
|
||||
{dataViewId}
|
||||
</EuiText>
|
||||
|
@ -135,7 +144,7 @@ interface DataViewIndexPatternProps {
|
|||
dataViewId: string;
|
||||
}
|
||||
|
||||
const DataViewIndexPattern = ({ dataViewId }: DataViewIndexPatternProps) => {
|
||||
export const DataViewIndexPattern = ({ dataViewId }: DataViewIndexPatternProps) => {
|
||||
const { data } = useKibana().services;
|
||||
const [indexPattern, setIndexPattern] = React.useState('');
|
||||
const [hasError, setHasError] = React.useState(false);
|
||||
|
@ -191,18 +200,24 @@ const AnomalyThreshold = ({ anomalyThreshold }: AnomalyThresholdProps) => (
|
|||
);
|
||||
|
||||
interface MachineLearningJobListProps {
|
||||
jobIds: string[];
|
||||
jobIds?: string | string[];
|
||||
isInteractive: boolean;
|
||||
}
|
||||
|
||||
const MachineLearningJobList = ({ jobIds, isInteractive }: MachineLearningJobListProps) => {
|
||||
export const MachineLearningJobList = ({ jobIds, isInteractive }: MachineLearningJobListProps) => {
|
||||
const { jobs } = useSecurityJobs();
|
||||
|
||||
if (isInteractive) {
|
||||
return <MlJobsDescription jobIds={jobIds} />;
|
||||
if (!jobIds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const relevantJobs = jobs.filter((job) => jobIds.includes(job.id));
|
||||
const jobIdsArray = Array.isArray(jobIds) ? jobIds : [jobIds];
|
||||
|
||||
if (isInteractive) {
|
||||
return <MlJobsDescription jobIds={jobIdsArray} />;
|
||||
}
|
||||
|
||||
const relevantJobs = jobs.filter((job) => jobIdsArray.includes(job.id));
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -251,7 +266,7 @@ interface RequiredFieldsProps {
|
|||
requiredFields: RequiredFieldArray;
|
||||
}
|
||||
|
||||
const RequiredFields = ({ requiredFields }: RequiredFieldsProps) => {
|
||||
export const RequiredFields = ({ requiredFields }: RequiredFieldsProps) => {
|
||||
const styles = useRequiredFieldsStyles();
|
||||
|
||||
return (
|
||||
|
@ -293,7 +308,7 @@ interface ThreatIndexProps {
|
|||
threatIndex: string[];
|
||||
}
|
||||
|
||||
const ThreatIndex = ({ threatIndex }: ThreatIndexProps) => (
|
||||
export const ThreatIndex = ({ threatIndex }: ThreatIndexProps) => (
|
||||
<BadgeList badges={threatIndex} data-test-subj="threatIndexPropertyValue" />
|
||||
);
|
||||
|
||||
|
@ -301,7 +316,7 @@ interface ThreatMappingProps {
|
|||
threatMapping: ThreatMappingType;
|
||||
}
|
||||
|
||||
const ThreatMapping = ({ threatMapping }: ThreatMappingProps) => {
|
||||
export const ThreatMapping = ({ threatMapping }: ThreatMappingProps) => {
|
||||
const description = threatMapping.reduce<string>(
|
||||
(accumThreatMaps, threatMap, threatMapIndex, { length: threatMappingLength }) => {
|
||||
const matches = threatMap.entries.reduce<string>(
|
||||
|
@ -434,14 +449,28 @@ const prepareDefinitionSectionListItems = (
|
|||
}
|
||||
|
||||
if (savedQuery) {
|
||||
definitionSectionListItems.push({
|
||||
title: (
|
||||
<span data-test-subj="savedQueryNamePropertyTitle">
|
||||
{descriptionStepI18n.SAVED_QUERY_NAME_LABEL}
|
||||
</span>
|
||||
),
|
||||
description: <SavedQueryName savedQueryName={savedQuery.attributes.title} />,
|
||||
});
|
||||
definitionSectionListItems.push(
|
||||
{
|
||||
title: (
|
||||
<span data-test-subj="savedQueryNamePropertyTitle">
|
||||
{descriptionStepI18n.SAVED_QUERY_NAME_LABEL}
|
||||
</span>
|
||||
),
|
||||
description: <SavedQueryName savedQueryName={savedQuery.attributes.title} />,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<span data-test-subj="savedQueryLanguagePropertyTitle">
|
||||
{i18n.SAVED_QUERY_LANGUAGE_LABEL}
|
||||
</span>
|
||||
),
|
||||
description: (
|
||||
<span data-test-subj="savedQueryLanguagePropertyValue">
|
||||
{getQueryLanguageLabel(savedQuery.attributes.query.language)}
|
||||
</span>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
if (savedQuery.attributes.filters) {
|
||||
definitionSectionListItems.push({
|
||||
|
@ -452,8 +481,10 @@ const prepareDefinitionSectionListItems = (
|
|||
),
|
||||
description: (
|
||||
<Filters
|
||||
filters={savedQuery.attributes.filters as Filter[]}
|
||||
filters={savedQuery.attributes.filters}
|
||||
data-test-subj="savedQueryFiltersPropertyValue"
|
||||
dataViewId={'data_view_id' in rule ? rule.data_view_id : undefined}
|
||||
index={'index' in rule ? rule.index : undefined}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
@ -508,12 +539,26 @@ const prepareDefinitionSectionListItems = (
|
|||
description: <Query query={rule.query} data-test-subj="esqlQueryPropertyValue" />,
|
||||
});
|
||||
} else {
|
||||
definitionSectionListItems.push({
|
||||
title: (
|
||||
<span data-test-subj="customQueryPropertyTitle">{descriptionStepI18n.QUERY_LABEL}</span>
|
||||
),
|
||||
description: <Query query={rule.query} data-test-subj="customQueryPropertyValue" />,
|
||||
});
|
||||
definitionSectionListItems.push(
|
||||
{
|
||||
title: (
|
||||
<span data-test-subj="customQueryPropertyTitle">{descriptionStepI18n.QUERY_LABEL}</span>
|
||||
),
|
||||
description: <Query query={rule.query} data-test-subj="customQueryPropertyValue" />,
|
||||
},
|
||||
{
|
||||
title: (
|
||||
<span data-test-subj="customQueryLanguagePropertyTitle">
|
||||
{i18n.QUERY_LANGUAGE_LABEL}
|
||||
</span>
|
||||
),
|
||||
description: (
|
||||
<span data-test-subj="customQueryLanguagePropertyValue">
|
||||
{getQueryLanguageLabel(rule.language || '')}
|
||||
</span>
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -542,7 +587,7 @@ const prepareDefinitionSectionListItems = (
|
|||
),
|
||||
description: (
|
||||
<MachineLearningJobList
|
||||
jobIds={rule.machine_learning_job_id as string[]}
|
||||
jobIds={rule.machine_learning_job_id}
|
||||
isInteractive={isInteractive}
|
||||
/>
|
||||
),
|
||||
|
@ -633,6 +678,21 @@ const prepareDefinitionSectionListItems = (
|
|||
});
|
||||
}
|
||||
|
||||
if ('threat_language' in rule && rule.threat_language) {
|
||||
definitionSectionListItems.push({
|
||||
title: (
|
||||
<span data-test-subj="threatQueryLanguagePropertyTitle">
|
||||
{i18n.THREAT_QUERY_LANGUAGE_LABEL}
|
||||
</span>
|
||||
),
|
||||
description: (
|
||||
<span data-test-subj="threatQueryLanguagePropertyValue">
|
||||
{getQueryLanguageLabel(rule.threat_language)}
|
||||
</span>
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
if ('new_terms_fields' in rule && rule.new_terms_fields && rule.new_terms_fields.length > 0) {
|
||||
definitionSectionListItems.push({
|
||||
title: (
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 type { DiffableAllFields } from '../../../../../../../common/api/detection_engine';
|
||||
import { KqlQueryReadOnly } from './fields/kql_query';
|
||||
import { DataSourceReadOnly } from './fields/data_source/data_source';
|
||||
import { EqlQueryReadOnly } from './fields/eql_query/eql_query';
|
||||
import { EsqlQueryReadOnly } from './fields/esql_query/esql_query';
|
||||
import { MachineLearningJobIdReadOnly } from './fields/machine_learning_job_id/machine_learning_job_id';
|
||||
import { RelatedIntegrationsReadOnly } from './fields/related_integrations/related_integrations';
|
||||
import { RequiredFieldsReadOnly } from './fields/required_fields/required_fields';
|
||||
import { SeverityMappingReadOnly } from './fields/severity_mapping/severity_mapping';
|
||||
import { RiskScoreMappingReadOnly } from './fields/risk_score_mapping/risk_score_mapping';
|
||||
import { ThreatMappingReadOnly } from './fields/threat_mapping/threat_mapping';
|
||||
import { ThreatReadOnly } from './fields/threat/threat';
|
||||
import { ThreatIndexReadOnly } from './fields/threat_index/threat_index';
|
||||
import { ThreatIndicatorPathReadOnly } from './fields/threat_indicator_path/threat_indicator_path';
|
||||
import { ThreatQueryReadOnly } from './fields/threat_query/threat_query';
|
||||
|
||||
interface FieldReadOnlyProps {
|
||||
fieldName: keyof DiffableAllFields;
|
||||
finalDiffableRule: DiffableAllFields;
|
||||
}
|
||||
|
||||
export function FieldReadOnly({ fieldName, finalDiffableRule }: FieldReadOnlyProps) {
|
||||
switch (fieldName) {
|
||||
case 'data_source':
|
||||
return <DataSourceReadOnly dataSource={finalDiffableRule.data_source} />;
|
||||
case 'eql_query':
|
||||
return (
|
||||
<EqlQueryReadOnly
|
||||
eqlQuery={finalDiffableRule.eql_query}
|
||||
dataSource={finalDiffableRule.data_source}
|
||||
/>
|
||||
);
|
||||
case 'esql_query':
|
||||
return <EsqlQueryReadOnly esqlQuery={finalDiffableRule.esql_query} />;
|
||||
case 'kql_query':
|
||||
return (
|
||||
<KqlQueryReadOnly
|
||||
kqlQuery={finalDiffableRule.kql_query}
|
||||
dataSource={finalDiffableRule.data_source}
|
||||
ruleType={finalDiffableRule.type}
|
||||
/>
|
||||
);
|
||||
case 'machine_learning_job_id':
|
||||
return (
|
||||
<MachineLearningJobIdReadOnly
|
||||
machineLearningJobId={finalDiffableRule.machine_learning_job_id}
|
||||
/>
|
||||
);
|
||||
case 'related_integrations':
|
||||
return (
|
||||
<RelatedIntegrationsReadOnly relatedIntegrations={finalDiffableRule.related_integrations} />
|
||||
);
|
||||
case 'required_fields':
|
||||
return <RequiredFieldsReadOnly requiredFields={finalDiffableRule.required_fields} />;
|
||||
case 'risk_score_mapping':
|
||||
return <RiskScoreMappingReadOnly riskScoreMapping={finalDiffableRule.risk_score_mapping} />;
|
||||
case 'severity_mapping':
|
||||
return <SeverityMappingReadOnly severityMapping={finalDiffableRule.severity_mapping} />;
|
||||
case 'threat':
|
||||
return <ThreatReadOnly threat={finalDiffableRule.threat} />;
|
||||
case 'threat_index':
|
||||
return <ThreatIndexReadOnly threatIndex={finalDiffableRule.threat_index} />;
|
||||
case 'threat_indicator_path':
|
||||
return (
|
||||
<ThreatIndicatorPathReadOnly
|
||||
threatIndicatorPath={finalDiffableRule.threat_indicator_path}
|
||||
/>
|
||||
);
|
||||
case 'threat_mapping':
|
||||
return <ThreatMappingReadOnly threatMapping={finalDiffableRule.threat_mapping} />;
|
||||
case 'threat_query':
|
||||
return (
|
||||
<ThreatQueryReadOnly
|
||||
threatQuery={finalDiffableRule.threat_query}
|
||||
dataSource={finalDiffableRule.data_source}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
|
||||
import {
|
||||
dataSourceWithDataView,
|
||||
dataSourceWithIndexPatterns,
|
||||
mockDataView,
|
||||
} from '../../storybook/mocks';
|
||||
|
||||
export default {
|
||||
component: FieldReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/data_source',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
kibanaServicesMock?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<ThreeWayDiffStorybookProviders kibanaServicesMock={args.kibanaServicesMock}>
|
||||
<FieldReadOnly
|
||||
fieldName="data_source"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
</ThreeWayDiffStorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const DataSourceWithIndexPatterns = Template.bind({});
|
||||
|
||||
DataSourceWithIndexPatterns.args = {
|
||||
finalDiffableRule: {
|
||||
data_source: dataSourceWithIndexPatterns,
|
||||
},
|
||||
};
|
||||
|
||||
export const DataSourceWithDataView = Template.bind({});
|
||||
|
||||
DataSourceWithDataView.args = {
|
||||
finalDiffableRule: {
|
||||
data_source: dataSourceWithDataView,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
get: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import { DataSourceType } from '../../../../../../../../../common/api/detection_engine';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { Index, DataViewId, DataViewIndexPattern } from '../../../../rule_definition_section';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { assertUnreachable } from '../../../../../../../../../common/utility_types';
|
||||
|
||||
interface DataSourceReadOnlyProps {
|
||||
dataSource: DiffableAllFields['data_source'];
|
||||
}
|
||||
|
||||
export function DataSourceReadOnly({ dataSource }: DataSourceReadOnlyProps) {
|
||||
if (!dataSource) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (dataSource.type === DataSourceType.index_patterns) {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.INDEX_FIELD_LABEL,
|
||||
description: <Index index={dataSource.index_patterns} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (dataSource.type === DataSourceType.data_view) {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.DATA_VIEW_ID_FIELD_LABEL,
|
||||
description: <DataViewId dataViewId={dataSource.data_view_id} />,
|
||||
},
|
||||
{
|
||||
title: ruleDetailsI18n.DATA_VIEW_INDEX_PATTERN_FIELD_LABEL,
|
||||
description: <DataViewIndexPattern dataViewId={dataSource.data_view_id} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return assertUnreachable(dataSource);
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
|
||||
import { EqlQueryReadOnly } from './eql_query';
|
||||
import {
|
||||
dataSourceWithDataView,
|
||||
dataSourceWithIndexPatterns,
|
||||
eqlQuery,
|
||||
mockDataView,
|
||||
} from '../../storybook/mocks';
|
||||
|
||||
export default {
|
||||
component: EqlQueryReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/eql_query',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
kibanaServicesMock?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<ThreeWayDiffStorybookProviders kibanaServicesMock={args.kibanaServicesMock}>
|
||||
<FieldReadOnly
|
||||
fieldName="eql_query"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
</ThreeWayDiffStorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const EqlQueryWithIndexPatterns = Template.bind({});
|
||||
|
||||
EqlQueryWithIndexPatterns.args = {
|
||||
finalDiffableRule: {
|
||||
eql_query: eqlQuery,
|
||||
data_source: dataSourceWithIndexPatterns,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
create: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const EqlQueryWithDataView = Template.bind({});
|
||||
|
||||
EqlQueryWithDataView.args = {
|
||||
finalDiffableRule: {
|
||||
eql_query: eqlQuery,
|
||||
data_source: dataSourceWithDataView,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
get: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { EuiDescriptionListProps } from '@elastic/eui';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations';
|
||||
import { Query, Filters } from '../../../../rule_definition_section';
|
||||
import { getDataSourceProps, typeCheckFilters } from '../../../../helpers';
|
||||
|
||||
interface EqlQueryReadOnlyProps {
|
||||
eqlQuery: DiffableAllFields['eql_query'];
|
||||
dataSource: DiffableAllFields['data_source'];
|
||||
}
|
||||
|
||||
export function EqlQueryReadOnly({ eqlQuery, dataSource }: EqlQueryReadOnlyProps) {
|
||||
const listItems: EuiDescriptionListProps['listItems'] = [
|
||||
{
|
||||
title: descriptionStepI18n.EQL_QUERY_LABEL,
|
||||
description: <Query query={eqlQuery.query} />,
|
||||
},
|
||||
];
|
||||
|
||||
const filters = typeCheckFilters(eqlQuery.filters);
|
||||
|
||||
if (filters.length > 0) {
|
||||
const dataSourceProps = getDataSourceProps(dataSource);
|
||||
|
||||
listItems.push({
|
||||
title: descriptionStepI18n.FILTERS_LABEL,
|
||||
description: <Filters filters={filters} {...dataSourceProps} />,
|
||||
});
|
||||
}
|
||||
|
||||
return <EuiDescriptionList listItems={listItems} />;
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
|
||||
export default {
|
||||
component: FieldReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/esql_query',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="esql_query"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
esql_query: {
|
||||
query: `SELECT user.name, source.ip FROM "logs-*" WHERE event.action = 'user_login' AND event.outcome = 'failure'`,
|
||||
language: 'esql',
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations';
|
||||
import { Query } from '../../../../rule_definition_section';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
|
||||
interface EsqlQueryReadonlyProps {
|
||||
esqlQuery: DiffableAllFields['esql_query'];
|
||||
}
|
||||
|
||||
export function EsqlQueryReadOnly({ esqlQuery }: EsqlQueryReadonlyProps) {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: descriptionStepI18n.ESQL_QUERY_LABEL,
|
||||
description: <Query query={esqlQuery.query} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 { KqlQueryType } from '../../../../../../../../../common/api/detection_engine';
|
||||
import type {
|
||||
DiffableAllFields,
|
||||
RuleKqlQuery,
|
||||
} from '../../../../../../../../../common/api/detection_engine';
|
||||
import { InlineKqlQueryReadOnly } from './inline_kql_query';
|
||||
import { SavedKqlQueryReadOnly } from './saved_kql_query';
|
||||
import { assertUnreachable } from '../../../../../../../../../common/utility_types';
|
||||
|
||||
interface KqlQueryReadOnlyProps {
|
||||
kqlQuery: RuleKqlQuery;
|
||||
dataSource: DiffableAllFields['data_source'];
|
||||
ruleType: DiffableAllFields['type'];
|
||||
}
|
||||
|
||||
export function KqlQueryReadOnly({ kqlQuery, dataSource, ruleType }: KqlQueryReadOnlyProps) {
|
||||
if (kqlQuery.type === KqlQueryType.inline_query) {
|
||||
return <InlineKqlQueryReadOnly kqlQuery={kqlQuery} dataSource={dataSource} />;
|
||||
}
|
||||
|
||||
if (kqlQuery.type === KqlQueryType.saved_query) {
|
||||
return (
|
||||
<SavedKqlQueryReadOnly kqlQuery={kqlQuery} dataSource={dataSource} ruleType={ruleType} />
|
||||
);
|
||||
}
|
||||
|
||||
return assertUnreachable(kqlQuery);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { EuiDescriptionListProps } from '@elastic/eui';
|
||||
import type {
|
||||
DiffableAllFields,
|
||||
InlineKqlQuery,
|
||||
} from '../../../../../../../../../common/api/detection_engine';
|
||||
import { Query, Filters } from '../../../../rule_definition_section';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations';
|
||||
import { getDataSourceProps, getQueryLanguageLabel, typeCheckFilters } from '../../../../helpers';
|
||||
|
||||
const defaultI18nLabels = {
|
||||
query: descriptionStepI18n.QUERY_LABEL,
|
||||
language: ruleDetailsI18n.QUERY_LANGUAGE_LABEL,
|
||||
filters: descriptionStepI18n.FILTERS_LABEL,
|
||||
};
|
||||
|
||||
interface InlineQueryProps {
|
||||
kqlQuery: InlineKqlQuery;
|
||||
dataSource?: DiffableAllFields['data_source'];
|
||||
i18nLabels?: {
|
||||
query: string;
|
||||
language: string;
|
||||
filters: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function InlineKqlQueryReadOnly({
|
||||
kqlQuery,
|
||||
dataSource,
|
||||
i18nLabels = defaultI18nLabels,
|
||||
}: InlineQueryProps) {
|
||||
const listItems: EuiDescriptionListProps['listItems'] = [
|
||||
{
|
||||
title: i18nLabels.query,
|
||||
description: <Query query={kqlQuery.query} />,
|
||||
},
|
||||
{
|
||||
title: i18nLabels.language,
|
||||
description: getQueryLanguageLabel(kqlQuery.language),
|
||||
},
|
||||
];
|
||||
|
||||
const filters = typeCheckFilters(kqlQuery.filters);
|
||||
|
||||
if (filters.length > 0) {
|
||||
const dataSourceProps = getDataSourceProps(dataSource);
|
||||
|
||||
listItems.push({
|
||||
title: i18nLabels.filters,
|
||||
description: <Filters filters={filters} {...dataSourceProps} />,
|
||||
});
|
||||
}
|
||||
|
||||
return <EuiDescriptionList listItems={listItems} />;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type {
|
||||
DiffableAllFields,
|
||||
RuleKqlQuery,
|
||||
} from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
|
||||
import {
|
||||
dataSourceWithDataView,
|
||||
dataSourceWithIndexPatterns,
|
||||
inlineKqlQuery,
|
||||
mockDataView,
|
||||
savedKqlQuery,
|
||||
savedQueryResponse,
|
||||
} from '../../storybook/mocks';
|
||||
|
||||
export default {
|
||||
component: FieldReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/kql_query',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields> | { kql_query: RuleKqlQuery };
|
||||
kibanaServicesMock?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<ThreeWayDiffStorybookProviders kibanaServicesMock={args.kibanaServicesMock}>
|
||||
<FieldReadOnly
|
||||
fieldName="kql_query"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
</ThreeWayDiffStorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const InlineKqlQueryWithIndexPatterns = Template.bind({});
|
||||
|
||||
InlineKqlQueryWithIndexPatterns.args = {
|
||||
finalDiffableRule: {
|
||||
kql_query: inlineKqlQuery,
|
||||
data_source: dataSourceWithIndexPatterns,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
create: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const InlineKqlQueryWithDataView = Template.bind({});
|
||||
|
||||
InlineKqlQueryWithDataView.args = {
|
||||
finalDiffableRule: {
|
||||
kql_query: inlineKqlQuery,
|
||||
data_source: dataSourceWithDataView,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
get: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const InlineKqlQueryWithoutDataSource = Template.bind({});
|
||||
|
||||
/*
|
||||
Filters should still be displayed if no `data_source` is provided.
|
||||
Component would fall back to the default index pattern in such case.
|
||||
*/
|
||||
InlineKqlQueryWithoutDataSource.args = {
|
||||
finalDiffableRule: {
|
||||
kql_query: inlineKqlQuery,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
create: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SavedKqlQueryWithIndexPatterns = Template.bind({});
|
||||
|
||||
SavedKqlQueryWithIndexPatterns.args = {
|
||||
finalDiffableRule: {
|
||||
kql_query: savedKqlQuery,
|
||||
data_source: dataSourceWithIndexPatterns,
|
||||
type: 'saved_query',
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
create: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
http: {
|
||||
get: async () => savedQueryResponse,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const SavedKqlQueryWithDataView = Template.bind({});
|
||||
|
||||
SavedKqlQueryWithDataView.args = {
|
||||
finalDiffableRule: {
|
||||
kql_query: savedKqlQuery,
|
||||
data_source: dataSourceWithDataView,
|
||||
type: 'saved_query',
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
get: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
http: {
|
||||
get: async () => savedQueryResponse,
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { EuiDescriptionListProps } from '@elastic/eui';
|
||||
import type {
|
||||
SavedKqlQuery,
|
||||
DiffableRule,
|
||||
DiffableAllFields,
|
||||
} from '../../../../../../../../../common/api/detection_engine';
|
||||
import { Query, SavedQueryName, Filters } from '../../../../rule_definition_section';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations';
|
||||
import { useGetSavedQuery } from '../../../../../../../../detections/pages/detection_engine/rules/use_get_saved_query';
|
||||
import { getDataSourceProps, getQueryLanguageLabel } from '../../../../helpers';
|
||||
|
||||
interface SavedQueryProps {
|
||||
kqlQuery: SavedKqlQuery;
|
||||
dataSource?: DiffableAllFields['data_source'];
|
||||
ruleType: DiffableRule['type'];
|
||||
}
|
||||
|
||||
export function SavedKqlQueryReadOnly({ kqlQuery, dataSource, ruleType }: SavedQueryProps) {
|
||||
const { savedQuery } = useGetSavedQuery({
|
||||
savedQueryId: kqlQuery.saved_query_id,
|
||||
ruleType,
|
||||
});
|
||||
|
||||
if (!savedQuery) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const listItems: EuiDescriptionListProps['listItems'] = [
|
||||
{
|
||||
title: descriptionStepI18n.SAVED_QUERY_NAME_LABEL,
|
||||
description: <SavedQueryName savedQueryName={savedQuery.attributes.title} />,
|
||||
},
|
||||
{
|
||||
title: ruleDetailsI18n.SAVED_QUERY_LANGUAGE_LABEL,
|
||||
description: getQueryLanguageLabel(savedQuery.attributes.query.language),
|
||||
},
|
||||
];
|
||||
|
||||
if (typeof savedQuery.attributes.query.query === 'string') {
|
||||
listItems.push({
|
||||
title: descriptionStepI18n.SAVED_QUERY_LABEL,
|
||||
description: <Query query={savedQuery.attributes.query.query} />,
|
||||
});
|
||||
}
|
||||
|
||||
if (savedQuery.attributes.filters) {
|
||||
const dataSourceProps = getDataSourceProps(dataSource);
|
||||
|
||||
listItems.push({
|
||||
title: descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL,
|
||||
description: <Filters filters={savedQuery.attributes.filters} {...dataSourceProps} />,
|
||||
});
|
||||
}
|
||||
|
||||
return <EuiDescriptionList listItems={listItems} />;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 { useQueryClient } from '@tanstack/react-query';
|
||||
import type { Story } from '@storybook/react';
|
||||
import { MachineLearningJobIdReadOnly } from './machine_learning_job_id';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
|
||||
import { GET_MODULES_QUERY_KEY } from '../../../../../../../../common/components/ml_popover/hooks/use_fetch_modules_query';
|
||||
import { GET_RECOGNIZER_QUERY_KEY } from '../../../../../../../../common/components/ml_popover/hooks/use_fetch_recognizer_query';
|
||||
import { GET_JOBS_SUMMARY_QUERY_KEY } from '../../../../../../../../common/components/ml/hooks/use_fetch_jobs_summary_query';
|
||||
|
||||
export default {
|
||||
component: MachineLearningJobIdReadOnly,
|
||||
title:
|
||||
'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/machine_learning_job_id',
|
||||
};
|
||||
|
||||
const mockedModulesData = [
|
||||
{
|
||||
id: 'security_auth',
|
||||
jobs: [
|
||||
{
|
||||
id: 'auth_high_count_logon_events',
|
||||
config: {
|
||||
groups: [],
|
||||
custom_settings: {
|
||||
security_app_display_name: 'Spike in Logon Events',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const mockedCompatibleModules = [
|
||||
{
|
||||
id: 'security_auth',
|
||||
},
|
||||
];
|
||||
|
||||
function MockMlData({ children }: { children: React.ReactNode }) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
queryClient.setQueryData([GET_JOBS_SUMMARY_QUERY_KEY, {}], []);
|
||||
|
||||
queryClient.setQueryData([GET_MODULES_QUERY_KEY, {}], mockedModulesData);
|
||||
|
||||
queryClient.setQueryData([GET_RECOGNIZER_QUERY_KEY, {}], mockedCompatibleModules);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<ThreeWayDiffStorybookProviders>
|
||||
<MockMlData>
|
||||
<FieldReadOnly
|
||||
fieldName="machine_learning_job_id"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
</MockMlData>
|
||||
</ThreeWayDiffStorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
machine_learning_job_id: 'auth_high_count_logon_events',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import { MachineLearningJobList } from '../../../../rule_definition_section';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
|
||||
interface MachineLearningJobIdProps {
|
||||
machineLearningJobId: DiffableAllFields['machine_learning_job_id'];
|
||||
}
|
||||
|
||||
export function MachineLearningJobIdReadOnly({ machineLearningJobId }: MachineLearningJobIdProps) {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.MACHINE_LEARNING_JOB_ID_FIELD_LABEL,
|
||||
description: (
|
||||
<MachineLearningJobList jobIds={machineLearningJobId} isInteractive={false} />
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { useQueryClient } from '@tanstack/react-query';
|
||||
import type { Story } from '@storybook/react';
|
||||
import { RelatedIntegrationsReadOnly } from './related_integrations';
|
||||
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
|
||||
export default {
|
||||
component: RelatedIntegrationsReadOnly,
|
||||
title:
|
||||
'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/related_integrations',
|
||||
};
|
||||
|
||||
const mockedIntegrationsData = [
|
||||
{
|
||||
package_name: 'endpoint',
|
||||
package_title: 'Elastic Defend',
|
||||
latest_package_version: '8.15.1',
|
||||
installed_package_version: '8.16.0-prerelease.1',
|
||||
is_installed: true,
|
||||
is_enabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
function MockRelatedIntegrationsData({ children }: { children: React.ReactNode }) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
queryClient.setQueryData(['integrations'], mockedIntegrationsData);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<ThreeWayDiffStorybookProviders>
|
||||
<MockRelatedIntegrationsData>
|
||||
<FieldReadOnly
|
||||
fieldName="related_integrations"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
</MockRelatedIntegrationsData>
|
||||
</ThreeWayDiffStorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
related_integrations: [{ package: 'endpoint', version: '^8.2.0' }],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { RelatedIntegrationsDescription } from '../../../../../../../../detections/components/rules/related_integrations/integrations_description';
|
||||
import type { RelatedIntegrationArray } from '../../../../../../../../../common/api/detection_engine';
|
||||
|
||||
interface RelatedIntegrationsReadOnly {
|
||||
relatedIntegrations: RelatedIntegrationArray;
|
||||
}
|
||||
|
||||
export function RelatedIntegrationsReadOnly({ relatedIntegrations }: RelatedIntegrationsReadOnly) {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.RELATED_INTEGRATIONS_FIELD_LABEL,
|
||||
description: <RelatedIntegrationsDescription relatedIntegrations={relatedIntegrations} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { RequiredFieldsReadOnly } from './required_fields';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
|
||||
export default {
|
||||
component: RequiredFieldsReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/required_fields',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="required_fields"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
required_fields: [
|
||||
{ name: 'event.kind', type: 'keyword', ecs: true },
|
||||
{ name: 'event.module', type: 'keyword', ecs: true },
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import type { RequiredFieldArray } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { RequiredFields } from '../../../../rule_definition_section';
|
||||
|
||||
interface RequiredFieldsReadOnlyProps {
|
||||
requiredFields: RequiredFieldArray;
|
||||
}
|
||||
|
||||
export function RequiredFieldsReadOnly({ requiredFields }: RequiredFieldsReadOnlyProps) {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.REQUIRED_FIELDS_FIELD_LABEL,
|
||||
description: <RequiredFields requiredFields={requiredFields} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { RiskScoreMappingReadOnly } from './risk_score_mapping';
|
||||
|
||||
export default {
|
||||
component: RiskScoreMappingReadOnly,
|
||||
title:
|
||||
'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/risk_score_mapping',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="risk_score_mapping"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
risk_score_mapping: [{ field: 'event.risk_score', operator: 'equals', value: '' }],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* 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 { requiredOptional } from '@kbn/zod-helpers';
|
||||
import { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { RiskScoreMapping } from '../../../../../../../../../common/api/detection_engine';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { RiskScoreMappingItem } from '../../../../rule_about_section';
|
||||
|
||||
export interface RiskScoreMappingReadProps {
|
||||
riskScoreMapping: RiskScoreMapping;
|
||||
}
|
||||
|
||||
export const RiskScoreMappingReadOnly = ({ riskScoreMapping }: RiskScoreMappingReadProps) => {
|
||||
const listItems = riskScoreMapping
|
||||
.filter((riskScoreMappingItem) => riskScoreMappingItem.field !== '')
|
||||
.map((riskScoreMappingItem, index) => ({
|
||||
title: index === 0 ? ruleDetailsI18n.RISK_SCORE_MAPPING_FIELD_LABEL : '',
|
||||
description: (
|
||||
<RiskScoreMappingItem riskScoreMappingItem={requiredOptional(riskScoreMappingItem)} />
|
||||
),
|
||||
}));
|
||||
|
||||
return <EuiDescriptionList listItems={listItems} />;
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { SeverityMappingReadOnly } from './severity_mapping';
|
||||
|
||||
export default {
|
||||
component: SeverityMappingReadOnly,
|
||||
title:
|
||||
'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/severity_mapping',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="severity_mapping"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
severity_mapping: [
|
||||
{
|
||||
field: 'event.severity',
|
||||
operator: 'equals',
|
||||
severity: 'low',
|
||||
value: 'LOW',
|
||||
},
|
||||
{
|
||||
field: 'google_workspace.alert.metadata.severity',
|
||||
operator: 'equals',
|
||||
severity: 'high',
|
||||
value: 'VERY HIGH',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { SeverityMapping } from '../../../../../../../../../common/api/detection_engine';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { SeverityMappingItem } from '../../../../rule_about_section';
|
||||
|
||||
export interface SeverityMappingReadOnlyProps {
|
||||
severityMapping: SeverityMapping;
|
||||
}
|
||||
|
||||
export const SeverityMappingReadOnly = ({ severityMapping }: SeverityMappingReadOnlyProps) => {
|
||||
const listItems = severityMapping
|
||||
.filter((severityMappingItem) => severityMappingItem.field !== '')
|
||||
.map((severityMappingItem, index) => ({
|
||||
title: index === 0 ? ruleDetailsI18n.SEVERITY_MAPPING_FIELD_LABEL : '',
|
||||
description: <SeverityMappingItem severityMappingItem={severityMappingItem} />,
|
||||
}));
|
||||
|
||||
return <EuiDescriptionList listItems={listItems} />;
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreatReadOnly } from './threat';
|
||||
|
||||
export default {
|
||||
component: ThreatReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/threat',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="threat"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
threat: [
|
||||
{
|
||||
framework: 'MITRE ATT&CK',
|
||||
tactic: {
|
||||
id: 'TA0006',
|
||||
name: 'Credential Access',
|
||||
reference: 'https://attack.mitre.org/tactics/TA0006/',
|
||||
},
|
||||
technique: [
|
||||
{
|
||||
id: 'T1003',
|
||||
name: 'OS Credential Dumping',
|
||||
reference: 'https://attack.mitre.org/techniques/T1003/',
|
||||
subtechnique: [
|
||||
{
|
||||
id: 'T1003.001',
|
||||
name: 'LSASS Memory',
|
||||
reference: 'https://attack.mitre.org/techniques/T1003/001/',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { Threats } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { Threat } from '../../../../rule_about_section';
|
||||
|
||||
export interface ThreatReadOnlyProps {
|
||||
threat: Threats;
|
||||
}
|
||||
|
||||
export const ThreatReadOnly = ({ threat }: ThreatReadOnlyProps) => {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.THREAT_FIELD_LABEL,
|
||||
description: <Threat threat={threat} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreatIndexReadOnly } from './threat_index';
|
||||
|
||||
export default {
|
||||
component: ThreatIndexReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/threat_index',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="threat_index"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
threat_index: ['logs-ti_*', 'logs-defend_*'],
|
||||
},
|
||||
};
|
|
@ -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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { ThreatIndex } from '../../../../rule_definition_section';
|
||||
|
||||
export interface ThreatIndexReadOnlyProps {
|
||||
threatIndex: string[];
|
||||
}
|
||||
|
||||
export const ThreatIndexReadOnly = ({ threatIndex }: ThreatIndexReadOnlyProps) => {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.THREAT_INDEX_FIELD_LABEL,
|
||||
description: <ThreatIndex threatIndex={threatIndex} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreatIndicatorPathReadOnly } from './threat_indicator_path';
|
||||
|
||||
export default {
|
||||
component: ThreatIndicatorPathReadOnly,
|
||||
title:
|
||||
'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/threat_indicator_path',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="threat_indicator_path"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
threat_indicator_path: 'threat.indicator',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import { ThreatIndicatorPath } from '../../../../rule_about_section';
|
||||
|
||||
export interface ThreatIndicatorPathReadOnlyProps {
|
||||
threatIndicatorPath?: string;
|
||||
}
|
||||
|
||||
export const ThreatIndicatorPathReadOnly = ({
|
||||
threatIndicatorPath,
|
||||
}: ThreatIndicatorPathReadOnlyProps) => {
|
||||
if (!threatIndicatorPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.THREAT_INDEX_FIELD_LABEL,
|
||||
description: <ThreatIndicatorPath threatIndicatorPath={threatIndicatorPath} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreatMappingReadOnly } from './threat_mapping';
|
||||
|
||||
export default {
|
||||
component: ThreatMappingReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/threat_mapping',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<FieldReadOnly
|
||||
fieldName="threat_mapping"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
finalDiffableRule: {
|
||||
threat_mapping: [
|
||||
{
|
||||
entries: [
|
||||
{
|
||||
field: 'Endpoint.capabilities',
|
||||
type: 'mapping',
|
||||
value: 'Target.dll.pe.description',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { EuiDescriptionList } from '@elastic/eui';
|
||||
import type { ThreatMapping } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreatMapping as ThreatMappingComponent } from '../../../../rule_definition_section';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
|
||||
export interface ThreatMappingReadOnlyProps {
|
||||
threatMapping: ThreatMapping;
|
||||
}
|
||||
|
||||
export const ThreatMappingReadOnly = ({ threatMapping }: ThreatMappingReadOnlyProps) => {
|
||||
return (
|
||||
<EuiDescriptionList
|
||||
listItems={[
|
||||
{
|
||||
title: ruleDetailsI18n.THREAT_MAPPING_FIELD_LABEL,
|
||||
description: <ThreatMappingComponent threatMapping={threatMapping} />,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 type { Story } from '@storybook/react';
|
||||
import { FieldReadOnly } from '../../field_readonly';
|
||||
import type { DiffableAllFields } from '../../../../../../../../../common/api/detection_engine';
|
||||
import { ThreatQueryReadOnly } from './threat_query';
|
||||
import {
|
||||
dataSourceWithDataView,
|
||||
dataSourceWithIndexPatterns,
|
||||
inlineKqlQuery,
|
||||
mockDataView,
|
||||
} from '../../storybook/mocks';
|
||||
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
|
||||
|
||||
export default {
|
||||
component: ThreatQueryReadOnly,
|
||||
title: 'Rule Management/Prebuilt Rules/Upgrade Flyout/ThreeWayDiff/FieldReadOnly/threat_query',
|
||||
};
|
||||
|
||||
interface TemplateProps {
|
||||
finalDiffableRule: Partial<DiffableAllFields>;
|
||||
kibanaServicesMock?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const Template: Story<TemplateProps> = (args) => {
|
||||
return (
|
||||
<ThreeWayDiffStorybookProviders kibanaServicesMock={args.kibanaServicesMock}>
|
||||
<FieldReadOnly
|
||||
fieldName="threat_query"
|
||||
finalDiffableRule={args.finalDiffableRule as DiffableAllFields}
|
||||
/>
|
||||
</ThreeWayDiffStorybookProviders>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThreatQueryWithIndexPatterns = Template.bind({});
|
||||
|
||||
ThreatQueryWithIndexPatterns.args = {
|
||||
finalDiffableRule: {
|
||||
threat_query: inlineKqlQuery,
|
||||
data_source: dataSourceWithIndexPatterns,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
create: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ThreatQueryWithDataView = Template.bind({});
|
||||
|
||||
ThreatQueryWithDataView.args = {
|
||||
finalDiffableRule: {
|
||||
threat_query: inlineKqlQuery,
|
||||
data_source: dataSourceWithDataView,
|
||||
},
|
||||
kibanaServicesMock: {
|
||||
data: {
|
||||
dataViews: {
|
||||
get: async () => mockDataView(),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 type {
|
||||
DiffableAllFields,
|
||||
InlineKqlQuery,
|
||||
} from '../../../../../../../../../common/api/detection_engine';
|
||||
import * as ruleDetailsI18n from '../../../../translations';
|
||||
import * as descriptionStepI18n from '../../../../../../../rule_creation_ui/components/description_step/translations';
|
||||
import { InlineKqlQueryReadOnly } from '../kql_query/inline_kql_query';
|
||||
|
||||
const i18nLabels = {
|
||||
query: descriptionStepI18n.THREAT_QUERY_LABEL,
|
||||
language: ruleDetailsI18n.THREAT_QUERY_LANGUAGE_LABEL,
|
||||
filters: ruleDetailsI18n.THREAT_FILTERS_FIELD_LABEL,
|
||||
};
|
||||
|
||||
export interface ThreatQueryReadOnlyProps {
|
||||
threatQuery: InlineKqlQuery;
|
||||
dataSource: DiffableAllFields['data_source'];
|
||||
}
|
||||
|
||||
export const ThreatQueryReadOnly = ({ threatQuery, dataSource }: ThreatQueryReadOnlyProps) => {
|
||||
return (
|
||||
<InlineKqlQueryReadOnly
|
||||
kqlQuery={threatQuery}
|
||||
dataSource={dataSource}
|
||||
i18nLabels={i18nLabels}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { FieldFormatsStartCommon } from '@kbn/field-formats-plugin/common';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { DataSourceType, KqlQueryType } from '../../../../../../../../common/api/detection_engine';
|
||||
import type {
|
||||
DiffableAllFields,
|
||||
SavedKqlQuery,
|
||||
} from '../../../../../../../../common/api/detection_engine';
|
||||
|
||||
export const filters = [
|
||||
{
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
index: 'logs-*',
|
||||
key: '@timestamp',
|
||||
field: '@timestamp',
|
||||
value: 'exists',
|
||||
type: 'exists',
|
||||
},
|
||||
query: {
|
||||
exists: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const savedQueryResponse = {
|
||||
id: 'fake-saved-query-id',
|
||||
attributes: {
|
||||
title: 'Fake Saved Query',
|
||||
description: '',
|
||||
query: {
|
||||
query: 'file.path: "/etc/passwd" and event.action: "modification"',
|
||||
language: 'kuery',
|
||||
},
|
||||
filters,
|
||||
},
|
||||
namespaces: ['default'],
|
||||
};
|
||||
|
||||
export const inlineKqlQuery: DiffableAllFields['kql_query'] = {
|
||||
type: KqlQueryType.inline_query,
|
||||
query: 'event.action: "user_login" and source.ip: "192.168.1.100"',
|
||||
language: 'kuery',
|
||||
filters,
|
||||
};
|
||||
|
||||
export const savedKqlQuery: SavedKqlQuery = {
|
||||
type: KqlQueryType.saved_query,
|
||||
saved_query_id: 'fake-saved-query-id',
|
||||
};
|
||||
|
||||
export const eqlQuery: DiffableAllFields['eql_query'] = {
|
||||
query: 'process where process.name == "powershell.exe" and process.args : "* -EncodedCommand *"',
|
||||
language: 'eql',
|
||||
filters,
|
||||
};
|
||||
|
||||
export const dataSourceWithIndexPatterns: DiffableAllFields['data_source'] = {
|
||||
type: DataSourceType.index_patterns,
|
||||
index_patterns: ['logs-*'],
|
||||
};
|
||||
|
||||
export const dataSourceWithDataView: DiffableAllFields['data_source'] = {
|
||||
type: DataSourceType.data_view,
|
||||
data_view_id: 'logs-*',
|
||||
};
|
||||
|
||||
type DataViewDeps = ConstructorParameters<typeof DataView>[0];
|
||||
|
||||
export function mockDataView(spec: Partial<DataViewDeps['spec']> = {}): DataView {
|
||||
const dataView = new DataView({
|
||||
spec: {
|
||||
title: 'logs-*',
|
||||
fields: {
|
||||
'@timestamp': {
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
searchable: true,
|
||||
aggregatable: true,
|
||||
},
|
||||
},
|
||||
...spec,
|
||||
},
|
||||
fieldFormats: {
|
||||
getDefaultInstance: () => ({
|
||||
toJSON: () => ({}),
|
||||
}),
|
||||
} as unknown as FieldFormatsStartCommon,
|
||||
});
|
||||
|
||||
return dataView;
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 { configureStore } from '@reduxjs/toolkit';
|
||||
import { merge } from 'lodash';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
|
||||
import { ReactQueryClientProvider } from '../../../../../../../common/containers/query_client/query_client_provider';
|
||||
|
||||
function createKibanaServicesMock(overrides?: Partial<CoreStart>) {
|
||||
const baseMock = {
|
||||
data: {
|
||||
dataViews: {
|
||||
create: async () => {},
|
||||
get: async () => {},
|
||||
},
|
||||
},
|
||||
http: {
|
||||
get: async () => {},
|
||||
basePath: {
|
||||
get: () => '',
|
||||
},
|
||||
},
|
||||
notifications: {
|
||||
toasts: {
|
||||
addError: () => {},
|
||||
addSuccess: () => {},
|
||||
addWarning: () => {},
|
||||
remove: () => {},
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
client: {
|
||||
get: (key: string, defaultOverride?: unknown) => defaultOverride,
|
||||
get$: () => new Subject(),
|
||||
set: () => {},
|
||||
},
|
||||
},
|
||||
uiSettings: {},
|
||||
};
|
||||
|
||||
return merge(baseMock, overrides);
|
||||
}
|
||||
|
||||
function createMockStore() {
|
||||
const store = configureStore({
|
||||
reducer: {
|
||||
app: () => ({
|
||||
enableExperimental: {
|
||||
prebuiltRulesCustomizationEnabled: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
return store;
|
||||
}
|
||||
|
||||
interface StorybookProvidersProps {
|
||||
children: React.ReactNode;
|
||||
kibanaServicesMock?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function ThreeWayDiffStorybookProviders({
|
||||
children,
|
||||
kibanaServicesMock,
|
||||
}: StorybookProvidersProps) {
|
||||
const KibanaReactContext = createKibanaReactContext(createKibanaServicesMock(kibanaServicesMock));
|
||||
|
||||
const store = createMockStore();
|
||||
|
||||
return (
|
||||
<KibanaReactContext.Provider>
|
||||
<ReactQueryClientProvider>
|
||||
<ReduxStoreProvider store={store}>{children}</ReduxStoreProvider>
|
||||
</ReactQueryClientProvider>
|
||||
</KibanaReactContext.Provider>
|
||||
);
|
||||
}
|
|
@ -356,3 +356,38 @@ export const CUSTOMIZED_PREBUILT_RULE_LABEL = i18n.translate(
|
|||
defaultMessage: 'Customized Elastic rule',
|
||||
}
|
||||
);
|
||||
|
||||
export const QUERY_LANGUAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.queryLanguageLabel',
|
||||
{
|
||||
defaultMessage: 'Custom query language',
|
||||
}
|
||||
);
|
||||
|
||||
export const THREAT_QUERY_LANGUAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.threatQueryLanguageLabel',
|
||||
{
|
||||
defaultMessage: 'Indicator index query language',
|
||||
}
|
||||
);
|
||||
|
||||
export const SAVED_QUERY_LANGUAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.savedQueryLanguageLabel',
|
||||
{
|
||||
defaultMessage: 'Saved query language',
|
||||
}
|
||||
);
|
||||
|
||||
export const KUERY_LANGUAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.kqlLanguageLabel',
|
||||
{
|
||||
defaultMessage: 'KQL',
|
||||
}
|
||||
);
|
||||
|
||||
export const LUCENE_LANGUAGE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.ruleDetails.luceneLanguageLabel',
|
||||
{
|
||||
defaultMessage: 'Lucene',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { useKibana } from '../../../../common/lib/kibana/kibana_react';
|
||||
import { DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN } from '../../../../../common/constants';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../common/hooks/use_experimental_features';
|
||||
|
||||
/**
|
||||
* Gets the default index pattern for cases when rule has neither index patterns or data view.
|
||||
* First checks the config value. If it's not present falls back to the hardcoded default value.
|
||||
*/
|
||||
export function useDefaultIndexPattern() {
|
||||
const { services } = useKibana();
|
||||
const isPrebuiltRulesCustomizationEnabled = useIsExperimentalFeatureEnabled(
|
||||
'prebuiltRulesCustomizationEnabled'
|
||||
);
|
||||
|
||||
if (isPrebuiltRulesCustomizationEnabled) {
|
||||
return services.settings.client.get(DEFAULT_INDEX_KEY, DEFAULT_INDEX_PATTERN);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue