From d5566c6b648a5e949dd70d0cea3ef73676125377 Mon Sep 17 00:00:00 2001 From: Carlos Crespo Date: Fri, 20 Sep 2024 19:31:54 +0200 Subject: [PATCH] [Inventory][ECO] Create header action menu (#193398) closes [#192326](https://github.com/elastic/kibana/issues/192326) ## Summary This PR introduces the "Add data" item to the header menu: https://github.com/user-attachments/assets/78ea3667-4ef1-4f02-a513-76e7ca896e67 image >[!NOTE] >I have refactored` plugin.ts`, moving the `ReactDOM.render` call to `application.tsx`. I've also created a new component to render the context providers. > >`useKibana` and `InventoryKibanaContext` were simplified. > >Besides, the analytics events created for the EEM Service Inventory 'Add data' button were replicated for this button. ### How to test - Add `xpack.inventory.enabled: true` to kibana.dev.yml - Start ES and Kibana locally - Navigate to Observability -> Inventory --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 1a192bcc002bdaf31733a3a8f3ec3b62d3b5b8ef) --- .../.storybook/get_mock_inventory_context.tsx | 24 ++-- .../inventory/kibana.jsonc | 3 +- .../inventory/public/application.tsx | 76 +++++------ .../add_data_action_menu.tsx | 120 ++++++++++++++++++ .../app_root/header_action_menu/index.tsx | 18 +++ .../public/components/app_root/index.tsx | 66 ++++++++++ .../inventory_page_template/index.tsx | 4 +- .../hooks/use_inventory_abortable_async.ts | 2 +- .../public/hooks/use_inventory_router.ts | 2 +- .../inventory/public/hooks/use_kibana.tsx | 12 +- .../public/{plugin.tsx => plugin.ts} | 43 +++---- .../services/telemetry/telemetry_client.ts | 17 +++ .../services/telemetry/telemetry_events.ts | 28 ++++ .../telemetry/telemetry_service.test.ts | 76 +++++++++++ .../services/telemetry/telemetry_service.ts | 35 +++++ .../public/services/telemetry/types.ts | 32 +++++ .../inventory/public/services/types.ts | 2 + .../inventory/public/types.ts | 2 + .../inventory/tsconfig.json | 7 +- 19 files changed, 462 insertions(+), 107 deletions(-) create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/add_data_action_menu.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/index.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx rename x-pack/plugins/observability_solution/inventory/public/{plugin.tsx => plugin.ts} (77%) create mode 100644 x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts diff --git a/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx b/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx index 1c5e2fd1f205b..da59f29c57842 100644 --- a/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx +++ b/x-pack/plugins/observability_solution/inventory/.storybook/get_mock_inventory_context.tsx @@ -8,24 +8,22 @@ import { coreMock } from '@kbn/core/public/mocks'; import type { ObservabilitySharedPluginStart } from '@kbn/observability-shared-plugin/public'; import type { InferencePublicStart } from '@kbn/inference-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; import type { InventoryKibanaContext } from '../public/hooks/use_kibana'; +import type { ITelemetryClient } from '../public/services/telemetry/types'; export function getMockInventoryContext(): InventoryKibanaContext { - const core = coreMock.createStart(); + const coreStart = coreMock.createStart(); return { - core, - dependencies: { - start: { - observabilityShared: {} as unknown as ObservabilitySharedPluginStart, - inference: {} as unknown as InferencePublicStart, - }, - }, - services: { - inventoryAPIClient: { - fetch: jest.fn(), - stream: jest.fn(), - }, + ...coreStart, + observabilityShared: {} as unknown as ObservabilitySharedPluginStart, + inference: {} as unknown as InferencePublicStart, + share: {} as unknown as SharePluginStart, + telemetry: {} as unknown as ITelemetryClient, + inventoryAPIClient: { + fetch: jest.fn(), + stream: jest.fn(), }, }; } diff --git a/x-pack/plugins/observability_solution/inventory/kibana.jsonc b/x-pack/plugins/observability_solution/inventory/kibana.jsonc index ced0f412ab935..9262e111c401f 100644 --- a/x-pack/plugins/observability_solution/inventory/kibana.jsonc +++ b/x-pack/plugins/observability_solution/inventory/kibana.jsonc @@ -11,7 +11,8 @@ "observabilityShared", "entityManager", "inference", - "dataViews" + "dataViews", + "share" ], "requiredBundles": [ "kibanaReact" diff --git a/x-pack/plugins/observability_solution/inventory/public/application.tsx b/x-pack/plugins/observability_solution/inventory/public/application.tsx index 5b235c15e7c4f..d34be920d68ff 100644 --- a/x-pack/plugins/observability_solution/inventory/public/application.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/application.tsx @@ -4,64 +4,46 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { CoreStart, CoreTheme } from '@kbn/core/public'; -import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; -import type { History } from 'history'; -import React, { useMemo } from 'react'; -import type { Observable } from 'rxjs'; -import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { APP_WRAPPER_CLASS, type AppMountParameters, type CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; +import { css } from '@emotion/css'; import type { InventoryStartDependencies } from './types'; -import { inventoryRouter } from './routes/config'; -import { InventoryKibanaContext } from './hooks/use_kibana'; import { InventoryServices } from './services/types'; -import { InventoryContextProvider } from './components/inventory_context_provider'; +import { AppRoot } from './components/app_root'; -function Application({ +export const renderApp = ({ coreStart, - history, pluginsStart, - theme$, services, + appMountParameters, }: { coreStart: CoreStart; - history: History; pluginsStart: InventoryStartDependencies; - theme$: Observable; services: InventoryServices; -}) { - const theme = useMemo(() => { - return { theme$ }; - }, [theme$]); +} & { appMountParameters: AppMountParameters }) => { + const { element } = appMountParameters; - const context: InventoryKibanaContext = useMemo( - () => ({ - core: coreStart, - dependencies: { - start: pluginsStart, - }, - services, - }), - [coreStart, pluginsStart, services] - ); + const appWrapperClassName = css` + overflow: auto; + `; + const appWrapperElement = document.getElementsByClassName(APP_WRAPPER_CLASS)[1]; + appWrapperElement.classList.add(appWrapperClassName); - return ( - - - - - - - - - - - + ReactDOM.render( + + + , + element ); -} - -export { Application }; + return () => { + ReactDOM.unmountComponentAtNode(element); + appWrapperElement.classList.remove(APP_WRAPPER_CLASS); + }; +}; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/add_data_action_menu.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/add_data_action_menu.tsx new file mode 100644 index 0000000000000..ca4bc06df648a --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/add_data_action_menu.tsx @@ -0,0 +1,120 @@ +/* + * 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, { useState } from 'react'; +import { + EuiContextMenu, + EuiContextMenuPanelDescriptor, + EuiFlexGroup, + EuiFlexItem, + EuiHeaderLink, + EuiIcon, + EuiPopover, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + OBSERVABILITY_ONBOARDING_LOCATOR, + ObservabilityOnboardingLocatorParams, +} from '@kbn/deeplinks-observability'; +import { useKibana } from '../../../hooks/use_kibana'; +import type { InventoryAddDataParams } from '../../../services/telemetry/types'; + +const addDataTitle = i18n.translate('xpack.inventory.addDataContextMenu.link', { + defaultMessage: 'Add data', +}); +const addDataItem = i18n.translate('xpack.inventory.add.apm.agent.button.', { + defaultMessage: 'Add data', +}); + +const associateServiceLogsItem = i18n.translate('xpack.inventory.associate.service.logs.button', { + defaultMessage: 'Associate existing service logs', +}); + +const ASSOCIATE_LOGS_LINK = 'https://ela.st/new-experience-associate-service-logs'; + +export function AddDataContextMenu() { + const [popoverOpen, setPopoverOpen] = useState(false); + const { + services: { share, telemetry }, + } = useKibana(); + + const onboardingLocator = share.url.locators.get( + OBSERVABILITY_ONBOARDING_LOCATOR + ); + + const button = ( + setPopoverOpen((prevState) => !prevState)} + data-test-subj="inventoryAddDataHeaderContextMenu" + > + + {addDataTitle} + + + + + + ); + + function reportButtonClick(journey: InventoryAddDataParams['journey']) { + telemetry.reportInventoryAddData({ + view: 'add_data_button', + journey, + }); + } + + const panels: EuiContextMenuPanelDescriptor[] = [ + { + id: 0, + title: addDataTitle, + items: [ + { + name: ( + + {associateServiceLogsItem} + + + + + ), + key: 'associateServiceLogs', + href: ASSOCIATE_LOGS_LINK, + 'data-test-subj': 'inventoryHeaderMenuAddDataAssociateServiceLogs', + target: '_blank', + onClick: () => { + reportButtonClick('associate_existing_service_logs'); + }, + }, + { + name: addDataItem, + key: 'addData', + href: onboardingLocator?.getRedirectUrl({ category: '' }), + icon: 'plusInCircle', + 'data-test-subj': 'inventoryHeaderMenuAddData', + onClick: () => { + reportButtonClick('add_data'); + }, + }, + ], + }, + ]; + + return ( + setPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downCenter" + > + + + ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/index.tsx new file mode 100644 index 0000000000000..5ae0f4dd24574 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/header_action_menu/index.tsx @@ -0,0 +1,18 @@ +/* + * 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 from 'react'; +import { EuiHeaderLinks } from '@elastic/eui'; +import { AddDataContextMenu } from './add_data_action_menu'; + +export function HeaderActionMenuItems() { + return ( + + + + ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx new file mode 100644 index 0000000000000..80fc8cbe3d604 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx @@ -0,0 +1,66 @@ +/* + * 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 { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; +import React from 'react'; +import { type AppMountParameters, type CoreStart } from '@kbn/core/public'; +import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; +import { HeaderMenuPortal } from '@kbn/observability-shared-plugin/public'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { InventoryContextProvider } from '../inventory_context_provider'; +import { inventoryRouter } from '../../routes/config'; +import { HeaderActionMenuItems } from './header_action_menu'; +import { InventoryStartDependencies } from '../../types'; +import { InventoryServices } from '../../services/types'; + +export function AppRoot({ + coreStart, + pluginsStart, + services, + appMountParameters, +}: { + coreStart: CoreStart; + pluginsStart: InventoryStartDependencies; + services: InventoryServices; +} & { appMountParameters: AppMountParameters }) { + const { history } = appMountParameters; + + const context = { + ...coreStart, + ...pluginsStart, + ...services, + }; + + return ( + + + + + + + + + ); +} + +export function InventoryHeaderActionMenu({ + appMountParameters, +}: { + appMountParameters: AppMountParameters; +}) { + const { setHeaderActionMenu, theme$ } = appMountParameters; + + return ( + + + + + + + + ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx index 4dd8eaf3899ee..7c5d94fe26449 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/inventory_page_template/index.tsx @@ -10,9 +10,7 @@ import { useKibana } from '../../hooks/use_kibana'; export function InventoryPageTemplate({ children }: { children: React.ReactNode }) { const { - dependencies: { - start: { observabilityShared }, - }, + services: { observabilityShared }, } = useKibana(); const { PageTemplate: ObservabilityPageTemplate } = observabilityShared.navigation; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_abortable_async.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_abortable_async.ts index 60b2fca72e721..84cef842488e0 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_abortable_async.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_abortable_async.ts @@ -14,7 +14,7 @@ const getDetailsFromErrorResponse = (error: IHttpFetchError) export function useInventoryAbortableAsync(...args: Parameters>) { const { - core: { notifications }, + services: { notifications }, } = useKibana(); const response = useAbortableAsync(...args); diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_router.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_router.ts index 5c968eaf852ed..a917daf576ded 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_router.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_inventory_router.ts @@ -24,7 +24,7 @@ interface StatefulInventoryRouter extends InventoryRouter { export function useInventoryRouter(): StatefulInventoryRouter { const { - core: { + services: { http, application: { navigateToApp }, }, diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana.tsx b/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana.tsx index 2b75cc513b241..0baf2acbc32b8 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana.tsx @@ -5,19 +5,13 @@ * 2.0. */ -import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { CoreStart } from '@kbn/core/public'; +import { type KibanaReactContextValue, useKibana } from '@kbn/kibana-react-plugin/public'; import type { InventoryStartDependencies } from '../types'; import type { InventoryServices } from '../services/types'; -export interface InventoryKibanaContext { - core: CoreStart; - dependencies: { start: InventoryStartDependencies }; - services: InventoryServices; -} +export type InventoryKibanaContext = CoreStart & InventoryStartDependencies & InventoryServices; -const useTypedKibana = () => { - return useKibana().services; -}; +const useTypedKibana = useKibana as () => KibanaReactContextValue; export { useTypedKibana as useKibana }; diff --git a/x-pack/plugins/observability_solution/inventory/public/plugin.tsx b/x-pack/plugins/observability_solution/inventory/public/plugin.ts similarity index 77% rename from x-pack/plugins/observability_solution/inventory/public/plugin.tsx rename to x-pack/plugins/observability_solution/inventory/public/plugin.ts index 0cb7df9552c74..c196ed41ae5f3 100644 --- a/x-pack/plugins/observability_solution/inventory/public/plugin.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/plugin.ts @@ -4,13 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React from 'react'; -import ReactDOM from 'react-dom'; + import { i18n } from '@kbn/i18n'; import { from, map } from 'rxjs'; import { AppMountParameters, - APP_WRAPPER_CLASS, CoreSetup, CoreStart, DEFAULT_APP_CATEGORIES, @@ -19,7 +17,6 @@ import { } from '@kbn/core/public'; import type { Logger } from '@kbn/logging'; import { INVENTORY_APP_ID } from '@kbn/deeplinks-observability/constants'; -import { css } from '@emotion/css'; import type { ConfigSchema, InventoryPublicSetup, @@ -29,6 +26,7 @@ import type { } from './types'; import { InventoryServices } from './services/types'; import { createCallInventoryAPI } from './api'; +import { TelemetryService } from './services/telemetry/telemetry_service'; export class InventoryPlugin implements @@ -40,15 +38,18 @@ export class InventoryPlugin > { logger: Logger; + telemetry: TelemetryService; constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); + this.telemetry = new TelemetryService(); } setup( coreSetup: CoreSetup, pluginsSetup: InventorySetupDependencies ): InventoryPublicSetup { const inventoryAPIClient = createCallInventoryAPI(coreSetup); + this.telemetry.setup({ analytics: coreSetup.analytics }); pluginsSetup.observabilityShared.navigation.registerSections( from(coreSetup.getStartServices()).pipe( @@ -75,6 +76,8 @@ export class InventoryPlugin ) ); + const telemetry = this.telemetry.start(); + coreSetup.application.register({ id: INVENTORY_APP_ID, title: i18n.translate('xpack.inventory.appTitle', { @@ -96,38 +99,22 @@ export class InventoryPlugin ], mount: async (appMountParameters: AppMountParameters) => { // Load application bundle and Get start services - const [{ Application }, [coreStart, pluginsStart]] = await Promise.all([ + const [{ renderApp }, [coreStart, pluginsStart]] = await Promise.all([ import('./application'), coreSetup.getStartServices(), ]); const services: InventoryServices = { inventoryAPIClient, + telemetry, }; - ReactDOM.render( - , - appMountParameters.element - ); - - const appWrapperClassName = css` - overflow: auto; - `; - - const appWrapperElement = document.getElementsByClassName(APP_WRAPPER_CLASS)[1]; - - appWrapperElement.classList.add(appWrapperClassName); - - return () => { - ReactDOM.unmountComponentAtNode(appMountParameters.element); - appWrapperElement.classList.remove(appWrapperClassName); - }; + return renderApp({ + coreStart, + pluginsStart, + services, + appMountParameters, + }); }, }); diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts new file mode 100644 index 0000000000000..1e36e8d6649ae --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts @@ -0,0 +1,17 @@ +/* + * 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 { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; +import { type ITelemetryClient, TelemetryEventTypes, type InventoryAddDataParams } from './types'; + +export class TelemetryClient implements ITelemetryClient { + constructor(private analytics: AnalyticsServiceSetup) {} + + public reportInventoryAddData = (params: InventoryAddDataParams) => { + this.analytics.reportEvent(TelemetryEventTypes.INVENTORY_ADD_DATA_CLICKED, params); + }; +} diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts new file mode 100644 index 0000000000000..c1509499e694b --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts @@ -0,0 +1,28 @@ +/* + * 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 { TelemetryEventTypes, TelemetryEvent } from './types'; + +const inventoryAddDataEventType: TelemetryEvent = { + eventType: TelemetryEventTypes.INVENTORY_ADD_DATA_CLICKED, + schema: { + view: { + type: 'keyword', + _meta: { + description: 'Where the action was initiated (add_data_button)', + }, + }, + journey: { + type: 'keyword', + _meta: { + optional: true, + description: 'Which action was performed (add_data or associate_existing_service_logs)', + }, + }, + }, +}; + +export const inventoryTelemetryEventBasedTypes = [inventoryAddDataEventType]; diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts new file mode 100644 index 0000000000000..ffa05ffbff9a2 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { coreMock } from '@kbn/core/server/mocks'; +import { inventoryTelemetryEventBasedTypes } from './telemetry_events'; + +import { TelemetryService } from './telemetry_service'; +import { TelemetryEventTypes } from './types'; + +describe('TelemetryService', () => { + let service: TelemetryService; + + beforeEach(() => { + service = new TelemetryService(); + }); + + const getSetupParams = () => { + const mockCoreStart = coreMock.createSetup(); + return { + analytics: mockCoreStart.analytics, + }; + }; + + describe('#setup()', () => { + it('should register all the custom events', () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + + expect(setupParams.analytics.registerEventType).toHaveBeenCalledTimes( + inventoryTelemetryEventBasedTypes.length + ); + + inventoryTelemetryEventBasedTypes.forEach((eventConfig, pos) => { + expect(setupParams.analytics.registerEventType).toHaveBeenNthCalledWith( + pos + 1, + eventConfig + ); + }); + }); + }); + + describe('#start()', () => { + it('should return all the available tracking methods', () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + + expect(telemetry).toHaveProperty('reportInventoryAddData'); + }); + }); + + describe('#reportInventoryAddData', () => { + it('should report inventory add data clicked with properties', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + + telemetry.reportInventoryAddData({ + view: 'add_data_button', + journey: 'add_data', + }); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + TelemetryEventTypes.INVENTORY_ADD_DATA_CLICKED, + { + view: 'add_data_button', + journey: 'add_data', + } + ); + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.ts new file mode 100644 index 0000000000000..fa416f76b3c16 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.ts @@ -0,0 +1,35 @@ +/* + * 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 { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; +import type { TelemetryServiceSetupParams, ITelemetryClient, TelemetryEventParams } from './types'; +import { inventoryTelemetryEventBasedTypes } from './telemetry_events'; +import { TelemetryClient } from './telemetry_client'; + +/** + * Service that interacts with the Core's analytics module + */ +export class TelemetryService { + constructor(private analytics: AnalyticsServiceSetup | null = null) {} + + public setup({ analytics }: TelemetryServiceSetupParams) { + this.analytics = analytics; + + inventoryTelemetryEventBasedTypes.forEach((eventConfig) => + analytics.registerEventType(eventConfig) + ); + } + + public start(): ITelemetryClient { + if (!this.analytics) { + throw new Error( + 'The TelemetryService.setup() method has not been invoked, be sure to call it during the plugin setup.' + ); + } + + return new TelemetryClient(this.analytics); + } +} diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts new file mode 100644 index 0000000000000..494391aa1a7c1 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts @@ -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. + */ + +import type { AnalyticsServiceSetup, RootSchema } from '@kbn/core/public'; + +export interface TelemetryServiceSetupParams { + analytics: AnalyticsServiceSetup; +} + +export interface InventoryAddDataParams { + view: 'add_data_button'; + journey?: 'add_data' | 'associate_existing_service_logs'; +} + +export type TelemetryEventParams = InventoryAddDataParams; + +export interface ITelemetryClient { + reportInventoryAddData(params: InventoryAddDataParams): void; +} + +export enum TelemetryEventTypes { + INVENTORY_ADD_DATA_CLICKED = 'inventory_add_data_clicked', +} + +export interface TelemetryEvent { + eventType: TelemetryEventTypes; + schema: RootSchema; +} diff --git a/x-pack/plugins/observability_solution/inventory/public/services/types.ts b/x-pack/plugins/observability_solution/inventory/public/services/types.ts index 008437fbf8895..d0cc176e7b53f 100644 --- a/x-pack/plugins/observability_solution/inventory/public/services/types.ts +++ b/x-pack/plugins/observability_solution/inventory/public/services/types.ts @@ -6,7 +6,9 @@ */ import type { InventoryAPIClient } from '../api'; +import type { ITelemetryClient } from './telemetry/types'; export interface InventoryServices { inventoryAPIClient: InventoryAPIClient; + telemetry: ITelemetryClient; } diff --git a/x-pack/plugins/observability_solution/inventory/public/types.ts b/x-pack/plugins/observability_solution/inventory/public/types.ts index 66c0789650a08..88a3188c45a57 100644 --- a/x-pack/plugins/observability_solution/inventory/public/types.ts +++ b/x-pack/plugins/observability_solution/inventory/public/types.ts @@ -9,6 +9,7 @@ import type { ObservabilitySharedPluginSetup, } from '@kbn/observability-shared-plugin/public'; import type { InferencePublicStart, InferencePublicSetup } from '@kbn/inference-plugin/public'; +import type { SharePluginStart } from '@kbn/share-plugin/public'; /* eslint-disable @typescript-eslint/no-empty-interface*/ @@ -22,6 +23,7 @@ export interface InventorySetupDependencies { export interface InventoryStartDependencies { observabilityShared: ObservabilitySharedPluginStart; inference: InferencePublicStart; + share: SharePluginStart; } export interface InventoryPublicSetup {} diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index 5c17e404701d5..324dc1d08cdb9 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -12,10 +12,7 @@ "server/**/*", ".storybook/**/*" ], - "exclude": [ - "target/**/*", - ".storybook/**/*.js" - ], + "exclude": ["target/**/*", ".storybook/**/*.js"], "kbn_references": [ "@kbn/core", "@kbn/logging", @@ -32,12 +29,14 @@ "@kbn/licensing-plugin", "@kbn/inference-plugin", "@kbn/data-views-plugin", + "@kbn/share-plugin", "@kbn/server-route-repository-client", "@kbn/react-kibana-context-render", "@kbn/es-types", "@kbn/entities-schema", "@kbn/i18n-react", "@kbn/io-ts-utils", + "@kbn/core-analytics-browser", "@kbn/core-http-browser" ] }