Skip to content

Commit

Permalink
[Stack Monitoring] Remove 'observability' and 'observabilityShared' p…
Browse files Browse the repository at this point in the history
…lugin dependencies (elastic#203492)

### Summary
A recent [bug](elastic#199902) that
affected some of the pages in Stack Monitoring was caused by changes
related to the locators of the logs-related apps.

The goal of this PR is to reduce the number of Observability
dependencies that could potentially cause issues in the app by removing
the `observability` and `observabilityShared` plugin dependencies from
the `monitoring` plugin.

Currently, the `monitoring` plugin is [categorised as
observability](https://github.com/elastic/kibana/blob/main/x-pack/plugins/monitoring/kibana.jsonc#L7)
but when the dependency on the `infra` plugin is removed, it can be
marked as a `platform` plugin.

### Notes for reviewers
- The components used to render the header menu as well as the
[use_track_metric](https://github.com/elastic/kibana/pull/203492/files#diff-7e39fc60ca80ee551d824ca97f9f879e3364a368a5736cf9178b5943a12ca7a7)
hook were copied from the `observabilityShared` plugin
- There should be no UX and functionality changes in the stack
monitoring header
- Usage collection could be verified by searching for UI counters sent
by the cluster created for this PR, once telemetry has been sent

### Testing
The stateful environment deployed by this PR includes logs and metrics
for stack monitoring. Please make sure to select a larger time range
(e.g. last 14 days).

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people authored Dec 10, 2024
1 parent 305bb1b commit 7e2f67e
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 19 deletions.
2 changes: 2 additions & 0 deletions x-pack/plugins/monitoring/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
4 changes: 1 addition & 3 deletions x-pack/plugins/monitoring/kibana.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
"features",
"data",
"navigation",
"observability",
"observabilityShared",
"dataViews",
"unifiedSearch",
"share"
Expand All @@ -42,4 +40,4 @@
"kibanaReact",
]
}
}
}
Original file line number Diff line number Diff line change
@@ -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<typeof useUiTracker>;
export type TrackEvent = (options: TrackMetricOptions) => void;

export { METRIC_TYPE };

export function useUiTracker<Services extends ServiceDeps>(): TrackEvent {
const reportUiCounter = useKibana<Services>().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<Services extends ServiceDeps>(
{ metric, metricType = METRIC_TYPE.COUNT, delay = 0 }: TrackMetricOptions,
effectDependencies: EffectDeps = []
) {
const reportUiCounter = useKibana<Services>().services?.usageCollection?.reportUiCounter;

useEffect(() => {
if (!reportUiCounter) {
// eslint-disable-next-line no-console
console.log(
'usageCollection.reportUiCounter is unavailable. Ensure this is setup via <KibanaContextProvider />.'
);
} 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<Services extends ServiceDeps>(
{ path, ...rest }: TrackPageviewProps,
effectDependencies: EffectDeps = []
) {
useTrackMetric<Services>({ ...rest, metric: `pageview__${path}` }, effectDependencies);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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(
<HeaderMenuPortal setHeaderActionMenu={setHeaderActionMenu} theme$={theme$}>
test
</HeaderMenuPortal>
);

unmount();

expect(setHeaderActionMenu).toHaveBeenCalledWith(undefined);
});
});
});
Original file line number Diff line number Diff line change
@@ -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(<OutPortal node={portalNode} />, { theme$ });
return mount(element);
});

return () => {
portalNode.unmount();
setHeaderActionMenu(undefined);
};
}, [portalNode, setHeaderActionMenu, theme$]);

return <InPortal node={portalNode}>{children}</InPortal>;
}
20 changes: 20 additions & 0 deletions x-pack/plugins/monitoring/public/components/header_menu/index.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Suspense fallback={<EuiLoadingSpinner />}>
<HeaderMenuPortalLazy {...props} />
</Suspense>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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 <PageLoadingUI />;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,7 +22,7 @@ export const SetupModeToggleButton: React.FC<SetupModeToggleButtonProps> = (
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);
Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/monitoring/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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$'];
}
2 changes: 1 addition & 1 deletion x-pack/plugins/monitoring/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -48,6 +47,7 @@
"@kbn/ui-theme",
"@kbn/core-elasticsearch-server",
"@kbn/share-plugin",
"@kbn/analytics",
],
"exclude": [
"target/**/*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,7 @@ export type HasData<T extends ObservabilityFetchDataPlugins> = (

export type ObservabilityFetchDataPlugins = Exclude<
ObservabilityApp,
| 'observability-overview'
| 'stack_monitoring'
| 'fleet'
| 'synthetics'
| 'profiling'
| 'observability-onboarding'
'observability-overview' | 'fleet' | 'synthetics' | 'profiling' | 'observability-onboarding'
>;

export interface DataHandler<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export type HasData<T extends ObservabilityFetchDataPlugins> = (

export type ObservabilityFetchDataPlugins = Exclude<
ObservabilityApp,
'observability-overview' | 'stack_monitoring' | 'fleet' | 'synthetics'
'observability-overview' | 'fleet' | 'synthetics'
>;

export interface DataHandler<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export type ObservabilityApp =
| 'uptime'
| 'synthetics'
| 'observability-overview'
| 'stack_monitoring'
| 'ux'
| 'fleet'
| 'universal_profiling';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ export type ObservabilityApp =
| 'uptime'
| 'synthetics'
| 'observability-overview'
| 'stack_monitoring'
| 'ux'
| 'fleet'
| 'profiling'
Expand Down

0 comments on commit 7e2f67e

Please sign in to comment.