feat(kind): Add telemetry to see what is happening (#1997)

* feat(kind): Add telemetry to see if kind is downloaded or not
Also log usage of create clusters
fixes https://github.com/containers/podman-desktop/issues/1954

Change-Id: I7f7a59360e5ed32edc3a6b4d4593c9771928705e
Signed-off-by: Florent Benoit <fbenoit@redhat.com>
This commit is contained in:
Florent BENOIT 2023-04-11 14:43:47 +02:00 committed by GitHub
parent 806ee91743
commit e52cfbe70f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 16 deletions

View file

@ -20,6 +20,7 @@ import { beforeEach, expect, test, vi } from 'vitest';
import type { Mock } from 'vitest';
import { createCluster, getKindClusterConfig } from './create-cluster';
import { runCliCommand } from './util';
import type { TelemetryLogger } from '@podman-desktop/api';
vi.mock('@podman-desktop/api', async () => {
return {
@ -37,19 +38,33 @@ beforeEach(() => {
vi.clearAllMocks();
});
const telemetryLogUsageMock = vi.fn();
const telemetryLogErrorMock = vi.fn();
const telemetryLoggerMock = {
logUsage: telemetryLogUsageMock,
logError: telemetryLogErrorMock,
} as unknown as TelemetryLogger;
test('expect error is cli returns non zero exit code', async () => {
try {
(runCliCommand as Mock).mockReturnValue({ exitCode: -1, error: 'error' });
await createCluster({}, undefined, '');
await createCluster({}, undefined, '', undefined, telemetryLoggerMock);
} catch (err) {
expect(err).to.be.a('Error');
expect(err.message).equal('Failed to create kind cluster. error');
expect(telemetryLogErrorMock).toBeCalledWith('createCluster', expect.objectContaining({ error: 'error' }));
}
});
test('expect cluster to be created', async () => {
(runCliCommand as Mock).mockReturnValue({ exitCode: 0 });
await createCluster({}, undefined, '');
await createCluster({}, undefined, '', undefined, telemetryLoggerMock);
expect(telemetryLogUsageMock).toHaveBeenNthCalledWith(
1,
'createCluster',
expect.objectContaining({ provider: 'docker' }),
);
expect(telemetryLogErrorMock).not.toBeCalled();
});
test('check cluster configuration generation', async () => {

View file

@ -39,6 +39,7 @@ export async function createCluster(
logger: extensionApi.Logger,
kindCli: string,
token?: CancellationToken,
telemetryLogger: extensionApi.TelemetryLogger,
): Promise<void> {
let clusterName = 'kind';
if (params['kind.cluster.creation.name']) {
@ -88,6 +89,15 @@ export async function createCluster(
await fs.promises.rm(tmpDirectory, { recursive: true });
if (result.exitCode !== 0) {
telemetryLogger.logError('createCluster', {
provider,
httpHostPort,
httpsHostPort,
error: result.error,
stdErr: result.stdErr,
});
throw new Error(`Failed to create kind cluster. ${result.error}`);
} else {
telemetryLogger.logUsage('createCluster', { provider, httpHostPort, httpsHostPort });
}
}

View file

@ -47,11 +47,15 @@ let kindCli: string | undefined;
const imageHandler = new ImageHandler();
function registerProvider(extensionContext: extensionApi.ExtensionContext, provider: extensionApi.Provider): void {
function registerProvider(
extensionContext: extensionApi.ExtensionContext,
provider: extensionApi.Provider,
telemetryLogger: extensionApi.TelemetryLogger,
): void {
const disposable = provider.setKubernetesProviderConnectionFactory({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
create: (params: { [key: string]: any }, logger?: Logger, token?: CancellationToken) =>
createCluster(params, logger, kindCli, token),
createCluster(params, logger, kindCli, token, telemetryLogger),
creationDisplayName: 'Kind cluster',
});
extensionContext.subscriptions.push(disposable);
@ -123,11 +127,13 @@ async function searchKindClusters(provider: extensionApi.Provider) {
const kindContainers = allContainers.filter(container => {
return container.Labels?.['io.x-k8s.kind.cluster'];
});
updateClusters(provider, kindContainers);
}
function createProvider(extensionContext: extensionApi.ExtensionContext) {
function createProvider(
extensionContext: extensionApi.ExtensionContext,
telemetryLogger: extensionApi.TelemetryLogger,
) {
const provider = extensionApi.provider.createProvider({
name: 'Kind',
id: 'kind',
@ -141,11 +147,12 @@ function createProvider(extensionContext: extensionApi.ExtensionContext) {
},
});
extensionContext.subscriptions.push(provider);
registerProvider(extensionContext, provider);
registerProvider(extensionContext, provider, telemetryLogger);
extensionContext.subscriptions.push(
extensionApi.commands.registerCommand(KIND_MOVE_IMAGE_COMMAND, image =>
imageHandler.moveImage(image, kindClusters, kindCli),
),
extensionApi.commands.registerCommand(KIND_MOVE_IMAGE_COMMAND, image => {
telemetryLogger.logUsage('moveImage');
imageHandler.moveImage(image, kindClusters, kindCli);
}),
);
// when containers are refreshed, update
@ -163,11 +170,12 @@ function createProvider(extensionContext: extensionApi.ExtensionContext) {
extensionApi.provider.onDidUnregisterContainerConnection(() => {
searchKindClusters(provider);
});
extensionApi.provider.onDidUpdateProvider(() => registerProvider(extensionContext, provider));
extensionApi.provider.onDidUpdateProvider(() => registerProvider(extensionContext, provider, telemetryLogger));
}
export async function activate(extensionContext: extensionApi.ExtensionContext): Promise<void> {
const installer = new KindInstaller(extensionContext.storagePath);
const telemetryLogger = extensionApi.env.createTelemetryLogger();
const installer = new KindInstaller(extensionContext.storagePath, telemetryLogger);
kindCli = await detectKind(extensionContext.storagePath, installer);
if (!kindCli) {
@ -184,7 +192,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
if (status) {
statusBarItem.dispose();
kindCli = await detectKind(extensionContext.storagePath, installer);
createProvider(extensionContext);
createProvider(extensionContext, telemetryLogger);
}
},
err => window.showErrorMessage('Kind installation failed ' + err),
@ -194,7 +202,7 @@ export async function activate(extensionContext: extensionApi.ExtensionContext):
statusBarItem.show();
}
} else {
createProvider(extensionContext);
createProvider(extensionContext, telemetryLogger);
}
}

View file

@ -44,8 +44,15 @@ vi.mock('@octokit/rest', () => {
};
});
const telemetryLogUsageMock = vi.fn();
const telemetryLogErrorMock = vi.fn();
const telemetryLoggerMock = {
logUsage: telemetryLogUsageMock,
logError: telemetryLogErrorMock,
} as unknown as extensionApi.TelemetryLogger;
beforeEach(() => {
installer = new KindInstaller('.');
installer = new KindInstaller('.', telemetryLoggerMock);
vi.clearAllMocks();
});
@ -66,6 +73,11 @@ test('expect showNotification to be called', async () => {
};
});
const result = await installer.performInstall();
expect(telemetryLogErrorMock).not.toBeCalled();
expect(telemetryLogUsageMock).toHaveBeenNthCalledWith(1, 'install-kind-prompt');
expect(telemetryLogUsageMock).toHaveBeenNthCalledWith(2, 'install-kind-prompt-yes');
expect(telemetryLogUsageMock).toHaveBeenNthCalledWith(3, 'install-kind-downloaded');
expect(result).toBeDefined();
expect(result).toBeTruthy();
expect(spy).toBeCalled();

View file

@ -59,7 +59,7 @@ export class KindInstaller {
private assetPromise: Promise<AssetInfo>;
constructor(private readonly storagePath: string) {
constructor(private readonly storagePath: string, private telemetryLogger: extensionApi.TelemetryLogger) {
this.assetNames.set(WINDOWS_X64_PLATFORM, WINDOWS_X64_ASSET_NAME);
this.assetNames.set(LINUX_X64_PLATFORM, LINUX_X64_ASSET_NAME);
this.assetNames.set(LINUX_ARM64_PLATFORM, LINUX_ARM64_ASSET_NAME);
@ -102,12 +102,14 @@ export class KindInstaller {
}
async performInstall(): Promise<boolean> {
this.telemetryLogger.logUsage('install-kind-prompt');
const dialogResult = await extensionApi.window.showInformationMessage(
'kind is not installed on this system, would you like to install Kind ?',
'Yes',
'No',
);
if (dialogResult === 'Yes') {
this.telemetryLogger.logUsage('install-kind-prompt-yes');
return extensionApi.window.withProgress({ location: ProgressLocation.APP_ICON }, async progress => {
progress.report({ increment: 5 });
try {
@ -133,6 +135,7 @@ export class KindInstaller {
const stat = fs.statSync(destFile);
fs.chmodSync(destFile, stat.mode | fs.constants.S_IXUSR);
}
this.telemetryLogger.logUsage('install-kind-downloaded');
extensionApi.window.showNotification({ body: 'Kind is successfully installed.' });
return true;
}
@ -141,6 +144,8 @@ export class KindInstaller {
progress.report({ increment: -1 });
}
});
} else {
this.telemetryLogger.logUsage('install-kind-prompt-no');
}
return false;
}