[Security Solution] FinalEdit: Add name and kql_query fields + shared components (#193828)

**Partially addresses: https://github.com/elastic/kibana/issues/171520**
**Is a follow-up PR to: https://github.com/elastic/kibana/pull/192342**

## Summary

Changes:
 - Adds editable components for `name` and `kql_query` fields
- Adds a `FieldFormWrapper` component that abstracts away form creation
and data preparation for each field
- Adds local context providers to pass data between the main context and
field components
- Adds some basic layout components to make the "edit" functionality
work

<img width="1392" alt="Scherm­afbeelding 2024-10-04 om 17 17 44"
src="https://github.com/user-attachments/assets/6272ac84-8159-4b8a-a0d4-88b458f4bc5f">

---------

Co-authored-by: Maxim Palenov <maxim.palenov@elastic.co>
This commit is contained in:
Nikita Indik 2024-10-14 19:09:59 +02:00 committed by GitHub
parent 37c1bb1e9d
commit 424ffbaffc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 1242 additions and 118 deletions

View file

@ -24,7 +24,7 @@ import * as i18n from './translations';
interface ComparisonSideProps<FieldName extends keyof DiffableAllFields> {
fieldName: FieldName;
fieldThreeWayDiff: ThreeWayDiff<DiffableAllFields[FieldName]>;
resolvedValue?: DiffableAllFields[FieldName];
resolvedValue: DiffableAllFields[FieldName];
}
export function ComparisonSide<FieldName extends keyof DiffableAllFields>({
@ -39,6 +39,7 @@ export function ComparisonSide<FieldName extends keyof DiffableAllFields>({
const [oldVersionType, newVersionType] = selectedVersions.split('_') as [Version, Version];
const oldFieldValue = pickFieldValueForVersion(oldVersionType, fieldThreeWayDiff, resolvedValue);
const newFieldValue = pickFieldValueForVersion(newVersionType, fieldThreeWayDiff, resolvedValue);
const subfieldChanges = getSubfieldChanges(fieldName, oldFieldValue, newFieldValue);

View file

@ -17,13 +17,13 @@ import type {
*
* @param version - The version for which the field value is to be picked.
* @param fieldThreeWayDiff - The three-way diff object containing the field values for different versions.
* @param resolvedValue - The user-set resolved value resolved value. Used if it is set and the version is final.
* @param resolvedValue - A value field will be upgraded to.
* @returns - The field value for the specified version
*/
export function pickFieldValueForVersion<FieldName extends keyof DiffableAllFields>(
version: Version,
fieldThreeWayDiff: ThreeWayDiff<DiffableAllFields[FieldName]>,
resolvedValue?: DiffableAllFields[FieldName]
resolvedValue: DiffableAllFields[FieldName]
): DiffableAllFields[FieldName] | undefined {
if (version === Version.Final) {
return resolvedValue;

View file

@ -0,0 +1,19 @@
/*
* 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 { DiffableAllFields } from '../../../../../../../common/api/detection_engine';
type NonEditableFields = Readonly<Set<keyof DiffableAllFields>>;
/* These fields are not visible in the comparison UI and are not editable */
export const HIDDEN_FIELDS: NonEditableFields = new Set([
'alert_suppression',
'author',
'rule_id',
'license',
'version',
]);

View file

@ -11,7 +11,6 @@ import { css } from '@emotion/css';
import { SplitAccordion } from '../../../../../../common/components/split_accordion';
import type {
DiffableAllFields,
DiffableRule,
RuleFieldsDiff,
ThreeWayDiff,
} from '../../../../../../../common/api/detection_engine';
@ -20,23 +19,25 @@ import type { FieldUpgradeState } from '../../../../model/prebuilt_rule_upgrade'
import { ComparisonSide } from '../comparison_side/comparison_side';
import { FinalSide } from '../final_side/final_side';
import { FieldUpgradeConflictsResolverHeader } from './field_upgrade_conflicts_resolver_header';
import { useDiffableRuleContext } from '../diffable_rule_context';
import type { UpgradeableDiffableFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface FieldUpgradeConflictsResolverProps<FieldName extends keyof RuleFieldsDiff> {
interface FieldUpgradeConflictsResolverProps<FieldName extends UpgradeableDiffableFields> {
fieldName: FieldName;
fieldUpgradeState: FieldUpgradeState;
fieldThreeWayDiff: RuleFieldsDiff[FieldName];
finalDiffableRule: DiffableRule;
}
export function FieldUpgradeConflictsResolver<FieldName extends keyof RuleFieldsDiff>({
export function FieldUpgradeConflictsResolver<FieldName extends UpgradeableDiffableFields>({
fieldName,
fieldUpgradeState,
fieldThreeWayDiff,
finalDiffableRule,
}: FieldUpgradeConflictsResolverProps<FieldName>): JSX.Element {
const { euiTheme } = useEuiTheme();
const hasConflict = fieldThreeWayDiff.conflict !== ThreeWayDiffConflict.NONE;
const { finalDiffableRule } = useDiffableRuleContext();
return (
<>
<SplitAccordion
@ -65,7 +66,7 @@ export function FieldUpgradeConflictsResolver<FieldName extends keyof RuleFields
`}
/>
<EuiFlexItem grow={1}>
<FinalSide fieldName={fieldName} finalDiffableRule={finalDiffableRule} />
<FinalSide fieldName={fieldName} />
</EuiFlexItem>
</EuiFlexGroup>
</SplitAccordion>

View file

@ -64,7 +64,12 @@ export function RuleUpgradeCallout({ ruleUpgradeState }: RuleUpgradeCalloutProps
}
return (
<EuiCallOut title={i18n.RULE_IS_READY_FOR_UPGRADE} iconType="warning" color="success" size="s">
<EuiCallOut
title={i18n.RULE_IS_READY_FOR_UPGRADE}
iconType="checkInCircleFilled"
color="success"
size="s"
>
<p>{i18n.RULE_IS_READY_FOR_UPGRADE_DESCRIPTION}</p>
</EuiCallOut>
);

View file

@ -13,7 +13,7 @@ export const RULE_HAS_NON_SOLVABLE_CONFLICTS = (count: number) =>
{
values: { count },
defaultMessage:
'{count} of the fields has a unsolved conflict. Please review and modify accordingly.',
'{count} of the fields {count, plural, one {has} other {have}} an unsolved conflict. Please review and modify accordingly.',
}
);
@ -31,7 +31,7 @@ export const RULE_HAS_SOLVABLE_CONFLICTS = (count: number) =>
{
values: { count },
defaultMessage:
'{count} of the fields has an update conflict, please review the suggested update being updating.',
'{count} of the fields {count, plural, one {has} other {have}} an update conflict, please review the suggested update being updating.',
}
);

View file

@ -6,36 +6,39 @@
*/
import React from 'react';
import type {
RuleUpgradeState,
SetRuleFieldResolvedValueFn,
} from '../../../../model/prebuilt_rule_upgrade';
import type { RuleUpgradeState } from '../../../../model/prebuilt_rule_upgrade';
import { FieldUpgradeConflictsResolver } from './field_upgrade_conflicts_resolver';
import type { NonUpgradeableDiffableFields } from '../../../../model/prebuilt_rule_upgrade/fields';
import { isNonUpgradeableFieldName } from '../../../../model/prebuilt_rule_upgrade/fields';
type FieldDiffEntries<FieldsDiff, ExcludedFields extends keyof FieldsDiff = never> = Array<
[
Exclude<keyof FieldsDiff, ExcludedFields>,
Required<FieldsDiff>[Exclude<keyof FieldsDiff, ExcludedFields>]
]
>;
interface RuleUpgradeConflictsResolverProps {
ruleUpgradeState: RuleUpgradeState;
setRuleFieldResolvedValue: SetRuleFieldResolvedValueFn;
}
export function RuleUpgradeConflictsResolver({
ruleUpgradeState,
setRuleFieldResolvedValue,
}: RuleUpgradeConflictsResolverProps): JSX.Element {
const fieldDiffEntries = Object.entries(ruleUpgradeState.diff.fields) as Array<
[
keyof typeof ruleUpgradeState.diff.fields,
Required<typeof ruleUpgradeState.diff.fields>[keyof typeof ruleUpgradeState.diff.fields]
]
}: RuleUpgradeConflictsResolverProps): React.ReactNode {
const fieldDiffEntries = Object.entries(ruleUpgradeState.diff.fields) as FieldDiffEntries<
typeof ruleUpgradeState.diff.fields
>;
const fields = fieldDiffEntries.map(([fieldName, fieldDiff]) => (
const fields = fieldDiffEntries.filter(([fieldName]) => {
return isNonUpgradeableFieldName(fieldName) === false;
}) as FieldDiffEntries<typeof ruleUpgradeState.diff.fields, NonUpgradeableDiffableFields>;
return fields.map(([fieldName, fieldDiff]) => (
<FieldUpgradeConflictsResolver
key={fieldName}
fieldName={fieldName}
fieldUpgradeState={ruleUpgradeState.fieldsUpgradeState[fieldName]}
fieldThreeWayDiff={fieldDiff}
finalDiffableRule={ruleUpgradeState.finalRule}
/>
));
return <>{fields}</>;
}

View file

@ -0,0 +1,50 @@
/*
* 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, { createContext, useContext } from 'react';
import type { DiffableRule } from '../../../../../../common/api/detection_engine';
import { invariant } from '../../../../../../common/utils/invariant';
import type { SetRuleFieldResolvedValueFn } from '../../../model/prebuilt_rule_upgrade/set_rule_field_resolved_value';
interface DiffableRuleContextType {
finalDiffableRule: DiffableRule;
setRuleFieldResolvedValue: SetRuleFieldResolvedValueFn;
}
const DiffableRuleContext = createContext<DiffableRuleContextType | null>(null);
interface DiffableRuleContextProviderProps {
finalDiffableRule: DiffableRule;
setRuleFieldResolvedValue: SetRuleFieldResolvedValueFn;
children: React.ReactNode;
}
export function DiffableRuleContextProvider({
finalDiffableRule,
setRuleFieldResolvedValue,
children,
}: DiffableRuleContextProviderProps) {
const contextValue = {
finalDiffableRule,
setRuleFieldResolvedValue,
};
return (
<DiffableRuleContext.Provider value={contextValue}>{children}</DiffableRuleContext.Provider>
);
}
export function useDiffableRuleContext() {
const context = useContext(DiffableRuleContext);
invariant(
context !== null,
'useDiffableRuleContext must be used inside a DiffableRuleContextProvider'
);
return context;
}

View file

@ -0,0 +1,23 @@
/*
* 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 { FieldFormWrapper } from './field_form_wrapper';
import { NameEdit, nameSchema } from './fields/name';
import type { UpgradeableCommonFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface CommonRuleFieldEditProps {
fieldName: UpgradeableCommonFields;
}
export function CommonRuleFieldEdit({ fieldName }: CommonRuleFieldEditProps) {
switch (fieldName) {
case 'name':
return <FieldFormWrapper component={NameEdit} fieldFormSchema={nameSchema} />;
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
}

View file

@ -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 { FieldFormWrapper } from './field_form_wrapper';
import {
KqlQueryEdit,
kqlQuerySchema,
kqlQuerySerializer,
kqlQueryDeserializer,
} from './fields/kql_query';
import type { UpgradeableCustomQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface CustomQueryRuleFieldEditProps {
fieldName: UpgradeableCustomQueryFields;
}
export function CustomQueryRuleFieldEdit({ fieldName }: CustomQueryRuleFieldEditProps) {
switch (fieldName) {
case 'kql_query':
return (
<FieldFormWrapper
component={KqlQueryEdit}
fieldFormSchema={kqlQuerySchema}
serializer={kqlQuerySerializer}
deserializer={kqlQueryDeserializer}
/>
);
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
}

View file

@ -0,0 +1,115 @@
/*
* 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, { useCallback, useState } from 'react';
import { EuiButtonEmpty, EuiFlexGroup } from '@elastic/eui';
import { useForm, Form } from '../../../../../../shared_imports';
import type { FormSchema, FormData } from '../../../../../../shared_imports';
import type {
DiffableAllFields,
DiffableRule,
} from '../../../../../../../common/api/detection_engine';
import { useFinalSideContext } from '../final_side/final_side_context';
import { useDiffableRuleContext } from '../diffable_rule_context';
import * as i18n from '../translations';
type FieldComponent = React.ComponentType<{
finalDiffableRule: DiffableRule;
setValidity: (isValid: boolean) => void;
setFieldValue: (fieldName: string, fieldValue: unknown) => void;
}>;
interface FieldFormWrapperProps {
component: FieldComponent;
fieldFormSchema: FormSchema;
deserializer?: (fieldValue: FormData, finalDiffableRule: DiffableRule) => FormData;
serializer?: (formData: FormData) => FormData;
}
/**
* FieldFormWrapper component manages form state and renders "Save" and "Cancel" buttons.
*
* @param {Object} props - Component props.
* @param {React.ComponentType} props.component - Field component to be wrapped.
* @param {FormSchema} props.fieldFormSchema - Configuration schema for the field.
* @param {Function} props.deserializer - Deserializer prepares initial form data. It converts field value from a DiffableRule format to a format used by the form.
* @param {Function} props.serializer - Serializer prepares form data for submission. It converts form data back to a DiffableRule format.
*/
export function FieldFormWrapper({
component: FieldComponent,
fieldFormSchema,
deserializer,
serializer,
}: FieldFormWrapperProps) {
const { fieldName, setReadOnlyMode } = useFinalSideContext();
const { finalDiffableRule, setRuleFieldResolvedValue } = useDiffableRuleContext();
const deserialize = useCallback(
(defaultValue: FormData): FormData => {
if (!deserializer) {
return defaultValue;
}
const rule = finalDiffableRule as Record<string, unknown>;
const fieldValue = rule[fieldName] as FormData;
return deserializer(fieldValue, finalDiffableRule);
},
[deserializer, fieldName, finalDiffableRule]
);
const handleSubmit = useCallback(
async (formData: FormData, isValid: boolean) => {
if (!isValid) {
return;
}
setRuleFieldResolvedValue({
ruleId: finalDiffableRule.rule_id,
fieldName: fieldName as keyof DiffableAllFields,
resolvedValue: formData[fieldName],
});
setReadOnlyMode();
},
[fieldName, finalDiffableRule.rule_id, setReadOnlyMode, setRuleFieldResolvedValue]
);
const { form } = useForm({
schema: fieldFormSchema,
defaultValue: getDefaultValue(fieldName, finalDiffableRule),
deserializer: deserialize,
serializer,
onSubmit: handleSubmit,
});
const [validity, setValidity] = useState<boolean | undefined>(undefined);
const isValid = validity === undefined ? form.isValid : validity;
return (
<>
<EuiFlexGroup justifyContent="flexEnd">
<EuiButtonEmpty iconType="cross" onClick={setReadOnlyMode}>
{i18n.CANCEL_BUTTON_LABEL}
</EuiButtonEmpty>
<EuiButtonEmpty iconType="save" onClick={form.submit} disabled={isValid === false}>
{i18n.SAVE_BUTTON_LABEL}
</EuiButtonEmpty>
</EuiFlexGroup>
<Form form={form}>
<FieldComponent
finalDiffableRule={finalDiffableRule}
setValidity={setValidity}
setFieldValue={form.setFieldValue}
/>
</Form>
</>
);
}
function getDefaultValue(fieldName: string, finalDiffableRule: Record<string, unknown>): FormData {
return { [fieldName]: finalDiffableRule[fieldName] };
}

View file

@ -0,0 +1,239 @@
/*
* 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, { useCallback } from 'react';
import { useToggle } from 'react-use';
import { css } from '@emotion/css';
import { EuiButtonEmpty } from '@elastic/eui';
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import type { FormSchema, FormData } from '../../../../../../../shared_imports';
import { HiddenField, UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_define_rule/schema';
import { QueryBarDefineRule } from '../../../../../../rule_creation_ui/components/query_bar';
import type { FieldValueQueryBar } from '../../../../../../rule_creation_ui/components/query_bar';
import * as stepDefineRuleI18n from '../../../../../../rule_creation_ui/components/step_define_rule/translations';
import { useRuleIndexPattern } from '../../../../../../rule_creation_ui/pages/form';
import {
DataSourceType as DataSourceTypeSnakeCase,
KqlQueryLanguage,
KqlQueryType,
RuleQuery,
SavedQueryId,
RuleKqlQuery,
} from '../../../../../../../../common/api/detection_engine';
import type {
DiffableRule,
DiffableRuleTypes,
InlineKqlQuery,
SavedKqlQuery,
} from '../../../../../../../../common/api/detection_engine';
import { useDefaultIndexPattern } from '../../../use_default_index_pattern';
import { DataSourceType } from '../../../../../../../detections/pages/detection_engine/rules/types';
import { isFilters } from '../../../helpers';
import type { SetRuleQuery } from '../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline';
import { useRuleFromTimeline } from '../../../../../../../detections/containers/detection_engine/rules/use_rule_from_timeline';
import { useGetSavedQuery } from '../../../../../../../detections/pages/detection_engine/rules/use_get_saved_query';
export const kqlQuerySchema = {
ruleType: schema.ruleType,
queryBar: schema.queryBar,
} as FormSchema<{
ruleType: DiffableRuleTypes;
queryBar: FieldValueQueryBar;
}>;
interface KqlQueryEditProps {
finalDiffableRule: DiffableRule;
setValidity: (isValid: boolean) => void;
setFieldValue: (fieldName: string, fieldValue: unknown) => void;
}
export function KqlQueryEdit({
finalDiffableRule,
setValidity,
setFieldValue,
}: KqlQueryEditProps): JSX.Element {
const defaultIndexPattern = useDefaultIndexPattern();
const indexPatternParameters = getUseRuleIndexPatternParameters(
finalDiffableRule,
defaultIndexPattern
);
const { indexPattern, isIndexPatternLoading } = useRuleIndexPattern(indexPatternParameters);
const [isTimelineSearchOpen, toggleIsTimelineSearchOpen] = useToggle(false);
const handleSetRuleFromTimeline = useCallback<SetRuleQuery>(
({ queryBar: timelineQueryBar }) => {
setFieldValue('queryBar', timelineQueryBar);
},
[setFieldValue]
);
const { onOpenTimeline } = useRuleFromTimeline(handleSetRuleFromTimeline);
const isSavedQueryRule = finalDiffableRule.type === 'saved_query';
const { savedQuery } = useGetSavedQuery({
savedQueryId: getSavedQueryId(finalDiffableRule),
ruleType: finalDiffableRule.type,
});
return (
<>
<UseField path="ruleType" component={HiddenField} />
<UseField
path="queryBar"
config={{
...kqlQuerySchema.queryBar,
label: stepDefineRuleI18n.QUERY_BAR_LABEL,
labelAppend: isSavedQueryRule ? null : (
<ImportTimelineQueryButton handleOpenTimelineSearch={toggleIsTimelineSearchOpen} />
),
}}
component={QueryBarDefineRule}
componentProps={{
indexPattern,
isLoading: isIndexPatternLoading,
openTimelineSearch: isTimelineSearchOpen,
onCloseTimelineSearch: toggleIsTimelineSearchOpen,
onValidityChange: setValidity,
onOpenTimeline,
isDisabled: isSavedQueryRule,
defaultSavedQuery: savedQuery,
resetToSavedQuery: isSavedQueryRule,
}}
/>
</>
);
}
const timelineButtonClassName = css`
height: 18px;
font-size: 12px;
`;
function ImportTimelineQueryButton({
handleOpenTimelineSearch,
}: {
handleOpenTimelineSearch: () => void;
}) {
return (
<EuiButtonEmpty className={timelineButtonClassName} onClick={handleOpenTimelineSearch}>
{stepDefineRuleI18n.IMPORT_TIMELINE_QUERY}
</EuiButtonEmpty>
);
}
export function kqlQuerySerializer(formData: FormData): {
kql_query: RuleKqlQuery;
} {
const formValue = formData as { ruleType: Type; queryBar: FieldValueQueryBar };
if (formValue.ruleType === 'saved_query') {
const savedQueryId = SavedQueryId.parse(formValue.queryBar.saved_id);
const savedKqlQuery: SavedKqlQuery = {
type: KqlQueryType.saved_query,
saved_query_id: savedQueryId,
};
return {
kql_query: savedKqlQuery,
};
}
const query = RuleQuery.parse(formValue.queryBar.query.query);
const language = KqlQueryLanguage.parse(formValue.queryBar.query.language);
const inlineKqlQuery: InlineKqlQuery = {
type: KqlQueryType.inline_query,
query,
language,
filters: formValue.queryBar.filters,
};
return { kql_query: inlineKqlQuery };
}
export function kqlQueryDeserializer(
fieldValue: FormData,
finalDiffableRule: DiffableRule
): {
ruleType: Type;
queryBar: FieldValueQueryBar;
} {
const parsedFieldValue = RuleKqlQuery.parse(fieldValue);
if (parsedFieldValue.type === KqlQueryType.inline_query) {
const returnValue = {
ruleType: finalDiffableRule.type,
queryBar: {
query: {
query: parsedFieldValue.query,
language: parsedFieldValue.language,
},
filters: isFilters(parsedFieldValue.filters) ? parsedFieldValue.filters : [],
saved_id: null,
},
};
return returnValue;
}
const returnValue = {
ruleType: finalDiffableRule.type,
queryBar: {
query: {
query: '',
language: '',
},
filters: [],
saved_id: parsedFieldValue.saved_query_id,
},
};
return returnValue;
}
interface UseRuleIndexPatternParameters {
dataSourceType: DataSourceType;
index: string[];
dataViewId: string | undefined;
}
function getUseRuleIndexPatternParameters(
finalDiffableRule: DiffableRule,
defaultIndexPattern: string[]
): UseRuleIndexPatternParameters {
if (!('data_source' in finalDiffableRule) || !finalDiffableRule.data_source) {
return {
dataSourceType: DataSourceType.IndexPatterns,
index: defaultIndexPattern,
dataViewId: undefined,
};
}
if (finalDiffableRule.data_source.type === DataSourceTypeSnakeCase.data_view) {
return {
dataSourceType: DataSourceType.DataView,
index: [],
dataViewId: finalDiffableRule.data_source.data_view_id,
};
}
return {
dataSourceType: DataSourceType.IndexPatterns,
index: finalDiffableRule.data_source.index_patterns,
dataViewId: undefined,
};
}
function getSavedQueryId(diffableRule: DiffableRule): string | undefined {
if (diffableRule.type === 'saved_query' && 'saved_query_id' in diffableRule.kql_query) {
return diffableRule.kql_query.saved_query_id;
}
return undefined;
}

View file

@ -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 type { FormSchema } from '../../../../../../../shared_imports';
import { Field, UseField } from '../../../../../../../shared_imports';
import { schema } from '../../../../../../rule_creation_ui/components/step_about_rule/schema';
import type { RuleName } from '../../../../../../../../common/api/detection_engine';
export const nameSchema = { name: schema.name } as FormSchema<{ name: RuleName }>;
export function NameEdit(): JSX.Element {
return (
<UseField
path="name"
component={Field}
componentProps={{
euiFieldProps: {
fullWidth: true,
},
}}
/>
);
}

View file

@ -0,0 +1,57 @@
/*
* 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 { assertUnreachable } from '../../../../../../../common/utility_types';
import { useDiffableRuleContext } from '../diffable_rule_context';
import { CommonRuleFieldEdit } from './common_rule_field_edit';
import { CustomQueryRuleFieldEdit } from './custom_query_rule_field_edit';
import { SavedQueryRuleFieldEdit } from './saved_query_rule_field_edit';
import { ThreatMatchRuleFieldEdit } from './threat_match_rule_field_edit';
import { ThresholdRuleFieldEdit } from './threshold_rule_field_edit';
import { NewTermsRuleFieldEdit } from './new_terms_rule_field_edit';
import type {
UpgradeableCustomQueryFields,
UpgradeableSavedQueryFields,
UpgradeableThreatMatchFields,
UpgradeableThresholdFields,
UpgradeableNewTermsFields,
} from '../../../../model/prebuilt_rule_upgrade/fields';
import { isCommonFieldName } from '../../../../model/prebuilt_rule_upgrade/fields';
import { useFinalSideContext } from '../final_side/final_side_context';
export function FinalEdit() {
const { finalDiffableRule } = useDiffableRuleContext();
const { type } = finalDiffableRule;
const { fieldName } = useFinalSideContext();
if (isCommonFieldName(fieldName)) {
return <CommonRuleFieldEdit fieldName={fieldName} />;
}
switch (type) {
case 'query':
return <CustomQueryRuleFieldEdit fieldName={fieldName as UpgradeableCustomQueryFields} />;
case 'saved_query':
return <SavedQueryRuleFieldEdit fieldName={fieldName as UpgradeableSavedQueryFields} />;
case 'eql':
return <span>{'Rule type not yet implemented'}</span>;
case 'esql':
return <span>{'Rule type not yet implemented'}</span>;
case 'threat_match':
return <ThreatMatchRuleFieldEdit fieldName={fieldName as UpgradeableThreatMatchFields} />;
case 'threshold':
return <ThresholdRuleFieldEdit fieldName={fieldName as UpgradeableThresholdFields} />;
case 'machine_learning':
return <span>{'Rule type not yet implemented'}</span>;
case 'new_terms':
return <NewTermsRuleFieldEdit fieldName={fieldName as UpgradeableNewTermsFields} />;
default:
return assertUnreachable(type);
}
}

View file

@ -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 { FieldFormWrapper } from './field_form_wrapper';
import {
KqlQueryEdit,
kqlQuerySchema,
kqlQuerySerializer,
kqlQueryDeserializer,
} from './fields/kql_query';
import type { UpgradeableNewTermsFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface NewTermsRuleFieldEditProps {
fieldName: UpgradeableNewTermsFields;
}
export function NewTermsRuleFieldEdit({ fieldName }: NewTermsRuleFieldEditProps) {
switch (fieldName) {
case 'kql_query':
return (
<FieldFormWrapper
component={KqlQueryEdit}
fieldFormSchema={kqlQuerySchema}
serializer={kqlQuerySerializer}
deserializer={kqlQueryDeserializer}
/>
);
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
}

View file

@ -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 { FieldFormWrapper } from './field_form_wrapper';
import {
KqlQueryEdit,
kqlQuerySchema,
kqlQuerySerializer,
kqlQueryDeserializer,
} from './fields/kql_query';
import type { UpgradeableSavedQueryFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface SavedQueryRuleFieldEditProps {
fieldName: UpgradeableSavedQueryFields;
}
export function SavedQueryRuleFieldEdit({ fieldName }: SavedQueryRuleFieldEditProps) {
switch (fieldName) {
case 'kql_query':
return (
<FieldFormWrapper
component={KqlQueryEdit}
fieldFormSchema={kqlQuerySchema}
serializer={kqlQuerySerializer}
deserializer={kqlQueryDeserializer}
/>
);
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
}

View file

@ -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 { FieldFormWrapper } from './field_form_wrapper';
import {
KqlQueryEdit,
kqlQuerySchema,
kqlQuerySerializer,
kqlQueryDeserializer,
} from './fields/kql_query';
import type { UpgradeableThreatMatchFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface ThreatMatchRuleFieldEditProps {
fieldName: UpgradeableThreatMatchFields;
}
export function ThreatMatchRuleFieldEdit({ fieldName }: ThreatMatchRuleFieldEditProps) {
switch (fieldName) {
case 'kql_query':
return (
<FieldFormWrapper
component={KqlQueryEdit}
fieldFormSchema={kqlQuerySchema}
serializer={kqlQuerySerializer}
deserializer={kqlQueryDeserializer}
/>
);
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
}

View file

@ -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 { FieldFormWrapper } from './field_form_wrapper';
import {
KqlQueryEdit,
kqlQuerySchema,
kqlQuerySerializer,
kqlQueryDeserializer,
} from './fields/kql_query';
import type { UpgradeableThresholdFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface ThresholdRuleFieldEditProps {
fieldName: UpgradeableThresholdFields;
}
export function ThresholdRuleFieldEdit({ fieldName }: ThresholdRuleFieldEditProps) {
switch (fieldName) {
case 'kql_query':
return (
<FieldFormWrapper
component={KqlQueryEdit}
fieldFormSchema={kqlQuerySchema}
serializer={kqlQuerySerializer}
deserializer={kqlQueryDeserializer}
/>
);
default:
return null; // Will be replaced with `assertUnreachable(fieldName)` once all fields are implemented
}
}

View file

@ -8,7 +8,6 @@
import React, { useMemo } from 'react';
import { DiffableCommonFields } from '../../../../../../../common/api/detection_engine';
import type {
DiffableRule,
DiffableCustomQueryFields,
DiffableSavedQueryFields,
DiffableEqlFields,
@ -28,13 +27,15 @@ import { ThresholdRuleFieldReadOnly } from './threshold_rule_field_readonly';
import { MachineLearningRuleFieldReadOnly } from './machine_learning_rule_field_readonly';
import { NewTermsRuleFieldReadOnly } from './new_terms_rule_field_readonly';
import { CommonRuleFieldReadOnly } from './common_rule_field_readonly';
import { useDiffableRuleContext } from '../diffable_rule_context';
interface FieldReadOnlyProps {
fieldName: string;
finalDiffableRule: DiffableRule;
}
export function FieldReadOnly({ fieldName, finalDiffableRule }: FieldReadOnlyProps) {
export function FieldReadOnly({ fieldName }: FieldReadOnlyProps) {
const { finalDiffableRule } = useDiffableRuleContext();
const { data: commonField } = useMemo(
() => DiffableCommonFields.keyof().safeParse(fieldName),
[fieldName]

View file

@ -25,8 +25,8 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders>
<FieldReadOnly fieldName="alert_suppression" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="alert_suppression" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { AnomalyThresholdReadOnly } from './anomaly_threshold';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockMachineLearningRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: AnomalyThresholdReadOnly,
@ -23,7 +24,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="anomaly_threshold" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="anomaly_threshold" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { AuthorReadOnly } from './author';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: AuthorReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="author" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="author" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { BuildingBlockReadOnly } from './building_block';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: BuildingBlockReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="building_block" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="building_block" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -29,8 +29,11 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders kibanaServicesOverrides={args.kibanaServicesOverrides}>
<FieldReadOnly fieldName="data_source" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders
kibanaServicesOverrides={args.kibanaServicesOverrides}
finalDiffableRule={args.finalDiffableRule}
>
<FieldReadOnly fieldName="data_source" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { DescriptionReadOnly } from './description';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: DescriptionReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="description" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="description" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -31,8 +31,11 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders kibanaServicesOverrides={args.kibanaServicesOverrides}>
<FieldReadOnly fieldName="eql_query" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders
kibanaServicesOverrides={args.kibanaServicesOverrides}
finalDiffableRule={args.finalDiffableRule}
>
<FieldReadOnly fieldName="eql_query" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -10,6 +10,7 @@ import type { Story } from '@storybook/react';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockEsqlRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: FieldReadOnly,
@ -21,7 +22,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="esql_query" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="esql_query" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { EventCategoryOverrideReadOnly } from './event_category_override';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockEqlRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: EventCategoryOverrideReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="event_category_override" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="event_category_override" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { FalsePositivesReadOnly } from './false_positives';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: FalsePositivesReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="false_positives" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="false_positives" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { HistoryWindowStartReadOnly } from './history_window_start';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockNewTermsRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: HistoryWindowStartReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="history_window_start" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="history_window_start" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { InvestigationFieldsReadOnly } from './investigation_fields';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: InvestigationFieldsReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="investigation_fields" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="investigation_fields" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -33,8 +33,11 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders kibanaServicesOverrides={args.kibanaServicesOverrides}>
<FieldReadOnly fieldName="kql_query" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders
kibanaServicesOverrides={args.kibanaServicesOverrides}
finalDiffableRule={args.finalDiffableRule}
>
<FieldReadOnly fieldName="kql_query" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -17,7 +17,7 @@ import { Query, SavedQueryName, Filters } from '../../../../rule_definition_sect
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';
import { getDataSourceProps, getQueryLanguageLabel, isFilters } from '../../../../helpers';
interface SavedQueryProps {
kqlQuery: SavedKqlQuery;
@ -53,12 +53,14 @@ export function SavedKqlQueryReadOnly({ kqlQuery, dataSource, ruleType }: SavedQ
});
}
if (savedQuery.attributes.filters) {
const filters = savedQuery.attributes.filters ?? [];
if (isFilters(filters) && filters.length > 0) {
const dataSourceProps = getDataSourceProps(dataSource);
listItems.push({
title: descriptionStepI18n.SAVED_QUERY_FILTERS_LABEL,
description: <Filters filters={savedQuery.attributes.filters} {...dataSourceProps} />,
description: <Filters filters={filters} {...dataSourceProps} />,
});
}

View file

@ -11,6 +11,7 @@ import { LicenseReadOnly } from './license';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: LicenseReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="license" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="license" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -64,12 +64,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders>
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<MockMlData>
<FieldReadOnly
fieldName="machine_learning_job_id"
finalDiffableRule={args.finalDiffableRule}
/>
<FieldReadOnly fieldName="machine_learning_job_id" />
</MockMlData>
</ThreeWayDiffStorybookProviders>
);

View file

@ -11,6 +11,7 @@ import { MaxSignalsReadOnly } from './max_signals';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: MaxSignalsReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="max_signals" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="max_signals" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { NameReadOnly } from './name';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: NameReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="name" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="name" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { NewTermsFieldsReadOnly } from './new_terms_fields';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockNewTermsRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: NewTermsFieldsReadOnly,
@ -23,7 +24,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="new_terms_fields" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="new_terms_fields" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { NoteReadOnly } from './note';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: NoteReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="note" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="note" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { ReferencesReadOnly } from './references';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ReferencesReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="references" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="references" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -45,12 +45,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders>
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<MockRelatedIntegrationsData>
<FieldReadOnly
fieldName="related_integrations"
finalDiffableRule={args.finalDiffableRule}
/>
<FieldReadOnly fieldName="related_integrations" />
</MockRelatedIntegrationsData>
</ThreeWayDiffStorybookProviders>
);

View file

@ -10,6 +10,7 @@ import { RequiredFieldsReadOnly } from './required_fields';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: RequiredFieldsReadOnly,
@ -21,7 +22,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="required_fields" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="required_fields" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { RiskScoreReadOnly } from './risk_score';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: RiskScoreReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="risk_score" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="risk_score" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { RiskScoreMappingReadOnly } from './risk_score_mapping';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: RiskScoreMappingReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="risk_score_mapping" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="risk_score_mapping" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { RuleNameOverrideReadOnly } from './rule_name_override';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: RuleNameOverrideReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="rule_name_override" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="rule_name_override" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { RuleScheduleReadOnly } from './rule_schedule';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: RuleScheduleReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="rule_schedule" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="rule_schedule" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { SetupReadOnly } from './setup';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: SetupReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="setup" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="setup" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { SeverityReadOnly } from './severity';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: SeverityReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="severity" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="severity" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { SeverityMappingReadOnly } from './severity_mapping';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: SeverityMappingReadOnly,
@ -23,7 +24,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="severity_mapping" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="severity_mapping" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { TagsReadOnly } from './tags';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: TagsReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="tags" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="tags" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { ThreatReadOnly } from './threat';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ThreatReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="threat" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="threat" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { ThreatIndexReadOnly } from './threat_index';
import { mockThreatMatchRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ThreatIndexReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="threat_index" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="threat_index" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { ThreatIndicatorPathReadOnly } from './threat_indicator_path';
import { mockThreatMatchRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ThreatIndicatorPathReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="threat_indicator_path" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="threat_indicator_path" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { ThreatLanguageReadOnly } from './threat_language';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockThreatMatchRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ThreatLanguageReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="threat_language" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="threat_language" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { ThreatMappingReadOnly } from './threat_mapping';
import { mockThreatMatchRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ThreatMappingReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="threat_mapping" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="threat_mapping" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -31,8 +31,11 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<ThreeWayDiffStorybookProviders kibanaServicesOverrides={args.kibanaServicesOverrides}>
<FieldReadOnly fieldName="threat_query" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders
kibanaServicesOverrides={args.kibanaServicesOverrides}
finalDiffableRule={args.finalDiffableRule}
>
<FieldReadOnly fieldName="threat_query" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { ThresholdReadOnly } from './threshold';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockThresholdRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: ThresholdReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="threshold" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="threshold" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { TiebreakerFieldReadOnly } from './tiebreaker_field';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockEqlRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: TiebreakerFieldReadOnly,
@ -23,7 +24,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="tiebreaker_field" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="tiebreaker_field" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { TimelineTemplateReadOnly } from './timeline_template';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: TimelineTemplateReadOnly,
@ -23,7 +24,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="timeline_template" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="timeline_template" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { TimestampFieldReadOnly } from './timestamp_field';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockEqlRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: TimestampFieldReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="timestamp_field" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="timestamp_field" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -11,6 +11,7 @@ import { TimestampOverrideReadOnly } from './timestamp_override';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: TimestampOverrideReadOnly,
@ -24,7 +25,9 @@ interface TemplateProps {
const Template: Story<TemplateProps> = (args) => {
return (
<FieldReadOnly fieldName="timestamp_override" finalDiffableRule={args.finalDiffableRule} />
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="timestamp_override" />
</ThreeWayDiffStorybookProviders>
);
};

View file

@ -11,6 +11,7 @@ import { TypeReadOnly } from './type';
import { FieldReadOnly } from '../../field_readonly';
import type { DiffableRule } from '../../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from '../../storybook/mocks';
import { ThreeWayDiffStorybookProviders } from '../../storybook/three_way_diff_storybook_providers';
export default {
component: TypeReadOnly,
@ -22,7 +23,11 @@ interface TemplateProps {
}
const Template: Story<TemplateProps> = (args) => {
return <FieldReadOnly fieldName="type" finalDiffableRule={args.finalDiffableRule} />;
return (
<ThreeWayDiffStorybookProviders finalDiffableRule={args.finalDiffableRule}>
<FieldReadOnly fieldName="type" />
</ThreeWayDiffStorybookProviders>
);
};
export const Default = Template.bind({});

View file

@ -0,0 +1,25 @@
/*
* 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 { EuiButtonEmpty } from '@elastic/eui';
import React from 'react';
import { FieldReadOnly } from './field_readonly';
import * as i18n from '../translations';
import { useFinalSideContext } from '../final_side/final_side_context';
export function FinalReadOnly() {
const { setEditMode, fieldName } = useFinalSideContext();
return (
<>
<EuiButtonEmpty iconType="pencil" onClick={setEditMode}>
{i18n.EDIT_BUTTON_LABEL}
</EuiButtonEmpty>
<FieldReadOnly fieldName={fieldName} />
</>
);
}

View file

@ -159,7 +159,7 @@ const customQueryDiffableRuleFields: DiffableCustomQueryFields = {
};
export function mockCustomQueryRule(
overrides: Partial<DiffableCommonFields & DiffableCustomQueryFields>
overrides: Partial<DiffableCommonFields & DiffableCustomQueryFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -177,7 +177,7 @@ const savedQueryDiffableRuleFields: DiffableSavedQueryFields = {
};
export function mockSavedQueryRule(
overrides: Partial<DiffableCommonFields & DiffableSavedQueryFields>
overrides: Partial<DiffableCommonFields & DiffableSavedQueryFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -196,7 +196,7 @@ const eqlDiffableRuleFields: DiffableEqlFields = {
};
export function mockEqlRule(
overrides: Partial<DiffableCommonFields & DiffableEqlFields>
overrides: Partial<DiffableCommonFields & DiffableEqlFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -214,7 +214,7 @@ const esqlDiffableRuleFields: DiffableEsqlFields = {
};
export function mockEsqlRule(
overrides: Partial<DiffableCommonFields & DiffableEsqlFields>
overrides: Partial<DiffableCommonFields & DiffableEsqlFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -230,7 +230,7 @@ const machineLearningDiffableRuleFields: DiffableMachineLearningFields = {
};
export function mockMachineLearningRule(
overrides: Partial<DiffableCommonFields & DiffableMachineLearningFields>
overrides: Partial<DiffableCommonFields & DiffableMachineLearningFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -268,7 +268,7 @@ const threatMatchDiffableRuleFields: DiffableThreatMatchFields = {
};
export function mockThreatMatchRule(
overrides: Partial<DiffableCommonFields & DiffableThreatMatchFields>
overrides: Partial<DiffableCommonFields & DiffableThreatMatchFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -290,7 +290,7 @@ const newTermsDiffableRuleFields: DiffableNewTermsFields = {
};
export function mockNewTermsRule(
overrides: Partial<DiffableCommonFields & DiffableNewTermsFields>
overrides: Partial<DiffableCommonFields & DiffableNewTermsFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,
@ -314,7 +314,7 @@ export const thresholdDiffableRuleFields: DiffableThresholdFields = {
};
export function mockThresholdRule(
overrides: Partial<DiffableCommonFields & DiffableThresholdFields>
overrides: Partial<DiffableCommonFields & DiffableThresholdFields> = {}
): DiffableRule {
return {
...commonDiffableRuleFields,

View file

@ -15,6 +15,9 @@ import type { UpsellingService } from '@kbn/security-solution-upselling/service'
import { createKibanaReactContext } from '@kbn/kibana-react-plugin/public';
import { ReactQueryClientProvider } from '../../../../../../../common/containers/query_client/query_client_provider';
import { UpsellingProvider } from '../../../../../../../common/components/upselling_provider';
import { DiffableRuleContextProvider } from '../../diffable_rule_context';
import type { DiffableRule } from '../../../../../../../../common/api/detection_engine';
import { mockCustomQueryRule } from './mocks';
function createKibanaServicesMock(overrides?: Partial<CoreStart>) {
const baseMock = {
@ -69,14 +72,18 @@ function createMockStore() {
return store;
}
const setRuleFieldResolvedValueMock = () => {};
interface StorybookProvidersProps {
children: React.ReactNode;
kibanaServicesOverrides?: Record<string, unknown>;
finalDiffableRule?: DiffableRule;
}
export function ThreeWayDiffStorybookProviders({
children,
kibanaServicesOverrides,
finalDiffableRule = mockCustomQueryRule(),
}: StorybookProvidersProps) {
const kibanaServicesMock = createKibanaServicesMock(kibanaServicesOverrides);
const KibanaReactContext = createKibanaReactContext(kibanaServicesMock);
@ -88,7 +95,12 @@ export function ThreeWayDiffStorybookProviders({
<ReactQueryClientProvider>
<ReduxStoreProvider store={store}>
<UpsellingProvider upsellingService={kibanaServicesMock.upsellingService}>
{children}
<DiffableRuleContextProvider
finalDiffableRule={finalDiffableRule}
setRuleFieldResolvedValue={setRuleFieldResolvedValueMock}
>
{children}
</DiffableRuleContextProvider>
</UpsellingProvider>
</ReduxStoreProvider>
</ReactQueryClientProvider>

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export enum FinalSideMode {
READONLY = 'readonly',
EDIT = 'edit',
}

View file

@ -7,18 +7,21 @@
import React from 'react';
import { EuiTitle } from '@elastic/eui';
import type { DiffableRule } from '../../../../../../../common/api/detection_engine';
import { FieldReadOnly } from '../final_readonly/field_readonly';
import { SideHeader } from '../components/side_header';
import { FinalSideHelpInfo } from './final_side_help_info';
import * as i18n from './translations';
import { FinalReadOnly } from '../final_readonly/final_readonly';
import { FinalEdit } from '../final_edit/final_edit';
import { FinalSideMode } from './constants';
import type { UpgradeableDiffableFields } from '../../../../model/prebuilt_rule_upgrade/fields';
import { assertUnreachable } from '../../../../../../../common/utility_types';
import { FinalSideContextProvider, useFinalSideContext } from './final_side_context';
interface FinalSideProps {
fieldName: string;
finalDiffableRule: DiffableRule;
fieldName: UpgradeableDiffableFields;
}
export function FinalSide({ fieldName, finalDiffableRule }: FinalSideProps): JSX.Element {
export function FinalSide({ fieldName }: FinalSideProps): JSX.Element {
return (
<>
<SideHeader>
@ -29,7 +32,22 @@ export function FinalSide({ fieldName, finalDiffableRule }: FinalSideProps): JSX
</h3>
</EuiTitle>
</SideHeader>
<FieldReadOnly fieldName={fieldName} finalDiffableRule={finalDiffableRule} />
<FinalSideContextProvider fieldName={fieldName}>
<FinalSideContent />
</FinalSideContextProvider>
</>
);
}
function FinalSideContent(): JSX.Element {
const { mode } = useFinalSideContext();
switch (mode) {
case FinalSideMode.READONLY:
return <FinalReadOnly />;
case FinalSideMode.EDIT:
return <FinalEdit />;
default:
return assertUnreachable(mode);
}
}

View file

@ -0,0 +1,50 @@
/*
* 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, { createContext, useContext, type PropsWithChildren, useCallback } from 'react';
import { invariant } from '../../../../../../../common/utils/invariant';
import { FinalSideMode } from './constants';
import type { UpgradeableDiffableFields } from '../../../../model/prebuilt_rule_upgrade/fields';
interface FinalSideContextType {
fieldName: UpgradeableDiffableFields;
mode: FinalSideMode;
setReadOnlyMode: () => void;
setEditMode: () => void;
}
const FinalSideContext = createContext<FinalSideContextType | null>(null);
interface FinalSideContextProviderProps {
fieldName: UpgradeableDiffableFields;
}
export function FinalSideContextProvider({
children,
fieldName,
}: PropsWithChildren<FinalSideContextProviderProps>) {
const [mode, setMode] = React.useState<FinalSideMode>(FinalSideMode.READONLY);
const setReadOnlyMode = useCallback(() => setMode(FinalSideMode.READONLY), []);
const setEditMode = useCallback(() => setMode(FinalSideMode.EDIT), []);
const contextValue = {
fieldName,
setReadOnlyMode,
setEditMode,
mode,
};
return <FinalSideContext.Provider value={contextValue}>{children}</FinalSideContext.Provider>;
}
export function useFinalSideContext() {
const context = useContext(FinalSideContext);
invariant(context !== null, 'useFinalSideContext must be used inside a FinalSideContextProvider');
return context;
}

View file

@ -13,6 +13,7 @@ import type {
} from '../../../model/prebuilt_rule_upgrade';
import { RuleUpgradeInfoBar } from './components/rule_upgrade_info_bar';
import { RuleUpgradeConflictsResolver } from './components/rule_upgrade_conflicts_resolver';
import { DiffableRuleContextProvider } from './diffable_rule_context';
import { RuleUpgradeCallout } from './components/rule_upgrade_callout';
interface RuleUpgradeConflictsResolverTabProps {
@ -25,16 +26,16 @@ export function RuleUpgradeConflictsResolverTab({
setRuleFieldResolvedValue,
}: RuleUpgradeConflictsResolverTabProps): JSX.Element {
return (
<>
<DiffableRuleContextProvider
finalDiffableRule={ruleUpgradeState.finalRule}
setRuleFieldResolvedValue={setRuleFieldResolvedValue}
>
<EuiSpacer size="s" />
<RuleUpgradeInfoBar ruleUpgradeState={ruleUpgradeState} />
<EuiSpacer size="s" />
<RuleUpgradeCallout ruleUpgradeState={ruleUpgradeState} />
<EuiSpacer size="s" />
<RuleUpgradeConflictsResolver
ruleUpgradeState={ruleUpgradeState}
setRuleFieldResolvedValue={setRuleFieldResolvedValue}
/>
</>
<RuleUpgradeConflictsResolver ruleUpgradeState={ruleUpgradeState} />
</DiffableRuleContextProvider>
);
}

View file

@ -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 { i18n } from '@kbn/i18n';
export const CANCEL_BUTTON_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.upgradeRules.cancelButtonLabel',
{
defaultMessage: 'Cancel',
}
);
export const SAVE_BUTTON_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.upgradeRules.saveButtonLabel',
{
defaultMessage: 'Save',
}
);
export const EDIT_BUTTON_LABEL = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.upgradeRules.editButtonLabel',
{
defaultMessage: 'Edit',
}
);

View file

@ -0,0 +1,82 @@
/*
* 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 { DiffableCommonFields } from '../../../../../common/api/detection_engine';
import type {
DiffableCustomQueryFields,
DiffableEqlFields,
DiffableEsqlFields,
DiffableMachineLearningFields,
DiffableNewTermsFields,
DiffableSavedQueryFields,
DiffableThreatMatchFields,
DiffableThresholdFields,
RuleFieldsDiff,
} from '../../../../../common/api/detection_engine';
export type NonUpgradeableDiffableFields = (typeof NON_UPGRADEABLE_DIFFABLE_FIELDS)[number];
export type UpgradeableDiffableFields = Exclude<keyof RuleFieldsDiff, NonUpgradeableDiffableFields>;
export type UpgradeableCommonFields = Exclude<
keyof DiffableCommonFields,
NonUpgradeableDiffableFields
>;
export type UpgradeableCustomQueryFields = Exclude<
keyof DiffableCustomQueryFields,
NonUpgradeableDiffableFields
>;
export type UpgradeableSavedQueryFields = Exclude<
keyof DiffableSavedQueryFields,
NonUpgradeableDiffableFields
>;
export type UpgradeableEqlFields = Exclude<keyof DiffableEqlFields, NonUpgradeableDiffableFields>;
export type UpgradeableEsqlFields = Exclude<keyof DiffableEsqlFields, NonUpgradeableDiffableFields>;
export type UpgradeableThreatMatchFields = Exclude<
keyof DiffableThreatMatchFields,
NonUpgradeableDiffableFields
>;
export type UpgradeableThresholdFields = Exclude<
keyof DiffableThresholdFields,
NonUpgradeableDiffableFields
>;
export type UpgradeableMachineLearningFields = Exclude<
keyof DiffableMachineLearningFields,
NonUpgradeableDiffableFields
>;
export type UpgradeableNewTermsFields = Exclude<
keyof DiffableNewTermsFields,
NonUpgradeableDiffableFields
>;
export const NON_UPGRADEABLE_DIFFABLE_FIELDS = [
'author',
'license',
'rule_id',
'type',
'version',
] as const;
export const COMMON_FIELD_NAMES = DiffableCommonFields.keyof().options;
export function isCommonFieldName(fieldName: string): fieldName is keyof DiffableCommonFields {
return (COMMON_FIELD_NAMES as string[]).includes(fieldName);
}
export function isNonUpgradeableFieldName(
fieldName: string
): fieldName is NonUpgradeableDiffableFields {
return (NON_UPGRADEABLE_DIFFABLE_FIELDS as Readonly<string[]>).includes(fieldName);
}

View file

@ -5,12 +5,15 @@
* 2.0.
*/
import type { DiffableAllFields, RuleObjectId } from '../../../../../common/api/detection_engine';
import type {
DiffableAllFields,
RuleSignatureId,
} from '../../../../../common/api/detection_engine';
export type SetRuleFieldResolvedValueFn<
FieldName extends keyof DiffableAllFields = keyof DiffableAllFields
> = (params: {
ruleId: RuleObjectId;
ruleId: RuleSignatureId;
fieldName: FieldName;
resolvedValue: DiffableAllFields[FieldName];
}) => void;

View file

@ -12,12 +12,12 @@ import type {
SetRuleFieldResolvedValueFn,
} from '../../../../rule_management/model/prebuilt_rule_upgrade';
import { FieldUpgradeState } from '../../../../rule_management/model/prebuilt_rule_upgrade';
import type { FieldsDiff } from '../../../../../../common/api/detection_engine';
import {
ThreeWayDiffConflict,
type FieldsDiff,
type DiffableAllFields,
type DiffableRule,
type RuleUpgradeInfoForReview,
ThreeWayDiffConflict,
} from '../../../../../../common/api/detection_engine';
import { convertRuleToDiffable } from '../../../../../../common/detection_engine/prebuilt_rules/diff/convert_rule_to_diffable';
@ -33,6 +33,7 @@ export function usePrebuiltRulesUpgradeState(
ruleUpgradeInfos: RuleUpgradeInfoForReview[]
): UseRulesUpgradeStateResult {
const [rulesResolvedConflicts, setRulesResolvedConflicts] = useState<RulesResolvedConflicts>({});
const setRuleFieldResolvedValue = useCallback(
(...[params]: Parameters<SetRuleFieldResolvedValueFn>) => {
setRulesResolvedConflicts((prevRulesResolvedConflicts) => ({
@ -45,6 +46,7 @@ export function usePrebuiltRulesUpgradeState(
},
[]
);
const rulesUpgradeState = useMemo(() => {
const state: RulesUpgradeState = {};
@ -88,7 +90,7 @@ function calcFinalDiffableRule(
}
/**
* Assembles a `DiffableRule` from rule fields diff `merge_value`s.
* Assembles a `DiffableRule` from rule fields diff `merged_version`s.
*/
function convertRuleFieldsDiffToDiffable(
ruleFieldsDiff: FieldsDiff<Record<string, unknown>>

View file

@ -9,7 +9,7 @@ import type { ReactNode } from 'react';
import React, { useCallback, useState, useMemo } from 'react';
import type { EuiTabbedContentTab } from '@elastic/eui';
import { invariant } from '../../../../../common/utils/invariant';
import type { RuleObjectId } from '../../../../../common/api/detection_engine';
import type { RuleSignatureId } from '../../../../../common/api/detection_engine';
import type { RuleResponse } from '../../../../../common/api/detection_engine/model/rule_schema';
import { RuleDetailsFlyout } from '../../../rule_management/components/rule_details/rule_details_flyout';
@ -30,7 +30,7 @@ interface RulePreviewFlyoutProps {
interface UseRulePreviewFlyoutResult {
rulePreviewFlyout: ReactNode;
openRulePreview: (ruleId: RuleObjectId) => void;
openRulePreview: (ruleId: RuleSignatureId) => void;
closeRulePreview: () => void;
}
@ -64,7 +64,7 @@ export function useRulePreviewFlyout({
/>
),
openRulePreview: useCallback(
(ruleId: RuleObjectId) => {
(ruleId: RuleSignatureId) => {
const ruleToShowInFlyout = rules.find((x) => x.id === ruleId);
invariant(ruleToShowInFlyout, `Rule with id ${ruleId} not found`);