Skip to content
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

Add group-by to APM Latency threshold rule #153432

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
86a218d
add group by to latency rule
benakansara Mar 22, 2023
db62957
add tests
benakansara Mar 22, 2023
2eeeff5
fix tests
benakansara Mar 22, 2023
43da706
fix types
benakansara Mar 22, 2023
0be4679
refactoring
benakansara Mar 27, 2023
51cb7e0
fix types
benakansara Mar 27, 2023
2615db7
added tests, refactoring
benakansara Mar 29, 2023
873b094
Merge branch 'main' into feat/add-groupby-in-apm-latency-rule
benakansara Mar 29, 2023
8aa225c
refactoring
benakansara Apr 2, 2023
26341fb
adding groupby fields to the alert context
benakansara Apr 2, 2023
986884c
remove console log
benakansara Apr 4, 2023
eb9b8eb
add tests
benakansara Apr 4, 2023
90296c4
removing fields from top_hits as not required
benakansara Apr 4, 2023
27680ea
remove unused interface
benakansara Apr 4, 2023
fece6fa
revert test case
benakansara Apr 4, 2023
9ca5317
added integration tests
benakansara Apr 5, 2023
d07ee84
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Apr 5, 2023
e866c52
Merge branch 'main' into feat/add-groupby-in-apm-latency-rule
benakansara Apr 5, 2023
b20abe9
adding trasnaction name in group by, removing other fields
benakansara Apr 11, 2023
869f45a
change description, fix tests
benakansara Apr 12, 2023
596cb16
change description
benakansara Apr 12, 2023
408877a
change description
benakansara Apr 12, 2023
ee48316
Merge branch 'main' into feat/add-groupby-in-apm-latency-rule
benakansara Apr 12, 2023
9de9781
fix tests
benakansara Apr 12, 2023
d8d3546
Merge branch 'main' into feat/add-groupby-in-apm-latency-rule
benakansara Apr 12, 2023
92011cb
remove usage of i18n in tests
benakansara Apr 12, 2023
6b83cf4
fix tests
benakansara Apr 12, 2023
e4f2a6d
Merge branch 'main' into feat/add-groupby-in-apm-latency-rule
benakansara Apr 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions x-pack/plugins/apm/common/rules/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ export const transactionDurationParamsSchema = schema.object({
schema.literal(AggregationType.P99),
]),
environment: schema.string(),
groupBy: schema.maybe(
schema.oneOf([schema.string(), schema.arrayOf(schema.string())])
),
});

export const anomalyParamsSchema = schema.object({
Expand Down
45 changes: 45 additions & 0 deletions x-pack/plugins/apm/common/utils/get_groupby_terms.test.ts
Original file line number Diff line number Diff line change
@@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { getGroupByTerms } from './get_groupby_terms';

describe('get terms fields based on group-by', () => {
it('returns single terms field', () => {
const ruleParams = { groupBy: 'service.name' };
const terms = getGroupByTerms(ruleParams.groupBy);
expect(terms).toEqual([
{ field: 'service.name', missing: 'SERVICE_NAME_NOT_DEFINED' },
]);
});

it('returns multiple terms fields', () => {
const ruleParams = {
groupBy: [
'service.name',
'service.environment',
'transaction.type',
'transaction.name',
],
};
const terms = getGroupByTerms(ruleParams.groupBy);
expect(terms).toEqual([
{ field: 'service.name', missing: 'SERVICE_NAME_NOT_DEFINED' },
{
field: 'service.environment',
missing: 'SERVICE_ENVIRONMENT_NOT_DEFINED',
},
{ field: 'transaction.type', missing: 'TRANSACTION_TYPE_NOT_DEFINED' },
{ field: 'transaction.name', missing: 'TRANSACTION_NAME_NOT_DEFINED' },
]);
});

it('returns an empty array', () => {
const ruleParams = { groupBy: undefined };
const terms = getGroupByTerms(ruleParams.groupBy);
expect(terms).toEqual([]);
});
});
15 changes: 15 additions & 0 deletions x-pack/plugins/apm/common/utils/get_groupby_terms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export const getGroupByTerms = (groupBy: string[] | string | undefined) => {
return (groupBy ? [groupBy] : []).flat().map((group) => {
return {
field: group,
missing: group.replaceAll('.', '_').toUpperCase().concat('_NOT_DEFINED'),
};
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import { EuiSelect } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { defaults, map, omit } from 'lodash';
import React, { useEffect } from 'react';
import React, { useCallback, useEffect } from 'react';
import { CoreStart } from '@kbn/core/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import {
ForLastExpression,
TIME_UNITS,
} from '@kbn/triggers-actions-ui-plugin/public';
import { EuiFormRow } from '@elastic/eui';
import { EuiSpacer } from '@elastic/eui';
import { AggregationType } from '../../../../../common/rules/apm_rule_types';
import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values';
import { getDurationFormatter } from '../../../../../common/utils/formatters';
Expand All @@ -34,6 +36,7 @@ import {
import { AlertMetadata, getIntervalAndTimeRange } from '../../utils/helper';
import { ApmRuleParamsContainer } from '../../ui_components/apm_rule_params_container';
import { PopoverExpression } from '../../ui_components/popover_expression';
import { APMRuleGroupBy } from '../../ui_components/apm_rule_group_by';

export interface RuleParams {
aggregationType: AggregationType;
Expand All @@ -43,6 +46,7 @@ export interface RuleParams {
transactionType: string;
windowSize: number;
windowUnit: string;
groupBy?: string | string[] | undefined;
}

const TRANSACTION_ALERT_AGGREGATION_TYPES: Record<AggregationType, string> = {
Expand Down Expand Up @@ -142,6 +146,13 @@ export function TransactionDurationRuleType(props: Props) {
/>
);

const onGroupByChange = useCallback(
(group: string[] | string | null) => {
setRuleParams('groupBy', group && group.length ? group : '');
},
[setRuleParams]
);

const fields = [
<ServiceField
allowAll={false}
Expand Down Expand Up @@ -206,12 +217,38 @@ export function TransactionDurationRuleType(props: Props) {
/>,
];

const groupAlertsBy = (
<>
<EuiFormRow
label={i18n.translate('xpack.apm.ruleFlyout.createAlertPerText', {
defaultMessage: 'Group alerts by',
})}
helpText={i18n.translate(
'xpack.apm.ruleFlyout.createAlertPerHelpText',
{
defaultMessage:
'Create an alert for every unique value. For example: "transaction.name". By default, alert is created for every unique service.name, service.environment and transaction.type.',
}
)}
fullWidth
display="rowCompressed"
>
<APMRuleGroupBy
onChange={onGroupByChange}
options={{ groupBy: ruleParams.groupBy }}
/>
</EuiFormRow>
<EuiSpacer size="m" />
</>
);

return (
<ApmRuleParamsContainer
minimumWindowSize={{ value: 5, unit: TIME_UNITS.MINUTE }}
chartPreview={chartPreview}
defaultParams={params}
fields={fields}
groupAlertsBy={groupAlertsBy}
setRuleParams={setRuleParams}
setRuleProperty={setRuleProperty}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { EuiComboBox } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React, { useCallback } from 'react';
import {
SERVICE_ENVIRONMENT,
SERVICE_NAME,
TRANSACTION_NAME,
} from '../../../../common/es_fields/apm';
import { TRANSACTION_TYPE } from './alert_details_app_section/types';

interface Props {
options: { groupBy: string[] | string | undefined };
onChange: (groupBy: string | null | string[]) => void;
errorOptions?: string[];
}

const preSelectedFields: string[] = [
SERVICE_NAME,
SERVICE_ENVIRONMENT,
TRANSACTION_TYPE,
];

const fields: string[] = [TRANSACTION_NAME];

export function APMRuleGroupBy({ options, onChange, errorOptions }: Props) {
const handleChange = useCallback(
(selectedOptions: Array<{ label: string }>) => {
const groupByOption = selectedOptions.map((option) => option.label);
onChange(
groupByOption.filter((group) => !preSelectedFields.includes(group))
);
},
[onChange]
);

const selectedOptions = [
...preSelectedFields.map((field) => ({
label: field,
color: 'lightgray',
})),
...(Array.isArray(options.groupBy)
? options.groupBy.map((field) => ({
label: field,
color: errorOptions?.includes(field) ? 'danger' : undefined,
}))
: options.groupBy
? [
{
label: options.groupBy,
color: errorOptions?.includes(options.groupBy)
? 'danger'
: undefined,
},
]
: []),
];

return (
<EuiComboBox
data-test-subj="apmRule-groupBy"
placeholder={i18n.translate('xpack.apm.ruleFlyout.groupByLabel', {
defaultMessage: 'Everything',
})}
aria-label={i18n.translate('xpack.apm.ruleFlyout.groupByAriaLabel', {
defaultMessage: 'Graph per',
})}
fullWidth
singleSelection={false}
selectedOptions={selectedOptions}
options={fields.map((field) => ({ label: field }))}
onChange={handleChange}
isClearable={true}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ interface Props {
setRuleProperty: (key: string, value: any) => void;
defaultParams: Record<string, any>;
fields: React.ReactNode[];
groupAlertsBy?: React.ReactNode;
chartPreview?: React.ReactNode;
minimumWindowSize?: MinimumWindowSize;
}

export function ApmRuleParamsContainer(props: Props) {
const {
fields,
groupAlertsBy,
setRuleParams,
defaultParams,
chartPreview,
Expand Down Expand Up @@ -72,6 +74,7 @@ export function ApmRuleParamsContainer(props: Props) {

{chartPreview}
<EuiSpacer size="m" />
{groupAlertsBy}
</>
);
}
Expand Down
Loading