From ab72206da338e81e549da128e6ca5fd7a30e2b30 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Thu, 12 Nov 2020 16:39:40 +0000 Subject: [PATCH 1/3] [Alerting] Moves the Index & Geo Threshold UIs into the Stack Alerts Public Plugin (#82951) This PR includes the following refactors: 1. Moves the Index Pattern Api from _Stack Alerts_ to the _Server_ plugin of _Trigger Actions UI_. This fixes a potential bug where a user could disable the _Stack Alerts_ plugin and inadvertently break the UI of the _ES Index _ action type. 2. Extracts the UI components for _Index Threshold_ and _Geo Threshold_ from the _Trigger Actions UI_ plugin and moves them into _Stack Alerts_. --- .eslintrc.js | 9 +- packages/kbn-optimizer/limits.yml | 1 + .../stack_alerts/{server => common}/config.ts | 1 + x-pack/plugins/stack_alerts/common/index.ts | 2 +- x-pack/plugins/stack_alerts/kibana.json | 4 +- .../alert_types}/geo_threshold/index.ts | 7 +- .../expressions/boundary_index_expression.tsx | 17 +- .../expressions/entity_by_expression.tsx | 8 +- .../expressions/entity_index_expression.tsx | 24 +- .../geo_threshold/query_builder/index.tsx | 37 ++- .../expression_with_popover.tsx | 12 +- .../geo_index_pattern_select.tsx | 26 +- .../util_components/single_field_select.tsx | 2 +- .../alert_types}/geo_threshold/types.ts | 0 .../geo_threshold/validation.test.ts | 0 .../alert_types}/geo_threshold/validation.ts | 18 +- .../public/alert_types}/index.ts | 15 +- .../alert_types}/threshold/expression.scss | 0 .../alert_types}/threshold/expression.tsx | 65 +++-- .../public/alert_types}/threshold/index.ts | 9 +- .../threshold/index_threshold_api.ts | 45 ++++ .../public/alert_types}/threshold/types.ts | 0 .../alert_types}/threshold/validation.test.ts | 0 .../alert_types}/threshold/validation.ts | 22 +- .../alert_types}/threshold/visualization.tsx | 40 +-- x-pack/plugins/stack_alerts/public/index.ts | 10 + x-pack/plugins/stack_alerts/public/plugin.tsx | 35 +++ .../alert_types/geo_threshold/alert_type.ts | 6 +- .../geo_threshold/es_query_builder.ts | 2 +- .../geo_threshold/geo_threshold.ts | 4 +- .../server/alert_types/geo_threshold/index.ts | 9 +- .../geo_threshold/tests/alert_type.test.ts | 6 +- .../stack_alerts/server/alert_types/index.ts | 8 +- .../alert_types/index_threshold/README.md | 230 +----------------- .../index_threshold/alert_type.test.ts | 10 +- .../alert_types/index_threshold/alert_type.ts | 19 +- .../index_threshold/alert_type_params.test.ts | 186 +++++++++++++- .../index_threshold/alert_type_params.ts | 5 +- .../alert_types/index_threshold/index.ts | 24 +- x-pack/plugins/stack_alerts/server/index.ts | 19 +- .../stack_alerts/server/plugin.test.ts | 29 --- x-pack/plugins/stack_alerts/server/plugin.ts | 31 +-- x-pack/plugins/stack_alerts/server/types.ts | 17 +- .../translations/translations/ja-JP.json | 150 ++++++------ .../translations/translations/zh-CN.json | 150 ++++++------ x-pack/plugins/triggers_actions_ui/README.md | 2 +- .../common/data}/index.ts | 0 .../triggers_actions_ui/common/index.ts | 7 + .../plugins/triggers_actions_ui/kibana.json | 4 +- .../public/application/boot.tsx | 2 +- .../public/common/index.ts | 4 + .../public/common/index_controls/index.ts | 19 +- .../public/common/lib/data_apis.ts | 67 +++++ .../public/common/lib/index.ts | 6 + .../public/common/lib/index_threshold_api.ts | 91 ------- .../triggers_actions_ui/public/index.ts | 7 +- .../triggers_actions_ui/public/plugin.ts | 23 +- .../triggers_actions_ui/server/data/README.md | 228 +++++++++++++++++ .../triggers_actions_ui/server/data/index.ts | 40 +++ .../server/data}/lib/core_query_types.test.ts | 0 .../server/data}/lib/core_query_types.ts | 67 +++-- .../server/data}/lib/date_range_info.test.ts | 0 .../server/data}/lib/date_range_info.ts | 15 +- .../server/data/lib/index.ts | 12 + .../data}/lib/time_series_query.test.ts | 21 +- .../server/data}/lib/time_series_query.ts | 5 +- .../data}/lib/time_series_types.test.ts | 0 .../server/data}/lib/time_series_types.ts | 34 +-- .../server/data}/routes/fields.ts | 12 +- .../server/data}/routes/index.ts | 15 +- .../server/data}/routes/indices.ts | 18 +- .../server/data}/routes/time_series_query.ts | 22 +- .../triggers_actions_ui/server/index.ts | 19 +- .../triggers_actions_ui/server/plugin.ts | 39 +++ .../index_threshold/alert.ts | 7 +- .../index_threshold/fields_endpoint.ts | 2 +- .../index_threshold/indices_endpoint.ts | 2 +- .../time_series_query_endpoint.ts | 4 +- 78 files changed, 1212 insertions(+), 896 deletions(-) rename x-pack/plugins/stack_alerts/{server => common}/config.ts (85%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/index.ts (75%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/expressions/boundary_index_expression.tsx (85%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/expressions/entity_by_expression.tsx (87%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/expressions/entity_index_expression.tsx (83%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/index.tsx (89%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/util_components/expression_with_popover.tsx (88%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx (80%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/query_builder/util_components/single_field_select.tsx (96%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/types.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/validation.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/geo_threshold/validation.ts (72%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/index.ts (57%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/expression.scss (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/expression.tsx (86%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/index.ts (77%) create mode 100644 x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/types.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/validation.test.ts (100%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/validation.ts (81%) rename x-pack/plugins/{triggers_actions_ui/public/application/components/builtin_alert_types => stack_alerts/public/alert_types}/threshold/visualization.tsx (86%) create mode 100644 x-pack/plugins/stack_alerts/public/index.ts create mode 100644 x-pack/plugins/stack_alerts/public/plugin.tsx rename x-pack/plugins/{stack_alerts/common/alert_types/index_threshold => triggers_actions_ui/common/data}/index.ts (100%) create mode 100644 x-pack/plugins/triggers_actions_ui/common/index.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts delete mode 100644 x-pack/plugins/triggers_actions_ui/public/common/lib/index_threshold_api.ts create mode 100644 x-pack/plugins/triggers_actions_ui/server/data/README.md create mode 100644 x-pack/plugins/triggers_actions_ui/server/data/index.ts rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/core_query_types.test.ts (100%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/core_query_types.ts (68%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/date_range_info.test.ts (100%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/date_range_info.ts (89%) create mode 100644 x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/time_series_query.test.ts (58%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/time_series_query.ts (96%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/time_series_types.test.ts (100%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/lib/time_series_types.ts (81%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/routes/fields.ts (90%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/routes/index.ts (55%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/routes/indices.ts (85%) rename x-pack/plugins/{stack_alerts/server/alert_types/index_threshold => triggers_actions_ui/server/data}/routes/time_series_query.ts (58%) create mode 100644 x-pack/plugins/triggers_actions_ui/server/plugin.ts diff --git a/.eslintrc.js b/.eslintrc.js index 561e9bc55bf9d..ad9de04251e4c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1035,12 +1035,19 @@ module.exports = { * Alerting Services overrides */ { - // typescript only for front and back end + // typescript for front and back end files: ['x-pack/plugins/{alerts,stack_alerts,actions,task_manager,event_log}/**/*.{ts,tsx}'], rules: { '@typescript-eslint/no-explicit-any': 'error', }, }, + { + // typescript only for back end + files: ['x-pack/plugins/triggers_actions_ui/server/**/*.ts'], + rules: { + '@typescript-eslint/no-explicit-any': 'error', + }, + }, /** * Lens overrides diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 701b7cab21600..e326c8e2cac39 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -102,3 +102,4 @@ pageLoadAssetSize: visualizations: 295025 visualize: 57431 watcher: 43598 + stackAlerts: 29684 diff --git a/x-pack/plugins/stack_alerts/server/config.ts b/x-pack/plugins/stack_alerts/common/config.ts similarity index 85% rename from x-pack/plugins/stack_alerts/server/config.ts rename to x-pack/plugins/stack_alerts/common/config.ts index 8a13aedd5fdd8..2e997ce0ebad6 100644 --- a/x-pack/plugins/stack_alerts/server/config.ts +++ b/x-pack/plugins/stack_alerts/common/config.ts @@ -8,6 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), + enableGeoTrackingThresholdAlert: schema.boolean({ defaultValue: false }), }); export type Config = TypeOf; diff --git a/x-pack/plugins/stack_alerts/common/index.ts b/x-pack/plugins/stack_alerts/common/index.ts index 79dd18d321f07..a75625d0641aa 100644 --- a/x-pack/plugins/stack_alerts/common/index.ts +++ b/x-pack/plugins/stack_alerts/common/index.ts @@ -3,5 +3,5 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +export * from './config'; export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; diff --git a/x-pack/plugins/stack_alerts/kibana.json b/x-pack/plugins/stack_alerts/kibana.json index b26114577c430..b7405c38d1611 100644 --- a/x-pack/plugins/stack_alerts/kibana.json +++ b/x-pack/plugins/stack_alerts/kibana.json @@ -3,7 +3,7 @@ "server": true, "version": "8.0.0", "kibanaVersion": "kibana", - "requiredPlugins": ["alerts", "features"], + "requiredPlugins": ["alerts", "features", "triggersActionsUi", "kibanaReact"], "configPath": ["xpack", "stack_alerts"], - "ui": false + "ui": true } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts similarity index 75% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts index 00d9ebbbbc066..35f5648de40f3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts @@ -5,18 +5,17 @@ */ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { AlertTypeModel } from '../../../../types'; import { validateExpression } from './validation'; import { GeoThresholdAlertParams } from './types'; -import { AlertsContextValue } from '../../../context/alerts_context'; +import { AlertTypeModel, AlertsContextValue } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.geo-threshold', - name: i18n.translate('xpack.triggersActionsUI.geoThreshold.name.trackingThreshold', { + name: i18n.translate('xpack.stackAlerts.geoThreshold.name.trackingThreshold', { defaultMessage: 'Tracking threshold', }), - description: i18n.translate('xpack.triggersActionsUI.geoThreshold.descriptionText', { + description: i18n.translate('xpack.stackAlerts.geoThreshold.descriptionText', { defaultMessage: 'Alert when an entity enters or leaves a geo boundary.', }), iconClass: 'globe', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx similarity index 85% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx index 497e053a4ed60..55dfc82bdbdb8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/boundary_index_expression.tsx @@ -7,14 +7,13 @@ import React, { Fragment, FunctionComponent, useEffect, useRef } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IErrorObject } from '../../../../../../types'; +import { IErrorObject, AlertsContextValue } from '../../../../../../triggers_actions_ui/public'; import { ES_GEO_SHAPE_TYPES, GeoThresholdAlertParams } from '../../types'; -import { AlertsContextValue } from '../../../../../context/alerts_context'; import { GeoIndexPatternSelect } from '../util_components/geo_index_pattern_select'; import { SingleFieldSelect } from '../util_components/single_field_select'; import { ExpressionWithPopover } from '../util_components/expression_with_popover'; -import { IFieldType } from '../../../../../../../../../../src/plugins/data/common/index_patterns/fields'; -import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; interface Props { alertParams: GeoThresholdAlertParams; @@ -117,12 +116,12 @@ export const BoundaryIndexExpression: FunctionComponent = ({ = ({ = ({ defaultValue={'Select an index pattern and geo shape field'} value={boundaryIndexPattern.title} popoverContent={indexPopover} - expressionDescription={i18n.translate('xpack.triggersActionsUI.geoThreshold.indexLabel', { + expressionDescription={i18n.translate('xpack.stackAlerts.geoThreshold.indexLabel', { defaultMessage: 'index', })} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx similarity index 87% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx index f355d25796b7c..f519ad882802c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_by_expression.tsx @@ -8,10 +8,10 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IErrorObject } from '../../../../../../types'; +import { IErrorObject } from '../../../../../../triggers_actions_ui/public'; import { SingleFieldSelect } from '../util_components/single_field_select'; import { ExpressionWithPopover } from '../util_components/expression_with_popover'; -import { IFieldType } from '../../../../../../../../../../src/plugins/data/common/index_patterns/fields'; +import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; interface Props { errors: IErrorObject; @@ -59,7 +59,7 @@ export const EntityByExpression: FunctionComponent = ({ = ({ value={entity} defaultValue={'Select entity field'} popoverContent={indexPopover} - expressionDescription={i18n.translate('xpack.triggersActionsUI.geoThreshold.entityByLabel', { + expressionDescription={i18n.translate('xpack.stackAlerts.geoThreshold.entityByLabel', { defaultMessage: 'by', })} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/entity_index_expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_index_expression.tsx similarity index 83% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/entity_index_expression.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_index_expression.tsx index 506530c171cd4..e5e43210d1e6b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/expressions/entity_index_expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/expressions/entity_index_expression.tsx @@ -8,14 +8,13 @@ import React, { Fragment, FunctionComponent, useEffect, useRef } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { IErrorObject } from '../../../../../../types'; +import { IErrorObject, AlertsContextValue } from '../../../../../../triggers_actions_ui/public'; import { ES_GEO_FIELD_TYPES } from '../../types'; -import { AlertsContextValue } from '../../../../../context/alerts_context'; import { GeoIndexPatternSelect } from '../util_components/geo_index_pattern_select'; import { SingleFieldSelect } from '../util_components/single_field_select'; import { ExpressionWithPopover } from '../util_components/expression_with_popover'; -import { IFieldType } from '../../../../../../../../../../src/plugins/data/common/index_patterns/fields'; -import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { IFieldType } from '../../../../../../../../src/plugins/data/common/index_patterns/fields'; +import { IIndexPattern } from '../../../../../../../../src/plugins/data/common/index_patterns'; interface Props { dateField: string; @@ -105,13 +104,13 @@ export const EntityIndexExpression: FunctionComponent = ({ fullWidth label={ } > = ({ = ({ value={indexPattern.title} defaultValue={'Select an index pattern and geo shape/point field'} popoverContent={indexPopover} - expressionDescription={i18n.translate( - 'xpack.triggersActionsUI.geoThreshold.entityIndexLabel', - { - defaultMessage: 'index', - } - )} + expressionDescription={i18n.translate('xpack.stackAlerts.geoThreshold.entityIndexLabel', { + defaultMessage: 'index', + })} /> ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx similarity index 89% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx index ccc2ddd9c01ca..f138c08c0f993 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/index.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/index.tsx @@ -19,15 +19,17 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { AlertTypeParamsExpressionProps } from '../../../../../types'; +import { + AlertTypeParamsExpressionProps, + getTimeOptions, + AlertsContextValue, +} from '../../../../../triggers_actions_ui/public'; import { GeoThresholdAlertParams, TrackingEvent } from '../types'; -import { AlertsContextValue } from '../../../../context/alerts_context'; import { ExpressionWithPopover } from './util_components/expression_with_popover'; import { EntityIndexExpression } from './expressions/entity_index_expression'; import { EntityByExpression } from './expressions/entity_by_expression'; import { BoundaryIndexExpression } from './expressions/boundary_index_expression'; -import { IIndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns'; -import { getTimeOptions } from '../../../../../common/lib/get_time_options'; +import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns'; const DEFAULT_VALUES = { TRACKING_EVENT: '', @@ -45,20 +47,20 @@ const DEFAULT_VALUES = { }; const conditionOptions = Object.keys(TrackingEvent).map((key) => ({ - text: (TrackingEvent as any)[key], - value: (TrackingEvent as any)[key], + text: TrackingEvent[key as TrackingEvent], + value: TrackingEvent[key as TrackingEvent], })); const labelForDelayOffset = ( <> {' '} @@ -125,7 +127,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent
@@ -221,7 +223,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent
@@ -251,7 +253,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent
@@ -280,19 +282,16 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent
} - expressionDescription={i18n.translate( - 'xpack.triggersActionsUI.geoThreshold.whenEntityLabel', - { - defaultMessage: 'when entity', - } - )} + expressionDescription={i18n.translate('xpack.stackAlerts.geoThreshold.whenEntityLabel', { + defaultMessage: 'when entity', + })} />
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/expression_with_popover.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/expression_with_popover.tsx similarity index 88% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/expression_with_popover.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/expression_with_popover.tsx index 7e1cae51f1411..a83667cfd92c6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/expression_with_popover.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/expression_with_popover.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useState } from 'react'; +import React, { ReactNode, useState } from 'react'; import { EuiButtonIcon, EuiExpression, @@ -22,10 +22,10 @@ export const ExpressionWithPopover: ({ value, isInvalid, }: { - popoverContent: any; - expressionDescription: any; - defaultValue?: any; - value?: any; + popoverContent: ReactNode; + expressionDescription: ReactNode; + defaultValue?: ReactNode; + value?: ReactNode; isInvalid?: boolean; }) => JSX.Element = ({ popoverContent, expressionDescription, defaultValue, value, isInvalid }) => { const [popoverOpen, setPopoverOpen] = useState(false); @@ -61,7 +61,7 @@ export const ExpressionWithPopover: ({ iconType="cross" color="danger" aria-label={i18n.translate( - 'xpack.triggersActionsUI.sections.alertAdd.geoThreshold.closePopoverLabel', + 'xpack.stackAlerts.geoThreshold.ui.expressionPopover.closePopoverLabel', { defaultMessage: 'Close', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx similarity index 80% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx index 42995dfb1b9d6..a552d6d998c7e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/geo_index_pattern_select.tsx @@ -14,6 +14,7 @@ import { HttpSetup } from 'kibana/public'; interface Props { onChange: (indexPattern: IndexPattern) => void; value: string | undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any IndexPatternSelectComponent: any; indexPatternService: IndexPatternsContract | undefined; http: HttpSetup; @@ -39,7 +40,7 @@ export class GeoIndexPatternSelect extends Component { this._isMounted = true; } - _onIndexPatternSelect = async (indexPatternId: any) => { + _onIndexPatternSelect = async (indexPatternId: string) => { if (!indexPatternId || indexPatternId.length === 0 || !this.props.indexPatternService) { return; } @@ -70,42 +71,39 @@ export class GeoIndexPatternSelect extends Component { return ( <>

@@ -123,7 +121,7 @@ export class GeoIndexPatternSelect extends Component { {this._renderNoIndexPatternWarning()} @@ -133,7 +131,7 @@ export class GeoIndexPatternSelect extends Component { indexPatternId={this.props.value} onChange={this._onIndexPatternSelect} placeholder={i18n.translate( - 'xpack.triggersActionsUI.geoThreshold.indexPatternSelectPlaceholder', + 'xpack.stackAlerts.geoThreshold.indexPatternSelectPlaceholder', { defaultMessage: 'Select index pattern', } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/single_field_select.tsx b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/single_field_select.tsx similarity index 96% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/single_field_select.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/single_field_select.tsx index 30389b31ce8c8..ef6e6f6f5e18f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/query_builder/util_components/single_field_select.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/query_builder/util_components/single_field_select.tsx @@ -14,7 +14,7 @@ import { EuiFlexItem, } from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; -import { FieldIcon } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; function fieldsToOptions(fields?: IFieldType[]): Array> { if (!fields) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/types.ts rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/types.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/validation.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/validation.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/validation.test.ts rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/validation.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/validation.ts similarity index 72% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/validation.ts rename to x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/validation.ts index 078a88d9e8415..7a511f681ecaa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/geo_threshold/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/validation.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { ValidationResult } from '../../../../types'; +import { ValidationResult } from '../../../../triggers_actions_ui/public'; import { GeoThresholdAlertParams } from './types'; export const validateExpression = (alertParams: GeoThresholdAlertParams): ValidationResult => { @@ -35,7 +35,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!index) { errors.index.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredIndexTitleText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredIndexTitleText', { defaultMessage: 'Index pattern is required.', }) ); @@ -43,7 +43,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!geoField) { errors.geoField.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredGeoFieldText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredGeoFieldText', { defaultMessage: 'Geo field is required.', }) ); @@ -51,7 +51,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!entity) { errors.entity.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredEntityText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredEntityText', { defaultMessage: 'Entity is required.', }) ); @@ -59,7 +59,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!dateField) { errors.dateField.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredDateFieldText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredDateFieldText', { defaultMessage: 'Date field is required.', }) ); @@ -67,7 +67,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!trackingEvent) { errors.trackingEvent.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredTrackingEventText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredTrackingEventText', { defaultMessage: 'Tracking event is required.', }) ); @@ -75,7 +75,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!boundaryType) { errors.boundaryType.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryTypeText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredBoundaryTypeText', { defaultMessage: 'Boundary type is required.', }) ); @@ -83,7 +83,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!boundaryIndexTitle) { errors.boundaryIndexTitle.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryIndexTitleText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredBoundaryIndexTitleText', { defaultMessage: 'Boundary index pattern title is required.', }) ); @@ -91,7 +91,7 @@ export const validateExpression = (alertParams: GeoThresholdAlertParams): Valida if (!boundaryGeoField) { errors.boundaryGeoField.push( - i18n.translate('xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryGeoFieldText', { + i18n.translate('xpack.stackAlerts.geoThreshold.error.requiredBoundaryGeoFieldText', { defaultMessage: 'Boundary geo field is required.', }) ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/index.ts similarity index 57% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts rename to x-pack/plugins/stack_alerts/public/alert_types/index.ts index 4b2860dcf9b72..61cf7193fedb7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/index.ts @@ -6,18 +6,17 @@ import { getAlertType as getGeoThresholdAlertType } from './geo_threshold'; import { getAlertType as getThresholdAlertType } from './threshold'; -import { TypeRegistry } from '../../type_registry'; -import { AlertTypeModel } from '../../../types'; -import { TriggersActionsUiConfigType } from '../../../plugin'; +import { Config } from '../../common'; +import { TriggersAndActionsUIPublicPluginSetup } from '../../../triggers_actions_ui/public'; -export function registerBuiltInAlertTypes({ +export function registerAlertTypes({ alertTypeRegistry, - triggerActionsUiConfig, + config, }: { - alertTypeRegistry: TypeRegistry; - triggerActionsUiConfig: TriggersActionsUiConfigType; + alertTypeRegistry: TriggersAndActionsUIPublicPluginSetup['alertTypeRegistry']; + config: Config; }) { - if (triggerActionsUiConfig.enableGeoTrackingThresholdAlert) { + if (config.enableGeoTrackingThresholdAlert) { alertTypeRegistry.register(getGeoThresholdAlertType()); } alertTypeRegistry.register(getThresholdAlertType()); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.scss b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.scss similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.scss rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.scss diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx similarity index 86% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx index e309d97b57f34..92cb8c9055bde 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/expression.tsx @@ -29,21 +29,20 @@ import { getIndexPatterns, getIndexOptions, getFields, -} from '../../../../common/index_controls'; -import { COMPARATORS, builtInComparators } from '../../../../common/constants'; -import { getTimeFieldOptions } from '../../../../common/lib/get_time_options'; -import { ThresholdVisualization } from './visualization'; -import { WhenExpression } from '../../../../common'; -import { + COMPARATORS, + builtInComparators, + getTimeFieldOptions, OfExpression, ThresholdExpression, ForLastExpression, GroupByExpression, -} from '../../../../common'; -import { builtInAggregationTypes } from '../../../../common/constants'; + WhenExpression, + builtInAggregationTypes, + AlertTypeParamsExpressionProps, + AlertsContextValue, +} from '../../../../triggers_actions_ui/public'; +import { ThresholdVisualization } from './visualization'; import { IndexThresholdAlertParams } from './types'; -import { AlertTypeParamsExpressionProps } from '../../../../types'; -import { AlertsContextValue } from '../../../context/alerts_context'; import './expression.scss'; const DEFAULT_VALUES = { @@ -89,7 +88,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent>([]); + const [esFields, setEsFields] = useState([]); const [indexOptions, setIndexOptions] = useState([]); const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); const [isIndiciesLoading, setIsIndiciesLoading] = useState(false); @@ -98,7 +97,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent expressionFieldsWithValidation.includes(errorKey) && errors[errorKey].length >= 1 && - (alertParams as { [key: string]: any })[errorKey] !== undefined + alertParams[errorKey as keyof IndexThresholdAlertParams] !== undefined ); const canShowVizualization = !!Object.keys(errors).find( @@ -106,7 +105,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent 0) { const currentEsFields = await getFields(http, index); - const timeFields = getTimeFieldOptions(currentEsFields as any); + const timeFields = getTimeFieldOptions(currentEsFields); setEsFields(currentEsFields); setTimeFieldOptions([firstFieldOption, ...timeFields]); @@ -159,7 +158,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent } @@ -167,7 +166,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent } @@ -211,7 +210,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent } @@ -284,7 +283,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent

@@ -296,12 +295,9 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent 0 ? renderIndices(index) : firstFieldOption.text} isActive={indexPopoverOpen} onClick={() => { @@ -321,12 +317,9 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent - {i18n.translate( - 'xpack.triggersActionsUI.sections.alertAdd.threshold.indexButtonLabel', - { - defaultMessage: 'index', - } - )} + {i18n.translate('xpack.stackAlerts.threshold.ui.alertParams.indexButtonLabel', { + defaultMessage: 'index', + })}
@@ -411,10 +404,10 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent + onChangeWindowSize={(selectedWindowSize: number | undefined) => setAlertParams('timeWindowSize', selectedWindowSize) } - onChangeWindowUnit={(selectedWindowUnit: any) => + onChangeWindowUnit={(selectedWindowUnit: string) => setAlertParams('timeWindowUnit', selectedWindowUnit) } /> @@ -427,7 +420,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts similarity index 77% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts index a5b2fbb37e838..b7923a3013613 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts @@ -5,18 +5,17 @@ */ import { lazy } from 'react'; import { i18n } from '@kbn/i18n'; -import { AlertTypeModel } from '../../../../types'; import { validateExpression } from './validation'; import { IndexThresholdAlertParams } from './types'; -import { AlertsContextValue } from '../../../context/alerts_context'; +import { AlertTypeModel, AlertsContextValue } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.index-threshold', - name: i18n.translate('xpack.triggersActionsUI.indexThresholdAlert.nameText', { + name: i18n.translate('xpack.stackAlerts.threshold.ui.alertType.nameText', { defaultMessage: 'Index threshold', }), - description: i18n.translate('xpack.triggersActionsUI.indexThresholdAlert.descriptionText', { + description: i18n.translate('xpack.stackAlerts.threshold.ui.alertType.descriptionText', { defaultMessage: 'Alert when an aggregated query meets the threshold.', }), iconClass: 'alert', @@ -26,7 +25,7 @@ export function getAlertType(): AlertTypeModel import('./expression')), validate: validateExpression, defaultActionMessage: i18n.translate( - 'xpack.triggersActionsUI.components.builtinAlertTypes.threshold.alertDefaultActionMessage', + 'xpack.stackAlerts.threshold.ui.alertType.defaultActionMessage', { defaultMessage: `alert \\{\\{alertName\\}\\} group \\{\\{context.group\\}\\} value \\{\\{context.value\\}\\} exceeded threshold \\{\\{context.function\\}\\} over \\{\\{params.timeWindowSize\\}\\}\\{\\{params.timeWindowUnit\\}\\} on \\{\\{context.date\\}\\}`, } diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts new file mode 100644 index 0000000000000..ec531b26fc8c6 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index_threshold_api.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { HttpSetup } from 'kibana/public'; +import { TimeSeriesResult } from '../../../../triggers_actions_ui/common'; +import { IndexThresholdAlertParams } from './types'; + +const INDEX_THRESHOLD_DATA_API_ROOT = '/api/triggers_actions_ui/data'; + +export interface GetThresholdAlertVisualizationDataParams { + model: IndexThresholdAlertParams; + visualizeOptions: { + rangeFrom: string; + rangeTo: string; + interval: string; + }; + http: HttpSetup; +} + +export async function getThresholdAlertVisualizationData({ + model, + visualizeOptions, + http, +}: GetThresholdAlertVisualizationDataParams): Promise { + const timeSeriesQueryParams = { + index: model.index, + timeField: model.timeField, + aggType: model.aggType, + aggField: model.aggField, + groupBy: model.groupBy, + termField: model.termField, + termSize: model.termSize, + timeWindowSize: model.timeWindowSize, + timeWindowUnit: model.timeWindowUnit, + dateStart: new Date(visualizeOptions.rangeFrom).toISOString(), + dateEnd: new Date(visualizeOptions.rangeTo).toISOString(), + interval: visualizeOptions.interval, + }; + + return await http.post(`${INDEX_THRESHOLD_DATA_API_ROOT}/_time_series_query`, { + body: JSON.stringify(timeSeriesQueryParams), + }); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/types.ts rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/types.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/validation.test.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.test.ts similarity index 100% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/validation.test.ts rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.test.ts diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/validation.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts similarity index 81% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/validation.ts rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts index 3912b2fffae1e..4bbf80906377b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/validation.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/validation.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { ValidationResult } from '../../../../types'; import { IndexThresholdAlertParams } from './types'; import { + ValidationResult, builtInGroupByTypes, builtInAggregationTypes, builtInComparators, -} from '../../../../common/constants'; +} from '../../../../triggers_actions_ui/public'; export const validateExpression = (alertParams: IndexThresholdAlertParams): ValidationResult => { const { @@ -39,21 +39,21 @@ export const validateExpression = (alertParams: IndexThresholdAlertParams): Vali validationResult.errors = errors; if (!index || index.length === 0) { errors.index.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredIndexText', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredIndexText', { defaultMessage: 'Index is required.', }) ); } if (!timeField) { errors.timeField.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredTimeFieldText', { defaultMessage: 'Time field is required.', }) ); } if (aggType && builtInAggregationTypes[aggType].fieldRequired && !aggField) { errors.aggField.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredAggFieldText', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredAggFieldText', { defaultMessage: 'Aggregation field is required.', }) ); @@ -65,7 +65,7 @@ export const validateExpression = (alertParams: IndexThresholdAlertParams): Vali !termSize ) { errors.termSize.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTermSizedText', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredTermSizedText', { defaultMessage: 'Term size is required.', }) ); @@ -77,21 +77,21 @@ export const validateExpression = (alertParams: IndexThresholdAlertParams): Vali !termField ) { errors.termField.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredTermFieldText', { defaultMessage: 'Term field is required.', }) ); } if (!timeWindowSize) { errors.timeWindowSize.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredTimeWindowSizeText', { defaultMessage: 'Time window size is required.', }) ); } if (!threshold || threshold.length === 0 || threshold[0] === undefined) { errors.threshold0.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold0Text', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredThreshold0Text', { defaultMessage: 'Threshold0 is required.', }) ); @@ -104,14 +104,14 @@ export const validateExpression = (alertParams: IndexThresholdAlertParams): Vali (threshold && threshold.length < builtInComparators[thresholdComparator!].requiredValues)) ) { errors.threshold1.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold1Text', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.requiredThreshold1Text', { defaultMessage: 'Threshold1 is required.', }) ); } if (threshold && threshold.length === 2 && threshold[0] > threshold[1]) { errors.threshold1.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.greaterThenThreshold0Text', { + i18n.translate('xpack.stackAlerts.threshold.ui.validation.error.greaterThenThreshold0Text', { defaultMessage: 'Threshold1 should be > Threshold0.', }) ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.tsx similarity index 86% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx rename to x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.tsx index a282fa08e8f38..6145aa3671a7f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/visualization.tsx +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/visualization.tsx @@ -29,11 +29,17 @@ import { EuiLoadingSpinner, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getThresholdAlertVisualizationData } from '../../../../common/lib/index_threshold_api'; -import { AggregationType, Comparator } from '../../../../common/types'; -import { AlertsContextValue } from '../../../context/alerts_context'; +import { + getThresholdAlertVisualizationData, + GetThresholdAlertVisualizationDataParams, +} from './index_threshold_api'; +import { + AlertsContextValue, + AggregationType, + Comparator, +} from '../../../../triggers_actions_ui/public'; import { IndexThresholdAlertParams } from './types'; -import { parseDuration } from '../../../../../../alerts/common/parse_duration'; +import { parseDuration } from '../../../../alerts/common/parse_duration'; const customTheme = () => { return { @@ -125,7 +131,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ const { http, toastNotifications, charts, uiSettings, dataFieldsFormats } = alertsContext; const [loadingState, setLoadingState] = useState(null); - const [error, setError] = useState(undefined); + const [error, setError] = useState(undefined); const [visualizationData, setVisualizationData] = useState>(); const [startVisualizationAt, setStartVisualizationAt] = useState(new Date()); @@ -150,7 +156,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ if (toastNotifications) { toastNotifications.addDanger({ title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertAdd.unableToLoadVisualizationMessage', + 'xpack.stackAlerts.threshold.ui.visualization.unableToLoadVisualizationMessage', { defaultMessage: 'Unable to load visualization' } ), }); @@ -199,7 +205,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ body={ @@ -215,7 +221,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ @@ -239,7 +245,7 @@ export const ThresholdVisualization: React.FunctionComponent = ({ const alertVisualizationDataKeys = Object.keys(visualizationData); const timezone = getTimezone(uiSettings); const actualThreshold = getThreshold(); - let maxY = actualThreshold[actualThreshold.length - 1] as any; + let maxY = actualThreshold[actualThreshold.length - 1]; (Object.values(visualizationData) as number[][][]).forEach((data) => { data.forEach(([, y]) => { @@ -288,14 +294,14 @@ export const ThresholdVisualization: React.FunctionComponent = ({ /> ); })} - {actualThreshold.map((_value: any, i: number) => { - const specId = i === 0 ? 'threshold' : `threshold${i}`; + {actualThreshold.map((_value: number, thresholdIndex: number) => { + const specId = thresholdIndex === 0 ? 'threshold' : `threshold${thresholdIndex}`; return ( ); })} @@ -305,14 +311,14 @@ export const ThresholdVisualization: React.FunctionComponent = ({ size="s" title={ } color="warning" > @@ -325,7 +331,11 @@ export const ThresholdVisualization: React.FunctionComponent = ({ }; // convert the data from the visualization API into something easier to digest with charts -async function getVisualizationData(model: any, visualizeOptions: any, http: HttpSetup) { +async function getVisualizationData( + model: IndexThresholdAlertParams, + visualizeOptions: GetThresholdAlertVisualizationDataParams['visualizeOptions'], + http: HttpSetup +) { const vizData = await getThresholdAlertVisualizationData({ model, visualizeOptions, diff --git a/x-pack/plugins/stack_alerts/public/index.ts b/x-pack/plugins/stack_alerts/public/index.ts new file mode 100644 index 0000000000000..2f84a5949f111 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { StackAlertsPublicPlugin } from './plugin'; + +export const plugin = (ctx: PluginInitializerContext) => new StackAlertsPublicPlugin(ctx); diff --git a/x-pack/plugins/stack_alerts/public/plugin.tsx b/x-pack/plugins/stack_alerts/public/plugin.tsx new file mode 100644 index 0000000000000..63176e7b30277 --- /dev/null +++ b/x-pack/plugins/stack_alerts/public/plugin.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { TriggersAndActionsUIPublicPluginSetup } from '../../../plugins/triggers_actions_ui/public'; +import { registerAlertTypes } from './alert_types'; +import { Config } from '../common'; + +export type Setup = void; +export type Start = void; + +export interface StackAlertsPublicSetupDeps { + triggersActionsUi: TriggersAndActionsUIPublicPluginSetup; +} + +export class StackAlertsPublicPlugin implements Plugin { + private initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { + this.initializerContext = initializerContext; + } + + public setup(core: CoreSetup, { triggersActionsUi }: StackAlertsPublicSetupDeps) { + registerAlertTypes({ + alertTypeRegistry: triggersActionsUi.alertTypeRegistry, + config: this.initializerContext.config.get(), + }); + } + + public start() {} + public stop() {} +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts index 360a6eb169573..9fc46fe2f2586 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { Service } from '../../types'; +import { Logger } from 'src/core/server'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { getGeoThresholdExecutor } from './geo_threshold'; import { @@ -173,7 +173,7 @@ export interface GeoThresholdParams { } export function getAlertType( - service: Omit + logger: Logger ): { defaultActionGroupId: string; actionGroups: ActionGroup[]; @@ -222,7 +222,7 @@ export function getAlertType( name: alertTypeName, actionGroups: [{ id: ActionGroupId, name: actionGroupName }], defaultActionGroupId: ActionGroupId, - executor: getGeoThresholdExecutor(service), + executor: getGeoThresholdExecutor(logger), producer: STACK_ALERTS_FEATURE_ID, validate: { params: ParamsSchema, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts index c4238e62ff261..97be51b2a6256 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/es_query_builder.ts @@ -6,7 +6,7 @@ import { ILegacyScopedClusterClient } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; -import { Logger } from '../../types'; +import { Logger } from 'src/core/server'; export const OTHER_CATEGORY = 'other'; // Consider dynamically obtaining from config? diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts index f30dea151ece8..394ee8d606abe 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/geo_threshold.ts @@ -6,10 +6,10 @@ import _ from 'lodash'; import { SearchResponse } from 'elasticsearch'; +import { Logger } from 'src/core/server'; import { executeEsQueryFactory, getShapesFilters, OTHER_CATEGORY } from './es_query_builder'; import { AlertServices, AlertTypeState } from '../../../../alerts/server'; import { ActionGroupId, GEO_THRESHOLD_ID, GeoThresholdParams } from './alert_type'; -import { Logger } from '../../types'; interface LatestEntityLocation { location: number[]; @@ -169,7 +169,7 @@ function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date { return adjustedDate; } -export const getGeoThresholdExecutor = ({ logger: log }: { logger: Logger }) => +export const getGeoThresholdExecutor = (log: Logger) => async function ({ previousStartedAt, startedAt, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/index.ts index d57f219bb8f9a..2fa2bed9d8419 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/index.ts @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Service, AlertingSetup } from '../../types'; +import { Logger } from 'src/core/server'; +import { AlertingSetup } from '../../types'; import { getAlertType } from './alert_type'; interface RegisterParams { - service: Omit; + logger: Logger; alerts: AlertingSetup; } export function register(params: RegisterParams) { - const { service, alerts } = params; - alerts.registerType(getAlertType(service)); + const { logger, alerts } = params; + alerts.registerType(getAlertType(logger)); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts index 5cf113f519a5a..49b56b5571b44 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts @@ -8,11 +8,9 @@ import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; import { getAlertType, GeoThresholdParams } from '../alert_type'; describe('alertType', () => { - const service = { - logger: loggingSystemMock.create().get(), - }; + const logger = loggingSystemMock.create().get(); - const alertType = getAlertType(service); + const alertType = getAlertType(logger); it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.geo-threshold'); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/index.ts index dd9f1488092f4..461358d1296e2 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index.ts @@ -4,15 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Service, IRouter, AlertingSetup } from '../types'; +import { Logger } from 'src/core/server'; +import { AlertingSetup, StackAlertsStartDeps } from '../types'; import { register as registerIndexThreshold } from './index_threshold'; import { register as registerGeoThreshold } from './geo_threshold'; interface RegisterAlertTypesParams { - service: Service; - router: IRouter; + logger: Logger; + data: Promise; alerts: AlertingSetup; - baseRoute: string; } export function registerBuiltInAlertTypes(params: RegisterAlertTypesParams) { diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/README.md b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/README.md index 0ff01ddfb49c7..9b0eb23950cc3 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/README.md +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/README.md @@ -13,10 +13,7 @@ is exceeded. ## alertType `.index-threshold` -The alertType parameters are specified in -[`lib/core_query_types.ts`][it-core-query] -and -[`alert_type_params.ts`][it-alert-params]. +The alertType parameters are specified in [`alert_type_params.ts`][it-alert-params]. The alertType has a single actionGroup, `'threshold met'`. The `context` object provided to actions is specified in @@ -123,227 +120,6 @@ server log [17:32:10.060] [warning][actions][actions][plugins] \ [now-iso]: https://github.com/pmuellr/now-iso -## http endpoints +## Data Apis via the TriggersActionsUi plugin and its http endpoints -The following endpoints are provided for this alert type: - -- `POST /api/stack_alerts/index_threshold/_indices` -- `POST /api/stack_alerts/index_threshold/_fields` -- `POST /api/stack_alerts/index_threshold/_time_series_query` - -### `POST .../_indices` - -This HTTP endpoint is provided for the alerting ui to list the available -"index names" for the user to select to use with the alert. This API also -returns aliases which match the supplied pattern. - -The request body is expected to be a JSON object in the following form, where the -`pattern` value may include comma-separated names and wildcards. - -```js -{ - pattern: "index-name-pattern" -} -``` - -The response body is a JSON object in the following form, where each element -of the `indices` array is the name of an index or alias. The number of elements -returned is limited, as this API is intended to be used to help narrow down -index names to use with the alert, and not support pagination, etc. - -```js -{ - indices: ["index-name-1", "alias-name-1", ...] -} -``` - -### `POST .../_fields` - -This HTTP endpoint is provided for the alerting ui to list the available -fields for the user to select to use with the alert. - -The request body is expected to be a JSON object in the following form, where the -`indexPatterns` array elements may include comma-separated names and wildcards. - -```js -{ - indexPatterns: ["index-pattern-1", "index-pattern-2"] -} -``` - -The response body is a JSON object in the following form, where each element -fields array is a field object. - -```js -{ - fields: [fieldObject1, fieldObject2, ...] -} -``` - -A field object is the following shape: - -```typescript -{ - name: string, // field name - type: string, // field type - eg 'keyword', 'date', 'long', etc - normalizedType: string, // for numeric types, this will be 'number' - aggregatable: true, // value from elasticsearch field capabilities - searchable: true, // value from elasticsearch field capabilities -} -``` - -### `POST .../_time_series_query` - -This HTTP endpoint is provided to return the values the alertType would calculate, -over a series of time. It is intended to be used in the alerting UI to -provide a "preview" of the alert during creation/editing based on recent data, -and could be used to show a "simulation" of the the alert over an arbitrary -range of time. - -The endpoint is `POST /api/stack_alerts/index_threshold/_time_series_query`. -The request and response bodies are specifed in -[`lib/core_query_types.ts`][it-core-query] -and -[`lib/time_series_types.ts`][it-timeSeries-types]. -The request body is very similar to the alertType's parameters. - -### example - -Continuing with the example above, here's a query to get the values calculated -for the last 10 seconds. -This example uses [now-iso][] to generate iso date strings. - -```console -curl -k "https://elastic:changeme@localhost:5601/api/stack_alerts/index_threshold/_time_series_query" \ - -H "kbn-xsrf: foo" -H "content-type: application/json" -d "{ - \"index\": \"es-hb-sim\", - \"timeField\": \"@timestamp\", - \"aggType\": \"avg\", - \"aggField\": \"summary.up\", - \"groupBy\": \"top\", - \"termSize\": 100, - \"termField\": \"monitor.name.keyword\", - \"interval\": \"1s\", - \"dateStart\": \"`now-iso -10s`\", - \"dateEnd\": \"`now-iso`\", - \"timeWindowSize\": 5, - \"timeWindowUnit\": \"s\" -}" -``` - -``` -{ - "results": [ - { - "group": "host-A", - "metrics": [ - [ "2020-02-26T15:10:40.000Z", 0 ], - [ "2020-02-26T15:10:41.000Z", 0 ], - [ "2020-02-26T15:10:42.000Z", 0 ], - [ "2020-02-26T15:10:43.000Z", 0 ], - [ "2020-02-26T15:10:44.000Z", 0 ], - [ "2020-02-26T15:10:45.000Z", 0 ], - [ "2020-02-26T15:10:46.000Z", 0 ], - [ "2020-02-26T15:10:47.000Z", 0 ], - [ "2020-02-26T15:10:48.000Z", 0 ], - [ "2020-02-26T15:10:49.000Z", 0 ], - [ "2020-02-26T15:10:50.000Z", 0 ] - ] - } - ] -} -``` - -To get the current value of the calculated metric, you can leave off the date: - -``` -curl -k "https://elastic:changeme@localhost:5601/api/stack_alerts/index_threshold/_time_series_query" \ - -H "kbn-xsrf: foo" -H "content-type: application/json" -d '{ - "index": "es-hb-sim", - "timeField": "@timestamp", - "aggType": "avg", - "aggField": "summary.up", - "groupBy": "top", - "termField": "monitor.name.keyword", - "termSize": 100, - "interval": "1s", - "timeWindowSize": 5, - "timeWindowUnit": "s" -}' -``` - -``` -{ - "results": [ - { - "group": "host-A", - "metrics": [ - [ "2020-02-26T15:23:36.635Z", 0 ] - ] - } - ] -} -``` - -[it-timeSeries-types]: lib/time_series_types.ts - -## service functions - -A single service function is available that provides the functionality -of the http endpoint `POST /api/stack_alerts/index_threshold/_time_series_query`, -but as an API for Kibana plugins. The function is available as -`alertingService.indexThreshold.timeSeriesQuery()` - -The parameters and return value for the function are the same as for the HTTP -request, though some additional parameters are required (logger, callCluster, -etc). - -## notes on the timeSeriesQuery API / http endpoint - -This API provides additional parameters beyond what the alertType itself uses: - -- `dateStart` -- `dateEnd` -- `interval` - -The `dateStart` and `dateEnd` parameters are ISO date strings. - -The `interval` parameter is intended to model the `interval` the alert is -currently using, and uses the same `1s`, `2m`, `3h`, etc format. Over the -supplied date range, a time-series data point will be calculated every -`interval` duration. - -So the number of time-series points in the output of the API should be: - -``` -( dateStart - dateEnd ) / interval -``` - -Example: - -``` -dateStart: '2020-01-01T00:00:00' -dateEnd: '2020-01-02T00:00:00' -interval: '1h' -``` - -The date range is 1 day === 24 hours. The interval is 1 hour. So there should -be ~24 time series points in the output. - -For preview purposes: - -- The `termSize` parameter should be used to help cut -down on the amount of work ES does, and keep the generated graphs a little -simpler. Probably something like `10`. - -- For queries with long date ranges, you probably don't want to use the -`interval` the alert is set to, as the `interval` used in the query, as this -could result in a lot of time-series points being generated, which is both -costly in ES, and may result in noisy graphs. - -- The `timeWindow*` parameters should be the same as what the alert is using, -especially for the `count` and `sum` aggregation types. Those aggregations -don't scale the same way the others do, when the window changes. Even for -the other aggregations, changing the window could result in dramatically -different values being generated - `avg` will be more "average-y", `min` -and `max` will be a little stickier. \ No newline at end of file +The Index Threshold Alert Type is backed by Apis exposed by the [TriggersActionsUi plugin](../../../../triggers_actions_ui/README.md). diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts index d75f3af22ab06..0febe805af4e0 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.test.ts @@ -9,14 +9,12 @@ import { getAlertType } from './alert_type'; import { Params } from './alert_type_params'; describe('alertType', () => { - const service = { - indexThreshold: { - timeSeriesQuery: jest.fn(), - }, - logger: loggingSystemMock.create().get(), + const logger = loggingSystemMock.create().get(); + const data = { + timeSeriesQuery: jest.fn(), }; - const alertType = getAlertType(service); + const alertType = getAlertType(logger, Promise.resolve(data)); it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.index-threshold'); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts index e0a9cd981dac0..2d9e1b3adc1b8 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type.ts @@ -5,23 +5,26 @@ */ import { i18n } from '@kbn/i18n'; -import { AlertType, AlertExecutorOptions } from '../../types'; +import { Logger } from 'src/core/server'; +import { AlertType, AlertExecutorOptions, StackAlertsStartDeps } from '../../types'; import { Params, ParamsSchema } from './alert_type_params'; import { ActionContext, BaseActionContext, addMessages } from './action_context'; -import { TimeSeriesQuery } from './lib/time_series_query'; -import { Service } from '../../types'; import { STACK_ALERTS_FEATURE_ID } from '../../../common'; +import { + CoreQueryParamsSchemaProperties, + TimeSeriesQuery, +} from '../../../../triggers_actions_ui/server'; export const ID = '.index-threshold'; -import { CoreQueryParamsSchemaProperties } from './lib/core_query_types'; const ActionGroupId = 'threshold met'; const ComparatorFns = getComparatorFns(); export const ComparatorFnNames = new Set(ComparatorFns.keys()); -export function getAlertType(service: Service): AlertType { - const { logger } = service; - +export function getAlertType( + logger: Logger, + data: Promise +): AlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', { defaultMessage: 'Index threshold', }); @@ -152,7 +155,7 @@ export function getAlertType(service: Service): AlertType> = { index: 'index-name', @@ -71,3 +71,185 @@ describe('alertType Params validate()', () => { return ParamsSchema.validate(params); } }); + +export function runTests(schema: ObjectType, defaultTypeParams: Record): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let params: any; + + const CoreDefaultParams: Writable> = { + index: 'index-name', + timeField: 'time-field', + aggType: 'count', + groupBy: 'all', + timeWindowSize: 5, + timeWindowUnit: 'm', + }; + + describe('coreQueryTypes', () => { + beforeEach(() => { + params = { ...CoreDefaultParams, ...defaultTypeParams }; + }); + + it('succeeds with minimal properties', async () => { + expect(validate()).toBeTruthy(); + }); + + it('succeeds with maximal properties', async () => { + params.aggType = 'avg'; + params.aggField = 'agg-field'; + params.groupBy = 'top'; + params.termField = 'group-field'; + params.termSize = 200; + expect(validate()).toBeTruthy(); + + params.index = ['index-name-1', 'index-name-2']; + params.aggType = 'avg'; + params.aggField = 'agg-field'; + params.groupBy = 'top'; + params.termField = 'group-field'; + params.termSize = 200; + expect(validate()).toBeTruthy(); + }); + + it('fails for invalid index', async () => { + delete params.index; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[index]: expected at least one defined value but got [undefined]"` + ); + + params.index = 42; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot(` +"[index]: types that failed validation: +- [index.0]: expected value of type [string] but got [number] +- [index.1]: expected value of type [array] but got [number]" +`); + + params.index = ''; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot(` +"[index]: types that failed validation: +- [index.0]: value has length [0] but it must have a minimum length of [1]. +- [index.1]: could not parse array value from json input" +`); + + params.index = ['', 'a']; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot(` +"[index]: types that failed validation: +- [index.0]: expected value of type [string] but got [Array] +- [index.1.0]: value has length [0] but it must have a minimum length of [1]." +`); + }); + + it('fails for invalid timeField', async () => { + delete params.timeField; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeField]: expected value of type [string] but got [undefined]"` + ); + + params.timeField = 42; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeField]: expected value of type [string] but got [number]"` + ); + + params.timeField = ''; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeField]: value has length [0] but it must have a minimum length of [1]."` + ); + }); + + it('fails for invalid aggType', async () => { + params.aggType = 42; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[aggType]: expected value of type [string] but got [number]"` + ); + + params.aggType = '-not-a-valid-aggType-'; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[aggType]: invalid aggType: \\"-not-a-valid-aggType-\\""` + ); + }); + + it('fails for invalid aggField', async () => { + params.aggField = 42; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[aggField]: expected value of type [string] but got [number]"` + ); + + params.aggField = ''; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[aggField]: value has length [0] but it must have a minimum length of [1]."` + ); + }); + + it('fails for invalid termField', async () => { + params.groupBy = 'top'; + params.termField = 42; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[termField]: expected value of type [string] but got [number]"` + ); + + params.termField = ''; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[termField]: value has length [0] but it must have a minimum length of [1]."` + ); + }); + + it('fails for invalid termSize', async () => { + params.groupBy = 'top'; + params.termField = 'fee'; + params.termSize = 'foo'; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[termSize]: expected value of type [number] but got [string]"` + ); + + params.termSize = MAX_GROUPS + 1; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[termSize]: must be less than or equal to 1000"` + ); + + params.termSize = 0; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[termSize]: Value must be equal to or greater than [1]."` + ); + }); + + it('fails for invalid timeWindowSize', async () => { + params.timeWindowSize = 'foo'; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeWindowSize]: expected value of type [number] but got [string]"` + ); + + params.timeWindowSize = 0; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeWindowSize]: Value must be equal to or greater than [1]."` + ); + }); + + it('fails for invalid timeWindowUnit', async () => { + params.timeWindowUnit = 42; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeWindowUnit]: expected value of type [string] but got [number]"` + ); + + params.timeWindowUnit = 'x'; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[timeWindowUnit]: invalid timeWindowUnit: \\"x\\""` + ); + }); + + it('fails for invalid aggType/aggField', async () => { + params.aggType = 'avg'; + delete params.aggField; + expect(onValidate()).toThrowErrorMatchingInlineSnapshot( + `"[aggField]: must have a value when [aggType] is \\"avg\\""` + ); + }); + }); + + function onValidate(): () => void { + return () => validate(); + } + + function validate(): unknown { + return schema.validate(params); + } +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts index 34d74fa98f959..b51545770dd7b 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/alert_type_params.ts @@ -7,7 +7,10 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { ComparatorFnNames, getInvalidComparatorMessage } from './alert_type'; -import { CoreQueryParamsSchemaProperties, validateCoreQueryBody } from './lib/core_query_types'; +import { + CoreQueryParamsSchemaProperties, + validateCoreQueryBody, +} from '../../../../triggers_actions_ui/server'; // alert type parameters diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts index 9787ece323c59..a075b0d614cbb 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/index.ts @@ -4,34 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Service, AlertingSetup, IRouter } from '../../types'; -import { timeSeriesQuery } from './lib/time_series_query'; +import { Logger } from 'src/core/server'; +import { AlertingSetup, StackAlertsStartDeps } from '../../types'; import { getAlertType } from './alert_type'; -import { registerRoutes } from './routes'; // future enhancement: make these configurable? export const MAX_INTERVALS = 1000; export const MAX_GROUPS = 1000; export const DEFAULT_GROUPS = 100; -export function getService() { - return { - timeSeriesQuery, - }; -} - interface RegisterParams { - service: Service; - router: IRouter; + logger: Logger; + data: Promise; alerts: AlertingSetup; - baseRoute: string; } export function register(params: RegisterParams) { - const { service, router, alerts, baseRoute } = params; - - alerts.registerType(getAlertType(service)); - - const baseBuiltInRoute = `${baseRoute}/index_threshold`; - registerRoutes({ service, router, baseRoute: baseBuiltInRoute }); + const { logger, data, alerts } = params; + alerts.registerType(getAlertType(logger, data)); } diff --git a/x-pack/plugins/stack_alerts/server/index.ts b/x-pack/plugins/stack_alerts/server/index.ts index 108393c0d1469..adb617558e6f4 100644 --- a/x-pack/plugins/stack_alerts/server/index.ts +++ b/x-pack/plugins/stack_alerts/server/index.ts @@ -4,15 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; import { AlertingBuiltinsPlugin } from './plugin'; -import { configSchema } from './config'; +import { configSchema, Config } from '../common/config'; export { ID as INDEX_THRESHOLD_ID } from './alert_types/index_threshold/alert_type'; -export const plugin = (ctx: PluginInitializerContext) => new AlertingBuiltinsPlugin(ctx); - -export const config = { +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + enableGeoTrackingThresholdAlert: true, + }, schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot( + 'xpack.triggers_actions_ui.enableGeoTrackingThresholdAlert', + 'xpack.stack_alerts.enableGeoTrackingThresholdAlert' + ), + ], }; -export { IService } from './types'; +export const plugin = (ctx: PluginInitializerContext) => new AlertingBuiltinsPlugin(ctx); diff --git a/x-pack/plugins/stack_alerts/server/plugin.test.ts b/x-pack/plugins/stack_alerts/server/plugin.test.ts index 3e2a919be0f13..71972707852fe 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.test.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.test.ts @@ -69,34 +69,5 @@ describe('AlertingBuiltins Plugin', () => { expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith(BUILT_IN_ALERTS_FEATURE); }); - - it('should return a service in the expected shape', async () => { - const alertingSetup = alertsMock.createSetup(); - const featuresSetup = featuresPluginMock.createSetup(); - const service = await plugin.setup(coreSetup, { - alerts: alertingSetup, - features: featuresSetup, - }); - - expect(typeof service.indexThreshold.timeSeriesQuery).toBe('function'); - }); - }); - - describe('start()', () => { - let context: ReturnType; - let plugin: AlertingBuiltinsPlugin; - let coreStart: ReturnType; - - beforeEach(() => { - context = coreMock.createPluginInitializerContext(); - plugin = new AlertingBuiltinsPlugin(context); - coreStart = coreMock.createStart(); - }); - - it('should return a service in the expected shape', async () => { - const service = await plugin.start(coreStart); - - expect(typeof service.indexThreshold.timeSeriesQuery).toBe('function'); - }); }); }); diff --git a/x-pack/plugins/stack_alerts/server/plugin.ts b/x-pack/plugins/stack_alerts/server/plugin.ts index f250bbc70fb80..66ac9e455e8b6 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.ts @@ -4,40 +4,35 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, Logger, CoreSetup, CoreStart, PluginInitializerContext } from 'src/core/server'; +import { Plugin, Logger, CoreSetup, PluginInitializerContext } from 'src/core/server'; -import { Service, IService, StackAlertsDeps } from './types'; -import { getService as getServiceIndexThreshold } from './alert_types/index_threshold'; +import { StackAlertsDeps, StackAlertsStartDeps } from './types'; import { registerBuiltInAlertTypes } from './alert_types'; import { BUILT_IN_ALERTS_FEATURE } from './feature'; -export class AlertingBuiltinsPlugin implements Plugin { +export class AlertingBuiltinsPlugin + implements Plugin { private readonly logger: Logger; - private readonly service: Service; constructor(ctx: PluginInitializerContext) { this.logger = ctx.logger.get(); - this.service = { - indexThreshold: getServiceIndexThreshold(), - logger: this.logger, - }; } - public async setup(core: CoreSetup, { alerts, features }: StackAlertsDeps): Promise { + public async setup( + core: CoreSetup, + { alerts, features }: StackAlertsDeps + ): Promise { features.registerKibanaFeature(BUILT_IN_ALERTS_FEATURE); registerBuiltInAlertTypes({ - service: this.service, - router: core.http.createRouter(), + logger: this.logger, + data: core + .getStartServices() + .then(async ([, { triggersActionsUi }]) => triggersActionsUi.data), alerts, - baseRoute: '/api/stack_alerts', }); - return this.service; - } - - public async start(core: CoreStart): Promise { - return this.service; } + public async start(): Promise {} public async stop(): Promise {} } diff --git a/x-pack/plugins/stack_alerts/server/types.ts b/x-pack/plugins/stack_alerts/server/types.ts index d0eb8aa768915..e37596e8ff970 100644 --- a/x-pack/plugins/stack_alerts/server/types.ts +++ b/x-pack/plugins/stack_alerts/server/types.ts @@ -4,11 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, LegacyScopedClusterClient } from '../../../../src/core/server'; +import { PluginStartContract as TriggersActionsUiStartContract } from '../../triggers_actions_ui/server'; import { PluginSetupContract as AlertingSetup } from '../../alerts/server'; -import { getService as getServiceIndexThreshold } from './alert_types/index_threshold'; - -export { Logger, IRouter } from '../../../../src/core/server'; export { PluginSetupContract as AlertingSetup, @@ -23,14 +20,6 @@ export interface StackAlertsDeps { features: FeaturesPluginSetup; } -// external service exposed through plugin setup/start -export interface IService { - indexThreshold: ReturnType; -} - -// version of service for internal use -export interface Service extends IService { - logger: Logger; +export interface StackAlertsStartDeps { + triggersActionsUi: TriggersActionsUiStartContract; } - -export type CallCluster = LegacyScopedClusterClient['callAsCurrentUser']; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 912cb01d458e2..238b3dccc698a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19525,24 +19525,24 @@ "xpack.stackAlerts.indexThreshold.actionVariableContextThresholdLabel": "しきい値として使用する値の配列。「between」と「notBetween」には2つの値が必要です。その他は1つの値が必要です。", "xpack.stackAlerts.indexThreshold.actionVariableContextTitleLabel": "アラートの事前構成タイトル。", "xpack.stackAlerts.indexThreshold.actionVariableContextValueLabel": "しきい値を超えた値。", - "xpack.stackAlerts.indexThreshold.aggTypeRequiredErrorMessage": "[aggType]が「{aggType}」のときには[aggField]に値が必要です", + "xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage": "[aggType]が「{aggType}」のときには[aggField]に値が必要です", "xpack.stackAlerts.indexThreshold.alertTypeContextMessageDescription": "アラート{name}グループ{group}値{value}が{date}に{window}にわたってしきい値{function}を超えました", "xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle": "アラート{name}グループ{group}がしきい値を超えました", "xpack.stackAlerts.indexThreshold.alertTypeTitle": "インデックスしきい値", - "xpack.stackAlerts.indexThreshold.dateStartGTdateEndErrorMessage": "[dateStart]が[dateEnd]よりも大です", - "xpack.stackAlerts.indexThreshold.formattedFieldErrorMessage": "{fieldName}の無効な{formatName}形式:「{fieldValue}」", - "xpack.stackAlerts.indexThreshold.intervalRequiredErrorMessage": "[interval]: [dateStart]が[dateEnd]と等しくない場合に指定する必要があります", - "xpack.stackAlerts.indexThreshold.invalidAggTypeErrorMessage": "無効な aggType:「{aggType}」", + "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]が[dateEnd]よりも大です", + "xpack.triggersActionsUI.data.coreQueryParams.formattedFieldErrorMessage": "{fieldName}の無効な{formatName}形式:「{fieldValue}」", + "xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval]: [dateStart]が[dateEnd]と等しくない場合に指定する必要があります", + "xpack.triggersActionsUI.data.coreQueryParams.invalidAggTypeErrorMessage": "無効な aggType:「{aggType}」", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "無効なthresholdComparatorが指定されました: {comparator}", - "xpack.stackAlerts.indexThreshold.invalidDateErrorMessage": "無効な日付{date}", - "xpack.stackAlerts.indexThreshold.invalidDurationErrorMessage": "無効な期間:「{duration}」", - "xpack.stackAlerts.indexThreshold.invalidGroupByErrorMessage": "無効なgroupBy:「{groupBy}」", - "xpack.stackAlerts.indexThreshold.invalidTermSizeMaximumErrorMessage": "[termSize]: {maxGroups}以下でなければなりません。", + "xpack.triggersActionsUI.data.coreQueryParams.invalidDateErrorMessage": "無効な日付{date}", + "xpack.triggersActionsUI.data.coreQueryParams.invalidDurationErrorMessage": "無効な期間:「{duration}」", + "xpack.triggersActionsUI.data.coreQueryParams.invalidGroupByErrorMessage": "無効なgroupBy:「{groupBy}」", + "xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage": "[termSize]: {maxGroups}以下でなければなりません。", "xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold]: 「{thresholdComparator}」比較子の場合には2つの要素が必要です", - "xpack.stackAlerts.indexThreshold.invalidTimeWindowUnitsErrorMessage": "無効なtimeWindowUnit:「{timeWindowUnit}」", - "xpack.stackAlerts.indexThreshold.maxIntervalsErrorMessage": "間隔{intervals}の計算値が{maxIntervals}よりも大です", - "xpack.stackAlerts.indexThreshold.termFieldRequiredErrorMessage": "[termField]: [groupBy]がトップのときにはtermFieldが必要です", - "xpack.stackAlerts.indexThreshold.termSizeRequiredErrorMessage": "[termSize]: [groupBy]がトップのときにはtermSizeが必要です", + "xpack.triggersActionsUI.data.coreQueryParams.invalidTimeWindowUnitsErrorMessage": "無効なtimeWindowUnit:「{timeWindowUnit}」", + "xpack.triggersActionsUI.data.coreQueryParams.maxIntervalsErrorMessage": "間隔{intervals}の計算値が{maxIntervals}よりも大です", + "xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage": "[termField]: [groupBy]がトップのときにはtermFieldが必要です", + "xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage": "[termSize]: [groupBy]がトップのときにはtermSizeが必要です", "xpack.transform.actionDeleteTransform.bulkDeleteDestinationIndexTitle": "ディスティネーションインデックスの削除", "xpack.transform.actionDeleteTransform.bulkDeleteDestIndexPatternTitle": "ディスティネーションインデックスパターンの削除", "xpack.transform.actionDeleteTransform.deleteDestinationIndexTitle": "ディスティネーションインデックス{destinationIndex}の削除", @@ -20078,42 +20078,42 @@ "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "キャンセル", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "{numIdsToDelete, plural, one {{singleTitle}} other {# {multipleTitle}}}を削除 ", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.descriptionText": "{numIdsToDelete, plural, one {a deleted {singleTitle}} other {deleted {multipleTitle}}}を回復できません。", - "xpack.triggersActionsUI.geoThreshold.boundaryNameSelect": "境界名を選択", - "xpack.triggersActionsUI.geoThreshold.boundaryNameSelectLabel": "人間が読み取れる境界名(任意)", - "xpack.triggersActionsUI.geoThreshold.delayOffset": "遅延評価オフセット", - "xpack.triggersActionsUI.geoThreshold.delayOffsetTooltip": "遅延サイクルでアラートを評価し、データレイテンシに合わせて調整します", - "xpack.triggersActionsUI.geoThreshold.entityByLabel": "グループ基準", - "xpack.triggersActionsUI.geoThreshold.entityIndexLabel": "インデックス", - "xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryGeoFieldText": "境界地理フィールドは必須です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryIndexTitleText": "境界インデックスパターンタイトルは必須です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryTypeText": "境界タイプは必須です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredDateFieldText": "日付フィールドが必要です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredEntityText": "エンティティは必須です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredGeoFieldText": "地理フィールドは必須です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredIndexTitleText": "インデックスパターンが必要です。", - "xpack.triggersActionsUI.geoThreshold.error.requiredTrackingEventText": "追跡イベントは必須です。", - "xpack.triggersActionsUI.geoThreshold.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", - "xpack.triggersActionsUI.geoThreshold.geofieldLabel": "地理空間フィールド", - "xpack.triggersActionsUI.geoThreshold.indexLabel": "インデックス", - "xpack.triggersActionsUI.geoThreshold.indexPatternSelectLabel": "インデックスパターン", - "xpack.triggersActionsUI.geoThreshold.indexPatternSelectPlaceholder": "インデックスパターンを選択", - "xpack.triggersActionsUI.geoThreshold.name.trackingThreshold": "追跡しきい値", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisLinkTextDescription": "インデックスパターンを作成します", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisPrefixDescription": "次のことが必要です ", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisSuffixDescription": " 地理空間フィールドを含む", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.getStartedLinkText": "サンプルデータセットで始めましょう。", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.hintDescription": "地理空間データセットがありませんか? ", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.messageTitle": "地理空間フィールドを含むインデックスパターンが見つかりませんでした", - "xpack.triggersActionsUI.geoThreshold.selectBoundaryIndex": "境界を選択:", - "xpack.triggersActionsUI.geoThreshold.selectEntity": "エンティティを選択", - "xpack.triggersActionsUI.geoThreshold.selectGeoLabel": "ジオフィールドを選択", - "xpack.triggersActionsUI.geoThreshold.selectIndex": "条件を定義してください", - "xpack.triggersActionsUI.geoThreshold.selectLabel": "ジオフィールドを選択", - "xpack.triggersActionsUI.geoThreshold.selectOffset": "オフセットを選択(任意)", - "xpack.triggersActionsUI.geoThreshold.selectTimeLabel": "時刻フィールドを選択", - "xpack.triggersActionsUI.geoThreshold.timeFieldLabel": "時間フィールド", - "xpack.triggersActionsUI.geoThreshold.topHitsSplitFieldSelectPlaceholder": "エンティティフィールドを選択", - "xpack.triggersActionsUI.geoThreshold.whenEntityLabel": "エンティティ", + "xpack.stackAlerts.geoThreshold.boundaryNameSelect": "境界名を選択", + "xpack.stackAlerts.geoThreshold.boundaryNameSelectLabel": "人間が読み取れる境界名(任意)", + "xpack.stackAlerts.geoThreshold.delayOffset": "遅延評価オフセット", + "xpack.stackAlerts.geoThreshold.delayOffsetTooltip": "遅延サイクルでアラートを評価し、データレイテンシに合わせて調整します", + "xpack.stackAlerts.geoThreshold.entityByLabel": "グループ基準", + "xpack.stackAlerts.geoThreshold.entityIndexLabel": "インデックス", + "xpack.stackAlerts.geoThreshold.error.requiredBoundaryGeoFieldText": "境界地理フィールドは必須です。", + "xpack.stackAlerts.geoThreshold.error.requiredBoundaryIndexTitleText": "境界インデックスパターンタイトルは必須です。", + "xpack.stackAlerts.geoThreshold.error.requiredBoundaryTypeText": "境界タイプは必須です。", + "xpack.stackAlerts.geoThreshold.error.requiredDateFieldText": "日付フィールドが必要です。", + "xpack.stackAlerts.geoThreshold.error.requiredEntityText": "エンティティは必須です。", + "xpack.stackAlerts.geoThreshold.error.requiredGeoFieldText": "地理フィールドは必須です。", + "xpack.stackAlerts.geoThreshold.error.requiredIndexTitleText": "インデックスパターンが必要です。", + "xpack.stackAlerts.geoThreshold.error.requiredTrackingEventText": "追跡イベントは必須です。", + "xpack.stackAlerts.geoThreshold.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", + "xpack.stackAlerts.geoThreshold.geofieldLabel": "地理空間フィールド", + "xpack.stackAlerts.geoThreshold.indexLabel": "インデックス", + "xpack.stackAlerts.geoThreshold.indexPatternSelectLabel": "インデックスパターン", + "xpack.stackAlerts.geoThreshold.indexPatternSelectPlaceholder": "インデックスパターンを選択", + "xpack.stackAlerts.geoThreshold.name.trackingThreshold": "追跡しきい値", + "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisLinkTextDescription": "インデックスパターンを作成します", + "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisPrefixDescription": "次のことが必要です ", + "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisSuffixDescription": " 地理空間フィールドを含む", + "xpack.stackAlerts.geoThreshold.noIndexPattern.getStartedLinkText": "サンプルデータセットで始めましょう。", + "xpack.stackAlerts.geoThreshold.noIndexPattern.hintDescription": "地理空間データセットがありませんか? ", + "xpack.stackAlerts.geoThreshold.noIndexPattern.messageTitle": "地理空間フィールドを含むインデックスパターンが見つかりませんでした", + "xpack.stackAlerts.geoThreshold.selectBoundaryIndex": "境界を選択:", + "xpack.stackAlerts.geoThreshold.selectEntity": "エンティティを選択", + "xpack.stackAlerts.geoThreshold.selectGeoLabel": "ジオフィールドを選択", + "xpack.stackAlerts.geoThreshold.selectIndex": "条件を定義してください", + "xpack.stackAlerts.geoThreshold.selectLabel": "ジオフィールドを選択", + "xpack.stackAlerts.geoThreshold.selectOffset": "オフセットを選択(任意)", + "xpack.stackAlerts.geoThreshold.selectTimeLabel": "時刻フィールドを選択", + "xpack.stackAlerts.geoThreshold.timeFieldLabel": "時間フィールド", + "xpack.stackAlerts.geoThreshold.topHitsSplitFieldSelectPlaceholder": "エンティティフィールドを選択", + "xpack.stackAlerts.geoThreshold.whenEntityLabel": "エンティティ", "xpack.triggersActionsUI.home.alertsTabTitle": "アラート", "xpack.triggersActionsUI.home.appTitle": "アラートとアクション", "xpack.triggersActionsUI.home.breadcrumbTitle": "アラートとアクション", @@ -20156,15 +20156,15 @@ "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredHeaderValueText": "値が必要です。", "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText": "メソッドが必要です", "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredPasswordText": "パスワードが必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.greaterThenThreshold0Text": "しきい値 1 はしきい値 0 よりも大きい値にしてください。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredAggFieldText": "集約フィールドが必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredIndexText": "インデックスが必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredTermSizedText": "用語サイズが必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold0Text": "しきい値 0 が必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold1Text": "しきい値 1 が必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText": "時間フィールドが必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText": "時間ウィンドウサイズが必要です。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText": "用語フィールドが必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.greaterThenThreshold0Text": "しきい値 1 はしきい値 0 よりも大きい値にしてください。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredAggFieldText": "集約フィールドが必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredIndexText": "インデックスが必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTermSizedText": "用語サイズが必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredThreshold0Text": "しきい値 0 が必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredThreshold1Text": "しきい値 1 が必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTimeFieldText": "時間フィールドが必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTimeWindowSizeText": "時間ウィンドウサイズが必要です。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTermFieldText": "用語フィールドが必要です。", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutTitle": "{actionTypeName} コネクタ", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "コネクターを選択", "xpack.triggersActionsUI.sections.addConnectorForm.updateErrorNotificationText": "コネクターを作成できません。", @@ -20173,27 +20173,27 @@ "xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle": "{actionTypeName} コネクター", "xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText": "「{connectorName}」を作成しました", - "xpack.triggersActionsUI.sections.alertAdd.conditionPrompt": "条件を定義してください", - "xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle": "アラートビジュアライゼーションを読み込めません", + "xpack.stackAlerts.threshold.ui.conditionPrompt": "条件を定義してください", + "xpack.stackAlerts.threshold.ui.visualization.errorLoadingAlertVisualizationTitle": "アラートビジュアライゼーションを読み込めません", "xpack.triggersActionsUI.sections.alertAdd.flyoutTitle": "アラートの作成", - "xpack.triggersActionsUI.sections.alertAdd.geoThreshold.closePopoverLabel": "閉じる", - "xpack.triggersActionsUI.sections.alertAdd.loadingAlertVisualizationDescription": "アラートビジュアライゼーションを読み込み中...", + "xpack.stackAlerts.geoThreshold.ui.expressionPopover.closePopoverLabel": "閉じる", + "xpack.stackAlerts.threshold.ui.visualization.loadingAlertVisualizationDescription": "アラートビジュアライゼーションを読み込み中...", "xpack.triggersActionsUI.sections.alertAdd.operationName": "作成", - "xpack.triggersActionsUI.sections.alertAdd.previewAlertVisualizationDescription": "プレビューを生成するための式を完成します。", + "xpack.stackAlerts.threshold.ui.previewAlertVisualizationDescription": "プレビューを生成するための式を完成します。", "xpack.triggersActionsUI.sections.alertAdd.saveErrorNotificationText": "アラートを作成できません。", "xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText": "「{alertName}」 を保存しました", - "xpack.triggersActionsUI.sections.alertAdd.selectIndex": "インデックスを選択してください", - "xpack.triggersActionsUI.sections.alertAdd.threshold.closeIndexPopoverLabel": "閉じる", - "xpack.triggersActionsUI.sections.alertAdd.threshold.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", - "xpack.triggersActionsUI.sections.alertAdd.threshold.howToBroadenSearchQueryDescription": "* で検索クエリの範囲を広げます。", - "xpack.triggersActionsUI.sections.alertAdd.threshold.indexButtonLabel": "インデックス", - "xpack.triggersActionsUI.sections.alertAdd.threshold.indexLabel": "インデックス", - "xpack.triggersActionsUI.sections.alertAdd.threshold.indicesToQueryLabel": "クエリを実行するインデックス", - "xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldLabel": "時間フィールド", - "xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldOptionLabel": "フィールドを選択", - "xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.dataDoesNotExistTextMessage": "時間範囲とフィルターが正しいことを確認してください。", - "xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.noDataTitle": "このクエリに一致するデータはありません", - "xpack.triggersActionsUI.sections.alertAdd.unableToLoadVisualizationMessage": "ビジュアライゼーションを読み込めません", + "xpack.stackAlerts.threshold.ui.selectIndex": "インデックスを選択してください", + "xpack.stackAlerts.threshold.ui.alertParams.closeIndexPopoverLabel": "閉じる", + "xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "下の表現のエラーを修正してください。", + "xpack.stackAlerts.threshold.ui.alertParams.howToBroadenSearchQueryDescription": "* で検索クエリの範囲を広げます。", + "xpack.stackAlerts.threshold.ui.alertParams.indexButtonLabel": "インデックス", + "xpack.stackAlerts.threshold.ui.alertParams.indexLabel": "インデックス", + "xpack.stackAlerts.threshold.ui.alertParams.indicesToQueryLabel": "クエリを実行するインデックス", + "xpack.stackAlerts.threshold.ui.alertParams.timeFieldLabel": "時間フィールド", + "xpack.triggersActionsUI.sections.alertAdd.indexControls.timeFieldOptionLabel": "フィールドを選択", + "xpack.stackAlerts.threshold.ui.visualization.thresholdPreviewChart.dataDoesNotExistTextMessage": "時間範囲とフィルターが正しいことを確認してください。", + "xpack.stackAlerts.threshold.ui.visualization.thresholdPreviewChart.noDataTitle": "このクエリに一致するデータはありません", + "xpack.stackAlerts.threshold.ui.visualization.unableToLoadVisualizationMessage": "ビジュアライゼーションを読み込めません", "xpack.triggersActionsUI.sections.alertDetails.alertInstances.disabledAlert": "このアラートは無効になっていて再表示できません。[↑ を無効にする]を切り替えてアクティブにします。", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.columns.duration": "期間", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.columns.instance": "インスタンス", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8ae964d9ee7d0..48654a5ec5ff4 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19544,24 +19544,24 @@ "xpack.stackAlerts.indexThreshold.actionVariableContextThresholdLabel": "用作阈值的值数组;“between”和“notBetween”需要两个值,其他则需要一个值。", "xpack.stackAlerts.indexThreshold.actionVariableContextTitleLabel": "告警的预构造标题。", "xpack.stackAlerts.indexThreshold.actionVariableContextValueLabel": "超过阈值的值。", - "xpack.stackAlerts.indexThreshold.aggTypeRequiredErrorMessage": "[aggField]:当 [aggType] 为“{aggType}”时必须有值", + "xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage": "[aggField]:当 [aggType] 为“{aggType}”时必须有值", "xpack.stackAlerts.indexThreshold.alertTypeContextMessageDescription": "告警 {name} 组 {group} 值 {value} 在 {window} 于 {date}超过了阈值 {function}", "xpack.stackAlerts.indexThreshold.alertTypeContextSubjectTitle": "告警 {name} 组 {group} 超过了阈值", "xpack.stackAlerts.indexThreshold.alertTypeTitle": "索引阈值", - "xpack.stackAlerts.indexThreshold.dateStartGTdateEndErrorMessage": "[dateStart]:晚于 [dateEnd]", - "xpack.stackAlerts.indexThreshold.formattedFieldErrorMessage": "{fieldName} 的 {formatName} 格式无效:“{fieldValue}”", - "xpack.stackAlerts.indexThreshold.intervalRequiredErrorMessage": "[interval]:如果 [dateStart] 不等于 [dateEnd],则必须指定", - "xpack.stackAlerts.indexThreshold.invalidAggTypeErrorMessage": "aggType 无效:“{aggType}”", + "xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage": "[dateStart]:晚于 [dateEnd]", + "xpack.triggersActionsUI.data.coreQueryParams.formattedFieldErrorMessage": "{fieldName} 的 {formatName} 格式无效:“{fieldValue}”", + "xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage": "[interval]:如果 [dateStart] 不等于 [dateEnd],则必须指定", + "xpack.triggersActionsUI.data.coreQueryParams.invalidAggTypeErrorMessage": "aggType 无效:“{aggType}”", "xpack.stackAlerts.indexThreshold.invalidComparatorErrorMessage": "指定的 thresholdComparator 无效:{comparator}", - "xpack.stackAlerts.indexThreshold.invalidDateErrorMessage": "日期 {date} 无效", - "xpack.stackAlerts.indexThreshold.invalidDurationErrorMessage": "持续时间无效:“{duration}”", - "xpack.stackAlerts.indexThreshold.invalidGroupByErrorMessage": "groupBy 无效:“{groupBy}”", - "xpack.stackAlerts.indexThreshold.invalidTermSizeMaximumErrorMessage": "[termSize]:必须小于或等于 {maxGroups}", + "xpack.triggersActionsUI.data.coreQueryParams.invalidDateErrorMessage": "日期 {date} 无效", + "xpack.triggersActionsUI.data.coreQueryParams.invalidDurationErrorMessage": "持续时间无效:“{duration}”", + "xpack.triggersActionsUI.data.coreQueryParams.invalidGroupByErrorMessage": "groupBy 无效:“{groupBy}”", + "xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage": "[termSize]:必须小于或等于 {maxGroups}", "xpack.stackAlerts.indexThreshold.invalidThreshold2ErrorMessage": "[threshold]:对于“{thresholdComparator}”比较运算符,必须包含两个元素", - "xpack.stackAlerts.indexThreshold.invalidTimeWindowUnitsErrorMessage": "timeWindowUnit 无效:“{timeWindowUnit}”", - "xpack.stackAlerts.indexThreshold.maxIntervalsErrorMessage": "时间间隔 {intervals} 的计算数目大于最大值 {maxIntervals}", - "xpack.stackAlerts.indexThreshold.termFieldRequiredErrorMessage": "[termField]:[groupBy] 为 top 时,termField 为必需", - "xpack.stackAlerts.indexThreshold.termSizeRequiredErrorMessage": "[termSize]:[groupBy] 为 top 时,termSize 为必需", + "xpack.triggersActionsUI.data.coreQueryParams.invalidTimeWindowUnitsErrorMessage": "timeWindowUnit 无效:“{timeWindowUnit}”", + "xpack.triggersActionsUI.data.coreQueryParams.maxIntervalsErrorMessage": "时间间隔 {intervals} 的计算数目大于最大值 {maxIntervals}", + "xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage": "[termField]:[groupBy] 为 top 时,termField 为必需", + "xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage": "[termSize]:[groupBy] 为 top 时,termSize 为必需", "xpack.transform.actionDeleteTransform.bulkDeleteDestinationIndexTitle": "删除目标索引", "xpack.transform.actionDeleteTransform.bulkDeleteDestIndexPatternTitle": "删除目标索引模式", "xpack.transform.actionDeleteTransform.deleteDestinationIndexTitle": "删除目标索引 {destinationIndex}", @@ -20097,42 +20097,42 @@ "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.cancelButtonLabel": "取消", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.deleteButtonLabel": "删除{numIdsToDelete, plural, one {{singleTitle}} other { # 个{multipleTitle}}} ", "xpack.triggersActionsUI.deleteSelectedIdsConfirmModal.descriptionText": "无法恢复{numIdsToDelete, plural, one {删除的{singleTitle}} other {删除的{multipleTitle}}}。", - "xpack.triggersActionsUI.geoThreshold.boundaryNameSelect": "选择边界名称", - "xpack.triggersActionsUI.geoThreshold.boundaryNameSelectLabel": "可人工读取的边界名称(可选)", - "xpack.triggersActionsUI.geoThreshold.delayOffset": "已延迟的评估偏移", - "xpack.triggersActionsUI.geoThreshold.delayOffsetTooltip": "评估延迟周期内的告警,以针对数据延迟进行调整", - "xpack.triggersActionsUI.geoThreshold.entityByLabel": "方式", - "xpack.triggersActionsUI.geoThreshold.entityIndexLabel": "索引", - "xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryGeoFieldText": "“边界地理”字段必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryIndexTitleText": "“边界索引模式标题”必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredBoundaryTypeText": "“边界类型”必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredDateFieldText": "“日期”字段必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredEntityText": "“实体”必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredGeoFieldText": "“地理”字段必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredIndexTitleText": "“索引模式”必填。", - "xpack.triggersActionsUI.geoThreshold.error.requiredTrackingEventText": "“跟踪事件”必填。", - "xpack.triggersActionsUI.geoThreshold.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", - "xpack.triggersActionsUI.geoThreshold.geofieldLabel": "地理空间字段", - "xpack.triggersActionsUI.geoThreshold.indexLabel": "索引", - "xpack.triggersActionsUI.geoThreshold.indexPatternSelectLabel": "索引模式", - "xpack.triggersActionsUI.geoThreshold.indexPatternSelectPlaceholder": "选择索引模式", - "xpack.triggersActionsUI.geoThreshold.name.trackingThreshold": "跟踪阈值", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisLinkTextDescription": "创建索引模式", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisPrefixDescription": "您将需要 ", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisSuffixDescription": " (包含地理空间字段)。", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.getStartedLinkText": "开始使用一些样例数据集。", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.hintDescription": "没有任何地理空间数据集? ", - "xpack.triggersActionsUI.geoThreshold.noIndexPattern.messageTitle": "找不到任何具有地理空间字段的索引模式", - "xpack.triggersActionsUI.geoThreshold.selectBoundaryIndex": "选择边界:", - "xpack.triggersActionsUI.geoThreshold.selectEntity": "选择实体", - "xpack.triggersActionsUI.geoThreshold.selectGeoLabel": "选择地理字段", - "xpack.triggersActionsUI.geoThreshold.selectIndex": "定义条件", - "xpack.triggersActionsUI.geoThreshold.selectLabel": "选择地理字段", - "xpack.triggersActionsUI.geoThreshold.selectOffset": "选择偏移(可选)", - "xpack.triggersActionsUI.geoThreshold.selectTimeLabel": "选择时间字段", - "xpack.triggersActionsUI.geoThreshold.timeFieldLabel": "时间字段", - "xpack.triggersActionsUI.geoThreshold.topHitsSplitFieldSelectPlaceholder": "选择实体字段", - "xpack.triggersActionsUI.geoThreshold.whenEntityLabel": "当实体", + "xpack.stackAlerts.geoThreshold.boundaryNameSelect": "选择边界名称", + "xpack.stackAlerts.geoThreshold.boundaryNameSelectLabel": "可人工读取的边界名称(可选)", + "xpack.stackAlerts.geoThreshold.delayOffset": "已延迟的评估偏移", + "xpack.stackAlerts.geoThreshold.delayOffsetTooltip": "评估延迟周期内的告警,以针对数据延迟进行调整", + "xpack.stackAlerts.geoThreshold.entityByLabel": "方式", + "xpack.stackAlerts.geoThreshold.entityIndexLabel": "索引", + "xpack.stackAlerts.geoThreshold.error.requiredBoundaryGeoFieldText": "“边界地理”字段必填。", + "xpack.stackAlerts.geoThreshold.error.requiredBoundaryIndexTitleText": "“边界索引模式标题”必填。", + "xpack.stackAlerts.geoThreshold.error.requiredBoundaryTypeText": "“边界类型”必填。", + "xpack.stackAlerts.geoThreshold.error.requiredDateFieldText": "“日期”字段必填。", + "xpack.stackAlerts.geoThreshold.error.requiredEntityText": "“实体”必填。", + "xpack.stackAlerts.geoThreshold.error.requiredGeoFieldText": "“地理”字段必填。", + "xpack.stackAlerts.geoThreshold.error.requiredIndexTitleText": "“索引模式”必填。", + "xpack.stackAlerts.geoThreshold.error.requiredTrackingEventText": "“跟踪事件”必填。", + "xpack.stackAlerts.geoThreshold.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", + "xpack.stackAlerts.geoThreshold.geofieldLabel": "地理空间字段", + "xpack.stackAlerts.geoThreshold.indexLabel": "索引", + "xpack.stackAlerts.geoThreshold.indexPatternSelectLabel": "索引模式", + "xpack.stackAlerts.geoThreshold.indexPatternSelectPlaceholder": "选择索引模式", + "xpack.stackAlerts.geoThreshold.name.trackingThreshold": "跟踪阈值", + "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisLinkTextDescription": "创建索引模式", + "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisPrefixDescription": "您将需要 ", + "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisSuffixDescription": " (包含地理空间字段)。", + "xpack.stackAlerts.geoThreshold.noIndexPattern.getStartedLinkText": "开始使用一些样例数据集。", + "xpack.stackAlerts.geoThreshold.noIndexPattern.hintDescription": "没有任何地理空间数据集? ", + "xpack.stackAlerts.geoThreshold.noIndexPattern.messageTitle": "找不到任何具有地理空间字段的索引模式", + "xpack.stackAlerts.geoThreshold.selectBoundaryIndex": "选择边界:", + "xpack.stackAlerts.geoThreshold.selectEntity": "选择实体", + "xpack.stackAlerts.geoThreshold.selectGeoLabel": "选择地理字段", + "xpack.stackAlerts.geoThreshold.selectIndex": "定义条件", + "xpack.stackAlerts.geoThreshold.selectLabel": "选择地理字段", + "xpack.stackAlerts.geoThreshold.selectOffset": "选择偏移(可选)", + "xpack.stackAlerts.geoThreshold.selectTimeLabel": "选择时间字段", + "xpack.stackAlerts.geoThreshold.timeFieldLabel": "时间字段", + "xpack.stackAlerts.geoThreshold.topHitsSplitFieldSelectPlaceholder": "选择实体字段", + "xpack.stackAlerts.geoThreshold.whenEntityLabel": "当实体", "xpack.triggersActionsUI.home.alertsTabTitle": "告警", "xpack.triggersActionsUI.home.appTitle": "告警和操作", "xpack.triggersActionsUI.home.breadcrumbTitle": "告警和操作", @@ -20176,15 +20176,15 @@ "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredHeaderValueText": "“值”必填。", "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText": "“方法”必填", "xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredPasswordText": "“密码”必填。", - "xpack.triggersActionsUI.sections.addAlert.error.greaterThenThreshold0Text": "阈值 1 应 > 阈值 0。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredAggFieldText": "聚合字段必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredIndexText": "“索引”必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredTermSizedText": "“词大小”必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold0Text": "阈值 0 必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold1Text": "阈值 1 必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText": "时间字段必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText": "“时间窗大小”必填。", - "xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText": "词字段必填。", + "xpack.stackAlerts.threshold.ui.validation.error.greaterThenThreshold0Text": "阈值 1 应 > 阈值 0。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredAggFieldText": "聚合字段必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredIndexText": "“索引”必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTermSizedText": "“词大小”必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredThreshold0Text": "阈值 0 必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredThreshold1Text": "阈值 1 必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTimeFieldText": "时间字段必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTimeWindowSizeText": "“时间窗大小”必填。", + "xpack.stackAlerts.threshold.ui.validation.error.requiredTermFieldText": "词字段必填。", "xpack.triggersActionsUI.sections.addConnectorForm.flyoutTitle": "{actionTypeName} 连接器", "xpack.triggersActionsUI.sections.addConnectorForm.selectConnectorFlyoutTitle": "选择连接器", "xpack.triggersActionsUI.sections.addConnectorForm.updateErrorNotificationText": "无法创建连接器。", @@ -20193,27 +20193,27 @@ "xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle": "{actionTypeName} 连接器", "xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel": "保存", "xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText": "已创建“{connectorName}”", - "xpack.triggersActionsUI.sections.alertAdd.conditionPrompt": "定义条件", - "xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle": "无法加载告警可视化", + "xpack.stackAlerts.threshold.ui.conditionPrompt": "定义条件", + "xpack.stackAlerts.threshold.ui.visualization.errorLoadingAlertVisualizationTitle": "无法加载告警可视化", "xpack.triggersActionsUI.sections.alertAdd.flyoutTitle": "创建告警", - "xpack.triggersActionsUI.sections.alertAdd.geoThreshold.closePopoverLabel": "关闭", - "xpack.triggersActionsUI.sections.alertAdd.loadingAlertVisualizationDescription": "正在加载告警可视化……", + "xpack.stackAlerts.geoThreshold.ui.expressionPopover.closePopoverLabel": "关闭", + "xpack.stackAlerts.threshold.ui.visualization.loadingAlertVisualizationDescription": "正在加载告警可视化……", "xpack.triggersActionsUI.sections.alertAdd.operationName": "创建", - "xpack.triggersActionsUI.sections.alertAdd.previewAlertVisualizationDescription": "完成表达式以生成预览。", + "xpack.stackAlerts.threshold.ui.previewAlertVisualizationDescription": "完成表达式以生成预览。", "xpack.triggersActionsUI.sections.alertAdd.saveErrorNotificationText": "无法创建告警。", "xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText": "已保存“{alertName}”", - "xpack.triggersActionsUI.sections.alertAdd.selectIndex": "选择索引", - "xpack.triggersActionsUI.sections.alertAdd.threshold.closeIndexPopoverLabel": "关闭", - "xpack.triggersActionsUI.sections.alertAdd.threshold.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", - "xpack.triggersActionsUI.sections.alertAdd.threshold.howToBroadenSearchQueryDescription": "使用 * 可扩大您的查询范围。", - "xpack.triggersActionsUI.sections.alertAdd.threshold.indexButtonLabel": "索引", - "xpack.triggersActionsUI.sections.alertAdd.threshold.indexLabel": "索引", - "xpack.triggersActionsUI.sections.alertAdd.threshold.indicesToQueryLabel": "要查询的索引", - "xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldLabel": "时间字段", - "xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldOptionLabel": "选择字段", - "xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.dataDoesNotExistTextMessage": "确认您的时间范围和筛选正确。", - "xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.noDataTitle": "没有数据匹配此查询", - "xpack.triggersActionsUI.sections.alertAdd.unableToLoadVisualizationMessage": "无法加载可视化", + "xpack.stackAlerts.threshold.ui.selectIndex": "选择索引", + "xpack.stackAlerts.threshold.ui.alertParams.closeIndexPopoverLabel": "关闭", + "xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage": "表达式包含错误。", + "xpack.stackAlerts.threshold.ui.alertParams.howToBroadenSearchQueryDescription": "使用 * 可扩大您的查询范围。", + "xpack.stackAlerts.threshold.ui.alertParams.indexButtonLabel": "索引", + "xpack.stackAlerts.threshold.ui.alertParams.indexLabel": "索引", + "xpack.stackAlerts.threshold.ui.alertParams.indicesToQueryLabel": "要查询的索引", + "xpack.stackAlerts.threshold.ui.alertParams.timeFieldLabel": "时间字段", + "xpack.triggersActionsUI.sections.alertAdd.indexControls.timeFieldOptionLabel": "选择字段", + "xpack.stackAlerts.threshold.ui.visualization.thresholdPreviewChart.dataDoesNotExistTextMessage": "确认您的时间范围和筛选正确。", + "xpack.stackAlerts.threshold.ui.visualization.thresholdPreviewChart.noDataTitle": "没有数据匹配此查询", + "xpack.stackAlerts.threshold.ui.visualization.unableToLoadVisualizationMessage": "无法加载可视化", "xpack.triggersActionsUI.sections.alertDetails.alertInstances.disabledAlert": "此告警已禁用,无法显示。切换禁用 ↑ 以激活。", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.columns.duration": "持续时间", "xpack.triggersActionsUI.sections.alertDetails.alertInstancesList.columns.instance": "实例", diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index 32e157255c0cc..ef81065608ad4 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -220,7 +220,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent .... diff --git a/x-pack/plugins/stack_alerts/common/alert_types/index_threshold/index.ts b/x-pack/plugins/triggers_actions_ui/common/data/index.ts similarity index 100% rename from x-pack/plugins/stack_alerts/common/alert_types/index_threshold/index.ts rename to x-pack/plugins/triggers_actions_ui/common/data/index.ts diff --git a/x-pack/plugins/triggers_actions_ui/common/index.ts b/x-pack/plugins/triggers_actions_ui/common/index.ts new file mode 100644 index 0000000000000..5775cc2454a7e --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './data'; diff --git a/x-pack/plugins/triggers_actions_ui/kibana.json b/x-pack/plugins/triggers_actions_ui/kibana.json index 72e1f0be5f7f4..9d79ab9232bf3 100644 --- a/x-pack/plugins/triggers_actions_ui/kibana.json +++ b/x-pack/plugins/triggers_actions_ui/kibana.json @@ -3,8 +3,8 @@ "version": "kibana", "server": true, "ui": true, - "optionalPlugins": ["alerts", "stackAlerts", "features", "home"], - "requiredPlugins": ["management", "charts", "data", "kibanaReact"], + "optionalPlugins": ["alerts", "features", "home"], + "requiredPlugins": ["management", "charts", "data"], "configPath": ["xpack", "trigger_actions_ui"], "extraPublicDirs": ["public/common", "public/common/constants"], "requiredBundles": ["home", "alerts", "esUiShared"] diff --git a/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx b/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx index 80f9ac532d1c9..bb46fd02a98a9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/boot.tsx @@ -9,7 +9,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { SavedObjectsClientContract } from 'src/core/public'; import { App, AppDeps } from './app'; -import { setSavedObjectsClient } from '../common/lib/index_threshold_api'; +import { setSavedObjectsClient } from '../common/lib/data_apis'; interface BootDeps extends AppDeps { element: HTMLElement; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/index.ts index 32b323334654e..86c33a373753f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/index.ts @@ -5,6 +5,10 @@ */ export * from './expression_items'; +export * from './constants'; +export * from './index_controls'; +export * from './lib'; +export * from './types'; export { connectorConfiguration as ServiceNowConnectorConfiguration } from '../application/components/builtin_action_types/servicenow/config'; export { connectorConfiguration as JiraConnectorConfiguration } from '../application/components/builtin_action_types/jira/config'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/index_controls/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/index_controls/index.ts index da332aa326ccf..8d10e531930cc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/index_controls/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/index_controls/index.ts @@ -9,10 +9,10 @@ import { HttpSetup } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { loadIndexPatterns, - getMatchingIndicesForThresholdAlertType, - getThresholdAlertTypeFields, + getMatchingIndices, + getESIndexFields, getSavedObjectsClient, -} from '../lib/index_threshold_api'; +} from '../lib/data_apis'; export interface IOption { label: string; @@ -39,7 +39,7 @@ export const getIndexOptions = async ( return options; } - const matchingIndices = (await getMatchingIndicesForThresholdAlertType({ + const matchingIndices = (await getMatchingIndices({ pattern, http, })) as string[]; @@ -85,12 +85,15 @@ export const getIndexOptions = async ( }; export const getFields = async (http: HttpSetup, indexes: string[]) => { - return await getThresholdAlertTypeFields({ indexes, http }); + return await getESIndexFields({ indexes, http }); }; export const firstFieldOption = { - text: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldOptionLabel', { - defaultMessage: 'Select a field', - }), + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.indexControls.timeFieldOptionLabel', + { + defaultMessage: 'Select a field', + } + ), value: '', }; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts new file mode 100644 index 0000000000000..573d306ae5550 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/data_apis.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { HttpSetup } from 'kibana/public'; + +const DATA_API_ROOT = '/api/triggers_actions_ui/data'; + +export async function getMatchingIndices({ + pattern, + http, +}: { + pattern: string; + http: HttpSetup; +}): Promise> { + if (!pattern.startsWith('*')) { + pattern = `*${pattern}`; + } + if (!pattern.endsWith('*')) { + pattern = `${pattern}*`; + } + const { indices } = await http.post(`${DATA_API_ROOT}/_indices`, { + body: JSON.stringify({ pattern }), + }); + return indices; +} + +export async function getESIndexFields({ + indexes, + http, +}: { + indexes: string[]; + http: HttpSetup; +}): Promise< + Array<{ + name: string; + type: string; + normalizedType: string; + searchable: boolean; + aggregatable: boolean; + }> +> { + const { fields } = await http.post(`${DATA_API_ROOT}/_fields`, { + body: JSON.stringify({ indexPatterns: indexes }), + }); + return fields; +} + +let savedObjectsClient: any; + +export const setSavedObjectsClient = (aSavedObjectsClient: any) => { + savedObjectsClient = aSavedObjectsClient; +}; + +export const getSavedObjectsClient = () => { + return savedObjectsClient; +}; + +export const loadIndexPatterns = async () => { + const { savedObjects } = await getSavedObjectsClient().find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000, + }); + return savedObjects; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts new file mode 100644 index 0000000000000..7671e239f8fff --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/common/lib/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { getTimeFieldOptions, getTimeOptions } from './get_time_options'; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/lib/index_threshold_api.ts b/x-pack/plugins/triggers_actions_ui/public/common/lib/index_threshold_api.ts deleted file mode 100644 index 11d273fcb7a42..0000000000000 --- a/x-pack/plugins/triggers_actions_ui/public/common/lib/index_threshold_api.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { HttpSetup } from 'kibana/public'; -import { TimeSeriesResult } from '../../../../stack_alerts/common/alert_types/index_threshold'; - -const INDEX_THRESHOLD_API_ROOT = '/api/stack_alerts/index_threshold'; - -export async function getMatchingIndicesForThresholdAlertType({ - pattern, - http, -}: { - pattern: string; - http: HttpSetup; -}): Promise> { - if (!pattern.startsWith('*')) { - pattern = `*${pattern}`; - } - if (!pattern.endsWith('*')) { - pattern = `${pattern}*`; - } - const { indices } = await http.post(`${INDEX_THRESHOLD_API_ROOT}/_indices`, { - body: JSON.stringify({ pattern }), - }); - return indices; -} - -export async function getThresholdAlertTypeFields({ - indexes, - http, -}: { - indexes: string[]; - http: HttpSetup; -}): Promise> { - const { fields } = await http.post(`${INDEX_THRESHOLD_API_ROOT}/_fields`, { - body: JSON.stringify({ indexPatterns: indexes }), - }); - return fields; -} - -let savedObjectsClient: any; - -export const setSavedObjectsClient = (aSavedObjectsClient: any) => { - savedObjectsClient = aSavedObjectsClient; -}; - -export const getSavedObjectsClient = () => { - return savedObjectsClient; -}; - -export const loadIndexPatterns = async () => { - const { savedObjects } = await getSavedObjectsClient().find({ - type: 'index-pattern', - fields: ['title'], - perPage: 10000, - }); - return savedObjects; -}; - -interface GetThresholdAlertVisualizationDataParams { - model: any; - visualizeOptions: any; - http: HttpSetup; -} - -export async function getThresholdAlertVisualizationData({ - model, - visualizeOptions, - http, -}: GetThresholdAlertVisualizationDataParams): Promise { - const timeSeriesQueryParams = { - index: model.index, - timeField: model.timeField, - aggType: model.aggType, - aggField: model.aggField, - groupBy: model.groupBy, - termField: model.termField, - termSize: model.termSize, - timeWindowSize: model.timeWindowSize, - timeWindowUnit: model.timeWindowUnit, - dateStart: new Date(visualizeOptions.rangeFrom).toISOString(), - dateEnd: new Date(visualizeOptions.rangeTo).toISOString(), - interval: visualizeOptions.interval, - }; - - return await http.post(`${INDEX_THRESHOLD_API_ROOT}/_time_series_query`, { - body: JSON.stringify(timeSeriesQueryParams), - }); -} diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index a28b10683c28f..3794112e1d502 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/public'; import { Plugin } from './plugin'; export { AlertsContextProvider, AlertsContextValue } from './application/context/alerts_context'; @@ -22,15 +21,17 @@ export { ValidationResult, ActionVariable, ActionConnector, + IErrorObject, } from './types'; export { ConnectorAddFlyout, ConnectorEditFlyout, } from './application/sections/action_connector_form'; export { loadActionTypes } from './application/lib/action_connector_api'; +export * from './common'; -export function plugin(ctx: PluginInitializerContext) { - return new Plugin(ctx); +export function plugin() { + return new Plugin(); } export { Plugin }; diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts index 61cd7699c50c5..2d93d368ad8e5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts +++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts @@ -4,17 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - CoreSetup, - CoreStart, - Plugin as CorePlugin, - PluginInitializerContext, -} from 'src/core/public'; +import { CoreSetup, CoreStart, Plugin as CorePlugin } from 'src/core/public'; import { i18n } from '@kbn/i18n'; import { FeaturesPluginStart } from '../../features/public'; import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; -import { registerBuiltInAlertTypes } from './application/components/builtin_alert_types'; import { ActionTypeModel, AlertTypeModel } from './types'; import { TypeRegistry } from './application/type_registry'; import { @@ -29,10 +23,6 @@ import { ChartsPluginStart } from '../../../../src/plugins/charts/public'; import { PluginStartContract as AlertingStart } from '../../alerts/public'; import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; -export interface TriggersActionsUiConfigType { - enableGeoTrackingThresholdAlert: boolean; -} - export interface TriggersAndActionsUIPublicPluginSetup { actionTypeRegistry: TypeRegistry; alertTypeRegistry: TypeRegistry; @@ -66,14 +56,10 @@ export class Plugin > { private actionTypeRegistry: TypeRegistry; private alertTypeRegistry: TypeRegistry; - private initializerContext: PluginInitializerContext; - constructor(initializerContext: PluginInitializerContext) { + constructor() { this.actionTypeRegistry = new TypeRegistry(); - this.alertTypeRegistry = new TypeRegistry(); - - this.initializerContext = initializerContext; } public setup(core: CoreSetup, plugins: PluginsSetup): TriggersAndActionsUIPublicPluginSetup { @@ -142,11 +128,6 @@ export class Plugin actionTypeRegistry: this.actionTypeRegistry, }); - registerBuiltInAlertTypes({ - alertTypeRegistry: this.alertTypeRegistry, - triggerActionsUiConfig: this.initializerContext.config.get(), - }); - return { actionTypeRegistry: this.actionTypeRegistry, alertTypeRegistry: this.alertTypeRegistry, diff --git a/x-pack/plugins/triggers_actions_ui/server/data/README.md b/x-pack/plugins/triggers_actions_ui/server/data/README.md new file mode 100644 index 0000000000000..78577f0783008 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/server/data/README.md @@ -0,0 +1,228 @@ +# Data Apis + +The TriggersActionsUi plugin's Data Apis back the functionality needed by the Index Threshold Stack Alert. + +## http endpoints + +The following endpoints are provided for this alert type: + +- `POST /api/triggers_actions_ui/data/_indices` +- `POST /api/triggers_actions_ui/data/_fields` +- `POST /api/triggers_actions_ui/data/_time_series_query` + +### `POST .../_indices` + +This HTTP endpoint is provided for the alerting ui to list the available +"index names" for the user to select to use with the alert. This API also +returns aliases which match the supplied pattern. + +The request body is expected to be a JSON object in the following form, where the +`pattern` value may include comma-separated names and wildcards. + +```js +{ + pattern: "index-name-pattern" +} +``` + +The response body is a JSON object in the following form, where each element +of the `indices` array is the name of an index or alias. The number of elements +returned is limited, as this API is intended to be used to help narrow down +index names to use with the alert, and not support pagination, etc. + +```js +{ + indices: ["index-name-1", "alias-name-1", ...] +} +``` + +### `POST .../_fields` + +This HTTP endpoint is provided for the alerting ui to list the available +fields for the user to select to use with the alert. + +The request body is expected to be a JSON object in the following form, where the +`indexPatterns` array elements may include comma-separated names and wildcards. + +```js +{ + indexPatterns: ["index-pattern-1", "index-pattern-2"] +} +``` + +The response body is a JSON object in the following form, where each element +fields array is a field object. + +```js +{ + fields: [fieldObject1, fieldObject2, ...] +} +``` + +A field object is the following shape: + +```typescript +{ + name: string, // field name + type: string, // field type - eg 'keyword', 'date', 'long', etc + normalizedType: string, // for numeric types, this will be 'number' + aggregatable: true, // value from elasticsearch field capabilities + searchable: true, // value from elasticsearch field capabilities +} +``` + +### `POST .../_time_series_query` + +This HTTP endpoint is provided to return the values the alertType would calculate, +over a series of time. It is intended to be used in the alerting UI to +provide a "preview" of the alert during creation/editing based on recent data, +and could be used to show a "simulation" of the the alert over an arbitrary +range of time. + +The endpoint is `POST /api/triggers_actions_ui/data/_time_series_query`. +The request and response bodies are specifed in +[`lib/core_query_types.ts`][it-core-query] +and +[`lib/time_series_types.ts`][it-timeSeries-types]. +The request body is very similar to the alertType's parameters. + +### example + +Continuing with the example above, here's a query to get the values calculated +for the last 10 seconds. +This example uses [now-iso][] to generate iso date strings. + +```console +curl -k "https://elastic:changeme@localhost:5601/api/triggers_actions_ui/data/_time_series_query" \ + -H "kbn-xsrf: foo" -H "content-type: application/json" -d "{ + \"index\": \"es-hb-sim\", + \"timeField\": \"@timestamp\", + \"aggType\": \"avg\", + \"aggField\": \"summary.up\", + \"groupBy\": \"top\", + \"termSize\": 100, + \"termField\": \"monitor.name.keyword\", + \"interval\": \"1s\", + \"dateStart\": \"`now-iso -10s`\", + \"dateEnd\": \"`now-iso`\", + \"timeWindowSize\": 5, + \"timeWindowUnit\": \"s\" +}" +``` + +``` +{ + "results": [ + { + "group": "host-A", + "metrics": [ + [ "2020-02-26T15:10:40.000Z", 0 ], + [ "2020-02-26T15:10:41.000Z", 0 ], + [ "2020-02-26T15:10:42.000Z", 0 ], + [ "2020-02-26T15:10:43.000Z", 0 ], + [ "2020-02-26T15:10:44.000Z", 0 ], + [ "2020-02-26T15:10:45.000Z", 0 ], + [ "2020-02-26T15:10:46.000Z", 0 ], + [ "2020-02-26T15:10:47.000Z", 0 ], + [ "2020-02-26T15:10:48.000Z", 0 ], + [ "2020-02-26T15:10:49.000Z", 0 ], + [ "2020-02-26T15:10:50.000Z", 0 ] + ] + } + ] +} +``` + +To get the current value of the calculated metric, you can leave off the date: + +``` +curl -k "https://elastic:changeme@localhost:5601/api/triggers_actions_ui/data/_time_series_query" \ + -H "kbn-xsrf: foo" -H "content-type: application/json" -d '{ + "index": "es-hb-sim", + "timeField": "@timestamp", + "aggType": "avg", + "aggField": "summary.up", + "groupBy": "top", + "termField": "monitor.name.keyword", + "termSize": 100, + "interval": "1s", + "timeWindowSize": 5, + "timeWindowUnit": "s" +}' +``` + +``` +{ + "results": [ + { + "group": "host-A", + "metrics": [ + [ "2020-02-26T15:23:36.635Z", 0 ] + ] + } + ] +} +``` + +[it-timeSeries-types]: lib/time_series_types.ts + +## service functions + +A single service function is available that provides the functionality +of the http endpoint `POST /api/triggers_actions_ui/data/_time_series_query`, +but as an API for Kibana plugins. The function is available as +`triggersActionsUi.data.timeSeriesQuery()` on the plugin's _Start_ contract + +The parameters and return value for the function are the same as for the HTTP +request, though some additional parameters are required (logger, callCluster, +etc). + +## notes on the timeSeriesQuery API / http endpoint + +This API provides additional parameters beyond what the alertType itself uses: + +- `dateStart` +- `dateEnd` +- `interval` + +The `dateStart` and `dateEnd` parameters are ISO date strings. + +The `interval` parameter is intended to model the `interval` the alert is +currently using, and uses the same `1s`, `2m`, `3h`, etc format. Over the +supplied date range, a time-series data point will be calculated every +`interval` duration. + +So the number of time-series points in the output of the API should be: + +``` +( dateStart - dateEnd ) / interval +``` + +Example: + +``` +dateStart: '2020-01-01T00:00:00' +dateEnd: '2020-01-02T00:00:00' +interval: '1h' +``` + +The date range is 1 day === 24 hours. The interval is 1 hour. So there should +be ~24 time series points in the output. + +For preview purposes: + +- The `termSize` parameter should be used to help cut +down on the amount of work ES does, and keep the generated graphs a little +simpler. Probably something like `10`. + +- For queries with long date ranges, you probably don't want to use the +`interval` the alert is set to, as the `interval` used in the query, as this +could result in a lot of time-series points being generated, which is both +costly in ES, and may result in noisy graphs. + +- The `timeWindow*` parameters should be the same as what the alert is using, +especially for the `count` and `sum` aggregation types. Those aggregations +don't scale the same way the others do, when the window changes. Even for +the other aggregations, changing the window could result in dramatically +different values being generated - `avg` will be more "average-y", `min` +and `max` will be a little stickier. \ No newline at end of file diff --git a/x-pack/plugins/triggers_actions_ui/server/data/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/index.ts new file mode 100644 index 0000000000000..6ee2b4bb8a5fe --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/server/data/index.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, IRouter } from '../../../../../src/core/server'; +import { timeSeriesQuery } from './lib/time_series_query'; +import { registerRoutes } from './routes'; + +export { + TimeSeriesQuery, + CoreQueryParams, + CoreQueryParamsSchemaProperties, + validateCoreQueryBody, +} from './lib'; + +// future enhancement: make these configurable? +export const MAX_INTERVALS = 1000; +export const MAX_GROUPS = 1000; +export const DEFAULT_GROUPS = 100; + +export function getService() { + return { + timeSeriesQuery, + }; +} + +interface RegisterParams { + logger: Logger; + router: IRouter; + data: ReturnType; + baseRoute: string; +} + +export function register(params: RegisterParams) { + const { logger, router, data, baseRoute } = params; + const baseBuiltInRoute = `${baseRoute}/data`; + registerRoutes({ logger, router, data, baseRoute: baseBuiltInRoute }); +} diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/core_query_types.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.test.ts similarity index 100% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/core_query_types.test.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.test.ts diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/core_query_types.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts similarity index 68% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/core_query_types.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts index d96f555eceff4..bc7d0c352756e 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/core_query_types.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/core_query_types.ts @@ -51,33 +51,45 @@ export function validateCoreQueryBody(anyParams: unknown): string | undefined { termSize, }: CoreQueryParams = anyParams as CoreQueryParams; if (aggType !== 'count' && !aggField) { - return i18n.translate('xpack.stackAlerts.indexThreshold.aggTypeRequiredErrorMessage', { - defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"', - values: { - aggType, - }, - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.aggTypeRequiredErrorMessage', + { + defaultMessage: '[aggField]: must have a value when [aggType] is "{aggType}"', + values: { + aggType, + }, + } + ); } // check grouping if (groupBy === 'top') { if (termField == null) { - return i18n.translate('xpack.stackAlerts.indexThreshold.termFieldRequiredErrorMessage', { - defaultMessage: '[termField]: termField required when [groupBy] is top', - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.termFieldRequiredErrorMessage', + { + defaultMessage: '[termField]: termField required when [groupBy] is top', + } + ); } if (termSize == null) { - return i18n.translate('xpack.stackAlerts.indexThreshold.termSizeRequiredErrorMessage', { - defaultMessage: '[termSize]: termSize required when [groupBy] is top', - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.termSizeRequiredErrorMessage', + { + defaultMessage: '[termSize]: termSize required when [groupBy] is top', + } + ); } if (termSize > MAX_GROUPS) { - return i18n.translate('xpack.stackAlerts.indexThreshold.invalidTermSizeMaximumErrorMessage', { - defaultMessage: '[termSize]: must be less than or equal to {maxGroups}', - values: { - maxGroups: MAX_GROUPS, - }, - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.invalidTermSizeMaximumErrorMessage', + { + defaultMessage: '[termSize]: must be less than or equal to {maxGroups}', + values: { + maxGroups: MAX_GROUPS, + }, + } + ); } } } @@ -89,7 +101,7 @@ function validateAggType(aggType: string): string | undefined { return; } - return i18n.translate('xpack.stackAlerts.indexThreshold.invalidAggTypeErrorMessage', { + return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.invalidAggTypeErrorMessage', { defaultMessage: 'invalid aggType: "{aggType}"', values: { aggType, @@ -102,7 +114,7 @@ export function validateGroupBy(groupBy: string): string | undefined { return; } - return i18n.translate('xpack.stackAlerts.indexThreshold.invalidGroupByErrorMessage', { + return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.invalidGroupByErrorMessage', { defaultMessage: 'invalid groupBy: "{groupBy}"', values: { groupBy, @@ -117,10 +129,13 @@ export function validateTimeWindowUnits(timeWindowUnit: string): string | undefi return; } - return i18n.translate('xpack.stackAlerts.indexThreshold.invalidTimeWindowUnitsErrorMessage', { - defaultMessage: 'invalid timeWindowUnit: "{timeWindowUnit}"', - values: { - timeWindowUnit, - }, - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.invalidTimeWindowUnitsErrorMessage', + { + defaultMessage: 'invalid timeWindowUnit: "{timeWindowUnit}"', + values: { + timeWindowUnit, + }, + } + ); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/date_range_info.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/date_range_info.test.ts similarity index 100% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/date_range_info.test.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/date_range_info.test.ts diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/date_range_info.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/date_range_info.ts similarity index 89% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/date_range_info.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/date_range_info.ts index 34f276d08706b..da125ba7ea29d 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/date_range_info.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/date_range_info.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { times } from 'lodash'; -import { parseDuration } from '../../../../../alerts/server'; +import { parseDuration } from '../../../../alerts/server'; import { MAX_INTERVALS } from '../index'; // dates as numbers are epoch millis @@ -100,7 +100,7 @@ function getDuration(durationS: string, field: string): number { } function getParseErrorMessage(formatName: string, fieldName: string, fieldValue: string) { - return i18n.translate('xpack.stackAlerts.indexThreshold.formattedFieldErrorMessage', { + return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.formattedFieldErrorMessage', { defaultMessage: 'invalid {formatName} format for {fieldName}: "{fieldValue}"', values: { formatName, @@ -111,7 +111,7 @@ function getParseErrorMessage(formatName: string, fieldName: string, fieldValue: } export function getTooManyIntervalsErrorMessage(intervals: number, maxIntervals: number) { - return i18n.translate('xpack.stackAlerts.indexThreshold.maxIntervalsErrorMessage', { + return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.maxIntervalsErrorMessage', { defaultMessage: 'calculated number of intervals {intervals} is greater than maximum {maxIntervals}', values: { @@ -122,7 +122,10 @@ export function getTooManyIntervalsErrorMessage(intervals: number, maxIntervals: } export function getDateStartAfterDateEndErrorMessage(): string { - return i18n.translate('xpack.stackAlerts.indexThreshold.dateStartGTdateEndErrorMessage', { - defaultMessage: '[dateStart]: is greater than [dateEnd]', - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.dateStartGTdateEndErrorMessage', + { + defaultMessage: '[dateStart]: is greater than [dateEnd]', + } + ); } diff --git a/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts new file mode 100644 index 0000000000000..096a928249fd5 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TimeSeriesQuery } from './time_series_query'; +export { + CoreQueryParams, + CoreQueryParamsSchemaProperties, + validateCoreQueryBody, +} from './core_query_types'; diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts similarity index 58% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query.test.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts index 0565a8634fc71..f1234249a257f 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query.test.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.test.ts @@ -6,12 +6,8 @@ // test error conditions of calling timeSeriesQuery - postive results tested in FT -import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; -import { coreMock } from '../../../../../../../src/core/server/mocks'; -import { AlertingBuiltinsPlugin } from '../../../plugin'; -import { TimeSeriesQueryParameters, TimeSeriesResult, TimeSeriesQuery } from './time_series_query'; - -type TimeSeriesQueryFn = (query: TimeSeriesQueryParameters) => Promise; +import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { TimeSeriesQueryParameters, TimeSeriesQuery, timeSeriesQuery } from './time_series_query'; const DefaultQueryParams: TimeSeriesQuery = { index: 'index-name', @@ -32,16 +28,7 @@ describe('timeSeriesQuery', () => { let params: TimeSeriesQueryParameters; const mockCallCluster = jest.fn(); - let timeSeriesQueryFn: TimeSeriesQueryFn; - beforeEach(async () => { - // rather than use the function from an import, retrieve it from the plugin - const context = coreMock.createPluginInitializerContext(); - const plugin = new AlertingBuiltinsPlugin(context); - const coreStart = coreMock.createStart(); - const service = await plugin.start(coreStart); - timeSeriesQueryFn = service.indexThreshold.timeSeriesQuery; - mockCallCluster.mockReset(); params = { logger: loggingSystemMock.create().get(), @@ -52,14 +39,14 @@ describe('timeSeriesQuery', () => { it('fails as expected when the callCluster call fails', async () => { mockCallCluster.mockRejectedValue(new Error('woopsie')); - expect(timeSeriesQueryFn(params)).rejects.toThrowErrorMatchingInlineSnapshot( + expect(timeSeriesQuery(params)).rejects.toThrowErrorMatchingInlineSnapshot( `"error running search"` ); }); it('fails as expected when the query params are invalid', async () => { params.query = { ...params.query, dateStart: 'x' }; - expect(timeSeriesQueryFn(params)).rejects.toThrowErrorMatchingInlineSnapshot( + expect(timeSeriesQuery(params)).rejects.toThrowErrorMatchingInlineSnapshot( `"invalid date format for dateStart: \\"x\\""` ); }); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts similarity index 96% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts index 9c4133be6f483..29a996bbb5ef6 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_query.ts @@ -5,16 +5,17 @@ */ import { SearchResponse } from 'elasticsearch'; +import { Logger } from 'kibana/server'; +import { LegacyScopedClusterClient } from '../../../../../../src/core/server'; import { DEFAULT_GROUPS } from '../index'; import { getDateRangeInfo } from './date_range_info'; -import { Logger, CallCluster } from '../../../types'; import { TimeSeriesQuery, TimeSeriesResult, TimeSeriesResultRow } from './time_series_types'; export { TimeSeriesQuery, TimeSeriesResult } from './time_series_types'; export interface TimeSeriesQueryParameters { logger: Logger; - callCluster: CallCluster; + callCluster: LegacyScopedClusterClient['callAsCurrentUser']; query: TimeSeriesQuery; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_types.test.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.test.ts similarity index 100% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_types.test.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.test.ts diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_types.ts b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts similarity index 81% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_types.ts rename to x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts index a8b35c34c596f..ef0fa15cf31e9 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_types.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/lib/time_series_types.ts @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; -import { parseDuration } from '../../../../../alerts/server'; +import { parseDuration } from '../../../../alerts/server'; import { MAX_INTERVALS } from '../index'; import { CoreQueryParamsSchemaProperties, validateCoreQueryBody } from './core_query_types'; import { @@ -18,11 +18,7 @@ import { getDateStartAfterDateEndErrorMessage, } from './date_range_info'; -export { - TimeSeriesResult, - TimeSeriesResultRow, - MetricResult, -} from '../../../../common/alert_types/index_threshold'; +export { TimeSeriesResult, TimeSeriesResultRow, MetricResult } from '../../../common/data'; // The parameters here are very similar to the alert parameters. // Missing are `comparator` and `threshold`, which aren't needed to generate @@ -66,9 +62,12 @@ function validateBody(anyParams: unknown): string | undefined { } if (epochStart !== epochEnd && !interval) { - return i18n.translate('xpack.stackAlerts.indexThreshold.intervalRequiredErrorMessage', { - defaultMessage: '[interval]: must be specified if [dateStart] does not equal [dateEnd]', - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.intervalRequiredErrorMessage', + { + defaultMessage: '[interval]: must be specified if [dateStart] does not equal [dateEnd]', + } + ); } if (interval) { @@ -84,7 +83,7 @@ function validateBody(anyParams: unknown): string | undefined { function validateDate(dateString: string): string | undefined { const parsed = Date.parse(dateString); if (isNaN(parsed)) { - return i18n.translate('xpack.stackAlerts.indexThreshold.invalidDateErrorMessage', { + return i18n.translate('xpack.triggersActionsUI.data.coreQueryParams.invalidDateErrorMessage', { defaultMessage: 'invalid date {date}', values: { date: dateString, @@ -97,11 +96,14 @@ export function validateDuration(duration: string): string | undefined { try { parseDuration(duration); } catch (err) { - return i18n.translate('xpack.stackAlerts.indexThreshold.invalidDurationErrorMessage', { - defaultMessage: 'invalid duration: "{duration}"', - values: { - duration, - }, - }); + return i18n.translate( + 'xpack.triggersActionsUI.data.coreQueryParams.invalidDurationErrorMessage', + { + defaultMessage: 'invalid duration: "{duration}"', + values: { + duration, + }, + } + ); } } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/fields.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts similarity index 90% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/fields.ts rename to x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts index ea1e17002c4a5..17a2b2929f0cf 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/fields.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/fields.ts @@ -16,7 +16,7 @@ import { KibanaResponseFactory, ILegacyScopedClusterClient, } from 'kibana/server'; -import { Service } from '../../../types'; +import { Logger } from '../../../../../../src/core/server'; const bodySchema = schema.object({ indexPatterns: schema.arrayOf(schema.string()), @@ -24,9 +24,9 @@ const bodySchema = schema.object({ type RequestBody = TypeOf; -export function createFieldsRoute(service: Service, router: IRouter, baseRoute: string) { +export function createFieldsRoute(logger: Logger, router: IRouter, baseRoute: string) { const path = `${baseRoute}/_fields`; - service.logger.debug(`registering indexThreshold route POST ${path}`); + logger.debug(`registering indexThreshold route POST ${path}`); router.post( { path, @@ -41,7 +41,7 @@ export function createFieldsRoute(service: Service, router: IRouter, baseRoute: req: KibanaRequest, res: KibanaResponseFactory ): Promise { - service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); + logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); let rawFields: RawFields; @@ -54,7 +54,7 @@ export function createFieldsRoute(service: Service, router: IRouter, baseRoute: rawFields = await getRawFields(ctx.core.elasticsearch.legacy.client, req.body.indexPatterns); } catch (err) { const indexPatterns = req.body.indexPatterns.join(','); - service.logger.warn( + logger.warn( `route ${path} error getting fields from pattern "${indexPatterns}": ${err.message}` ); return res.ok({ body: { fields: [] } }); @@ -62,7 +62,7 @@ export function createFieldsRoute(service: Service, router: IRouter, baseRoute: const result = { fields: getFieldsFromRawFields(rawFields) }; - service.logger.debug(`route ${path} response: ${JSON.stringify(result)}`); + logger.debug(`route ${path} response: ${JSON.stringify(result)}`); return res.ok({ body: result }); } } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/index.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/index.ts similarity index 55% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/index.ts rename to x-pack/plugins/triggers_actions_ui/server/data/routes/index.ts index 8410e48dd46d9..664b78cabb560 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/index.ts @@ -4,19 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Service, IRouter } from '../../../types'; +import { Logger } from '../../../../../../src/core/server'; import { createTimeSeriesQueryRoute } from './time_series_query'; import { createFieldsRoute } from './fields'; import { createIndicesRoute } from './indices'; +import { IRouter } from '../../../../../../src/core/server'; +import { getService } from '..'; interface RegisterRoutesParams { - service: Service; + logger: Logger; router: IRouter; + data: ReturnType; baseRoute: string; } export function registerRoutes(params: RegisterRoutesParams) { - const { service, router, baseRoute } = params; - createTimeSeriesQueryRoute(service, router, baseRoute); - createFieldsRoute(service, router, baseRoute); - createIndicesRoute(service, router, baseRoute); + const { logger, router, baseRoute, data } = params; + createTimeSeriesQueryRoute(logger, data.timeSeriesQuery, router, baseRoute); + createFieldsRoute(logger, router, baseRoute); + createIndicesRoute(logger, router, baseRoute); } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/indices.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts similarity index 85% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/indices.ts rename to x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts index c94705829ec60..9b84ce5ac0bcc 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/indices.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/indices.ts @@ -19,7 +19,7 @@ import { ILegacyScopedClusterClient, } from 'kibana/server'; import { SearchResponse } from 'elasticsearch'; -import { Service } from '../../../types'; +import { Logger } from '../../../../../../src/core/server'; const bodySchema = schema.object({ pattern: schema.string(), @@ -27,9 +27,9 @@ const bodySchema = schema.object({ type RequestBody = TypeOf; -export function createIndicesRoute(service: Service, router: IRouter, baseRoute: string) { +export function createIndicesRoute(logger: Logger, router: IRouter, baseRoute: string) { const path = `${baseRoute}/_indices`; - service.logger.debug(`registering indexThreshold route POST ${path}`); + logger.debug(`registering indexThreshold route POST ${path}`); router.post( { path, @@ -45,7 +45,7 @@ export function createIndicesRoute(service: Service, router: IRouter, baseRoute: res: KibanaResponseFactory ): Promise { const pattern = req.body.pattern; - service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); + logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); if (pattern.trim() === '') { return res.ok({ body: { indices: [] } }); @@ -55,23 +55,19 @@ export function createIndicesRoute(service: Service, router: IRouter, baseRoute: try { aliases = await getAliasesFromPattern(ctx.core.elasticsearch.legacy.client, pattern); } catch (err) { - service.logger.warn( - `route ${path} error getting aliases from pattern "${pattern}": ${err.message}` - ); + logger.warn(`route ${path} error getting aliases from pattern "${pattern}": ${err.message}`); } let indices: string[] = []; try { indices = await getIndicesFromPattern(ctx.core.elasticsearch.legacy.client, pattern); } catch (err) { - service.logger.warn( - `route ${path} error getting indices from pattern "${pattern}": ${err.message}` - ); + logger.warn(`route ${path} error getting indices from pattern "${pattern}": ${err.message}`); } const result = { indices: uniqueCombined(aliases, indices, MAX_INDICES) }; - service.logger.debug(`route ${path} response: ${JSON.stringify(result)}`); + logger.debug(`route ${path} response: ${JSON.stringify(result)}`); return res.ok({ body: result }); } } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/time_series_query.ts b/x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts similarity index 58% rename from x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/time_series_query.ts rename to x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts index 9af01dc766a99..805bd7d4004c2 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/index_threshold/routes/time_series_query.ts +++ b/x-pack/plugins/triggers_actions_ui/server/data/routes/time_series_query.ts @@ -11,14 +11,20 @@ import { IKibanaResponse, KibanaResponseFactory, } from 'kibana/server'; +import { Logger } from '../../../../../../src/core/server'; +import { TimeSeriesQueryParameters } from '../lib/time_series_query'; -import { Service } from '../../../types'; -import { TimeSeriesQuery, TimeSeriesQuerySchema } from '../lib/time_series_types'; +import { TimeSeriesQuery, TimeSeriesQuerySchema, TimeSeriesResult } from '../lib/time_series_types'; export { TimeSeriesQuery, TimeSeriesResult } from '../lib/time_series_types'; -export function createTimeSeriesQueryRoute(service: Service, router: IRouter, baseRoute: string) { +export function createTimeSeriesQueryRoute( + logger: Logger, + timeSeriesQuery: (params: TimeSeriesQueryParameters) => Promise, + router: IRouter, + baseRoute: string +) { const path = `${baseRoute}/_time_series_query`; - service.logger.debug(`registering indexThreshold route POST ${path}`); + logger.debug(`registering indexThreshold route POST ${path}`); router.post( { path, @@ -33,15 +39,15 @@ export function createTimeSeriesQueryRoute(service: Service, router: IRouter, ba req: KibanaRequest, res: KibanaResponseFactory ): Promise { - service.logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); + logger.debug(`route ${path} request: ${JSON.stringify(req.body)}`); - const result = await service.indexThreshold.timeSeriesQuery({ - logger: service.logger, + const result = await timeSeriesQuery({ + logger, callCluster: ctx.core.elasticsearch.legacy.client.callAsCurrentUser, query: req.body, }); - service.logger.debug(`route ${path} response: ${JSON.stringify(result)}`); + logger.debug(`route ${path} response: ${JSON.stringify(result)}`); return res.ok({ body: result }); } } diff --git a/x-pack/plugins/triggers_actions_ui/server/index.ts b/x-pack/plugins/triggers_actions_ui/server/index.ts index c12572f4ea7e9..abd61f2bd3541 100644 --- a/x-pack/plugins/triggers_actions_ui/server/index.ts +++ b/x-pack/plugins/triggers_actions_ui/server/index.ts @@ -4,8 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginConfigDescriptor } from 'kibana/server'; +import { PluginConfigDescriptor, PluginInitializerContext } from 'kibana/server'; import { configSchema, ConfigSchema } from '../config'; +import { TriggersActionsPlugin } from './plugin'; + +export { PluginStartContract } from './plugin'; +export { + TimeSeriesQuery, + CoreQueryParams, + CoreQueryParamsSchemaProperties, + validateCoreQueryBody, + MAX_INTERVALS, + MAX_GROUPS, + DEFAULT_GROUPS, +} from './data'; export const config: PluginConfigDescriptor = { exposeToBrowser: { @@ -14,7 +26,4 @@ export const config: PluginConfigDescriptor = { schema: configSchema, }; -export const plugin = () => ({ - setup() {}, - start() {}, -}); +export const plugin = (ctx: PluginInitializerContext) => new TriggersActionsPlugin(ctx); diff --git a/x-pack/plugins/triggers_actions_ui/server/plugin.ts b/x-pack/plugins/triggers_actions_ui/server/plugin.ts new file mode 100644 index 0000000000000..c0d29341e217b --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/server/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, Plugin, CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { getService, register as registerDataService } from './data'; + +export interface PluginStartContract { + data: ReturnType; +} + +export class TriggersActionsPlugin implements Plugin { + private readonly logger: Logger; + private readonly data: PluginStartContract['data']; + + constructor(ctx: PluginInitializerContext) { + this.logger = ctx.logger.get(); + this.data = getService(); + } + + public async setup(core: CoreSetup): Promise { + registerDataService({ + logger: this.logger, + data: this.data, + router: core.http.createRouter(), + baseRoute: '/api/triggers_actions_ui', + }); + } + + public async start(): Promise { + return { + data: this.data, + }; + } + + public async stop(): Promise {} +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts index c05fa6cf051ff..e918ce174a031 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/alert.ts @@ -15,7 +15,6 @@ import { ObjectRemover, } from '../../../../../common/lib'; import { createEsDocuments } from './create_test_data'; -import { getAlertType } from '../../../../../../../plugins/triggers_actions_ui/public/application/components/builtin_alert_types/threshold/'; const ALERT_TYPE_ID = '.index-threshold'; const ACTION_TYPE_ID = '.index'; @@ -27,7 +26,7 @@ const ALERT_INTERVALS_TO_WRITE = 5; const ALERT_INTERVAL_SECONDS = 3; const ALERT_INTERVAL_MILLIS = ALERT_INTERVAL_SECONDS * 1000; -const DefaultActionMessage = getAlertType().defaultActionMessage; +const DefaultActionMessage = `alert {{alertName}} group {{context.group}} value {{context.value}} exceeded threshold {{context.function}} over {{params.timeWindowSize}}{{params.timeWindowUnit}} on {{context.date}}`; // eslint-disable-next-line import/no-default-export export default function alertTests({ getService }: FtrProviderContext) { @@ -65,10 +64,6 @@ export default function alertTests({ getService }: FtrProviderContext) { await esTestIndexToolOutput.destroy(); }); - it('has a default action message', () => { - expect(DefaultActionMessage).to.be.ok(); - }); - // The tests below create two alerts, one that will fire, one that will // never fire; the tests ensure the ones that should fire, do fire, and // those that shouldn't fire, do not fire. diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts index 76ff41aac5397..881be83236be5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/fields_endpoint.ts @@ -10,7 +10,7 @@ import { Spaces } from '../../../../scenarios'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { ESTestIndexTool, ES_TEST_INDEX_NAME, getUrlPrefix } from '../../../../../common/lib'; -const API_URI = 'api/stack_alerts/index_threshold/_fields'; +const API_URI = 'api/triggers_actions_ui/data/_fields'; // eslint-disable-next-line import/no-default-export export default function fieldsEndpointTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts index ba2b71e7134b6..7d89e2701d628 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/indices_endpoint.ts @@ -11,7 +11,7 @@ import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { ESTestIndexTool, ES_TEST_INDEX_NAME, getUrlPrefix } from '../../../../../common/lib'; import { createEsDocuments } from './create_test_data'; -const API_URI = 'api/stack_alerts/index_threshold/_indices'; +const API_URI = 'api/triggers_actions_ui/data/_indices'; // eslint-disable-next-line import/no-default-export export default function indicesEndpointTests({ getService }: FtrProviderContext) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts index 70dbb860e08d6..334f898232bbc 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/builtin_alert_types/index_threshold/time_series_query_endpoint.ts @@ -9,11 +9,11 @@ import expect from '@kbn/expect'; import { Spaces } from '../../../../scenarios'; import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; import { ESTestIndexTool, ES_TEST_INDEX_NAME, getUrlPrefix } from '../../../../../common/lib'; -import { TimeSeriesQuery } from '../../../../../../../plugins/stack_alerts/server/alert_types/index_threshold/lib/time_series_query'; +import { TimeSeriesQuery } from '../../../../../../../plugins/triggers_actions_ui/server'; import { createEsDocuments } from './create_test_data'; -const INDEX_THRESHOLD_TIME_SERIES_QUERY_URL = 'api/stack_alerts/index_threshold/_time_series_query'; +const INDEX_THRESHOLD_TIME_SERIES_QUERY_URL = 'api/triggers_actions_ui/data/_time_series_query'; const START_DATE_MM_DD_HH_MM_SS_MS = '01-01T00:00:00.000Z'; const START_DATE = `2020-${START_DATE_MM_DD_HH_MM_SS_MS}`; From 1babb5f6bfb08791506c5472377c2d76c3ac0dc8 Mon Sep 17 00:00:00 2001 From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com> Date: Thu, 12 Nov 2020 11:44:15 -0500 Subject: [PATCH 2/3] [Fleet] IngestManager Plugin interface for registering UI extensions (#82783) * Expose `registerExtension()` interface on `Plugin#start` * Refactor use of `CustomConfigurePackagePolicy` to the new registerExtension approach * Refactor to always show registered ui extension (even if Integration has configuration options) --- .../fleet/components/extension_wrapper.tsx | 17 ++++ .../fleet/hooks/use_ui_extension.ts | 35 +++++++ .../fleet/public/applications/fleet/index.tsx | 12 ++- .../components/custom_package_policy.tsx | 61 ------------ .../components/index.ts | 1 - .../create_package_policy_page/index.tsx | 34 ++++++- .../step_configure_package.tsx | 38 ++++++-- .../edit_package_policy_page/index.tsx | 48 +++++++++- .../fleet/services/ui_extensions.test.ts | 76 +++++++++++++++ .../fleet/services/ui_extensions.ts | 26 +++++ .../public/applications/fleet/types/index.ts | 2 + .../applications/fleet/types/ui_extensions.ts | 96 +++++++++++++++++++ x-pack/plugins/fleet/public/index.ts | 7 +- x-pack/plugins/fleet/public/plugin.ts | 20 +++- .../mock/endpoint/dependencies_start_mock.ts | 4 +- .../endpoint_policy_create_extension.tsx | 35 +++++++ ...tsx => endpoint_policy_edit_extension.tsx} | 34 ++----- .../lazy_endpoint_policy_create_extension.tsx | 17 ++++ .../lazy_endpoint_policy_edit_extension.tsx | 18 ++++ .../security_solution/public/plugin.tsx | 22 +++-- 20 files changed, 478 insertions(+), 125 deletions(-) create mode 100644 x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts delete mode 100644 x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx create mode 100644 x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts create mode 100644 x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx rename x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/{configure_package_policy.tsx => endpoint_policy_edit_extension.tsx} (81%) create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx create mode 100644 x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx diff --git a/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx b/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx new file mode 100644 index 0000000000000..874c91e8e546b --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/components/extension_wrapper.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo, ReactNode, Suspense } from 'react'; +import { EuiErrorBoundary } from '@elastic/eui'; +import { Loading } from './loading'; + +export const ExtensionWrapper = memo<{ children: ReactNode }>(({ children }) => { + return ( + + }>{children} + + ); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts new file mode 100644 index 0000000000000..93bc1eae28cf6 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/hooks/use_ui_extension.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { UIExtensionPoint, UIExtensionsStorage } from '../types'; + +export const UIExtensionsContext = React.createContext({}); + +type NarrowExtensionPoint = A extends { + view: V; +} + ? A + : never; + +export const useUIExtension = ( + packageName: UIExtensionPoint['package'], + view: V +): NarrowExtensionPoint['component'] | undefined => { + const registeredExtensions = useContext(UIExtensionsContext); + + if (!registeredExtensions) { + throw new Error('useUIExtension called outside of UIExtensionsContext'); + } + + const extension = registeredExtensions?.[packageName]?.[view]; + + if (extension) { + // FIXME:PT Revisit ignore below and see if TS error can be addressed + // @ts-ignore + return extension.component; + } +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/index.tsx index a49306f8e8d55..d4e652ad95831 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/index.tsx @@ -36,6 +36,8 @@ import { import { PackageInstallProvider } from './sections/epm/hooks'; import { FleetStatusProvider, useBreadcrumbs } from './hooks'; import { IntraAppStateProvider } from './hooks/use_intra_app_state'; +import { UIExtensionsStorage } from './types'; +import { UIExtensionsContext } from './hooks/use_ui_extension'; export interface ProtectedRouteProps extends RouteProps { isAllowed?: boolean; @@ -235,6 +237,7 @@ const IngestManagerApp = ({ config, history, kibanaVersion, + extensions, }: { basepath: string; coreStart: CoreStart; @@ -243,6 +246,7 @@ const IngestManagerApp = ({ config: IngestManagerConfigType; history: AppMountParameters['history']; kibanaVersion: string; + extensions: UIExtensionsStorage; }) => { const isDarkMode = useObservable(coreStart.uiSettings.get$('theme:darkMode')); return ( @@ -252,7 +256,9 @@ const IngestManagerApp = ({ - + + + @@ -268,7 +274,8 @@ export function renderApp( setupDeps: IngestManagerSetupDeps, startDeps: IngestManagerStartDeps, config: IngestManagerConfigType, - kibanaVersion: string + kibanaVersion: string, + extensions: UIExtensionsStorage ) { ReactDOM.render( , element ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx deleted file mode 100644 index d5163e1b9abbe..0000000000000 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy.tsx +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiEmptyPrompt, EuiText } from '@elastic/eui'; -import { NewPackagePolicy } from '../../../../types'; -import { CreatePackagePolicyFrom } from '../types'; - -export interface CustomConfigurePackagePolicyProps { - packageName: string; - from: CreatePackagePolicyFrom; - packagePolicy: NewPackagePolicy; - packagePolicyId?: string; -} - -/** - * Custom content type that external plugins can provide to Ingest's - * package policy UI. - */ -export type CustomConfigurePackagePolicyContent = React.FC; - -type AllowedPackageKey = 'endpoint'; -const PackagePolicyMapping: { - [key: string]: CustomConfigurePackagePolicyContent; -} = {}; - -/** - * Plugins can call this function from the start lifecycle to - * register a custom component in the Ingest package policy. - */ -export function registerPackagePolicyComponent( - key: AllowedPackageKey, - value: CustomConfigurePackagePolicyContent -) { - PackagePolicyMapping[key] = value; -} - -const EmptyPackagePolicy: CustomConfigurePackagePolicyContent = () => ( - -

- -

- - } - /> -); - -export const CustomPackagePolicy = (props: CustomConfigurePackagePolicyProps) => { - const CustomPackagePolicyContent = PackagePolicyMapping[props.packageName] || EmptyPackagePolicy; - return ; -}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts index 893ed00cca9ac..58b5b1cd3126e 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/components/index.ts @@ -6,4 +6,3 @@ export { CreatePackagePolicyPageLayout } from './layout'; export { PackagePolicyInputPanel } from './package_policy_input_panel'; export { PackagePolicyInputVarField } from './package_policy_input_var_field'; -export { CustomPackagePolicy } from './custom_package_policy'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx index b45794b9f87db..a837ed33e4110 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/index.tsx @@ -46,6 +46,9 @@ import { StepSelectAgentPolicy } from './step_select_agent_policy'; import { StepConfigurePackagePolicy } from './step_configure_package'; import { StepDefinePackagePolicy } from './step_define_package_policy'; import { useIntraAppState } from '../../../hooks/use_intra_app_state'; +import { useUIExtension } from '../../../hooks/use_ui_extension'; +import { ExtensionWrapper } from '../../../components/extension_wrapper'; +import { PackagePolicyEditExtensionComponentProps } from '../../../types'; const StepsWithLessPadding = styled(EuiSteps)` .euiStep__content { @@ -191,6 +194,21 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { [packagePolicy, updatePackagePolicyValidation] ); + const handleExtensionViewOnChange = useCallback< + PackagePolicyEditExtensionComponentProps['onChange'] + >( + ({ isValid, updatedPolicy }) => { + updatePackagePolicy(updatedPolicy); + setFormState((prevState) => { + if (prevState === 'VALID' && !isValid) { + return 'INVALID'; + } + return prevState; + }); + }, + [updatePackagePolicy] + ); + // Cancel path const cancelUrl = useMemo(() => { if (routeState && routeState.onCancelUrl) { @@ -287,6 +305,8 @@ export const CreatePackagePolicyPage: React.FunctionComponent = () => { [pkgkey, updatePackageInfo, agentPolicy, updateAgentPolicy] ); + const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-create'); + const stepSelectPackage = useMemo( () => ( { validationResults={validationResults!} submitAttempted={formState === 'INVALID'} /> + {/* If an Agent Policy and a package has been selected, then show UI extension (if any) */} + {packagePolicy.policy_id && packagePolicy.package?.name && ExtensionView && ( + + + + )} ) : (
), [ - agentPolicy, - formState, isLoadingSecondStep, - packagePolicy, + agentPolicy, packageInfo, + packagePolicy, updatePackagePolicy, validationResults, + formState, + ExtensionView, + handleExtensionViewOnChange, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx index b335ff439684b..671bc829af82a 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/step_configure_package.tsx @@ -4,7 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiEmptyPrompt, + EuiText, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import { PackageInfo, RegistryStream, @@ -13,8 +20,9 @@ import { } from '../../../types'; import { Loading } from '../../../components'; import { PackagePolicyValidationResults } from './services'; -import { PackagePolicyInputPanel, CustomPackagePolicy } from './components'; +import { PackagePolicyInputPanel } from './components'; import { CreatePackagePolicyFrom } from './types'; +import { useUIExtension } from '../../../hooks/use_ui_extension'; const findStreamsForInputType = ( inputType: string, @@ -55,6 +63,12 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ validationResults, submitAttempted, }) => { + const hasUiExtension = + useUIExtension( + packageInfo.name, + from === 'edit' ? 'package-policy-edit' : 'package-policy-create' + ) !== undefined; + // Configure inputs (and their streams) // Assume packages only export one config template for now const renderConfigureInputs = () => @@ -98,12 +112,20 @@ export const StepConfigurePackagePolicy: React.FunctionComponent<{ })} - ) : ( - +

+ +

+ + } /> ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx index d642619515a57..bfc10848d378f 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx @@ -41,6 +41,10 @@ import { } from '../create_package_policy_page/types'; import { StepConfigurePackagePolicy } from '../create_package_policy_page/step_configure_package'; import { StepDefinePackagePolicy } from '../create_package_policy_page/step_define_package_policy'; +import { useUIExtension } from '../../../hooks/use_ui_extension'; +import { ExtensionWrapper } from '../../../components/extension_wrapper'; +import { GetOnePackagePolicyResponse } from '../../../../../../common/types/rest_spec'; +import { PackagePolicyEditExtensionComponentProps } from '../../../types'; export const EditPackagePolicyPage: React.FunctionComponent = () => { const { notifications } = useCore(); @@ -68,6 +72,9 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { inputs: [], version: '', }); + const [originalPackagePolicy, setOriginalPackagePolicy] = useState< + GetOnePackagePolicyResponse['item'] + >(); // Retrieve agent policy, package, and package policy info useEffect(() => { @@ -83,6 +90,8 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { setAgentPolicy(agentPolicyData.item); } if (packagePolicyData?.item) { + setOriginalPackagePolicy(packagePolicyData.item); + const { id, revision, @@ -189,6 +198,21 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { [packagePolicy, updatePackagePolicyValidation] ); + const handleExtensionViewOnChange = useCallback< + PackagePolicyEditExtensionComponentProps['onChange'] + >( + ({ isValid, updatedPolicy }) => { + updatePackagePolicy(updatedPolicy); + setFormState((prevState) => { + if (prevState === 'VALID' && !isValid) { + return 'INVALID'; + } + return prevState; + }); + }, + [updatePackagePolicy] + ); + // Cancel url const cancelUrl = getHref('policy_details', { policyId }); @@ -267,6 +291,8 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { packageInfo, }; + const ExtensionView = useUIExtension(packagePolicy.package?.name ?? '', 'package-policy-edit'); + const configurePackage = useMemo( () => agentPolicy && packageInfo ? ( @@ -288,16 +314,32 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => { validationResults={validationResults!} submitAttempted={formState === 'INVALID'} /> + + {packagePolicy.policy_id && + packagePolicy.package?.name && + originalPackagePolicy && + ExtensionView && ( + + + + )} ) : null, [ agentPolicy, - formState, - packagePolicy, - packagePolicyId, packageInfo, + packagePolicy, updatePackagePolicy, validationResults, + packagePolicyId, + formState, + originalPackagePolicy, + ExtensionView, + handleExtensionViewOnChange, ] ); diff --git a/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts new file mode 100644 index 0000000000000..97c0203fab056 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { lazy } from 'react'; + +import { + PackagePolicyEditExtensionComponent, + UIExtensionRegistrationCallback, + UIExtensionsStorage, +} from '../types'; +import { createExtensionRegistrationCallback } from './ui_extensions'; + +describe('UI Extension services', () => { + describe('When using createExtensionRegistrationCallback factory', () => { + let storage: UIExtensionsStorage; + let register: UIExtensionRegistrationCallback; + + beforeEach(() => { + storage = {}; + register = createExtensionRegistrationCallback(storage); + }); + + it('should return a function', () => { + expect(register).toBeInstanceOf(Function); + }); + + it('should store an extension points', () => { + const LazyCustomView = lazy(async () => { + return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + }); + register({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + + expect(storage.endpoint['package-policy-edit']).toEqual({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + }); + + it('should throw if extension point has already registered', () => { + const LazyCustomView = lazy(async () => { + return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + }); + const LazyCustomView2 = lazy(async () => { + return { default: ((() => {}) as unknown) as PackagePolicyEditExtensionComponent }; + }); + + register({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + + expect(() => { + register({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView2, + }); + }).toThrow(); + + expect(storage.endpoint['package-policy-edit']).toEqual({ + view: 'package-policy-edit', + package: 'endpoint', + component: LazyCustomView, + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts new file mode 100644 index 0000000000000..5af9122d4f12a --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/services/ui_extensions.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UIExtensionRegistrationCallback, UIExtensionsStorage } from '../types'; + +/** Factory that returns a callback that can be used to register UI extensions */ +export const createExtensionRegistrationCallback = ( + storage: UIExtensionsStorage +): UIExtensionRegistrationCallback => { + return (extensionPoint) => { + const { package: packageName, view } = extensionPoint; + + if (!storage[packageName]) { + storage[packageName] = {}; + } + + if (storage[packageName]?.[view]) { + throw new Error(`Extension point has already been registered: [${packageName}][${view}]`); + } + + storage[packageName][view] = extensionPoint; + }; +}; diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts index 1cf8077aeda40..78cb355318d40 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/types/index.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/types/index.ts @@ -119,3 +119,5 @@ export { } from '../../../../common'; export * from './intra_app_route_state'; + +export * from './ui_extensions'; diff --git a/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts new file mode 100644 index 0000000000000..fbede8af95b66 --- /dev/null +++ b/x-pack/plugins/fleet/public/applications/fleet/types/ui_extensions.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ComponentType, LazyExoticComponent } from 'react'; +import { NewPackagePolicy, PackagePolicy } from './index'; + +/** Register a Fleet UI extension */ +export type UIExtensionRegistrationCallback = (extensionPoint: UIExtensionPoint) => void; + +/** Internal storage for registered UI Extension Points */ +export interface UIExtensionsStorage { + [key: string]: Partial>; +} + +/** + * UI Component Extension is used on the pages displaying the ability to edit an + * Integration Policy + */ +export type PackagePolicyEditExtensionComponent = ComponentType< + PackagePolicyEditExtensionComponentProps +>; + +export interface PackagePolicyEditExtensionComponentProps { + /** The current integration policy being edited */ + policy: PackagePolicy; + /** The new (updated) integration policy that will be saved */ + newPolicy: NewPackagePolicy; + /** + * A callback that should be executed anytime a change to the Integration Policy needs to + * be reported back to the Fleet Policy Edit page + */ + onChange: (opts: { + /** is current form state is valid */ + isValid: boolean; + /** The updated Integration Policy to be merged back and included in the API call */ + updatedPolicy: NewPackagePolicy; + }) => void; +} + +/** Extension point registration contract for Integration Policy Edit views */ +export interface PackagePolicyEditExtension { + package: string; + view: 'package-policy-edit'; + component: LazyExoticComponent; +} + +/** + * UI Component Extension is used on the pages displaying the ability to Create an + * Integration Policy + */ +export type PackagePolicyCreateExtensionComponent = ComponentType< + PackagePolicyCreateExtensionComponentProps +>; + +export interface PackagePolicyCreateExtensionComponentProps { + /** The integration policy being created */ + newPolicy: NewPackagePolicy; + /** + * A callback that should be executed anytime a change to the Integration Policy needs to + * be reported back to the Fleet Policy Edit page + */ + onChange: (opts: { + /** is current form state is valid */ + isValid: boolean; + /** The updated Integration Policy to be merged back and included in the API call */ + updatedPolicy: NewPackagePolicy; + }) => void; +} + +/** Extension point registration contract for Integration Policy Create views */ +export interface PackagePolicyCreateExtension { + package: string; + view: 'package-policy-create'; + component: LazyExoticComponent; +} + +/** + * UI Component Extension is used to display a Custom tab (and view) under a given Integration + */ +export type PackageCustomExtensionComponent = ComponentType; + +/** Extension point registration contract for Integration details Custom view */ +export interface PackageCustomExtension { + package: string; + view: 'package-detail-custom'; + component: LazyExoticComponent; +} + +/** Fleet UI Extension Point */ +export type UIExtensionPoint = + | PackagePolicyEditExtension + | PackageCustomExtension + | PackagePolicyCreateExtension; diff --git a/x-pack/plugins/fleet/public/index.ts b/x-pack/plugins/fleet/public/index.ts index f974a8c3d3cc8..1de001a6fc69e 100644 --- a/x-pack/plugins/fleet/public/index.ts +++ b/x-pack/plugins/fleet/public/index.ts @@ -12,13 +12,8 @@ export const plugin = (initializerContext: PluginInitializerContext) => { return new IngestManagerPlugin(initializerContext); }; -export { - CustomConfigurePackagePolicyContent, - CustomConfigurePackagePolicyProps, - registerPackagePolicyComponent, -} from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; - export type { NewPackagePolicy } from './applications/fleet/types'; export * from './applications/fleet/types/intra_app_route_state'; +export * from './applications/fleet/types/ui_extensions'; export { pagePathGetters } from './applications/fleet/constants'; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 2e7cbb9cb86ab..377ba770b5ca2 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -30,7 +30,8 @@ import { TutorialDirectoryHeaderLink, TutorialModuleNotice, } from './applications/fleet/components/home_integration'; -import { registerPackagePolicyComponent } from './applications/fleet/sections/agent_policy/create_package_policy_page/components/custom_package_policy'; +import { createExtensionRegistrationCallback } from './applications/fleet/services/ui_extensions'; +import { UIExtensionRegistrationCallback, UIExtensionsStorage } from './applications/fleet/types'; export { IngestManagerConfigType } from '../common/types'; @@ -43,7 +44,7 @@ export interface IngestManagerSetup {} * Describes public IngestManager plugin contract returned at the `start` stage. */ export interface IngestManagerStart { - registerPackagePolicyComponent: typeof registerPackagePolicyComponent; + registerExtension: UIExtensionRegistrationCallback; isInitialized: () => Promise; } @@ -62,6 +63,7 @@ export class IngestManagerPlugin Plugin { private config: IngestManagerConfigType; private kibanaVersion: string; + private extensions: UIExtensionsStorage = {}; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); @@ -71,6 +73,7 @@ export class IngestManagerPlugin public setup(core: CoreSetup, deps: IngestManagerSetupDeps) { const config = this.config; const kibanaVersion = this.kibanaVersion; + const extensions = this.extensions; // Set up http client setHttpClient(core.http); @@ -92,7 +95,15 @@ export class IngestManagerPlugin IngestManagerStart ]; const { renderApp, teardownIngestManager } = await import('./applications/fleet/'); - const unmount = renderApp(coreStart, params, deps, startDeps, config, kibanaVersion); + const unmount = renderApp( + coreStart, + params, + deps, + startDeps, + config, + kibanaVersion, + extensions + ); return () => { unmount(); @@ -153,7 +164,8 @@ export class IngestManagerPlugin return successPromise; }, - registerPackagePolicyComponent, + + registerExtension: createExtensionRegistrationCallback(this.extensions), }; } diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts index ff3fe7517e64a..3388fb5355845 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/dependencies_start_mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IngestManagerStart, registerPackagePolicyComponent } from '../../../../../fleet/public'; +import { IngestManagerStart } from '../../../../../fleet/public'; import { dataPluginMock, Start as DataPublicStartMock, @@ -58,7 +58,7 @@ export const depsStartMock: () => DepsStartMock = () => { data: dataMock, ingestManager: { isInitialized: () => Promise.resolve(true), - registerPackagePolicyComponent, + registerExtension: jest.fn(), }, }; }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx new file mode 100644 index 0000000000000..69406a41fe055 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_create_extension.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { memo } from 'react'; +import { EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { PackagePolicyCreateExtensionComponentProps } from '../../../../../../../fleet/public'; + +/** + * Exports Endpoint-specific package policy instructions + * for use in the Ingest app create / edit package policy + */ +export const EndpointPolicyCreateExtension = memo( + () => { + return ( + <> + + + +

+ +

+
+
+ + ); + } +); +EndpointPolicyCreateExtension.displayName = 'EndpointPolicyCreateExtension'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx similarity index 81% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx rename to x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx index 0be5f119e5eff..b667ea965af68 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/configure_package_policy.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/endpoint_policy_edit_extension.tsx @@ -20,9 +20,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { - CustomConfigurePackagePolicyContent, - CustomConfigurePackagePolicyProps, pagePathGetters, + PackagePolicyEditExtensionComponentProps, } from '../../../../../../../fleet/public'; import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing'; import { MANAGEMENT_APP_ID } from '../../../../common/constants'; @@ -37,42 +36,21 @@ import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoi * Exports Endpoint-specific package policy instructions * for use in the Ingest app create / edit package policy */ -export const ConfigureEndpointPackagePolicy = memo( - ({ - from, - packagePolicyId, - packagePolicy: { policy_id: agentPolicyId }, - }: CustomConfigurePackagePolicyProps) => { +export const EndpointPolicyEditExtension = memo( + ({ policy }) => { return ( <> - + - {from === 'edit' ? ( - <> - - - ) : ( -

- -

- )} +
); } ); -ConfigureEndpointPackagePolicy.displayName = 'ConfigureEndpointPackagePolicy'; +EndpointPolicyEditExtension.displayName = 'EndpointPolicyEditExtension'; const EditFlowMessage = memo<{ agentPolicyId: string; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx new file mode 100644 index 0000000000000..b7a6fa36e4eb7 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { lazy } from 'react'; +import { PackagePolicyCreateExtensionComponent } from '../../../../../../../fleet/public'; + +export const LazyEndpointPolicyCreateExtension = lazy( + async () => { + const { EndpointPolicyCreateExtension } = await import('./endpoint_policy_create_extension'); + return { + default: EndpointPolicyCreateExtension, + }; + } +); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx new file mode 100644 index 0000000000000..b417bc9ad5d9c --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { lazy } from 'react'; +import { PackagePolicyEditExtensionComponent } from '../../../../../../../fleet/public'; + +export const LazyEndpointPolicyEditExtension = lazy( + async () => { + const { EndpointPolicyEditExtension } = await import('./endpoint_policy_edit_extension'); + return { + // FIXME: remove casting once old UI component registration is removed + default: (EndpointPolicyEditExtension as unknown) as PackagePolicyEditExtensionComponent, + }; + } +); diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 08c780d4a7203..5895880adb26a 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { BehaviorSubject } from 'rxjs'; import { pluck } from 'rxjs/operators'; - import { PluginSetup, PluginStart, @@ -44,8 +43,6 @@ import { DEFAULT_INDEX_KEY, } from '../common/constants'; -import { ConfigureEndpointPackagePolicy } from './management/pages/policy/view/ingest_manager_integration/configure_package_policy'; - import { SecurityPageName } from './app/types'; import { manageOldSiemRoutes } from './helpers'; import { @@ -63,6 +60,8 @@ import { } from '../common/search_strategy/index_fields'; import { SecurityAppStore } from './common/store/store'; import { getCaseConnectorUI } from './common/lib/connectors'; +import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; +import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; export class Plugin implements IPlugin { private kibanaVersion: string; @@ -332,10 +331,19 @@ export class Plugin implements IPlugin Date: Thu, 12 Nov 2020 17:51:42 +0100 Subject: [PATCH 3/3] [ML] Update apidoc config with the Trained models endpoints (#83274) * [ML] fix apidoc annotations * [ML] add trained models * [ML] use full path to the apidoc-markdown package --- x-pack/plugins/ml/package.json | 2 +- x-pack/plugins/ml/server/routes/apidoc.json | 8 ++++- .../ml/server/routes/trained_models.ts | 30 +++++++++---------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/ml/package.json b/x-pack/plugins/ml/package.json index a41e9e845063f..1ec697568a849 100644 --- a/x-pack/plugins/ml/package.json +++ b/x-pack/plugins/ml/package.json @@ -6,6 +6,6 @@ "license": "Elastic-License", "scripts": { "build:apiDocScripts": "cd server/routes/apidoc_scripts && ../../../../../../node_modules/.bin/tsc", - "apiDocs": "yarn build:apiDocScripts && cd ./server/routes/ && ../../../../../node_modules/.bin/apidoc --parse-workers apischema=./apidoc_scripts/target/schema_worker.js --parse-parsers apischema=./apidoc_scripts/target/schema_parser.js --parse-filters apiversion=./apidoc_scripts/target/version_filter.js -i . -o ../routes_doc && apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md -t ./apidoc_scripts/template.md" + "apiDocs": "yarn build:apiDocScripts && cd ./server/routes/ && ../../../../../node_modules/.bin/apidoc --parse-workers apischema=./apidoc_scripts/target/schema_worker.js --parse-parsers apischema=./apidoc_scripts/target/schema_parser.js --parse-filters apiversion=./apidoc_scripts/target/version_filter.js -i . -o ../routes_doc && ../../../../../node_modules/.bin/apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md -t ./apidoc_scripts/template.md" } } \ No newline at end of file diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index 780835e2a300b..8d6dd692cc130 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -148,6 +148,12 @@ "InitializeJobSavedObjects", "AssignJobsToSpaces", "RemoveJobsFromSpaces", - "JobsSpaces" + "JobsSpaces", + + "TrainedModels", + "GetTrainedModel", + "GetTrainedModelStats", + "GetTrainedModelPipelines", + "DeleteTrainedModel" ] } diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index 579f63e13328d..e9bd854864c2d 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -16,11 +16,11 @@ import { InferenceConfigResponse } from '../../common/types/trained_models'; export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) { /** - * @apiGroup Inference + * @apiGroup TrainedModels * * @api {get} /api/ml/trained_models/:modelId Get info of a trained inference model - * @apiName GetInferenceModel - * @apiDescription Retrieves configuration information for a trained inference model. + * @apiName GetTrainedModel + * @apiDescription Retrieves configuration information for a trained model. */ router.get( { @@ -68,11 +68,11 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) ); /** - * @apiGroup Inference + * @apiGroup TrainedModels * - * @api {get} /api/ml/trained_models/:modelId/_stats Get stats of a trained inference model - * @apiName GetInferenceModelStats - * @apiDescription Retrieves usage information for trained inference models. + * @api {get} /api/ml/trained_models/:modelId/_stats Get stats of a trained model + * @apiName GetTrainedModelStats + * @apiDescription Retrieves usage information for trained models. */ router.get( { @@ -100,11 +100,11 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) ); /** - * @apiGroup Inference + * @apiGroup TrainedModels * - * @api {get} /api/ml/trained_models/:modelId/pipelines Get model pipelines - * @apiName GetModelPipelines - * @apiDescription Retrieves pipelines associated with a model + * @api {get} /api/ml/trained_models/:modelId/pipelines Get trained model pipelines + * @apiName GetTrainedModelPipelines + * @apiDescription Retrieves pipelines associated with a trained model */ router.get( { @@ -130,11 +130,11 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) ); /** - * @apiGroup Inference + * @apiGroup TrainedModels * - * @api {delete} /api/ml/trained_models/:modelId Get stats of a trained inference model - * @apiName DeleteInferenceModel - * @apiDescription Deletes an existing trained inference model that is currently not referenced by an ingest pipeline. + * @api {delete} /api/ml/trained_models/:modelId Delete a trained model + * @apiName DeleteTrainedModel + * @apiDescription Deletes an existing trained model that is currently not referenced by an ingest pipeline. */ router.delete( {