Skip to content

Commit

Permalink
[Custom threshold][Alert details page] Deprecate feature flag for Cus…
Browse files Browse the repository at this point in the history
…tom threshold rule alert details page (elastic#176666)

Closes elastic#153867

## Summary

This PR deprecates the following feature flag for the Custom threshold
rule alert details page.
```
xpack.observability.unsafe.alertDetails.observability.enabled
```


![image](https://github.com/elastic/kibana/assets/12370520/b0b7bfa4-5f53-48bc-a44c-2f999a3c53f6)


## 🧪 How to test
- Remove `xpack.observability.unsafe.alertDetails.observability.enabled`
from Kibana config
- Create a custom threshold rule
- Check the alert details page
    - From alert table's flyout
    - From action variables
- Add `xpack.observability.unsafe.alertDetails.observability.enabled` to
the Kibana config
- You should see the following warning
  ```
[WARN ][config.deprecation] You no longer need to configure
"xpack.observability.unsafe.alertDetails.observability.enabled".
  ```

## Release note

- Adds a dedicated alert details page for the Custom threshold rule.
When a threshold is breached, the alert details page helps users quickly
triage the alert, showing details and context around that alert such as
when the alert started, its current status, and other details. When a
log rate-based threshold is breached, the alert details page also
includes an automatic log rate analysis depicting shared characteristics
among log messages contributing to this change and in-context
Observability AI Assistant to provide further insight and remedy
suggestions.
  • Loading branch information
maryam-saeidi authored and CoenWarmer committed Feb 15, 2024
1 parent 92b15ae commit ee30caa
Show file tree
Hide file tree
Showing 20 changed files with 48 additions and 76 deletions.
2 changes: 2 additions & 0 deletions packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
*/

export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold';
export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate';

export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold';
export const LOG_THRESHOLD_ALERT_TYPE_ID = 'logs.alert.document.count';

export enum ApmRuleType {
ErrorCount = 'apm.error_rate', // ErrorRate was renamed to ErrorCount but the key is kept as `error_rate` for backwards-compat.
Expand Down
6 changes: 0 additions & 6 deletions x-pack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ xpack.observability.unsafe.alertDetails.uptime.enabled: true

**[For Uptime rule type]** In Kibana configuration, will allow the user to navigate to the new Alert Details page, instead of the Alert Flyout when clicking on `View alert details` in the Alert table

```yaml
xpack.observability.unsafe.alertDetails.observability.enabled: true
```

**[For Observability Threshold rule type]** In Kibana configuration, will allow the user to navigate to the new Alert Details page, instead of the Alert Flyout when clicking on `View alert details` in the Alert table

# Development

By default, Kibana will run with X-Pack installed as mentioned in the [contributing guide](../CONTRIBUTING.md).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,16 @@ export function ExperimentalBadge() {
/>
);
}

export function BetaBadge() {
return (
<EuiBetaBadge
label={i18n.translate('xpack.observability.betaBadgeLabel', {
defaultMessage: 'Beta',
})}
tooltipContent={i18n.translate('xpack.observability.betaBadgeDescription', {
defaultMessage: 'This functionality is in beta and is subject to change.',
})}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import moment from 'moment';
import React from 'react';
import {
EuiFlexGroup,
Expand All @@ -23,13 +24,13 @@ import {
ALERT_FLAPPING,
ALERT_RULE_CATEGORY,
ALERT_RULE_TYPE_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
TIMESTAMP,
} from '@kbn/rule-data-utils';
import moment from 'moment';
import { css } from '@emotion/react';
import { asDuration } from '../../../../common/utils/formatters';
import { TopAlert } from '../../../typings/alerts';
import { ExperimentalBadge } from '../../../components/experimental_badge';
import { BetaBadge, ExperimentalBadge } from '../../../components/experimental_badge';
import {
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
METRIC_THRESHOLD_ALERT_TYPE_ID,
Expand Down Expand Up @@ -60,11 +61,14 @@ export function PageTitle({ alert, alertStatus, dataTestSubj }: PageTitleProps)
alert.fields[ALERT_RULE_TYPE_ID] === METRIC_THRESHOLD_ALERT_TYPE_ID ||
alert.fields[ALERT_RULE_TYPE_ID] === METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID;

const showBetaBadge = alert.fields[ALERT_RULE_TYPE_ID] === OBSERVABILITY_THRESHOLD_RULE_TYPE_ID;

return (
<div data-test-subj={dataTestSubj}>
<EuiFlexGroup direction="row" alignItems="center" gutterSize="s">
{pageTitleContent(alert.fields[ALERT_RULE_CATEGORY])}
{showExperimentalBadge && <ExperimentalBadge />}
{showBetaBadge && <BetaBadge />}
</EuiFlexGroup>
<EuiSpacer size="l" />
<EuiFlexGroup direction="row" alignItems="center" gutterSize="xl">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ jest.spyOn(pluginContext, 'usePluginContext').mockImplementation(() => ({
apm: { enabled: false },
metrics: { enabled: false },
uptime: { enabled: false },
observability: { enabled: false },
},
},
aiAssistant: {
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/observability/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export interface ConfigSchema {
uptime: {
enabled: boolean;
};
observability: {
observability?: {
enabled: boolean;
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,26 @@
*/

import { ALERT_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import {
ApmRuleType,
LOG_THRESHOLD_ALERT_TYPE_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
SLO_BURN_RATE_RULE_TYPE_ID,
} from '@kbn/rule-data-utils';
import type { ConfigSchema } from '../plugin';
import type { TopAlert } from '../typings/alerts';

const ALLOWED_RULE_TYPES = [
'apm.transaction_duration',
'logs.alert.document.count',
'slo.rules.burnRate',
ApmRuleType.TransactionDuration,
LOG_THRESHOLD_ALERT_TYPE_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
SLO_BURN_RATE_RULE_TYPE_ID,
];

const isUnsafeAlertDetailsFlag = (
subject: string
): subject is keyof Omit<ConfigSchema['unsafe']['alertDetails'], 'logs'> =>
['uptime', 'metrics', 'observability'].includes(subject);
): subject is keyof Omit<ConfigSchema['unsafe']['alertDetails'], 'logs' | 'observability'> =>
['uptime', 'metrics'].includes(subject);

// We are mapping the ruleTypeId from the feature flag with the ruleTypeId from the alert
// to know whether the feature flag is enabled or not.
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/observability/public/utils/test_helper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const defaultConfig: ConfigSchema = {
alertDetails: {
metrics: { enabled: false },
uptime: { enabled: false },
observability: { enabled: false },
},
},
};
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/observability/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export const config: PluginConfigDescriptor = {
deprecations: ({ unused }) => [
unused('unsafe.thresholdRule.enabled', { level: 'warning' }),
unused('unsafe.alertDetails.logs.enabled', { level: 'warning' }),
unused('unsafe.alertDetails.observability.enabled', { level: 'warning' }),
],
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1400,7 +1400,7 @@ describe('The custom threshold alert type', () => {
await execute(true);
const recentAction = mostRecentAction(instanceID);
expect(recentAction.action).toEqual({
alertDetailsUrl: '',
alertDetailsUrl: 'http://localhost:5601/app/observability/alerts/mock-alert-uuid',
reason: 'Average test.metric.3 reported no data in the last 1m',
timestamp: STARTED_AT_MOCK_DATE.toISOString(),
value: ['[NO DATA]', null],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { RecoveredActionGroup } from '@kbn/alerting-plugin/common';
import { IBasePath, Logger } from '@kbn/core/server';
import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server';
import { getEvaluationValues, getThreshold } from './lib/get_values';
import { AlertsLocatorParams, getAlertUrl } from '../../../../common';
import { AlertsLocatorParams, getAlertDetailsUrl } from '../../../../common';
import { getViewInAppUrl } from '../../../../common/custom_threshold_rule/get_view_in_app_url';
import { ObservabilityConfig } from '../../..';
import { FIRED_ACTIONS_ID, NO_DATA_ACTIONS_ID, UNGROUPED_FACTORY_KEY } from './constants';
Expand Down Expand Up @@ -278,13 +278,7 @@ export const createCustomThresholdExecutor = ({
scheduledActionsCount++;

alert.scheduleActions(actionGroupId, {
alertDetailsUrl: await getAlertUrl(
alertUuid,
spaceId,
indexedStartedAt,
alertsLocator,
basePath.publicBaseUrl
),
alertDetailsUrl: getAlertDetailsUrl(basePath, spaceId, alertUuid),
group: groupByKeysObjectMapping[group],
reason,
timestamp,
Expand Down Expand Up @@ -327,13 +321,7 @@ export const createCustomThresholdExecutor = ({
const additionalContext = getContextForRecoveredAlerts(alertHits);

alert.setContext({
alertDetailsUrl: await getAlertUrl(
alertUuid,
spaceId,
indexedStartedAt,
alertsLocator,
basePath.publicBaseUrl
),
alertDetailsUrl: getAlertDetailsUrl(basePath, spaceId, alertUuid),
group,
timestamp: startedAt.toISOString(),
viewInAppUrl: getViewInAppUrl({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 2.0.
*/

import moment from 'moment';
import { omit } from 'lodash';
import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge';
import {
Expand Down Expand Up @@ -55,7 +54,6 @@ export default function ({ getService }: FtrProviderContext) {
let actionId: string;
let ruleId: string;
let alertId: string;
let startedAt: string;

before(async () => {
dataForgeConfig = {
Expand Down Expand Up @@ -181,7 +179,6 @@ export default function ({ getService }: FtrProviderContext) {
ruleId,
});
alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid'];
startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start'];

expect(resp.hits.hits[0]._source).property(
'kibana.alert.rule.category',
Expand Down Expand Up @@ -232,15 +229,14 @@ export default function ({ getService }: FtrProviderContext) {
});

it('should set correct action variables', async () => {
const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString();
const resp = await waitForDocumentInIndex<ActionDocument>({
esClient,
indexName: ALERT_ACTION_INDEX,
});

expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold');
expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql(
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
`https://localhost:5601/app/observability/alerts/${alertId}`
);
expect(resp.hits.hits[0]._source?.reason).eql(
`Average system.cpu.user.pct is 250%, above the threshold of 50%. (duration: 5 mins, data view: ${DATA_VIEW_NAME})`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { omit } from 'lodash';
import moment from 'moment';
import {
Aggregators,
Comparator,
Expand Down Expand Up @@ -41,7 +40,6 @@ export default function ({ getService }: FtrProviderContext) {
let actionId: string;
let ruleId: string;
let alertId: string;
let startedAt: string;

before(async () => {
await createDataView({
Expand Down Expand Up @@ -149,7 +147,6 @@ export default function ({ getService }: FtrProviderContext) {
ruleId,
});
alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid'];
startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start'];

expect(resp.hits.hits[0]._source).property(
'kibana.alert.rule.category',
Expand Down Expand Up @@ -200,15 +197,14 @@ export default function ({ getService }: FtrProviderContext) {
});

it('should set correct action variables', async () => {
const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString();
const resp = await waitForDocumentInIndex<ActionDocument>({
esClient,
indexName: ALERT_ACTION_INDEX,
});

expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold');
expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql(
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
`https://localhost:5601/app/observability/alerts/${alertId}`
);
expect(resp.hits.hits[0]._source?.reason).eql(
'Average system.cpu.user.pct reported no data in the last 5m'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export default function ({ getService }: FtrProviderContext) {
let actionId: string;
let ruleId: string;
let alertId: string;
let startedAt: string;

before(async () => {
synthtraceEsClient = await getSyntraceClient({ esClient, kibanaUrl });
Expand Down Expand Up @@ -159,7 +158,6 @@ export default function ({ getService }: FtrProviderContext) {
ruleId,
});
alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid'];
startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start'];

expect(resp.hits.hits[0]._source).property(
'kibana.alert.rule.category',
Expand Down Expand Up @@ -210,15 +208,14 @@ export default function ({ getService }: FtrProviderContext) {
});

it('should set correct action parameter: ruleType', async () => {
const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString();
const resp = await waitForDocumentInIndex<ActionDocument>({
esClient,
indexName: ALERT_ACTION_INDEX,
});

expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold');
expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql(
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
`https://localhost:5601/app/observability/alerts/${alertId}`
);
expect(resp.hits.hits[0]._source?.reason).eql(
`Average span.self_time.sum.us is 10,000,000, above the threshold of 7,500,000. (duration: 5 mins, data view: ${DATA_VIEW_NAME})`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* 2.0.
*/

import moment from 'moment';
import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge';
import {
Aggregators,
Expand Down Expand Up @@ -42,7 +41,6 @@ export default function ({ getService }: FtrProviderContext) {
let actionId: string;
let ruleId: string;
let alertId: string;
let startedAt: string;

before(async () => {
dataForgeConfig = {
Expand Down Expand Up @@ -178,7 +176,6 @@ export default function ({ getService }: FtrProviderContext) {
ruleId,
});
alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid'];
startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start'];

expect(resp.hits.hits[0]._source).property(
'kibana.alert.rule.category',
Expand Down Expand Up @@ -230,15 +227,14 @@ export default function ({ getService }: FtrProviderContext) {
});

it('should set correct action variables', async () => {
const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString();
const resp = await waitForDocumentInIndex<ActionDocument>({
esClient,
indexName: ALERT_ACTION_INDEX,
});

expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold');
expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql(
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
`https://localhost:5601/app/observability/alerts/${alertId}`
);
expect(resp.hits.hits[0]._source?.reason).eql(
`Custom equation is 1, above the threshold of 0.9. (duration: 1 min, data view: ${DATA_VIEW})`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/

import { omit } from 'lodash';
import moment from 'moment';
import expect from '@kbn/expect';
import { cleanup, generate, Dataset, PartialConfig } from '@kbn/data-forge';
import {
Expand Down Expand Up @@ -45,7 +44,6 @@ export default function ({ getService }: FtrProviderContext) {
let actionId: string;
let ruleId: string;
let alertId: string;
let startedAt: string;

before(async () => {
dataForgeConfig = {
Expand Down Expand Up @@ -178,7 +176,6 @@ export default function ({ getService }: FtrProviderContext) {
ruleId,
});
alertId = (resp.hits.hits[0]._source as any)['kibana.alert.uuid'];
startedAt = (resp.hits.hits[0]._source as any)['kibana.alert.start'];

expect(resp.hits.hits[0]._source).property(
'kibana.alert.rule.category',
Expand Down Expand Up @@ -231,15 +228,14 @@ export default function ({ getService }: FtrProviderContext) {
});

it('should set correct action variables', async () => {
const rangeFrom = moment(startedAt).subtract('5', 'minute').toISOString();
const resp = await waitForDocumentInIndex<ActionDocument>({
esClient,
indexName: ALERT_ACTION_INDEX,
});

expect(resp.hits.hits[0]._source?.ruleType).eql('observability.rules.custom_threshold');
expect(resp.hits.hits[0]._source?.alertDetailsUrl).eql(
`https://localhost:5601/app/observability/alerts?_a=(kuery:%27kibana.alert.uuid:%20%22${alertId}%22%27%2CrangeFrom:%27${rangeFrom}%27%2CrangeTo:now%2Cstatus:all)`
`https://localhost:5601/app/observability/alerts/${alertId}`
);

expect(resp.hits.hits[0]._source?.reason).eql(
Expand Down
Loading

0 comments on commit ee30caa

Please sign in to comment.