Skip to content

Commit

Permalink
[Observability Onboarding] Add new telemetry hook for auto-detect flow (
Browse files Browse the repository at this point in the history
#191028)

## Summary

Resolves elastic/observability-dev#3856.

Adds a new field to the onboarding telemetry event type and ships two
new events, one for awaiting data and the other for receiving data. The
new field is a string array that will indicate the integration package
names the user has chosen.

~NOTE: I was unsure how much detail to include about the integrations in
question. Right now, the event would ship `integration.title`, but
perhaps we want to re-define the new field to include all the
integration fields and have it contain that?~

There's information in the [comment
below](#191028 (comment))
showing the types of data we will collect with these changes.
  • Loading branch information
justinkambic authored Sep 5, 2024
1 parent fbe2da0 commit 1ad8851
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,24 @@

import { type EventTypeOpts } from '@elastic/ebt/client';

export const OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT: EventTypeOpts<{
interface ObservabilityOnboardingIntegrationTelemetryFields {
installSource: string;
pkgName: string;
pkgVersion: string;
title: string;
}

interface FlowEventFields {
flow?: string;
step?: string;
step_status?: string;
step_message?: string;
uses_legacy_onboarding_page: boolean;
}> = {
}

type ObservabilityOnboardingTelemetryEvent = EventTypeOpts<FlowEventFields>;

export const OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT: ObservabilityOnboardingTelemetryEvent = {
eventType: 'observability_onboarding',
schema: {
flow: {
Expand Down Expand Up @@ -75,3 +86,45 @@ export const OBSERVABILITY_ONBOARDING_FEEDBACK_TELEMETRY_EVENT: EventTypeOpts<{
},
},
};

type ObservabilityOnboardingAutodetectTelemetryEvent = EventTypeOpts<
FlowEventFields & {
integrations?: ObservabilityOnboardingIntegrationTelemetryFields[];
}
>;

export const OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT: ObservabilityOnboardingAutodetectTelemetryEvent =
{
eventType: 'observability_onboarding_autodetect',
schema: {
...OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT.schema,
integrations: {
type: 'array',
items: {
properties: {
installSource: {
type: 'keyword',
_meta: {
description:
'The source of the package used to create the integration. Usually "registry" or "custom".',
},
},
pkgName: {
type: 'keyword',
_meta: {
description: 'The name of the package used to create the integration.',
},
},
pkgVersion: {
type: 'keyword',
_meta: { description: 'The version of the package used to create the integration.' },
},
title: { type: 'keyword', _meta: { description: 'The visual name of the package.' } },
},
},
_meta: {
optional: true,
},
},
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,23 @@ import { LocatorButtonEmpty } from '../shared/locator_button_empty';
import { GetStartedPanel } from '../shared/get_started_panel';
import { isSupportedLogo, LogoIcon } from '../../shared/logo_icon';
import { FeedbackButtons } from '../shared/feedback_buttons';
import { useAutoDetectTelemetry } from './use_auto_detect_telemetry';

export const AutoDetectPanel: FunctionComponent = () => {
const { status, data, error, refetch, installedIntegrations } = useOnboardingFlow();
const command = data ? getAutoDetectCommand(data) : undefined;
const accordionId = useGeneratedHtmlId({ prefix: 'accordion' });

useAutoDetectTelemetry(
status,
installedIntegrations.map(({ title, pkgName, pkgVersion, installSource }) => ({
title,
pkgName,
pkgVersion,
installSource,
}))
);

if (error) {
return <EmptyPrompt error={error} onRetryClick={refetch} />;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { renderHook } from '@testing-library/react-hooks';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useAutoDetectTelemetry } from './use_auto_detect_telemetry';
import { ObservabilityOnboardingFlowStatus } from './get_onboarding_status';
import { OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT } from '../../../../common/telemetry_events';

jest.mock('@kbn/kibana-react-plugin/public', () => ({
useKibana: jest.fn(),
}));

describe('useAutoDetectTelemetry', () => {
let reportEventMock: any;

beforeEach(() => {
reportEventMock = jest.fn();
(useKibana as jest.Mock).mockReturnValue({
services: {
analytics: {
reportEvent: reportEventMock,
},
},
});
});

afterEach(() => {
jest.clearAllMocks();
});

it(`should report "awaiting_data" event when status is "awaitingData"`, () => {
const expectedIntegration = {
installSource: 'source1',
pkgName: 'pkgName1',
pkgVersion: 'pkgVersion1',
title: 'title',
};
renderHook(() => useAutoDetectTelemetry('awaitingData', [expectedIntegration]));

expect(reportEventMock).toHaveBeenCalledWith(
OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT.eventType,
{
uses_legacy_onboarding_page: false,
flow: 'auto_detect',
step: 'awaiting_data',
integrations: [expectedIntegration],
}
);
});

it(`should report "data_shipped" event when status is "dataReceived"`, () => {
const expectedIntegration = {
installSource: 'source2',
pkgName: 'pkgName2',
pkgVersion: 'pkgVersion2',
title: 'title2',
};
renderHook(() => useAutoDetectTelemetry('dataReceived', [expectedIntegration]));

// The effect runs after initial render
expect(reportEventMock).toHaveBeenCalledWith(
OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT.eventType,
{
uses_legacy_onboarding_page: false,
flow: 'auto_detect',
step: 'data_shipped',
integrations: [expectedIntegration],
}
);
});

it('should not report the same event more than once', () => {
const expectedIntegration = {
installSource: 'source1',
pkgName: 'pkgName1',
pkgVersion: 'pkgVersion1',
title: 'title',
};
const { rerender } = renderHook(
({ status }: { status: ObservabilityOnboardingFlowStatus }) =>
useAutoDetectTelemetry(status, [expectedIntegration]),
{ initialProps: { status: 'awaitingData' } }
);

expect(reportEventMock).toHaveBeenCalledWith(
OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT.eventType,
{
uses_legacy_onboarding_page: false,
flow: 'auto_detect',
step: 'awaiting_data',
integrations: [expectedIntegration],
}
);

rerender({ status: 'awaitingData' });
expect(reportEventMock).toHaveBeenCalledTimes(1);

rerender({ status: 'dataReceived' });
expect(reportEventMock).toHaveBeenCalledWith(
OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT.eventType,
{
uses_legacy_onboarding_page: false,
flow: 'auto_detect',
step: 'data_shipped',
integrations: [expectedIntegration],
}
);
expect(reportEventMock).toHaveBeenCalledTimes(2);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useEffect, useState } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { ObservabilityOnboardingFlowStatus } from './get_onboarding_status';
import { OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT } from '../../../../common/telemetry_events';

interface IntegrationFields {
installSource: string;
pkgName: string;
pkgVersion: string;
title: string;
}

export function useAutoDetectTelemetry(
status: ObservabilityOnboardingFlowStatus,
integrations: IntegrationFields[]
) {
const [waitingMessageSent, setWaitingMessageSent] = useState(false);
const [dataShippedMessageSent, setDataShippedMessageSent] = useState(false);
const {
services: { analytics },
} = useKibana();

useEffect(() => {
if (status === 'awaitingData' && !waitingMessageSent) {
analytics?.reportEvent(OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT.eventType, {
uses_legacy_onboarding_page: false,
flow: 'auto_detect',
step: 'awaiting_data',
integrations,
});
setWaitingMessageSent(true);
}
if (status === 'dataReceived' && !dataShippedMessageSent) {
analytics?.reportEvent(OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT.eventType, {
uses_legacy_onboarding_page: false,
flow: 'auto_detect',
step: 'data_shipped',
integrations,
});
setDataShippedMessageSent(true);
}
}, [analytics, dataShippedMessageSent, integrations, status, waitingMessageSent]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { ConfigSchema } from '.';
import {
OBSERVABILITY_ONBOARDING_FEEDBACK_TELEMETRY_EVENT,
OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT,
OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT,
} from '../common/telemetry_events';

export type ObservabilityOnboardingPluginSetup = void;
Expand Down Expand Up @@ -126,6 +127,7 @@ export class ObservabilityOnboardingPlugin

core.analytics.registerEventType(OBSERVABILITY_ONBOARDING_TELEMETRY_EVENT);
core.analytics.registerEventType(OBSERVABILITY_ONBOARDING_FEEDBACK_TELEMETRY_EVENT);
core.analytics.registerEventType(OBSERVABILITY_ONBOARDING_AUTODETECT_TELEMETRY_EVENT);

return {
locators: this.locators,
Expand Down

0 comments on commit 1ad8851

Please sign in to comment.