New slo plugin (#177937)

Fixes https://github.com/elastic/kibana/issues/176420

## 🍒 Summary
This PR copies the SLO code that was inside the Observability app into
its own app under `observability-solution/slo` folder.


4f6b8dfb-9612-4d30-ad50-4ee5c55a9c32

## ✔️  Acceptance criteria
- URL of new app: `app/slos`
- Design and functionality are not changed. 
- Git history has been retained for all files in
`x-pack/plugins/observability_solution/slo`.
- SLO should appear on server less
- SLO code inside `observability_solution/observability` code has been
removed. A new clean up round might be needed though for possible
leftovers.
- Burn rate rule is registered within the new slo app
- SLO embeddables are moved inside the new slo app
  - overview
  - alerts embeddable
  - error budget burn down
- Alerts table configuration registration for slo details page and
alerts table embeddable is still done in the observability app. Response
Ops team is working on removing the need to register the alert table
anyway
- Slo app is wrapped into `ApplicationUsageTrackingProvider` which will
send slo `Application usage` information tracked by the `slo` appId
- Redirect old `app/observability/slos` route to `app/slos`
- Rename old `xpack.observability.slo` keys to `xpack.slo` in the
translation files


## 🌮 How to test
Design and functionality didn't change, so simply navigate to existing
slo pages and try to break it
- Slo list page
  - group by
  - unified search
  - toggle buttons
  - actions
- Slo creation
  - try group by as well 
- Slo detail page
  - Actions on top 
  - navigate to overview and alerts tabs
- Create SLO flyout in Logs Explorer
- Create burn rate rules and verify they appear on rules page
- Verify SLO alerts appear on Alerts page and slo details page
- Embeddables
  - Through the dashboard app
- Using the attach to dashboard action on the slo card item on slo list
page and the error budget burn down chart on the slo detail page
- SLOs only for platinum users
- Permissions
- Spaces


## TODO

- [x] Move slo stuff from observability folder to new slo plugin
- [x] Remove old slo stuff from observability folder
- [x] Update references 
- [x] Fix typescript and eslint errors
- [x] Paths
- [x] Locators
- [x] Burn rate rule registration
- [x] Embeddable Alerts table configuration registration
- [x] Embeddables
- [x] Translations
- [x] Verify plugin.ts files contain all registration logic
  - [x] public
  - [x] server
- [x] Final cleanup for observability folder
- [x] Run tests
- [x] Application Usage (Telemetry)
- [x] Permissions

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: shahzad31 <shahzad31comp@gmail.com>
Co-authored-by: Coen Warmer <coen.warmer@gmail.com>
This commit is contained in:
Panagiota Mitsopoulou 2024-03-19 11:17:34 +01:00 committed by GitHub
parent e90d7fe6ef
commit d5dfee7146
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
589 changed files with 5174 additions and 3220 deletions

View file

@ -875,6 +875,7 @@ module.exports = {
'x-pack/plugins/observability_solution/observability/**/*.{js,mjs,ts,tsx}',
'x-pack/plugins/observability_solution/exploratory_view/**/*.{js,mjs,ts,tsx}',
'x-pack/plugins/observability_solution/ux/**/*.{js,mjs,ts,tsx}',
'x-pack/plugins/observability_solution/slo/**/*.{js,mjs,ts,tsx}',
],
rules: {
'no-console': ['warn', { allow: ['error'] }],
@ -897,6 +898,7 @@ module.exports = {
'x-pack/plugins/observability_solution/apm/**/*.stories.*',
'x-pack/plugins/observability_solution/observability/**/*.stories.*',
'x-pack/plugins/observability_solution/exploratory_view/**/*.stories.*',
'x-pack/plugins/observability_solution/slo/**/*.stories.*',
],
rules: {
'react/function-component-definition': [

1
.github/CODEOWNERS vendored
View file

@ -783,6 +783,7 @@ packages/shared-ux/router/types @elastic/appex-sharedux
packages/shared-ux/storybook/config @elastic/appex-sharedux
packages/shared-ux/storybook/mock @elastic/appex-sharedux
packages/kbn-shared-ux-utility @elastic/appex-sharedux
x-pack/plugins/observability_solution/slo @elastic/obs-ux-management-team
x-pack/packages/kbn-slo-schema @elastic/obs-ux-management-team
x-pack/plugins/snapshot_restore @elastic/kibana-management
packages/solution-nav/es @elastic/appex-sharedux

View file

@ -7,11 +7,14 @@ xpack.infra.enabled: true
xpack.uptime.enabled: true
xpack.securitySolution.enabled: false
## Enable the slo plugin
xpack.slo.enabled: true
## Cloud settings
xpack.cloud.serverless.project_type: observability
## Enable the Serverless Observability plugin
xpack.serverless.observability.enabled: true
xpack.serverless.observability.enabled: true
## Configure plugins

View file

@ -794,6 +794,10 @@ It leverages universal configuration and other APIs in the serverless plugin to
|Session View is meant to provide a visualization into what is going on in a particular Linux environment where the agent is running. It looks likes a terminal emulator; however, it is a tool for introspecting process activity and understanding user and service behaviour in your Linux servers and infrastructure. It is a time-ordered series of process executions displayed in a tree over time.
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/slo/README.md[slo]
|A Kibana plugin
|{kib-repo}blob/{branch}/x-pack/plugins/snapshot_restore/README.md[snapshotRestore]
|or

View file

@ -785,6 +785,7 @@
"@kbn/shared-ux-storybook-config": "link:packages/shared-ux/storybook/config",
"@kbn/shared-ux-storybook-mock": "link:packages/shared-ux/storybook/mock",
"@kbn/shared-ux-utility": "link:packages/kbn-shared-ux-utility",
"@kbn/slo-plugin": "link:x-pack/plugins/observability_solution/slo",
"@kbn/slo-schema": "link:x-pack/packages/kbn-slo-schema",
"@kbn/snapshot-restore-plugin": "link:x-pack/plugins/snapshot_restore",
"@kbn/solution-nav-es": "link:packages/solution-nav/es",

View file

@ -19,3 +19,5 @@ export const APM_APP_ID = 'apm';
export const SYNTHETICS_APP_ID = 'synthetics';
export const OBSERVABILITY_ONBOARDING_APP_ID = 'observabilityOnboarding';
export const SLO_APP_ID = 'slo';

View file

@ -14,6 +14,7 @@ import {
OBSERVABILITY_ONBOARDING_APP_ID,
OBSERVABILITY_OVERVIEW_APP_ID,
SYNTHETICS_APP_ID,
SLO_APP_ID,
} from './constants';
type LogsApp = typeof LOGS_APP_ID;
@ -23,6 +24,7 @@ type MetricsApp = typeof METRICS_APP_ID;
type ApmApp = typeof APM_APP_ID;
type SyntheticsApp = typeof SYNTHETICS_APP_ID;
type ObservabilityOnboardingApp = typeof OBSERVABILITY_ONBOARDING_APP_ID;
type SLO_APP = typeof SLO_APP_ID;
export type AppId =
| LogsApp
@ -31,7 +33,8 @@ export type AppId =
| ObservabilityOnboardingApp
| ApmApp
| MetricsApp
| SyntheticsApp;
| SyntheticsApp
| SLO_APP;
export type LogsLinkId = 'log-categories' | 'settings' | 'anomalies' | 'stream';
@ -40,8 +43,7 @@ export type ObservabilityOverviewLinkId =
| 'cases'
| 'cases_configure'
| 'cases_create'
| 'rules'
| 'slos';
| 'rules';
export type MetricsLinkId =
| 'inventory'
@ -61,12 +63,15 @@ export type ApmLinkId =
export type SyntheticsLinkId = 'certificates' | 'overview';
export type SLOLinkId = 'slo';
export type LinkId =
| LogsLinkId
| ObservabilityOverviewLinkId
| MetricsLinkId
| ApmLinkId
| SyntheticsLinkId;
| SyntheticsLinkId
| SLOLinkId;
export type DeepLinkId =
| AppId

View file

@ -14,7 +14,7 @@ module.exports = {
USES_STYLED_COMPONENTS: [
/packages[\/\\]kbn-ui-shared-deps-(npm|src)[\/\\]/,
/src[\/\\]plugins[\/\\](kibana_react)[\/\\]/,
/x-pack[\/\\]plugins[\/\\](observability_solution\/apm|beats_management|cases|fleet|observability_solution\/infra|lists|observability_solution\/observability|observability_solution\/observability_shared|observability_solution\/exploratory_view|security_solution|timelines|observability_solution\/synthetics|observability_solution\/ux|observability_solution\/uptime)[\/\\]/,
/x-pack[\/\\]plugins[\/\\](observability_solution\/apm|beats_management|cases|fleet|observability_solution\/infra|lists|observability_solution\/observability|observability_solution\/observability_shared|observability_solution\/exploratory_view|observability_solution\/slo|security_solution|timelines|observability_solution\/synthetics|observability_solution\/ux|observability_solution\/uptime)[\/\\]/,
/x-pack[\/\\]test[\/\\]plugin_functional[\/\\]plugins[\/\\]resolver_test[\/\\]/,
/x-pack[\/\\]packages[\/\\]elastic_assistant[\/\\]/,
/x-pack[\/\\]packages[\/\\]security-solution[\/\\]ecs_data_quality_dashboard[\/\\]/,

View file

@ -103,7 +103,7 @@ pageLoadAssetSize:
navigation: 37269
newsfeed: 42228
noDataPage: 5000
observability: 115443
observability: 167673
observabilityAIAssistant: 58230
observabilityAIAssistantApp: 27680
observabilityLogsExplorer: 46650
@ -136,6 +136,7 @@ pageLoadAssetSize:
serverlessSearch: 72995
sessionView: 77750
share: 71239
slo: 37039
snapshotRestore: 79032
spaces: 57868
stackAlerts: 58316

View file

@ -57,6 +57,7 @@ export const storybookAliases = {
security_solution_packages: 'x-pack/packages/security-solution/storybook/config',
serverless: 'packages/serverless/storybook/config',
shared_ux: 'packages/shared-ux/storybook/config',
slo: 'x-pack/plugins/observability_solution/slo/.storybook',
threat_intelligence: 'x-pack/plugins/threat_intelligence/.storybook',
triggers_actions_ui: 'x-pack/plugins/triggers_actions_ui/.storybook',
ui_actions_enhanced: 'src/plugins/ui_actions_enhanced/.storybook',

View file

@ -162,6 +162,7 @@ export const applicationUsageSchema = {
'exploratory-view': commonSchema,
osquery: commonSchema,
profiling: commonSchema,
slo: commonSchema,
security_account: commonSchema,
reportingRedirect: commonSchema,
security_access_agreement: commonSchema,

View file

@ -5635,6 +5635,137 @@
}
}
},
"slo": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "Always `main`"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen over the last 90 days"
}
},
"views": {
"type": "array",
"items": {
"properties": {
"appId": {
"type": "keyword",
"_meta": {
"description": "The application being tracked"
}
},
"viewId": {
"type": "keyword",
"_meta": {
"description": "The application view being tracked"
}
},
"clicks_total": {
"type": "long",
"_meta": {
"description": "General number of clicks in the application sub view since we started counting them"
}
},
"clicks_7_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 7 days"
}
},
"clicks_30_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 30 days"
}
},
"clicks_90_days": {
"type": "long",
"_meta": {
"description": "General number of clicks in the active application sub view over the last 90 days"
}
},
"minutes_on_screen_total": {
"type": "float",
"_meta": {
"description": "Minutes the application sub view is active and on-screen since we started counting them."
}
},
"minutes_on_screen_7_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
}
},
"minutes_on_screen_30_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
}
},
"minutes_on_screen_90_days": {
"type": "float",
"_meta": {
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
}
}
}
}
}
}
},
"security_account": {
"properties": {
"appId": {

View file

@ -1560,6 +1560,8 @@
"@kbn/shared-ux-storybook-mock/*": ["packages/shared-ux/storybook/mock/*"],
"@kbn/shared-ux-utility": ["packages/kbn-shared-ux-utility"],
"@kbn/shared-ux-utility/*": ["packages/kbn-shared-ux-utility/*"],
"@kbn/slo-plugin": ["x-pack/plugins/observability_solution/slo"],
"@kbn/slo-plugin/*": ["x-pack/plugins/observability_solution/slo/*"],
"@kbn/slo-schema": ["x-pack/packages/kbn-slo-schema"],
"@kbn/slo-schema/*": ["x-pack/packages/kbn-slo-schema/*"],
"@kbn/snapshot-restore-plugin": ["x-pack/plugins/snapshot_restore"],

View file

@ -115,6 +115,7 @@
"xpack.securitySolutionEss": "plugins/security_solution_ess",
"xpack.securitySolutionServerless": "plugins/security_solution_serverless",
"xpack.sessionView": "plugins/session_view",
"xpack.slo": "plugins/observability_solution/slo",
"xpack.snapshotRestore": "plugins/snapshot_restore",
"xpack.spaces": "plugins/spaces",
"xpack.savedObjectsTagging": [

View file

@ -5,49 +5,14 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import { AlertConsumers } from '@kbn/rule-data-utils';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import type { RuleCreationValidConsumer } from '@kbn/triggers-actions-ui-plugin/public';
export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate';
export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g;
export const ALERT_STATUS_ALL = 'all';
export const ALERTS_URL_STORAGE_KEY = '_a';
export const ALERT_ACTION_ID = 'slo.burnRate.alert';
export const ALERT_ACTION = {
id: ALERT_ACTION_ID,
name: i18n.translate('xpack.observability.slo.alerting.burnRate.alertAction', {
defaultMessage: 'Critical',
}),
};
export const HIGH_PRIORITY_ACTION_ID = 'slo.burnRate.high';
export const HIGH_PRIORITY_ACTION = {
id: HIGH_PRIORITY_ACTION_ID,
name: i18n.translate('xpack.observability.slo.alerting.burnRate.highPriorityAction', {
defaultMessage: 'High',
}),
};
export const MEDIUM_PRIORITY_ACTION_ID = 'slo.burnRate.medium';
export const MEDIUM_PRIORITY_ACTION = {
id: MEDIUM_PRIORITY_ACTION_ID,
name: i18n.translate('xpack.observability.slo.alerting.burnRate.mediumPriorityAction', {
defaultMessage: 'Medium',
}),
};
export const LOW_PRIORITY_ACTION_ID = 'slo.burnRate.low';
export const LOW_PRIORITY_ACTION = {
id: LOW_PRIORITY_ACTION_ID,
name: i18n.translate('xpack.observability.slo.alerting.burnRate.lowPriorityAction', {
defaultMessage: 'Low',
}),
};
export const observabilityAlertFeatureIds: ValidFeatureId[] = [
AlertConsumers.APM,
AlertConsumers.INFRASTRUCTURE,

View file

@ -60,9 +60,8 @@ export {
getProbabilityFromProgressiveLoadingQuality,
} from './progressive_loading';
export const sloFeatureId = 'slo';
export const casesFeatureId = 'observabilityCases';
export const sloFeatureId = 'slo';
// The ID of the observability app. Should more appropriately be called
// 'observability' but it's used in telemetry by applicationUsage so we don't
// want to change it.
@ -87,3 +86,5 @@ export const sloListLocatorID = 'SLO_LIST_LOCATOR';
import { paths } from './locators/paths';
export const observabilityPaths = paths.observability;
export type { AlertsLocatorParams } from './locators/alerts';
export { AlertsLocatorDefinition } from './locators/alerts';
export { observabilityAlertFeatureIds } from './constants';

View file

@ -15,14 +15,17 @@ export const EXPLORATORY_VIEW_PATH = '/exploratory-view' as const; // has been m
export const RULES_PATH = '/alerts/rules' as const;
export const RULES_LOGS_PATH = '/alerts/rules/logs' as const;
export const RULE_DETAIL_PATH = '/alerts/rules/:ruleId' as const;
export const SLOS_PATH = '/slos' as const;
export const SLOS_WELCOME_PATH = '/slos/welcome' as const;
export const SLO_DETAIL_PATH = '/slos/:sloId' as const;
export const SLO_CREATE_PATH = '/slos/create' as const;
export const SLO_EDIT_PATH = '/slos/edit/:sloId' as const;
export const SLOS_OUTDATED_DEFINITIONS_PATH = '/slos/outdated-definitions' as const;
export const CASES_PATH = '/cases' as const;
// // SLOs have been moved to its own app (slo). Keeping around for redirecting purposes.
export const OLD_SLOS_PATH = '/slos' as const;
export const OLD_SLOS_WELCOME_PATH = '/slos/welcome' as const;
export const OLD_SLOS_OUTDATED_DEFINITIONS_PATH = '/slos/outdated-definitions' as const;
export const OLD_SLO_DETAIL_PATH = '/slos/:sloId' as const;
export const OLD_SLO_EDIT_PATH = '/slos/edit/:sloId' as const;
export const SLO_DETAIL_PATH = '/:sloId' as const;
export const paths = {
observability: {
alerts: `${OBSERVABILITY_BASE_PATH}${ALERTS_PATH}`,
@ -31,24 +34,6 @@ export const paths = {
rules: `${OBSERVABILITY_BASE_PATH}${RULES_PATH}`,
ruleDetails: (ruleId: string) =>
`${OBSERVABILITY_BASE_PATH}${RULES_PATH}/${encodeURIComponent(ruleId)}`,
slos: `${OBSERVABILITY_BASE_PATH}${SLOS_PATH}`,
slosWelcome: `${OBSERVABILITY_BASE_PATH}${SLOS_WELCOME_PATH}`,
slosOutdatedDefinitions: `${OBSERVABILITY_BASE_PATH}${SLOS_OUTDATED_DEFINITIONS_PATH}`,
sloCreate: `${OBSERVABILITY_BASE_PATH}${SLO_CREATE_PATH}`,
sloCreateWithEncodedForm: (encodedParams: string) =>
`${OBSERVABILITY_BASE_PATH}${SLO_CREATE_PATH}?_a=${encodedParams}`,
sloEdit: (sloId: string) =>
`${OBSERVABILITY_BASE_PATH}${SLOS_PATH}/edit/${encodeURIComponent(sloId)}`,
sloEditWithEncodedForm: (sloId: string, encodedParams: string) =>
`${OBSERVABILITY_BASE_PATH}${SLOS_PATH}/edit/${encodeURIComponent(
sloId
)}?_a=${encodedParams}`,
sloDetails: (sloId: string, instanceId?: string) =>
!!instanceId
? `${OBSERVABILITY_BASE_PATH}${SLOS_PATH}/${encodeURIComponent(
sloId
)}?instanceId=${encodeURIComponent(instanceId)}`
: `${OBSERVABILITY_BASE_PATH}${SLOS_PATH}/${encodeURIComponent(sloId)}`,
},
};

View file

@ -20,7 +20,6 @@
"data",
"dataViews",
"dataViewEditor",
"embeddable",
"fieldFormats",
"uiActions",
"presentationUtil",
@ -32,7 +31,6 @@
"observabilityShared",
"observabilityAIAssistant",
"ruleRegistry",
"taskManager",
"triggersActionsUi",
"security",
"share",
@ -59,8 +57,6 @@
"unifiedSearch",
"stackAlerts",
"spaces",
"embeddable",
"ingestPipelines"
],
"extraPublicDirs": [
"common"

View file

@ -9,7 +9,7 @@ import React, { useCallback, useMemo } from 'react';
import { AlertsTableFlyoutBaseProps } from '@kbn/triggers-actions-ui-plugin/public';
import { useRouteMatch } from 'react-router-dom';
import { SLO_ALERTS_TABLE_ID } from '../../pages/slo_details/components/slo_detail_alerts';
import { SLO_ALERTS_TABLE_ID } from '@kbn/observability-shared-plugin/common';
import { SLO_DETAIL_PATH } from '../../../common/locators/paths';
import type { ObservabilityRuleTypeRegistry } from '../../rules/create_observability_rule_type_registry';
import { AlertsFlyoutHeader } from './alerts_flyout_header';

View file

@ -32,9 +32,6 @@ export const registerAlertsTableConfiguration = (
alertTableConfigRegistry.register(ruleDetailsAlertsTableConfig);
// SLO
const sloAlertsTableConfig = getSloAlertsTableConfiguration(
observabilityRuleTypeRegistry,
config
);
const sloAlertsTableConfig = getSloAlertsTableConfiguration(observabilityRuleTypeRegistry);
alertTableConfigRegistry.register(sloAlertsTableConfig);
};

View file

@ -25,7 +25,7 @@ export const columns: Array<
{
columnHeaderType: 'not-filtered',
displayAsText: i18n.translate(
'xpack.observability.sloAlertsEmbeddable.alertsTGrid.statusColumnDescription',
'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.statusColumnDescription',
{
defaultMessage: 'Status',
}
@ -35,25 +35,34 @@ export const columns: Array<
},
{
columnHeaderType: 'not-filtered',
displayAsText: i18n.translate('xpack.observability.alertsTGrid.durationColumnDescription', {
defaultMessage: 'Duration',
}),
displayAsText: i18n.translate(
'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.durationColumnDescription',
{
defaultMessage: 'Duration',
}
),
id: ALERT_DURATION,
initialWidth: 116,
},
{
columnHeaderType: 'not-filtered',
displayAsText: i18n.translate('xpack.observability.alertsTGrid.sloColumnDescription', {
defaultMessage: 'Rule name',
}),
displayAsText: i18n.translate(
'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.sloColumnDescription',
{
defaultMessage: 'Rule name',
}
),
id: ALERT_RULE_NAME,
initialWidth: 110,
},
{
columnHeaderType: 'not-filtered',
displayAsText: i18n.translate('xpack.observability.alertsTGrid.reasonColumnDescription', {
defaultMessage: 'Reason',
}),
displayAsText: i18n.translate(
'xpack.observability.slo.sloAlertsEmbeddable.alertsTGrid.reasonColumnDescription',
{
defaultMessage: 'Reason',
}
),
id: ALERT_REASON,
linkField: '*',
},

View file

@ -8,17 +8,18 @@
import { SortOrder } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { ALERT_DURATION } from '@kbn/rule-data-utils';
import { AlertsTableConfigurationRegistry } from '@kbn/triggers-actions-ui-plugin/public/types';
import { casesFeatureId, observabilityFeatureId } from '../../../../common';
import {
casesFeatureId,
observabilityFeatureId,
SLO_ALERTS_TABLE_CONFIG_ID,
} from '@kbn/observability-shared-plugin/public';
import { getRenderCellValue } from '../common/render_cell_value';
import { columns } from './default_columns';
import type { ObservabilityRuleTypeRegistry } from '../../..';
import { useGetAlertFlyoutComponents } from '../../alerts_flyout/use_get_alert_flyout_components';
import type { ObservabilityRuleTypeRegistry } from '../../../rules/create_observability_rule_type_registry';
import type { ConfigSchema } from '../../../plugin';
import { SLO_ALERTS_TABLE_CONFIG_ID } from '../../../embeddable/slo/constants';
import { columns } from './default_columns';
export const getSloAlertsTableConfiguration = (
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry,
config: ConfigSchema
observabilityRuleTypeRegistry: ObservabilityRuleTypeRegistry
): AlertsTableConfigurationRegistry => ({
id: SLO_ALERTS_TABLE_CONFIG_ID,
cases: { featureId: casesFeatureId, owner: [observabilityFeatureId] },

View file

@ -1,64 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
import moment from 'moment';
import { TimeRange } from '../../../error_rate_chart/use_lens_definition';
export function getLastDurationInUnit(timeRange: TimeRange): string {
const duration = moment.duration(moment(timeRange.to).diff(timeRange.from));
const durationInSeconds = duration.asSeconds();
const oneMinute = 60;
if (durationInSeconds < oneMinute) {
return i18n.translate(
'xpack.observability.slo.burnRateRule.alertDetailsAppSection.lastDurationInSeconds',
{
defaultMessage: 'Last {duration} seconds',
values: {
duration: Math.trunc(durationInSeconds),
},
}
);
}
const twoHours = 2 * 60 * 60;
if (durationInSeconds < twoHours) {
return i18n.translate(
'xpack.observability.slo.burnRateRule.alertDetailsAppSection.lastDurationInMinutes',
{
defaultMessage: 'Last {duration} minutes',
values: {
duration: Math.trunc(duration.asMinutes()),
},
}
);
}
const twoDays = 2 * 24 * 60 * 60;
if (durationInSeconds < twoDays) {
return i18n.translate(
'xpack.observability.slo.burnRateRule.alertDetailsAppSection.lastDurationInHours',
{
defaultMessage: 'Last {duration} hours',
values: {
duration: Math.trunc(duration.asHours()),
},
}
);
}
return i18n.translate(
'xpack.observability.slo.burnRateRule.alertDetailsAppSection.lastDurationInDays',
{
defaultMessage: 'Last {duration} days',
values: {
duration: Math.trunc(duration.asDays()),
},
}
);
}

View file

@ -7,7 +7,5 @@
export const DEFAULT_INTERVAL = '60s';
export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD HH:mm';
export const SLO_LONG_REFETCH_INTERVAL = 60 * 1000; // 1 minute
export const SLO_SHORT_REFETCH_INTERVAL = 5 * 1000; // 5 seconds
export const RULE_DETAILS_ALERTS_TABLE_CONFIG_ID = `rule-details-alerts-table`;

View file

@ -32,6 +32,8 @@ export const plugin: PluginInitializer<
return new Plugin(initializerContext);
};
export type { ConfigSchema } from './plugin';
export {
enableLegacyUptimeApp,
syntheticsThrottlingEnabled,
@ -51,12 +53,10 @@ export {
uptimeOverviewLocatorID,
} from '../common';
export type { SloEditLocatorParams } from './locators/slo_edit';
export type { RulesParams } from './locators/rules';
export { getCoreVitalsComponent } from './pages/overview/components/sections/ux/core_web_vitals/get_core_web_vitals_lazy';
export { DatePicker } from './pages/overview/components/date_picker/date_picker';
export { ObservabilityAlertSearchBar } from './components/alert_search_bar/get_alert_search_bar_lazy';
export { DatePicker } from './pages/overview/components/date_picker/date_picker';
export const LazyAlertsFlyout = lazy(() => import('./components/alerts_flyout/alerts_flyout'));
@ -68,11 +68,16 @@ export type { TopAlert, AlertSummary, AlertSummaryField };
export { observabilityFeatureId, observabilityAppId } from '../common';
export { useFetchDataViews } from './hooks/use_fetch_data_views';
export { useTimeBuckets } from './hooks/use_time_buckets';
export { createUseRulesLink } from './hooks/create_use_rules_link';
export { useSummaryTimeRange } from './hooks/use_summary_time_range';
export { useGetFilteredRuleTypes } from './hooks/use_get_filtered_rule_types';
export { useCreateRule } from './hooks/use_create_rule';
export { getApmTraceUrl } from './utils/get_apm_trace_url';
export { buildEsQuery } from './utils/build_es_query';
export { KibanaReactStorybookDecorator } from './utils/kibana_react.storybook_decorator';
export type {
ObservabilityRuleTypeFormatter,
@ -86,8 +91,10 @@ export { DatePickerContextProvider } from './context/date_picker_context/date_pi
export { fromQuery, toQuery } from './utils/url';
export { getAlertSummaryTimeRange } from './utils/alert_summary_widget';
export { calculateTimeRangeBucketSize } from './pages/overview/helpers/calculate_bucket_size';
export type { render } from './utils/test_helper';
export { convertTo } from '../common/utils/formatters/duration';
export { getElasticsearchQueryOrThrow } from '../common/utils/parse_kuery';
export { formatAlertEvaluationValue } from './utils/format_alert_evaluation_value';
export { WithKueryAutocompletion } from './components/rule_kql_filter/with_kuery_autocompletion';
export { AutocompleteField } from './components/rule_kql_filter/autocomplete_field';

View file

@ -22,7 +22,7 @@ import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
import type { AlertActionsProps } from '@kbn/triggers-actions-ui-plugin/public/types';
import { useRouteMatch } from 'react-router-dom';
import { SLO_ALERTS_TABLE_ID } from '../../slo_details/components/slo_detail_alerts';
import { SLO_ALERTS_TABLE_ID } from '@kbn/observability-shared-plugin/common';
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/constants';
import { paths, SLO_DETAIL_PATH } from '../../../../common/locators/paths';
import { isAlertDetailsEnabledPerApp } from '../../../utils/is_alert_details_enabled';

View file

@ -1,88 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const AGGREGATION_OPTIONS = [
{
value: 'avg',
label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.average', {
defaultMessage: 'Average',
}),
},
{
value: 'max',
label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.max', {
defaultMessage: 'Max',
}),
},
{
value: 'min',
label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.min', {
defaultMessage: 'Min',
}),
},
{
value: 'sum',
label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.sum', {
defaultMessage: 'Sum',
}),
},
{
value: 'cardinality',
label: i18n.translate(
'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.cardinality',
{
defaultMessage: 'Cardinality',
}
),
},
{
value: 'last_value',
label: i18n.translate(
'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.last_value',
{
defaultMessage: 'Last value',
}
),
},
{
value: 'std_deviation',
label: i18n.translate(
'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.std_deviation',
{
defaultMessage: 'Std. Deviation',
}
),
},
{
value: 'doc_count',
label: i18n.translate('xpack.observability.slo.sloEdit.timesliceMetric.aggregation.doc_count', {
defaultMessage: 'Doc count',
}),
},
{
value: 'percentile',
label: i18n.translate(
'xpack.observability.slo.sloEdit.timesliceMetric.aggregation.percentile',
{
defaultMessage: 'Percentile',
}
),
},
];
export const CUSTOM_METRIC_AGGREGATION_OPTIONS = AGGREGATION_OPTIONS.filter((option) =>
['doc_count', 'sum'].includes(option.value)
);
export function aggValueToLabel(value: string) {
const aggregation = AGGREGATION_OPTIONS.find((agg) => agg.value === value);
if (aggregation) {
return aggregation.label;
}
return value;
}

View file

@ -66,11 +66,9 @@ import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/publi
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
import type { UiActionsStart, UiActionsSetup } from '@kbn/ui-actions-plugin/public';
import { firstValueFrom } from 'rxjs';
import type { PresentationUtilPluginStart } from '@kbn/presentation-util-plugin/public';
import { DataViewFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
import { getCreateSLOFlyoutLazy } from './pages/slo_edit/shared_flyout/get_create_slo_flyout';
import { observabilityAppId, observabilityFeatureId } from '../common';
import {
ALERTS_PATH,
@ -78,15 +76,11 @@ import {
OBSERVABILITY_BASE_PATH,
OVERVIEW_PATH,
RULES_PATH,
SLOS_PATH,
} from '../common/locators/paths';
import { registerDataHandler } from './context/has_data_context/data_handler';
import { createUseRulesLink } from './hooks/create_use_rules_link';
import { RulesLocatorDefinition } from './locators/rules';
import { RuleDetailsLocatorDefinition } from './locators/rule_details';
import { SloDetailsLocatorDefinition } from './locators/slo_details';
import { SloEditLocatorDefinition } from './locators/slo_edit';
import { SloListLocatorDefinition } from './locators/slo_list';
import {
createObservabilityRuleTypeRegistry,
ObservabilityRuleTypeRegistry,
@ -213,15 +207,6 @@ export class Plugin
},
],
},
{
id: 'slos',
title: i18n.translate('xpack.observability.slosLinkTitle', {
defaultMessage: 'SLOs',
}),
visibleIn: [],
order: 8002,
path: SLOS_PATH,
},
getCasesDeepLinks({
basePath: CASES_PATH,
extend: {
@ -260,12 +245,6 @@ export class Plugin
new RuleDetailsLocatorDefinition()
);
const sloDetailsLocator = pluginsSetup.share.url.locators.create(
new SloDetailsLocatorDefinition()
);
const sloEditLocator = pluginsSetup.share.url.locators.create(new SloEditLocatorDefinition());
const sloListLocator = pluginsSetup.share.url.locators.create(new SloListLocatorDefinition());
const logsExplorerLocator =
pluginsSetup.share.url.locators.get<LogsExplorerLocatorParams>(LOGS_EXPLORER_LOCATOR_ID);
@ -331,53 +310,6 @@ export class Plugin
logsExplorerLocator
);
const assertPlatinumLicense = async () => {
const licensing = await pluginsSetup.licensing;
const license = await firstValueFrom(licensing.license$);
const hasPlatinumLicense = license.hasAtLeast('platinum');
if (hasPlatinumLicense) {
const registerSloOverviewEmbeddableFactory = async () => {
const { SloOverviewEmbeddableFactoryDefinition } = await import(
'./embeddable/slo/overview/slo_embeddable_factory'
);
const factory = new SloOverviewEmbeddableFactoryDefinition(coreSetup.getStartServices);
pluginsSetup.embeddable.registerEmbeddableFactory(factory.type, factory);
};
registerSloOverviewEmbeddableFactory();
const registerSloAlertsEmbeddableFactory = async () => {
const { SloAlertsEmbeddableFactoryDefinition } = await import(
'./embeddable/slo/alerts/slo_alerts_embeddable_factory'
);
const factory = new SloAlertsEmbeddableFactoryDefinition(
coreSetup.getStartServices,
kibanaVersion
);
pluginsSetup.embeddable.registerEmbeddableFactory(factory.type, factory);
};
registerSloAlertsEmbeddableFactory();
const registerSloErrorBudgetEmbeddableFactory = async () => {
const { SloErrorBudgetEmbeddableFactoryDefinition } = await import(
'./embeddable/slo/error_budget/slo_error_budget_embeddable_factory'
);
const factory = new SloErrorBudgetEmbeddableFactoryDefinition(coreSetup.getStartServices);
pluginsSetup.embeddable.registerEmbeddableFactory(factory.type, factory);
};
registerSloErrorBudgetEmbeddableFactory();
const registerAsyncSloAlertsUiActions = async () => {
if (pluginsSetup.uiActions) {
const { registerSloAlertsUiActions } = await import('./ui_actions');
registerSloAlertsUiActions(pluginsSetup.uiActions, coreSetup);
}
};
registerAsyncSloAlertsUiActions();
}
};
assertPlatinumLicense();
if (pluginsSetup.home) {
pluginsSetup.home.featureCatalogue.registerSolution({
id: observabilityFeatureId,
@ -439,6 +371,18 @@ export class Plugin
]
: [];
const sloLink = coreStart.application.capabilities.slo?.read
? [
{
label: i18n.translate('xpack.observability.sloLinkTitle', {
defaultMessage: 'SLOs',
}),
app: 'slo',
path: '/',
},
]
: [];
// Reformat the visible links to be NavigationEntry objects instead of
// AppDeepLink objects.
//
@ -449,8 +393,17 @@ export class Plugin
// properties used by the deepLinks.
//
// See https://github.com/elastic/kibana/issues/103325.
const otherLinks: NavigationEntry[] = deepLinks
.filter((link) => (link.visibleIn ?? []).length > 0)
const otherLinks = deepLinks.filter((link) => (link.visibleIn ?? []).length > 0);
const alertsLink: NavigationEntry[] = otherLinks
.filter((link) => link.id === 'alerts')
.map((link) => ({
app: observabilityAppId,
label: link.title,
path: link.path ?? '',
}));
const casesLink: NavigationEntry[] = otherLinks
.filter((link) => link.id === 'cases')
.map((link) => ({
app: observabilityAppId,
label: link.title,
@ -461,7 +414,13 @@ export class Plugin
{
label: '',
sortKey: 100,
entries: [...overviewLink, ...otherLinks, ...aiAssistantLink],
entries: [
...overviewLink,
...alertsLink,
...sloLink,
...casesLink,
...aiAssistantLink,
],
},
];
})
@ -476,9 +435,7 @@ export class Plugin
useRulesLink: createUseRulesLink(),
rulesLocator,
ruleDetailsLocator,
sloDetailsLocator,
sloEditLocator,
sloListLocator,
config,
};
}
@ -499,22 +456,11 @@ export class Plugin
deepLinks: this.deepLinks,
updater$: this.appUpdater$,
});
const kibanaVersion = this.initContext.env.packageInfo.version;
const { ruleTypeRegistry, actionTypeRegistry } = pluginsStart.triggersActionsUi;
return {
config,
observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry,
useRulesLink: createUseRulesLink(),
getCreateSLOFlyout: getCreateSLOFlyoutLazy({
config,
core: coreStart,
isDev: this.initContext.env.mode.dev,
kibanaVersion,
observabilityRuleTypeRegistry: this.observabilityRuleTypeRegistry,
ObservabilityPageTemplate: pluginsStart.observabilityShared.navigation.PageTemplate,
plugins: { ...pluginsStart, ruleTypeRegistry, actionTypeRegistry },
isServerless: !!pluginsStart.serverless,
}),
};
}
}

View file

@ -16,10 +16,6 @@ import { LandingPage } from '../pages/landing/landing';
import { OverviewPage } from '../pages/overview/overview';
import { RulesPage } from '../pages/rules/rules';
import { RuleDetailsPage } from '../pages/rule_details/rule_details';
import { SlosPage } from '../pages/slos/slos';
import { SlosWelcomePage } from '../pages/slos_welcome/slos_welcome';
import { SloDetailsPage } from '../pages/slo_details/slo_details';
import { SloEditPage } from '../pages/slo_edit/slo_edit';
import {
ALERTS_PATH,
ALERT_DETAIL_PATH,
@ -31,15 +27,13 @@ import {
RULES_LOGS_PATH,
RULES_PATH,
RULE_DETAIL_PATH,
SLOS_OUTDATED_DEFINITIONS_PATH,
SLOS_PATH,
SLOS_WELCOME_PATH,
SLO_CREATE_PATH,
SLO_DETAIL_PATH,
SLO_EDIT_PATH,
OLD_SLOS_PATH,
OLD_SLOS_WELCOME_PATH,
OLD_SLOS_OUTDATED_DEFINITIONS_PATH,
OLD_SLO_DETAIL_PATH,
OLD_SLO_EDIT_PATH,
} from '../../common/locators/paths';
import { HasDataContextProvider } from '../context/has_data_context/has_data_context';
import { SlosOutdatedDefinitions } from '../pages/slo_outdated_definitions';
// Note: React Router DOM <Redirect> component was not working here
// so I've recreated this simple version for this purpose.
@ -48,10 +42,15 @@ function SimpleRedirect({ to, redirectToApp }: { to: string; redirectToApp?: str
application: { navigateToApp },
} = useKibana().services;
const history = useHistory();
const { search, hash } = useLocation();
const { search, hash, pathname } = useLocation();
if (redirectToApp) {
navigateToApp(redirectToApp, { path: `/${search}${hash}`, replace: true });
if (to === '/:sloId') {
to = pathname.split('/slos')[1];
}
navigateToApp(redirectToApp, {
path: `/${to}${search ? `?${search}` : ''}${hash}`,
replace: true,
});
} else if (to) {
history.replace(to);
}
@ -139,44 +138,37 @@ export const routes = {
params: {},
exact: true,
},
[SLOS_PATH]: {
[OLD_SLOS_PATH]: {
handler: () => {
return <SlosPage />;
return <SimpleRedirect to="/" redirectToApp="slo" />;
},
params: {},
exact: true,
},
[SLO_CREATE_PATH]: {
[OLD_SLOS_WELCOME_PATH]: {
handler: () => {
return <SloEditPage />;
return <SimpleRedirect to="/welcome" redirectToApp="slo" />;
},
params: {},
exact: true,
},
[SLOS_WELCOME_PATH]: {
[OLD_SLOS_OUTDATED_DEFINITIONS_PATH]: {
handler: () => {
return <SlosWelcomePage />;
return <SimpleRedirect to="/outdated-definitions" redirectToApp="slo" />;
},
params: {},
exact: true,
},
[SLOS_OUTDATED_DEFINITIONS_PATH]: {
[OLD_SLO_DETAIL_PATH]: {
handler: () => {
return <SlosOutdatedDefinitions />;
return <SimpleRedirect to="/:sloId" redirectToApp="slo" />;
},
params: {},
exact: true,
},
[SLO_EDIT_PATH]: {
[OLD_SLO_EDIT_PATH]: {
handler: () => {
return <SloEditPage />;
},
params: {},
exact: true,
},
[SLO_DETAIL_PATH]: {
handler: () => {
return <SloDetailsPage />;
return <SimpleRedirect to="/:sloId" redirectToApp="slo" />;
},
params: {},
exact: true,

View file

@ -27,45 +27,9 @@ import type {
import type { MetricExpression } from '../components/custom_threshold/types';
import { getViewInAppUrl } from '../../common/custom_threshold_rule/get_view_in_app_url';
import { getGroups } from '../../common/custom_threshold_rule/helpers/get_group';
import { SLO_ID_FIELD, SLO_INSTANCE_ID_FIELD } from '../../common/field_names/slo';
import { ObservabilityRuleTypeRegistry } from './create_observability_rule_type_registry';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../common/constants';
import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation';
import { validateCustomThreshold } from '../components/custom_threshold/components/validation';
const sloBurnRateDefaultActionMessage = i18n.translate(
'xpack.observability.slo.rules.burnRate.defaultActionMessage',
{
defaultMessage: `\\{\\{context.reason\\}\\}
\\{\\{rule.name\\}\\} is active with the following conditions:
- SLO: \\{\\{context.sloName\\}\\}'
- The burn rate over the last \\{\\{context.longWindow.duration\\}\\} is \\{\\{context.longWindow.burnRate\\}\\}
- The burn rate over the last \\{\\{context.shortWindow.duration\\}\\} is \\{\\{context.shortWindow.burnRate\\}\\}
- Threshold: \\{\\{context.burnRateThreshold\\}\\}
[View alert details](\\{\\{context.alertDetailsUrl\\}\\})
`,
}
);
const sloBurnRateDefaultRecoveryMessage = i18n.translate(
'xpack.observability.slo.rules.burnRate.defaultRecoveryMessage',
{
defaultMessage: `\\{\\{context.reason\\}\\}
\\{\\{rule.name\\}\\} has recovered.
- SLO: \\{\\{context.sloName\\}\\}'
- The burn rate over the last \\{\\{context.longWindow.duration\\}\\} is \\{\\{context.longWindow.burnRate\\}\\}
- The burn rate over the last \\{\\{context.shortWindow.duration\\}\\} is \\{\\{context.shortWindow.burnRate\\}\\}
- Threshold: \\{\\{context.burnRateThreshold\\}\\}
[View alert details](\\{\\{context.alertDetailsUrl\\}\\})
`,
}
);
const thresholdDefaultActionMessage = i18n.translate(
'xpack.observability.customThreshold.rule.alerting.threshold.defaultActionMessage',
{
@ -97,33 +61,6 @@ export const registerObservabilityRuleTypes = async (
uiSettings: IUiSettingsClient,
logsExplorerLocator?: LocatorPublic<LogsExplorerLocatorParams>
) => {
observabilityRuleTypeRegistry.register({
id: SLO_BURN_RATE_RULE_TYPE_ID,
description: i18n.translate('xpack.observability.slo.rules.burnRate.description', {
defaultMessage: 'Alert when your SLO burn rate is too high over a defined period of time.',
}),
format: ({ fields }) => {
return {
reason: fields[ALERT_REASON] ?? '-',
link: `/app/observability/slos/${fields[SLO_ID_FIELD]}?instanceId=${
fields[SLO_INSTANCE_ID_FIELD] ?? '*'
}`,
};
},
iconClass: 'bell',
documentationUrl(docLinks) {
return `${docLinks.links.observability.sloBurnRateRule}`;
},
ruleParamsExpression: lazy(() => import('../components/burn_rate_rule_editor')),
validate: validateBurnRateRule,
requiresAppContext: false,
defaultActionMessage: sloBurnRateDefaultActionMessage,
defaultRecoveryMessage: sloBurnRateDefaultRecoveryMessage,
alertDetailsAppSection: lazy(
() => import('../components/slo/burn_rate/alert_details/alert_details_app_section')
),
priority: 100,
});
const validateCustomThresholdWithUiSettings = ({
criteria,
searchConfiguration,

View file

@ -6,5 +6,4 @@
*/
export * from './fetch_overview_data';
export * from './slo';
export * from './utils';

View file

@ -12,7 +12,7 @@ import { AppMountParameters } from '@kbn/core-application-browser';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { CoreTheme } from '@kbn/core-theme-browser';
import { MemoryRouter } from 'react-router-dom';
import { casesFeatureId, sloFeatureId } from '../../common';
import { casesFeatureId, sloFeatureId } from '@kbn/observability-shared-plugin/common';
import { PluginContext } from '../context/plugin_context/plugin_context';
import { createObservabilityRuleTypeRegistryMock } from '../rules/observability_rule_type_registry_mock';
import { ConfigSchema } from '../plugin';

View file

@ -5,5 +5,4 @@
* 2.0.
*/
export const SLO_RULE_REGISTRATION_CONTEXT = 'observability.slo';
export const THRESHOLD_RULE_REGISTRATION_CONTEXT = 'observability.threshold';

View file

@ -14,6 +14,7 @@ import type { ObservabilityPluginSetup } from './plugin';
import { createOrUpdateIndex, Mappings } from './utils/create_or_update_index';
import { createOrUpdateIndexTemplate } from './utils/create_or_update_index_template';
import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations';
import { CustomThresholdLocators } from './lib/rules/custom_threshold/custom_threshold_executor';
import {
unwrapEsResponse,
WrappedElasticsearchClientError,
@ -57,7 +58,6 @@ const configSchema = schema.object({
}),
enabled: schema.boolean({ defaultValue: true }),
createO11yGenericFeatureId: schema.boolean({ defaultValue: false }),
sloOrphanSummaryCleanUpTaskEnabled: schema.boolean({ defaultValue: true }),
});
export const config: PluginConfigDescriptor = {
@ -85,7 +85,12 @@ export const plugin = async (initContext: PluginInitializerContext) => {
return new ObservabilityPlugin(initContext);
};
export type { Mappings, ObservabilityPluginSetup, ScopedAnnotationsClient };
export type {
Mappings,
ObservabilityPluginSetup,
ScopedAnnotationsClient,
CustomThresholdLocators,
};
export {
createOrUpdateIndex,
createOrUpdateIndexTemplate,

View file

@ -15,15 +15,10 @@ import {
import { mappingFromFieldMap } from '@kbn/alerting-plugin/common';
import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
import { CustomThresholdLocators } from './custom_threshold/custom_threshold_executor';
import { sloFeatureId, observabilityFeatureId } from '../../../common';
import { observabilityFeatureId } from '../../../common';
import { ObservabilityConfig } from '../..';
import {
SLO_RULE_REGISTRATION_CONTEXT,
THRESHOLD_RULE_REGISTRATION_CONTEXT,
} from '../../common/constants';
import { sloBurnRateRuleType } from './slo_burn_rate';
import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../common/constants';
import { thresholdRuleType } from './custom_threshold/register_custom_threshold_rule_type';
import { sloRuleFieldMap } from './slo_burn_rate/field_map';
export function registerRuleTypes(
alertingPlugin: PluginSetupContract,
@ -33,31 +28,6 @@ export function registerRuleTypes(
ruleDataService: IRuleDataService,
locators: CustomThresholdLocators
) {
// SLO RULE
const ruleDataClientSLO = ruleDataService.initializeIndex({
feature: sloFeatureId,
registrationContext: SLO_RULE_REGISTRATION_CONTEXT,
dataset: Dataset.alerts,
componentTemplateRefs: [],
componentTemplates: [
{
name: 'mappings',
mappings: mappingFromFieldMap(
{ ...legacyExperimentalFieldMap, ...sloRuleFieldMap },
'strict'
),
},
],
});
const createLifecycleRuleExecutorSLO = createLifecycleExecutor(
logger.get('rules'),
ruleDataClientSLO
);
alertingPlugin.registerType(
sloBurnRateRuleType(createLifecycleRuleExecutorSLO, basePath, locators.alertsLocator)
);
const ruleDataClientThreshold = ruleDataService.initializeIndex({
feature: observabilityFeatureId,
registrationContext: THRESHOLD_RULE_REGISTRATION_CONTEXT,

View file

@ -18,7 +18,6 @@ import {
Logger,
Plugin,
PluginInitializerContext,
SavedObjectsClient,
} from '@kbn/core/server';
import { LogsExplorerLocatorParams, LOGS_EXPLORER_LOCATOR_ID } from '@kbn/deeplinks-observability';
import { PluginSetupContract as FeaturesSetup } from '@kbn/features-plugin/server';
@ -31,19 +30,14 @@ import {
ML_ANOMALY_DETECTION_RULE_TYPE_ID,
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
SLO_BURN_RATE_RULE_TYPE_ID,
} from '@kbn/rule-data-utils';
import {
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/server';
import { SharePluginSetup } from '@kbn/share-plugin/server';
import { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { SloOrphanSummaryCleanupTask } from './services/slo/tasks/orphan_summary_cleanup_task';
import { ObservabilityConfig } from '.';
import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '../common/constants';
import { casesFeatureId, observabilityFeatureId } from '../common';
import {
kubernetesGuideConfig,
kubernetesGuideId,
@ -54,14 +48,10 @@ import {
bootstrapAnnotations,
ScopedAnnotationsClientFactory,
} from './lib/annotations/bootstrap_annotations';
import { registerSloUsageCollector } from './lib/collectors/register';
import { registerRuleTypes } from './lib/rules/register_rule_types';
import { getObservabilityServerRouteRepository } from './routes/get_global_observability_server_route_repository';
import { registerRoutes } from './routes/register_routes';
import { slo, SO_SLO_TYPE } from './saved_objects';
import { threshold } from './saved_objects/threshold';
import { DefaultResourceInstaller, DefaultSLOInstaller } from './services/slo';
import { uiSettings } from './ui_settings';
export type ObservabilityPluginSetup = ReturnType<ObservabilityPlugin['setup']>;
@ -75,17 +65,13 @@ interface PluginSetup {
spaces?: SpacesPluginSetup;
usageCollection?: UsageCollectionSetup;
cloud?: CloudSetup;
taskManager: TaskManagerSetupContract;
}
interface PluginStart {
alerting: PluginStartContract;
taskManager: TaskManagerStartContract;
spaces?: SpacesPluginStart;
}
const sloRuleTypes = [SLO_BURN_RATE_RULE_TYPE_ID];
const o11yRuleTypes = [
SLO_BURN_RATE_RULE_TYPE_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
@ -97,7 +83,6 @@ const o11yRuleTypes = [
export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
private logger: Logger;
private sloOrphanCleanupTask?: SloOrphanSummaryCleanupTask;
constructor(private readonly initContext: PluginInitializerContext) {
this.initContext = initContext;
@ -290,65 +275,12 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
const { ruleDataService } = plugins.ruleRegistry;
const savedObjectTypes = [SO_SLO_TYPE];
plugins.features.registerKibanaFeature({
id: sloFeatureId,
name: i18n.translate('xpack.observability.featureRegistry.linkSloTitle', {
defaultMessage: 'SLOs',
}),
order: 1200,
category: DEFAULT_APP_CATEGORIES.observability,
app: [sloFeatureId, 'kibana'],
catalogue: [sloFeatureId, 'observability'],
alerting: sloRuleTypes,
privileges: {
all: {
app: [sloFeatureId, 'kibana'],
catalogue: [sloFeatureId, 'observability'],
api: ['slo_write', 'slo_read', 'rac'],
savedObject: {
all: savedObjectTypes,
read: [],
},
alerting: {
rule: {
all: sloRuleTypes,
},
alert: {
all: sloRuleTypes,
},
},
ui: ['read', 'write'],
},
read: {
app: [sloFeatureId, 'kibana'],
catalogue: [sloFeatureId, 'observability'],
api: ['slo_read', 'rac'],
savedObject: {
all: [],
read: savedObjectTypes,
},
alerting: {
rule: {
read: sloRuleTypes,
},
alert: {
read: sloRuleTypes,
},
},
ui: ['read'],
},
},
});
core.savedObjects.registerType(slo);
core.savedObjects.registerType(threshold);
registerRuleTypes(plugins.alerting, core.http.basePath, config, this.logger, ruleDataService, {
alertsLocator,
logsExplorerLocator,
});
registerSloUsageCollector(plugins.usageCollection);
core.getStartServices().then(([coreStart, pluginStart]) => {
registerRoutes({
@ -366,24 +298,12 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
logger: this.logger,
repository: getObservabilityServerRouteRepository(config),
});
const esInternalClient = coreStart.elasticsearch.client.asInternalUser;
const sloResourceInstaller = new DefaultResourceInstaller(esInternalClient, this.logger);
const sloInstaller = new DefaultSLOInstaller(sloResourceInstaller, this.logger);
sloInstaller.install();
});
/**
* Register a config for the observability guide
*/
plugins.guidedOnboarding?.registerGuideConfig(kubernetesGuideId, kubernetesGuideConfig);
this.sloOrphanCleanupTask = new SloOrphanSummaryCleanupTask(
plugins.taskManager,
this.logger,
config
);
return {
getAlertDetailsConfig() {
return config.unsafe.alertDetails;
@ -396,12 +316,7 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
};
}
public start(core: CoreStart, plugins: PluginStart) {
const internalSoClient = new SavedObjectsClient(core.savedObjects.createInternalRepository());
const internalEsClient = core.elasticsearch.client.asInternalUser;
this.sloOrphanCleanupTask?.start(plugins.taskManager, internalSoClient, internalEsClient);
}
public start(core: CoreStart, plugins: PluginStart) {}
public stop() {}
}

View file

@ -7,12 +7,10 @@
import { ObservabilityConfig } from '..';
import { rulesRouteRepository } from './rules/route';
import { sloRouteRepository } from './slo/route';
export function getObservabilityServerRouteRepository(config: ObservabilityConfig) {
const repository = {
...rulesRouteRepository,
...sloRouteRepository,
};
return repository;
}

View file

@ -10,7 +10,6 @@
"server/**/*",
"typings/**/*",
"../../../../typings/**/*",
"../../observability/public/embeddable/slo/common"
],
"kbn_references": [
"@kbn/core",
@ -47,13 +46,10 @@
"@kbn/server-route-repository",
"@kbn/ui-theme",
"@kbn/test-jest-helpers",
"@kbn/core-http-browser",
"@kbn/config-schema",
"@kbn/features-plugin",
"@kbn/core-saved-objects-server",
"@kbn/logging-mocks",
"@kbn/logging",
"@kbn/core-saved-objects-api-server",
"@kbn/share-plugin",
"@kbn/core-notifications-browser",
"@kbn/slo-schema",
@ -93,27 +89,14 @@
"@kbn/react-kibana-mount",
"@kbn/react-kibana-context-theme",
"@kbn/shared-ux-link-redirect-app",
"@kbn/core-chrome-browser",
"@kbn/lens-embeddable-utils",
"@kbn/serverless",
"@kbn/dashboard-plugin",
"@kbn/calculate-auto",
"@kbn/presentation-util-plugin",
"@kbn/task-manager-plugin",
"@kbn/core-elasticsearch-client-server-mocks",
"@kbn/es-types",
"@kbn/ingest-pipelines-plugin",
"@kbn/core-saved-objects-api-server-mocks",
"@kbn/core-ui-settings-browser-mocks",
"@kbn/field-formats-plugin",
"@kbn/aiops-utils",
"@kbn/event-annotation-common",
"@kbn/controls-plugin",
"@kbn/core-lifecycle-browser",
"@kbn/unified-data-table",
"@kbn/cell-actions",
"@kbn/discover-utils",
"@kbn/unified-field-list",
"@kbn/data-view-field-editor-plugin"
],
"exclude": ["target/**/*"]

View file

@ -18,7 +18,7 @@
"logsShared",
"observabilityAIAssistant",
"observabilityShared",
"observability",
"slo",
"share",
"kibanaUtils",
"datasetQuality"

View file

@ -20,7 +20,7 @@ import { hydrateDataSourceSelection } from '@kbn/logs-explorer-plugin/common';
import { getDiscoverFiltersFromState } from '@kbn/logs-explorer-plugin/public';
import type { AlertParams } from '@kbn/observability-plugin/public/components/custom_threshold/types';
import { useLinkProps } from '@kbn/observability-shared-plugin/public';
import { sloFeatureId } from '@kbn/observability-plugin/common';
import { sloFeatureId } from '@kbn/observability-shared-plugin/common';
import { loadRuleTypes } from '@kbn/triggers-actions-ui-plugin/public';
import useAsync from 'react-use/lib/useAsync';
import { useKibanaContextForPlugin } from '../utils/use_kibana';
@ -78,9 +78,8 @@ function alertsPopoverReducer(state: AlertsPopoverState, action: AlertsPopoverAc
export const AlertsPopover = () => {
const {
services: { triggersActionsUi, observability, application, http },
services: { triggersActionsUi, slo, application, http },
} = useKibanaContextForPlugin();
const manageRulesLinkProps = useLinkProps({ app: 'observability', pathname: '/alerts/rules' });
const [pageState] = useActor(useObservabilityLogsExplorerPageStateContext());
@ -144,7 +143,7 @@ export const AlertsPopover = () => {
logsExplorerState?.query && 'query' in logsExplorerState.query
? String(logsExplorerState.query.query)
: undefined;
return observability.getCreateSLOFlyout({
return slo.getCreateSLOFlyout({
initialValues: {
indicator: {
type: 'sli.kql.custom',
@ -166,7 +165,7 @@ export const AlertsPopover = () => {
onClose: closeCreateSLOFlyout,
});
}
}, [observability, pageState, state.isCreateSLOFlyoutOpen]);
}, [slo, pageState, state.isCreateSLOFlyoutOpen]);
// Check whether the user has the necessary permissions to create an SLO
const canCreateSLOs = !!application.capabilities[sloFeatureId]?.write;

View file

@ -20,7 +20,7 @@ import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/publi
import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
import { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { LensPublicStart } from '@kbn/lens-plugin/public';
import { ObservabilityPublicStart } from '@kbn/observability-plugin/public';
import { SloPublicStart } from '@kbn/slo-plugin/public';
import {
ObservabilityLogsExplorerLocators,
ObservabilityLogsExplorerLocationState,
@ -46,7 +46,7 @@ export interface ObservabilityLogsExplorerStartDeps {
logsShared: LogsSharedClientStartExports;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
observabilityShared: ObservabilitySharedPluginStart;
observability: ObservabilityPublicStart;
slo: SloPublicStart;
serverless?: ServerlessPluginStart;
triggersActionsUi?: TriggersAndActionsUIPublicPluginStart;
unifiedSearch?: UnifiedSearchPublicPluginStart;

View file

@ -46,6 +46,7 @@
"@kbn/data-view-editor-plugin",
"@kbn/lens-plugin",
"@kbn/shared-ux-prompt-not-found",
"@kbn/slo-plugin",
],
"exclude": [
"target/**/*"

View file

@ -4,12 +4,18 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AlertConsumers } from '@kbn/rule-data-utils';
export const observabilityFeatureId = 'observability';
export const observabilityAppId = 'observability-overview';
export const casesFeatureId = 'observabilityCases';
export const sloFeatureId = 'slo';
// SLO alerts table in slo detail page
export const SLO_ALERTS_TABLE_ID = 'xpack.observability.slo.sloDetails.alertTable';
// Emebeddable SLO alerts table
export const SLO_ALERTS_TABLE_CONFIG_ID = `${AlertConsumers.SLO}-embeddable-alerts-table`;
export {
CLOUD,
CLOUD_AVAILABILITY_ZONE,

View file

@ -80,6 +80,7 @@ export {
observabilityAppId,
casesFeatureId,
sloFeatureId,
SLO_ALERTS_TABLE_CONFIG_ID,
} from '../common';
export {

View file

@ -7,7 +7,7 @@
import { Subject } from 'rxjs';
import { App, AppDeepLink, ApplicationStart, AppUpdater } from '@kbn/core/public';
import { casesFeatureId, sloFeatureId } from '../../common';
import { casesFeatureId } from '../../common';
import { updateGlobalNavigation } from './update_global_navigation';
// Used in updater callback
@ -157,70 +157,5 @@ describe('updateGlobalNavigation', () => {
});
});
});
it("hides the slo link when the capabilities don't include it", () => {
const capabilities = {
navLinks: { apm: true, logs: false, metrics: false, uptime: false },
} as unknown as ApplicationStart['capabilities'];
const sloRoute = {
id: 'slos',
title: 'SLOs',
order: 8002,
path: '/slos',
visibleIn: [],
};
const deepLinks = [sloRoute];
const callback = jest.fn();
const updater$ = {
next: (cb: AppUpdater) => callback(cb(app)),
} as unknown as Subject<AppUpdater>;
updateGlobalNavigation({ capabilities, deepLinks, updater$ });
expect(callback).toHaveBeenCalledWith({
deepLinks: [], // Deeplink has been filtered out
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
describe('when slos are enabled', () => {
it('shows the slos deep link', () => {
const capabilities = {
[casesFeatureId]: { read_cases: true },
[sloFeatureId]: { read: true },
navLinks: { apm: false, logs: false, metrics: false, uptime: false },
} as unknown as ApplicationStart['capabilities'];
const sloRoute = {
id: 'slos',
title: 'SLOs',
order: 8002,
path: '/slos',
visibleIn: [],
};
const deepLinks = [sloRoute];
const callback = jest.fn();
const updater$ = {
next: (cb: AppUpdater) => callback(cb(app)),
} as unknown as Subject<AppUpdater>;
updateGlobalNavigation({ capabilities, deepLinks, updater$ });
expect(callback).toHaveBeenCalledWith({
deepLinks: [
{
...sloRoute,
visibleIn: ['sideNav', 'globalSearch'],
},
],
visibleIn: ['sideNav', 'globalSearch', 'home', 'kibanaOverview'],
});
});
});
});
});

View file

@ -8,7 +8,7 @@
import { Subject } from 'rxjs';
import { AppUpdater, ApplicationStart, AppDeepLink } from '@kbn/core/public';
import { CasesDeepLinkId } from '@kbn/cases-plugin/public';
import { casesFeatureId, sloFeatureId } from '../../common';
import { casesFeatureId } from '../../common';
export function updateGlobalNavigation({
capabilities,
@ -19,12 +19,13 @@ export function updateGlobalNavigation({
deepLinks: AppDeepLink[];
updater$: Subject<AppUpdater>;
}) {
const { apm, logs, metrics, uptime } = capabilities.navLinks;
const { apm, logs, metrics, uptime, slo } = capabilities.navLinks;
const someVisible = Object.values({
apm,
logs,
metrics,
uptime,
slo,
}).some((visible) => visible);
const updatedDeepLinks = deepLinks
@ -54,14 +55,6 @@ export function updateGlobalNavigation({
};
}
return null;
case 'slos':
if (!!capabilities[sloFeatureId]?.read) {
return {
...link,
visibleIn: ['sideNav', 'globalSearch'],
};
}
return null;
default:
return link;
}
@ -70,9 +63,6 @@ export function updateGlobalNavigation({
updater$.next(() => ({
deepLinks: updatedDeepLinks,
visibleIn:
someVisible || !!capabilities[sloFeatureId]?.read
? ['sideNav', 'globalSearch', 'home', 'kibanaOverview']
: [],
visibleIn: someVisible ? ['sideNav', 'globalSearch', 'home', 'kibanaOverview'] : [],
}));
}

View file

@ -41,6 +41,7 @@
"@kbn/management-settings-types",
"@kbn/management-settings-utilities",
"@kbn/core-chrome-browser",
"@kbn/rule-data-utils",
],
"exclude": ["target/**/*"]
}

View file

@ -0,0 +1,11 @@
/*
* 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 { setGlobalConfig } from '@storybook/testing-react';
import * as globalStorybookConfig from './preview';
setGlobalConfig(globalStorybookConfig);

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
module.exports = require('@kbn/storybook').defaultConfig;

View file

@ -0,0 +1,10 @@
/*
* 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 { EuiThemeProviderDecorator } from '@kbn/kibana-react-plugin/common';
export const decorators = [EuiThemeProviderDecorator];

View file

@ -0,0 +1,22 @@
# slos
A Kibana plugin
---
## Development
See the [kibana contributing guide](https://github.com/elastic/kibana/blob/main/CONTRIBUTING.md) for instructions setting up your development environment.
## Scripts
<dl>
<dt><code>yarn kbn bootstrap</code></dt>
<dd>Execute this to install node_modules and setup the dependencies in your plugin and in Kibana</dd>
<dt><code>yarn plugin-helpers build</code></dt>
<dd>Execute this to create a distributable version of this plugin that can be installed in Kibana</dd>
<dt><code>yarn plugin-helpers dev --watch</code></dt>
<dd>Execute this to build your plugin ui browser side so Kibana could pick up when started in development</dd>
</dl>

View file

@ -5,6 +5,42 @@
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g;
export const ALERT_ACTION_ID = 'slo.burnRate.alert';
export const ALERT_ACTION = {
id: ALERT_ACTION_ID,
name: i18n.translate('xpack.slo.alerting.burnRate.alertAction', {
defaultMessage: 'Critical',
}),
};
export const HIGH_PRIORITY_ACTION_ID = 'slo.burnRate.high';
export const HIGH_PRIORITY_ACTION = {
id: HIGH_PRIORITY_ACTION_ID,
name: i18n.translate('xpack.slo.alerting.burnRate.highPriorityAction', {
defaultMessage: 'High',
}),
};
export const MEDIUM_PRIORITY_ACTION_ID = 'slo.burnRate.medium';
export const MEDIUM_PRIORITY_ACTION = {
id: MEDIUM_PRIORITY_ACTION_ID,
name: i18n.translate('xpack.slo.alerting.burnRate.mediumPriorityAction', {
defaultMessage: 'Medium',
}),
};
export const LOW_PRIORITY_ACTION_ID = 'slo.burnRate.low';
export const LOW_PRIORITY_ACTION = {
id: LOW_PRIORITY_ACTION_ID,
name: i18n.translate('xpack.slo.alerting.burnRate.lowPriorityAction', {
defaultMessage: 'Low',
}),
};
export const SLO_MODEL_VERSION = 2;
export const SLO_RESOURCES_VERSION = 3;

View file

@ -0,0 +1,12 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const NOT_AVAILABLE_LABEL = i18n.translate('xpack.slo.notAvailable', {
defaultMessage: 'N/A',
});

View file

@ -0,0 +1,12 @@
/*
* 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 sloAppId = 'slo';
export const PLUGIN_NAME = 'SLOs';
import { paths } from './locators/paths';
export const sloPaths = paths;

View file

@ -0,0 +1,32 @@
/*
* 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 SLOS_BASE_PATH = '/app/slos';
export const SLO_PREFIX = '/slos';
export const SLOS_PATH = '/' as const;
export const SLOS_WELCOME_PATH = '/welcome' as const;
export const SLO_DETAIL_PATH = '/:sloId' as const;
export const SLO_CREATE_PATH = '/create' as const;
export const SLO_EDIT_PATH = '/edit/:sloId' as const;
export const SLOS_OUTDATED_DEFINITIONS_PATH = '/outdated-definitions' as const;
export const paths = {
slos: `${SLOS_BASE_PATH}${SLOS_PATH}`,
slosWelcome: `${SLOS_BASE_PATH}${SLOS_WELCOME_PATH}`,
slosOutdatedDefinitions: `${SLOS_BASE_PATH}${SLOS_OUTDATED_DEFINITIONS_PATH}`,
sloCreate: `${SLOS_BASE_PATH}${SLO_CREATE_PATH}`,
sloCreateWithEncodedForm: (encodedParams: string) =>
`${SLOS_BASE_PATH}${SLO_CREATE_PATH}?_a=${encodedParams}`,
sloEdit: (sloId: string) => `${SLOS_BASE_PATH}${SLOS_PATH}/edit/${encodeURIComponent(sloId)}`,
sloEditWithEncodedForm: (sloId: string, encodedParams: string) =>
`${SLOS_BASE_PATH}${SLOS_PATH}/edit/${encodeURIComponent(sloId)}?_a=${encodedParams}`,
sloDetails: (sloId: string, instanceId?: string) =>
!!instanceId
? `${SLOS_BASE_PATH}${SLOS_PATH}/${encodeURIComponent(sloId)}?instanceId=${encodeURIComponent(
instanceId
)}`
: `${SLOS_BASE_PATH}${SLOS_PATH}/${encodeURIComponent(sloId)}`,
};

Some files were not shown because too many files have changed in this diff Show more