From 1656636aa05a827622582b3f527a7ada8d506231 Mon Sep 17 00:00:00 2001 From: Tim deBoer Date: Fri, 5 Apr 2024 13:30:51 -0400 Subject: [PATCH] feat: add telemetry to pull, goto build page (#288) * feat: add telemetry to pull, goto build page Exposed the telemetry logger from the extension, and changed build-disk-image.ts to use it instead of its own. While doing this I noticed that logUsage was being called before and after the build, so I removed the first to avoid double-counting. Adds telemetry event when pulling images. The intent here is to show how often users pull the example but will also trigger when pulling the image builder itself, which might also be useful. Exposes telemetry logging to frontend, and sends an event when someone clicks on the button to go to the Build page. Part of #179. Signed-off-by: Tim deBoer * chore: telemetryLogUsage and logError Responding to PR review: rename API to telemetryLogUsage so that it's clear, and expose logError() while we're at it. Signed-off-by: Tim deBoer --------- Signed-off-by: Tim deBoer --- packages/backend/src/api-impl.ts | 11 ++++++++++ packages/backend/src/build-disk-image.ts | 6 ++---- packages/backend/src/container-utils.spec.ts | 11 ++++++++++ packages/backend/src/container-utils.ts | 8 +++++++ packages/backend/src/extension.ts | 4 ++-- packages/frontend/src/Homepage.spec.ts | 22 ++++++++++++++++++++ packages/frontend/src/Homepage.svelte | 1 + packages/shared/src/BootcAPI.ts | 2 ++ 8 files changed, 59 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/api-impl.ts b/packages/backend/src/api-impl.ts index 23309aab..5500445c 100644 --- a/packages/backend/src/api-impl.ts +++ b/packages/backend/src/api-impl.ts @@ -24,6 +24,7 @@ import { buildDiskImage } from './build-disk-image'; import { History } from './history'; import * as containerUtils from './container-utils'; import { Messages } from '/@shared/src/messages/Messages'; +import { telemetryLogger } from './extension'; export class BootcApiImpl implements BootcApi { private history: History; @@ -145,6 +146,16 @@ export class BootcApiImpl implements BootcApi { } } + // Log an event to telemetry + async telemetryLogUsage(eventName: string, data?: Record): Promise { + telemetryLogger.logUsage(eventName, data); + } + + // Log an error to telemetry + async telemetryLogError(eventName: string, data?: Record): Promise { + telemetryLogger.logError(eventName, data); + } + // The API does not allow callbacks through the RPC, so instead // we send "notify" messages to the frontend to trigger a refresh // this method is internal and meant to be used by the API implementation diff --git a/packages/backend/src/build-disk-image.ts b/packages/backend/src/build-disk-image.ts index 7b610ae8..71eb1116 100644 --- a/packages/backend/src/build-disk-image.ts +++ b/packages/backend/src/build-disk-image.ts @@ -25,8 +25,7 @@ import { bootcImageBuilderContainerName, bootcImageBuilderName } from './constan import type { BootcBuildInfo, BuildType } from '/@shared/src/models/bootc'; import type { History } from './history'; import * as machineUtils from './machine-utils'; - -const telemetryLogger = extensionApi.env.createTelemetryLogger(); +import { telemetryLogger } from './extension'; export async function buildDiskImage(build: BootcBuildInfo, history: History): Promise { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -101,9 +100,8 @@ export async function buildDiskImage(build: BootcBuildInfo, history: History): P build.status = 'creating'; await history.addOrUpdateBuildInfo(build); - // After resolving all the information, adding it to the history, finally telemetry the data. + // Store the build information for telemetry telemetryData.build = build; - telemetryLogger.logUsage('buildDiskImage', telemetryData); // "Returning" withProgress allows PD to handle the task in the background with building. return extensionApi.window diff --git a/packages/backend/src/container-utils.spec.ts b/packages/backend/src/container-utils.spec.ts index e17932f0..fc7d91a2 100644 --- a/packages/backend/src/container-utils.spec.ts +++ b/packages/backend/src/container-utils.spec.ts @@ -28,6 +28,10 @@ import { deleteOldImages, } from './container-utils'; +const mocks = vi.hoisted(() => ({ + logUsageMock: vi.fn(), +})); + // Mocks and utilities vi.mock('@podman-desktop/api', async () => { return { @@ -48,6 +52,11 @@ vi.mock('@podman-desktop/api', async () => { }, ]), }, + env: { + createTelemetryLogger: () => ({ + logUsage: mocks.logUsageMock, + }), + }, }; }); @@ -63,7 +72,9 @@ test('getContainerEngine should return a running podman engine', async () => { test('pullImage should call pullImage from containerEngine', async () => { await pullImage('someImage'); + expect(extensionApi.containerEngine.pullImage).toBeCalled(); + expect(mocks.logUsageMock).toHaveBeenCalled(); }); // Test createAndStartContainer diff --git a/packages/backend/src/container-utils.ts b/packages/backend/src/container-utils.ts index 01b0fa76..3fd3379f 100644 --- a/packages/backend/src/container-utils.ts +++ b/packages/backend/src/container-utils.ts @@ -17,6 +17,7 @@ ***********************************************************************/ import type { ContainerCreateOptions } from '@podman-desktop/api'; import * as extensionApi from '@podman-desktop/api'; +import { telemetryLogger } from './extension'; // Get the running container engine export async function getContainerEngine(): Promise { @@ -46,13 +47,20 @@ export async function getContainerEngine(): Promise = {}; + telemetryData.image = image; + console.log('Pulling image: ', image); try { const containerConnection = await getContainerEngine(); await extensionApi.containerEngine.pullImage(containerConnection, image, () => {}); + telemetryData.success = true; } catch (e) { console.error(e); + telemetryData.error = e; throw new Error('There was an error pulling the image: ' + e); + } finally { + telemetryLogger.logUsage('pullImage', telemetryData); } } diff --git a/packages/backend/src/extension.ts b/packages/backend/src/extension.ts index ab877bd2..526c84cd 100644 --- a/packages/backend/src/extension.ts +++ b/packages/backend/src/extension.ts @@ -27,11 +27,11 @@ import { Messages } from '/@shared/src/messages/Messages'; import { satisfies, minVersion, coerce } from 'semver'; import { engines } from '../package.json'; +export const telemetryLogger = extensionApi.env.createTelemetryLogger(); + export async function activate(extensionContext: ExtensionContext): Promise { console.log('starting bootc extension'); - const telemetryLogger = extensionApi.env.createTelemetryLogger(); - // Ensure version is above the minimum Podman Desktop version required const version = extensionApi.version ?? 'unknown'; if (!checkVersion(version)) { diff --git a/packages/frontend/src/Homepage.spec.ts b/packages/frontend/src/Homepage.spec.ts index 04a45527..a8e1f308 100644 --- a/packages/frontend/src/Homepage.spec.ts +++ b/packages/frontend/src/Homepage.spec.ts @@ -49,6 +49,7 @@ vi.mock('./api/client', async () => { listHistoryInfo: vi.fn(), listBootcImages: vi.fn(), deleteBuilds: vi.fn(), + telemetryLogUsage: vi.fn(), }, rpcBrowser: { subscribe: () => { @@ -121,3 +122,24 @@ test('Test clicking on delete button', async () => { expect(spyOnDelete).toHaveBeenCalled(); }); + +test('Test clicking on build button', async () => { + vi.mocked(bootcClient.listHistoryInfo).mockResolvedValue(mockHistoryInfo); + + await waitRender(Homepage); + + // Wait until header 'Welcome to Bootable Containers' is removed + // as that means it's fully loaded + while (screen.queryByText('Welcome to Bootable Containers')) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + + // spy on telemetryLogUsage function + const spyOnLogUsage = vi.spyOn(bootcClient, 'telemetryLogUsage'); + + // Click on build button + const buildButton = screen.getAllByRole('button', { name: 'Build' })[0]; + buildButton.click(); + + expect(spyOnLogUsage).toHaveBeenCalled(); +}); diff --git a/packages/frontend/src/Homepage.svelte b/packages/frontend/src/Homepage.svelte index fcf263a5..9014d705 100644 --- a/packages/frontend/src/Homepage.svelte +++ b/packages/frontend/src/Homepage.svelte @@ -51,6 +51,7 @@ async function deleteSelectedBuilds() { } async function gotoBuild(): Promise { + bootcClient.telemetryLogUsage('nav-build'); router.goto('/build'); } diff --git a/packages/shared/src/BootcAPI.ts b/packages/shared/src/BootcAPI.ts index 9983304e..b6eb5724 100644 --- a/packages/shared/src/BootcAPI.ts +++ b/packages/shared/src/BootcAPI.ts @@ -29,4 +29,6 @@ export abstract class BootcApi { abstract openFolder(folder: string): Promise; abstract generateUniqueBuildID(name: string): Promise; abstract openLink(link: string): Promise; + abstract telemetryLogUsage(eventName: string, data?: Record | undefined): Promise; + abstract telemetryLogError(eventName: string, data?: Record | undefined): Promise; }