mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[AO] Add alertDetailsUrl to SLO rule type (#158920)
Closes #158662 ## Summary This PR adds alertDetailsUrl to the SLO burn rate rule type |Action|Result| |---|---| || #### Filtered alert <img src="7ac8f529
-d18e-4940-a8fe-3d148fa21d82" width="800"/> ## 🧪 How to test - Ensure that `server.publicBaseUrl` is configured in kibana.dev.yml - Create an SLO rule - Add an action to this rule and specify `context.alertDetailsUrl` variable there - After the alert is triggered, open the value of `alertDetailsUrl` in the browser - you should be able to see the related alert on the `Observability > Alerts` page
This commit is contained in:
parent
94fb44ae0c
commit
3b3a409021
5 changed files with 92 additions and 11 deletions
|
@ -8,14 +8,19 @@
|
|||
import { PluginSetupContract } from '@kbn/alerting-plugin/server';
|
||||
import { IBasePath, Logger } from '@kbn/core/server';
|
||||
import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { AlertsLocatorParams } from '../../../common';
|
||||
import { sloBurnRateRuleType } from './slo_burn_rate';
|
||||
|
||||
export function registerRuleTypes(
|
||||
alertingPlugin: PluginSetupContract,
|
||||
logger: Logger,
|
||||
ruleDataClient: IRuleDataClient,
|
||||
basePath: IBasePath
|
||||
basePath: IBasePath,
|
||||
alertsLocator?: LocatorPublic<AlertsLocatorParams>
|
||||
) {
|
||||
const createLifecycleRuleExecutor = createLifecycleExecutor(logger.get('rules'), ruleDataClient);
|
||||
alertingPlugin.registerType(sloBurnRateRuleType(createLifecycleRuleExecutor, basePath));
|
||||
alertingPlugin.registerType(
|
||||
sloBurnRateRuleType(createLifecycleRuleExecutor, basePath, alertsLocator)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ import {
|
|||
ALERT_EVALUATION_VALUE,
|
||||
ALERT_REASON,
|
||||
} from '@kbn/rule-data-utils';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import type { AlertsLocatorParams } from '../../../../common';
|
||||
import { getRuleExecutor } from './executor';
|
||||
import { createSLO } from '../../../services/slo/fixtures/slo';
|
||||
import { SLO, StoredSLO } from '../../../domain/models';
|
||||
|
@ -85,7 +87,15 @@ describe('BurnRateRuleExecutor', () => {
|
|||
let esClientMock: ElasticsearchClientMock;
|
||||
let soClientMock: jest.Mocked<SavedObjectsClientContract>;
|
||||
let loggerMock: jest.Mocked<MockedLogger>;
|
||||
const alertUuid = 'mockedAlertUuid';
|
||||
const basePathMock = { publicBaseUrl: 'https://kibana.dev' } as IBasePath;
|
||||
const alertsLocatorMock = {
|
||||
getLocation: jest.fn().mockImplementation(() => ({
|
||||
path: 'mockedAlertsLocator > getLocation',
|
||||
})),
|
||||
} as any as LocatorPublic<AlertsLocatorParams>;
|
||||
const ISO_DATE_REGEX =
|
||||
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)((-(\d{2}):(\d{2})|Z)?)$/;
|
||||
let alertWithLifecycleMock: jest.MockedFn<LifecycleAlertService>;
|
||||
let alertFactoryMock: jest.Mocked<
|
||||
PublicAlertFactory<BurnRateAlertState, BurnRateAlertContext, BurnRateAllowedActionGroups>
|
||||
|
@ -119,7 +129,7 @@ describe('BurnRateRuleExecutor', () => {
|
|||
shouldWriteAlerts: jest.fn(),
|
||||
shouldStopExecution: jest.fn(),
|
||||
getAlertStartedDate: jest.fn(),
|
||||
getAlertUuid: jest.fn(),
|
||||
getAlertUuid: jest.fn().mockImplementation(() => alertUuid),
|
||||
getAlertByAlertUuid: jest.fn(),
|
||||
share: {} as SharePluginStart,
|
||||
dataViews: dataViewPluginMocks.createStartContract(),
|
||||
|
@ -231,7 +241,10 @@ describe('BurnRateRuleExecutor', () => {
|
|||
alertWithLifecycleMock.mockImplementation(() => alertMock as any);
|
||||
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [] });
|
||||
|
||||
const executor = getRuleExecutor({ basePath: basePathMock });
|
||||
const executor = getRuleExecutor({
|
||||
basePath: basePathMock,
|
||||
alertsLocator: alertsLocatorMock,
|
||||
});
|
||||
await executor({
|
||||
params: someRuleParamsWithWindows({ sloId: slo.id }),
|
||||
startedAt: new Date(),
|
||||
|
@ -264,9 +277,16 @@ describe('BurnRateRuleExecutor', () => {
|
|||
burnRateThreshold: 2,
|
||||
reason:
|
||||
'CRITICAL: The burn rate for the past 1h is 2 and for the past 5m is 2. Alert when above 2 for both windows',
|
||||
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
|
||||
})
|
||||
);
|
||||
expect(alertMock.replaceState).toBeCalledWith({ alertState: AlertStates.ALERT });
|
||||
expect(alertsLocatorMock.getLocation).toBeCalledWith({
|
||||
baseUrl: 'https://kibana.dev',
|
||||
kuery: 'kibana.alert.uuid: "mockedAlertUuid"',
|
||||
rangeFrom: expect.stringMatching(ISO_DATE_REGEX),
|
||||
spaceId: 'irrelevant',
|
||||
});
|
||||
});
|
||||
|
||||
it('schedules an alert when both windows of second window definition burn rate have reached the threshold', async () => {
|
||||
|
@ -329,7 +349,10 @@ describe('BurnRateRuleExecutor', () => {
|
|||
};
|
||||
alertFactoryMock.done.mockReturnValueOnce({ getRecoveredAlerts: () => [alertMock] as any });
|
||||
|
||||
const executor = getRuleExecutor({ basePath: basePathMock });
|
||||
const executor = getRuleExecutor({
|
||||
basePath: basePathMock,
|
||||
alertsLocator: alertsLocatorMock,
|
||||
});
|
||||
|
||||
await executor({
|
||||
params: someRuleParamsWithWindows({ sloId: slo.id }),
|
||||
|
@ -350,8 +373,15 @@ describe('BurnRateRuleExecutor', () => {
|
|||
longWindow: { burnRate: 0.9, duration: '6h' },
|
||||
shortWindow: { burnRate: 0.9, duration: '30m' },
|
||||
burnRateThreshold: 1,
|
||||
alertDetailsUrl: 'mockedAlertsLocator > getLocation',
|
||||
})
|
||||
);
|
||||
expect(alertsLocatorMock.getLocation).toBeCalledWith({
|
||||
baseUrl: 'https://kibana.dev',
|
||||
kuery: 'kibana.alert.uuid: "mockedAlertUuid"',
|
||||
rangeFrom: expect.stringMatching(ISO_DATE_REGEX),
|
||||
spaceId: 'irrelevant',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,9 +15,11 @@ import {
|
|||
import { LifecycleRuleExecutor } from '@kbn/rule-registry-plugin/server';
|
||||
import { ExecutorType } from '@kbn/alerting-plugin/server';
|
||||
import { IBasePath } from '@kbn/core/server';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
|
||||
import { memoize, last, upperCase } from 'lodash';
|
||||
import { addSpaceIdToPath } from '@kbn/spaces-plugin/server';
|
||||
import { AlertsLocatorParams, getAlertUrl } from '../../../../common';
|
||||
import { SLO_ID_FIELD, SLO_REVISION_FIELD } from '../../../../common/field_names/infra_metrics';
|
||||
import { Duration, SLO, toDurationUnit } from '../../../domain/models';
|
||||
import { DefaultSLIClient, KibanaSavedObjectsSLORepository } from '../../../services/slo';
|
||||
|
@ -91,8 +93,10 @@ async function evaluate(slo: SLO, summaryClient: DefaultSLIClient, params: BurnR
|
|||
|
||||
export const getRuleExecutor = ({
|
||||
basePath,
|
||||
alertsLocator,
|
||||
}: {
|
||||
basePath: IBasePath;
|
||||
alertsLocator?: LocatorPublic<AlertsLocatorParams>;
|
||||
}): LifecycleRuleExecutor<
|
||||
BurnRateRuleParams,
|
||||
BurnRateRuleTypeState,
|
||||
|
@ -119,6 +123,8 @@ export const getRuleExecutor = ({
|
|||
savedObjectsClient: soClient,
|
||||
scopedClusterClient: esClient,
|
||||
alertFactory,
|
||||
getAlertStartedDate,
|
||||
getAlertUuid,
|
||||
} = services;
|
||||
|
||||
const sloRepository = new KibanaSavedObjectsSLORepository(soClient);
|
||||
|
@ -157,9 +163,21 @@ export const getRuleExecutor = ({
|
|||
windowDef
|
||||
);
|
||||
|
||||
const alertId = `alert-${slo.id}-${slo.revision}`;
|
||||
const indexedStartedAt = getAlertStartedDate(alertId) ?? startedAt.toISOString();
|
||||
const alertUuid = getAlertUuid(alertId);
|
||||
const alertDetailsUrl = await getAlertUrl(
|
||||
alertUuid,
|
||||
spaceId,
|
||||
indexedStartedAt,
|
||||
alertsLocator,
|
||||
basePath.publicBaseUrl
|
||||
);
|
||||
|
||||
const context = {
|
||||
longWindow: { burnRate: longWindowBurnRate, duration: longWindowDuration.format() },
|
||||
alertDetailsUrl,
|
||||
reason,
|
||||
longWindow: { burnRate: longWindowBurnRate, duration: longWindowDuration.format() },
|
||||
shortWindow: { burnRate: shortWindowBurnRate, duration: shortWindowDuration.format() },
|
||||
burnRateThreshold: windowDef.burnRateThreshold,
|
||||
timestamp: startedAt.toISOString(),
|
||||
|
@ -169,7 +187,7 @@ export const getRuleExecutor = ({
|
|||
};
|
||||
|
||||
const alert = alertWithLifecycle({
|
||||
id: `alert-${slo.id}-${slo.revision}`,
|
||||
id: alertId,
|
||||
fields: {
|
||||
[ALERT_REASON]: reason,
|
||||
[ALERT_EVALUATION_THRESHOLD]: windowDef.burnRateThreshold,
|
||||
|
@ -186,12 +204,23 @@ export const getRuleExecutor = ({
|
|||
const { getRecoveredAlerts } = alertFactory.done();
|
||||
const recoveredAlerts = getRecoveredAlerts();
|
||||
for (const recoveredAlert of recoveredAlerts) {
|
||||
const alertId = `alert-${slo.id}-${slo.revision}`;
|
||||
const indexedStartedAt = getAlertStartedDate(alertId) ?? startedAt.toISOString();
|
||||
const alertUuid = getAlertUuid(alertId);
|
||||
const alertDetailsUrl = await getAlertUrl(
|
||||
alertUuid,
|
||||
spaceId,
|
||||
indexedStartedAt,
|
||||
alertsLocator,
|
||||
basePath.publicBaseUrl
|
||||
);
|
||||
const context = {
|
||||
longWindow: { burnRate: longWindowBurnRate, duration: longWindowDuration.format() },
|
||||
shortWindow: { burnRate: shortWindowBurnRate, duration: shortWindowDuration.format() },
|
||||
burnRateThreshold: windowDef.burnRateThreshold,
|
||||
timestamp: startedAt.toISOString(),
|
||||
viewInAppUrl,
|
||||
alertDetailsUrl,
|
||||
sloId: slo.id,
|
||||
sloName: slo.name,
|
||||
};
|
||||
|
|
|
@ -11,7 +11,8 @@ import { LicenseType } from '@kbn/licensing-plugin/server';
|
|||
import { createLifecycleExecutor } from '@kbn/rule-registry-plugin/server';
|
||||
import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
|
||||
import { IBasePath } from '@kbn/core/server';
|
||||
import { sloFeatureId } from '../../../../common';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
import { AlertsLocatorParams, sloFeatureId } from '../../../../common';
|
||||
import { SLO_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
|
||||
|
||||
import {
|
||||
|
@ -43,7 +44,8 @@ type CreateLifecycleExecutor = ReturnType<typeof createLifecycleExecutor>;
|
|||
|
||||
export function sloBurnRateRuleType(
|
||||
createLifecycleRuleExecutor: CreateLifecycleExecutor,
|
||||
basePath: IBasePath
|
||||
basePath: IBasePath,
|
||||
alertsLocator?: LocatorPublic<AlertsLocatorParams>
|
||||
) {
|
||||
return {
|
||||
id: SLO_BURN_RATE_RULE_ID,
|
||||
|
@ -61,7 +63,7 @@ export function sloBurnRateRuleType(
|
|||
producer: sloFeatureId,
|
||||
minimumLicenseRequired: 'platinum' as LicenseType,
|
||||
isExportable: true,
|
||||
executor: createLifecycleRuleExecutor(getRuleExecutor({ basePath })),
|
||||
executor: createLifecycleRuleExecutor(getRuleExecutor({ basePath, alertsLocator })),
|
||||
doesSetRecoveryContext: true,
|
||||
actionVariables: {
|
||||
context: [
|
||||
|
@ -71,6 +73,7 @@ export function sloBurnRateRuleType(
|
|||
{ name: 'longWindow', description: windowActionVariableDescription },
|
||||
{ name: 'shortWindow', description: windowActionVariableDescription },
|
||||
{ name: 'viewInAppUrl', description: viewInAppUrlActionVariableDescription },
|
||||
{ name: 'alertDetailsUrl', description: alertDetailsUrlActionVariableDescription },
|
||||
{ name: 'sloId', description: sloIdActionVariableDescription },
|
||||
{ name: 'sloName', description: sloNameActionVariableDescription },
|
||||
],
|
||||
|
@ -118,6 +121,14 @@ export const viewInAppUrlActionVariableDescription = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const alertDetailsUrlActionVariableDescription = i18n.translate(
|
||||
'xpack.observability.slo.alerting.alertDetailsUrlDescription',
|
||||
{
|
||||
defaultMessage:
|
||||
'Link to the alert troubleshooting view for further context and details. This will be an empty string if the server.publicBaseUrl is not configured.',
|
||||
}
|
||||
);
|
||||
|
||||
export const sloIdActionVariableDescription = i18n.translate(
|
||||
'xpack.observability.slo.alerting.sloIdDescription',
|
||||
{
|
||||
|
|
|
@ -244,7 +244,13 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
|
|||
},
|
||||
],
|
||||
});
|
||||
registerRuleTypes(plugins.alerting, this.logger, ruleDataClient, core.http.basePath);
|
||||
registerRuleTypes(
|
||||
plugins.alerting,
|
||||
this.logger,
|
||||
ruleDataClient,
|
||||
core.http.basePath,
|
||||
alertsLocator
|
||||
);
|
||||
registerSloUsageCollector(plugins.usageCollection);
|
||||
|
||||
core.getStartServices().then(([coreStart, pluginStart]) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue