diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx index 985b0c303cd21..ee80e467e7fb4 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx @@ -19,6 +19,7 @@ import { RuleCreationValidConsumer, STACK_ALERTS_FEATURE_ID, } from '@kbn/rule-data-utils'; +import { RuleTypeMetaData } from '@kbn/alerting-plugin/common'; import { DiscoverStateContainer } from '../../services/discover_state'; import { DiscoverServices } from '../../../../build_services'; @@ -42,7 +43,7 @@ interface AlertsPopoverProps { isPlainRecord?: boolean; } -interface EsQueryAlertMetaData { +interface EsQueryAlertMetaData extends RuleTypeMetaData { isManagementPage?: boolean; adHocDataViewList: DataView[]; } @@ -110,11 +111,11 @@ export function AlertsPopover({ metadata: discoverMetadata, consumer: 'alerts', onClose: (_, metadata) => { - onFinishFlyoutInteraction(metadata as EsQueryAlertMetaData); + onFinishFlyoutInteraction(metadata!); onClose(); }, onSave: async (metadata) => { - onFinishFlyoutInteraction(metadata as EsQueryAlertMetaData); + onFinishFlyoutInteraction(metadata!); }, canChangeTrigger: false, ruleTypeId: ES_QUERY_ID, diff --git a/src/plugins/unified_search/public/filter_badge/filter_badge.tsx b/src/plugins/unified_search/public/filter_badge/filter_badge.tsx index 7b20eab971e9c..e20429d5e9f36 100644 --- a/src/plugins/unified_search/public/filter_badge/filter_badge.tsx +++ b/src/plugins/unified_search/public/filter_badge/filter_badge.tsx @@ -67,7 +67,7 @@ function FilterBadge({ `} > - {!hideAlias && filter.meta.alias !== null ? ( + {filter.meta.alias && !hideAlias ? ( <> {prefix} diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 7cec5bdbdd7a6..6a66b39720402 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -19,6 +19,7 @@ export type { ActionVariable } from '@kbn/alerting-types'; export type RuleTypeState = Record; export type RuleTypeParams = Record; +export type RuleTypeMetaData = Record; // rule type defined alert fields to persist in alerts index export type RuleAlertData = Record; diff --git a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap index 70ffc475d01d6..6360d65c0e66c 100644 --- a/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap +++ b/x-pack/plugins/alerting/server/integration_tests/__snapshots__/serverless_upgrade_and_rollback_checks.test.ts.snap @@ -3064,6 +3064,101 @@ Object { "presence": "optional", }, "keys": Object { + "filter": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "items": Array [ + Object { + "flags": Object { + "default": Object { + "special": "deep", + }, + "error": [Function], + "presence": "optional", + }, + "keys": Object { + "meta": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + "query": Object { + "flags": Object { + "default": [Function], + "error": [Function], + "presence": "optional", + }, + "rules": Array [ + Object { + "args": Object { + "key": Object { + "flags": Object { + "error": [Function], + }, + "rules": Array [ + Object { + "args": Object { + "method": [Function], + }, + "name": "custom", + }, + ], + "type": "string", + }, + "value": Object { + "flags": Object { + "error": [Function], + }, + "type": "any", + }, + }, + "name": "entries", + }, + ], + "type": "record", + }, + }, + "preferences": Object { + "stripUnknown": Object { + "objects": false, + }, + }, + "type": "object", + }, + ], + "type": "array", + }, "index": Object { "flags": Object { "error": [Function], diff --git a/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx b/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx index 95820bf8f84d4..c671bc2dda540 100644 --- a/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ui_components/alerting_flyout/index.tsx @@ -8,6 +8,7 @@ import React, { useCallback, useMemo } from 'react'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { ApmRuleType } from '@kbn/rule-data-utils'; +import type { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { APM_SERVER_FEATURE_ID } from '../../../../../common/rules/apm_rule_types'; import { getInitialAlertValues } from '../../utils/get_initial_alert_values'; import { ApmPluginStartDeps } from '../../../../plugin'; @@ -35,7 +36,7 @@ export function AlertingFlyout(props: Props) { const { start, end } = useTimeRange({ rangeFrom, rangeTo, optional: true }); const environment = - 'environment' in query ? query.environment : ENVIRONMENT_ALL.value; + 'environment' in query ? query.environment! : ENVIRONMENT_ALL.value; const transactionType = 'transactionType' in query ? query.transactionType : undefined; const transactionName = @@ -53,7 +54,10 @@ export function AlertingFlyout(props: Props) { const addAlertFlyout = useMemo( () => ruleType && - services.triggersActionsUi.getAddRuleFlyout({ + services.triggersActionsUi.getAddRuleFlyout< + RuleTypeParams, + AlertMetadata + >({ consumer: APM_SERVER_FEATURE_ID, onClose: onCloseAddFlyout, ruleTypeId: ruleType, @@ -67,7 +71,7 @@ export function AlertingFlyout(props: Props) { errorGroupingKey, start, end, - } as AlertMetadata, + }, useRuleProducer: true, }), /* eslint-disable-next-line react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/apm/public/components/alerting/utils/helper.ts b/x-pack/plugins/apm/public/components/alerting/utils/helper.ts index 7cc0d958aaf9e..66cfe522388f7 100644 --- a/x-pack/plugins/apm/public/components/alerting/utils/helper.ts +++ b/x-pack/plugins/apm/public/components/alerting/utils/helper.ts @@ -6,9 +6,11 @@ */ import { TIME_UNITS } from '@kbn/triggers-actions-ui-plugin/public'; +import type { RuleTypeMetaData } from '@kbn/alerting-plugin/common'; + import moment from 'moment'; -export interface AlertMetadata { +export interface AlertMetadata extends RuleTypeMetaData { environment: string; serviceName?: string; transactionType?: string; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap index b963137281b70..a9a77b477f1a2 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/__snapshots__/alert_details_app_section.test.tsx.snap @@ -28,7 +28,6 @@ Array [ }, ], "dataView": undefined, - "filterQuery": "", "groupBy": Array [ "host.hostname", ], @@ -46,6 +45,13 @@ Array [ "timeSize": 15, "timeUnit": "m", }, + "searchConfiguration": Object { + "index": "mockedIndex", + "query": Object { + "language": "kuery", + "query": "host.hostname: Users-System.local and service.type: system", + }, + }, "seriesType": "bar_stacked", "timeRange": Object { "from": "2023-03-28T10:43:13.802Z", diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx index 2506516efd81a..f07a6ddac4501 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_details_app_section/alert_details_app_section.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { useEffect, useState } from 'react'; @@ -54,7 +53,6 @@ import { LogRateAnalysis } from './log_rate_analysis'; import { Groups } from './groups'; import { Tags } from './tags'; import { RuleConditionChart } from '../rule_condition_chart/rule_condition_chart'; -import { getFilterQuery } from './helpers/get_filter_query'; // TODO Use a generic props for app sections https://github.com/elastic/kibana/issues/152690 export type CustomThresholdRule = Rule; @@ -118,7 +116,6 @@ export default function AlertDetailsAppSection({ const { euiTheme } = useEuiTheme(); const hasLogRateAnalysisLicense = hasAtLeast('platinum'); const [dataView, setDataView] = useState(); - const [filterQuery, setFilterQuery] = useState(''); const [, setDataViewError] = useState(); const ruleParams = rule.params as RuleTypeParams & AlertParams; const chartProps = { @@ -204,11 +201,6 @@ export default function AlertDetailsAppSection({ setAlertSummaryFields(alertSummaryFields); }, [groups, tags, rule, ruleLink, setAlertSummaryFields]); - useEffect(() => { - const query = `${(ruleParams.searchConfiguration?.query as Query)?.query as string}`; - setFilterQuery(getFilterQuery(query, groups)); - }, [groups, ruleParams.searchConfiguration]); - useEffect(() => { const initDataView = async () => { const ruleSearchConfiguration = ruleParams.searchConfiguration; @@ -271,7 +263,7 @@ export default function AlertDetailsAppSection({ { ); diff --git a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx index cd93a5e134c2b..19a1c0fcd164e 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/custom_threshold_rule_expression.tsx @@ -405,7 +405,7 @@ export default function Expressions(props: Props) { indexPatterns={dataView ? [dataView] : undefined} showQueryInput={true} showQueryMenu={false} - showFilterBar={false} + showFilterBar={!!ruleParams.searchConfiguration?.filter} showDatePicker={false} showSubmitButton={false} displayStyle="inPage" @@ -413,6 +413,16 @@ export default function Expressions(props: Props) { onQuerySubmit={onFilterChange} dataTestSubj="thresholdRuleUnifiedSearchBar" query={ruleParams.searchConfiguration?.query as Query} + filters={ruleParams.searchConfiguration?.filter} + onFiltersUpdated={(filter) => { + // Since rule params will be sent to the API as is, and we only need meta and query parameters to be + // saved in the rule's saved object, we filter extra fields here (such as $state). + const filters = filter.map(({ meta, query }) => ({ meta, query })); + setRuleParams('searchConfiguration', { + ...ruleParams.searchConfiguration, + filter: filters, + }); + }} /> {errors.filterQuery && ( @@ -454,7 +464,7 @@ export default function Expressions(props: Props) { { - const groupByFilters = Object.values(group.bucketKey).map((key, index) => { - return { - match: { - [groupByFields[index]]: key, - }, - }; - }); + const groupByQueries: QueryDslQueryContainer[] = Object.values(group.bucketKey).map( + (key, index) => { + return { + match: { + [groupByFields[index]]: key, + }, + }; + } + ); + const query = createBoolQuery( + currentTimeFrame, + timeFieldName, + searchConfiguration, + groupByQueries + ); return [ { index: indexPattern }, { size: 0, terminate_after: 1, track_total_hits: true, - query: { - bool: { - filter: [...baseFilters, ...groupByFilters], - }, - }, + query, }, ]; }); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts index 59f5801613dd0..87b7d9983465b 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/evaluate_rule.ts @@ -69,7 +69,7 @@ export const evaluateRule = async { @@ -27,6 +28,18 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { threshold: [1], comparator: Comparator.GT, }; + const searchConfiguration: SearchConfigurationType = { + index: { + id: 'dataset-logs-*-*', + name: 'All logs', + timeFieldName: '@timestamp', + title: 'logs-*-*', + }, + query: { + language: 'kuery', + query: '', + }, + }; const groupBy = 'host.doggoname'; const timeFieldName = 'mockedTimeFieldName'; @@ -35,13 +48,14 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { end: moment().valueOf(), }; - describe('when passed no filterQuery', () => { + describe('when passed no KQL query', () => { const searchBody = getElasticsearchMetricQuery( expressionParams, timeframe, timeFieldName, 100, true, + searchConfiguration, void 0, groupBy ); @@ -78,11 +92,18 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { }); }); - describe('when passed a filterQuery', () => { + describe('when passed a KQL query', () => { // This is adapted from a real-world query that previously broke alerts // We want to make sure it doesn't override any existing filters // https://github.com/elastic/kibana/issues/68492 - const filterQuery = 'NOT host.name:dv* and NOT host.name:ts*'; + const query = 'NOT host.name:dv* and NOT host.name:ts*'; + const currentSearchConfiguration = { + ...searchConfiguration, + query: { + language: 'kuery', + query, + }, + }; const searchBody = getElasticsearchMetricQuery( expressionParams, @@ -90,9 +111,9 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { timeFieldName, 100, true, + currentSearchConfiguration, void 0, - groupBy, - filterQuery + groupBy ); test('includes a range filter', () => { expect( @@ -164,4 +185,60 @@ describe("The Metric Threshold Alert's getElasticsearchMetricQuery", () => { ); }); }); + + describe('when passed a filter', () => { + const currentSearchConfiguration = { + ...searchConfiguration, + query: { + language: 'kuery', + query: '', + }, + filter: [ + { + meta: { + alias: null, + disabled: false, + field: 'service.name', + key: 'service.name', + negate: false, + params: { + query: 'synth-node-2', + }, + type: 'phrase', + index: 'dataset-logs-*-*', + }, + query: { + match_phrase: { + 'service.name': 'synth-node-2', + }, + }, + }, + ], + }; + + const searchBody = getElasticsearchMetricQuery( + expressionParams, + timeframe, + timeFieldName, + 100, + true, + currentSearchConfiguration, + void 0, + groupBy + ); + test('includes a range filter', () => { + expect( + searchBody.query.bool.filter.find((filter) => filter.hasOwnProperty('range')) + ).toBeTruthy(); + }); + + test('includes a metric field filter', () => { + expect(searchBody.query.bool.filter).toMatchObject( + expect.arrayContaining([ + { range: { mockedTimeFieldName: expect.any(Object) } }, + { match_phrase: { 'service.name': 'synth-node-2' } }, + ]) + ); + }); + }); }); diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts index 3cc1eee92fec9..14c18e4af1334 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/lib/metric_query.ts @@ -6,10 +6,14 @@ */ import moment from 'moment'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { Filter } from '@kbn/es-query'; import { Aggregators, CustomMetricExpressionParams, } from '../../../../../common/custom_threshold_rule/types'; +import { getSearchConfigurationBoolQuery } from '../../../../utils/get_parsed_filtered_query'; +import { SearchConfigurationType } from '../types'; import { createCustomMetricsAggregations } from './create_custom_metrics_aggregations'; import { CONTAINER_ID, @@ -20,7 +24,6 @@ import { } from '../utils'; import { createBucketSelector } from './create_bucket_selector'; import { wrapInCurrentPeriod } from './wrap_in_period'; -import { getParsedFilterQuery } from '../../../../utils/get_parsed_filtered_query'; export const calculateCurrentTimeFrame = ( metricParams: CustomMetricExpressionParams, @@ -38,25 +41,30 @@ export const calculateCurrentTimeFrame = ( }; }; -export const createBaseFilters = ( +const QueryDslQueryContainerToFilter = (queries: QueryDslQueryContainer[]): Filter[] => { + return queries.map((query) => ({ + meta: {}, + query, + })); +}; + +export const createBoolQuery = ( timeframe: { start: number; end: number }, timeFieldName: string, - filterQuery?: string + searchConfiguration: SearchConfigurationType, + additionalQueries: QueryDslQueryContainer[] = [] ) => { - const rangeFilters = [ - { - range: { - [timeFieldName]: { - gte: moment(timeframe.start).toISOString(), - lte: moment(timeframe.end).toISOString(), - }, + const rangeQuery: QueryDslQueryContainer = { + range: { + [timeFieldName]: { + gte: moment(timeframe.start).toISOString(), + lte: moment(timeframe.end).toISOString(), }, }, - ]; - - const parsedFilterQuery = getParsedFilterQuery(filterQuery); + }; + const filters = QueryDslQueryContainerToFilter([rangeQuery, ...additionalQueries]); - return [...rangeFilters, ...parsedFilterQuery]; + return getSearchConfigurationBoolQuery(searchConfiguration, filters); }; export const getElasticsearchMetricQuery = ( @@ -65,9 +73,9 @@ export const getElasticsearchMetricQuery = ( timeFieldName: string, compositeSize: number, alertOnGroupDisappear: boolean, + searchConfiguration: SearchConfigurationType, lastPeriodEnd?: number, groupBy?: string | string[], - filterQuery?: string, afterKey?: Record, fieldsExisted?: Record | null ) => { @@ -196,15 +204,11 @@ export const getElasticsearchMetricQuery = ( aggs.groupings.composite.after = afterKey; } - const baseFilters = createBaseFilters(timeframe, timeFieldName, filterQuery); + const query = createBoolQuery(timeframe, timeFieldName, searchConfiguration); return { track_total_hits: true, - query: { - bool: { - filter: baseFilters, - }, - }, + query, size: 0, aggs, }; diff --git a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts index 5e9c2e0cea019..df64a67ca8e4a 100644 --- a/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts +++ b/x-pack/plugins/observability/server/lib/rules/custom_threshold/register_custom_threshold_rule_type.ts @@ -58,6 +58,14 @@ export const searchConfigurationSchema = schema.object({ }), query: schema.string(), }), + filter: schema.maybe( + schema.arrayOf( + schema.object({ + query: schema.maybe(schema.recordOf(schema.string(), schema.any())), + meta: schema.recordOf(schema.string(), schema.any()), + }) + ) + ), }); type CreateLifecycleExecutor = ReturnType; diff --git a/x-pack/plugins/observability/server/utils/get_parsed_filtered_query.ts b/x-pack/plugins/observability/server/utils/get_parsed_filtered_query.ts index fabefa63f0695..033a6cadc282e 100644 --- a/x-pack/plugins/observability/server/utils/get_parsed_filtered_query.ts +++ b/x-pack/plugins/observability/server/utils/get_parsed_filtered_query.ts @@ -5,7 +5,14 @@ * 2.0. */ -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { + BoolQuery, + buildEsQuery, + Filter, + fromKueryExpression, + toElasticsearchQuery, +} from '@kbn/es-query'; +import { SearchConfigurationType } from '../lib/rules/custom_threshold/types'; export const getParsedFilterQuery: (filter: string | undefined) => Array> = ( filter @@ -19,3 +26,24 @@ export const getParsedFilterQuery: (filter: string | undefined) => Array { bool: BoolQuery } = (searchConfiguration, additionalFilters) => { + try { + const searchConfigurationFilters = (searchConfiguration.filter as Filter[]) || []; + const filters = [...additionalFilters, ...searchConfigurationFilters]; + + return buildEsQuery(undefined, searchConfiguration.query, filters, {}); + } catch (error) { + return { + bool: { + must: [], + must_not: [], + filter: [], + should: [], + }, + }; + } +}; diff --git a/x-pack/plugins/observability_solution/logs_explorer/public/utils/convert_discover_app_state.ts b/x-pack/plugins/observability_solution/logs_explorer/public/utils/convert_discover_app_state.ts index 90d51f75e8c7c..639d4bdb2b0d1 100644 --- a/x-pack/plugins/observability_solution/logs_explorer/public/utils/convert_discover_app_state.ts +++ b/x-pack/plugins/observability_solution/logs_explorer/public/utils/convert_discover_app_state.ts @@ -16,7 +16,7 @@ import { GridColumnDisplayOptions, GridRowsDisplayOptions, } from '../../common'; -import { ControlOptions, OptionsListControlOption } from '../controller'; +import type { ControlOptions, OptionsListControl } from '../controller'; export const getGridColumnDisplayOptionsFromDiscoverAppState = ( discoverAppState: DiscoverAppState @@ -79,55 +79,78 @@ const createDiscoverPhrasesFilter = ({ key, values, negate, + index, }: { - values: PhraseFilterValue[]; + index: string; key: string; + values: PhraseFilterValue[]; negate?: boolean; -}): PhrasesFilter => - ({ - meta: { - key, - negate, - type: FILTERS.PHRASES, - params: values, - }, - query: { - bool: { - should: values.map((value) => ({ match_phrase: { [key]: value.toString() } })), - minimum_should_match: 1, - }, +}): PhrasesFilter => ({ + meta: { + index, + type: FILTERS.PHRASES, + key, + params: values.map((value) => value.toString()), + negate, + }, + query: { + bool: { + should: values.map((value) => ({ match_phrase: { [key]: value.toString() } })), + minimum_should_match: 1, }, - } as PhrasesFilter); + }, +}); const createDiscoverExistsFilter = ({ + index, key, negate, }: { key: string; + index: string; negate?: boolean; }): ExistsFilter => ({ meta: { + index, + type: FILTERS.EXISTS, + value: FILTERS.EXISTS, // Required for the filter to be displayed correctly in FilterBadge key, negate, - type: FILTERS.EXISTS, }, query: { exists: { field: key } }, }); -export const getDiscoverFiltersFromState = (filters: Filter[] = [], controls?: ControlOptions) => [ - ...filters, - ...(controls - ? (Object.keys(controls) as Array).map((key) => - controls[key as keyof ControlOptions]?.selection.type === 'exists' - ? createDiscoverExistsFilter({ - key, - negate: controls[key]?.mode === 'exclude', - }) - : createDiscoverPhrasesFilter({ - key, - values: (controls[key]?.selection as OptionsListControlOption).selectedOptions, - negate: controls[key]?.mode === 'exclude', - }) - ) - : []), -]; +export const getDiscoverFiltersFromState = ( + index: string, + filters: Filter[] = [], + controls?: ControlOptions +) => { + return [ + ...filters, + ...(controls + ? (Object.entries(controls) as Array<[keyof ControlOptions, OptionsListControl]>).reduce< + Filter[] + >((acc, [key, control]) => { + if (control.selection.type === 'exists') { + acc.push( + createDiscoverExistsFilter({ + index, + key, + negate: control.mode === 'exclude', + }) + ); + } else if (control.selection.selectedOptions.length > 0) { + acc.push( + createDiscoverPhrasesFilter({ + index, + key, + values: control.selection.selectedOptions, + negate: control.mode === 'exclude', + }) + ); + } + return acc; + }, []) + : []), + ]; +}; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/kibana.jsonc b/x-pack/plugins/observability_solution/observability_logs_explorer/kibana.jsonc index 42d762820aaad..8f6e248557efa 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/kibana.jsonc +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/kibana.jsonc @@ -23,7 +23,12 @@ "datasetQuality" ], "optionalPlugins": [ - "serverless" + "serverless", + "triggersActionsUi", + "unifiedSearch", + "dataViews", + "dataViewEditor", + "lens" ], "requiredBundles": [ "kibanaReact" diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/observability_logs_explorer.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/observability_logs_explorer.tsx index 5f6739a5dfe3d..cab3742c06f05 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/observability_logs_explorer.tsx +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/applications/observability_logs_explorer.tsx @@ -6,6 +6,7 @@ */ import { CoreStart } from '@kbn/core/public'; +import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { Route, Router, Routes } from '@kbn/shared-ux-router'; import React from 'react'; @@ -57,6 +58,7 @@ export const ObservabilityLogsExplorerApp = ({ plugins, pluginStart, }: ObservabilityLogsExplorerAppProps) => { + const isDarkMode = core.theme.getTheme().darkMode; const KibanaContextProviderForPlugin = useKibanaContextForPluginProvider( core, plugins, @@ -69,10 +71,20 @@ export const ObservabilityLogsExplorerApp = ({ - - } /> - } /> - + + + } + /> + } + /> + + diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/alerts_popover.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/alerts_popover.tsx new file mode 100644 index 0000000000000..22f689010a2df --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/alerts_popover.tsx @@ -0,0 +1,142 @@ +/* + * 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 { EuiPopover, EuiButtonEmpty, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import React, { useMemo, useReducer } from 'react'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { useActor } from '@xstate/react'; +import { hydrateDatasetSelection } from '@kbn/logs-explorer-plugin/common'; +import { getDiscoverFiltersFromState } from '@kbn/logs-explorer-plugin/public'; +import type { AlertParams } from '@kbn/observability-plugin/public/components/custom_threshold/types'; +import { useLinkProps } from '@kbn/observability-shared-plugin/public'; +import { useKibanaContextForPlugin } from '../utils/use_kibana'; +import { useObservabilityLogsExplorerPageStateContext } from '../state_machines/observability_logs_explorer/src'; + +type ThresholdRuleTypeParams = Pick; + +interface AlertsPopoverState { + isPopoverOpen: boolean; + isAddRuleFlyoutOpen: boolean; +} + +type AlertsPopoverAction = + | { + type: 'togglePopover'; + isOpen?: boolean; + } + | { + type: 'toggleAddRuleFlyout'; + isOpen?: boolean; + }; + +function alertsPopoverReducer(state: AlertsPopoverState, action: AlertsPopoverAction) { + switch (action.type) { + case 'togglePopover': + return { + isPopoverOpen: action.isOpen ?? !state.isPopoverOpen, + isAddRuleFlyoutOpen: state.isAddRuleFlyoutOpen, + }; + + case 'toggleAddRuleFlyout': + return { + isPopoverOpen: false, + isAddRuleFlyoutOpen: action.isOpen ?? !state.isAddRuleFlyoutOpen, + }; + + default: + return state; + } +} + +export const AlertsPopover = () => { + const { + services: { triggersActionsUi }, + } = useKibanaContextForPlugin(); + + const manageRulesLinkProps = useLinkProps({ app: 'observability', pathname: '/alerts/rules' }); + + const [pageState] = useActor(useObservabilityLogsExplorerPageStateContext()); + + const [state, dispatch] = useReducer(alertsPopoverReducer, { + isPopoverOpen: false, + isAddRuleFlyoutOpen: false, + }); + + const togglePopover = () => dispatch({ type: 'togglePopover' }); + const closePopover = () => dispatch({ type: 'togglePopover', isOpen: false }); + const openAddRuleFlyout = () => dispatch({ type: 'toggleAddRuleFlyout', isOpen: true }); + const closeAddRuleFlyout = () => dispatch({ type: 'toggleAddRuleFlyout', isOpen: false }); + + const addRuleFlyout = useMemo(() => { + if ( + state.isAddRuleFlyoutOpen && + triggersActionsUi && + pageState.matches({ initialized: 'validLogsExplorerState' }) + ) { + const { logsExplorerState } = pageState.context; + const index = hydrateDatasetSelection(logsExplorerState.datasetSelection).toDataviewSpec(); + + return triggersActionsUi.getAddRuleFlyout({ + consumer: 'logs', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + canChangeTrigger: false, + initialValues: { + params: { + searchConfiguration: { + index, + query: logsExplorerState.query, + filter: getDiscoverFiltersFromState( + index.id, + logsExplorerState.filters, + logsExplorerState.controls + ), + }, + }, + }, + onClose: closeAddRuleFlyout, + }); + } + }, [triggersActionsUi, pageState, state.isAddRuleFlyoutOpen]); + + return ( + <> + {state.isAddRuleFlyoutOpen && addRuleFlyout} + + + + } + isOpen={state.isPopoverOpen} + closePopover={closePopover} + panelPaddingSize="none" + anchorPosition="downLeft" + > + + + , + + + , + ]} + /> + + + ); +}; diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/discover_link.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/discover_link.tsx index b12390cc952a1..7c0b4596b4326 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/discover_link.tsx +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/discover_link.tsx @@ -53,18 +53,22 @@ export const DiscoverLinkForValidState = React.memo( discover: DiscoverStart; pageState: InitializedPageState; }) => { - const discoverLinkParams = useMemo( - () => ({ + const discoverLinkParams = useMemo(() => { + const index = hydrateDatasetSelection(logsExplorerState.datasetSelection).toDataviewSpec(); + return { breakdownField: logsExplorerState.chart.breakdownField ?? undefined, columns: getDiscoverColumnsFromDisplayOptions(logsExplorerState), - filters: getDiscoverFiltersFromState(logsExplorerState.filters, logsExplorerState.controls), + filters: getDiscoverFiltersFromState( + index.id, + logsExplorerState.filters, + logsExplorerState.controls + ), query: logsExplorerState.query, refreshInterval: logsExplorerState.refreshInterval, timeRange: logsExplorerState.time, - dataViewSpec: hydrateDatasetSelection(logsExplorerState.datasetSelection).toDataviewSpec(), - }), - [logsExplorerState] - ); + dataViewSpec: index, + }; + }, [logsExplorerState]); return ; } diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx index 9c2ea0a5e4817..0f64f586ab3fd 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/components/logs_explorer_top_nav_menu.tsx @@ -26,6 +26,7 @@ import { useKibanaContextForPlugin } from '../utils/use_kibana'; import { ConnectedDiscoverLink } from './discover_link'; import { FeedbackLink } from './feedback_link'; import { ConnectedOnboardingLink } from './onboarding_link'; +import { AlertsPopover } from './alerts_popover'; export const LogsExplorerTopNavMenu = () => { const { @@ -67,6 +68,8 @@ const ServerlessTopNav = () => { + + {ObservabilityAIAssistantActionMenuItem ? ( ) : null} @@ -143,6 +146,8 @@ const StatefulTopNav = () => { + + {ObservabilityAIAssistantActionMenuItem ? ( ) : null} diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/public/types.ts b/x-pack/plugins/observability_solution/observability_logs_explorer/public/types.ts index c3f094033f697..96754cfdab021 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/public/types.ts +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/public/types.ts @@ -15,6 +15,11 @@ import { AppMountParameters, ScopedHistory } from '@kbn/core/public'; import { LogsSharedClientStartExports } from '@kbn/logs-shared-plugin/public'; import { DatasetQualityPluginStart } from '@kbn/dataset-quality-plugin/public'; import { ObservabilityAIAssistantPluginStart } from '@kbn/observability-ai-assistant-plugin/public'; +import { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public'; +import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; +import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; +import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import { LensPublicStart } from '@kbn/lens-plugin/public'; import { ObservabilityLogsExplorerLocators, ObservabilityLogsExplorerLocationState, @@ -41,6 +46,11 @@ export interface ObservabilityLogsExplorerStartDeps { observabilityAIAssistant: ObservabilityAIAssistantPluginStart; observabilityShared: ObservabilitySharedPluginStart; serverless?: ServerlessPluginStart; + triggersActionsUi?: TriggersAndActionsUIPublicPluginStart; + unifiedSearch?: UnifiedSearchPublicPluginStart; + dataViews?: DataViewsPublicPluginStart; + dataViewEditor?: DataViewEditorStart; + lens?: LensPublicStart; share: SharePluginStart; datasetQuality: DatasetQualityPluginStart; } diff --git a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json index 7192e3001a70b..c434a418c4246 100644 --- a/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_logs_explorer/tsconfig.json @@ -38,6 +38,13 @@ "@kbn/xstate-utils", "@kbn/router-utils", "@kbn/observability-ai-assistant-plugin", + "@kbn/rule-data-utils", + "@kbn/observability-plugin", + "@kbn/triggers-actions-ui-plugin", + "@kbn/unified-search-plugin", + "@kbn/data-views-plugin", + "@kbn/data-view-editor-plugin", + "@kbn/lens-plugin", ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/index.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/index.tsx index dfab594febf10..1a99e346ed808 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/index.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/index.tsx @@ -7,6 +7,13 @@ import { lazy } from 'react'; import { suspendedComponentWithProps } from '../../lib/suspended_component_with_props'; +import type { RuleAddComponent } from './rule_add'; +import type { RuleEditComponent } from './rule_edit'; -export const RuleAdd = suspendedComponentWithProps(lazy(() => import('./rule_add'))); -export const RuleEdit = suspendedComponentWithProps(lazy(() => import('./rule_edit'))); +export const RuleAdd = suspendedComponentWithProps( + lazy(() => import('./rule_add')) +) as RuleAddComponent; // `React.lazy` is not typed correctly to support generics so casting back to imported component + +export const RuleEdit = suspendedComponentWithProps( + lazy(() => import('./rule_edit')) +) as RuleEditComponent; // `React.lazy` is not typed correctly to support generics so casting back to imported component diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx index 07264709dd544..19eb8da4bf0d3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx @@ -15,6 +15,7 @@ import { parseRuleCircuitBreakerErrorMessage } from '@kbn/alerting-plugin/common import { Rule, RuleTypeParams, + RuleTypeMetaData, RuleUpdates, RuleFlyoutCloseReason, IErrorObject, @@ -49,7 +50,12 @@ const defaultCreateRuleErrorMessage = i18n.translate( } ); -const RuleAdd = ({ +export type RuleAddComponent = typeof RuleAdd; + +const RuleAdd = < + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData +>({ consumer, ruleTypeRegistry, actionTypeRegistry, @@ -67,7 +73,7 @@ const RuleAdd = ({ useRuleProducer, initialSelectedConsumer, ...props -}: RuleAddProps) => { +}: RuleAddProps) => { const onSaveHandler = onSave ?? reloadRules; const [metadata, setMetadata] = useState(initialMetadata); const onChangeMetaData = useCallback((newMetadata) => setMetadata(newMetadata), []); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx index 0aebaaaa29882..975881e516e45 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_edit.tsx @@ -34,6 +34,8 @@ import { RuleEditProps, IErrorObject, RuleType, + RuleTypeParams, + RuleTypeMetaData, TriggersActionsUiConfig, RuleNotifyWhenType, } from '../../../types'; @@ -81,7 +83,12 @@ const cloneAndMigrateRule = (initialRule: Rule) => { return clonedRule; }; -export const RuleEdit = ({ +export type RuleEditComponent = typeof RuleEdit; + +export const RuleEdit = < + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData +>({ initialRule, onClose, reloadRules, @@ -91,7 +98,7 @@ export const RuleEdit = ({ actionTypeRegistry, metadata: initialMetadata, ...props -}: RuleEditProps) => { +}: RuleEditProps) => { const onSaveHandler = onSave ?? reloadRules; const [{ rule }, dispatch] = useReducer(ruleReducer as ConcreteRuleReducer, { rule: cloneAndMigrateRule(initialRule), diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_add_rule_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_add_rule_flyout.tsx index 23f751201d1be..c6a79b4c6e82d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_add_rule_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_add_rule_flyout.tsx @@ -9,11 +9,14 @@ import React from 'react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ConnectorProvider } from '../application/context/connector_context'; import { RuleAdd } from '../application/sections/rule_form'; -import type { ConnectorServices, RuleAddProps } from '../types'; +import type { ConnectorServices, RuleAddProps, RuleTypeParams, RuleTypeMetaData } from '../types'; import { queryClient } from '../application/query_client'; -export const getAddRuleFlyoutLazy = ( - props: RuleAddProps & { connectorServices: ConnectorServices } +export const getAddRuleFlyoutLazy = < + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData +>( + props: RuleAddProps & { connectorServices: ConnectorServices } ) => { return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/common/get_edit_rule_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/common/get_edit_rule_flyout.tsx index 2d99e3911a168..f3fbccce267c5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/get_edit_rule_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/get_edit_rule_flyout.tsx @@ -9,11 +9,14 @@ import React from 'react'; import { QueryClientProvider } from '@tanstack/react-query'; import { ConnectorProvider } from '../application/context/connector_context'; import { RuleEdit } from '../application/sections/rule_form'; -import type { ConnectorServices, RuleEditProps as AlertEditProps } from '../types'; +import type { ConnectorServices, RuleEditProps, RuleTypeParams, RuleTypeMetaData } from '../types'; import { queryClient } from '../application/query_client'; -export const getEditRuleFlyoutLazy = ( - props: AlertEditProps & { connectorServices: ConnectorServices } +export const getEditRuleFlyoutLazy = < + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData +>( + props: RuleEditProps & { connectorServices: ConnectorServices } ) => { return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/mocks.ts b/x-pack/plugins/triggers_actions_ui/public/mocks.ts index 223a54205cb48..48691c15ed62f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/mocks.ts +++ b/x-pack/plugins/triggers_actions_ui/public/mocks.ts @@ -15,8 +15,6 @@ import { getEditRuleFlyoutLazy } from './common/get_edit_rule_flyout'; import { TypeRegistry } from './application/type_registry'; import { ActionTypeModel, - RuleAddProps, - RuleEditProps, RuleTypeModel, AlertsTableProps, FieldBrowserProps, @@ -73,7 +71,7 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { connectorServices, }); }, - getAddRuleFlyout: (props: Omit) => { + getAddRuleFlyout: (props) => { return getAddRuleFlyoutLazy({ ...props, actionTypeRegistry, @@ -81,7 +79,7 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart { connectorServices, }); }, - getEditRuleFlyout: (props: Omit) => { + getEditRuleFlyout: (props) => { return getEditRuleFlyoutLazy({ ...props, actionTypeRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 872528a9a5f85..bcd639e21a2ff 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -62,6 +62,8 @@ import type { RuleAddProps, RuleEditProps, RuleTypeModel, + RuleTypeParams, + RuleTypeMetaData, AlertsTableProps, RuleStatusDropdownProps, RuleTagFilterProps, @@ -115,12 +117,18 @@ export interface TriggersAndActionsUIPublicPluginStart { getEditConnectorFlyout: ( props: Omit ) => ReactElement; - getAddRuleFlyout: ( - props: Omit - ) => ReactElement; - getEditRuleFlyout: ( - props: Omit - ) => ReactElement; + getAddRuleFlyout: < + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData + >( + props: Omit, 'actionTypeRegistry' | 'ruleTypeRegistry'> + ) => ReactElement>; + getEditRuleFlyout: < + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData + >( + props: Omit, 'actionTypeRegistry' | 'ruleTypeRegistry'> + ) => ReactElement>; getAlertsTable: (props: AlertsTableProps) => ReactElement; getAlertsTableDefaultAlertActions:

( props: P @@ -403,7 +411,7 @@ export class Plugin connectorServices: this.connectorServices!, }); }, - getAddRuleFlyout: (props: Omit) => { + getAddRuleFlyout: (props) => { return getAddRuleFlyoutLazy({ ...props, actionTypeRegistry: this.actionTypeRegistry, @@ -411,9 +419,7 @@ export class Plugin connectorServices: this.connectorServices!, }); }, - getEditRuleFlyout: ( - props: Omit - ) => { + getEditRuleFlyout: (props) => { return getEditRuleFlyoutLazy({ ...props, actionTypeRegistry: this.actionTypeRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index b47d80a0839e5..36cc294bbda5f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -51,6 +51,7 @@ import { AlertingFrameworkHealth, RuleNotifyWhenType, RuleTypeParams, + RuleTypeMetaData, ActionVariable, RuleLastRun, MaintenanceWindow, @@ -127,6 +128,7 @@ export type { AlertingFrameworkHealth, RuleNotifyWhenType, RuleTypeParams, + RuleTypeMetaData, ResolvedRule, SanitizedRule, RuleStatusDropdownProps, @@ -412,8 +414,11 @@ export enum EditConnectorTabs { Test = 'test', } -export interface RuleEditProps> { - initialRule: Rule; +export interface RuleEditProps< + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData +> { + initialRule: Rule; ruleTypeRegistry: RuleTypeRegistryContract; actionTypeRegistry: ActionTypeRegistryContract; onClose: (reason: RuleFlyoutCloseReason, metadata?: MetaData) => void; @@ -425,14 +430,27 @@ export interface RuleEditProps> { ruleType?: RuleType; } -export interface RuleAddProps> { +export interface RuleAddProps< + Params extends RuleTypeParams = RuleTypeParams, + MetaData extends RuleTypeMetaData = RuleTypeMetaData +> { + /** + * ID of the feature this rule should be created for. + * + * Notes: + * - The feature needs to be registered using `featuresPluginSetup.registerKibanaFeature()` API during your plugin's setup phase. + * - The user needs to have permission to access the feature in order to create the rule. + * */ consumer: string; ruleTypeRegistry: RuleTypeRegistryContract; actionTypeRegistry: ActionTypeRegistryContract; onClose: (reason: RuleFlyoutCloseReason, metadata?: MetaData) => void; ruleTypeId?: string; + /** + * Determines whether the user should be able to change the rule type in the UI. + */ canChangeTrigger?: boolean; - initialValues?: Partial; + initialValues?: Partial>; /** @deprecated use `onSave` as a callback after an alert is saved*/ reloadRules?: () => Promise; hideGrouping?: boolean; @@ -445,8 +463,8 @@ export interface RuleAddProps> { useRuleProducer?: boolean; initialSelectedConsumer?: RuleCreationValidConsumer | null; } -export interface RuleDefinitionProps { - rule: Rule; +export interface RuleDefinitionProps { + rule: Rule; ruleTypeRegistry: RuleTypeRegistryContract; actionTypeRegistry: ActionTypeRegistryContract; onEditRule: () => Promise;