Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 const ruleFormKeys = {
all: ['ruleForm'] as const,
queryColumns: (query: string) => [...ruleFormKeys.all, 'queryColumns', query] as const,
dataFields: (query: string) => [...ruleFormKeys.all, 'dataFields', query] as const,
};
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ describe('useCreateRule', () => {
// Note: empty condition field is omitted from the payload
const expectedApiPayload = {
kind: 'signal',
time_field: '@timestamp',
metadata: {
name: 'Test Rule',
labels: ['tag1', 'tag2'],
},
time_field: '@timestamp',
schedule: { every: '5m', lookback: '1m' },
evaluation: {
query: {
Expand Down Expand Up @@ -362,11 +362,11 @@ describe('useCreateRule', () => {
// Note: empty condition field is omitted from the payload
const expectedPayload = {
kind: 'signal',
time_field: 'event.timestamp',
metadata: {
name: 'Complex Rule',
labels: ['production', 'critical'],
},
time_field: 'event.timestamp',
schedule: { every: '1m', lookback: '1m' },
evaluation: {
query: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,119 +7,21 @@

import { useMutation } from '@kbn/react-query';
import type { HttpStart, NotificationsStart } from '@kbn/core/public';
import type { CreateRuleData, RuleResponse } from '@kbn/alerting-v2-schemas';
import type { RuleResponse } from '@kbn/alerting-v2-schemas';
import type { FormValues } from '../types';
import { mapFormValuesToCreateRequest } from '../utils/rule_request_mappers';

interface UseCreateRuleProps {
http: HttpStart;
notifications: NotificationsStart;
onSuccess?: () => void;
}

/**
* Builds the `recovery_policy.query` portion of the API payload.
*
* Two modes:
* 1. **Condition mode** – The user specified an evaluation condition (WHERE clause)
* and wrote a recovery condition. The base query for recovery is the same
* evaluation base query.
* 2. **Full-query mode** – The user wrote a standalone recovery base query
* (no evaluation condition was split out).
*/
const buildRecoveryQuery = (
recoveryPolicy: NonNullable<FormValues['recoveryPolicy']>,
evaluation: FormValues['evaluation']
): { query: { base: string; condition?: string } } | Record<string, never> => {
const { query } = recoveryPolicy;

// Condition-only mode: recovery WHERE clause applied to the evaluation base query
if (query?.condition) {
return {
query: {
base: query.base || evaluation.query.base,
condition: query.condition,
},
};
}

// Full-query mode: user provided a standalone recovery base query
if (query?.base) {
return { query: { base: query.base } };
}

return {};
};

/**
* Maps form values to the API request payload.
* This function serves as the boundary between the form contract (FormValues)
* and the API contract (CreateRuleData).
*/
const mapFormValuesToCreateRuleData = (formValues: FormValues): CreateRuleData => {
const {
kind,
metadata,
timeField,
schedule,
evaluation,
grouping,
recoveryPolicy,
stateTransition,
} = formValues;

const hasStateTransition =
kind === 'alert' &&
stateTransition != null &&
(stateTransition.pendingCount != null || stateTransition.pendingTimeframe != null);

return {
kind,
time_field: timeField,
metadata: {
name: metadata.name,
owner: metadata.owner,
labels: metadata.labels,
},
schedule: {
every: schedule.every,
lookback: schedule.lookback,
},
evaluation: {
query: {
base: evaluation.query.base,
...(evaluation.query.condition ? { condition: evaluation.query.condition } : {}),
},
},
...(grouping?.fields?.length ? { grouping: { fields: grouping.fields } } : {}),
...(recoveryPolicy
? {
recovery_policy: {
type: recoveryPolicy.type,
...(recoveryPolicy.type === 'query'
? buildRecoveryQuery(recoveryPolicy, evaluation)
: {}),
},
}
: {}),
...(hasStateTransition
? {
state_transition: {
pending_count: stateTransition!.pendingCount,
...(stateTransition!.pendingTimeframe != null
? { pending_timeframe: stateTransition!.pendingTimeframe }
: {}),
},
}
: {}),
};
};

export const useCreateRule = ({ http, notifications, onSuccess }: UseCreateRuleProps) => {
const mutation = useMutation(
(formValues: FormValues) => {
const ruleData = mapFormValuesToCreateRuleData(formValues);
return http.post<RuleResponse>('/internal/alerting/v2/rule', {
body: JSON.stringify(ruleData),
body: JSON.stringify(mapFormValuesToCreateRequest(formValues)),
});
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import type { DataViewFieldMap } from '@kbn/data-views-plugin/common';
import { useQuery } from '@kbn/react-query';
import { getESQLAdHocDataview } from '@kbn/esql-utils';
import { ruleFormKeys } from './query_key_factory';

interface UseDataFieldsProps {
query: string;
Expand All @@ -24,7 +25,7 @@ export const useDataFields = ({ query, http, dataViews, onSuccess }: UseDataFiel
onSuccessRef.current = onSuccess;

const fieldsQuery = useQuery({
queryKey: ['dataFields', query],
queryKey: ruleFormKeys.dataFields(query),
queryFn: async () => {
const dataView = await getESQLAdHocDataview({
dataViewsService: dataViews,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useEffect, useRef } from 'react';
import type { ISearchGeneric } from '@kbn/search-types';
import { useQuery } from '@kbn/react-query';
import { getESQLQueryColumnsRaw } from '@kbn/esql-utils';
import { ruleFormKeys } from './query_key_factory';

export interface QueryColumn {
name: string;
Expand All @@ -26,7 +27,7 @@ export const useQueryColumns = ({ query, search, onSuccess }: UseQueryColumnsPro
onSuccessRef.current = onSuccess;

const columnsQuery = useQuery({
queryKey: ['queryColumns', query],
queryKey: ruleFormKeys.queryColumns(query),
queryFn: async ({ signal }) => {
return getESQLQueryColumnsRaw({
esqlQuery: query,
Expand Down
Loading
Loading