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 9c2ea13cf753e..d3d28fe040198 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 @@ -17,7 +17,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { HttpStart } from '@kbn/core-http-browser'; import { action } from '@storybook/addon-actions'; import type { InventoryKibanaContext } from '../public/hooks/use_kibana'; -import type { ITelemetryClient } from '../public/services/telemetry/types'; +import { ITelemetryClient } from '../public/services/telemetry/types'; export function getMockInventoryContext(): InventoryKibanaContext { const coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx index f3488dfddbc4e..982a616da8fda 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entity_name/index.tsx @@ -25,13 +25,22 @@ interface EntityNameProps { } export function EntityName({ entity }: EntityNameProps) { - const { services } = useKibana(); + const { + services: { telemetry, share }, + } = useKibana(); const assetDetailsLocator = - services.share?.url.locators.get(ASSET_DETAILS_LOCATOR_ID); + share?.url.locators.get(ASSET_DETAILS_LOCATOR_ID); const serviceOverviewLocator = - services.share?.url.locators.get('serviceOverviewLocator'); + share?.url.locators.get('serviceOverviewLocator'); + + const handleLinkClick = useCallback(() => { + telemetry.reportEntityViewClicked({ + view_type: 'detail', + entity_type: entity['entity.type'], + }); + }, [entity, telemetry]); const getEntityRedirectUrl = useCallback(() => { const type = entity[ENTITY_TYPE]; @@ -58,7 +67,12 @@ export function EntityName({ entity }: EntityNameProps) { }, [entity, assetDetailsLocator, serviceOverviewLocator]); return ( - + // eslint-disable-next-line @elastic/eui/href-or-on-click + diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx index 6d65669c61651..e3c0d24837f91 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx @@ -84,12 +84,13 @@ export function EntitiesGrid({ } const columnEntityTableId = columnId as EntityColumnIds; + const entityType = entity[ENTITY_TYPE]; + switch (columnEntityTableId) { case 'alertsCount': return entity?.alertsCount ? : null; case ENTITY_TYPE: - const entityType = entity[columnEntityTableId]; return ( @@ -36,7 +37,7 @@ const INVENTORY_FEEDBACK_LINK = 'https://ela.st/feedback-new-inventory'; export function InventoryPageTemplate({ children }: { children: React.ReactNode }) { const { - services: { observabilityShared, inventoryAPIClient, kibanaEnvironment }, + services: { observabilityShared, inventoryAPIClient, kibanaEnvironment, telemetry }, } = useKibana(); const { PageTemplate: ObservabilityPageTemplate } = observabilityShared.navigation; @@ -62,6 +63,23 @@ export function InventoryPageTemplate({ children }: { children: React.ReactNode [inventoryAPIClient] ); + const isLoadingComplete = useIsLoadingComplete({ + loadingStates: [isEnablementLoading, hasDataLoading], + }); + + useEffect(() => { + if (isLoadingComplete) { + const viewState = isEntityManagerEnabled + ? value.hasData + ? 'populated' + : 'empty' + : 'eem_disabled'; + telemetry.reportEntityInventoryViewed({ + view_state: viewState, + }); + } + }, [isEntityManagerEnabled, value.hasData, telemetry, isLoadingComplete]); + if (isEnablementLoading || hasDataLoading) { return ( { + telemetry.reportEntityInventorySearchQuerySubmitted({ + kuery_fields: getKqlFieldsWithFallback(searchQuery?.query as string), + entity_types: searchEntityTypes || [], + action: searchIsUpdate ? 'submit' : 'refresh', + }); + }, + [telemetry] + ); + + const registerEntityTypeFilteredEvent = useCallback( + ({ filterEntityTypes, filterKuery }: { filterEntityTypes: string[]; filterKuery?: string }) => { + telemetry.reportEntityInventoryEntityTypeFiltered({ + entity_types: filterEntityTypes, + kuery_fields: filterKuery ? getKqlFieldsWithFallback(filterKuery) : [], + }); + }, + [telemetry] + ); + const handleEntityTypesChange = useCallback( (nextEntityTypes: string[]) => { searchBarContentSubject$.next({ kuery, entityTypes: nextEntityTypes, refresh: false }); + registerEntityTypeFilteredEvent({ filterEntityTypes: nextEntityTypes, filterKuery: kuery }); }, - [kuery, searchBarContentSubject$] + [kuery, registerEntityTypeFilteredEvent, searchBarContentSubject$] ); const handleQuerySubmit = useCallback>( @@ -64,8 +97,14 @@ export function SearchBar() { entityTypes, refresh: !isUpdate, }); + + registerSearchSubmittedEvent({ + searchQuery: query, + searchEntityTypes: entityTypes, + searchIsUpdate: isUpdate, + }); }, - [entityTypes, searchBarContentSubject$] + [entityTypes, registerSearchSubmittedEvent, searchBarContentSubject$] ); return ( diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts new file mode 100644 index 0000000000000..61306a0b66a3b --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.test.ts @@ -0,0 +1,109 @@ +/* + * 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 { renderHook } from '@testing-library/react-hooks'; +import { useIsLoadingComplete } from './use_is_loading_complete'; + +describe('useIsLoadingComplete', () => { + describe('initialization', () => { + it('should initialize with undefined', () => { + const { result } = renderHook(() => useIsLoadingComplete({ loadingStates: [false, false] })); + expect(result.current).toBeUndefined(); + }); + + it('should handle an empty array of loadingStates', () => { + const { result } = renderHook(() => useIsLoadingComplete({ loadingStates: [] })); + expect(result.current).toBeUndefined(); + }); + + it('should handle a single loading state that is false', () => { + const { result } = renderHook(() => useIsLoadingComplete({ loadingStates: [false] })); + expect(result.current).toBeUndefined(); + }); + }); + + describe('loading states', () => { + it('should set isLoadingComplete to false when some loadingStates are true', () => { + const { result } = renderHook(() => useIsLoadingComplete({ loadingStates: [true, false] })); + expect(result.current).toBe(false); + }); + + it('should set isLoadingComplete to false when all loadingStates are true', () => { + const { result } = renderHook(() => useIsLoadingComplete({ loadingStates: [true, true] })); + expect(result.current).toBe(false); + }); + + it('should handle a single loading state that is true', () => { + const { result } = renderHook(() => useIsLoadingComplete({ loadingStates: [true] })); + expect(result.current).toBe(false); + }); + }); + + describe('loading completion', () => { + it('should set isLoadingComplete to true when all loadingStates are false after being true', () => { + const { result, rerender } = renderHook( + ({ loadingStates }) => useIsLoadingComplete({ loadingStates }), + { + initialProps: { loadingStates: [true, false] }, + } + ); + + expect(result.current).toBe(false); + + rerender({ loadingStates: [false, false] }); + + expect(result.current).toBe(true); + }); + + it('should set isLoadingComplete to true when all loadingStates are false after being mixed', () => { + const { result, rerender } = renderHook( + ({ loadingStates }) => useIsLoadingComplete({ loadingStates }), + { + initialProps: { loadingStates: [true, false] }, + } + ); + + expect(result.current).toBe(false); + + rerender({ loadingStates: [false, false] }); + + expect(result.current).toBe(true); + }); + }); + + describe('mixed states', () => { + it('should not change isLoadingComplete if loadingStates are mixed', () => { + const { result, rerender } = renderHook( + ({ loadingStates }) => useIsLoadingComplete({ loadingStates }), + { + initialProps: { loadingStates: [true, true] }, + } + ); + + expect(result.current).toBe(false); + + rerender({ loadingStates: [true, false] }); + + expect(result.current).toBe(false); + }); + + it('should not change isLoadingComplete if loadingStates change from all true to mixed', () => { + const { result, rerender } = renderHook( + ({ loadingStates }) => useIsLoadingComplete({ loadingStates }), + { + initialProps: { loadingStates: [true, true] }, + } + ); + + expect(result.current).toBe(false); + + rerender({ loadingStates: [true, false] }); + + expect(result.current).toBe(false); + }); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.ts new file mode 100644 index 0000000000000..76b863efaeceb --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_is_loading_complete.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 { useState, useEffect } from 'react'; + +interface UseIsLoadingCompleteProps { + loadingStates: boolean[]; +} + +export const useIsLoadingComplete = ({ loadingStates }: UseIsLoadingCompleteProps) => { + const [isLoadingComplete, setIsLoadingComplete] = useState(undefined); + + useEffect(() => { + const someLoading = loadingStates.some((loading) => loading); + const allLoaded = loadingStates.every((loading) => !loading); + + if (isLoadingComplete === undefined && someLoading) { + setIsLoadingComplete(false); + } else if (isLoadingComplete === false && allLoaded) { + setIsLoadingComplete(true); + } + }, [isLoadingComplete, loadingStates]); + + return isLoadingComplete; +}; diff --git a/x-pack/plugins/observability_solution/inventory/public/plugin.ts b/x-pack/plugins/observability_solution/inventory/public/plugin.ts index b6771d2f95550..109123859d4ca 100644 --- a/x-pack/plugins/observability_solution/inventory/public/plugin.ts +++ b/x-pack/plugins/observability_solution/inventory/public/plugin.ts @@ -49,6 +49,7 @@ export class InventoryPlugin this.kibanaVersion = context.env.packageInfo.version; this.isServerlessEnv = context.env.packageInfo.buildFlavor === 'serverless'; } + setup( coreSetup: CoreSetup, pluginsSetup: InventorySetupDependencies @@ -58,6 +59,13 @@ export class InventoryPlugin 'observability:entityCentricExperience', true ); + + this.telemetry.setup({ + analytics: coreSetup.analytics, + }); + + const telemetry = this.telemetry.start(); + const getStartServices = coreSetup.getStartServices(); const hideInventory$ = from(getStartServices).pipe( @@ -105,9 +113,6 @@ export class InventoryPlugin pluginsSetup.observabilityShared.navigation.registerSections(sections$); - this.telemetry.setup({ analytics: coreSetup.analytics }); - const telemetry = this.telemetry.start(); - const isCloudEnv = !!pluginsSetup.cloud?.isCloudEnabled; const isServerlessEnv = pluginsSetup.cloud?.isServerlessEnabled || this.isServerlessEnv; 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 index 1e36e8d6649ae..54d20ea324b11 100644 --- 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 @@ -6,7 +6,16 @@ */ import { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; -import { type ITelemetryClient, TelemetryEventTypes, type InventoryAddDataParams } from './types'; + +import { + type ITelemetryClient, + TelemetryEventTypes, + type InventoryAddDataParams, + type EntityInventoryViewedParams, + type EntityInventorySearchQuerySubmittedParams, + type EntityViewClickedParams, + type EntityInventoryEntityTypeFilteredParams, +} from './types'; export class TelemetryClient implements ITelemetryClient { constructor(private analytics: AnalyticsServiceSetup) {} @@ -14,4 +23,24 @@ export class TelemetryClient implements ITelemetryClient { public reportInventoryAddData = (params: InventoryAddDataParams) => { this.analytics.reportEvent(TelemetryEventTypes.INVENTORY_ADD_DATA_CLICKED, params); }; + + public reportEntityInventoryViewed = (params: EntityInventoryViewedParams) => { + this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_VIEWED, params); + }; + + public reportEntityInventorySearchQuerySubmitted = ( + params: EntityInventorySearchQuerySubmittedParams + ) => { + this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_SEARCH_QUERY_SUBMITTED, params); + }; + + public reportEntityInventoryEntityTypeFiltered = ( + params: EntityInventoryEntityTypeFilteredParams + ) => { + this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_ENTITY_TYPE_FILTERED, params); + }; + + public reportEntityViewClicked = (params: EntityViewClickedParams) => { + this.analytics.reportEvent(TelemetryEventTypes.ENTITY_VIEW_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 index c1509499e694b..d61a90f7d30ab 100644 --- 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 @@ -25,4 +25,94 @@ const inventoryAddDataEventType: TelemetryEvent = { }, }; -export const inventoryTelemetryEventBasedTypes = [inventoryAddDataEventType]; +const entityInventoryViewedEventType: TelemetryEvent = { + eventType: TelemetryEventTypes.ENTITY_INVENTORY_VIEWED, + schema: { + view_state: { + type: 'keyword', + _meta: { + description: 'State of the view: empty, populated or eem_disabled.', + }, + }, + }, +}; + +const searchQuerySubmittedEventType: TelemetryEvent = { + eventType: TelemetryEventTypes.ENTITY_INVENTORY_SEARCH_QUERY_SUBMITTED, + schema: { + kuery_fields: { + type: 'array', + items: { + type: 'text', + _meta: { + description: 'Kuery fields used in the search.', + }, + }, + }, + entity_types: { + type: 'array', + items: { + type: 'keyword', + _meta: { + description: 'Entity types used in the search.', + }, + }, + }, + action: { + type: 'keyword', + _meta: { + description: 'Action performed: submit or refresh.', + }, + }, + }, +}; + +const entityInventoryEntityTypeFilteredEventType: TelemetryEvent = { + eventType: TelemetryEventTypes.ENTITY_INVENTORY_ENTITY_TYPE_FILTERED, + schema: { + entity_types: { + type: 'array', + items: { + type: 'keyword', + _meta: { + description: 'Entity types used in the filter.', + }, + }, + }, + kuery_fields: { + type: 'array', + items: { + type: 'text', + _meta: { + description: 'Kuery fields used in the filter.', + }, + }, + }, + }, +}; + +const entityViewClickedEventType: TelemetryEvent = { + eventType: TelemetryEventTypes.ENTITY_VIEW_CLICKED, + schema: { + entity_type: { + type: 'keyword', + _meta: { + description: 'Type of the entity: container, host or service.', + }, + }, + view_type: { + type: 'keyword', + _meta: { + description: 'Type of the view: detail or flyout.', + }, + }, + }, +}; + +export const inventoryTelemetryEventBasedTypes = [ + inventoryAddDataEventType, + entityInventoryViewedEventType, + searchQuerySubmittedEventType, + entityInventoryEntityTypeFilteredEventType, + entityViewClickedEventType, +]; 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 index ffa05ffbff9a2..415cf0e7d4406 100644 --- 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 @@ -8,7 +8,13 @@ import { coreMock } from '@kbn/core/server/mocks'; import { inventoryTelemetryEventBasedTypes } from './telemetry_events'; import { TelemetryService } from './telemetry_service'; -import { TelemetryEventTypes } from './types'; +import { + type EntityInventoryViewedParams, + type EntityViewClickedParams, + type EntityInventorySearchQuerySubmittedParams, + TelemetryEventTypes, + type EntityInventoryEntityTypeFilteredParams, +} from './types'; describe('TelemetryService', () => { let service: TelemetryService; @@ -48,7 +54,15 @@ describe('TelemetryService', () => { service.setup(setupParams); const telemetry = service.start(); - expect(telemetry).toHaveProperty('reportInventoryAddData'); + const expectedProperties = [ + 'reportInventoryAddData', + 'reportEntityInventoryViewed', + 'reportEntityInventorySearchQuerySubmitted', + 'reportEntityViewClicked', + ]; + expectedProperties.forEach((property) => { + expect(telemetry).toHaveProperty(property); + }); }); }); @@ -73,4 +87,84 @@ describe('TelemetryService', () => { ); }); }); + + describe('#reportEntityInventoryViewed', () => { + it('should report entity inventory viewed with properties', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + const params: EntityInventoryViewedParams = { + view_state: 'empty', + }; + + telemetry.reportEntityInventoryViewed(params); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + TelemetryEventTypes.ENTITY_INVENTORY_VIEWED, + params + ); + }); + }); + + describe('#reportEntityInventorySearchQuerySubmitted', () => { + it('should report search query submitted with properties', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + const params: EntityInventorySearchQuerySubmittedParams = { + kuery_fields: ['_index'], + action: 'submit', + entity_types: ['container'], + }; + + telemetry.reportEntityInventorySearchQuerySubmitted(params); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + TelemetryEventTypes.ENTITY_INVENTORY_SEARCH_QUERY_SUBMITTED, + params + ); + }); + }); + + describe('#reportEntityInventoryEntityTypeFiltered', () => { + it('should report entity type filtered with properties', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + const params: EntityInventoryEntityTypeFilteredParams = { + kuery_fields: ['_index'], + entity_types: ['container'], + }; + + telemetry.reportEntityInventoryEntityTypeFiltered(params); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + TelemetryEventTypes.ENTITY_INVENTORY_ENTITY_TYPE_FILTERED, + params + ); + }); + }); + + describe('#reportEntityViewClicked', () => { + it('should report entity view clicked with properties', async () => { + const setupParams = getSetupParams(); + service.setup(setupParams); + const telemetry = service.start(); + const params: EntityViewClickedParams = { + entity_type: 'container', + view_type: 'detail', + }; + + telemetry.reportEntityViewClicked(params); + + expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); + expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( + TelemetryEventTypes.ENTITY_VIEW_CLICKED, + params + ); + }); + }); }); 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 index fa416f76b3c16..b81aff39672bb 100644 --- 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 @@ -5,7 +5,7 @@ * 2.0. */ import type { AnalyticsServiceSetup } from '@kbn/core-analytics-browser'; -import type { TelemetryServiceSetupParams, ITelemetryClient, TelemetryEventParams } from './types'; +import type { TelemetryServiceSetupParams, TelemetryEventParams } from './types'; import { inventoryTelemetryEventBasedTypes } from './telemetry_events'; import { TelemetryClient } from './telemetry_client'; @@ -23,7 +23,7 @@ export class TelemetryService { ); } - public start(): ITelemetryClient { + public start(): TelemetryClient { if (!this.analytics) { throw new Error( 'The TelemetryService.setup() method has not been invoked, be sure to call it during the plugin setup.' 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 index e5fdf162b750c..0e52d115d4597 100644 --- a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts @@ -6,24 +6,64 @@ */ import type { AnalyticsServiceSetup, RootSchema } from '@kbn/core/public'; +import { EntityManagerPublicPluginSetup } from '@kbn/entityManager-plugin/public'; export interface TelemetryServiceSetupParams { analytics: AnalyticsServiceSetup; } +export interface TelemetryServiceStartParams { + entityManager: EntityManagerPublicPluginSetup; +} + export interface InventoryAddDataParams { view: 'add_data_button' | 'empty_state'; journey?: 'add_data' | 'associate_existing_service_logs'; } -export type TelemetryEventParams = InventoryAddDataParams; +export interface EntityInventoryViewedParams { + view_state: 'empty' | 'populated' | 'eem_disabled'; +} + +export interface EntityInventorySearchQuerySubmittedParams { + kuery_fields: string[]; + entity_types: string[]; + action: 'submit' | 'refresh'; +} + +export interface EntityInventoryEntityTypeFilteredParams { + kuery_fields: string[]; + entity_types: string[]; +} + +export interface EntityViewClickedParams { + entity_type: string; + view_type: 'detail' | 'flyout'; +} + +export type TelemetryEventParams = + | InventoryAddDataParams + | EntityInventoryViewedParams + | EntityInventorySearchQuerySubmittedParams + | EntityInventoryEntityTypeFilteredParams + | EntityViewClickedParams; export interface ITelemetryClient { reportInventoryAddData(params: InventoryAddDataParams): void; + reportEntityInventoryViewed(params: EntityInventoryViewedParams): void; + reportEntityInventorySearchQuerySubmitted( + params: EntityInventorySearchQuerySubmittedParams + ): void; + reportEntityInventoryEntityTypeFiltered(params: EntityInventoryEntityTypeFilteredParams): void; + reportEntityViewClicked(params: EntityViewClickedParams): void; } export enum TelemetryEventTypes { INVENTORY_ADD_DATA_CLICKED = 'inventory_add_data_clicked', + ENTITY_INVENTORY_VIEWED = 'Entity Inventory Viewed', + ENTITY_INVENTORY_SEARCH_QUERY_SUBMITTED = 'Entity Inventory Search Query Submitted', + ENTITY_INVENTORY_ENTITY_TYPE_FILTERED = 'Entity Inventory Entity Type Filtered', + ENTITY_VIEW_CLICKED = 'Entity View Clicked', } export interface TelemetryEvent { 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 d0cc176e7b53f..b498a1fd49079 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,7 @@ */ import type { InventoryAPIClient } from '../api'; -import type { ITelemetryClient } from './telemetry/types'; +import { ITelemetryClient } from './telemetry/types'; export interface InventoryServices { inventoryAPIClient: InventoryAPIClient; diff --git a/x-pack/plugins/observability_solution/inventory/public/utils/get_kql_field_names_with_fallback.test.ts b/x-pack/plugins/observability_solution/inventory/public/utils/get_kql_field_names_with_fallback.test.ts new file mode 100644 index 0000000000000..89305e2bb08c9 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/utils/get_kql_field_names_with_fallback.test.ts @@ -0,0 +1,44 @@ +/* + * 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 { getKqlFieldsWithFallback } from './get_kql_field_names_with_fallback'; +import { getKqlFieldNamesFromExpression } from '@kbn/es-query'; + +jest.mock('@kbn/es-query', () => ({ + getKqlFieldNamesFromExpression: jest.fn(), +})); + +describe('getKqlFieldsWithFallback', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return field names when getKqlFieldNamesFromExpression succeeds', () => { + const mockFieldNames = ['field1', 'field2']; + (getKqlFieldNamesFromExpression as jest.Mock).mockReturnValue(mockFieldNames); + const expectedArg = 'testKuery'; + + const result = getKqlFieldsWithFallback(expectedArg); + expect(result).toEqual(mockFieldNames); + expect(getKqlFieldNamesFromExpression).toHaveBeenCalledWith(expectedArg); + }); + + it('should return an empty array when getKqlFieldNamesFromExpression throws an error', () => { + (getKqlFieldNamesFromExpression as jest.Mock).mockImplementation(() => { + throw new Error('Test error'); + }); + const expectedArg = 'testKuery'; + + const result = getKqlFieldsWithFallback(expectedArg); + expect(result).toEqual([]); + expect(getKqlFieldNamesFromExpression).toHaveBeenCalledWith(expectedArg); + }); +}); diff --git a/x-pack/plugins/observability_solution/inventory/public/utils/get_kql_field_names_with_fallback.ts b/x-pack/plugins/observability_solution/inventory/public/utils/get_kql_field_names_with_fallback.ts new file mode 100644 index 0000000000000..029405b5fc235 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/utils/get_kql_field_names_with_fallback.ts @@ -0,0 +1,16 @@ +/* + * 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 { getKqlFieldNamesFromExpression } from '@kbn/es-query'; + +export function getKqlFieldsWithFallback(kuery: string): string[] { + try { + return getKqlFieldNamesFromExpression(kuery); + } catch (e) { + return []; + } +}