mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[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_.
This commit is contained in:
parent
208e86e66a
commit
ab72206da3
78 changed files with 1212 additions and 896 deletions
|
@ -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
|
||||
|
|
|
@ -102,3 +102,4 @@ pageLoadAssetSize:
|
|||
visualizations: 295025
|
||||
visualize: 57431
|
||||
watcher: 43598
|
||||
stackAlerts: 29684
|
||||
|
|
|
@ -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<typeof configSchema>;
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<GeoThresholdAlertParams, AlertsContextValue> {
|
||||
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',
|
|
@ -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<Props> = ({
|
|||
<EuiFormRow
|
||||
id="geoField"
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.triggersActionsUI.geoThreshold.geofieldLabel', {
|
||||
label={i18n.translate('xpack.stackAlerts.geoThreshold.geofieldLabel', {
|
||||
defaultMessage: 'Geospatial field',
|
||||
})}
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate('xpack.triggersActionsUI.geoThreshold.selectLabel', {
|
||||
placeholder={i18n.translate('xpack.stackAlerts.geoThreshold.selectLabel', {
|
||||
defaultMessage: 'Select geo field',
|
||||
})}
|
||||
value={boundaryGeoField}
|
||||
|
@ -133,12 +132,12 @@ export const BoundaryIndexExpression: FunctionComponent<Props> = ({
|
|||
<EuiFormRow
|
||||
id="boundaryNameFieldSelect"
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.triggersActionsUI.geoThreshold.boundaryNameSelectLabel', {
|
||||
label={i18n.translate('xpack.stackAlerts.geoThreshold.boundaryNameSelectLabel', {
|
||||
defaultMessage: 'Human-readable boundary name (optional)',
|
||||
})}
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate('xpack.triggersActionsUI.geoThreshold.boundaryNameSelect', {
|
||||
placeholder={i18n.translate('xpack.stackAlerts.geoThreshold.boundaryNameSelect', {
|
||||
defaultMessage: 'Select boundary name',
|
||||
})}
|
||||
value={boundaryNameField || null}
|
||||
|
@ -156,7 +155,7 @@ export const BoundaryIndexExpression: FunctionComponent<Props> = ({
|
|||
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',
|
||||
})}
|
||||
/>
|
|
@ -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<Props> = ({
|
|||
<EuiFormRow id="entitySelect" fullWidth error={errors.index}>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate(
|
||||
'xpack.triggersActionsUI.geoThreshold.topHitsSplitFieldSelectPlaceholder',
|
||||
'xpack.stackAlerts.geoThreshold.topHitsSplitFieldSelectPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Select entity field',
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ export const EntityByExpression: FunctionComponent<Props> = ({
|
|||
value={entity}
|
||||
defaultValue={'Select entity field'}
|
||||
popoverContent={indexPopover}
|
||||
expressionDescription={i18n.translate('xpack.triggersActionsUI.geoThreshold.entityByLabel', {
|
||||
expressionDescription={i18n.translate('xpack.stackAlerts.geoThreshold.entityByLabel', {
|
||||
defaultMessage: 'by',
|
||||
})}
|
||||
/>
|
|
@ -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<Props> = ({
|
|||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.timeFieldLabel"
|
||||
id="xpack.stackAlerts.geoThreshold.timeFieldLabel"
|
||||
defaultMessage="Time field"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate('xpack.triggersActionsUI.geoThreshold.selectTimeLabel', {
|
||||
placeholder={i18n.translate('xpack.stackAlerts.geoThreshold.selectTimeLabel', {
|
||||
defaultMessage: 'Select time field',
|
||||
})}
|
||||
value={timeField}
|
||||
|
@ -124,12 +123,12 @@ export const EntityIndexExpression: FunctionComponent<Props> = ({
|
|||
<EuiFormRow
|
||||
id="geoField"
|
||||
fullWidth
|
||||
label={i18n.translate('xpack.triggersActionsUI.geoThreshold.geofieldLabel', {
|
||||
label={i18n.translate('xpack.stackAlerts.geoThreshold.geofieldLabel', {
|
||||
defaultMessage: 'Geospatial field',
|
||||
})}
|
||||
>
|
||||
<SingleFieldSelect
|
||||
placeholder={i18n.translate('xpack.triggersActionsUI.geoThreshold.selectGeoLabel', {
|
||||
placeholder={i18n.translate('xpack.stackAlerts.geoThreshold.selectGeoLabel', {
|
||||
defaultMessage: 'Select geo field',
|
||||
})}
|
||||
value={geoField}
|
||||
|
@ -148,12 +147,9 @@ export const EntityIndexExpression: FunctionComponent<Props> = ({
|
|||
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',
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -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 = (
|
||||
<>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.delayOffset"
|
||||
id="xpack.stackAlerts.geoThreshold.delayOffset"
|
||||
defaultMessage="Delayed evaluation offset"
|
||||
/>{' '}
|
||||
<EuiIconTip
|
||||
position="right"
|
||||
type="questionInCircle"
|
||||
content={i18n.translate('xpack.triggersActionsUI.geoThreshold.delayOffsetTooltip', {
|
||||
content={i18n.translate('xpack.stackAlerts.geoThreshold.delayOffsetTooltip', {
|
||||
defaultMessage: 'Evaluate alerts on a delayed cycle to adjust for data latency',
|
||||
})}
|
||||
/>
|
||||
|
@ -125,7 +127,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
|
||||
const hasExpressionErrors = false;
|
||||
const expressionErrorMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.geoThreshold.fixErrorInExpressionBelowValidationMessage',
|
||||
'xpack.stackAlerts.geoThreshold.fixErrorInExpressionBelowValidationMessage',
|
||||
{
|
||||
defaultMessage: 'Expression contains errors.',
|
||||
}
|
||||
|
@ -180,7 +182,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.selectOffset"
|
||||
id="xpack.stackAlerts.geoThreshold.selectOffset"
|
||||
defaultMessage="Select offset (optional)"
|
||||
/>
|
||||
</h5>
|
||||
|
@ -221,7 +223,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.selectEntity"
|
||||
id="xpack.stackAlerts.geoThreshold.selectEntity"
|
||||
defaultMessage="Select entity"
|
||||
/>
|
||||
</h5>
|
||||
|
@ -251,7 +253,7 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.selectIndex"
|
||||
id="xpack.stackAlerts.geoThreshold.selectIndex"
|
||||
defaultMessage="Define the condition"
|
||||
/>
|
||||
</h5>
|
||||
|
@ -280,19 +282,16 @@ export const GeoThresholdAlertTypeExpression: React.FunctionComponent<AlertTypeP
|
|||
</div>
|
||||
</EuiFormRow>
|
||||
}
|
||||
expressionDescription={i18n.translate(
|
||||
'xpack.triggersActionsUI.geoThreshold.whenEntityLabel',
|
||||
{
|
||||
defaultMessage: 'when entity',
|
||||
}
|
||||
)}
|
||||
expressionDescription={i18n.translate('xpack.stackAlerts.geoThreshold.whenEntityLabel', {
|
||||
defaultMessage: 'when entity',
|
||||
})}
|
||||
/>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.selectBoundaryIndex"
|
||||
id="xpack.stackAlerts.geoThreshold.selectBoundaryIndex"
|
||||
defaultMessage="Select boundary:"
|
||||
/>
|
||||
</h5>
|
|
@ -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',
|
||||
}
|
|
@ -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<Props, State> {
|
|||
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<Props, State> {
|
|||
return (
|
||||
<>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.triggersActionsUI.geoThreshold.noIndexPattern.messageTitle',
|
||||
{
|
||||
defaultMessage: `Couldn't find any index patterns with geospatial fields`,
|
||||
}
|
||||
)}
|
||||
title={i18n.translate('xpack.stackAlerts.geoThreshold.noIndexPattern.messageTitle', {
|
||||
defaultMessage: `Couldn't find any index patterns with geospatial fields`,
|
||||
})}
|
||||
color="warning"
|
||||
>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisPrefixDescription"
|
||||
id="xpack.stackAlerts.geoThreshold.noIndexPattern.doThisPrefixDescription"
|
||||
defaultMessage="You'll need to "
|
||||
/>
|
||||
<EuiLink
|
||||
href={this.props.http.basePath.prepend(`/app/management/kibana/indexPatterns`)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisLinkTextDescription"
|
||||
id="xpack.stackAlerts.geoThreshold.noIndexPattern.doThisLinkTextDescription"
|
||||
defaultMessage="create an index pattern"
|
||||
/>
|
||||
</EuiLink>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.noIndexPattern.doThisSuffixDescription"
|
||||
id="xpack.stackAlerts.geoThreshold.noIndexPattern.doThisSuffixDescription"
|
||||
defaultMessage=" with geospatial fields."
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.noIndexPattern.hintDescription"
|
||||
id="xpack.stackAlerts.geoThreshold.noIndexPattern.hintDescription"
|
||||
defaultMessage="Don't have any geospatial data sets? "
|
||||
/>
|
||||
<EuiLink
|
||||
href={this.props.http.basePath.prepend('/app/home#/tutorial_directory/sampleData')}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.geoThreshold.noIndexPattern.getStartedLinkText"
|
||||
id="xpack.stackAlerts.geoThreshold.noIndexPattern.getStartedLinkText"
|
||||
defaultMessage="Get started with some sample data sets."
|
||||
/>
|
||||
</EuiLink>
|
||||
|
@ -123,7 +121,7 @@ export class GeoIndexPatternSelect extends Component<Props, State> {
|
|||
{this._renderNoIndexPatternWarning()}
|
||||
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.triggersActionsUI.geoThreshold.indexPatternSelectLabel', {
|
||||
label={i18n.translate('xpack.stackAlerts.geoThreshold.indexPatternSelectLabel', {
|
||||
defaultMessage: 'Index pattern',
|
||||
})}
|
||||
>
|
||||
|
@ -133,7 +131,7 @@ export class GeoIndexPatternSelect extends Component<Props, State> {
|
|||
indexPatternId={this.props.value}
|
||||
onChange={this._onIndexPatternSelect}
|
||||
placeholder={i18n.translate(
|
||||
'xpack.triggersActionsUI.geoThreshold.indexPatternSelectPlaceholder',
|
||||
'xpack.stackAlerts.geoThreshold.indexPatternSelectPlaceholder',
|
||||
{
|
||||
defaultMessage: 'Select index pattern',
|
||||
}
|
|
@ -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<EuiComboBoxOptionOption<IFieldType>> {
|
||||
if (!fields) {
|
|
@ -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.',
|
||||
})
|
||||
);
|
|
@ -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<AlertTypeModel>;
|
||||
triggerActionsUiConfig: TriggersActionsUiConfigType;
|
||||
alertTypeRegistry: TriggersAndActionsUIPublicPluginSetup['alertTypeRegistry'];
|
||||
config: Config;
|
||||
}) {
|
||||
if (triggerActionsUiConfig.enableGeoTrackingThresholdAlert) {
|
||||
if (config.enableGeoTrackingThresholdAlert) {
|
||||
alertTypeRegistry.register(getGeoThresholdAlertType());
|
||||
}
|
||||
alertTypeRegistry.register(getThresholdAlertType());
|
|
@ -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<AlertTyp
|
|||
|
||||
const [indexPopoverOpen, setIndexPopoverOpen] = useState(false);
|
||||
const [indexPatterns, setIndexPatterns] = useState([]);
|
||||
const [esFields, setEsFields] = useState<Record<string, any>>([]);
|
||||
const [esFields, setEsFields] = useState<unknown[]>([]);
|
||||
const [indexOptions, setIndexOptions] = useState<EuiComboBoxOptionOption[]>([]);
|
||||
const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]);
|
||||
const [isIndiciesLoading, setIsIndiciesLoading] = useState<boolean>(false);
|
||||
|
@ -98,7 +97,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
(errorKey) =>
|
||||
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<AlertTyp
|
|||
);
|
||||
|
||||
const expressionErrorMessage = i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertAdd.threshold.fixErrorInExpressionBelowValidationMessage',
|
||||
'xpack.stackAlerts.threshold.ui.alertParams.fixErrorInExpressionBelowValidationMessage',
|
||||
{
|
||||
defaultMessage: 'Expression contains errors.',
|
||||
}
|
||||
|
@ -126,7 +125,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
|
||||
if (index && index.length > 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<AlertTyp
|
|||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.threshold.indicesToQueryLabel"
|
||||
id="xpack.stackAlerts.threshold.ui.alertParams.indicesToQueryLabel"
|
||||
defaultMessage="Indices to query"
|
||||
/>
|
||||
}
|
||||
|
@ -167,7 +166,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
error={errors.index}
|
||||
helpText={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.threshold.howToBroadenSearchQueryDescription"
|
||||
id="xpack.stackAlerts.threshold.ui.alertParams.howToBroadenSearchQueryDescription"
|
||||
defaultMessage="Use * to broaden your query."
|
||||
/>
|
||||
}
|
||||
|
@ -211,7 +210,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
return;
|
||||
}
|
||||
const currentEsFields = await getFields(http, indices);
|
||||
const timeFields = getTimeFieldOptions(currentEsFields as any);
|
||||
const timeFields = getTimeFieldOptions(currentEsFields);
|
||||
|
||||
setEsFields(currentEsFields);
|
||||
setTimeFieldOptions([firstFieldOption, ...timeFields]);
|
||||
|
@ -233,7 +232,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
fullWidth
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldLabel"
|
||||
id="xpack.stackAlerts.threshold.ui.alertParams.timeFieldLabel"
|
||||
defaultMessage="Time field"
|
||||
/>
|
||||
}
|
||||
|
@ -284,7 +283,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.selectIndex"
|
||||
id="xpack.stackAlerts.threshold.ui.selectIndex"
|
||||
defaultMessage="Select an index"
|
||||
/>
|
||||
</h5>
|
||||
|
@ -296,12 +295,9 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
<EuiExpression
|
||||
display="columns"
|
||||
data-test-subj="selectIndexExpression"
|
||||
description={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertAdd.threshold.indexLabel',
|
||||
{
|
||||
defaultMessage: 'index',
|
||||
}
|
||||
)}
|
||||
description={i18n.translate('xpack.stackAlerts.threshold.ui.alertParams.indexLabel', {
|
||||
defaultMessage: 'index',
|
||||
})}
|
||||
value={index && index.length > 0 ? renderIndices(index) : firstFieldOption.text}
|
||||
isActive={indexPopoverOpen}
|
||||
onClick={() => {
|
||||
|
@ -321,12 +317,9 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
<EuiPopoverTitle>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
{i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertAdd.threshold.indexButtonLabel',
|
||||
{
|
||||
defaultMessage: 'index',
|
||||
}
|
||||
)}
|
||||
{i18n.translate('xpack.stackAlerts.threshold.ui.alertParams.indexButtonLabel', {
|
||||
defaultMessage: 'index',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
|
@ -334,7 +327,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
iconType="cross"
|
||||
color="danger"
|
||||
aria-label={i18n.translate(
|
||||
'xpack.triggersActionsUI.sections.alertAdd.threshold.closeIndexPopoverLabel',
|
||||
'xpack.stackAlerts.threshold.ui.alertParams.closeIndexPopoverLabel',
|
||||
{
|
||||
defaultMessage: 'Close',
|
||||
}
|
||||
|
@ -386,7 +379,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.conditionPrompt"
|
||||
id="xpack.stackAlerts.threshold.ui.conditionPrompt"
|
||||
defaultMessage="Define the condition"
|
||||
/>
|
||||
</h5>
|
||||
|
@ -411,10 +404,10 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<AlertTyp
|
|||
timeWindowUnit={timeWindowUnit}
|
||||
display="fullWidth"
|
||||
errors={errors}
|
||||
onChangeWindowSize={(selectedWindowSize: any) =>
|
||||
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<AlertTyp
|
|||
body={
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.previewAlertVisualizationDescription"
|
||||
id="xpack.stackAlerts.threshold.ui.previewAlertVisualizationDescription"
|
||||
defaultMessage="Complete the expression to generate a preview."
|
||||
/>
|
||||
</EuiText>
|
|
@ -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<IndexThresholdAlertParams, AlertsContextValue> {
|
||||
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<IndexThresholdAlertParams, Alerts
|
|||
alertParamsExpression: lazy(() => 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\\}\\}`,
|
||||
}
|
|
@ -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<TimeSeriesResult> {
|
||||
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<TimeSeriesResult>(`${INDEX_THRESHOLD_DATA_API_ROOT}/_time_series_query`, {
|
||||
body: JSON.stringify(timeSeriesQueryParams),
|
||||
});
|
||||
}
|
|
@ -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.',
|
||||
})
|
||||
);
|
|
@ -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<Props> = ({
|
|||
const { http, toastNotifications, charts, uiSettings, dataFieldsFormats } = alertsContext;
|
||||
|
||||
const [loadingState, setLoadingState] = useState<LoadingStateType | null>(null);
|
||||
const [error, setError] = useState<undefined | any>(undefined);
|
||||
const [error, setError] = useState<undefined | Error>(undefined);
|
||||
const [visualizationData, setVisualizationData] = useState<Record<string, MetricResult[]>>();
|
||||
const [startVisualizationAt, setStartVisualizationAt] = useState<Date>(new Date());
|
||||
|
||||
|
@ -150,7 +156,7 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({
|
|||
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<Props> = ({
|
|||
body={
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.loadingAlertVisualizationDescription"
|
||||
id="xpack.stackAlerts.threshold.ui.visualization.loadingAlertVisualizationDescription"
|
||||
defaultMessage="Loading alert visualization…"
|
||||
/>
|
||||
</EuiText>
|
||||
|
@ -215,7 +221,7 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({
|
|||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.errorLoadingAlertVisualizationTitle"
|
||||
id="xpack.stackAlerts.threshold.ui.visualization.errorLoadingAlertVisualizationTitle"
|
||||
defaultMessage="Cannot load alert visualization"
|
||||
values={{}}
|
||||
/>
|
||||
|
@ -239,7 +245,7 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({
|
|||
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<Props> = ({
|
|||
/>
|
||||
);
|
||||
})}
|
||||
{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 (
|
||||
<LineAnnotation
|
||||
key={specId}
|
||||
id={specId}
|
||||
domainType={AnnotationDomainTypes.YDomain}
|
||||
dataValues={[{ dataValue: threshold[i], details: specId }]}
|
||||
dataValues={[{ dataValue: threshold[thresholdIndex], details: specId }]}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -305,14 +311,14 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({
|
|||
size="s"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.noDataTitle"
|
||||
id="xpack.stackAlerts.threshold.ui.visualization.thresholdPreviewChart.noDataTitle"
|
||||
defaultMessage="No data matches this query"
|
||||
/>
|
||||
}
|
||||
color="warning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.dataDoesNotExistTextMessage"
|
||||
id="xpack.stackAlerts.threshold.ui.visualization.thresholdPreviewChart.dataDoesNotExistTextMessage"
|
||||
defaultMessage="Check that your time range and filters are correct."
|
||||
/>
|
||||
</EuiCallOut>
|
||||
|
@ -325,7 +331,11 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({
|
|||
};
|
||||
|
||||
// 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,
|
10
x-pack/plugins/stack_alerts/public/index.ts
Normal file
10
x-pack/plugins/stack_alerts/public/index.ts
Normal file
|
@ -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);
|
35
x-pack/plugins/stack_alerts/public/plugin.tsx
Normal file
35
x-pack/plugins/stack_alerts/public/plugin.tsx
Normal file
|
@ -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<Setup, Start, StackAlertsPublicSetupDeps> {
|
||||
private initializerContext: PluginInitializerContext;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
this.initializerContext = initializerContext;
|
||||
}
|
||||
|
||||
public setup(core: CoreSetup, { triggersActionsUi }: StackAlertsPublicSetupDeps) {
|
||||
registerAlertTypes({
|
||||
alertTypeRegistry: triggersActionsUi.alertTypeRegistry,
|
||||
config: this.initializerContext.config.get<Config>(),
|
||||
});
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
|
@ -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<Service, 'indexThreshold'>
|
||||
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,
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<Service, 'indexThreshold'>;
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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<StackAlertsStartDeps['triggersActionsUi']['data']>;
|
||||
alerts: AlertingSetup;
|
||||
baseRoute: string;
|
||||
}
|
||||
|
||||
export function registerBuiltInAlertTypes(params: RegisterAlertTypesParams) {
|
||||
|
|
|
@ -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.
|
||||
The Index Threshold Alert Type is backed by Apis exposed by the [TriggersActionsUi plugin](../../../../triggers_actions_ui/README.md).
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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<Params, {}, {}, ActionContext> {
|
||||
const { logger } = service;
|
||||
|
||||
export function getAlertType(
|
||||
logger: Logger,
|
||||
data: Promise<StackAlertsStartDeps['triggersActionsUi']['data']>
|
||||
): AlertType<Params, {}, {}, ActionContext> {
|
||||
const alertTypeName = i18n.translate('xpack.stackAlerts.indexThreshold.alertTypeTitle', {
|
||||
defaultMessage: 'Index threshold',
|
||||
});
|
||||
|
@ -152,7 +155,7 @@ export function getAlertType(service: Service): AlertType<Params, {}, {}, Action
|
|||
interval: undefined,
|
||||
};
|
||||
// console.log(`index_threshold: query: ${JSON.stringify(queryParams, null, 4)}`);
|
||||
const result = await service.indexThreshold.timeSeriesQuery({
|
||||
const result = await (await data).timeSeriesQuery({
|
||||
logger,
|
||||
callCluster,
|
||||
query: queryParams,
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
*/
|
||||
|
||||
import { ParamsSchema, Params } from './alert_type_params';
|
||||
import { runTests } from './lib/core_query_types.test';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { ObjectType, TypeOf } from '@kbn/config-schema';
|
||||
import type { Writable } from '@kbn/utility-types';
|
||||
import { CoreQueryParams, MAX_GROUPS } from '../../../../triggers_actions_ui/server';
|
||||
|
||||
const DefaultParams: Writable<Partial<Params>> = {
|
||||
index: 'index-name',
|
||||
|
@ -71,3 +71,185 @@ describe('alertType Params validate()', () => {
|
|||
return ParamsSchema.validate(params);
|
||||
}
|
||||
});
|
||||
|
||||
export function runTests(schema: ObjectType, defaultTypeParams: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let params: any;
|
||||
|
||||
const CoreDefaultParams: Writable<Partial<CoreQueryParams>> = {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<StackAlertsStartDeps['triggersActionsUi']['data']>;
|
||||
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));
|
||||
}
|
||||
|
|
|
@ -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<Config> = {
|
||||
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);
|
||||
|
|
|
@ -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<typeof coreMock['createPluginInitializerContext']>;
|
||||
let plugin: AlertingBuiltinsPlugin;
|
||||
let coreStart: ReturnType<typeof coreMock['createStart']>;
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<IService, IService> {
|
||||
export class AlertingBuiltinsPlugin
|
||||
implements Plugin<void, void, StackAlertsDeps, StackAlertsStartDeps> {
|
||||
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<IService> {
|
||||
public async setup(
|
||||
core: CoreSetup<StackAlertsStartDeps>,
|
||||
{ alerts, features }: StackAlertsDeps
|
||||
): Promise<void> {
|
||||
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<IService> {
|
||||
return this.service;
|
||||
}
|
||||
|
||||
public async start(): Promise<void> {}
|
||||
public async stop(): Promise<void> {}
|
||||
}
|
||||
|
|
|
@ -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<typeof getServiceIndexThreshold>;
|
||||
export interface StackAlertsStartDeps {
|
||||
triggersActionsUi: TriggersActionsUiStartContract;
|
||||
}
|
||||
|
||||
// version of service for internal use
|
||||
export interface Service extends IService {
|
||||
logger: Logger;
|
||||
}
|
||||
|
||||
export type CallCluster = LegacyScopedClusterClient['callAsCurrentUser'];
|
||||
|
|
|
@ -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": "インスタンス",
|
||||
|
|
|
@ -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": "实例",
|
||||
|
|
|
@ -220,7 +220,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<IndexThr
|
|||
<EuiFormLabel>
|
||||
<FormattedMessage
|
||||
defaultMessage="Select Index to query:"
|
||||
id="xpack.triggersActionsUI.sections.alertAdd.selectIndex"
|
||||
id="xpack.stackAlerts.threshold.ui.selectIndex"
|
||||
/>
|
||||
....
|
||||
</Fragment>
|
||||
|
|
7
x-pack/plugins/triggers_actions_ui/common/index.ts
Normal file
7
x-pack/plugins/triggers_actions_ui/common/index.ts
Normal file
|
@ -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';
|
|
@ -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"]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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: '',
|
||||
};
|
||||
|
|
|
@ -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<Record<string, any>> {
|
||||
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;
|
||||
};
|
|
@ -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';
|
|
@ -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<Record<string, any>> {
|
||||
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<Record<string, any>> {
|
||||
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<TimeSeriesResult> {
|
||||
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<TimeSeriesResult>(`${INDEX_THRESHOLD_API_ROOT}/_time_series_query`, {
|
||||
body: JSON.stringify(timeSeriesQueryParams),
|
||||
});
|
||||
}
|
|
@ -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 };
|
||||
|
|
|
@ -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<ActionTypeModel>;
|
||||
alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
|
@ -66,14 +56,10 @@ export class Plugin
|
|||
> {
|
||||
private actionTypeRegistry: TypeRegistry<ActionTypeModel>;
|
||||
private alertTypeRegistry: TypeRegistry<AlertTypeModel>;
|
||||
private initializerContext: PluginInitializerContext;
|
||||
|
||||
constructor(initializerContext: PluginInitializerContext) {
|
||||
constructor() {
|
||||
this.actionTypeRegistry = new TypeRegistry<ActionTypeModel>();
|
||||
|
||||
this.alertTypeRegistry = new TypeRegistry<AlertTypeModel>();
|
||||
|
||||
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<TriggersActionsUiConfigType>(),
|
||||
});
|
||||
|
||||
return {
|
||||
actionTypeRegistry: this.actionTypeRegistry,
|
||||
alertTypeRegistry: this.alertTypeRegistry,
|
||||
|
|
228
x-pack/plugins/triggers_actions_ui/server/data/README.md
Normal file
228
x-pack/plugins/triggers_actions_ui/server/data/README.md
Normal file
|
@ -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.
|
40
x-pack/plugins/triggers_actions_ui/server/data/index.ts
Normal file
40
x-pack/plugins/triggers_actions_ui/server/data/index.ts
Normal file
|
@ -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<typeof getService>;
|
||||
baseRoute: string;
|
||||
}
|
||||
|
||||
export function register(params: RegisterParams) {
|
||||
const { logger, router, data, baseRoute } = params;
|
||||
const baseBuiltInRoute = `${baseRoute}/data`;
|
||||
registerRoutes({ logger, router, data, baseRoute: baseBuiltInRoute });
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -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]',
|
||||
}
|
||||
);
|
||||
}
|
12
x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts
Normal file
12
x-pack/plugins/triggers_actions_ui/server/data/lib/index.ts
Normal file
|
@ -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';
|
|
@ -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<TimeSeriesResult>;
|
||||
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\\""`
|
||||
);
|
||||
});
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<typeof bodySchema>;
|
||||
|
||||
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<unknown, unknown, RequestBody>,
|
||||
res: KibanaResponseFactory
|
||||
): Promise<IKibanaResponse> {
|
||||
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 });
|
||||
}
|
||||
}
|
|
@ -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<typeof getService>;
|
||||
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);
|
||||
}
|
|
@ -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<typeof bodySchema>;
|
||||
|
||||
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<IKibanaResponse> {
|
||||
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 });
|
||||
}
|
||||
}
|
|
@ -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<TimeSeriesResult>,
|
||||
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<unknown, unknown, TimeSeriesQuery>,
|
||||
res: KibanaResponseFactory
|
||||
): Promise<IKibanaResponse> {
|
||||
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 });
|
||||
}
|
||||
}
|
|
@ -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<ConfigSchema> = {
|
||||
exposeToBrowser: {
|
||||
|
@ -14,7 +26,4 @@ export const config: PluginConfigDescriptor<ConfigSchema> = {
|
|||
schema: configSchema,
|
||||
};
|
||||
|
||||
export const plugin = () => ({
|
||||
setup() {},
|
||||
start() {},
|
||||
});
|
||||
export const plugin = (ctx: PluginInitializerContext) => new TriggersActionsPlugin(ctx);
|
||||
|
|
39
x-pack/plugins/triggers_actions_ui/server/plugin.ts
Normal file
39
x-pack/plugins/triggers_actions_ui/server/plugin.ts
Normal file
|
@ -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<typeof getService>;
|
||||
}
|
||||
|
||||
export class TriggersActionsPlugin implements Plugin<void, PluginStartContract> {
|
||||
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<void> {
|
||||
registerDataService({
|
||||
logger: this.logger,
|
||||
data: this.data,
|
||||
router: core.http.createRouter(),
|
||||
baseRoute: '/api/triggers_actions_ui',
|
||||
});
|
||||
}
|
||||
|
||||
public async start(): Promise<PluginStartContract> {
|
||||
return {
|
||||
data: this.data,
|
||||
};
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}`;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue