-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[APM] Transaction duration anomaly alerting integration #75719
Changes from 1 commit
ec08dca
717d296
ac4bde1
dd31e8b
94c3b48
c798812
d19427c
b1f3b25
936ae05
7ec5155
9d3a35a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* 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, { useState, useEffect } 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 { EuiTheme } from '../../../../../observability/public'; | ||
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, | ||
}, | ||
}; | ||
|
||
const getOption = (theme: EuiTheme, value: SeverityScore) => { | ||
const { label, severity } = anomalyScoreSeverityMap[value]; | ||
const defaultColor = theme.eui.euiColorMediumShade; | ||
const color = getSeverityColor(theme, severity) || defaultColor; | ||
return { | ||
value: value.toString(10), | ||
inputDisplay: ( | ||
<> | ||
<EuiHealth color={color} style={{ lineHeight: 'inherit' }}> | ||
{label} | ||
</EuiHealth> | ||
</> | ||
), | ||
dropdownDisplay: ( | ||
<> | ||
<EuiHealth color={color} style={{ lineHeight: 'inherit' }}> | ||
{label} | ||
</EuiHealth> | ||
<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 theme = useTheme(); | ||
const options = ANOMALY_SCORES.map((anomalyScore) => | ||
getOption(theme, anomalyScore) | ||
); | ||
const [anomalyScore, setAnomalyScore] = useState<SeverityScore>(value); | ||
|
||
useEffect(() => { | ||
setAnomalyScore(value); | ||
}, [value]); | ||
ogupte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return ( | ||
<EuiSuperSelect | ||
hasDividers | ||
style={{ width: 200 }} | ||
options={options} | ||
valueOfSelected={anomalyScore.toString(10)} | ||
onChange={(selectedValue: string) => { | ||
const selectedAnomalyScore = parseInt( | ||
selectedValue, | ||
10 | ||
) as SeverityScore; | ||
setAnomalyScore(selectedAnomalyScore); | ||
onChange(selectedAnomalyScore); | ||
}} | ||
/> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* 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 { EuiText, EuiSelect, EuiExpression } from '@elastic/eui'; | ||
import { i18n } from '@kbn/i18n'; | ||
import React from 'react'; | ||
import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; | ||
import { ALL_OPTION, useEnvironments } from '../../../hooks/useEnvironments'; | ||
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes'; | ||
import { useUrlParams } from '../../../hooks/useUrlParams'; | ||
import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; | ||
import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; | ||
import { SelectAnomalySeverity } from './SelectAnomalySeverity'; | ||
|
||
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 }); | ||
|
||
if (!transactionTypes.length || !serviceName) { | ||
return null; | ||
} | ||
|
||
const defaults: Params = { | ||
windowSize: 15, | ||
windowUnit: 'm', | ||
transactionType: transactionTypes[0], | ||
serviceName, | ||
environment: urlParams.environment || ALL_OPTION.value, | ||
anomalyScore: 75, | ||
}; | ||
|
||
const params = { | ||
...defaults, | ||
...alertParams, | ||
}; | ||
|
||
const fields = [ | ||
<EuiExpression | ||
description={i18n.translate( | ||
'xpack.apm.transactionDurationAnomalyAlertTrigger.service', | ||
{ | ||
defaultMessage: 'Service', | ||
} | ||
)} | ||
value={ | ||
<EuiText className="eui-displayInlineBlock"> | ||
<h5>{serviceName}</h5> | ||
</EuiText> | ||
} | ||
/>, | ||
<PopoverExpression | ||
value={ | ||
params.environment === ALL_OPTION.value | ||
? ALL_OPTION.text | ||
: params.environment | ||
} | ||
ogupte marked this conversation as resolved.
Show resolved
Hide resolved
|
||
title={i18n.translate( | ||
'xpack.apm.transactionDurationAnomalyAlertTrigger.environment', | ||
{ | ||
defaultMessage: 'Environment', | ||
} | ||
)} | ||
> | ||
<EuiSelect | ||
value={params.environment} | ||
options={environmentOptions} | ||
onChange={(e) => | ||
setAlertParams('environment', e.target.value as Params['environment']) | ||
} | ||
compressed | ||
/> | ||
</PopoverExpression>, | ||
<PopoverExpression | ||
value={params.transactionType} | ||
title={i18n.translate( | ||
'xpack.apm.transactionDurationAnomalyAlertTrigger.type', | ||
{ | ||
defaultMessage: 'Type', | ||
} | ||
)} | ||
> | ||
<EuiSelect | ||
value={params.transactionType} | ||
options={transactionTypes.map((key) => { | ||
return { | ||
text: key, | ||
value: key, | ||
}; | ||
})} | ||
onChange={(e) => | ||
setAlertParams( | ||
'transactionType', | ||
e.target.value as Params['transactionType'] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nice to avoid There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can probably safely avoid the type assertion. It's a holdover from |
||
) | ||
} | ||
compressed | ||
/> | ||
</PopoverExpression>, | ||
<PopoverExpression | ||
value={params.anomalyScore.toString(10)} | ||
title={i18n.translate( | ||
'xpack.apm.transactionDurationAnomalyAlertTrigger.anomalyScore', | ||
{ | ||
defaultMessage: 'Has anomaly score', | ||
} | ||
)} | ||
> | ||
<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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: looks like all three are booleans so there should be no reason to coerce with
!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the double negative because the logical and expression returns the type of the last operand (
capabilities.ml.canGetJobs
) which is:boolean | Readonly<{[x: string]: boolean;}>
. Without the double NOT operator,canReadAnomalies
fails the type assignment for thecanReadAnomalies
prop of theAlertIntegrations
component.