diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index e099d878557d4..865368fa16bf0 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
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/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
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..c089b17a56b51
--- /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 = '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/application.tsx b/x-pack/platform/plugins/shared/search_inference_endpoints/public/application.tsx
index f5bae783d9cac..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,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 { UsageTrackerContextProvider } from './contexts/usage_tracker_context';
const renderMgmtApp = (
core: CoreStart,
@@ -24,9 +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 c671048107df5..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,6 +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 { useUsageTracker } from '../../contexts/usage_tracker_context';
+import { EventType } from '../../analytics/constants';
const EXCLUDED_PROVIDERS = [ServiceProviderKeys.elasticsearch, ServiceProviderKeys.elastic];
@@ -29,10 +31,12 @@ export const AddInferenceFlyoutWrapper: React.FC
serverless,
},
} = useKibana();
+ const usageTracker = useUsageTracker();
const onSubmitSuccess = useCallback(() => {
+ usageTracker.count(EventType.ENDPOINT_CREATED);
reloadFn();
- }, [reloadFn]);
+ }, [reloadFn, usageTracker]);
return (
{
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+ const usageTracker = useUsageTracker();
const handleValueChange = useCallback(
(newOptions: EuiSelectableOption[]) => {
const selectedOption = newOptions.find((option) => option.checked === 'on');
- onChange(parseGroupByValue(selectedOption?.key));
+ const parsed = parseGroupByValue(selectedOption?.key);
+ usageTracker.count([EventType.GROUP_BY_CHANGED, `${EventType.GROUP_BY_CHANGED}_${parsed}`]);
+ onChange(parsed);
setIsPopoverOpen(false);
},
- [onChange]
+ [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 9800c27250e08..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,6 +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 { useUsageTracker } from '../../contexts/usage_tracker_context';
+import { EventType } from '../../analytics/constants';
interface EditInterfaceFlyoutProps {
onFlyoutClose: () => void;
@@ -29,9 +31,11 @@ export const EditInferenceFlyout: React.FC = ({
},
} = useKibana();
const { refetch } = useQueryInferenceEndpoints();
+ const usageTracker = useUsageTracker();
const onEditSuccess = useCallback(() => {
+ usageTracker.count(EventType.ENDPOINT_EDITED);
refetch();
- }, [refetch]);
+ }, [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 9b66afe828627..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
@@ -5,12 +5,14 @@
* 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 { useUsageTracker } from '../contexts/usage_tracker_context';
+import { EventType } from '../analytics/constants';
interface ExternalInferenceEmptyPromptProps {
onFlyoutOpen: () => void;
@@ -19,6 +21,10 @@ interface ExternalInferenceEmptyPromptProps {
export const ExternalInferenceEmptyPrompt: React.FC = ({
onFlyoutOpen,
}) => {
+ const usageTracker = useUsageTracker();
+ useEffect(() => {
+ usageTracker.load(EventType.EMPTY_STATE_VIEWED);
+ }, [usageTracker]);
return (
= ({
const euiThemeContext = useEuiTheme();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const toggleIsPopoverOpen = () => setIsPopoverOpen((prevValue) => !prevValue);
+ const usageTracker = useUsageTracker();
const options: MultiSelectFilterOption[] = _.uniqBy(
rawOptions.map(({ key, label }) => ({
label,
@@ -91,7 +94,11 @@ export const MultiSelectFilter: React.FC = ({
emptyMessage={i18n.translate('xpack.searchInferenceEndpoints.filter.emptyMessage', {
defaultMessage: 'No options',
})}
- onChange={onChange}
+ onChange={(newOptions) => {
+ const filter = dataTestSubj ?? 'unknown';
+ usageTracker.count([EventType.FILTER_APPLIED, `${EventType.FILTER_APPLIED}_${filter}`]);
+ 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..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,18 +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 { 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 usageTracker = useUsageTracker();
const onFlyoutOpen = useCallback(() => {
+ usageTracker.count([EventType.FLYOUT_OPENED, `${EventType.FLYOUT_OPENED}_add_inference`]);
setIsAddInferenceFlyoutOpen(true);
- }, []);
+ }, [usageTracker]);
const onFlyoutClose = useCallback(() => {
+ usageTracker.count([EventType.FLYOUT_CLOSED, `${EventType.FLYOUT_CLOSED}_add_inference`]);
setIsAddInferenceFlyoutOpen(false);
- }, []);
+ }, [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 04d24685c4d84..ecfc9452f3ed4 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,8 @@ import {
import { getModelId } from '../../utils/get_model_id';
import { AddEndpointModal } from './add_endpoint_modal';
import { ModelEndpointRow } from './model_endpoint_row';
+import { useUsageTracker } from '../../contexts/usage_tracker_context';
+import { EventType } from '../../analytics/constants';
export interface ModelDetailFlyoutProps {
modelId: string;
@@ -56,6 +58,11 @@ export const ModelDetailFlyout: React.FC = ({
const flyoutTitleId = useGeneratedHtmlId();
const [isModalOpen, setIsModalOpen] = useState(false);
const [editingEndpoint, setEditingEndpoint] = useState();
+ const usageTracker = useUsageTracker();
+
+ useEffect(() => {
+ usageTracker.load([EventType.EIS_MODEL_VIEWED, `${EventType.EIS_MODEL_VIEWED}_${modelId}`]);
+ }, [usageTracker, modelId]);
const { endpoints, displayName, modelAuthor } = useMemo(() => {
const filtered = allEndpoints.filter((ep) => getModelId(ep) === modelId);
@@ -89,19 +96,26 @@ export const ModelDetailFlyout: React.FC = ({
}, [endpoints]);
const handleOpenAddModal = useCallback(() => {
+ usageTracker.count([EventType.MODAL_OPENED, `${EventType.MODAL_OPENED}_add_endpoint`]);
setEditingEndpoint(undefined);
setIsModalOpen(true);
- }, []);
+ }, [usageTracker]);
- const handleOpenEditModal = useCallback((endpoint: InferenceAPIConfigResponse) => {
- setEditingEndpoint(endpoint);
- setIsModalOpen(true);
- }, []);
+ const handleOpenEditModal = useCallback(
+ (endpoint: InferenceAPIConfigResponse) => {
+ usageTracker.count([EventType.MODAL_OPENED, `${EventType.MODAL_OPENED}_edit_endpoint`]);
+ setEditingEndpoint(endpoint);
+ setIsModalOpen(true);
+ },
+ [usageTracker]
+ );
const handleCloseModal = useCallback(() => {
+ const modalKind = editingEndpoint ? 'edit_endpoint' : 'add_endpoint';
+ usageTracker.count([EventType.MODAL_CLOSED, `${EventType.MODAL_CLOSED}_${modalKind}`]);
setIsModalOpen(false);
setEditingEndpoint(undefined);
- }, []);
+ }, [usageTracker, editingEndpoint]);
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 0e18e973a2451..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,6 +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 { useUsageTracker } from '../../contexts/usage_tracker_context';
+import { EventType } from '../../analytics/constants';
interface Props {
defaultModelSettings: UseDefaultModelSettingsReturn;
@@ -89,6 +91,7 @@ export const DefaultModelSection: React.FC = ({ defaultModelSettings }) =
const { exists: connectorExists, loading: connectorExistsLoading } = useConnectorExists(
state.defaultModelId
);
+ const usageTracker = useUsageTracker();
const options = useMemo(() => getOptions(connectors), [connectors]);
const selectedOptions = useMemo(
@@ -124,6 +127,7 @@ export const DefaultModelSection: React.FC = ({ defaultModelSettings }) =
const onChangeDefaultModel = (selected: EuiComboBoxOptionOption[]) => {
const value = selected[0]?.value ?? NO_DEFAULT_MODEL;
+ 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 e6be73a70d381..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,6 +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 { useUsageTracker } from '../../contexts/usage_tracker_context';
+import { EventType } from '../../analytics/constants';
export const ModelSettings: React.FC = () => {
const {
@@ -78,6 +80,8 @@ export const ModelSettings: React.FC = () => {
};
}, [isDirty, history]);
+ const usageTracker = useUsageTracker();
+
const handleSave = useCallback(async () => {
if (isFeatureDirty) {
saveFeatures();
@@ -85,7 +89,8 @@ export const ModelSettings: React.FC = () => {
if (defaultModelSettings.isDirty) {
await defaultModelSettings.save();
}
- }, [isFeatureDirty, saveFeatures, defaultModelSettings]);
+ 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 86460a05748f4..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,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 { UsageTrackerContextProvider } from './contexts/usage_tracker_context';
export const renderElasticInferenceServiceApp = async (
core: CoreStart,
@@ -25,9 +26,11 @@ export const renderElasticInferenceServiceApp = async (
-
-
-
+
+
+
+
+
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/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/**/*"
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..b69b167eac897
--- /dev/null
+++ b/x-pack/solutions/search/plugins/search_getting_started/public/analytics/constants.ts
@@ -0,0 +1,14 @@
+/*
+ * 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',
+}
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 fad7410a02ba3..f9429c062b4c6 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/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}
);
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/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..7e3f10504eba1
--- /dev/null
+++ b/x-pack/solutions/search/plugins/search_homepage/public/analytics/constants.ts
@@ -0,0 +1,11 @@
+/*
+ * 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',
+ 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/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 (