From 1a92a4ba136dc0e3488c06b25d1c96ec01c638cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Rica=20Pais=20da=20Silva?= Date: Tue, 3 Sep 2024 12:05:56 +0200 Subject: [PATCH] Implement Metrics Explorer Locator (#190821) Relates to #176667 ## Summary This PR adds the `MetricsExplorerLocator` and fixes some bugs that were found regarding how locators were getting created and propagated. ## Testing Alerts were created with group_by and no grouping to trigger for some CPU usage. Going to the alert page and clicking the app view icon should take you to the asset details page (if grouped) or to the metrics explorer page (if no group) via the locators. ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Elastic Machine Co-authored-by: Carlos Crespo Co-authored-by: Bena Kansara <69037875+benakansara@users.noreply.github.com> --- .../alerting/metrics/alert_link.test.ts | 23 ++++++++-- .../common/alerting/metrics/alert_link.ts | 26 ++++++++--- .../infra/common/constants.ts | 1 - .../public/alerting/metric_threshold/index.ts | 9 +++- .../metric_threshold/rule_data_formatters.ts | 11 +++-- .../infra/public/plugin.ts | 12 ++++- .../server/lib/alerting/common/utils.test.ts | 2 + .../infra/server/lib/alerting/common/utils.ts | 46 ++++++++----------- ...nventory_metric_threshold_executor.test.ts | 29 +++++++++++- .../inventory_metric_threshold_executor.ts | 33 +++---------- ...er_inventory_metric_threshold_rule_type.ts | 7 +-- .../metric_threshold_executor.test.ts | 31 +++++++++++-- .../metric_threshold_executor.ts | 37 ++++++--------- .../register_metric_threshold_rule_type.ts | 7 +-- .../lib/alerting/register_rule_types.ts | 7 +-- .../infra/server/lib/infra_types.ts | 13 ++++++ .../infra/server/plugin.ts | 22 ++++++++- .../observability/common/index.ts | 2 +- .../observability/common/locators/alerts.ts | 4 +- .../observability/server/plugin.ts | 1 + .../observability_shared/common/index.ts | 4 ++ .../common/locators/index.ts | 1 + .../infra/metrics_explorer_locator.ts | 29 ++++++++++++ .../observability_shared/public/plugin.ts | 4 ++ 24 files changed, 249 insertions(+), 112 deletions(-) create mode 100644 x-pack/plugins/observability_solution/observability_shared/common/locators/infra/metrics_explorer_locator.ts diff --git a/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.test.ts b/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.test.ts index 2513a3432742d..ea483e129e2e7 100644 --- a/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.test.ts +++ b/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.test.ts @@ -18,6 +18,8 @@ import { AssetDetailsLocator, InventoryLocatorParams, AssetDetailsLocatorParams, + MetricsExplorerLocatorParams, + MetricsExplorerLocator, } from '@kbn/observability-shared-plugin/common'; jest.mock('@kbn/observability-shared-plugin/common'); @@ -40,6 +42,12 @@ const mockAssetDetailsLocator = { ), } as unknown as jest.Mocked; +const mockMetricsExplorerLocator = { + getRedirectUrl: jest + .fn() + .mockImplementation(({}: MetricsExplorerLocatorParams) => `/metrics-mock`), +} as unknown as jest.Mocked; + describe('Inventory Threshold Rule', () => { afterEach(() => { jest.clearAllMocks(); @@ -260,6 +268,7 @@ describe('Metrics Rule', () => { const url = getMetricsViewInAppUrl({ fields, assetDetailsLocator: mockAssetDetailsLocator, + metricsExplorerLocator: mockMetricsExplorerLocator, groupBy: ['host.name'], }); expect(mockAssetDetailsLocator.getRedirectUrl).toHaveBeenCalledTimes(1); @@ -276,6 +285,7 @@ describe('Metrics Rule', () => { const url = getMetricsViewInAppUrl({ fields, assetDetailsLocator: mockAssetDetailsLocator, + metricsExplorerLocator: mockMetricsExplorerLocator, groupBy: ['container.id'], }); expect(mockAssetDetailsLocator.getRedirectUrl).toHaveBeenCalledTimes(1); @@ -292,17 +302,24 @@ describe('Metrics Rule', () => { const url = getMetricsViewInAppUrl({ fields, assetDetailsLocator: mockAssetDetailsLocator, + metricsExplorerLocator: mockMetricsExplorerLocator, groupBy: ['kubernetes.pod.name'], }); - expect(url).toEqual('/app/metrics/explorer'); + expect(mockMetricsExplorerLocator.getRedirectUrl).toHaveBeenCalledTimes(1); + expect(url).toEqual('/metrics-mock'); }); it('should point to metrics explorer', () => { const fields = { [TIMESTAMP]: '2022-01-01T00:00:00.000Z', } as unknown as ParsedTechnicalFields & Record; - const url = getMetricsViewInAppUrl({ fields }); - expect(url).toEqual('/app/metrics/explorer'); + const url = getMetricsViewInAppUrl({ + fields, + assetDetailsLocator: mockAssetDetailsLocator, + metricsExplorerLocator: mockMetricsExplorerLocator, + }); + expect(mockMetricsExplorerLocator.getRedirectUrl).toHaveBeenCalledTimes(1); + expect(url).toEqual('/metrics-mock'); }); }); }); diff --git a/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.ts b/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.ts index 3dc84f0406145..95a2fd85eb9fa 100644 --- a/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.ts +++ b/x-pack/plugins/observability_solution/infra/common/alerting/metrics/alert_link.ts @@ -12,11 +12,12 @@ import { ParsedTechnicalFields } from '@kbn/rule-registry-plugin/common/parse_te import { type InventoryItemType, findInventoryModel } from '@kbn/metrics-data-access-plugin/common'; import type { LocatorPublic } from '@kbn/share-plugin/common'; import { + MetricsExplorerLocatorParams, type AssetDetailsLocatorParams, type InventoryLocatorParams, } from '@kbn/observability-shared-plugin/common'; import { castArray } from 'lodash'; -import { fifteenMinutesInMilliseconds, METRICS_EXPLORER_URL } from '../../constants'; +import { fifteenMinutesInMilliseconds } from '../../constants'; import { SupportedAssetTypes } from '../../asset_details/types'; const ALERT_RULE_PARAMTERS_INVENTORY_METRIC_ID = `${ALERT_RULE_PARAMETERS}.criteria.metric`; @@ -48,7 +49,7 @@ export const getInventoryViewInAppUrl = ({ inventoryLocator?: LocatorPublic; }): string => { if (!assetDetailsLocator || !inventoryLocator) { - return ''; + throw new Error('Locators for Asset Details and Inventory are required'); } /* Temporary Solution -> https://github.com/elastic/kibana/issues/137033 @@ -127,13 +128,19 @@ export const getMetricsViewInAppUrl = ({ fields, groupBy, assetDetailsLocator, + metricsExplorerLocator, }: { fields: ParsedTechnicalFields & Record; groupBy?: string[]; assetDetailsLocator?: LocatorPublic; + metricsExplorerLocator?: LocatorPublic; }) => { - if (!groupBy || !assetDetailsLocator) { - return METRICS_EXPLORER_URL; + if (!assetDetailsLocator || !metricsExplorerLocator) { + throw new Error('Locators for Asset Details and Metrics Explorer are required'); + } + + if (!groupBy) { + return metricsExplorerLocator.getRedirectUrl({}); } // creates an object of asset details supported assetType by their assetId field name @@ -143,12 +150,19 @@ export const getMetricsViewInAppUrl = ({ }, {} as Record); // detemines if the groupBy has a field that the asset details supports - const supportedAssetId = groupBy?.find((field) => !!assetTypeByAssetId[field]); + const supportedAssetId = groupBy.find((field) => !!assetTypeByAssetId[field]); // assigns a nodeType if the groupBy field is supported by asset details const supportedAssetType = supportedAssetId ? assetTypeByAssetId[supportedAssetId] : undefined; if (supportedAssetType) { const assetId = fields[findInventoryModel(supportedAssetType).fields.id]; + + // A supported asset type can still return no id. In such a case, we can't + // generate a valid link, so we redirect to Metrics Explorer. + if (!assetId) { + return metricsExplorerLocator.getRedirectUrl({}); + } + const timestamp = fields[TIMESTAMP]; return getLinkToAssetDetails({ @@ -158,7 +172,7 @@ export const getMetricsViewInAppUrl = ({ assetDetailsLocator, }); } else { - return METRICS_EXPLORER_URL; + return metricsExplorerLocator.getRedirectUrl({}); } }; diff --git a/x-pack/plugins/observability_solution/infra/common/constants.ts b/x-pack/plugins/observability_solution/infra/common/constants.ts index 63dfa663ce256..482406171a673 100644 --- a/x-pack/plugins/observability_solution/infra/common/constants.ts +++ b/x-pack/plugins/observability_solution/infra/common/constants.ts @@ -43,7 +43,6 @@ export const O11Y_AAD_FIELDS = [ 'tags', ]; -export const METRICS_EXPLORER_URL = '/app/metrics/explorer'; export const fifteenMinutesInMilliseconds = 15 * 60 * 1000; export const DEFAULT_METRICS_VIEW_ATTRIBUTES = { diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/index.ts index a37d14c061f76..3f831e4c8e2c0 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/index.ts +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/index.ts @@ -10,7 +10,10 @@ import { lazy } from 'react'; import { RuleTypeParams } from '@kbn/alerting-plugin/common'; import { ObservabilityRuleTypeModel } from '@kbn/observability-plugin/public'; import { LocatorPublic } from '@kbn/share-plugin/common'; -import { AssetDetailsLocatorParams } from '@kbn/observability-shared-plugin/common'; +import { + AssetDetailsLocatorParams, + MetricsExplorerLocatorParams, +} from '@kbn/observability-shared-plugin/common'; import { MetricExpressionParams, METRIC_THRESHOLD_ALERT_TYPE_ID, @@ -54,8 +57,10 @@ const metricThresholdDefaultRecoveryMessage = i18n.translate( export function createMetricThresholdRuleType({ assetDetailsLocator, + metricsExplorerLocator, }: { assetDetailsLocator?: LocatorPublic; + metricsExplorerLocator?: LocatorPublic; }): ObservabilityRuleTypeModel { return { id: METRIC_THRESHOLD_ALERT_TYPE_ID, @@ -71,7 +76,7 @@ export function createMetricThresholdRuleType({ defaultActionMessage: metricThresholdDefaultActionMessage, defaultRecoveryMessage: metricThresholdDefaultRecoveryMessage, requiresAppContext: false, - format: getRuleFormat({ assetDetailsLocator }), + format: getRuleFormat({ assetDetailsLocator, metricsExplorerLocator }), alertDetailsAppSection: lazy(() => import('./components/alert_details_app_section')), priority: 10, }; diff --git a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/rule_data_formatters.ts b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/rule_data_formatters.ts index 85169903c68d9..17132055bd210 100644 --- a/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/rule_data_formatters.ts +++ b/x-pack/plugins/observability_solution/infra/public/alerting/metric_threshold/rule_data_formatters.ts @@ -8,15 +8,19 @@ import { ALERT_REASON, ALERT_RULE_PARAMETERS } from '@kbn/rule-data-utils'; import { ObservabilityRuleTypeFormatter } from '@kbn/observability-plugin/public'; import { LocatorPublic } from '@kbn/share-plugin/common'; -import type { AssetDetailsLocatorParams } from '@kbn/observability-shared-plugin/common'; +import type { + AssetDetailsLocatorParams, + MetricsExplorerLocatorParams, +} from '@kbn/observability-shared-plugin/common'; import { castArray } from 'lodash'; -import { METRICS_EXPLORER_URL } from '../../../common/constants'; import { getMetricsViewInAppUrl } from '../../../common/alerting/metrics/alert_link'; export const getRuleFormat = ({ assetDetailsLocator, + metricsExplorerLocator, }: { assetDetailsLocator?: LocatorPublic; + metricsExplorerLocator?: LocatorPublic; }): ObservabilityRuleTypeFormatter => { return ({ fields }) => { const reason = fields[ALERT_REASON] ?? '-'; @@ -26,12 +30,13 @@ export const getRuleFormat = ({ fields, groupBy: castArray(parameters?.groupBy as string[] | string), assetDetailsLocator, + metricsExplorerLocator, }); return { reason, link, - hasBasePath: link !== METRICS_EXPLORER_URL, + hasBasePath: true, }; }; }; diff --git a/x-pack/plugins/observability_solution/infra/public/plugin.ts b/x-pack/plugins/observability_solution/infra/public/plugin.ts index 52ca927cff4b9..c0bc2af260b96 100644 --- a/x-pack/plugins/observability_solution/infra/public/plugin.ts +++ b/x-pack/plugins/observability_solution/infra/public/plugin.ts @@ -16,7 +16,11 @@ import { } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import { enableInfrastructureHostsView } from '@kbn/observability-plugin/public'; -import { ObservabilityTriggerId } from '@kbn/observability-shared-plugin/common'; +import { + METRICS_EXPLORER_LOCATOR_ID, + MetricsExplorerLocatorParams, + ObservabilityTriggerId, +} from '@kbn/observability-shared-plugin/common'; import { BehaviorSubject, combineLatest, from } from 'rxjs'; import { map } from 'rxjs'; import type { EmbeddableApiContext } from '@kbn/presentation-publishing'; @@ -90,13 +94,17 @@ export class Plugin implements InfraClientPluginClass { pluginsSetup.share.url.locators.get(ASSET_DETAILS_LOCATOR_ID); const inventoryLocator = pluginsSetup.share.url.locators.get(INVENTORY_LOCATOR_ID); + const metricsExplorerLocator = + pluginsSetup.share.url.locators.get( + METRICS_EXPLORER_LOCATOR_ID + ); pluginsSetup.observability.observabilityRuleTypeRegistry.register( createInventoryMetricRuleType({ assetDetailsLocator, inventoryLocator }) ); pluginsSetup.observability.observabilityRuleTypeRegistry.register( - createMetricThresholdRuleType({ assetDetailsLocator }) + createMetricThresholdRuleType({ assetDetailsLocator, metricsExplorerLocator }) ); if (this.config.featureFlags.logsUIEnabled) { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.test.ts index 808b75cdb36c9..65c3e468e5e7e 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.test.ts @@ -10,6 +10,7 @@ import { flattenObject } from './utils'; describe('FlattenObject', () => { it('flattens multi level item', () => { const data = { + key0: 'value', key1: { item1: 'value 1', item2: { itemA: 'value 2' }, @@ -22,6 +23,7 @@ describe('FlattenObject', () => { const flatten = flattenObject(data); expect(flatten).toEqual({ + key0: 'value', 'key2.item3.itemA.itemAB': 'value AB', 'key2.item4': 'value 4', 'key1.item1': 'value 1', diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts index 73a7ed749446e..696bed5054576 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/common/utils.ts @@ -8,9 +8,8 @@ import { isEmpty, isError } from 'lodash'; import { schema } from '@kbn/config-schema'; import { Logger, LogMeta } from '@kbn/logging'; -import type { ElasticsearchClient, IBasePath } from '@kbn/core/server'; +import type { ElasticsearchClient } from '@kbn/core/server'; import { ObservabilityConfig } from '@kbn/observability-plugin/server'; -import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import { ALERT_RULE_PARAMETERS, TIMESTAMP } from '@kbn/rule-data-utils'; import { ParsedTechnicalFields, @@ -25,6 +24,7 @@ import type { LocatorPublic } from '@kbn/share-plugin/common'; import type { AssetDetailsLocatorParams, InventoryLocatorParams, + MetricsExplorerLocatorParams, } from '@kbn/observability-shared-plugin/common'; import { ALERT_RULE_PARAMETERS_NODE_TYPE, @@ -130,19 +130,15 @@ export const getAlertDetailsPageEnabledForApp = ( }; export const getInventoryViewInAppUrlWithSpaceId = ({ - basePath, criteria, nodeType, - spaceId, timestamp, hostName, assetDetailsLocator, inventoryLocator, }: { - basePath: IBasePath; criteria: InventoryMetricConditions[]; nodeType: string; - spaceId: string; timestamp: string; hostName?: string; assetDetailsLocator?: LocatorPublic; @@ -160,43 +156,37 @@ export const getInventoryViewInAppUrlWithSpaceId = ({ [HOST_NAME]: hostName, }; - return addSpaceIdToPath( - basePath.publicBaseUrl, - spaceId, - getInventoryViewInAppUrl({ - fields: parseTechnicalFields(fields, true), - assetDetailsLocator, - inventoryLocator, - }) - ); + return getInventoryViewInAppUrl({ + fields: parseTechnicalFields(fields, true), + assetDetailsLocator, + inventoryLocator, + }); }; export const getMetricsViewInAppUrlWithSpaceId = ({ - basePath, - spaceId, timestamp, groupBy, assetDetailsLocator, + metricsExplorerLocator, + additionalContext, }: { - basePath: IBasePath; - spaceId: string; timestamp: string; groupBy?: string[]; assetDetailsLocator?: LocatorPublic; + metricsExplorerLocator?: LocatorPublic; + additionalContext?: AdditionalContext; }) => { const fields = { + ...flattenAdditionalContext(additionalContext), [TIMESTAMP]: timestamp, }; - return addSpaceIdToPath( - basePath.publicBaseUrl, - spaceId, - getMetricsViewInAppUrl({ - fields: parseTechnicalFields(fields, true), - groupBy, - assetDetailsLocator, - }) - ); + return getMetricsViewInAppUrl({ + fields: parseTechnicalFields(fields, true), + groupBy, + assetDetailsLocator, + metricsExplorerLocator, + }); }; export const KUBERNETES_POD_UID = 'kubernetes.pod.uid'; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts index 2f621d04f38df..c54b29d52714f 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import rison from '@kbn/rison'; import { AlertInstanceContext as AlertContext, AlertInstanceState as AlertState, @@ -19,14 +20,33 @@ import type { LogMeta, Logger } from '@kbn/logging'; import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common'; import { createInventoryMetricThresholdExecutor } from './inventory_metric_threshold_executor'; import { ConditionResult } from './evaluate_condition'; -import { InfraBackendLibs } from '../../infra_types'; +import { InfraBackendLibs, InfraLocators } from '../../infra_types'; import { infraPluginMock } from '../../../mocks'; import { logsSharedPluginMock } from '@kbn/logs-shared-plugin/server/mocks'; import { createLogSourcesServiceMock } from '@kbn/logs-data-access-plugin/common/services/log_sources_service/log_sources_service.mocks'; import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; +import { + AssetDetailsLocator, + AssetDetailsLocatorParams, + InventoryLocator, + InventoryLocatorParams, +} from '@kbn/observability-shared-plugin/common'; jest.mock('./evaluate_condition', () => ({ evaluateCondition: jest.fn() })); +const mockAssetDetailsLocator = { + getRedirectUrl: jest + .fn() + .mockImplementation( + ({ assetId, assetType, assetDetails }: AssetDetailsLocatorParams) => + `/node-mock/${assetType}/${assetId}?receivedParams=${rison.encodeUnknown(assetDetails)}` + ), +} as unknown as jest.Mocked; + +const mockInventoryLocator = { + getRedirectUrl: jest.fn().mockImplementation(({}: InventoryLocatorParams) => `/inventory-mock`), +} as unknown as jest.Mocked; + interface AlertTestInstance { actionGroup: string; payload: any[]; @@ -182,7 +202,12 @@ function clearInstances() { alerts.clear(); } -const executor = createInventoryMetricThresholdExecutor(mockLibs); +const mockLocators = { + assetDetailsLocator: mockAssetDetailsLocator, + inventoryLocator: mockInventoryLocator, +} as unknown as InfraLocators; + +const executor = createInventoryMetricThresholdExecutor(mockLibs, mockLocators); const baseCriterion = { aggType: Aggregators.AVERAGE, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts index 9f8b3b6d0bfa0..d4c5a3b25c902 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/inventory_metric_threshold_executor.ts @@ -21,20 +21,9 @@ import { AlertInstanceState as AlertState, } from '@kbn/alerting-plugin/common'; import { AlertsClientError, RuleExecutorOptions, RuleTypeState } from '@kbn/alerting-plugin/server'; -import { - AlertsLocatorParams, - alertsLocatorID, - convertToBuiltInComparators, - getAlertUrl, -} from '@kbn/observability-plugin/common'; +import { convertToBuiltInComparators, getAlertUrl } from '@kbn/observability-plugin/common'; import type { InventoryItemType, SnapshotMetricType } from '@kbn/metrics-data-access-plugin/common'; import { ObservabilityMetricsAlert } from '@kbn/alerts-as-data-utils'; -import { - ASSET_DETAILS_LOCATOR_ID, - INVENTORY_LOCATOR_ID, - type AssetDetailsLocatorParams, - type InventoryLocatorParams, -} from '@kbn/observability-shared-plugin/common'; import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; import { AlertStates, @@ -45,7 +34,7 @@ import { createFormatter } from '../../../../common/formatters'; import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label'; import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats'; import { toMetricOpt } from '../../../../common/snapshot_metric_i18n'; -import { InfraBackendLibs } from '../../infra_types'; +import { InfraBackendLibs, InfraLocators } from '../../infra_types'; import { LogQueryFields } from '../../metrics/types'; import { buildErrorAlertReason, @@ -86,7 +75,10 @@ export type InventoryMetricThresholdAlert = Omit< }; export const createInventoryMetricThresholdExecutor = - (libs: InfraBackendLibs) => + ( + libs: InfraBackendLibs, + { alertsLocator, assetDetailsLocator, inventoryLocator }: InfraLocators + ) => async ( options: RuleExecutorOptions< InventoryMetricThresholdParams & Record, @@ -107,13 +99,6 @@ export const createInventoryMetricThresholdExecutor = getTimeRange, } = options; - const { share } = libs.plugins; - const alertsLocator = share.setup.url.locators.get(alertsLocatorID); - const assetDetailsLocator = - share.setup.url.locators.get(ASSET_DETAILS_LOCATOR_ID); - const inventoryLocator = - share.setup.url.locators.get(INVENTORY_LOCATOR_ID); - const startTime = Date.now(); const { criteria, filterQuery, sourceId = 'default', nodeType, alertOnNoData } = params; @@ -169,11 +154,9 @@ export const createInventoryMetricThresholdExecutor = timestamp: startedAt.toISOString(), value: null, viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ - basePath: libs.basePath, criteria, nodeType, timestamp: indexedStartedAt, - spaceId, assetDetailsLocator, inventoryLocator, }), @@ -326,11 +309,9 @@ export const createInventoryMetricThresholdExecutor = formatMetric(result[group].metric, result[group].currentValue) ), viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ - basePath: libs.basePath, criteria, nodeType, timestamp: indexedStartedAt, - spaceId, hostName: additionalContext?.host?.name, assetDetailsLocator, inventoryLocator, @@ -378,11 +359,9 @@ export const createInventoryMetricThresholdExecutor = threshold: mapToConditionsLookup(criteria, (c) => c.threshold), timestamp: startedAt.toISOString(), viewInAppUrl: getInventoryViewInAppUrlWithSpaceId({ - basePath: libs.basePath, criteria, nodeType, timestamp: indexedStartedAt, - spaceId, hostName: additionalContext?.host?.name, assetDetailsLocator, inventoryLocator, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts index 4f124b8327841..7b69d76ba0ce9 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_rule_type.ts @@ -24,7 +24,7 @@ import { } from '../../../../common/http_api'; import type { InfraConfig } from '../../../../common/plugin_config_types'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; -import { InfraBackendLibs } from '../../infra_types'; +import { InfraBackendLibs, InfraLocators } from '../../infra_types'; import { alertDetailUrlActionVariableDescription, alertStateActionVariableDescription, @@ -83,7 +83,8 @@ const groupActionVariableDescription = i18n.translate( export function registerInventoryThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs, - { featureFlags }: InfraConfig + { featureFlags }: InfraConfig, + locators: InfraLocators ) { if (!featureFlags.inventoryThresholdAlertRuleEnabled) { return; @@ -123,7 +124,7 @@ export function registerInventoryThresholdRuleType( producer: 'infrastructure', minimumLicenseRequired: 'basic', isExportable: true, - executor: createInventoryMetricThresholdExecutor(libs), + executor: createInventoryMetricThresholdExecutor(libs, locators), actionVariables: { context: [ { name: 'group', description: groupActionVariableDescription }, diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 44cd61943df49..47266cee5ec06 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -6,6 +6,7 @@ */ import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; +import rison from '@kbn/rison'; import { getThresholds } from '../common/get_values'; import { set } from '@kbn/safer-lodash-set'; import { COMPARATORS } from '@kbn/alerting-comparators'; @@ -32,6 +33,11 @@ import { } from '@kbn/rule-data-utils'; import { type Group } from '@kbn/observability-alerting-rule-utils'; import { sharePluginMock } from '@kbn/share-plugin/public/mocks'; +import { + AssetDetailsLocatorParams, + MetricsExplorerLocatorParams, +} from '@kbn/observability-shared-plugin/common'; +import { InfraLocators } from '../../infra_types'; jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() })); @@ -52,6 +58,14 @@ const mockNow = new Date('2023-09-20T15:11:04.105Z'); const STARTED_AT_MOCK_DATE = new Date(); +const mockAssetDetailsLocator = { + getRedirectUrl: jest.fn(), +}; + +const mockMetricsExplorerLocator = { + getRedirectUrl: jest.fn(), +}; + const mockOptions = { executionId: '', startedAt: mockNow, @@ -102,6 +116,15 @@ describe('The metric threshold rule type', () => { beforeEach(() => { jest.resetAllMocks(); + mockAssetDetailsLocator.getRedirectUrl.mockImplementation( + ({ assetId, assetType, assetDetails }: AssetDetailsLocatorParams) => + `/node-mock/${assetType}/${assetId}?receivedParams=${rison.encodeUnknown(assetDetails)}` + ); + + mockMetricsExplorerLocator.getRedirectUrl.mockImplementation( + ({}: MetricsExplorerLocatorParams) => `/metrics-mock` + ); + services.alertsClient.report.mockImplementation(({ id }: { id: string }) => ({ uuid: `uuid-${id}`, start: new Date().toISOString(), @@ -2366,8 +2389,7 @@ describe('The metric threshold rule type', () => { group: id, reason, timestamp: mockNow.toISOString(), - viewInAppUrl: 'http://localhost:5601/app/metrics/explorer', - + viewInAppUrl: '/metrics-mock', metric: conditions.reduce((acc, curr, ndx) => { set(acc, `condition${ndx}`, curr.metric); return acc; @@ -2482,7 +2504,10 @@ const mockLibs: any = { logger, }; -const executor = createMetricThresholdExecutor(mockLibs); +const executor = createMetricThresholdExecutor(mockLibs, { + assetDetailsLocator: mockAssetDetailsLocator, + metricsExplorerLocator: mockMetricsExplorerLocator, +} as unknown as InfraLocators); const services = alertsMock.createRuleExecutorServices(); services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId: string) => { diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 258a410d4775c..a98253553e4f2 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -20,24 +20,15 @@ import { RecoveredActionGroup, } from '@kbn/alerting-plugin/common'; import { AlertsClientError, RuleExecutorOptions, RuleTypeState } from '@kbn/alerting-plugin/server'; -import { - AlertsLocatorParams, - TimeUnitChar, - alertsLocatorID, - getAlertUrl, -} from '@kbn/observability-plugin/common'; +import { TimeUnitChar, getAlertUrl } from '@kbn/observability-plugin/common'; import { ObservabilityMetricsAlert } from '@kbn/alerts-as-data-utils'; import { COMPARATORS } from '@kbn/alerting-comparators'; import { getEcsGroups, type Group } from '@kbn/observability-alerting-rule-utils'; import { convertToBuiltInComparators } from '@kbn/observability-plugin/common/utils/convert_legacy_outside_comparator'; -import { - ASSET_DETAILS_LOCATOR_ID, - AssetDetailsLocatorParams, -} from '@kbn/observability-shared-plugin/common'; import { getOriginalActionGroup } from '../../../utils/get_original_action_group'; import { AlertStates } from '../../../../common/alerting/metrics'; import { createFormatter } from '../../../../common/formatters'; -import { InfraBackendLibs } from '../../infra_types'; +import { InfraBackendLibs, InfraLocators } from '../../infra_types'; import { buildFiredAlertReason, buildInvalidQueryAlertReason, @@ -108,8 +99,13 @@ type MetricThresholdAlertReporter = (params: { thresholds?: Array; }) => void; +// TODO: Refactor the executor code to have better flow-control with better +// reasoning of different state/conditions for improved maintainability export const createMetricThresholdExecutor = - (libs: InfraBackendLibs) => + ( + libs: InfraBackendLibs, + { alertsLocator, assetDetailsLocator, metricsExplorerLocator }: InfraLocators + ) => async ( options: RuleExecutorOptions< MetricThresholdRuleParams, @@ -120,11 +116,6 @@ export const createMetricThresholdExecutor = MetricThresholdAlert > ) => { - const { share } = libs.plugins; - const alertsLocator = share.setup.url.locators.get(alertsLocatorID); - const assetDetailsLocator = - share.setup.url.locators.get(ASSET_DETAILS_LOCATOR_ID); - const startTime = Date.now(); const { @@ -215,12 +206,12 @@ export const createMetricThresholdExecutor = reason, timestamp, value: null, + // TODO: Check if we need additionalContext here or not? viewInAppUrl: getMetricsViewInAppUrlWithSpaceId({ - basePath: libs.basePath, - spaceId, timestamp, groupBy, assetDetailsLocator, + metricsExplorerLocator, }), }; @@ -425,11 +416,11 @@ export const createMetricThresholdExecutor = }).currentValue; }), viewInAppUrl: getMetricsViewInAppUrlWithSpaceId({ - basePath: libs.basePath, - spaceId, timestamp, groupBy, assetDetailsLocator, + metricsExplorerLocator, + additionalContext, }), ...additionalContext, }; @@ -484,11 +475,11 @@ export const createMetricThresholdExecutor = timestamp, threshold: mapToConditionsLookup(criteria, (c) => c.threshold), viewInAppUrl: getMetricsViewInAppUrlWithSpaceId({ - basePath: libs.basePath, - spaceId, timestamp: indexedStartedAt, groupBy, assetDetailsLocator, + metricsExplorerLocator, + additionalContext, }), originalAlertState: translateActionGroupToAlertState(originalActionGroup), diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts index 8bd8aafcbf207..6369465773cf7 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/metric_threshold/register_metric_threshold_rule_type.ts @@ -15,7 +15,7 @@ import { LEGACY_COMPARATORS } from '@kbn/observability-plugin/common/utils/conve import type { InfraConfig } from '../../../../common/plugin_config_types'; import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../../common/alerting/metrics'; import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api'; -import { InfraBackendLibs } from '../../infra_types'; +import { InfraBackendLibs, InfraLocators } from '../../infra_types'; import { alertDetailUrlActionVariableDescription, alertStateActionVariableDescription, @@ -48,7 +48,8 @@ import { O11Y_AAD_FIELDS } from '../../../../common/constants'; export function registerMetricThresholdRuleType( alertingPlugin: PluginSetupContract, libs: InfraBackendLibs, - { featureFlags }: InfraConfig + { featureFlags }: InfraConfig, + locators: InfraLocators ) { if (!featureFlags.metricThresholdAlertRuleEnabled) { return; @@ -142,7 +143,7 @@ export function registerMetricThresholdRuleType( actionGroups: [FIRED_ACTIONS, WARNING_ACTIONS, NO_DATA_ACTIONS], minimumLicenseRequired: 'basic', isExportable: true, - executor: createMetricThresholdExecutor(libs), + executor: createMetricThresholdExecutor(libs, locators), doesSetRecoveryContext: true, actionVariables: { context: [ diff --git a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts index 61f26348ca53f..125c3135a235d 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/alerting/register_rule_types.ts @@ -10,7 +10,7 @@ import { type IRuleTypeAlerts, PluginSetupContract } from '@kbn/alerting-plugin/ import { registerMetricThresholdRuleType } from './metric_threshold/register_metric_threshold_rule_type'; import { registerInventoryThresholdRuleType } from './inventory_metric_threshold/register_inventory_metric_threshold_rule_type'; import { registerLogThresholdRuleType } from './log_threshold/register_log_threshold_rule_type'; -import { InfraBackendLibs } from '../infra_types'; +import { InfraBackendLibs, InfraLocators } from '../infra_types'; import type { InfraConfig } from '../../types'; import { MetricThresholdAlert } from './metric_threshold/metric_threshold_executor'; import { LogThresholdAlert } from './log_threshold/log_threshold_executor'; @@ -38,7 +38,8 @@ export const MetricsRulesTypeAlertDefinition: IRuleTypeAlerts { if (alertingPlugin) { const registerFns = [ @@ -47,7 +48,7 @@ const registerRuleTypes = ( registerMetricThresholdRuleType, ]; registerFns.forEach((fn) => { - fn(alertingPlugin, libs, config); + fn(alertingPlugin, libs, config, locators); }); } }; diff --git a/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts b/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts index f13424c6331d3..10f80fbe86c76 100644 --- a/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts +++ b/x-pack/plugins/observability_solution/infra/server/lib/infra_types.ts @@ -10,6 +10,12 @@ import type { IBasePath } from '@kbn/core/server'; import type { handleEsError } from '@kbn/es-ui-shared-plugin/server'; import { ObservabilityConfig } from '@kbn/observability-plugin/server'; import type { ILogsSharedLogEntriesDomain } from '@kbn/logs-shared-plugin/server'; +import type { + AssetDetailsLocator, + InventoryLocator, + MetricsExplorerLocator, +} from '@kbn/observability-shared-plugin/common'; +import type { AlertsLocator } from '@kbn/observability-plugin/common'; import { RulesServiceSetup } from '../services/rules'; import { InfraConfig, InfraPluginStartServicesAccessor } from '../types'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; @@ -46,3 +52,10 @@ export interface InfraBackendLibs extends InfraDomainLibs { logger: Logger; plugins: Plugins; } + +export interface InfraLocators { + alertsLocator?: AlertsLocator; + assetDetailsLocator: AssetDetailsLocator; + metricsExplorerLocator: MetricsExplorerLocator; + inventoryLocator: InventoryLocator; +} diff --git a/x-pack/plugins/observability_solution/infra/server/plugin.ts b/x-pack/plugins/observability_solution/infra/server/plugin.ts index 530dec8bc1ca4..6b1d1e4ea4b63 100644 --- a/x-pack/plugins/observability_solution/infra/server/plugin.ts +++ b/x-pack/plugins/observability_solution/infra/server/plugin.ts @@ -18,6 +18,12 @@ import { i18n } from '@kbn/i18n'; import { Logger } from '@kbn/logging'; import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common'; import { GetMetricIndicesOptions } from '@kbn/metrics-data-access-plugin/server'; +import { + AssetDetailsLocatorDefinition, + InventoryLocatorDefinition, + MetricsExplorerLocatorDefinition, +} from '@kbn/observability-shared-plugin/common'; +import { type AlertsLocatorParams, alertsLocatorID } from '@kbn/observability-plugin/common'; import { mapValues } from 'lodash'; import { LOGS_FEATURE_ID, METRICS_FEATURE_ID } from '../common/constants'; import { publicConfigKeys } from '../common/plugin_config_types'; @@ -192,6 +198,15 @@ export class InfraServerPlugin { sources } ); + const alertsLocator = plugins.share.url.locators.get(alertsLocatorID); + const assetDetailsLocator = plugins.share.url.locators.create( + new AssetDetailsLocatorDefinition() + ); + const metricsExplorerLocator = plugins.share.url.locators.create( + new MetricsExplorerLocatorDefinition() + ); + const inventoryLocator = plugins.share.url.locators.create(new InventoryLocatorDefinition()); + // Setup infra services const inventoryViews = this.inventoryViews.setup(); const metricsExplorerViews = this.metricsExplorerViews?.setup(); @@ -268,7 +283,12 @@ export class InfraServerPlugin ]); } - registerRuleTypes(plugins.alerting, this.libs, this.config); + registerRuleTypes(plugins.alerting, this.libs, this.config, { + alertsLocator, + assetDetailsLocator, + metricsExplorerLocator, + inventoryLocator, + }); core.http.registerRouteHandlerContext( 'infra', diff --git a/x-pack/plugins/observability_solution/observability/common/index.ts b/x-pack/plugins/observability_solution/observability/common/index.ts index 07effc9293676..3dc44c5ac02aa 100644 --- a/x-pack/plugins/observability_solution/observability/common/index.ts +++ b/x-pack/plugins/observability_solution/observability/common/index.ts @@ -88,6 +88,6 @@ export const sloListLocatorID = 'SLO_LIST_LOCATOR'; import { paths } from './locators/paths'; export const observabilityPaths = paths.observability; -export type { AlertsLocatorParams } from './locators/alerts'; +export type { AlertsLocator, AlertsLocatorParams } from './locators/alerts'; export { AlertsLocatorDefinition } from './locators/alerts'; export { observabilityAlertFeatureIds } from './constants'; diff --git a/x-pack/plugins/observability_solution/observability/common/locators/alerts.ts b/x-pack/plugins/observability_solution/observability/common/locators/alerts.ts index a5ce97a1aab87..5d779598fd337 100644 --- a/x-pack/plugins/observability_solution/observability/common/locators/alerts.ts +++ b/x-pack/plugins/observability_solution/observability/common/locators/alerts.ts @@ -10,11 +10,13 @@ import rison from '@kbn/rison'; import { url as urlUtils } from '@kbn/kibana-utils-plugin/common'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/common'; import type { SerializableRecord } from '@kbn/utility-types'; -import type { LocatorDefinition } from '@kbn/share-plugin/public'; +import type { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/public'; import { alertsLocatorID } from '..'; import { ALERTS_URL_STORAGE_KEY } from '../constants'; import type { AlertStatus } from '../typings'; +export type AlertsLocator = LocatorPublic; + export interface AlertsLocatorParams extends SerializableRecord { baseUrl: string; spaceId: string; diff --git a/x-pack/plugins/observability_solution/observability/server/plugin.ts b/x-pack/plugins/observability_solution/observability/server/plugin.ts index 13d60368cd8d5..4b04ac032a82c 100644 --- a/x-pack/plugins/observability_solution/observability/server/plugin.ts +++ b/x-pack/plugins/observability_solution/observability/server/plugin.ts @@ -99,6 +99,7 @@ export class ObservabilityPlugin implements Plugin { const config = this.initContext.config.get(); const alertsLocator = plugins.share.url.locators.create(new AlertsLocatorDefinition()); + const logsExplorerLocator = plugins.share.url.locators.get(LOGS_EXPLORER_LOCATOR_ID); diff --git a/x-pack/plugins/observability_solution/observability_shared/common/index.ts b/x-pack/plugins/observability_solution/observability_shared/common/index.ts index 550ddbcc329c2..7889eda66bd4a 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/index.ts @@ -161,6 +161,8 @@ export type { HostsLocatorParams, InventoryLocator, InventoryLocatorParams, + MetricsExplorerLocator, + MetricsExplorerLocatorParams, FlamegraphLocatorParams, FlamegraphLocator, StacktracesLocatorParams, @@ -179,6 +181,8 @@ export { HostsLocatorDefinition, INVENTORY_LOCATOR_ID, InventoryLocatorDefinition, + METRICS_EXPLORER_LOCATOR_ID, + MetricsExplorerLocatorDefinition, FlamegraphLocatorDefinition, StacktracesLocatorDefinition, TopNFunctionsLocatorDefinition, diff --git a/x-pack/plugins/observability_solution/observability_shared/common/locators/index.ts b/x-pack/plugins/observability_solution/observability_shared/common/locators/index.ts index f8f8ae7bbb433..98604adc201a2 100644 --- a/x-pack/plugins/observability_solution/observability_shared/common/locators/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/common/locators/index.ts @@ -11,6 +11,7 @@ export * from './infra/asset_details_flyout_locator'; export * from './infra/asset_details_locator'; export * from './infra/hosts_locator'; export * from './infra/inventory_locator'; +export * from './infra/metrics_explorer_locator'; export * from './profiling/flamegraph_locator'; export * from './profiling/stacktraces_locator'; export * from './profiling/topn_functions_locator'; diff --git a/x-pack/plugins/observability_solution/observability_shared/common/locators/infra/metrics_explorer_locator.ts b/x-pack/plugins/observability_solution/observability_shared/common/locators/infra/metrics_explorer_locator.ts new file mode 100644 index 0000000000000..e669a301e5134 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_shared/common/locators/infra/metrics_explorer_locator.ts @@ -0,0 +1,29 @@ +/* + * 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 type { SerializableRecord } from '@kbn/utility-types'; +import { LocatorDefinition, LocatorPublic } from '@kbn/share-plugin/common'; + +export type MetricsExplorerLocator = LocatorPublic; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface MetricsExplorerLocatorParams extends SerializableRecord {} + +export const METRICS_EXPLORER_LOCATOR_ID = 'METRICS_EXPLORER_LOCATOR'; + +export class MetricsExplorerLocatorDefinition + implements LocatorDefinition +{ + public readonly id = METRICS_EXPLORER_LOCATOR_ID; + + public readonly getLocation = async (params: MetricsExplorerLocatorParams) => { + return { + app: 'metrics', + path: `/explorer`, + state: {}, + }; + }; +} diff --git a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts index 2ac6a69c6a0d9..723ab4f758af4 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/plugin.ts @@ -25,6 +25,7 @@ import { AssetDetailsLocatorDefinition, HostsLocatorDefinition, InventoryLocatorDefinition, + MetricsExplorerLocatorDefinition, FlamegraphLocatorDefinition, StacktracesLocatorDefinition, TopNFunctionsLocatorDefinition, @@ -39,6 +40,7 @@ import { type TopNFunctionsLocator, type ServiceOverviewLocator, type TransactionDetailsByNameLocator, + type MetricsExplorerLocator, } from '../common'; import { updateGlobalNavigation } from './services/update_global_navigation'; export interface ObservabilitySharedSetup { @@ -63,6 +65,7 @@ interface ObservabilitySharedLocators { assetDetailsFlyoutLocator: AssetDetailsFlyoutLocator; hostsLocator: HostsLocator; inventoryLocator: InventoryLocator; + metricsExplorerLocator: MetricsExplorerLocator; }; profiling: { flamegraphLocator: FlamegraphLocator; @@ -133,6 +136,7 @@ export class ObservabilitySharedPlugin implements Plugin { ), hostsLocator: urlService.locators.create(new HostsLocatorDefinition()), inventoryLocator: urlService.locators.create(new InventoryLocatorDefinition()), + metricsExplorerLocator: urlService.locators.create(new MetricsExplorerLocatorDefinition()), }, profiling: { flamegraphLocator: urlService.locators.create(new FlamegraphLocatorDefinition()),