mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* [APM] Transaction duration anomaly alerting integration (#75719) * Closes #72636. Adds alerting integration for APM transaction duration anomalies. * Code review feedback * Display alert summary with the selected anomaly severity label instead of the anomaly score. * - refactored ALL_OPTION and NOT_DEFINED_OPTION to be shared from common/environment_filter_values - utilize getEnvironmentLabel in the alerting trigger components and added support for the 'All' label * refactor get_all_environments to minimize exports and be more consistent and clean * - Reorg the alerts menu for different alert types (threshold/anomaly) - default environment alert settings to the selected filter * - Filters default transaction type to only those supported in the APM anomaly detection jobs - Removes Service name and transaction type from the set of expressions in the alerting setup * - remove bell icon from alerts menu * Adds target service back into the anomaly alert setup as a ready-only expression Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> * Fixes prettier linting * Fixes prettier linting again Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
9f630a26da
commit
5a08b86087
30 changed files with 578 additions and 122 deletions
|
@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
|
|||
export enum AlertType {
|
||||
ErrorRate = 'apm.error_rate',
|
||||
TransactionDuration = 'apm.transaction_duration',
|
||||
TransactionDurationAnomaly = 'apm.transaction_duration_anomaly',
|
||||
}
|
||||
|
||||
export const ALERT_TYPES_CONFIG = {
|
||||
|
@ -45,6 +46,24 @@ export const ALERT_TYPES_CONFIG = {
|
|||
defaultActionGroupId: 'threshold_met',
|
||||
producer: 'apm',
|
||||
},
|
||||
[AlertType.TransactionDurationAnomaly]: {
|
||||
name: i18n.translate('xpack.apm.transactionDurationAnomalyAlert.name', {
|
||||
defaultMessage: 'Transaction duration anomaly',
|
||||
}),
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'threshold_met',
|
||||
name: i18n.translate(
|
||||
'xpack.apm.transactionDurationAlert.thresholdMet',
|
||||
{
|
||||
defaultMessage: 'Threshold met',
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'threshold_met',
|
||||
producer: 'apm',
|
||||
},
|
||||
};
|
||||
|
||||
export const TRANSACTION_ALERT_AGGREGATION_TYPES = {
|
||||
|
|
|
@ -6,14 +6,30 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const ENVIRONMENT_ALL = 'ENVIRONMENT_ALL';
|
||||
export const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED';
|
||||
const ENVIRONMENT_ALL_VALUE = 'ENVIRONMENT_ALL';
|
||||
const ENVIRONMENT_NOT_DEFINED_VALUE = 'ENVIRONMENT_NOT_DEFINED';
|
||||
|
||||
const environmentLabels: Record<string, string> = {
|
||||
[ENVIRONMENT_ALL_VALUE]: i18n.translate(
|
||||
'xpack.apm.filter.environment.allLabel',
|
||||
{ defaultMessage: 'All' }
|
||||
),
|
||||
[ENVIRONMENT_NOT_DEFINED_VALUE]: i18n.translate(
|
||||
'xpack.apm.filter.environment.notDefinedLabel',
|
||||
{ defaultMessage: 'Not defined' }
|
||||
),
|
||||
};
|
||||
|
||||
export const ENVIRONMENT_ALL = {
|
||||
value: ENVIRONMENT_ALL_VALUE,
|
||||
text: environmentLabels[ENVIRONMENT_ALL_VALUE],
|
||||
};
|
||||
|
||||
export const ENVIRONMENT_NOT_DEFINED = {
|
||||
value: ENVIRONMENT_NOT_DEFINED_VALUE,
|
||||
text: environmentLabels[ENVIRONMENT_NOT_DEFINED_VALUE],
|
||||
};
|
||||
|
||||
export function getEnvironmentLabel(environment: string) {
|
||||
if (environment === ENVIRONMENT_NOT_DEFINED) {
|
||||
return i18n.translate('xpack.apm.filter.environment.notDefinedLabel', {
|
||||
defaultMessage: 'Not defined',
|
||||
});
|
||||
}
|
||||
return environment;
|
||||
return environmentLabels[environment] || environment;
|
||||
}
|
||||
|
|
|
@ -18,27 +18,37 @@ import { useApmPluginContext } from '../../../../hooks/useApmPluginContext';
|
|||
|
||||
const alertLabel = i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.alerts',
|
||||
{
|
||||
defaultMessage: 'Alerts',
|
||||
}
|
||||
{ defaultMessage: 'Alerts' }
|
||||
);
|
||||
const transactionDurationLabel = i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.transactionDuration',
|
||||
{ defaultMessage: 'Transaction duration' }
|
||||
);
|
||||
const errorRateLabel = i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.errorRate',
|
||||
{ defaultMessage: 'Error rate' }
|
||||
);
|
||||
|
||||
const createThresholdAlertLabel = i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.createThresholdAlert',
|
||||
{
|
||||
defaultMessage: 'Create threshold alert',
|
||||
}
|
||||
{ defaultMessage: 'Create threshold alert' }
|
||||
);
|
||||
const createAnomalyAlertAlertLabel = i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.createAnomalyAlert',
|
||||
{ defaultMessage: 'Create anomaly alert' }
|
||||
);
|
||||
|
||||
const CREATE_THRESHOLD_ALERT_PANEL_ID = 'create_threshold';
|
||||
const CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID =
|
||||
'create_transaction_duration';
|
||||
const CREATE_ERROR_RATE_ALERT_PANEL_ID = 'create_error_rate';
|
||||
|
||||
interface Props {
|
||||
canReadAlerts: boolean;
|
||||
canSaveAlerts: boolean;
|
||||
canReadAnomalies: boolean;
|
||||
}
|
||||
|
||||
export function AlertIntegrations(props: Props) {
|
||||
const { canSaveAlerts, canReadAlerts } = props;
|
||||
const { canSaveAlerts, canReadAlerts, canReadAnomalies } = props;
|
||||
|
||||
const plugin = useApmPluginContext();
|
||||
|
||||
|
@ -52,9 +62,7 @@ export function AlertIntegrations(props: Props) {
|
|||
iconSide="right"
|
||||
onClick={() => setPopoverOpen(true)}
|
||||
>
|
||||
{i18n.translate('xpack.apm.serviceDetails.alertsMenu.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
})}
|
||||
{alertLabel}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
|
@ -66,10 +74,10 @@ export function AlertIntegrations(props: Props) {
|
|||
...(canSaveAlerts
|
||||
? [
|
||||
{
|
||||
name: createThresholdAlertLabel,
|
||||
panel: CREATE_THRESHOLD_ALERT_PANEL_ID,
|
||||
icon: 'bell',
|
||||
name: transactionDurationLabel,
|
||||
panel: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID,
|
||||
},
|
||||
{ name: errorRateLabel, panel: CREATE_ERROR_RATE_ALERT_PANEL_ID },
|
||||
]
|
||||
: []),
|
||||
...(canReadAlerts
|
||||
|
@ -77,9 +85,7 @@ export function AlertIntegrations(props: Props) {
|
|||
{
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts',
|
||||
{
|
||||
defaultMessage: 'View active alerts',
|
||||
}
|
||||
{ defaultMessage: 'View active alerts' }
|
||||
),
|
||||
href: plugin.core.http.basePath.prepend(
|
||||
'/app/management/insightsAndAlerting/triggersActions/alerts'
|
||||
|
@ -91,29 +97,38 @@ export function AlertIntegrations(props: Props) {
|
|||
],
|
||||
},
|
||||
{
|
||||
id: CREATE_THRESHOLD_ALERT_PANEL_ID,
|
||||
title: createThresholdAlertLabel,
|
||||
id: CREATE_TRANSACTION_DURATION_ALERT_PANEL_ID,
|
||||
title: transactionDurationLabel,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.transactionDuration',
|
||||
{
|
||||
defaultMessage: 'Transaction duration',
|
||||
}
|
||||
),
|
||||
name: createThresholdAlertLabel,
|
||||
onClick: () => {
|
||||
setAlertType(AlertType.TransactionDuration);
|
||||
setPopoverOpen(false);
|
||||
},
|
||||
},
|
||||
...(canReadAnomalies
|
||||
? [
|
||||
{
|
||||
name: createAnomalyAlertAlertLabel,
|
||||
onClick: () => {
|
||||
setAlertType(AlertType.TransactionDurationAnomaly);
|
||||
setPopoverOpen(false);
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: CREATE_ERROR_RATE_ALERT_PANEL_ID,
|
||||
title: errorRateLabel,
|
||||
items: [
|
||||
{
|
||||
name: i18n.translate(
|
||||
'xpack.apm.serviceDetails.alertsMenu.errorRate',
|
||||
{
|
||||
defaultMessage: 'Error rate',
|
||||
}
|
||||
),
|
||||
name: createThresholdAlertLabel,
|
||||
onClick: () => {
|
||||
setAlertType(AlertType.ErrorRate);
|
||||
setPopoverOpen(false);
|
||||
},
|
||||
},
|
||||
],
|
||||
|
|
|
@ -26,19 +26,18 @@ export function ServiceDetails({ tab }: Props) {
|
|||
const plugin = useApmPluginContext();
|
||||
const { urlParams } = useUrlParams();
|
||||
const { serviceName } = urlParams;
|
||||
|
||||
const canReadAlerts = !!plugin.core.application.capabilities.apm[
|
||||
'alerting:show'
|
||||
];
|
||||
const canSaveAlerts = !!plugin.core.application.capabilities.apm[
|
||||
'alerting:save'
|
||||
];
|
||||
const capabilities = plugin.core.application.capabilities;
|
||||
const canReadAlerts = !!capabilities.apm['alerting:show'];
|
||||
const canSaveAlerts = !!capabilities.apm['alerting:save'];
|
||||
const isAlertingPluginEnabled = 'alerts' in plugin.plugins;
|
||||
|
||||
const isAlertingAvailable =
|
||||
isAlertingPluginEnabled && (canReadAlerts || canSaveAlerts);
|
||||
|
||||
const { core } = useApmPluginContext();
|
||||
const isMlPluginEnabled = 'ml' in plugin.plugins;
|
||||
const canReadAnomalies = !!(
|
||||
isMlPluginEnabled &&
|
||||
capabilities.ml.canAccessML &&
|
||||
capabilities.ml.canGetJobs
|
||||
);
|
||||
|
||||
const ADD_DATA_LABEL = i18n.translate('xpack.apm.addDataButtonLabel', {
|
||||
defaultMessage: 'Add data',
|
||||
|
@ -58,12 +57,15 @@ export function ServiceDetails({ tab }: Props) {
|
|||
<AlertIntegrations
|
||||
canReadAlerts={canReadAlerts}
|
||||
canSaveAlerts={canSaveAlerts}
|
||||
canReadAnomalies={canReadAnomalies}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
href={core.http.basePath.prepend('/app/home#/tutorial/apm')}
|
||||
href={plugin.core.http.basePath.prepend(
|
||||
'/app/home#/tutorial/apm'
|
||||
)}
|
||||
size="s"
|
||||
color="primary"
|
||||
iconType="plusInCircle"
|
||||
|
|
|
@ -15,14 +15,14 @@ import {
|
|||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
} from '../../../../common/environment_filter_values';
|
||||
import { useEnvironments, ALL_OPTION } from '../../../hooks/useEnvironments';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
|
||||
function updateEnvironmentUrl(
|
||||
location: ReturnType<typeof useLocation>,
|
||||
environment?: string
|
||||
) {
|
||||
const nextEnvironmentQueryParam =
|
||||
environment !== ENVIRONMENT_ALL ? environment : undefined;
|
||||
environment !== ENVIRONMENT_ALL.value ? environment : undefined;
|
||||
history.push({
|
||||
...location,
|
||||
search: fromQuery({
|
||||
|
@ -32,13 +32,6 @@ function updateEnvironmentUrl(
|
|||
});
|
||||
}
|
||||
|
||||
const NOT_DEFINED_OPTION = {
|
||||
value: ENVIRONMENT_NOT_DEFINED,
|
||||
text: i18n.translate('xpack.apm.filter.environment.notDefinedLabel', {
|
||||
defaultMessage: 'Not defined',
|
||||
}),
|
||||
};
|
||||
|
||||
const SEPARATOR_OPTION = {
|
||||
text: `- ${i18n.translate(
|
||||
'xpack.apm.filter.environment.selectEnvironmentLabel',
|
||||
|
@ -49,16 +42,16 @@ const SEPARATOR_OPTION = {
|
|||
|
||||
function getOptions(environments: string[]) {
|
||||
const environmentOptions = environments
|
||||
.filter((env) => env !== ENVIRONMENT_NOT_DEFINED)
|
||||
.filter((env) => env !== ENVIRONMENT_NOT_DEFINED.value)
|
||||
.map((environment) => ({
|
||||
value: environment,
|
||||
text: environment,
|
||||
}));
|
||||
|
||||
return [
|
||||
ALL_OPTION,
|
||||
...(environments.includes(ENVIRONMENT_NOT_DEFINED)
|
||||
? [NOT_DEFINED_OPTION]
|
||||
ENVIRONMENT_ALL,
|
||||
...(environments.includes(ENVIRONMENT_NOT_DEFINED.value)
|
||||
? [ENVIRONMENT_NOT_DEFINED]
|
||||
: []),
|
||||
...(environmentOptions.length > 0 ? [SEPARATOR_OPTION] : []),
|
||||
...environmentOptions,
|
||||
|
@ -83,7 +76,7 @@ export function EnvironmentFilter() {
|
|||
defaultMessage: 'environment',
|
||||
})}
|
||||
options={getOptions(environments)}
|
||||
value={environment || ENVIRONMENT_ALL}
|
||||
value={environment || ENVIRONMENT_ALL.value}
|
||||
onChange={(event) => {
|
||||
updateEnvironmentUrl(location, event.target.value);
|
||||
}}
|
||||
|
|
|
@ -12,8 +12,12 @@ import { ForLastExpression } from '../../../../../triggers_actions_ui/public';
|
|||
import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types';
|
||||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
|
||||
import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
|
||||
import { useEnvironments, ALL_OPTION } from '../../../hooks/useEnvironments';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
getEnvironmentLabel,
|
||||
} from '../../../../common/environment_filter_values';
|
||||
|
||||
export interface ErrorRateAlertTriggerParams {
|
||||
windowSize: number;
|
||||
|
@ -39,7 +43,7 @@ export function ErrorRateAlertTrigger(props: Props) {
|
|||
threshold: 25,
|
||||
windowSize: 1,
|
||||
windowUnit: 'm',
|
||||
environment: ALL_OPTION.value,
|
||||
environment: urlParams.environment || ENVIRONMENT_ALL.value,
|
||||
};
|
||||
|
||||
const params = {
|
||||
|
@ -51,11 +55,7 @@ export function ErrorRateAlertTrigger(props: Props) {
|
|||
|
||||
const fields = [
|
||||
<PopoverExpression
|
||||
value={
|
||||
params.environment === ALL_OPTION.value
|
||||
? ALL_OPTION.text
|
||||
: params.environment
|
||||
}
|
||||
value={getEnvironmentLabel(params.environment)}
|
||||
title={i18n.translate('xpack.apm.errorRateAlertTrigger.environment', {
|
||||
defaultMessage: 'Environment',
|
||||
})}
|
||||
|
|
|
@ -8,8 +8,8 @@ import React, { useState } from 'react';
|
|||
import { EuiExpression, EuiPopover } from '@elastic/eui';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
value: string;
|
||||
title: React.ReactNode;
|
||||
value: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,15 @@ import {
|
|||
ALERT_TYPES_CONFIG,
|
||||
TRANSACTION_ALERT_AGGREGATION_TYPES,
|
||||
} from '../../../../common/alert_types';
|
||||
import { ALL_OPTION, useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
|
||||
import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
getEnvironmentLabel,
|
||||
} from '../../../../common/environment_filter_values';
|
||||
|
||||
interface Params {
|
||||
windowSize: number;
|
||||
|
@ -54,7 +58,7 @@ export function TransactionDurationAlertTrigger(props: Props) {
|
|||
windowSize: 5,
|
||||
windowUnit: 'm',
|
||||
transactionType: transactionTypes[0],
|
||||
environment: ALL_OPTION.value,
|
||||
environment: urlParams.environment || ENVIRONMENT_ALL.value,
|
||||
};
|
||||
|
||||
const params = {
|
||||
|
@ -64,11 +68,7 @@ export function TransactionDurationAlertTrigger(props: Props) {
|
|||
|
||||
const fields = [
|
||||
<PopoverExpression
|
||||
value={
|
||||
params.environment === ALL_OPTION.value
|
||||
? ALL_OPTION.text
|
||||
: params.environment
|
||||
}
|
||||
value={getEnvironmentLabel(params.environment)}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionDurationAlertTrigger.environment',
|
||||
{
|
||||
|
|
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui';
|
||||
import { getSeverityColor } from '../../app/ServiceMap/cytoscapeOptions';
|
||||
import { useTheme } from '../../../hooks/useTheme';
|
||||
import { severity as Severity } from '../../app/ServiceMap/Popover/getSeverity';
|
||||
|
||||
type SeverityScore = 0 | 25 | 50 | 75;
|
||||
const ANOMALY_SCORES: SeverityScore[] = [0, 25, 50, 75];
|
||||
|
||||
const anomalyScoreSeverityMap: {
|
||||
[key in SeverityScore]: { label: string; severity: Severity };
|
||||
} = {
|
||||
0: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.warningLabel', {
|
||||
defaultMessage: 'warning',
|
||||
}),
|
||||
severity: Severity.warning,
|
||||
},
|
||||
25: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.minorLabel', {
|
||||
defaultMessage: 'minor',
|
||||
}),
|
||||
severity: Severity.minor,
|
||||
},
|
||||
50: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.majorLabel', {
|
||||
defaultMessage: 'major',
|
||||
}),
|
||||
severity: Severity.major,
|
||||
},
|
||||
75: {
|
||||
label: i18n.translate('xpack.apm.alerts.anomalySeverity.criticalLabel', {
|
||||
defaultMessage: 'critical',
|
||||
}),
|
||||
severity: Severity.critical,
|
||||
},
|
||||
};
|
||||
|
||||
export function AnomalySeverity({
|
||||
severityScore,
|
||||
}: {
|
||||
severityScore: SeverityScore;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const { label, severity } = anomalyScoreSeverityMap[severityScore];
|
||||
const defaultColor = theme.eui.euiColorMediumShade;
|
||||
const color = getSeverityColor(theme, severity) || defaultColor;
|
||||
return (
|
||||
<EuiHealth color={color} style={{ lineHeight: 'inherit' }}>
|
||||
{label}
|
||||
</EuiHealth>
|
||||
);
|
||||
}
|
||||
|
||||
const getOption = (value: SeverityScore) => {
|
||||
return {
|
||||
value: value.toString(10),
|
||||
inputDisplay: <AnomalySeverity severityScore={value} />,
|
||||
dropdownDisplay: (
|
||||
<>
|
||||
<AnomalySeverity severityScore={value} />
|
||||
<EuiSpacer size="xs" />
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p className="euiTextColor--subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.apm.alerts.anomalySeverity.scoreDetailsDescription"
|
||||
defaultMessage="score {value} and above"
|
||||
values={{ value }}
|
||||
/>
|
||||
</p>
|
||||
</EuiText>
|
||||
</>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
interface Props {
|
||||
onChange: (value: SeverityScore) => void;
|
||||
value: SeverityScore;
|
||||
}
|
||||
|
||||
export function SelectAnomalySeverity({ onChange, value }: Props) {
|
||||
const options = ANOMALY_SCORES.map((anomalyScore) => getOption(anomalyScore));
|
||||
|
||||
return (
|
||||
<EuiSuperSelect
|
||||
hasDividers
|
||||
style={{ width: 200 }}
|
||||
options={options}
|
||||
valueOfSelected={value.toString(10)}
|
||||
onChange={(selectedValue: string) => {
|
||||
const selectedAnomalyScore = parseInt(
|
||||
selectedValue,
|
||||
10
|
||||
) as SeverityScore;
|
||||
onChange(selectedAnomalyScore);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* 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 { EuiExpression, EuiSelect } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types';
|
||||
import { useEnvironments } from '../../../hooks/useEnvironments';
|
||||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
|
||||
import { useUrlParams } from '../../../hooks/useUrlParams';
|
||||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger';
|
||||
import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression';
|
||||
import {
|
||||
AnomalySeverity,
|
||||
SelectAnomalySeverity,
|
||||
} from './SelectAnomalySeverity';
|
||||
import {
|
||||
ENVIRONMENT_ALL,
|
||||
getEnvironmentLabel,
|
||||
} from '../../../../common/environment_filter_values';
|
||||
import {
|
||||
TRANSACTION_PAGE_LOAD,
|
||||
TRANSACTION_REQUEST,
|
||||
} from '../../../../common/transaction_types';
|
||||
|
||||
interface Params {
|
||||
windowSize: number;
|
||||
windowUnit: string;
|
||||
serviceName: string;
|
||||
transactionType: string;
|
||||
environment: string;
|
||||
anomalyScore: 0 | 25 | 50 | 75;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
alertParams: Params;
|
||||
setAlertParams: (key: string, value: any) => void;
|
||||
setAlertProperty: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
export function TransactionDurationAnomalyAlertTrigger(props: Props) {
|
||||
const { setAlertParams, alertParams, setAlertProperty } = props;
|
||||
const { urlParams } = useUrlParams();
|
||||
const transactionTypes = useServiceTransactionTypes(urlParams);
|
||||
const { serviceName, start, end } = urlParams;
|
||||
const { environmentOptions } = useEnvironments({ serviceName, start, end });
|
||||
const supportedTransactionTypes = transactionTypes.filter((transactionType) =>
|
||||
[TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST].includes(transactionType)
|
||||
);
|
||||
|
||||
if (!supportedTransactionTypes.length || !serviceName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaults: Params = {
|
||||
windowSize: 15,
|
||||
windowUnit: 'm',
|
||||
transactionType: supportedTransactionTypes[0], // 'page-load' for RUM, 'request' otherwise
|
||||
serviceName,
|
||||
environment: urlParams.environment || ENVIRONMENT_ALL.value,
|
||||
anomalyScore: 75,
|
||||
};
|
||||
|
||||
const params = {
|
||||
...defaults,
|
||||
...alertParams,
|
||||
};
|
||||
|
||||
const fields = [
|
||||
<EuiExpression
|
||||
description={i18n.translate(
|
||||
'xpack.apm.transactionDurationAnomalyAlertTrigger.service',
|
||||
{
|
||||
defaultMessage: 'Service',
|
||||
}
|
||||
)}
|
||||
value={serviceName}
|
||||
/>,
|
||||
<PopoverExpression
|
||||
value={getEnvironmentLabel(params.environment)}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionDurationAnomalyAlertTrigger.environment',
|
||||
{
|
||||
defaultMessage: 'Environment',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiSelect
|
||||
value={params.environment}
|
||||
options={environmentOptions}
|
||||
onChange={(e) => setAlertParams('environment', e.target.value)}
|
||||
compressed
|
||||
/>
|
||||
</PopoverExpression>,
|
||||
<PopoverExpression
|
||||
value={<AnomalySeverity severityScore={params.anomalyScore} />}
|
||||
title={i18n.translate(
|
||||
'xpack.apm.transactionDurationAnomalyAlertTrigger.anomalySeverity',
|
||||
{
|
||||
defaultMessage: 'Has anomaly with severity',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<SelectAnomalySeverity
|
||||
value={params.anomalyScore}
|
||||
onChange={(value) => {
|
||||
setAlertParams('anomalyScore', value);
|
||||
}}
|
||||
/>
|
||||
</PopoverExpression>,
|
||||
];
|
||||
|
||||
return (
|
||||
<ServiceAlertTrigger
|
||||
alertTypeName={
|
||||
ALERT_TYPES_CONFIG['apm.transaction_duration_anomaly'].name
|
||||
}
|
||||
fields={fields}
|
||||
defaults={defaults}
|
||||
setAlertParams={setAlertParams}
|
||||
setAlertProperty={setAlertProperty}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Default export is required for React.lazy loading
|
||||
//
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default TransactionDurationAnomalyAlertTrigger;
|
|
@ -5,30 +5,22 @@
|
|||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useFetcher } from './useFetcher';
|
||||
import {
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
ENVIRONMENT_ALL,
|
||||
ENVIRONMENT_NOT_DEFINED,
|
||||
} from '../../common/environment_filter_values';
|
||||
import { callApmApi } from '../services/rest/createCallApmApi';
|
||||
|
||||
export const ALL_OPTION = {
|
||||
value: ENVIRONMENT_ALL,
|
||||
text: i18n.translate('xpack.apm.environment.allLabel', {
|
||||
defaultMessage: 'All',
|
||||
}),
|
||||
};
|
||||
|
||||
function getEnvironmentOptions(environments: string[]) {
|
||||
const environmentOptions = environments
|
||||
.filter((env) => env !== ENVIRONMENT_NOT_DEFINED)
|
||||
.filter((env) => env !== ENVIRONMENT_NOT_DEFINED.value)
|
||||
.map((environment) => ({
|
||||
value: environment,
|
||||
text: environment,
|
||||
}));
|
||||
|
||||
return [ALL_OPTION, ...environmentOptions];
|
||||
return [ENVIRONMENT_ALL, ...environmentOptions];
|
||||
}
|
||||
|
||||
export function useEnvironments({
|
||||
|
|
|
@ -168,5 +168,21 @@ export class ApmPlugin implements Plugin<ApmPluginSetup, ApmPluginStart> {
|
|||
}),
|
||||
requiresAppContext: true,
|
||||
});
|
||||
|
||||
plugins.triggers_actions_ui.alertTypeRegistry.register({
|
||||
id: AlertType.TransactionDurationAnomaly,
|
||||
name: i18n.translate('xpack.apm.alertTypes.transactionDurationAnomaly', {
|
||||
defaultMessage: 'Transaction duration anomaly',
|
||||
}),
|
||||
iconClass: 'bell',
|
||||
alertParamsExpression: lazy(
|
||||
() =>
|
||||
import('./components/shared/TransactionDurationAnomalyAlertTrigger')
|
||||
),
|
||||
validate: () => ({
|
||||
errors: [],
|
||||
}),
|
||||
requiresAppContext: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,15 @@ import { Observable } from 'rxjs';
|
|||
import { AlertingPlugin } from '../../../../alerts/server';
|
||||
import { ActionsPlugin } from '../../../../actions/server';
|
||||
import { registerTransactionDurationAlertType } from './register_transaction_duration_alert_type';
|
||||
import { registerTransactionDurationAnomalyAlertType } from './register_transaction_duration_anomaly_alert_type';
|
||||
import { registerErrorRateAlertType } from './register_error_rate_alert_type';
|
||||
import { APMConfig } from '../..';
|
||||
import { MlPluginSetup } from '../../../../ml/server';
|
||||
|
||||
interface Params {
|
||||
alerts: AlertingPlugin['setup'];
|
||||
actions: ActionsPlugin['setup'];
|
||||
ml?: MlPluginSetup;
|
||||
config$: Observable<APMConfig>;
|
||||
}
|
||||
|
||||
|
@ -22,6 +25,11 @@ export function registerApmAlerts(params: Params) {
|
|||
alerts: params.alerts,
|
||||
config$: params.config$,
|
||||
});
|
||||
registerTransactionDurationAnomalyAlertType({
|
||||
alerts: params.alerts,
|
||||
ml: params.ml,
|
||||
config$: params.config$,
|
||||
});
|
||||
registerErrorRateAlertType({
|
||||
alerts: params.alerts,
|
||||
config$: params.config$,
|
||||
|
|
|
@ -75,7 +75,7 @@ export function registerErrorRateAlertType({
|
|||
});
|
||||
|
||||
const environmentTerm =
|
||||
alertParams.environment === ENVIRONMENT_ALL
|
||||
alertParams.environment === ENVIRONMENT_ALL.value
|
||||
? []
|
||||
: [{ term: { [SERVICE_ENVIRONMENT]: alertParams.environment } }];
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ export function registerTransactionDurationAlertType({
|
|||
});
|
||||
|
||||
const environmentTerm =
|
||||
alertParams.environment === ENVIRONMENT_ALL
|
||||
alertParams.environment === ENVIRONMENT_ALL.value
|
||||
? []
|
||||
: [{ term: { [SERVICE_ENVIRONMENT]: alertParams.environment } }];
|
||||
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* 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 { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { Observable } from 'rxjs';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KibanaRequest } from '../../../../../../src/core/server';
|
||||
import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types';
|
||||
import { AlertingPlugin } from '../../../../alerts/server';
|
||||
import { APMConfig } from '../..';
|
||||
import { MlPluginSetup } from '../../../../ml/server';
|
||||
import { getMLJobIds } from '../service_map/get_service_anomalies';
|
||||
|
||||
interface RegisterAlertParams {
|
||||
alerts: AlertingPlugin['setup'];
|
||||
ml?: MlPluginSetup;
|
||||
config$: Observable<APMConfig>;
|
||||
}
|
||||
|
||||
const paramsSchema = schema.object({
|
||||
serviceName: schema.string(),
|
||||
transactionType: schema.string(),
|
||||
windowSize: schema.number(),
|
||||
windowUnit: schema.string(),
|
||||
environment: schema.string(),
|
||||
anomalyScore: schema.number(),
|
||||
});
|
||||
|
||||
const alertTypeConfig =
|
||||
ALERT_TYPES_CONFIG[AlertType.TransactionDurationAnomaly];
|
||||
|
||||
export function registerTransactionDurationAnomalyAlertType({
|
||||
alerts,
|
||||
ml,
|
||||
config$,
|
||||
}: RegisterAlertParams) {
|
||||
alerts.registerType({
|
||||
id: AlertType.TransactionDurationAnomaly,
|
||||
name: alertTypeConfig.name,
|
||||
actionGroups: alertTypeConfig.actionGroups,
|
||||
defaultActionGroupId: alertTypeConfig.defaultActionGroupId,
|
||||
validate: {
|
||||
params: paramsSchema,
|
||||
},
|
||||
actionVariables: {
|
||||
context: [
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.apm.registerTransactionDurationAnomalyAlertType.variables.serviceName',
|
||||
{
|
||||
defaultMessage: 'Service name',
|
||||
}
|
||||
),
|
||||
name: 'serviceName',
|
||||
},
|
||||
{
|
||||
description: i18n.translate(
|
||||
'xpack.apm.registerTransactionDurationAnomalyAlertType.variables.transactionType',
|
||||
{
|
||||
defaultMessage: 'Transaction type',
|
||||
}
|
||||
),
|
||||
name: 'transactionType',
|
||||
},
|
||||
],
|
||||
},
|
||||
producer: 'apm',
|
||||
executor: async ({ services, params, state }) => {
|
||||
if (!ml) {
|
||||
return;
|
||||
}
|
||||
const alertParams = params as TypeOf<typeof paramsSchema>;
|
||||
const mlClient = services.getLegacyScopedClusterClient(ml.mlClient);
|
||||
const request = { params: 'DummyKibanaRequest' } as KibanaRequest;
|
||||
const { mlAnomalySearch } = ml.mlSystemProvider(mlClient, request);
|
||||
const anomalyDetectors = ml.anomalyDetectorsProvider(mlClient, request);
|
||||
|
||||
const mlJobIds = await getMLJobIds(
|
||||
anomalyDetectors,
|
||||
alertParams.environment
|
||||
);
|
||||
const anomalySearchParams = {
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{ term: { result_type: 'record' } },
|
||||
{ terms: { job_id: mlJobIds } },
|
||||
{
|
||||
range: {
|
||||
timestamp: {
|
||||
gte: `now-${alertParams.windowSize}${alertParams.windowUnit}`,
|
||||
format: 'epoch_millis',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
partition_field_value: alertParams.serviceName,
|
||||
},
|
||||
},
|
||||
{
|
||||
range: {
|
||||
record_score: {
|
||||
gte: alertParams.anomalyScore,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const response = ((await mlAnomalySearch(
|
||||
anomalySearchParams
|
||||
)) as unknown) as { hits: { total: { value: number } } };
|
||||
const hitCount = response.hits.total.value;
|
||||
|
||||
if (hitCount > 0) {
|
||||
const alertInstance = services.alertInstanceFactory(
|
||||
AlertType.TransactionDurationAnomaly
|
||||
);
|
||||
alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId, {
|
||||
serviceName: alertParams.serviceName,
|
||||
});
|
||||
}
|
||||
|
||||
return {};
|
||||
},
|
||||
});
|
||||
}
|
|
@ -22,7 +22,7 @@ export async function getAnomalyDetectionJobs(setup: Setup, logger: Logger) {
|
|||
throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE);
|
||||
}
|
||||
|
||||
const response = await getMlJobsWithAPMGroup(ml);
|
||||
const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors);
|
||||
return response.jobs
|
||||
.filter((job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2)
|
||||
.map((job) => {
|
||||
|
|
|
@ -4,14 +4,16 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Setup } from '../helpers/setup_request';
|
||||
import { MlPluginSetup } from '../../../../ml/server';
|
||||
import { APM_ML_JOB_GROUP } from './constants';
|
||||
|
||||
// returns ml jobs containing "apm" group
|
||||
// workaround: the ML api returns 404 when no jobs are found. This is handled so instead of throwing an empty response is returned
|
||||
export async function getMlJobsWithAPMGroup(ml: NonNullable<Setup['ml']>) {
|
||||
export async function getMlJobsWithAPMGroup(
|
||||
anomalyDetectors: ReturnType<MlPluginSetup['anomalyDetectorsProvider']>
|
||||
) {
|
||||
try {
|
||||
return await ml.anomalyDetectors.jobs(APM_ML_JOB_GROUP);
|
||||
return await anomalyDetectors.jobs(APM_ML_JOB_GROUP);
|
||||
} catch (e) {
|
||||
if (e.statusCode === 404) {
|
||||
return { count: 0, jobs: [] };
|
||||
|
|
|
@ -23,7 +23,7 @@ export async function hasLegacyJobs(setup: Setup) {
|
|||
throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE);
|
||||
}
|
||||
|
||||
const response = await getMlJobsWithAPMGroup(ml);
|
||||
const response = await getMlJobsWithAPMGroup(ml.anomalyDetectors);
|
||||
return response.jobs.some(
|
||||
(job) =>
|
||||
job.job_id.endsWith('high_mean_response_time') &&
|
||||
|
|
|
@ -48,7 +48,7 @@ export async function getAllEnvironments({
|
|||
terms: {
|
||||
field: SERVICE_ENVIRONMENT,
|
||||
size: 100,
|
||||
missing: includeMissing ? ENVIRONMENT_NOT_DEFINED : undefined,
|
||||
missing: includeMissing ? ENVIRONMENT_NOT_DEFINED.value : undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('getEnvironmentUiFilterES', () => {
|
|||
});
|
||||
|
||||
it('should create a filter for missing service environments', () => {
|
||||
const uiFilterES = getEnvironmentUiFilterES(ENVIRONMENT_NOT_DEFINED);
|
||||
const uiFilterES = getEnvironmentUiFilterES(ENVIRONMENT_NOT_DEFINED.value);
|
||||
expect(uiFilterES).toHaveLength(1);
|
||||
expect(uiFilterES[0]).toHaveProperty(
|
||||
['bool', 'must_not', 'exists', 'field'],
|
||||
|
|
|
@ -12,7 +12,7 @@ export function getEnvironmentUiFilterES(environment?: string): ESFilter[] {
|
|||
if (!environment) {
|
||||
return [];
|
||||
}
|
||||
if (environment === ENVIRONMENT_NOT_DEFINED) {
|
||||
if (environment === ENVIRONMENT_NOT_DEFINED.value) {
|
||||
return [{ bool: { must_not: { exists: { field: SERVICE_ENVIRONMENT } } } }];
|
||||
}
|
||||
return [{ term: { [SERVICE_ENVIRONMENT]: environment } }];
|
||||
|
|
|
@ -98,7 +98,11 @@ export async function setupRequest<TParams extends SetupRequestParams>(
|
|||
context,
|
||||
request,
|
||||
}),
|
||||
ml: getMlSetup(context, request),
|
||||
ml: getMlSetup(
|
||||
context.plugins.ml,
|
||||
context.core.savedObjects.client,
|
||||
request
|
||||
),
|
||||
config,
|
||||
};
|
||||
|
||||
|
@ -110,20 +114,21 @@ export async function setupRequest<TParams extends SetupRequestParams>(
|
|||
} as InferSetup<TParams>;
|
||||
}
|
||||
|
||||
function getMlSetup(context: APMRequestHandlerContext, request: KibanaRequest) {
|
||||
if (!context.plugins.ml) {
|
||||
function getMlSetup(
|
||||
ml: APMRequestHandlerContext['plugins']['ml'],
|
||||
savedObjectsClient: APMRequestHandlerContext['core']['savedObjects']['client'],
|
||||
request: KibanaRequest
|
||||
) {
|
||||
if (!ml) {
|
||||
return;
|
||||
}
|
||||
const ml = context.plugins.ml;
|
||||
const mlClient = ml.mlClient.asScoped(request);
|
||||
const mlSystem = ml.mlSystemProvider(mlClient, request);
|
||||
return {
|
||||
mlSystem: ml.mlSystemProvider(mlClient, request),
|
||||
anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request),
|
||||
modules: ml.modulesProvider(
|
||||
mlClient,
|
||||
request,
|
||||
context.core.savedObjects.client
|
||||
),
|
||||
mlClient,
|
||||
mlSystem,
|
||||
modules: ml.modulesProvider(mlClient, request, savedObjectsClient),
|
||||
anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request),
|
||||
mlAnomalySearch: mlSystem.mlAnomalySearch,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@ import {
|
|||
ML_ERRORS,
|
||||
} from '../../../common/anomaly_detection';
|
||||
import { getMlJobsWithAPMGroup } from '../anomaly_detection/get_ml_jobs_with_apm_group';
|
||||
import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values';
|
||||
import { MlPluginSetup } from '../../../../ml/server';
|
||||
|
||||
export const DEFAULT_ANOMALIES = { mlJobIds: [], serviceAnomalies: {} };
|
||||
|
||||
|
@ -43,7 +45,7 @@ export async function getServiceAnomalies({
|
|||
throw Boom.forbidden(ML_ERRORS.ML_NOT_AVAILABLE_IN_SPACE);
|
||||
}
|
||||
|
||||
const mlJobIds = await getMLJobIds(ml, environment);
|
||||
const mlJobIds = await getMLJobIds(ml.anomalyDetectors, environment);
|
||||
const params = {
|
||||
body: {
|
||||
size: 0,
|
||||
|
@ -136,16 +138,17 @@ function transformResponseToServiceAnomalies(
|
|||
}
|
||||
|
||||
export async function getMLJobIds(
|
||||
ml: Required<Setup>['ml'],
|
||||
anomalyDetectors: ReturnType<MlPluginSetup['anomalyDetectorsProvider']>,
|
||||
environment?: string
|
||||
) {
|
||||
const response = await getMlJobsWithAPMGroup(ml);
|
||||
const response = await getMlJobsWithAPMGroup(anomalyDetectors);
|
||||
|
||||
// to filter out legacy jobs we are filtering by the existence of `apm_ml_version` in `custom_settings`
|
||||
// and checking that it is compatable.
|
||||
const mlJobs = response.jobs.filter(
|
||||
(job) => (job.custom_settings?.job_tags?.apm_ml_version ?? 0) >= 2
|
||||
);
|
||||
if (environment) {
|
||||
if (environment && environment !== ENVIRONMENT_ALL.value) {
|
||||
const matchingMLJob = mlJobs.find(
|
||||
(job) => job.custom_settings?.job_tags?.environment === environment
|
||||
);
|
||||
|
|
|
@ -66,7 +66,10 @@ export async function getAnomalySeries({
|
|||
|
||||
let mlJobIds: string[] = [];
|
||||
try {
|
||||
mlJobIds = await getMLJobIds(setup.ml, uiFilters.environment);
|
||||
mlJobIds = await getMLJobIds(
|
||||
setup.ml.anomalyDetectors,
|
||||
uiFilters.environment
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return;
|
||||
|
|
|
@ -47,7 +47,7 @@ export async function getEnvironments(
|
|||
environments: {
|
||||
terms: {
|
||||
field: SERVICE_ENVIRONMENT,
|
||||
missing: ENVIRONMENT_NOT_DEFINED,
|
||||
missing: ENVIRONMENT_NOT_DEFINED.value,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -83,6 +83,7 @@ export class APMPlugin implements Plugin<APMPluginSetup> {
|
|||
registerApmAlerts({
|
||||
alerts: plugins.alerts,
|
||||
actions: plugins.actions,
|
||||
ml: plugins.ml,
|
||||
config$: mergedConfig$,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -23,7 +23,13 @@ export function getAnomalyDetectorsProvider({
|
|||
}: SharedServicesChecks): AnomalyDetectorsProvider {
|
||||
return {
|
||||
anomalyDetectorsProvider(mlClusterClient: ILegacyScopedClusterClient, request: KibanaRequest) {
|
||||
const hasMlCapabilities = getHasMlCapabilities(request);
|
||||
// APM is using this service in anomaly alert, kibana alerting doesn't provide request object
|
||||
// So we are adding a dummy request for now
|
||||
// TODO: Remove this once kibana alerting provides request object
|
||||
const hasMlCapabilities =
|
||||
request.params !== 'DummyKibanaRequest'
|
||||
? getHasMlCapabilities(request)
|
||||
: (_caps: string[]) => Promise.resolve();
|
||||
return {
|
||||
async jobs(jobId?: string) {
|
||||
isFullLicense();
|
||||
|
|
|
@ -4747,7 +4747,6 @@
|
|||
"xpack.apm.customLink.empty": "カスタムリンクが見つかりません。独自のカスタムリンク、たとえば特定のダッシュボードまたは外部リンクへのリンクをセットアップします。",
|
||||
"xpack.apm.emptyMessage.noDataFoundDescription": "別の時間範囲を試すか検索フィルターをリセットしてください。",
|
||||
"xpack.apm.emptyMessage.noDataFoundLabel": "データが見つかりません。",
|
||||
"xpack.apm.environment.allLabel": "すべて",
|
||||
"xpack.apm.error.prompt.body": "詳細はブラウザの開発者コンソールをご確認ください。",
|
||||
"xpack.apm.error.prompt.title": "申し訳ございませんが、エラーが発生しました :(",
|
||||
"xpack.apm.errorGroupDetails.avgLabel": "平均",
|
||||
|
@ -4785,6 +4784,7 @@
|
|||
"xpack.apm.fetcher.error.title": "リソースの取得中にエラーが発生しました",
|
||||
"xpack.apm.fetcher.error.url": "URL",
|
||||
"xpack.apm.filter.environment.label": "環境",
|
||||
"xpack.apm.filter.environment.allLabel": "すべて",
|
||||
"xpack.apm.filter.environment.notDefinedLabel": "未定義",
|
||||
"xpack.apm.filter.environment.selectEnvironmentLabel": "環境を選択",
|
||||
"xpack.apm.formatters.hoursTimeUnitLabel": "h",
|
||||
|
|
|
@ -4748,7 +4748,6 @@
|
|||
"xpack.apm.customLink.empty": "未找到定制链接。设置自己的定制链接,如特定仪表板的链接或外部链接。",
|
||||
"xpack.apm.emptyMessage.noDataFoundDescription": "尝试其他时间范围或重置搜索筛选。",
|
||||
"xpack.apm.emptyMessage.noDataFoundLabel": "未找到任何数据",
|
||||
"xpack.apm.environment.allLabel": "全部",
|
||||
"xpack.apm.error.prompt.body": "有关详情,请查看您的浏览器开发者控制台。",
|
||||
"xpack.apm.error.prompt.title": "抱歉,发生错误 :(",
|
||||
"xpack.apm.errorGroupDetails.avgLabel": "平均",
|
||||
|
@ -4786,6 +4785,7 @@
|
|||
"xpack.apm.fetcher.error.title": "提取资源时出错",
|
||||
"xpack.apm.fetcher.error.url": "URL",
|
||||
"xpack.apm.filter.environment.label": "环境",
|
||||
"xpack.apm.filter.environment.allLabel": "全部",
|
||||
"xpack.apm.filter.environment.notDefinedLabel": "未定义",
|
||||
"xpack.apm.filter.environment.selectEnvironmentLabel": "选择环境",
|
||||
"xpack.apm.formatters.hoursTimeUnitLabel": "h",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue