[8.8] fix(slo): remove good status codes from apm availability (#157497) (#157504)

# Backport

This will backport the following commits from `main` to `8.8`:
- [fix(slo): remove good status codes from apm availability
(#157497)](https://github.com/elastic/kibana/pull/157497)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Kevin
Delemme","email":"kevin.delemme@elastic.co"},"sourceCommit":{"committedDate":"2023-05-12T15:11:39Z","message":"fix(slo):
remove good status codes from apm availability
(#157497)","sha":"65a6f7435b13caf730721e61b12c4b735c11d7bd","branchLabelMapping":{"^v8.9.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:
Actionable
Observability","backport:prev-minor","v8.9.0"],"number":157497,"url":"https://github.com/elastic/kibana/pull/157497","mergeCommit":{"message":"fix(slo):
remove good status codes from apm availability
(#157497)","sha":"65a6f7435b13caf730721e61b12c4b735c11d7bd"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.9.0","labelRegex":"^v8.9.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/157497","number":157497,"mergeCommit":{"message":"fix(slo):
remove good status codes from apm availability
(#157497)","sha":"65a6f7435b13caf730721e61b12c4b735c11d7bd"}}]}]
BACKPORT-->

Co-authored-by: Kevin Delemme <kevin.delemme@elastic.co>
This commit is contained in:
Kibana Machine 2023-05-12 12:25:38 -04:00 committed by GitHub
parent aa7f4e40e5
commit e5132c79d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 21 additions and 202 deletions

View file

@ -39,9 +39,6 @@ const apmTransactionErrorRateIndicatorSchema = t.type({
index: t.string,
}),
t.partial({
goodStatusCodes: t.array(
t.union([t.literal('2xx'), t.literal('3xx'), t.literal('4xx'), t.literal('5xx')])
),
filter: t.string,
}),
]),

View file

@ -10,7 +10,7 @@ We currently support the following SLI:
- APM Transaction Duration, known as APM Latency
- Custom KQL
For the APM SLIs, customer can provide the service, environment, transaction name and type to configure them. For the **APM Latency** SLI, a threshold in milliseconds needs to be provided to discriminate the good and bad responses (events). For the **APM Availability** SLI, a list of good status codes needs to be provided to discriminate the good and bad responses (events). The API supports an optional kql filter to further filter the apm data.
For the APM SLIs, customer can provide the service, environment, transaction name and type to configure them. For the **APM Latency** SLI, a threshold in milliseconds needs to be provided to discriminate the good and bad responses (events). For the **APM Availability** SLI, we use the `event.outcome` as a way to discriminate the good and the bad responses(events). The API supports an optional kql filter to further filter the apm data.
The **custom KQL** SLI requires an index pattern, an optional filter query, a numerator query, and denominator query. A custom 'timestampField' can be provided to override the default @timestamp field.
@ -69,7 +69,6 @@ curl --request POST \
"service": "o11y-app",
"transactionType": "request",
"transactionName": "GET /api",
"goodStatusCodes": ["2xx", "3xx", "4xx"],
"index": "metrics-apm*"
}
},
@ -105,7 +104,6 @@ curl --request POST \
"service": "o11y-app",
"transactionType": "request",
"transactionName": "GET /api",
"goodStatusCodes": ["2xx", "3xx", "4xx"],
"index": "metrics-apm*"
}
},
@ -143,7 +141,6 @@ curl --request POST \
"service": "o11y-app",
"transactionType": "request",
"transactionName": "GET /api",
"goodStatusCodes": ["2xx", "3xx", "4xx"],
"index": "metrics-apm*"
}
},

View file

@ -424,15 +424,6 @@ components:
description: The APM transaction name or "*"
type: string
example: GET /my/api
goodStatusCodes:
description: The status codes considered as good events. Default to 2xx, 3xx and 4xx
type: array
items:
type: string
example:
- 2xx
- 3xx
- 4xx
filter:
description: KQL query used for filtering the data
type: string

View file

@ -32,15 +32,6 @@ properties:
description: The APM transaction name or "*"
type: string
example: GET /my/api
goodStatusCodes:
description: The status codes considered as good events. Default to 2xx, 3xx and 4xx
type: array
items:
type: string
example:
- "2xx"
- "3xx"
- "4xx"
filter:
description: KQL query used for filtering the data
type: string

View file

@ -17,7 +17,6 @@ export const buildApmAvailabilityIndicator = (
service: 'o11y-app',
transactionType: 'request',
transactionName: 'GET /flaky',
goodStatusCodes: ['2xx', '3xx', '4xx'],
index: 'metrics-apm*',
...params,
},

View file

@ -6,15 +6,8 @@
*/
import React, { useEffect } from 'react';
import {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiIconTip,
} from '@elastic/eui';
import { Controller, useFormContext } from 'react-hook-form';
import { EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import { useFormContext } from 'react-hook-form';
import { i18n } from '@kbn/i18n';
import type { CreateSLOInput } from '@kbn/slo-schema';
@ -23,7 +16,7 @@ import { FieldSelector } from '../apm_common/field_selector';
import { QueryBuilder } from '../common/query_builder';
export function ApmAvailabilityIndicatorTypeForm() {
const { control, setValue, watch, getFieldState } = useFormContext<CreateSLOInput>();
const { control, setValue, watch } = useFormContext<CreateSLOInput>();
const { data: apmIndex } = useFetchApmIndex();
useEffect(() => {
setValue('indicator.params.index', apmIndex);
@ -104,65 +97,6 @@ export function ApmAvailabilityIndicatorTypeForm() {
</EuiFlexGroup>
<EuiFlexGroup direction="row" gutterSize="l">
<EuiFlexItem>
<EuiFormRow
label={
<span>
{i18n.translate('xpack.observability.slo.sloEdit.apmAvailability.goodStatusCodes', {
defaultMessage: 'Good status codes',
})}{' '}
<EuiIconTip
content={i18n.translate(
'xpack.observability.slo.sloEdit.apmAvailability.goodStatusCodes.tooltip',
{
defaultMessage:
'Configure the HTTP status codes defining the "good" or "successful" requests for the SLO.',
}
)}
position="top"
/>
</span>
}
isInvalid={getFieldState('indicator.params.goodStatusCodes').invalid}
>
<Controller
shouldUnregister
name="indicator.params.goodStatusCodes"
control={control}
defaultValue={['2xx', '3xx', '4xx']}
rules={{ required: true }}
render={({ field: { ref, ...field }, fieldState }) => (
<EuiComboBox
{...field}
aria-label={i18n.translate(
'xpack.observability.slo.sloEdit.apmAvailability.goodStatusCodes.placeholder',
{
defaultMessage: 'Select the good status codes',
}
)}
placeholder={i18n.translate(
'xpack.observability.slo.sloEdit.apmAvailability.goodStatusCodes.placeholder',
{
defaultMessage: 'Select the good status codes',
}
)}
isInvalid={fieldState.invalid}
options={generateStatusCodeOptions(['2xx', '3xx', '4xx', '5xx'])}
selectedOptions={generateStatusCodeOptions(field.value)}
onChange={(selected: EuiComboBoxOptionOption[]) => {
if (selected.length) {
return field.onChange(selected.map((opts) => opts.value));
}
field.onChange([]);
}}
isClearable
data-test-subj="sloEditApmAvailabilityGoodStatusCodesSelector"
/>
)}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<QueryBuilder
control={control}
@ -193,11 +127,3 @@ export function ApmAvailabilityIndicatorTypeForm() {
</EuiFlexGroup>
);
}
function generateStatusCodeOptions(codes: string[] = []) {
return codes.map((code) => ({
label: code,
value: code,
'data-test-subj': `${code}Option`,
}));
}

View file

@ -154,7 +154,7 @@ export function SloEditFormDescriptionSection() {
}
}}
isClearable
data-test-subj="sloEditApmAvailabilityGoodStatusCodesSelector"
data-test-subj="sloEditTagsSelector"
/>
)}
/>

View file

@ -57,7 +57,7 @@ export function useSectionFormValidation({ getFieldState, getValues, formState,
'indicator.params.transactionName',
] as const
).every((field) => !getFieldState(field, formState).invalid && getValues(field) !== '') &&
(['indicator.params.index', 'indicator.params.goodStatusCodes'] as const).every(
(['indicator.params.index'] as const).every(
(field) => !getFieldState(field, formState).invalid
);
break;

View file

@ -34,7 +34,6 @@ export const createAPMTransactionErrorRateIndicator = (
service: 'irrelevant',
transactionName: 'irrelevant',
transactionType: 'irrelevant',
goodStatusCodes: ['2xx', '3xx', '4xx'],
index: 'metrics-apm*',
...params,
},

View file

@ -53,7 +53,6 @@ describe('GetSLO', () => {
service: 'irrelevant',
transactionName: 'irrelevant',
transactionType: 'irrelevant',
goodStatusCodes: ['2xx', '3xx', '4xx'],
index: 'metrics-apm*',
},
type: 'sli.apm.transactionErrorRate',

View file

@ -181,23 +181,11 @@ Object {
"slo.numerator": Object {
"filter": Object {
"bool": Object {
"should": Array [
Object {
"match": Object {
"transaction.result": "HTTP 2xx",
},
"should": Object {
"match": Object {
"event.outcome": "success",
},
Object {
"match": Object {
"transaction.result": "HTTP 3xx",
},
},
Object {
"match": Object {
"transaction.result": "HTTP 4xx",
},
},
],
},
},
},
},
@ -329,23 +317,11 @@ Object {
"slo.numerator": Object {
"filter": Object {
"bool": Object {
"should": Array [
Object {
"match": Object {
"transaction.result": "HTTP 2xx",
},
"should": Object {
"match": Object {
"event.outcome": "success",
},
Object {
"match": Object {
"transaction.result": "HTTP 3xx",
},
},
Object {
"match": Object {
"transaction.result": "HTTP 4xx",
},
},
],
},
},
},
},
@ -453,36 +429,3 @@ Object {
"transform_id": Any<String>,
}
`;
exports[`APM Transaction Error Rate Transform Generator uses default values when 'good_status_codes' is not specified 1`] = `
Object {
"slo.denominator": Object {
"value_count": Object {
"field": "transaction.duration.histogram",
},
},
"slo.numerator": Object {
"filter": Object {
"bool": Object {
"should": Array [
Object {
"match": Object {
"transaction.result": "HTTP 2xx",
},
},
Object {
"match": Object {
"transaction.result": "HTTP 3xx",
},
},
Object {
"match": Object {
"transaction.result": "HTTP 4xx",
},
},
],
},
},
},
}
`;

View file

@ -44,15 +44,6 @@ describe('APM Transaction Error Rate Transform Generator', () => {
});
});
it("uses default values when 'good_status_codes' is not specified", async () => {
const anSLO = createSLO({
indicator: createAPMTransactionErrorRateIndicator({ goodStatusCodes: [] }),
});
const transform = generator.getTransformParams(anSLO);
expect(transform.pivot?.aggregations).toMatchSnapshot();
});
it("does not include the query filter when params are '*'", async () => {
const anSLO = createSLO({
indicator: createAPMTransactionErrorRateIndicator({

View file

@ -24,9 +24,6 @@ import { APMTransactionErrorRateIndicator, SLO } from '../../../domain/models';
import { Query } from './types';
import { parseIndex } from './common';
const ALLOWED_STATUS_CODES = ['2xx', '3xx', '4xx', '5xx'];
const DEFAULT_GOOD_STATUS_CODES = ['2xx', '3xx', '4xx'];
export class ApmTransactionErrorRateTransformGenerator extends TransformGenerator {
public getTransformParams(slo: SLO): TransformPutTransformRequest {
if (!apmTransactionErrorRateIndicatorSchema.is(slo.indicator)) {
@ -39,7 +36,7 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
this.buildSource(slo, slo.indicator),
this.buildDestination(),
this.buildGroupBy(slo),
this.buildAggregations(slo, slo.indicator),
this.buildAggregations(slo),
this.buildSettings(slo)
);
}
@ -119,14 +116,16 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
};
}
private buildAggregations(slo: SLO, indicator: APMTransactionErrorRateIndicator) {
const goodStatusCodesFilter = this.getGoodStatusCodesFilter(indicator.params.goodStatusCodes);
private buildAggregations(slo: SLO) {
return {
'slo.numerator': {
filter: {
bool: {
should: goodStatusCodesFilter,
should: {
match: {
'event.outcome': 'success',
},
},
},
},
},
@ -148,17 +147,4 @@ export class ApmTransactionErrorRateTransformGenerator extends TransformGenerato
}),
};
}
private getGoodStatusCodesFilter(goodStatusCodes: string[] | undefined) {
let statusCodes = goodStatusCodes?.filter((code) => ALLOWED_STATUS_CODES.includes(code));
if (statusCodes === undefined || statusCodes.length === 0) {
statusCodes = DEFAULT_GOOD_STATUS_CODES;
}
return statusCodes.map((code) => ({
match: {
'transaction.result': `HTTP ${code}`,
},
}));
}
}