mirror of
https://github.com/podman-desktop/podman-desktop
synced 2026-05-24 02:08:24 +00:00
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:
parent
806ee91743
commit
e52cfbe70f
5 changed files with 66 additions and 16 deletions
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue