diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index 89dcba5637787..61dcf1a17df6b 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -10,6 +10,8 @@ import { CommonAlertParamDetail, ExpressionConfig } from './types/alerts'; import { AlertParamType } from './enums'; import { validateDuration } from './validate_duration'; +export const USAGE_COLLECTION_APP_NAME = 'stack_monitoring'; + /** * Helper string to add as a tag in every logging call */ diff --git a/x-pack/plugins/monitoring/kibana.jsonc b/x-pack/plugins/monitoring/kibana.jsonc index 334ffd05890bc..5ba71cdc96ee3 100644 --- a/x-pack/plugins/monitoring/kibana.jsonc +++ b/x-pack/plugins/monitoring/kibana.jsonc @@ -18,8 +18,6 @@ "features", "data", "navigation", - "observability", - "observabilityShared", "dataViews", "unifiedSearch", "share" @@ -42,4 +40,4 @@ "kibanaReact", ] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/monitoring/public/application/hooks/use_track_metric.tsx b/x-pack/plugins/monitoring/public/application/hooks/use_track_metric.tsx new file mode 100644 index 0000000000000..863f1af6422be --- /dev/null +++ b/x-pack/plugins/monitoring/public/application/hooks/use_track_metric.tsx @@ -0,0 +1,88 @@ +/* + * 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 { useEffect, useMemo } from 'react'; +import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; +import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { USAGE_COLLECTION_APP_NAME } from '../../../common/constants'; + +/** + * Note: The usage_collection plugin will take care of sending this data to the telemetry server. + * You can find the metrics that are collected by these hooks in Stack Telemetry. + * Search the index `kibana-ui-counter`. You can filter for `eventName` and/or `appName`. + */ + +interface TrackOptions { + metricType?: UiCounterMetricType; + delay?: number; // in ms +} +type EffectDeps = unknown[]; + +interface ServiceDeps { + usageCollection: UsageCollectionSetup; // TODO: This should really be start. Looking into it. +} + +export type TrackMetricOptions = TrackOptions & { metric: string }; +export type UiTracker = ReturnType; +export type TrackEvent = (options: TrackMetricOptions) => void; + +export { METRIC_TYPE }; + +export function useUiTracker(): TrackEvent { + const reportUiCounter = useKibana().services?.usageCollection?.reportUiCounter; + const trackEvent = useMemo(() => { + return ({ metric, metricType = METRIC_TYPE.COUNT }: TrackMetricOptions) => { + if (reportUiCounter) { + reportUiCounter(USAGE_COLLECTION_APP_NAME, metricType, metric); + } + }; + }, [reportUiCounter]); + return trackEvent; +} + +export function useTrackMetric( + { metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions, + effectDependencies: EffectDeps = [] +) { + const reportUiCounter = useKibana().services?.usageCollection?.reportUiCounter; + + useEffect(() => { + if (!reportUiCounter) { + // eslint-disable-next-line no-console + console.log( + 'usageCollection.reportUiCounter is unavailable. Ensure this is setup via .' + ); + } else { + let decoratedMetric = metric; + if (delay > 0) { + decoratedMetric += `__delayed_${delay}ms`; + } + const id = setTimeout( + () => reportUiCounter(USAGE_COLLECTION_APP_NAME, metricType, decoratedMetric), + Math.max(delay, 0) + ); + return () => clearTimeout(id); + } + // the dependencies are managed externally + // eslint-disable-next-line react-hooks/exhaustive-deps + }, effectDependencies); +} + +/** + * useTrackPageview is a convenience wrapper for tracking a pageview + * Its metrics will be found at: + * stack_stats.kibana.plugins.ui_metric.{app}.pageview__{path}(__delayed_{n}ms)? + */ +type TrackPageviewProps = TrackOptions & { path: string }; + +export function useTrackPageview( + { path, ...rest }: TrackPageviewProps, + effectDependencies: EffectDeps = [] +) { + useTrackMetric({ ...rest, metric: `pageview__${path}` }, effectDependencies); +} diff --git a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx index 88868295b0bca..befe47b3f7c64 100644 --- a/x-pack/plugins/monitoring/public/application/pages/page_template.tsx +++ b/x-pack/plugins/monitoring/public/application/pages/page_template.tsx @@ -9,7 +9,6 @@ import { EuiPage, EuiPageBody, EuiPageTemplate, EuiTab, EuiTabs, EuiSpacer } fro import React, { useContext, useState, useEffect, useCallback, FC, PropsWithChildren } from 'react'; import { useHistory } from 'react-router-dom'; import type { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; -import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; import { useTitle } from '../hooks/use_title'; import { MonitoringToolbar } from '../../components/shared/toolbar'; import { useMonitoringTimeContainerContext } from '../hooks/use_monitoring_time'; @@ -25,6 +24,7 @@ import { AlertsDropdown } from '../../alerts/alerts_dropdown'; import { useRequestErrorHandler } from '../hooks/use_request_error_handler'; import { SetupModeToggleButton } from '../../components/setup_mode/toggle_button'; import { HeaderActionMenuContext } from '../contexts/header_action_menu_context'; +import { HeaderMenuPortal } from '../../components/header_menu'; export interface TabMenuItem { id: string; diff --git a/x-pack/plugins/monitoring/public/components/header_menu/header_menu_portal.test.tsx b/x-pack/plugins/monitoring/public/components/header_menu/header_menu_portal.test.tsx new file mode 100644 index 0000000000000..055c974dcf6db --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/header_menu/header_menu_portal.test.tsx @@ -0,0 +1,30 @@ +/* + * 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 { render } from '@testing-library/react'; +import React from 'react'; +import HeaderMenuPortal from './header_menu_portal'; +import { themeServiceMock } from '@kbn/core/public/mocks'; + +describe('HeaderMenuPortal', () => { + describe('when unmounted', () => { + it('calls setHeaderActionMenu with undefined', () => { + const setHeaderActionMenu = jest.fn(); + const theme$ = themeServiceMock.createTheme$(); + + const { unmount } = render( + + test + + ); + + unmount(); + + expect(setHeaderActionMenu).toHaveBeenCalledWith(undefined); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/public/components/header_menu/header_menu_portal.tsx b/x-pack/plugins/monitoring/public/components/header_menu/header_menu_portal.tsx new file mode 100644 index 0000000000000..4f7c4bb817e3e --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/header_menu/header_menu_portal.tsx @@ -0,0 +1,34 @@ +/* + * 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 React, { useEffect, useMemo } from 'react'; +import { createHtmlPortalNode, InPortal, OutPortal } from 'react-reverse-portal'; +import { toMountPoint } from '@kbn/kibana-react-plugin/public'; +import type { HeaderMenuPortalProps } from '../../types'; + +// eslint-disable-next-line import/no-default-export +export default function HeaderMenuPortal({ + children, + setHeaderActionMenu, + theme$, +}: HeaderMenuPortalProps) { + const portalNode = useMemo(() => createHtmlPortalNode(), []); + + useEffect(() => { + setHeaderActionMenu((element) => { + const mount = toMountPoint(, { theme$ }); + return mount(element); + }); + + return () => { + portalNode.unmount(); + setHeaderActionMenu(undefined); + }; + }, [portalNode, setHeaderActionMenu, theme$]); + + return {children}; +} diff --git a/x-pack/plugins/monitoring/public/components/header_menu/index.tsx b/x-pack/plugins/monitoring/public/components/header_menu/index.tsx new file mode 100644 index 0000000000000..f0f39d1f12a3f --- /dev/null +++ b/x-pack/plugins/monitoring/public/components/header_menu/index.tsx @@ -0,0 +1,20 @@ +/* + * 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 React, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { HeaderMenuPortalProps } from '../../types'; + +const HeaderMenuPortalLazy = lazy(() => import('./header_menu_portal')); + +export function HeaderMenuPortal(props: HeaderMenuPortalProps) { + return ( + }> + + + ); +} diff --git a/x-pack/plugins/monitoring/public/components/page_loading/index.tsx b/x-pack/plugins/monitoring/public/components/page_loading/index.tsx index 3d2d788a787ae..2b8b7ed522e91 100644 --- a/x-pack/plugins/monitoring/public/components/page_loading/index.tsx +++ b/x-pack/plugins/monitoring/public/components/page_loading/index.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiPage, EuiPageBody, EuiPageTemplate, EuiLoadingSpinner } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; +import { useTrackPageview } from '../../application/hooks/use_track_metric'; function PageLoadingUI() { return ( @@ -29,8 +29,8 @@ const PageLoadingTracking: React.FunctionComponent<{ pageViewTitle: string }> = pageViewTitle, }) => { const path = pageViewTitle.toLowerCase().replace(/-/g, '').replace(/\s+/g, '_'); - useTrackPageview({ app: 'stack_monitoring', path }); - useTrackPageview({ app: 'stack_monitoring', path, delay: 15000 }); + useTrackPageview({ path }); + useTrackPageview({ path, delay: 15000 }); return ; }; diff --git a/x-pack/plugins/monitoring/public/components/setup_mode/toggle_button.tsx b/x-pack/plugins/monitoring/public/components/setup_mode/toggle_button.tsx index 90d90075f1c49..22c638f8f1a6f 100644 --- a/x-pack/plugins/monitoring/public/components/setup_mode/toggle_button.tsx +++ b/x-pack/plugins/monitoring/public/components/setup_mode/toggle_button.tsx @@ -8,9 +8,10 @@ import React from 'react'; import { EuiButton } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { METRIC_TYPE, useUiTracker } from '@kbn/observability-shared-plugin/public'; +import { METRIC_TYPE } from '@kbn/analytics'; import { TELEMETRY_METRIC_BUTTON_CLICK } from '../../../common/constants'; import { SetupModeExitButton } from './exit_button'; +import { useUiTracker } from '../../application/hooks/use_track_metric'; export interface SetupModeToggleButtonProps { enabled: boolean; @@ -21,7 +22,7 @@ export const SetupModeToggleButton: React.FC = ( props: SetupModeToggleButtonProps ) => { const [isLoading, setIsLoading] = React.useState(false); - const trackStat = useUiTracker({ app: 'stack_monitoring' }); + const trackStat = useUiTracker(); function toggleSetupMode(enabled: boolean, stat: string) { setIsLoading(true); diff --git a/x-pack/plugins/monitoring/public/types.ts b/x-pack/plugins/monitoring/public/types.ts index cd5314521f947..1df9894ca9327 100644 --- a/x-pack/plugins/monitoring/public/types.ts +++ b/x-pack/plugins/monitoring/public/types.ts @@ -17,6 +17,7 @@ import { DashboardStart } from '@kbn/dashboard-plugin/public'; import { FleetStart } from '@kbn/fleet-plugin/public'; import type { InfraClientStartExports } from '@kbn/infra-plugin/public'; import { SharePluginStart } from '@kbn/share-plugin/public'; +import { ReactNode } from 'react'; export interface MonitoringStartPluginDependencies { navigation: NavigationStart; @@ -43,3 +44,9 @@ export type LegacyMonitoringStartPluginDependencies = MonitoringStartPluginDepen LegacyStartDependencies; export type MonitoringStartServices = CoreStart & MonitoringStartPluginDependencies; + +export interface HeaderMenuPortalProps { + children: ReactNode; + setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; + theme$: AppMountParameters['theme$']; +} diff --git a/x-pack/plugins/monitoring/tsconfig.json b/x-pack/plugins/monitoring/tsconfig.json index 6be14087cc11d..56063b3e06ceb 100644 --- a/x-pack/plugins/monitoring/tsconfig.json +++ b/x-pack/plugins/monitoring/tsconfig.json @@ -38,7 +38,6 @@ "@kbn/dashboard-plugin", "@kbn/fleet-plugin", "@kbn/shared-ux-router", - "@kbn/observability-shared-plugin", "@kbn/shared-ux-link-redirect-app", "@kbn/alerts-as-data-utils", "@kbn/rule-data-utils", @@ -48,6 +47,7 @@ "@kbn/ui-theme", "@kbn/core-elasticsearch-server", "@kbn/share-plugin", + "@kbn/analytics", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/observability_solution/exploratory_view/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability_solution/exploratory_view/public/typings/fetch_overview_data/index.ts index 67a2663aca27a..f338354d52265 100644 --- a/x-pack/plugins/observability_solution/exploratory_view/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability_solution/exploratory_view/public/typings/fetch_overview_data/index.ts @@ -75,12 +75,7 @@ export type HasData = ( export type ObservabilityFetchDataPlugins = Exclude< ObservabilityApp, - | 'observability-overview' - | 'stack_monitoring' - | 'fleet' - | 'synthetics' - | 'profiling' - | 'observability-onboarding' + 'observability-overview' | 'fleet' | 'synthetics' | 'profiling' | 'observability-onboarding' >; export interface DataHandler< diff --git a/x-pack/plugins/observability_solution/observability/public/typings/fetch_overview_data/index.ts b/x-pack/plugins/observability_solution/observability/public/typings/fetch_overview_data/index.ts index 99943d305f659..7848ab3df3cba 100644 --- a/x-pack/plugins/observability_solution/observability/public/typings/fetch_overview_data/index.ts +++ b/x-pack/plugins/observability_solution/observability/public/typings/fetch_overview_data/index.ts @@ -80,7 +80,7 @@ export type HasData = ( export type ObservabilityFetchDataPlugins = Exclude< ObservabilityApp, - 'observability-overview' | 'stack_monitoring' | 'fleet' | 'synthetics' + 'observability-overview' | 'fleet' | 'synthetics' >; export interface DataHandler< diff --git a/x-pack/plugins/observability_solution/observability/typings/common.ts b/x-pack/plugins/observability_solution/observability/typings/common.ts index 06e78c4ec6e64..ccc58c89077b7 100644 --- a/x-pack/plugins/observability_solution/observability/typings/common.ts +++ b/x-pack/plugins/observability_solution/observability/typings/common.ts @@ -15,7 +15,6 @@ export type ObservabilityApp = | 'uptime' | 'synthetics' | 'observability-overview' - | 'stack_monitoring' | 'ux' | 'fleet' | 'universal_profiling'; diff --git a/x-pack/plugins/observability_solution/observability_shared/typings/common.ts b/x-pack/plugins/observability_solution/observability_shared/typings/common.ts index 83ad51daf0b2d..6e7340700259a 100644 --- a/x-pack/plugins/observability_solution/observability_shared/typings/common.ts +++ b/x-pack/plugins/observability_solution/observability_shared/typings/common.ts @@ -13,7 +13,6 @@ export type ObservabilityApp = | 'uptime' | 'synthetics' | 'observability-overview' - | 'stack_monitoring' | 'ux' | 'fleet' | 'profiling'