From c6caa884303b82eec20bb1ef7ec04fcef2f6a81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Thu, 23 Apr 2026 15:58:25 +0200 Subject: [PATCH 1/7] Add custom events for EBT --- .../public/analytics/event_tracker.ts | 85 ++++++++++++++++++ .../analytics/event_tracker_context.tsx | 31 +++++++ .../public/analytics/register_event_types.ts | 90 +++++++++++++++++++ .../public/application.tsx | 5 +- .../add_inference_flyout_wrapper.tsx | 5 +- .../group_by_select.tsx | 8 +- .../edit_inference_flyout.tsx | 5 +- .../external_inference_empty_prompt.tsx | 7 +- .../components/filter/multi_select_filter.tsx | 7 +- .../public/components/inference_endpoints.tsx | 8 +- .../model_detail_flyout.tsx | 26 ++++-- .../settings/default_model_section.tsx | 3 + .../components/settings/model_settings.tsx | 6 +- .../elastic_inference_service_application.tsx | 5 +- .../public/hooks/use_delete_endpoint.tsx | 4 + .../public/hooks/use_scan_usage.tsx | 11 ++- .../public/plugin.ts | 2 + .../public/analytics/constants.ts | 16 ++++ .../agent_install/agent_install.tsx | 6 +- .../components/agent_install/prompt_modal.tsx | 12 ++- .../connect_code/language_selector.tsx | 11 ++- .../elasticsearch_connection_details.tsx | 8 +- .../tutorials/console_tutorials_group.tsx | 11 ++- .../public/contexts/usage_tracker_context.tsx | 3 +- .../public/analytics/constants.ts | 13 +++ .../search_homepage/basic_metric_badges.tsx | 38 +++++++- .../connect_to_elasticsearch.tsx | 18 +++- .../search_homepage/metric_panels.tsx | 13 ++- .../public/contexts/usage_tracker_context.tsx | 3 +- 29 files changed, 425 insertions(+), 35 deletions(-) create mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts create mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx create mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts create mode 100644 x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts create mode 100644 x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts new file mode 100644 index 0000000000000..a3dd3119c55c4 --- /dev/null +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts @@ -0,0 +1,85 @@ +/* + * 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 { AnalyticsServiceStart } from '@kbn/core/public'; + +export enum EventType { + ENDPOINT_CREATED = 'searchInferenceEndpoints_endpoint_created', + ENDPOINT_DELETED = 'searchInferenceEndpoints_endpoint_deleted', + ENDPOINT_EDITED = 'searchInferenceEndpoints_endpoint_edited', + DEFAULT_MODEL_CHANGED = 'searchInferenceEndpoints_default_model_changed', + FEATURE_SETTINGS_SAVED = 'searchInferenceEndpoints_feature_settings_saved', + FILTER_APPLIED = 'searchInferenceEndpoints_filter_applied', + GROUP_BY_CHANGED = 'searchInferenceEndpoints_group_by_changed', + ENDPOINT_USAGE_SCANNED = 'searchInferenceEndpoints_endpoint_usage_scanned', + EMPTY_STATE_VIEWED = 'searchInferenceEndpoints_empty_state_viewed', + API_ERROR = 'searchInferenceEndpoints_api_error', + FLYOUT_OPENED = 'searchInferenceEndpoints_flyout_opened', + FLYOUT_CLOSED = 'searchInferenceEndpoints_flyout_closed', + MODAL_OPENED = 'searchInferenceEndpoints_modal_opened', + MODAL_CLOSED = 'searchInferenceEndpoints_modal_closed', + EIS_MODEL_VIEWED = 'searchInferenceEndpoints_eis_model_viewed', +} + +export class EventTracker { + constructor(private readonly analytics: Pick) {} + + private track(eventType: EventType, data: Record = {}) { + try { + this.analytics.reportEvent(eventType, data); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + } + + endpointCreated() { + this.track(EventType.ENDPOINT_CREATED); + } + endpointDeleted() { + this.track(EventType.ENDPOINT_DELETED); + } + endpointEdited() { + this.track(EventType.ENDPOINT_EDITED); + } + defaultModelChanged() { + this.track(EventType.DEFAULT_MODEL_CHANGED); + } + featureSettingsSaved() { + this.track(EventType.FEATURE_SETTINGS_SAVED); + } + filterApplied(filter: string) { + this.track(EventType.FILTER_APPLIED, { filter }); + } + groupByChanged(groupBy: string) { + this.track(EventType.GROUP_BY_CHANGED, { group_by: groupBy }); + } + endpointUsageScanned() { + this.track(EventType.ENDPOINT_USAGE_SCANNED); + } + emptyStateViewed() { + this.track(EventType.EMPTY_STATE_VIEWED); + } + apiError(operation: string) { + this.track(EventType.API_ERROR, { operation }); + } + flyoutOpened(flyout: string) { + this.track(EventType.FLYOUT_OPENED, { flyout }); + } + flyoutClosed(flyout: string) { + this.track(EventType.FLYOUT_CLOSED, { flyout }); + } + modalOpened(modal: string) { + this.track(EventType.MODAL_OPENED, { modal }); + } + modalClosed() { + this.track(EventType.MODAL_CLOSED); + } + eisModelViewed(modelId: string) { + this.track(EventType.EIS_MODEL_VIEWED, { model_id: modelId }); + } +} diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx new file mode 100644 index 0000000000000..25dc1ffaffc7a --- /dev/null +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx @@ -0,0 +1,31 @@ +/* + * 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, { createContext, useContext, useMemo } from 'react'; +import type { AnalyticsServiceStart } from '@kbn/core/public'; + +import { EventTracker } from './event_tracker'; + +const EventTrackerContext = createContext(null); + +export interface EventTrackerProviderProps { + children: React.ReactNode | React.ReactNode[]; + analytics: AnalyticsServiceStart; +} + +export const EventTrackerProvider = ({ children, analytics }: EventTrackerProviderProps) => { + const tracker = useMemo(() => new EventTracker(analytics), [analytics]); + return {children}; +}; + +export const useEventTracker = (): EventTracker => { + const tracker = useContext(EventTrackerContext); + if (!tracker) { + throw new Error('useEventTracker must be used inside an EventTrackerProvider'); + } + return tracker; +}; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts new file mode 100644 index 0000000000000..df5cde9bed0db --- /dev/null +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts @@ -0,0 +1,90 @@ +/* + * 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, EventTypeOpts } from '@kbn/core/public'; + +import { EventType } from './event_tracker'; + +const eventTypes: Array>> = [ + { eventType: EventType.ENDPOINT_CREATED, schema: {} }, + { eventType: EventType.ENDPOINT_DELETED, schema: {} }, + { eventType: EventType.ENDPOINT_EDITED, schema: {} }, + { eventType: EventType.DEFAULT_MODEL_CHANGED, schema: {} }, + { eventType: EventType.FEATURE_SETTINGS_SAVED, schema: {} }, + { + eventType: EventType.FILTER_APPLIED, + schema: { + filter: { + type: 'keyword', + _meta: { description: 'Identifier of the filter popover the user interacted with.' }, + }, + }, + }, + { + eventType: EventType.GROUP_BY_CHANGED, + schema: { + group_by: { + type: 'keyword', + _meta: { description: 'The group-by option selected by the user.' }, + }, + }, + }, + { eventType: EventType.ENDPOINT_USAGE_SCANNED, schema: {} }, + { eventType: EventType.EMPTY_STATE_VIEWED, schema: {} }, + { + eventType: EventType.API_ERROR, + schema: { + operation: { + type: 'keyword', + _meta: { description: 'The operation that failed (e.g. delete).' }, + }, + }, + }, + { + eventType: EventType.FLYOUT_OPENED, + schema: { + flyout: { + type: 'keyword', + _meta: { description: 'Identifier of the flyout that was opened.' }, + }, + }, + }, + { + eventType: EventType.FLYOUT_CLOSED, + schema: { + flyout: { + type: 'keyword', + _meta: { description: 'Identifier of the flyout that was closed.' }, + }, + }, + }, + { + eventType: EventType.MODAL_OPENED, + schema: { + modal: { + type: 'keyword', + _meta: { description: 'Identifier of the modal that was opened.' }, + }, + }, + }, + { eventType: EventType.MODAL_CLOSED, schema: {} }, + { + eventType: EventType.EIS_MODEL_VIEWED, + schema: { + model_id: { + type: 'keyword', + _meta: { description: 'Identifier of the Elastic Inference Service model viewed.' }, + }, + }, + }, +]; + +export const registerSearchInferenceEndpointsEventTypes = (analytics: AnalyticsServiceSetup) => { + for (const eventType of eventTypes) { + analytics.registerEventType(eventType); + } +}; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx index f5bae783d9cac..8f193932a1389 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx @@ -13,6 +13,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; import { Router } from '@kbn/shared-ux-router'; import type { AppPluginStartDependencies } from './types'; +import { EventTrackerProvider } from './analytics/event_tracker_context'; const renderMgmtApp = ( core: CoreStart, @@ -25,7 +26,9 @@ const renderMgmtApp = ( - + + + diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx index c671048107df5..28e303ae381ba 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx @@ -10,6 +10,7 @@ import React, { useCallback } from 'react'; import InferenceFlyoutWrapper from '@kbn/inference-endpoint-ui-common'; import { ServiceProviderKeys } from '@kbn/inference-endpoint-ui-common'; import { useKibana } from '../../hooks/use_kibana'; +import { useEventTracker } from '../../analytics/event_tracker_context'; const EXCLUDED_PROVIDERS = [ServiceProviderKeys.elasticsearch, ServiceProviderKeys.elastic]; @@ -29,10 +30,12 @@ export const AddInferenceFlyoutWrapper: React.FC serverless, }, } = useKibana(); + const eventTracker = useEventTracker(); const onSubmitSuccess = useCallback(() => { + eventTracker.endpointCreated(); reloadFn(); - }, [reloadFn]); + }, [reloadFn, eventTracker]); return ( { const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const eventTracker = useEventTracker(); const handleValueChange = useCallback( (newOptions: EuiSelectableOption[]) => { const selectedOption = newOptions.find((option) => option.checked === 'on'); - onChange(parseGroupByValue(selectedOption?.key)); + const parsed = parseGroupByValue(selectedOption?.key); + eventTracker.groupByChanged(parsed); + onChange(parsed); setIsPopoverOpen(false); }, - [onChange] + [onChange, eventTracker] ); const { options, selectedOptionLabel } = useMemo(() => { let selectedOption = GROUP_BY_OPTIONS[0].label; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx index 9800c27250e08..97694124f75d4 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx @@ -12,6 +12,7 @@ import { flattenObject } from '@kbn/object-utils'; import type { InferenceInferenceEndpointInfo } from '@elastic/elasticsearch/lib/api/types'; import { useKibana } from '../../hooks/use_kibana'; import { useQueryInferenceEndpoints } from '../../hooks/use_inference_endpoints'; +import { useEventTracker } from '../../analytics/event_tracker_context'; interface EditInterfaceFlyoutProps { onFlyoutClose: () => void; @@ -29,9 +30,11 @@ export const EditInferenceFlyout: React.FC = ({ }, } = useKibana(); const { refetch } = useQueryInferenceEndpoints(); + const eventTracker = useEventTracker(); const onEditSuccess = useCallback(() => { + eventTracker.endpointEdited(); refetch(); - }, [refetch]); + }, [refetch, eventTracker]); const onFocusReturn = useCallback(() => { // Defer focus until after any closing animations complete requestAnimationFrame(() => { diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx index 9b66afe828627..ea95ac2d8e8e1 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx @@ -5,12 +5,13 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { EuiButton, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { docLinks } from '../../common/doc_links'; +import { useEventTracker } from '../analytics/event_tracker_context'; interface ExternalInferenceEmptyPromptProps { onFlyoutOpen: () => void; @@ -19,6 +20,10 @@ interface ExternalInferenceEmptyPromptProps { export const ExternalInferenceEmptyPrompt: React.FC = ({ onFlyoutOpen, }) => { + const eventTracker = useEventTracker(); + useEffect(() => { + eventTracker.emptyStateViewed(); + }, [eventTracker]); return ( = ({ const euiThemeContext = useEuiTheme(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const toggleIsPopoverOpen = () => setIsPopoverOpen((prevValue) => !prevValue); + const eventTracker = useEventTracker(); const options: MultiSelectFilterOption[] = _.uniqBy( rawOptions.map(({ key, label }) => ({ label, @@ -91,7 +93,10 @@ export const MultiSelectFilter: React.FC = ({ emptyMessage={i18n.translate('xpack.searchInferenceEndpoints.filter.emptyMessage', { defaultMessage: 'No options', })} - onChange={onChange} + onChange={(newOptions) => { + eventTracker.filterApplied(dataTestSubj ?? 'unknown'); + onChange(newOptions); + }} singleSelection={false} renderOption={renderOption} > diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx index 58d0692e533e4..b57746e37c54c 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx @@ -16,18 +16,22 @@ import { TabularPage } from './all_inference_endpoints/tabular_page'; import { ExternalInferenceHeader } from './external_inference_header'; import { AddInferenceFlyoutWrapper } from './add_inference_endpoints/add_inference_flyout_wrapper'; import { ExternalInferenceEmptyPrompt } from './external_inference_empty_prompt'; +import { useEventTracker } from '../analytics/event_tracker_context'; export const InferenceEndpoints: React.FC = () => { const { data, isLoading, refetch } = useQueryInferenceEndpoints(); const [isAddInferenceFlyoutOpen, setIsAddInferenceFlyoutOpen] = useState(false); + const eventTracker = useEventTracker(); const onFlyoutOpen = useCallback(() => { + eventTracker.flyoutOpened('add_inference'); setIsAddInferenceFlyoutOpen(true); - }, []); + }, [eventTracker]); const onFlyoutClose = useCallback(() => { + eventTracker.flyoutClosed('add_inference'); setIsAddInferenceFlyoutOpen(false); - }, []); + }, [eventTracker]); const reload = useCallback(() => { refetch(); diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx index a440697950500..f8ae75e876c0d 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiBadge, EuiButtonEmpty, @@ -35,6 +35,7 @@ import { TASK_TYPE_TOOLTIPS } from '../all_inference_endpoints/render_table_colu import { getModelId } from '../../utils/get_model_id'; import { AddEndpointModal } from './add_endpoint_modal'; import { ModelEndpointRow } from './model_endpoint_row'; +import { useEventTracker } from '../../analytics/event_tracker_context'; export interface ModelDetailFlyoutProps { modelId: string; @@ -56,6 +57,11 @@ export const ModelDetailFlyout: React.FC = ({ const flyoutTitleId = useGeneratedHtmlId(); const [isModalOpen, setIsModalOpen] = useState(false); const [editingEndpoint, setEditingEndpoint] = useState(); + const eventTracker = useEventTracker(); + + useEffect(() => { + eventTracker.eisModelViewed(modelId); + }, [eventTracker, modelId]); const { endpoints, displayName, modelAuthor } = useMemo(() => { const filtered = allEndpoints.filter((ep) => getModelId(ep) === modelId); @@ -89,19 +95,25 @@ export const ModelDetailFlyout: React.FC = ({ }, [endpoints]); const handleOpenAddModal = useCallback(() => { + eventTracker.modalOpened('add_endpoint'); setEditingEndpoint(undefined); setIsModalOpen(true); - }, []); + }, [eventTracker]); - const handleOpenEditModal = useCallback((endpoint: InferenceAPIConfigResponse) => { - setEditingEndpoint(endpoint); - setIsModalOpen(true); - }, []); + const handleOpenEditModal = useCallback( + (endpoint: InferenceAPIConfigResponse) => { + eventTracker.modalOpened('edit_endpoint'); + setEditingEndpoint(endpoint); + setIsModalOpen(true); + }, + [eventTracker] + ); const handleCloseModal = useCallback(() => { + eventTracker.modalClosed(); setIsModalOpen(false); setEditingEndpoint(undefined); - }, []); + }, [eventTracker]); const descriptionListItems = [ { diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx index 0e18e973a2451..39e926bb329ab 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx @@ -24,6 +24,7 @@ import { NO_DEFAULT_MODEL } from '../../../common/constants'; import { useConnectors } from '../../hooks/use_connectors'; import { useConnectorExists } from '../../hooks/use_connector_exists'; import type { UseDefaultModelSettingsReturn } from '../../hooks/use_default_model_settings'; +import { useEventTracker } from '../../analytics/event_tracker_context'; interface Props { defaultModelSettings: UseDefaultModelSettingsReturn; @@ -89,6 +90,7 @@ export const DefaultModelSection: React.FC = ({ defaultModelSettings }) = const { exists: connectorExists, loading: connectorExistsLoading } = useConnectorExists( state.defaultModelId ); + const eventTracker = useEventTracker(); const options = useMemo(() => getOptions(connectors), [connectors]); const selectedOptions = useMemo( @@ -124,6 +126,7 @@ export const DefaultModelSection: React.FC = ({ defaultModelSettings }) = const onChangeDefaultModel = (selected: EuiComboBoxOptionOption[]) => { const value = selected[0]?.value ?? NO_DEFAULT_MODEL; + eventTracker.defaultModelChanged(); setDefaultModelId(value); }; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx index 125a3f28eb2cb..70b9a37890cad 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx @@ -28,6 +28,7 @@ import { useModelSettingsForm } from './use_model_settings_form'; import { useDefaultModelSettings } from '../../hooks/use_default_model_settings'; import { useConnectors } from '../../hooks/use_connectors'; import { useKibana } from '../../hooks/use_kibana'; +import { useEventTracker } from '../../analytics/event_tracker_context'; export const ModelSettings: React.FC = () => { const { @@ -75,6 +76,8 @@ export const ModelSettings: React.FC = () => { }; }, [isDirty, history]); + const eventTracker = useEventTracker(); + const handleSave = useCallback(async () => { if (isFeatureDirty) { saveFeatures(); @@ -82,7 +85,8 @@ export const ModelSettings: React.FC = () => { if (defaultModelSettings.isDirty) { await defaultModelSettings.save(); } - }, [isFeatureDirty, saveFeatures, defaultModelSettings]); + eventTracker.featureSettingsSaved(); + }, [isFeatureDirty, saveFeatures, defaultModelSettings, eventTracker]); const handleDiscardAndLeave = useCallback(() => { defaultModelSettings.reset(); diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx index 86460a05748f4..39eef1a0ea633 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx @@ -14,6 +14,7 @@ import { Router } from '@kbn/shared-ux-router'; import type { AppPluginStartDependencies } from './types'; import { ElasticInferenceService } from './components/elastic_inference_service'; import { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; +import { EventTrackerProvider } from './analytics/event_tracker_context'; export const renderElasticInferenceServiceApp = async ( core: CoreStart, @@ -26,7 +27,9 @@ export const renderElasticInferenceServiceApp = async ( - + + + diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx index 35d2302668809..c46c02747c7e9 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx @@ -9,6 +9,7 @@ import { useMutation, useQueryClient } from '@kbn/react-query'; import type { KibanaServerError } from '@kbn/kibana-utils-plugin/common'; import { i18n } from '@kbn/i18n'; import { useKibana } from './use_kibana'; +import { useEventTracker } from '../analytics/event_tracker_context'; import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants'; @@ -21,6 +22,7 @@ export const useDeleteEndpoint = (onSuccess?: () => void) => { const queryClient = useQueryClient(); const { services } = useKibana(); const toasts = services.notifications?.toasts; + const eventTracker = useEventTracker(); return useMutation( async ({ type, id }: MutationArgs) => { @@ -29,6 +31,7 @@ export const useDeleteEndpoint = (onSuccess?: () => void) => { { onSuccess: () => { queryClient.invalidateQueries([INFERENCE_ENDPOINTS_QUERY_KEY]); + eventTracker.endpointDeleted(); toasts?.addSuccess({ title: i18n.translate('xpack.searchInferenceEndpoints.deleteEndpoint.deleteSuccess', { defaultMessage: 'The inference endpoint has been deleted sucessfully.', @@ -39,6 +42,7 @@ export const useDeleteEndpoint = (onSuccess?: () => void) => { } }, onError: (error: { body: KibanaServerError }) => { + eventTracker.apiError('delete'); toasts?.addError(new Error(error.body.message), { title: i18n.translate( 'xpack.searchInferenceEndpoints.deleteEndpoint.endpointDeletionFailed', diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx index 8f5198e339366..0fde7971370aa 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx @@ -7,6 +7,7 @@ import { useQuery } from '@kbn/react-query'; import { useKibana } from './use_kibana'; +import { useEventTracker } from '../analytics/event_tracker_context'; import type { InferenceUsageResponse } from '../types'; interface ScanUsageProps { @@ -16,17 +17,21 @@ interface ScanUsageProps { export const useScanUsage = ({ type, id }: ScanUsageProps) => { const { services } = useKibana(); + const eventTracker = useEventTracker(); return useQuery({ queryKey: ['inference-endpoint-scan-usage', type, id], - queryFn: () => - services.http.delete( + queryFn: async () => { + const response = await services.http.delete( `/internal/inference_endpoint/endpoints/${type}/${id}`, { query: { scanUsage: true, }, } - ), + ); + eventTracker.endpointUsageScanned(); + return response; + }, }); }; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts index 52a8035500c22..4db511b244790 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts @@ -24,6 +24,7 @@ import type { SearchInferenceEndpointsPluginStart, } from './types'; import { registerLocators } from './locators'; +import { registerSearchInferenceEndpointsEventTypes } from './analytics/register_event_types'; export class SearchInferenceEndpointsPlugin implements Plugin @@ -43,6 +44,7 @@ export class SearchInferenceEndpointsPlugin ): SearchInferenceEndpointsPluginSetup { if (!this.config.ui?.enabled) return {}; + registerSearchInferenceEndpointsEventTypes(core.analytics); registerLocators(plugins.share); this.registerInferenceEndpoints = diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts b/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts new file mode 100644 index 0000000000000..84ef3204e3898 --- /dev/null +++ b/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.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. + */ + +export enum AnalyticsEvents { + openedApp = 'opened_app', + agentBuilderOpened = 'agent_builder_opened', + claudeCliPromptCopied = 'claude_cli_prompt_copied', + codeExampleCopied = 'code_example_copied', + languageSelected = 'language_selected', + tutorialSelected = 'tutorial_selected', + connectionDetailsViewed = 'connection_details_viewed', +} diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/agent_install.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/agent_install.tsx index 386aceb19d9de..e6d97f41cb830 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/agent_install.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/agent_install.tsx @@ -27,6 +27,8 @@ import { useKibana } from '../../hooks/use_kibana'; import { PromptModal } from './prompt_modal'; import { buildPrompt } from './util'; import { AgentBuilderPanelContainer } from './styles'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; const AgentInstallPanel: React.FC<{ icon: string; @@ -62,6 +64,7 @@ export const AgentInstallSection = () => { const { services } = useKibana(); const [isPromptModalOpen, setIsPromptModalOpen] = useState(false); const [modalPrompt, setModalPrompt] = useState(''); + const usageTracker = useUsageTracker(); const closePromptModal = useCallback(() => setIsPromptModalOpen(false), []); @@ -73,13 +76,14 @@ export const AgentInstallSection = () => { }, []); const handleOpenInAgentBuilder = useCallback(() => { + usageTracker.click(AnalyticsEvents.agentBuilderOpened); services.agentBuilder?.openChat({ initialMessage: buildPrompt('agent-builder'), autoSendInitialMessage: true, newConversation: true, sessionTag: 'search-getting-started', }); - }, [services.agentBuilder]); + }, [services.agentBuilder, usageTracker]); return ( <> diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/prompt_modal.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/prompt_modal.tsx index 7ee57ac5baf71..fdaf5cc14f318 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/prompt_modal.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/agent_install/prompt_modal.tsx @@ -21,6 +21,8 @@ import { useGeneratedHtmlId, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; interface PromptModalProps { prompt: string; @@ -29,6 +31,7 @@ interface PromptModalProps { export const PromptModal: React.FC = ({ prompt, onClose }) => { const modalTitleId = useGeneratedHtmlId({ prefix: 'promptModal' }); + const usageTracker = useUsageTracker(); return ( @@ -61,7 +64,14 @@ export const PromptModal: React.FC = ({ prompt, onClose }) => {(copy) => ( - + { + usageTracker.click(AnalyticsEvents.claudeCliPromptCopied); + copy(); + }} + fill + data-test-subj="promptModalCopyBtn" + > {i18n.translate('xpack.gettingStarted.promptModal.copy', { defaultMessage: 'Copy to clipboard', })} diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/language_selector.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/language_selector.tsx index e6428b448cd4e..6b61466a9d3ce 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/language_selector.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/language_selector.tsx @@ -12,6 +12,8 @@ import { i18n } from '@kbn/i18n'; import type { AvailableLanguages } from '@kbn/search-code-examples/src/getting-started-tutorials'; import type { CodeLanguage } from '@kbn/search-code-examples/src/getting-started-tutorials/types'; import { useAssetBasePath } from '../../hooks/use_asset_base_path'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; export interface LanguageSelectorProps { selectedLanguage: AvailableLanguages; @@ -27,6 +29,7 @@ export const LanguageSelector = ({ showLabel = false, }: LanguageSelectorProps) => { const assetBasePath = useAssetBasePath(); + const usageTracker = useUsageTracker(); const languageOptions = useMemo( () => options.map((lang) => ({ @@ -62,7 +65,13 @@ export const LanguageSelector = ({ })} options={languageOptions} valueOfSelected={selectedLanguage} - onChange={onSelectLanguage} + onChange={(value) => { + usageTracker.click([ + AnalyticsEvents.languageSelected, + `${AnalyticsEvents.languageSelected}_${value}`, + ]); + onSelectLanguage(value); + }} data-test-subj="codeExampleLanguageSelect" /> ); diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx index 85332b46b34d8..c015f063fda12 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx @@ -13,9 +13,12 @@ import { openWiredConnectionDetails } from '@kbn/cloud/connection_details'; import { ApiKeyForm } from '@kbn/search-api-keys-components'; import { FormInfoField } from '@kbn/search-shared-ui'; import { useElasticsearchUrl } from '../hooks/use_elasticsearch_url'; +import { useUsageTracker } from '../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../analytics/constants'; export const ElasticsearchConnectionDetails = () => { const elasticsearchUrl = useElasticsearchUrl(); + const usageTracker = useUsageTracker(); return ( @@ -67,7 +70,10 @@ export const ElasticsearchConnectionDetails = () => { iconType="plugs" iconSide="left" target="_blank" - onClick={() => openWiredConnectionDetails()} + onClick={() => { + usageTracker.click(AnalyticsEvents.connectionDetailsViewed); + openWiredConnectionDetails(); + }} color="text" aria-label={i18n.translate( 'xpack.search.gettingStarted.elasticsearchConnectionDetails.viewDetails', diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx index aafaa3a3bfb52..dbf5985b287d9 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx @@ -21,6 +21,8 @@ import { orderBy } from 'lodash'; import { useKibana } from '../../hooks/use_kibana'; import { isNew } from '../../common/utils'; import { TutorialCardStyles } from './styles'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; interface TutorialMetadata { title: string; @@ -35,6 +37,7 @@ export const ConsoleTutorialsGroup = () => { const isMediumBreakpoint = useIsWithinMaxBreakpoint('m'); const isSmallBreakpoint = useIsWithinMaxBreakpoint('s'); const tutorialColumns = isSmallBreakpoint ? 1 : isMediumBreakpoint ? 2 : 3; + const usageTracker = useUsageTracker(); const openConsole = useCallback( (request: string) => @@ -163,7 +166,13 @@ export const ConsoleTutorialsGroup = () => { css={TutorialCardStyles} data-test-subj={tutorial.dataTestSubj} data-telemetry-id={tutorial.dataTestSubj} - onClick={() => openConsole(tutorial.request)} + onClick={() => { + usageTracker.click([ + AnalyticsEvents.tutorialSelected, + `${AnalyticsEvents.tutorialSelected}_${tutorial.dataTestSubj}`, + ]); + openConsole(tutorial.request); + }} > diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/contexts/usage_tracker_context.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/contexts/usage_tracker_context.tsx index 7c1e0a27daa6c..a66a0cc76698d 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/contexts/usage_tracker_context.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/contexts/usage_tracker_context.tsx @@ -13,6 +13,7 @@ import type { import { createUsageTracker, createEmptyUsageTracker } from '../usage_tracker'; import type { AppUsageTracker } from '../types'; +import { AnalyticsEvents } from '../analytics/constants'; const UsageTrackerContext = createContext(createEmptyUsageTracker()); @@ -26,7 +27,7 @@ export function UsageTrackerContextProvider({ }: UsageTrackerContextProviderProps) { const usageTracker = useMemo(() => { const gettingStartedUsageTracker = createUsageTracker(usageCollection); - gettingStartedUsageTracker.load('opened_app'); + gettingStartedUsageTracker.load(AnalyticsEvents.openedApp); return gettingStartedUsageTracker; }, [usageCollection]); return ( diff --git a/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts b/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts new file mode 100644 index 0000000000000..4ae3ba224f486 --- /dev/null +++ b/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export enum AnalyticsEvents { + openedApp = 'opened_app', + metricPanelClicked = 'metric_panel_clicked', + connectionDetailsOpened = 'connection_details_opened', + metricFetchFailed = 'metric_fetch_failed', +} diff --git a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/basic_metric_badges.tsx b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/basic_metric_badges.tsx index a7479687c4455..b68f30c0ee202 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/basic_metric_badges.tsx +++ b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/basic_metric_badges.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { EuiBadge, @@ -21,6 +21,8 @@ import { useDashboardsStats } from '../../hooks/api/use_dashboards_stats'; import { useIndicesStats } from '../../hooks/api/use_indices_stats'; import { useStats } from '../../hooks/api/use_stats'; import { useAgentCount } from '../../hooks/api/use_agent_count'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; const BASIC_METRIC_PANEL_TYPES = ['indices', 'storage', 'agentBuilder', 'discover'] as const; @@ -83,6 +85,40 @@ export const BasicMetricBadges = () => { isError: isErrorDashboards, } = useDashboardsStats(); const { tools, agents, isLoading: isLoadingAgents, isError: isErrorAgents } = useAgentCount(); + const usageTracker = useUsageTracker(); + + useEffect(() => { + if (isErrorStorageStats) { + usageTracker.count([ + AnalyticsEvents.metricFetchFailed, + `${AnalyticsEvents.metricFetchFailed}_storage`, + ]); + } + }, [isErrorStorageStats, usageTracker]); + useEffect(() => { + if (isErrorIndicesStats) { + usageTracker.count([ + AnalyticsEvents.metricFetchFailed, + `${AnalyticsEvents.metricFetchFailed}_indices`, + ]); + } + }, [isErrorIndicesStats, usageTracker]); + useEffect(() => { + if (isErrorDashboards) { + usageTracker.count([ + AnalyticsEvents.metricFetchFailed, + `${AnalyticsEvents.metricFetchFailed}_dashboards`, + ]); + } + }, [isErrorDashboards, usageTracker]); + useEffect(() => { + if (isErrorAgents) { + usageTracker.count([ + AnalyticsEvents.metricFetchFailed, + `${AnalyticsEvents.metricFetchFailed}_agents`, + ]); + } + }, [isErrorAgents, usageTracker]); const basicPanels: Array = [ { diff --git a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx index 073a4d6e46563..df22c2833347d 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx +++ b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx @@ -22,9 +22,12 @@ import { FormInfoField } from '@kbn/search-shared-ui'; import { openWiredConnectionDetails } from '@kbn/cloud/connection_details'; import { useSearchApiKey, Status } from '@kbn/search-api-keys-components'; import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; export const ConnectToElasticsearch = () => { const elasticsearchUrl = useElasticsearchUrl(); + const usageTracker = useUsageTracker(); const { status } = useSearchApiKey(); const hasAPIKeyManagePermissions = useMemo(() => { @@ -63,11 +66,15 @@ export const ConnectToElasticsearch = () => { color="text" iconType="plusCircle" size="s" - onClick={() => + onClick={() => { + usageTracker.click([ + AnalyticsEvents.connectionDetailsOpened, + `${AnalyticsEvents.connectionDetailsOpened}_apiKeys`, + ]); openWiredConnectionDetails({ props: { options: { defaultTabId: 'apiKeys' } }, - }) - } + }); + }} disabled={!hasAPIKeyManagePermissions} > { size="s" iconSize="m" iconType="plugs" - onClick={() => openWiredConnectionDetails()} + onClick={() => { + usageTracker.click(AnalyticsEvents.connectionDetailsOpened); + openWiredConnectionDetails(); + }} data-test-subj="searchHomepageConnectToElasticsearchConnectionDetailsButton" color="text" aria-label={i18n.translate( diff --git a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx index 572dce8101ecd..2083e11225372 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx +++ b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx @@ -23,6 +23,8 @@ import type { ApplicationStart } from '@kbn/core/public'; import { WORKFLOWS_UI_SETTING_ENABLED_ID } from '../../../common'; import { useAssetBasePath } from '../../hooks/use_asset_base_path'; import { useKibana } from '../../hooks/use_kibana'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; const PANEL_TYPES = [ 'discover', @@ -170,12 +172,19 @@ const MetricPanelEmpty = ({ panel }: MetricPanelEmptyProps) => { } = useKibana(); const assetBasePath = useAssetBasePath(); const { euiTheme } = useEuiTheme(); + const usageTracker = useUsageTracker(); - const { getImageUrl, metricTitle, metricDescription, onPanelClick, dataTestSubj } = panel; + const { getImageUrl, metricTitle, metricDescription, onPanelClick, type, dataTestSubj } = panel; return ( onPanelClick && onPanelClick({ share, application })} + onClick={() => { + usageTracker.click([ + AnalyticsEvents.metricPanelClicked, + `${AnalyticsEvents.metricPanelClicked}_${type}`, + ]); + if (onPanelClick) onPanelClick({ share, application }); + }} data-test-subj={dataTestSubj} > diff --git a/x-pack/solutions/search/plugins/search_homepage/public/contexts/usage_tracker_context.tsx b/x-pack/solutions/search/plugins/search_homepage/public/contexts/usage_tracker_context.tsx index a6a65892ebcdf..2ffe5277ef6f6 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/contexts/usage_tracker_context.tsx +++ b/x-pack/solutions/search/plugins/search_homepage/public/contexts/usage_tracker_context.tsx @@ -13,6 +13,7 @@ import type { import { createUsageTracker, createEmptyUsageTracker } from '../usage_tracker'; import type { AppUsageTracker } from '../types'; +import { AnalyticsEvents } from '../analytics/constants'; const UsageTrackerContext = createContext(createEmptyUsageTracker()); @@ -26,7 +27,7 @@ export function UsageTrackerContextProvider({ }: UsageTrackerContextProviderProps) { const usageTracker = useMemo(() => { const homePageUsageTracker = createUsageTracker(usageCollection); - homePageUsageTracker.load('opened_app'); + homePageUsageTracker.load(AnalyticsEvents.openedApp); return homePageUsageTracker; }, [usageCollection]); return ( From bb2b9563afdca664c670d7743864254d777b39c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Thu, 23 Apr 2026 17:39:46 +0200 Subject: [PATCH 2/7] review-changes --- .../public/analytics/event_tracker.ts | 12 --- .../public/analytics/register_event_types.ts | 80 ++++++++++++++----- .../public/application.tsx | 8 +- .../elastic_inference_service_application.tsx | 8 +- .../public/hooks/use_delete_endpoint.tsx | 4 - .../public/hooks/use_scan_usage.tsx | 11 +-- .../public/analytics/constants.ts | 2 - .../elasticsearch_connection_details.tsx | 8 +- .../tutorials/console_tutorials_group.tsx | 11 +-- .../public/analytics/constants.ts | 2 - .../connect_to_elasticsearch.tsx | 18 +---- .../search_homepage/metric_panels.tsx | 13 +-- 12 files changed, 78 insertions(+), 99 deletions(-) diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts index a3dd3119c55c4..2928af1b6e826 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts @@ -9,15 +9,12 @@ import type { AnalyticsServiceStart } from '@kbn/core/public'; export enum EventType { ENDPOINT_CREATED = 'searchInferenceEndpoints_endpoint_created', - ENDPOINT_DELETED = 'searchInferenceEndpoints_endpoint_deleted', ENDPOINT_EDITED = 'searchInferenceEndpoints_endpoint_edited', DEFAULT_MODEL_CHANGED = 'searchInferenceEndpoints_default_model_changed', FEATURE_SETTINGS_SAVED = 'searchInferenceEndpoints_feature_settings_saved', FILTER_APPLIED = 'searchInferenceEndpoints_filter_applied', GROUP_BY_CHANGED = 'searchInferenceEndpoints_group_by_changed', - ENDPOINT_USAGE_SCANNED = 'searchInferenceEndpoints_endpoint_usage_scanned', EMPTY_STATE_VIEWED = 'searchInferenceEndpoints_empty_state_viewed', - API_ERROR = 'searchInferenceEndpoints_api_error', FLYOUT_OPENED = 'searchInferenceEndpoints_flyout_opened', FLYOUT_CLOSED = 'searchInferenceEndpoints_flyout_closed', MODAL_OPENED = 'searchInferenceEndpoints_modal_opened', @@ -40,9 +37,6 @@ export class EventTracker { endpointCreated() { this.track(EventType.ENDPOINT_CREATED); } - endpointDeleted() { - this.track(EventType.ENDPOINT_DELETED); - } endpointEdited() { this.track(EventType.ENDPOINT_EDITED); } @@ -58,15 +52,9 @@ export class EventTracker { groupByChanged(groupBy: string) { this.track(EventType.GROUP_BY_CHANGED, { group_by: groupBy }); } - endpointUsageScanned() { - this.track(EventType.ENDPOINT_USAGE_SCANNED); - } emptyStateViewed() { this.track(EventType.EMPTY_STATE_VIEWED); } - apiError(operation: string) { - this.track(EventType.API_ERROR, { operation }); - } flyoutOpened(flyout: string) { this.track(EventType.FLYOUT_OPENED, { flyout }); } diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts index df5cde9bed0db..dea9e806aeb95 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts @@ -10,46 +10,72 @@ import type { AnalyticsServiceSetup, EventTypeOpts } from '@kbn/core/public'; import { EventType } from './event_tracker'; const eventTypes: Array>> = [ - { eventType: EventType.ENDPOINT_CREATED, schema: {} }, - { eventType: EventType.ENDPOINT_DELETED, schema: {} }, - { eventType: EventType.ENDPOINT_EDITED, schema: {} }, - { eventType: EventType.DEFAULT_MODEL_CHANGED, schema: {} }, - { eventType: EventType.FEATURE_SETTINGS_SAVED, schema: {} }, + { + eventType: EventType.ENDPOINT_CREATED, + schema: {}, + // Emitted from InferenceFlyoutWrapper.onSubmitSuccess when an external inference + // endpoint is successfully created via the add-endpoint flyout. + }, + { + eventType: EventType.ENDPOINT_EDITED, + schema: {}, + // Emitted from InferenceFlyoutWrapper.onSubmitSuccess (edit mode) when an + // existing inference endpoint's configuration is successfully saved. + }, + { + eventType: EventType.DEFAULT_MODEL_CHANGED, + schema: {}, + // Emitted when the user picks a different default model in the feature-settings + // combobox (fired on selection, not on save). + }, + { + eventType: EventType.FEATURE_SETTINGS_SAVED, + schema: {}, + // Emitted when the user saves the feature-settings page via the Save button. + // Fires after both feature assignments and default-model changes have been persisted. + }, { eventType: EventType.FILTER_APPLIED, schema: { filter: { type: 'keyword', - _meta: { description: 'Identifier of the filter popover the user interacted with.' }, + _meta: { + description: + 'Identifier of the multi-select filter popover the user changed. Value is the popover\'s dataTestSubj (e.g. "provider-type-select", "task-type-select"); "unknown" if the filter has no dataTestSubj.', + }, }, }, + // Emitted when the user changes the selected options inside a MultiSelectFilter popover + // on the inference endpoints list (once per onChange, regardless of how many options changed). }, { eventType: EventType.GROUP_BY_CHANGED, schema: { group_by: { type: 'keyword', - _meta: { description: 'The group-by option selected by the user.' }, + _meta: { + description: + 'New grouping option selected on the inference-endpoints list. One of: "none", "model_id", "service".', + }, }, }, + // Emitted when the user selects a different option in the Group-by popover. }, - { eventType: EventType.ENDPOINT_USAGE_SCANNED, schema: {} }, - { eventType: EventType.EMPTY_STATE_VIEWED, schema: {} }, { - eventType: EventType.API_ERROR, - schema: { - operation: { - type: 'keyword', - _meta: { description: 'The operation that failed (e.g. delete).' }, - }, - }, + eventType: EventType.EMPTY_STATE_VIEWED, + schema: {}, + // Emitted once per mount of ExternalInferenceEmptyPrompt (user has no external + // inference endpoints configured). Impression signal, not a click. }, { eventType: EventType.FLYOUT_OPENED, schema: { flyout: { type: 'keyword', - _meta: { description: 'Identifier of the flyout that was opened.' }, + _meta: { + description: + 'Identifier of the flyout that opened. Allowed values: "add_inference" (add-endpoint flyout on the external inference page).', + }, }, }, }, @@ -58,7 +84,9 @@ const eventTypes: Array>> = [ schema: { flyout: { type: 'keyword', - _meta: { description: 'Identifier of the flyout that was closed.' }, + _meta: { + description: 'Identifier of the flyout that closed. Allowed values: "add_inference".', + }, }, }, }, @@ -67,17 +95,27 @@ const eventTypes: Array>> = [ schema: { modal: { type: 'keyword', - _meta: { description: 'Identifier of the modal that was opened.' }, + _meta: { + description: + 'Identifier of the modal that opened inside the model detail flyout. Allowed values: "add_endpoint", "edit_endpoint".', + }, }, }, }, - { eventType: EventType.MODAL_CLOSED, schema: {} }, + { + eventType: EventType.MODAL_CLOSED, + schema: {}, + // Emitted when the add/edit endpoint modal inside the model detail flyout is dismissed. + }, { eventType: EventType.EIS_MODEL_VIEWED, schema: { model_id: { type: 'keyword', - _meta: { description: 'Identifier of the Elastic Inference Service model viewed.' }, + _meta: { + description: + 'Elastic Inference Service catalog model identifier shown in the model detail flyout (e.g. ".elser_model_2", "rainbow-sprinkles", ".multilingual-e5-small"). Only emitted for EIS-hosted models; never user-provided strings.', + }, }, }, }, diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx index 8f193932a1389..9dcbd69fda402 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx @@ -25,11 +25,11 @@ const renderMgmtApp = ( - - + + - - + + , diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx index 39eef1a0ea633..18f4477168899 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx @@ -26,11 +26,11 @@ export const renderElasticInferenceServiceApp = async ( - - + + - - + + diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx index c46c02747c7e9..35d2302668809 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_delete_endpoint.tsx @@ -9,7 +9,6 @@ import { useMutation, useQueryClient } from '@kbn/react-query'; import type { KibanaServerError } from '@kbn/kibana-utils-plugin/common'; import { i18n } from '@kbn/i18n'; import { useKibana } from './use_kibana'; -import { useEventTracker } from '../analytics/event_tracker_context'; import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants'; @@ -22,7 +21,6 @@ export const useDeleteEndpoint = (onSuccess?: () => void) => { const queryClient = useQueryClient(); const { services } = useKibana(); const toasts = services.notifications?.toasts; - const eventTracker = useEventTracker(); return useMutation( async ({ type, id }: MutationArgs) => { @@ -31,7 +29,6 @@ export const useDeleteEndpoint = (onSuccess?: () => void) => { { onSuccess: () => { queryClient.invalidateQueries([INFERENCE_ENDPOINTS_QUERY_KEY]); - eventTracker.endpointDeleted(); toasts?.addSuccess({ title: i18n.translate('xpack.searchInferenceEndpoints.deleteEndpoint.deleteSuccess', { defaultMessage: 'The inference endpoint has been deleted sucessfully.', @@ -42,7 +39,6 @@ export const useDeleteEndpoint = (onSuccess?: () => void) => { } }, onError: (error: { body: KibanaServerError }) => { - eventTracker.apiError('delete'); toasts?.addError(new Error(error.body.message), { title: i18n.translate( 'xpack.searchInferenceEndpoints.deleteEndpoint.endpointDeletionFailed', diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx index 0fde7971370aa..8f5198e339366 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/hooks/use_scan_usage.tsx @@ -7,7 +7,6 @@ import { useQuery } from '@kbn/react-query'; import { useKibana } from './use_kibana'; -import { useEventTracker } from '../analytics/event_tracker_context'; import type { InferenceUsageResponse } from '../types'; interface ScanUsageProps { @@ -17,21 +16,17 @@ interface ScanUsageProps { export const useScanUsage = ({ type, id }: ScanUsageProps) => { const { services } = useKibana(); - const eventTracker = useEventTracker(); return useQuery({ queryKey: ['inference-endpoint-scan-usage', type, id], - queryFn: async () => { - const response = await services.http.delete( + queryFn: () => + services.http.delete( `/internal/inference_endpoint/endpoints/${type}/${id}`, { query: { scanUsage: true, }, } - ); - eventTracker.endpointUsageScanned(); - return response; - }, + ), }); }; diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts b/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts index 84ef3204e3898..b69b167eac897 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts +++ b/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts @@ -11,6 +11,4 @@ export enum AnalyticsEvents { claudeCliPromptCopied = 'claude_cli_prompt_copied', codeExampleCopied = 'code_example_copied', languageSelected = 'language_selected', - tutorialSelected = 'tutorial_selected', - connectionDetailsViewed = 'connection_details_viewed', } diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx index c015f063fda12..85332b46b34d8 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/elasticsearch_connection_details.tsx @@ -13,12 +13,9 @@ import { openWiredConnectionDetails } from '@kbn/cloud/connection_details'; import { ApiKeyForm } from '@kbn/search-api-keys-components'; import { FormInfoField } from '@kbn/search-shared-ui'; import { useElasticsearchUrl } from '../hooks/use_elasticsearch_url'; -import { useUsageTracker } from '../contexts/usage_tracker_context'; -import { AnalyticsEvents } from '../analytics/constants'; export const ElasticsearchConnectionDetails = () => { const elasticsearchUrl = useElasticsearchUrl(); - const usageTracker = useUsageTracker(); return ( @@ -70,10 +67,7 @@ export const ElasticsearchConnectionDetails = () => { iconType="plugs" iconSide="left" target="_blank" - onClick={() => { - usageTracker.click(AnalyticsEvents.connectionDetailsViewed); - openWiredConnectionDetails(); - }} + onClick={() => openWiredConnectionDetails()} color="text" aria-label={i18n.translate( 'xpack.search.gettingStarted.elasticsearchConnectionDetails.viewDetails', diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx index dbf5985b287d9..aafaa3a3bfb52 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/tutorials/console_tutorials_group.tsx @@ -21,8 +21,6 @@ import { orderBy } from 'lodash'; import { useKibana } from '../../hooks/use_kibana'; import { isNew } from '../../common/utils'; import { TutorialCardStyles } from './styles'; -import { useUsageTracker } from '../../contexts/usage_tracker_context'; -import { AnalyticsEvents } from '../../analytics/constants'; interface TutorialMetadata { title: string; @@ -37,7 +35,6 @@ export const ConsoleTutorialsGroup = () => { const isMediumBreakpoint = useIsWithinMaxBreakpoint('m'); const isSmallBreakpoint = useIsWithinMaxBreakpoint('s'); const tutorialColumns = isSmallBreakpoint ? 1 : isMediumBreakpoint ? 2 : 3; - const usageTracker = useUsageTracker(); const openConsole = useCallback( (request: string) => @@ -166,13 +163,7 @@ export const ConsoleTutorialsGroup = () => { css={TutorialCardStyles} data-test-subj={tutorial.dataTestSubj} data-telemetry-id={tutorial.dataTestSubj} - onClick={() => { - usageTracker.click([ - AnalyticsEvents.tutorialSelected, - `${AnalyticsEvents.tutorialSelected}_${tutorial.dataTestSubj}`, - ]); - openConsole(tutorial.request); - }} + onClick={() => openConsole(tutorial.request)} > diff --git a/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts b/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts index 4ae3ba224f486..7e3f10504eba1 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts +++ b/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts @@ -7,7 +7,5 @@ export enum AnalyticsEvents { openedApp = 'opened_app', - metricPanelClicked = 'metric_panel_clicked', - connectionDetailsOpened = 'connection_details_opened', metricFetchFailed = 'metric_fetch_failed', } diff --git a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx index df22c2833347d..073a4d6e46563 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx +++ b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/connect_to_elasticsearch.tsx @@ -22,12 +22,9 @@ import { FormInfoField } from '@kbn/search-shared-ui'; import { openWiredConnectionDetails } from '@kbn/cloud/connection_details'; import { useSearchApiKey, Status } from '@kbn/search-api-keys-components'; import { useElasticsearchUrl } from '../../hooks/use_elasticsearch_url'; -import { useUsageTracker } from '../../contexts/usage_tracker_context'; -import { AnalyticsEvents } from '../../analytics/constants'; export const ConnectToElasticsearch = () => { const elasticsearchUrl = useElasticsearchUrl(); - const usageTracker = useUsageTracker(); const { status } = useSearchApiKey(); const hasAPIKeyManagePermissions = useMemo(() => { @@ -66,15 +63,11 @@ export const ConnectToElasticsearch = () => { color="text" iconType="plusCircle" size="s" - onClick={() => { - usageTracker.click([ - AnalyticsEvents.connectionDetailsOpened, - `${AnalyticsEvents.connectionDetailsOpened}_apiKeys`, - ]); + onClick={() => openWiredConnectionDetails({ props: { options: { defaultTabId: 'apiKeys' } }, - }); - }} + }) + } disabled={!hasAPIKeyManagePermissions} > { size="s" iconSize="m" iconType="plugs" - onClick={() => { - usageTracker.click(AnalyticsEvents.connectionDetailsOpened); - openWiredConnectionDetails(); - }} + onClick={() => openWiredConnectionDetails()} data-test-subj="searchHomepageConnectToElasticsearchConnectionDetailsButton" color="text" aria-label={i18n.translate( diff --git a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx index 2083e11225372..572dce8101ecd 100644 --- a/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx +++ b/x-pack/solutions/search/plugins/search_homepage/public/components/search_homepage/metric_panels.tsx @@ -23,8 +23,6 @@ import type { ApplicationStart } from '@kbn/core/public'; import { WORKFLOWS_UI_SETTING_ENABLED_ID } from '../../../common'; import { useAssetBasePath } from '../../hooks/use_asset_base_path'; import { useKibana } from '../../hooks/use_kibana'; -import { useUsageTracker } from '../../contexts/usage_tracker_context'; -import { AnalyticsEvents } from '../../analytics/constants'; const PANEL_TYPES = [ 'discover', @@ -172,19 +170,12 @@ const MetricPanelEmpty = ({ panel }: MetricPanelEmptyProps) => { } = useKibana(); const assetBasePath = useAssetBasePath(); const { euiTheme } = useEuiTheme(); - const usageTracker = useUsageTracker(); - const { getImageUrl, metricTitle, metricDescription, onPanelClick, type, dataTestSubj } = panel; + const { getImageUrl, metricTitle, metricDescription, onPanelClick, dataTestSubj } = panel; return ( { - usageTracker.click([ - AnalyticsEvents.metricPanelClicked, - `${AnalyticsEvents.metricPanelClicked}_${type}`, - ]); - if (onPanelClick) onPanelClick({ share, application }); - }} + onClick={() => onPanelClick && onPanelClick({ share, application })} data-test-subj={dataTestSubj} > From 89b88495d00ab147a7f91cd7b6a7ce43f0a2d85e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Fri, 24 Apr 2026 17:21:42 +0200 Subject: [PATCH 3/7] Update limits --- packages/kbn-optimizer/limits.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 69f1a56ef7cad..79a7432ed4fa1 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -97,9 +97,9 @@ pageLoadAssetSize: interactiveSetup: 36524 intercepts: 21066 kibanaOverview: 6339 - kibanaReact: 21499 + kibanaReact: 21495 kibanaUsageCollection: 1736 - kibanaUtils: 54161 + kibanaUtils: 54084 kql: 15428 kubernetesSecurity: 6807 lens: 90000 @@ -154,7 +154,7 @@ pageLoadAssetSize: searchAssistant: 7079 searchGettingStarted: 6678 searchHomepage: 9005 - searchInferenceEndpoints: 10345 + searchInferenceEndpoints: 13404 searchNavigation: 8900 searchNotebooks: 18826 searchPlayground: 12122 From d25ade5edcfe0ed0837283ef4930dfd5a8366dc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Mon, 27 Apr 2026 15:24:17 +0200 Subject: [PATCH 4/7] Update event tracker --- .../search_inference_endpoints/kibana.jsonc | 1 + .../public/analytics/constants.ts | 22 +++ .../public/analytics/event_tracker.ts | 73 ---------- .../analytics/event_tracker_context.tsx | 31 ----- .../public/analytics/register_event_types.ts | 128 ------------------ .../public/application.tsx | 6 +- .../add_inference_flyout_wrapper.tsx | 9 +- .../group_by_select.tsx | 9 +- .../edit_inference_flyout.tsx | 9 +- .../external_inference_empty_prompt.tsx | 9 +- .../components/filter/multi_select_filter.tsx | 8 +- .../public/components/inference_endpoints.tsx | 13 +- .../model_detail_flyout.tsx | 21 +-- .../components/settings/copy_to_modal.tsx | 13 +- .../settings/default_model_section.tsx | 7 +- .../components/settings/model_settings.tsx | 9 +- .../public/contexts/usage_tracker_context.tsx | 40 ++++++ .../elastic_inference_service_application.tsx | 6 +- .../public/plugin.ts | 2 - .../public/types.ts | 12 ++ .../public/usage_tracker.ts | 41 ++++++ .../connect_code/install_command_code_box.tsx | 15 +- 22 files changed, 198 insertions(+), 286 deletions(-) create mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts delete mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts delete mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx delete mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts create mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/contexts/usage_tracker_context.tsx create mode 100644 x-pack/platform/plugins/shared/search_inference_endpoints/public/usage_tracker.ts diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/kibana.jsonc b/x-pack/platform/plugins/shared/search_inference_endpoints/kibana.jsonc index 140b77f5f501b..2a3479fc5e12e 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/kibana.jsonc +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/kibana.jsonc @@ -25,6 +25,7 @@ "cloudConnect", "console", "serverless", + "usageCollection", ], "requiredBundles": [ "kibanaReact", diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts new file mode 100644 index 0000000000000..40282334f203a --- /dev/null +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export enum EventType { + ENDPOINT_CREATED = 'searchInferenceEndpoints_endpoint_created', + ENDPOINT_EDITED = 'searchInferenceEndpoints_endpoint_edited', + DEFAULT_MODEL_CHANGED = 'searchInferenceEndpoints_default_model_changed', + FEATURE_SETTINGS_SAVED = 'searchInferenceEndpoints_feature_settings_saved', + FILTER_APPLIED = 'searchInferenceEndpoints_filter_applied', + GROUP_BY_CHANGED = 'searchInferenceEndpoints_group_by_changed', + EMPTY_STATE_VIEWED = 'searchInferenceEndpoints_empty_state_viewed', + FLYOUT_OPENED = 'searchInferenceEndpoints_flyout_opened', + FLYOUT_CLOSED = 'searchInferenceEndpoints_flyout_closed', + MODAL_OPENED = 'searchInferenceEndpoints_modal_opened', + MODAL_CLOSED = 'searchInferenceEndpoints_modal_closed', + EIS_MODEL_VIEWED = 'searchInferenceEndpoints_eis_model_viewed', + COPY_TO_FEATURE_TOGGLED = 'searchInferenceEndpoints_copy_to_feature_toggled', +} diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts deleted file mode 100644 index 2928af1b6e826..0000000000000 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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 { AnalyticsServiceStart } from '@kbn/core/public'; - -export enum EventType { - ENDPOINT_CREATED = 'searchInferenceEndpoints_endpoint_created', - ENDPOINT_EDITED = 'searchInferenceEndpoints_endpoint_edited', - DEFAULT_MODEL_CHANGED = 'searchInferenceEndpoints_default_model_changed', - FEATURE_SETTINGS_SAVED = 'searchInferenceEndpoints_feature_settings_saved', - FILTER_APPLIED = 'searchInferenceEndpoints_filter_applied', - GROUP_BY_CHANGED = 'searchInferenceEndpoints_group_by_changed', - EMPTY_STATE_VIEWED = 'searchInferenceEndpoints_empty_state_viewed', - FLYOUT_OPENED = 'searchInferenceEndpoints_flyout_opened', - FLYOUT_CLOSED = 'searchInferenceEndpoints_flyout_closed', - MODAL_OPENED = 'searchInferenceEndpoints_modal_opened', - MODAL_CLOSED = 'searchInferenceEndpoints_modal_closed', - EIS_MODEL_VIEWED = 'searchInferenceEndpoints_eis_model_viewed', -} - -export class EventTracker { - constructor(private readonly analytics: Pick) {} - - private track(eventType: EventType, data: Record = {}) { - try { - this.analytics.reportEvent(eventType, data); - } catch (err) { - // eslint-disable-next-line no-console - console.error(err); - } - } - - endpointCreated() { - this.track(EventType.ENDPOINT_CREATED); - } - endpointEdited() { - this.track(EventType.ENDPOINT_EDITED); - } - defaultModelChanged() { - this.track(EventType.DEFAULT_MODEL_CHANGED); - } - featureSettingsSaved() { - this.track(EventType.FEATURE_SETTINGS_SAVED); - } - filterApplied(filter: string) { - this.track(EventType.FILTER_APPLIED, { filter }); - } - groupByChanged(groupBy: string) { - this.track(EventType.GROUP_BY_CHANGED, { group_by: groupBy }); - } - emptyStateViewed() { - this.track(EventType.EMPTY_STATE_VIEWED); - } - flyoutOpened(flyout: string) { - this.track(EventType.FLYOUT_OPENED, { flyout }); - } - flyoutClosed(flyout: string) { - this.track(EventType.FLYOUT_CLOSED, { flyout }); - } - modalOpened(modal: string) { - this.track(EventType.MODAL_OPENED, { modal }); - } - modalClosed() { - this.track(EventType.MODAL_CLOSED); - } - eisModelViewed(modelId: string) { - this.track(EventType.EIS_MODEL_VIEWED, { model_id: modelId }); - } -} diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx deleted file mode 100644 index 25dc1ffaffc7a..0000000000000 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/event_tracker_context.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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, { createContext, useContext, useMemo } from 'react'; -import type { AnalyticsServiceStart } from '@kbn/core/public'; - -import { EventTracker } from './event_tracker'; - -const EventTrackerContext = createContext(null); - -export interface EventTrackerProviderProps { - children: React.ReactNode | React.ReactNode[]; - analytics: AnalyticsServiceStart; -} - -export const EventTrackerProvider = ({ children, analytics }: EventTrackerProviderProps) => { - const tracker = useMemo(() => new EventTracker(analytics), [analytics]); - return {children}; -}; - -export const useEventTracker = (): EventTracker => { - const tracker = useContext(EventTrackerContext); - if (!tracker) { - throw new Error('useEventTracker must be used inside an EventTrackerProvider'); - } - return tracker; -}; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts deleted file mode 100644 index dea9e806aeb95..0000000000000 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/register_event_types.ts +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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, EventTypeOpts } from '@kbn/core/public'; - -import { EventType } from './event_tracker'; - -const eventTypes: Array>> = [ - { - eventType: EventType.ENDPOINT_CREATED, - schema: {}, - // Emitted from InferenceFlyoutWrapper.onSubmitSuccess when an external inference - // endpoint is successfully created via the add-endpoint flyout. - }, - { - eventType: EventType.ENDPOINT_EDITED, - schema: {}, - // Emitted from InferenceFlyoutWrapper.onSubmitSuccess (edit mode) when an - // existing inference endpoint's configuration is successfully saved. - }, - { - eventType: EventType.DEFAULT_MODEL_CHANGED, - schema: {}, - // Emitted when the user picks a different default model in the feature-settings - // combobox (fired on selection, not on save). - }, - { - eventType: EventType.FEATURE_SETTINGS_SAVED, - schema: {}, - // Emitted when the user saves the feature-settings page via the Save button. - // Fires after both feature assignments and default-model changes have been persisted. - }, - { - eventType: EventType.FILTER_APPLIED, - schema: { - filter: { - type: 'keyword', - _meta: { - description: - 'Identifier of the multi-select filter popover the user changed. Value is the popover\'s dataTestSubj (e.g. "provider-type-select", "task-type-select"); "unknown" if the filter has no dataTestSubj.', - }, - }, - }, - // Emitted when the user changes the selected options inside a MultiSelectFilter popover - // on the inference endpoints list (once per onChange, regardless of how many options changed). - }, - { - eventType: EventType.GROUP_BY_CHANGED, - schema: { - group_by: { - type: 'keyword', - _meta: { - description: - 'New grouping option selected on the inference-endpoints list. One of: "none", "model_id", "service".', - }, - }, - }, - // Emitted when the user selects a different option in the Group-by popover. - }, - { - eventType: EventType.EMPTY_STATE_VIEWED, - schema: {}, - // Emitted once per mount of ExternalInferenceEmptyPrompt (user has no external - // inference endpoints configured). Impression signal, not a click. - }, - { - eventType: EventType.FLYOUT_OPENED, - schema: { - flyout: { - type: 'keyword', - _meta: { - description: - 'Identifier of the flyout that opened. Allowed values: "add_inference" (add-endpoint flyout on the external inference page).', - }, - }, - }, - }, - { - eventType: EventType.FLYOUT_CLOSED, - schema: { - flyout: { - type: 'keyword', - _meta: { - description: 'Identifier of the flyout that closed. Allowed values: "add_inference".', - }, - }, - }, - }, - { - eventType: EventType.MODAL_OPENED, - schema: { - modal: { - type: 'keyword', - _meta: { - description: - 'Identifier of the modal that opened inside the model detail flyout. Allowed values: "add_endpoint", "edit_endpoint".', - }, - }, - }, - }, - { - eventType: EventType.MODAL_CLOSED, - schema: {}, - // Emitted when the add/edit endpoint modal inside the model detail flyout is dismissed. - }, - { - eventType: EventType.EIS_MODEL_VIEWED, - schema: { - model_id: { - type: 'keyword', - _meta: { - description: - 'Elastic Inference Service catalog model identifier shown in the model detail flyout (e.g. ".elser_model_2", "rainbow-sprinkles", ".multilingual-e5-small"). Only emitted for EIS-hosted models; never user-provided strings.', - }, - }, - }, - }, -]; - -export const registerSearchInferenceEndpointsEventTypes = (analytics: AnalyticsServiceSetup) => { - for (const eventType of eventTypes) { - analytics.registerEventType(eventType); - } -}; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx index 9dcbd69fda402..b81798a7c2204 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx @@ -13,7 +13,7 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; import { Router } from '@kbn/shared-ux-router'; import type { AppPluginStartDependencies } from './types'; -import { EventTrackerProvider } from './analytics/event_tracker_context'; +import { UsageTrackerContextProvider } from './contexts/usage_tracker_context'; const renderMgmtApp = ( core: CoreStart, @@ -25,11 +25,11 @@ const renderMgmtApp = ( - + - + , diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx index 28e303ae381ba..39b94768e1a6a 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/add_inference_endpoints/add_inference_flyout_wrapper.tsx @@ -10,7 +10,8 @@ import React, { useCallback } from 'react'; import InferenceFlyoutWrapper from '@kbn/inference-endpoint-ui-common'; import { ServiceProviderKeys } from '@kbn/inference-endpoint-ui-common'; import { useKibana } from '../../hooks/use_kibana'; -import { useEventTracker } from '../../analytics/event_tracker_context'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { EventType } from '../../analytics/constants'; const EXCLUDED_PROVIDERS = [ServiceProviderKeys.elasticsearch, ServiceProviderKeys.elastic]; @@ -30,12 +31,12 @@ export const AddInferenceFlyoutWrapper: React.FC serverless, }, } = useKibana(); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const onSubmitSuccess = useCallback(() => { - eventTracker.endpointCreated(); + usageTracker.count(EventType.ENDPOINT_CREATED); reloadFn(); - }, [reloadFn, eventTracker]); + }, [reloadFn, usageTracker]); return ( { const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const handleValueChange = useCallback( (newOptions: EuiSelectableOption[]) => { const selectedOption = newOptions.find((option) => option.checked === 'on'); const parsed = parseGroupByValue(selectedOption?.key); - eventTracker.groupByChanged(parsed); + usageTracker.count([EventType.GROUP_BY_CHANGED, `${EventType.GROUP_BY_CHANGED}_${parsed}`]); onChange(parsed); setIsPopoverOpen(false); }, - [onChange, eventTracker] + [onChange, usageTracker] ); const { options, selectedOptionLabel } = useMemo(() => { let selectedOption = GROUP_BY_OPTIONS[0].label; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx index 97694124f75d4..d54169687a073 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/edit_inference_endpoints/edit_inference_flyout.tsx @@ -12,7 +12,8 @@ import { flattenObject } from '@kbn/object-utils'; import type { InferenceInferenceEndpointInfo } from '@elastic/elasticsearch/lib/api/types'; import { useKibana } from '../../hooks/use_kibana'; import { useQueryInferenceEndpoints } from '../../hooks/use_inference_endpoints'; -import { useEventTracker } from '../../analytics/event_tracker_context'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { EventType } from '../../analytics/constants'; interface EditInterfaceFlyoutProps { onFlyoutClose: () => void; @@ -30,11 +31,11 @@ export const EditInferenceFlyout: React.FC = ({ }, } = useKibana(); const { refetch } = useQueryInferenceEndpoints(); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const onEditSuccess = useCallback(() => { - eventTracker.endpointEdited(); + usageTracker.count(EventType.ENDPOINT_EDITED); refetch(); - }, [refetch, eventTracker]); + }, [refetch, usageTracker]); const onFocusReturn = useCallback(() => { // Defer focus until after any closing animations complete requestAnimationFrame(() => { diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx index ea95ac2d8e8e1..f5fa71ae6c60f 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx @@ -11,7 +11,8 @@ import { i18n } from '@kbn/i18n'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { docLinks } from '../../common/doc_links'; -import { useEventTracker } from '../analytics/event_tracker_context'; +import { useUsageTracker } from '../contexts/usage_tracker_context'; +import { EventType } from '../analytics/constants'; interface ExternalInferenceEmptyPromptProps { onFlyoutOpen: () => void; @@ -20,10 +21,10 @@ interface ExternalInferenceEmptyPromptProps { export const ExternalInferenceEmptyPrompt: React.FC = ({ onFlyoutOpen, }) => { - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); useEffect(() => { - eventTracker.emptyStateViewed(); - }, [eventTracker]); + usageTracker.count(EventType.EMPTY_STATE_VIEWED); + }, [usageTracker]); return ( = ({ const euiThemeContext = useEuiTheme(); const [isPopoverOpen, setIsPopoverOpen] = useState(false); const toggleIsPopoverOpen = () => setIsPopoverOpen((prevValue) => !prevValue); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const options: MultiSelectFilterOption[] = _.uniqBy( rawOptions.map(({ key, label }) => ({ label, @@ -94,7 +95,8 @@ export const MultiSelectFilter: React.FC = ({ defaultMessage: 'No options', })} onChange={(newOptions) => { - eventTracker.filterApplied(dataTestSubj ?? 'unknown'); + const filter = dataTestSubj ?? 'unknown'; + usageTracker.count([EventType.FILTER_APPLIED, `${EventType.FILTER_APPLIED}_${filter}`]); onChange(newOptions); }} singleSelection={false} diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx index b57746e37c54c..416688e5e9afb 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/inference_endpoints.tsx @@ -16,22 +16,23 @@ import { TabularPage } from './all_inference_endpoints/tabular_page'; import { ExternalInferenceHeader } from './external_inference_header'; import { AddInferenceFlyoutWrapper } from './add_inference_endpoints/add_inference_flyout_wrapper'; import { ExternalInferenceEmptyPrompt } from './external_inference_empty_prompt'; -import { useEventTracker } from '../analytics/event_tracker_context'; +import { useUsageTracker } from '../contexts/usage_tracker_context'; +import { EventType } from '../analytics/constants'; export const InferenceEndpoints: React.FC = () => { const { data, isLoading, refetch } = useQueryInferenceEndpoints(); const [isAddInferenceFlyoutOpen, setIsAddInferenceFlyoutOpen] = useState(false); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const onFlyoutOpen = useCallback(() => { - eventTracker.flyoutOpened('add_inference'); + usageTracker.count([EventType.FLYOUT_OPENED, `${EventType.FLYOUT_OPENED}_add_inference`]); setIsAddInferenceFlyoutOpen(true); - }, [eventTracker]); + }, [usageTracker]); const onFlyoutClose = useCallback(() => { - eventTracker.flyoutClosed('add_inference'); + usageTracker.count([EventType.FLYOUT_CLOSED, `${EventType.FLYOUT_CLOSED}_add_inference`]); setIsAddInferenceFlyoutOpen(false); - }, [eventTracker]); + }, [usageTracker]); const reload = useCallback(() => { refetch(); diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx index 3de58cb9ebb48..393b30c4258ef 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/model_detail_flyout/model_detail_flyout.tsx @@ -35,7 +35,8 @@ import { import { getModelId } from '../../utils/get_model_id'; import { AddEndpointModal } from './add_endpoint_modal'; import { ModelEndpointRow } from './model_endpoint_row'; -import { useEventTracker } from '../../analytics/event_tracker_context'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { EventType } from '../../analytics/constants'; export interface ModelDetailFlyoutProps { modelId: string; @@ -57,11 +58,11 @@ export const ModelDetailFlyout: React.FC = ({ const flyoutTitleId = useGeneratedHtmlId(); const [isModalOpen, setIsModalOpen] = useState(false); const [editingEndpoint, setEditingEndpoint] = useState(); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); useEffect(() => { - eventTracker.eisModelViewed(modelId); - }, [eventTracker, modelId]); + usageTracker.count([EventType.EIS_MODEL_VIEWED, `${EventType.EIS_MODEL_VIEWED}_${modelId}`]); + }, [usageTracker, modelId]); const { endpoints, displayName, modelAuthor } = useMemo(() => { const filtered = allEndpoints.filter((ep) => getModelId(ep) === modelId); @@ -95,25 +96,25 @@ export const ModelDetailFlyout: React.FC = ({ }, [endpoints]); const handleOpenAddModal = useCallback(() => { - eventTracker.modalOpened('add_endpoint'); + usageTracker.count([EventType.MODAL_OPENED, `${EventType.MODAL_OPENED}_add_endpoint`]); setEditingEndpoint(undefined); setIsModalOpen(true); - }, [eventTracker]); + }, [usageTracker]); const handleOpenEditModal = useCallback( (endpoint: InferenceAPIConfigResponse) => { - eventTracker.modalOpened('edit_endpoint'); + usageTracker.count([EventType.MODAL_OPENED, `${EventType.MODAL_OPENED}_edit_endpoint`]); setEditingEndpoint(endpoint); setIsModalOpen(true); }, - [eventTracker] + [usageTracker] ); const handleCloseModal = useCallback(() => { - eventTracker.modalClosed(); + usageTracker.count(EventType.MODAL_CLOSED); setIsModalOpen(false); setEditingEndpoint(undefined); - }, [eventTracker]); + }, [usageTracker]); const descriptionListItems = [ { diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/copy_to_modal.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/copy_to_modal.tsx index 140622e89bd0c..660f283b38059 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/copy_to_modal.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/copy_to_modal.tsx @@ -24,6 +24,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { useRegisteredFeatures } from '../../hooks/use_registered_features'; import type { InferenceFeatureResponse as InferenceFeatureConfig } from '../../../common/types'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { EventType } from '../../analytics/constants'; interface CopyToModalProps { sourceFeatureName: string; @@ -43,10 +45,15 @@ export const CopyToModal: React.FC = ({ const modalTitleId = useGeneratedHtmlId(); const { features: registeredFeatures } = useRegisteredFeatures(); const [idToSelectedMap, setIdToSelectedMap] = useState>({}); + const usageTracker = useUsageTracker(); - const handleToggle = useCallback((id: string) => { - setIdToSelectedMap((prev) => ({ ...prev, [id]: !prev[id] })); - }, []); + const handleToggle = useCallback( + (id: string) => { + usageTracker.count(EventType.COPY_TO_FEATURE_TOGGLED); + setIdToSelectedMap((prev) => ({ ...prev, [id]: !prev[id] })); + }, + [usageTracker] + ); const treeItems = useMemo(() => { const parentNameMap = new Map( diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx index 39e926bb329ab..741c979284073 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/default_model_section.tsx @@ -24,7 +24,8 @@ import { NO_DEFAULT_MODEL } from '../../../common/constants'; import { useConnectors } from '../../hooks/use_connectors'; import { useConnectorExists } from '../../hooks/use_connector_exists'; import type { UseDefaultModelSettingsReturn } from '../../hooks/use_default_model_settings'; -import { useEventTracker } from '../../analytics/event_tracker_context'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { EventType } from '../../analytics/constants'; interface Props { defaultModelSettings: UseDefaultModelSettingsReturn; @@ -90,7 +91,7 @@ export const DefaultModelSection: React.FC = ({ defaultModelSettings }) = const { exists: connectorExists, loading: connectorExistsLoading } = useConnectorExists( state.defaultModelId ); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const options = useMemo(() => getOptions(connectors), [connectors]); const selectedOptions = useMemo( @@ -126,7 +127,7 @@ export const DefaultModelSection: React.FC = ({ defaultModelSettings }) = const onChangeDefaultModel = (selected: EuiComboBoxOptionOption[]) => { const value = selected[0]?.value ?? NO_DEFAULT_MODEL; - eventTracker.defaultModelChanged(); + usageTracker.count(EventType.DEFAULT_MODEL_CHANGED); setDefaultModelId(value); }; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx index a20f355da1905..563f0c985c582 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/settings/model_settings.tsx @@ -28,7 +28,8 @@ import { useModelSettingsForm } from './use_model_settings_form'; import { useDefaultModelSettings } from '../../hooks/use_default_model_settings'; import { useConnectors } from '../../hooks/use_connectors'; import { useKibana } from '../../hooks/use_kibana'; -import { useEventTracker } from '../../analytics/event_tracker_context'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { EventType } from '../../analytics/constants'; export const ModelSettings: React.FC = () => { const { @@ -79,7 +80,7 @@ export const ModelSettings: React.FC = () => { }; }, [isDirty, history]); - const eventTracker = useEventTracker(); + const usageTracker = useUsageTracker(); const handleSave = useCallback(async () => { if (isFeatureDirty) { @@ -88,8 +89,8 @@ export const ModelSettings: React.FC = () => { if (defaultModelSettings.isDirty) { await defaultModelSettings.save(); } - eventTracker.featureSettingsSaved(); - }, [isFeatureDirty, saveFeatures, defaultModelSettings, eventTracker]); + usageTracker.count(EventType.FEATURE_SETTINGS_SAVED); + }, [isFeatureDirty, saveFeatures, defaultModelSettings, usageTracker]); const handleDiscardAndLeave = useCallback(() => { defaultModelSettings.reset(); diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/contexts/usage_tracker_context.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/contexts/usage_tracker_context.tsx new file mode 100644 index 0000000000000..39aac211159b0 --- /dev/null +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/contexts/usage_tracker_context.tsx @@ -0,0 +1,40 @@ +/* + * 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, { createContext, useContext, useMemo } from 'react'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; + +import { createUsageTracker, createEmptyUsageTracker } from '../usage_tracker'; +import type { AppUsageTracker } from '../types'; + +const UsageTrackerContext = createContext(createEmptyUsageTracker()); + +export interface UsageTrackerContextProviderProps { + children: React.ReactNode | React.ReactNode[]; + usageCollection?: UsageCollectionSetup | UsageCollectionStart; +} + +export const UsageTrackerContextProvider = ({ + children, + usageCollection, +}: UsageTrackerContextProviderProps) => { + const usageTracker = useMemo(() => createUsageTracker(usageCollection), [usageCollection]); + return ( + {children} + ); +}; + +export const useUsageTracker = () => { + const ctx = useContext(UsageTrackerContext); + if (!ctx) { + throw new Error('UsageTrackerContext should be used inside of the UsageTrackerContextProvider'); + } + return ctx; +}; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx index 18f4477168899..72deb3fc97de6 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/elastic_inference_service_application.tsx @@ -14,7 +14,7 @@ import { Router } from '@kbn/shared-ux-router'; import type { AppPluginStartDependencies } from './types'; import { ElasticInferenceService } from './components/elastic_inference_service'; import { InferenceEndpointsProvider } from './providers/inference_endpoints_provider'; -import { EventTrackerProvider } from './analytics/event_tracker_context'; +import { UsageTrackerContextProvider } from './contexts/usage_tracker_context'; export const renderElasticInferenceServiceApp = async ( core: CoreStart, @@ -26,11 +26,11 @@ export const renderElasticInferenceServiceApp = async ( - + - + diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts index 4db511b244790..52a8035500c22 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/plugin.ts @@ -24,7 +24,6 @@ import type { SearchInferenceEndpointsPluginStart, } from './types'; import { registerLocators } from './locators'; -import { registerSearchInferenceEndpointsEventTypes } from './analytics/register_event_types'; export class SearchInferenceEndpointsPlugin implements Plugin @@ -44,7 +43,6 @@ export class SearchInferenceEndpointsPlugin ): SearchInferenceEndpointsPluginSetup { if (!this.config.ui?.enabled) return {}; - registerSearchInferenceEndpointsEventTypes(core.analytics); registerLocators(plugins.share); this.registerInferenceEndpoints = diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/types.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/types.ts index 9849b6b657de5..558d9f7883b22 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/types.ts +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/types.ts @@ -16,6 +16,10 @@ import type { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/publi import type { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; import type { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugin/public'; import type { CloudConnectedPluginStart } from '@kbn/cloud-connect-plugin/public'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; import type { ServiceProviderKeys } from '@kbn/inference-endpoint-ui-common'; import type { InferenceTaskType } from '@elastic/elasticsearch/lib/api/types'; @@ -34,6 +38,7 @@ export interface AppPluginStartDependencies { serverless?: ServerlessPluginStart; cloud?: CloudStart; cloudConnect?: CloudConnectedPluginStart; + usageCollection?: UsageCollectionStart; } export interface AppPluginSetupDependencies { @@ -45,6 +50,13 @@ export interface AppPluginSetupDependencies { management: ManagementSetup; share: SharePluginSetup; serverless?: ServerlessPluginSetup; + usageCollection?: UsageCollectionSetup; +} + +export interface AppUsageTracker { + click: (eventName: string | string[]) => void; + count: (eventName: string | string[]) => void; + load: (eventName: string | string[]) => void; } export type AppServicesContext = CoreStart & AppPluginStartDependencies; diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/usage_tracker.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/usage_tracker.ts new file mode 100644 index 0000000000000..8eb462a7e3508 --- /dev/null +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/usage_tracker.ts @@ -0,0 +1,41 @@ +/* + * 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 { UiCounterMetricType } from '@kbn/analytics'; +import { METRIC_TYPE } from '@kbn/analytics'; +import type { + UsageCollectionSetup, + UsageCollectionStart, +} from '@kbn/usage-collection-plugin/public'; +import type { AppUsageTracker } from './types'; + +const APP_TRACKER_NAME = 'searchInferenceEndpoints'; + +export const createUsageTracker = ( + usageCollection?: UsageCollectionSetup | UsageCollectionStart +): AppUsageTracker => { + const track = (type: UiCounterMetricType, name: string | string[]) => + usageCollection?.reportUiCounter(APP_TRACKER_NAME, type, name); + + return { + click: (eventName: string | string[]) => { + track(METRIC_TYPE.CLICK, eventName); + }, + count: (eventName: string | string[]) => { + track(METRIC_TYPE.COUNT, eventName); + }, + load: (eventName: string | string[]) => { + track(METRIC_TYPE.LOADED, eventName); + }, + }; +}; + +export const createEmptyUsageTracker = (): AppUsageTracker => ({ + click: (_eventName: string | string[]) => {}, + count: (_eventName: string | string[]) => {}, + load: (_eventName: string | string[]) => {}, +}); diff --git a/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/install_command_code_box.tsx b/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/install_command_code_box.tsx index 5af0f7c8fe61d..f6f698b7edcee 100644 --- a/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/install_command_code_box.tsx +++ b/x-pack/solutions/search/plugins/search_getting_started/public/components/connect_code/install_command_code_box.tsx @@ -11,6 +11,8 @@ import { type AvailableLanguages, GettingStartedCodeExample, } from '@kbn/search-code-examples/src/getting-started-tutorials'; +import { useUsageTracker } from '../../contexts/usage_tracker_context'; +import { AnalyticsEvents } from '../../analytics/constants'; interface Props { selectedLanguage: AvailableLanguages; @@ -18,11 +20,22 @@ interface Props { export const InstallCommandCodeBox = ({ selectedLanguage }: Props) => { const installCommand = GettingStartedCodeExample[selectedLanguage].installCommandShell; + const usageTracker = useUsageTracker(); if (!installCommand) return null; return ( - + { + usageTracker.click([ + AnalyticsEvents.codeExampleCopied, + `${AnalyticsEvents.codeExampleCopied}_install_${selectedLanguage}`, + ]); + }} + > {installCommand} ); From 85565b9f9b7d7ff6523a7cf377c0fc42a8013d86 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:33:56 +0000 Subject: [PATCH 5/7] Changes from node scripts/lint_ts_projects --fix --- .../plugins/shared/search_inference_endpoints/tsconfig.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/tsconfig.json b/x-pack/platform/plugins/shared/search_inference_endpoints/tsconfig.json index 11265f9c01b43..9c207a56ecae8 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/tsconfig.json +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/tsconfig.json @@ -47,7 +47,9 @@ "@kbn/inference-plugin", "@kbn/management-settings-ids", "@kbn/scout", - "@kbn/search-api-panels" + "@kbn/search-api-panels", + "@kbn/usage-collection-plugin", + "@kbn/analytics" ], "exclude": [ "target/**/*" From 055ee52f1c104b98743d1009069045a46408289c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 27 Apr 2026 13:45:58 +0000 Subject: [PATCH 6/7] Changes from node scripts/regenerate_moon_projects.js --update --- .../platform/plugins/shared/search_inference_endpoints/moon.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/moon.yml b/x-pack/platform/plugins/shared/search_inference_endpoints/moon.yml index 175b768765d4c..76f784bfba392 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/moon.yml +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/moon.yml @@ -54,6 +54,8 @@ dependsOn: - '@kbn/management-settings-ids' - '@kbn/scout' - '@kbn/search-api-panels' + - '@kbn/usage-collection-plugin' + - '@kbn/analytics' tags: - plugin - prod From 0b16d2ed82bdd535df7ad1ad01ced155b6b35630 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Efe=20G=C3=BCrkan=20YALAMAN?= Date: Tue, 28 Apr 2026 12:10:33 +0200 Subject: [PATCH 7/7] review changes --- .../public/analytics/constants.ts | 26 +++++++++---------- .../external_inference_empty_prompt.tsx | 2 +- .../model_detail_flyout.tsx | 7 ++--- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts index 40282334f203a..c089b17a56b51 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/analytics/constants.ts @@ -6,17 +6,17 @@ */ export enum EventType { - ENDPOINT_CREATED = 'searchInferenceEndpoints_endpoint_created', - ENDPOINT_EDITED = 'searchInferenceEndpoints_endpoint_edited', - DEFAULT_MODEL_CHANGED = 'searchInferenceEndpoints_default_model_changed', - FEATURE_SETTINGS_SAVED = 'searchInferenceEndpoints_feature_settings_saved', - FILTER_APPLIED = 'searchInferenceEndpoints_filter_applied', - GROUP_BY_CHANGED = 'searchInferenceEndpoints_group_by_changed', - EMPTY_STATE_VIEWED = 'searchInferenceEndpoints_empty_state_viewed', - FLYOUT_OPENED = 'searchInferenceEndpoints_flyout_opened', - FLYOUT_CLOSED = 'searchInferenceEndpoints_flyout_closed', - MODAL_OPENED = 'searchInferenceEndpoints_modal_opened', - MODAL_CLOSED = 'searchInferenceEndpoints_modal_closed', - EIS_MODEL_VIEWED = 'searchInferenceEndpoints_eis_model_viewed', - COPY_TO_FEATURE_TOGGLED = 'searchInferenceEndpoints_copy_to_feature_toggled', + ENDPOINT_CREATED = 'endpoint_created', + ENDPOINT_EDITED = 'endpoint_edited', + DEFAULT_MODEL_CHANGED = 'default_model_changed', + FEATURE_SETTINGS_SAVED = 'feature_settings_saved', + FILTER_APPLIED = 'filter_applied', + GROUP_BY_CHANGED = 'group_by_changed', + EMPTY_STATE_VIEWED = 'empty_state_viewed', + FLYOUT_OPENED = 'flyout_opened', + FLYOUT_CLOSED = 'flyout_closed', + MODAL_OPENED = 'modal_opened', + MODAL_CLOSED = 'modal_closed', + EIS_MODEL_VIEWED = 'eis_model_viewed', + COPY_TO_FEATURE_TOGGLED = 'copy_to_feature_toggled', } diff --git a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx index f5fa71ae6c60f..e79b9c5c0dea3 100644 --- a/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx +++ b/x-pack/platform/plugins/shared/search_inference_endpoints/public/components/external_inference_empty_prompt.tsx @@ -23,7 +23,7 @@ export const ExternalInferenceEmptyPrompt: React.FC { const usageTracker = useUsageTracker(); useEffect(() => { - usageTracker.count(EventType.EMPTY_STATE_VIEWED); + usageTracker.load(EventType.EMPTY_STATE_VIEWED); }, [usageTracker]); return ( = ({ const usageTracker = useUsageTracker(); useEffect(() => { - usageTracker.count([EventType.EIS_MODEL_VIEWED, `${EventType.EIS_MODEL_VIEWED}_${modelId}`]); + usageTracker.load([EventType.EIS_MODEL_VIEWED, `${EventType.EIS_MODEL_VIEWED}_${modelId}`]); }, [usageTracker, modelId]); const { endpoints, displayName, modelAuthor } = useMemo(() => { @@ -111,10 +111,11 @@ export const ModelDetailFlyout: React.FC = ({ ); const handleCloseModal = useCallback(() => { - usageTracker.count(EventType.MODAL_CLOSED); + const modalKind = editingEndpoint ? 'edit_endpoint' : 'add_endpoint'; + usageTracker.count([EventType.MODAL_CLOSED, `${EventType.MODAL_CLOSED}_${modalKind}`]); setIsModalOpen(false); setEditingEndpoint(undefined); - }, [usageTracker]); + }, [usageTracker, editingEndpoint]); const descriptionListItems = [ {