[9.0] [Security Solution][Detection Engine] fixes rule preview works for form's invalid state (#173930) (#213801) (#214744)

# Backport

This will backport the following commits from `main` to `9.0`:
- [[Security Solution][Detection Engine] fixes rule preview works for
form's invalid state (#173930)
(#213801)](https://github.com/elastic/kibana/pull/213801)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Edgar
Santos","email":"edgar.santos@elastic.co"},"sourceCommit":{"committedDate":"2025-03-17T12:56:13Z","message":"[Security
Solution][Detection Engine] fixes rule preview works for form's invalid
state (#173930) (#213801)\n\n# Summary\nThis PR changes the behavior of
the Preview rule button in the create\nrule form. Now the button will
always be enabled, but upon clicking it\nthe validation of the define
rule form will be triggered. If the form is\ninvalid, it will show a
warning redirecting the user to the form in\norder to fix the
errors.\n\nThis PR removes the helper function
`getIsRulePreviewDisabled` after\nverifying that all the validations
that it was doing are already covered\nby the validation rules of the
define rule form.\n\n\nThis solves issue
#173930\n\n---------\n\nCo-authored-by: Nastasha Solomon
<79124755+nastasha-solomon@users.noreply.github.com>","sha":"07baf3a79f5d2e37ba2a4823c3c4ef339684959d","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","v9.0.0","Feature:Detection
Rules","Feature:Detection Rule Preview","Team:Detection
Engine","backport:version","v8.18.0","v9.1.0","v8.17.4"],"title":"[Security
Solution][Detection Engine] fixes rule preview works for form's invalid
state
(#173930)","number":213801,"url":"https://github.com/elastic/kibana/pull/213801","mergeCommit":{"message":"[Security
Solution][Detection Engine] fixes rule preview works for form's invalid
state (#173930) (#213801)\n\n# Summary\nThis PR changes the behavior of
the Preview rule button in the create\nrule form. Now the button will
always be enabled, but upon clicking it\nthe validation of the define
rule form will be triggered. If the form is\ninvalid, it will show a
warning redirecting the user to the form in\norder to fix the
errors.\n\nThis PR removes the helper function
`getIsRulePreviewDisabled` after\nverifying that all the validations
that it was doing are already covered\nby the validation rules of the
define rule form.\n\n\nThis solves issue
#173930\n\n---------\n\nCo-authored-by: Nastasha Solomon
<79124755+nastasha-solomon@users.noreply.github.com>","sha":"07baf3a79f5d2e37ba2a4823c3c4ef339684959d"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.18","8.17"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.18","label":"v8.18.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/213801","number":213801,"mergeCommit":{"message":"[Security
Solution][Detection Engine] fixes rule preview works for form's invalid
state (#173930) (#213801)\n\n# Summary\nThis PR changes the behavior of
the Preview rule button in the create\nrule form. Now the button will
always be enabled, but upon clicking it\nthe validation of the define
rule form will be triggered. If the form is\ninvalid, it will show a
warning redirecting the user to the form in\norder to fix the
errors.\n\nThis PR removes the helper function
`getIsRulePreviewDisabled` after\nverifying that all the validations
that it was doing are already covered\nby the validation rules of the
define rule form.\n\n\nThis solves issue
#173930\n\n---------\n\nCo-authored-by: Nastasha Solomon
<79124755+nastasha-solomon@users.noreply.github.com>","sha":"07baf3a79f5d2e37ba2a4823c3c4ef339684959d"}},{"branch":"8.17","label":"v8.17.4","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Edgar Santos <edgar.santos@elastic.co>
This commit is contained in:
Kibana Machine 2025-03-17 15:59:23 +01:00 committed by GitHub
parent c8e9793ab3
commit 266bcefdc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 79 additions and 425 deletions

View file

@ -6,8 +6,7 @@
*/
import moment from 'moment';
import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types';
import { isNoisy, getTimeframeOptions, getIsRulePreviewDisabled } from './helpers';
import { isNoisy, getTimeframeOptions } from './helpers';
describe('query_preview/helpers', () => {
const timeframeEnd = moment();
@ -84,282 +83,6 @@ describe('query_preview/helpers', () => {
});
});
describe('isRulePreviewDisabled', () => {
test('disabled when there is no index', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: [],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [
{ entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] },
],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when query bar is invalid', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: false,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [
{ entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] },
],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when threat query bar is invalid', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: true,
isThreatQueryBarValid: false,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [
{ entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] },
],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when there is no threat index', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: [],
threatMapping: [
{ entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] },
],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when there is no threat mapping', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when there is no machine learning job id', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [],
machineLearningJobId: [],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when eql rule with no query', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'eql',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [],
machineLearningJobId: [],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when new_terms rule with no fields', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'new_terms',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: [],
threatMapping: [],
machineLearningJobId: [],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('enabled', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'threat_match',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [
{ entries: [{ field: 'test-field', value: 'test-value', type: 'mapping' }] },
],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: 'testlang' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(false);
});
test('enabled when eql rule with query', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'eql',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: ['threat-*'],
threatMapping: [],
machineLearningJobId: [],
queryBar: {
filters: [],
query: { query: 'any where true', language: 'testlang' },
saved_id: null,
},
newTermsFields: [],
});
expect(isDisabled).toEqual(false);
});
test('disabled when eql rule with empty query and non-empty filters', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'eql',
isQueryBarValid: true,
isThreatQueryBarValid: false,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: [],
threatMapping: [],
machineLearningJobId: [],
queryBar: {
filters: [
{
meta: {},
query: {
exists: {
field: '_index',
},
},
},
],
query: { query: '', language: 'eql' },
saved_id: null,
},
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('disabled when eql rule with empty query and empty filters', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'eql',
isQueryBarValid: true,
isThreatQueryBarValid: false,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: [],
threatMapping: [],
machineLearningJobId: [],
queryBar: {
filters: [],
query: { query: '', language: 'eql' },
saved_id: null,
},
newTermsFields: [],
});
expect(isDisabled).toEqual(true);
});
test('enabled when eql rule with non empty query', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'eql',
isQueryBarValid: true,
isThreatQueryBarValid: false,
index: ['test-*'],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: [],
threatMapping: [],
machineLearningJobId: [],
queryBar: {
filters: [],
query: { query: 'any where true', language: 'eql' },
saved_id: null,
},
newTermsFields: [],
});
expect(isDisabled).toEqual(false);
});
// ML rule does not have index or data view id properties, so preview should not depend on these fields
test('enabled for ML rule when index patterns and data view id are empty', () => {
const isDisabled = getIsRulePreviewDisabled({
ruleType: 'machine_learning',
isQueryBarValid: true,
isThreatQueryBarValid: true,
index: [],
dataViewId: undefined,
dataSourceType: DataSourceType.IndexPatterns,
threatIndex: [],
threatMapping: [],
machineLearningJobId: ['test-ml-job-id'],
queryBar: { filters: [], query: { query: '', language: '' }, saved_id: null },
newTermsFields: [],
});
expect(isDisabled).toEqual(false);
});
});
describe('getTimeframeOptions', () => {
test('returns hour and day options if ruleType is eql', () => {
const options = getTimeframeOptions('eql');

View file

@ -5,16 +5,11 @@
* 2.0.
*/
import { isEmpty } from 'lodash';
import type { EuiSelectOption } from '@elastic/eui';
import type { Type, ThreatMapping } from '@kbn/securitysolution-io-ts-alerting-types';
import type { Type } from '@kbn/securitysolution-io-ts-alerting-types';
import * as i18n from './translations';
import type { FieldValueQueryBar } from '../query_bar_field';
import type { TimeframePreviewOptions } from '../../../../detections/pages/detection_engine/rules/types';
import { DataSourceType } from '../../../../detections/pages/detection_engine/rules/types';
import { MAX_NUMBER_OF_NEW_TERMS_FIELDS } from '../../../../../common/constants';
/**
* Determines whether or not to display noise warning.
* Is considered noisy if alerts/hour rate > 1
@ -58,97 +53,3 @@ export const getTimeframeOptions = (ruleType: Type): EuiSelectOption[] => {
];
}
};
const isNewTermsPreviewDisabled = (newTermsFields: string[]): boolean => {
return newTermsFields.length === 0 || newTermsFields.length > MAX_NUMBER_OF_NEW_TERMS_FIELDS;
};
const isEsqlPreviewDisabled = ({
isQueryBarValid,
queryBar,
}: {
queryBar: FieldValueQueryBar;
isQueryBarValid: boolean;
}): boolean => {
return !isQueryBarValid || isEmpty(queryBar.query.query);
};
const isThreatMatchPreviewDisabled = ({
isThreatQueryBarValid,
threatIndex,
threatMapping,
}: {
threatIndex: string[];
threatMapping: ThreatMapping;
isThreatQueryBarValid: boolean;
}): boolean => {
if (!isThreatQueryBarValid || !threatIndex.length || !threatMapping) {
return true;
} else if (
!threatMapping.length ||
!threatMapping[0].entries?.length ||
!threatMapping[0].entries[0].field ||
!threatMapping[0].entries[0].value
) {
return true;
}
return false;
};
export const getIsRulePreviewDisabled = ({
ruleType,
isQueryBarValid,
isThreatQueryBarValid,
index,
dataViewId,
dataSourceType,
threatIndex,
threatMapping,
machineLearningJobId,
queryBar,
newTermsFields = [],
}: {
ruleType: Type;
isQueryBarValid: boolean;
isThreatQueryBarValid: boolean;
index: string[];
dataViewId: string | undefined;
dataSourceType: DataSourceType;
threatIndex: string[];
threatMapping: ThreatMapping;
machineLearningJobId: string[] | undefined;
queryBar: FieldValueQueryBar;
newTermsFields: string[];
}) => {
if (ruleType === 'esql') {
return isEsqlPreviewDisabled({ isQueryBarValid, queryBar });
}
if (ruleType === 'machine_learning') {
return !machineLearningJobId ?? machineLearningJobId?.length === 0;
}
if (
!isQueryBarValid ||
(dataSourceType === DataSourceType.DataView && !dataViewId) ||
(dataSourceType === DataSourceType.IndexPatterns && index.length === 0)
) {
return true;
}
if (ruleType === 'threat_match') {
return isThreatMatchPreviewDisabled({
threatIndex,
threatMapping,
isThreatQueryBarValid,
});
}
if (ruleType === 'eql') {
return isEmpty(queryBar.query.query);
}
if (ruleType === 'query' || ruleType === 'threshold') {
return isEmpty(queryBar.query.query) && isEmpty(queryBar.filters);
}
if (ruleType === 'new_terms') {
return isNewTermsPreviewDisabled(newTermsFields);
}
return false;
};

View file

@ -39,6 +39,8 @@ jest.mock('../../../../common/hooks/use_experimental_features', () => ({
useIsExperimentalFeatureEnabled: jest.fn(),
}));
const verifyRuleDefinitionMock = jest.fn().mockResolvedValue(true);
// rule types that do not support logged requests
const doNotSupportLoggedRequests: Type[] = ['threat_match'];
@ -59,6 +61,7 @@ const getMockIndexPattern = (): DataViewBase => ({
});
const defaultProps: RulePreviewProps = {
verifyRuleDefinition: verifyRuleDefinitionMock,
defineRuleData: {
...stepDefineDefaultValue,
ruleType: 'threat_match',
@ -155,6 +158,18 @@ describe('PreviewQuery', () => {
expect(await wrapper.findByTestId('previewInvocationCountWarning')).toBeTruthy();
});
test('it renders a warning when the definition step form is invalid', async () => {
verifyRuleDefinitionMock.mockResolvedValueOnce(false);
const wrapper = render(
<TestProviders>
<RulePreview {...defaultProps} />
</TestProviders>
);
(await wrapper.findByTestId('previewSubmitButton')).click();
expect(await wrapper.findByTestId('previewRuleDefinitionInvalidWarning')).toBeTruthy();
});
supportLoggedRequests.forEach((ruleType) => {
test(`renders "Show Elasticsearch requests" for ${ruleType} rule type`, () => {
render(

View file

@ -65,7 +65,7 @@ const timeRanges = [
];
export interface RulePreviewProps {
isDisabled?: boolean;
verifyRuleDefinition: () => Promise<boolean>;
defineRuleData: DefineStepRule;
aboutRuleData: AboutStepRule;
scheduleRuleData: ScheduleStepRule;
@ -88,7 +88,7 @@ const refreshedTimeframe = (startDate: string, endDate: string) => {
};
const RulePreviewComponent: React.FC<RulePreviewProps> = ({
isDisabled,
verifyRuleDefinition,
defineRuleData,
aboutRuleData,
scheduleRuleData,
@ -116,6 +116,8 @@ const RulePreviewComponent: React.FC<RulePreviewProps> = ({
const [isDateRangeInvalid, setIsDateRangeInvalid] = useState(false);
const [showRuleDefitnionInvalidWarning, setShowRuleDefinitionInvalidWarning] = useState(false);
const isLoggedRequestsSupported = RULE_TYPES_SUPPORTING_LOGGED_REQUESTS.includes(ruleType);
useEffect(() => {
@ -189,7 +191,7 @@ const RulePreviewComponent: React.FC<RulePreviewProps> = ({
[]
);
const onTimeframeRefresh = useCallback(() => {
const triggerTimeFrameRefresh = useCallback(() => {
startTransaction({ name: SINGLE_RULE_ACTIONS.PREVIEW });
const { start, end } = refreshedTimeframe(startDate, endDate);
setTimeframeStart(start);
@ -218,6 +220,22 @@ const RulePreviewComponent: React.FC<RulePreviewProps> = ({
isLoggedRequestsSupported,
]);
const onPreviewTriggered = useCallback(() => {
setIsRefreshing(true);
const doVerification = async () => {
const isValid = await verifyRuleDefinition();
if (!isValid) {
setShowRuleDefinitionInvalidWarning(true);
setIsRefreshing(false);
return;
}
setShowRuleDefinitionInvalidWarning(false);
triggerTimeFrameRefresh();
};
doVerification();
}, [triggerTimeFrameRefresh, verifyRuleDefinition]);
const isDirty = useMemo(
() =>
!timeframeStart.isSame(previewData.timeframeOptions.timeframeStart) ||
@ -261,6 +279,18 @@ const RulePreviewComponent: React.FC<RulePreviewProps> = ({
<EuiSpacer />
</>
)}
{showRuleDefitnionInvalidWarning && (
<>
<EuiCallOut
color="warning"
title={i18n.QUERY_PREVIEW_RULE_DEFINITION_INVALID_WARNING_TITLE}
data-test-subj="previewRuleDefinitionInvalidWarning"
>
{i18n.QUERY_PREVIEW_RULE_DEFINITION_INVALID_WARNING_MESSAGE}
</EuiCallOut>
<EuiSpacer />
</>
)}
<EuiSpacer size="xs" />
<EuiFormRow label={i18n.QUERY_PREVIEW_LABEL}>
<EuiFlexGroup alignItems="center" gutterSize="s" responsive>
@ -268,20 +298,19 @@ const RulePreviewComponent: React.FC<RulePreviewProps> = ({
<EuiSuperDatePicker
start={startDate}
end={endDate}
isDisabled={isDisabled}
onTimeChange={onTimeChange}
showUpdateButton={false}
commonlyUsedRanges={timeRanges}
onRefresh={onTimeframeRefresh}
onRefresh={onPreviewTriggered}
data-test-subj="preview-time-frame"
width="full"
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiSuperUpdateButton
isDisabled={isDateRangeInvalid || isDisabled}
isDisabled={isDateRangeInvalid}
iconType={isDirty ? 'kqlFunction' : 'refresh'}
onClick={onTimeframeRefresh}
onClick={onPreviewTriggered}
color={isDirty ? 'success' : 'primary'}
fill={true}
data-test-subj="previewSubmitButton"

View file

@ -86,6 +86,20 @@ export const QUERY_PREVIEW_INVOCATION_COUNT_WARNING_MESSAGE = i18n.translate(
}
);
export const QUERY_PREVIEW_RULE_DEFINITION_INVALID_WARNING_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.queryPreview.queryPreviewRuleDefinitionInvalidWarningTitle',
{
defaultMessage: 'Rule definition is invalid',
}
);
export const QUERY_PREVIEW_RULE_DEFINITION_INVALID_WARNING_MESSAGE = i18n.translate(
'xpack.securitySolution.detectionEngine.queryPreview.queryPreviewRuleDefinitionInvalidWarningMessage',
{
defaultMessage: `One or more rule definition fields have validation errors that must be fixed before previewing this rule. To learn more, check the error messages for the invalid rule definition fields.`,
}
);
export const QUERY_GRAPH_COUNT = i18n.translate(
'xpack.securitySolution.detectionEngine.queryPreview.queryGraphCountLabel',
{

View file

@ -78,7 +78,6 @@ import {
} from '../../../../../common/constants';
import { useKibana, useUiSetting$ } from '../../../../common/lib/kibana';
import { RulePreview } from '../../components/rule_preview';
import { getIsRulePreviewDisabled } from '../../components/rule_preview/helpers';
import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs';
import { VALIDATION_WARNING_CODE_FIELD_NAME_MAP } from '../../../rule_creation/constants/validation_warning_codes';
import { extractValidationMessages } from '../../../rule_creation/logic/extract_validation_messages';
@ -219,24 +218,6 @@ const CreateRulePageComponent: React.FC = () => {
const defineFieldsTransform = useExperimentalFeatureFieldsTransform<DefineStepRule>();
const defineStepFormFields = defineStepForm.getFields();
const isPreviewDisabled = getIsRulePreviewDisabled({
ruleType,
isQueryBarValid,
isThreatQueryBarValid:
defineStepFormFields.threatIndex?.isValid &&
defineStepFormFields.threatQueryBar?.isValid &&
defineStepFormFields.threatMapping?.isValid,
index: memoizedIndex,
dataViewId: defineStepData.dataViewId,
dataSourceType: defineStepData.dataSourceType,
threatIndex: defineStepData.threatIndex,
threatMapping: defineStepData.threatMapping,
machineLearningJobId: defineStepData.machineLearningJobId,
queryBar: defineStepData.queryBar,
newTermsFields: defineStepData.newTermsFields,
});
useEffect(() => {
if (prevRuleType !== ruleType) {
aboutStepForm.updateFieldValues({
@ -380,6 +361,11 @@ const CreateRulePageComponent: React.FC = () => {
return { valid, warnings };
}, [validateStep]);
const verifyRuleDefinitionForPreview = useCallback(
() => defineStepForm.validate(),
[defineStepForm]
);
const editStep = useCallback(
async (step: RuleStep) => {
const { valid } = await validateStep(activeStep);
@ -921,7 +907,7 @@ const CreateRulePageComponent: React.FC = () => {
onToggleCollapsed={onToggleCollapsedMemo}
>
<RulePreview
isDisabled={isPreviewDisabled && activeStep === RuleStep.defineRule}
verifyRuleDefinition={verifyRuleDefinitionForPreview}
defineRuleData={defineStepData}
aboutRuleData={aboutStepData}
scheduleRuleData={scheduleStepData}

View file

@ -28,7 +28,6 @@ import { useConfirmValidationErrorsModal } from '../../../../common/hooks/use_co
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { isEsqlRule } from '../../../../../common/detection_engine/utils';
import { RulePreview } from '../../components/rule_preview';
import { getIsRulePreviewDisabled } from '../../components/rule_preview/helpers';
import type {
RuleResponse,
RuleUpdateProps,
@ -154,24 +153,6 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
[defineStepData.index, esqlIndex, defineStepData.ruleType]
);
const defineStepFormFields = defineStepForm.getFields();
const isPreviewDisabled = getIsRulePreviewDisabled({
ruleType: defineStepData.ruleType,
isQueryBarValid,
isThreatQueryBarValid:
defineStepFormFields.threatIndex?.isValid &&
defineStepFormFields.threatQueryBar?.isValid &&
defineStepFormFields.threatMapping?.isValid,
index: memoizedIndex,
dataViewId: defineStepData.dataViewId,
dataSourceType: defineStepData.dataSourceType,
threatIndex: defineStepData.threatIndex,
threatMapping: defineStepData.threatMapping,
machineLearningJobId: defineStepData.machineLearningJobId,
queryBar: defineStepData.queryBar,
newTermsFields: defineStepData.newTermsFields,
});
const loading = userInfoLoading || listsConfigLoading;
const { isSavedQueryLoading, savedQuery } = useGetSavedQuery({
savedQueryId: 'saved_id' in rule ? rule.saved_id : undefined,
@ -519,6 +500,11 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
),
});
const verifyRuleDefinitionForPreview = useCallback(
() => defineStepForm.validate(),
[defineStepForm]
);
if (
redirectToDetections(
isSignalIndexExists,
@ -631,7 +617,7 @@ const EditRulePageComponent: FC<{ rule: RuleResponse }> = ({ rule }) => {
onToggleCollapsed={() => setIsRulePreviewVisible((isVisible) => !isVisible)}
>
<RulePreview
isDisabled={isPreviewDisabled}
verifyRuleDefinition={verifyRuleDefinitionForPreview}
defineRuleData={defineStepData}
aboutRuleData={aboutStepData}
scheduleRuleData={scheduleStepData}