Skip to content

Commit

Permalink
feat: add telemetry to pull, goto build page (#288)
Browse files Browse the repository at this point in the history
* 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 <git@tdeboer.ca>

* 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 <git@tdeboer.ca>

---------

Signed-off-by: Tim deBoer <git@tdeboer.ca>
  • Loading branch information
deboer-tim committed Apr 5, 2024
1 parent 5342f0f commit 1656636
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 6 deletions.
11 changes: 11 additions & 0 deletions packages/backend/src/api-impl.ts
Expand Up @@ -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;
Expand Down Expand Up @@ -145,6 +146,16 @@ export class BootcApiImpl implements BootcApi {
}
}

// Log an event to telemetry
async telemetryLogUsage(eventName: string, data?: Record<string, unknown>): Promise<void> {
telemetryLogger.logUsage(eventName, data);
}

// Log an error to telemetry
async telemetryLogError(eventName: string, data?: Record<string, unknown>): Promise<void> {
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
Expand Down
6 changes: 2 additions & 4 deletions packages/backend/src/build-disk-image.ts
Expand Up @@ -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<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions packages/backend/src/container-utils.spec.ts
Expand Up @@ -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 {
Expand All @@ -48,6 +52,11 @@ vi.mock('@podman-desktop/api', async () => {
},
]),
},
env: {
createTelemetryLogger: () => ({
logUsage: mocks.logUsageMock,
}),
},
};
});

Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions packages/backend/src/container-utils.ts
Expand Up @@ -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<extensionApi.ContainerProviderConnection> {
Expand Down Expand Up @@ -46,13 +47,20 @@ export async function getContainerEngine(): Promise<extensionApi.ContainerProvid

// Pull the image
export async function pullImage(image: string) {
const telemetryData: Record<string, unknown> = {};
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);
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/backend/src/extension.ts
Expand Up @@ -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<void> {
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)) {
Expand Down
22 changes: 22 additions & 0 deletions packages/frontend/src/Homepage.spec.ts
Expand Up @@ -49,6 +49,7 @@ vi.mock('./api/client', async () => {
listHistoryInfo: vi.fn(),
listBootcImages: vi.fn(),
deleteBuilds: vi.fn(),
telemetryLogUsage: vi.fn(),
},
rpcBrowser: {
subscribe: () => {
Expand Down Expand Up @@ -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();
});
1 change: 1 addition & 0 deletions packages/frontend/src/Homepage.svelte
Expand Up @@ -51,6 +51,7 @@ async function deleteSelectedBuilds() {
}
async function gotoBuild(): Promise<void> {
bootcClient.telemetryLogUsage('nav-build');
router.goto('/build');
}
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/src/BootcAPI.ts
Expand Up @@ -29,4 +29,6 @@ export abstract class BootcApi {
abstract openFolder(folder: string): Promise<boolean>;
abstract generateUniqueBuildID(name: string): Promise<string>;
abstract openLink(link: string): Promise<void>;
abstract telemetryLogUsage(eventName: string, data?: Record<string, unknown> | undefined): Promise<void>;
abstract telemetryLogError(eventName: string, data?: Record<string, unknown> | undefined): Promise<void>;
}

0 comments on commit 1656636

Please sign in to comment.