Skip to content

Commit

Permalink
utils: Add telemetry about target azure resource and subscription IDs (
Browse files Browse the repository at this point in the history
…#1906)

* Remove linux from stage

* Add subId and resourceId to telemetry

* Bump version

* Update utils/src/callWithTelemetryAndErrorHandling.ts

Co-authored-by: Brandon Waterloo [MSFT] <[email protected]>

* Update utils/src/callWithTelemetryAndErrorHandling.ts

Co-authored-by: Brandon Waterloo [MSFT] <[email protected]>

---------

Co-authored-by: Brandon Waterloo [MSFT] <[email protected]>
  • Loading branch information
nturinski and bwateratmsft authored Feb 28, 2025
1 parent 3baef8e commit 03fbc55
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 11 deletions.
12 changes: 12 additions & 0 deletions utils/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,16 @@ export interface IActionContext {
* IMPORTANT: For the most sensitive information, `callWithMaskHandling` should be used instead
*/
valuesToMask: string[];

/**
* If applicable, it is the id of the resource that is being acted upon. Actions that are not against Azure resources will leave this undefined.
*/
resourceId?: string;

/**
* If applicable, it is the id of the subscription of the resource that is being acted upon. Actions that are not against Azure resources will leave this undefined.
*/
subscriptionId?: string;
}

export interface ITelemetryContext {
Expand Down Expand Up @@ -1948,6 +1958,8 @@ export declare interface PickSubscriptionWizardContext extends QuickPickWizardCo
export declare interface AzureResourceQuickPickWizardContext extends QuickPickWizardContext, PickSubscriptionWizardContext {
resource?: AzureResource;
resourceGroup?: string;
resourceId?: string;
subscriptionId?: string;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions utils/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion utils/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@microsoft/vscode-azext-utils",
"author": "Microsoft Corporation",
"version": "2.5.14",
"version": "2.6.0",
"description": "Common UI tools for developing Azure extensions for VS Code",
"tags": [
"azure",
Expand Down
6 changes: 6 additions & 0 deletions utils/src/callWithTelemetryAndErrorHandling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ function handleError(context: types.IActionContext, callbackId: string, error: u
const errorData: types.IParsedError = parseError(errorContext.error);
const unMaskedMessage: string = errorData.message;
errorData.message = maskUserInfo(errorData.message, context.valuesToMask);
context.telemetry.properties.azureSubscriptionId = context.subscriptionId;
context.telemetry.properties.azureResourceId = context.resourceId;

if (errorData.stepName) {
context.telemetry.properties.lastStep = errorData.stepName;
}
Expand Down Expand Up @@ -224,6 +227,9 @@ function handleTelemetry(context: types.IActionContext, callbackId: string, star
if (shouldSendTelemtry(context)) {
const end: number = Date.now();
context.telemetry.measurements.duration = (end - start) / 1000;
context.telemetry.properties.azureSubscriptionId = context.subscriptionId;
context.telemetry.properties.azureResourceId = context.resourceId;

// de-dupe
context.valuesToMask = context.valuesToMask.filter((v, index) => context.valuesToMask.indexOf(v) === index);
for (const [key, value] of Object.entries(context.telemetry.properties)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as types from '../../../../index';
import * as vscode from 'vscode';
import { ResourceGroupsItem } from '../../quickPickAzureResource/tempTypes';
import { azureResourceExperience } from '../azureResourceExperience';
import { azureResourceExperience, InternalAzureResourceExperienceOptions } from '../azureResourceExperience';
import { subscriptionExperience } from '../subscriptionExperience';
import { isAzExtTreeItem } from '../../../tree/isAzExtTreeItem';
import { createSubscriptionContext } from '../../../utils/credentialUtils';
Expand All @@ -20,7 +20,8 @@ export namespace PickTreeItemWithCompatibility {
*/
export async function resource<TPick extends types.AzExtTreeItem>(context: types.IActionContext, tdp: vscode.TreeDataProvider<ResourceGroupsItem>, options: types.CompatibilityPickResourceExperienceOptions): Promise<TPick> {
const { resourceTypes, childItemFilter } = options;
return azureResourceExperience({ ...context, v1Compatibility: true }, tdp, resourceTypes ? Array.isArray(resourceTypes) ? resourceTypes : [resourceTypes] : undefined, childItemFilter);
(context as InternalAzureResourceExperienceOptions).v1Compatibility = true;
return azureResourceExperience(context, tdp, resourceTypes ? Array.isArray(resourceTypes) ? resourceTypes : [resourceTypes] : undefined, childItemFilter);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export class QuickPickAzureResourceStep extends GenericQuickPickStep<AzureResour
// TODO
wizardContext.resource = pickedAzureResource.resource;
wizardContext.resourceGroup = pickedAzureResource.resource.resourceGroup;
wizardContext.resourceId = pickedAzureResource.resource.id;
wizardContext.subscriptionId = pickedAzureResource.resource.subscription.subscriptionId;

return pickedAzureResource;
}
Expand Down
9 changes: 4 additions & 5 deletions utils/src/pickTreeItem/runQuickPickWizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,19 @@ import { getLastNode } from './getLastNode';

export async function runQuickPickWizard<TPick>(context: types.PickExperienceContext, wizardOptions?: types.IWizardOptions<types.AzureResourceQuickPickWizardContext>, startingNode?: unknown): Promise<TPick> {
// Fill in the `pickedNodes` property
const wizardContext = { ...context } as types.AzureResourceQuickPickWizardContext;
wizardContext.pickedNodes = startingNode ? [startingNode] : [];
(context as types.AzureResourceQuickPickWizardContext).pickedNodes = startingNode ? [startingNode] : [];

const wizard = new AzureWizard(wizardContext, {
const wizard = new AzureWizard(context, {
hideStepCount: true,
showLoadingPrompt: wizardOptions?.showLoadingPrompt ?? true,
...wizardOptions
});

await wizard.prompt();

const lastPickedItem = getLastNode(wizardContext);
const lastPickedItem = getLastNode(context as types.AzureResourceQuickPickWizardContext);
if (!lastPickedItem) {
throw new NoResourceFoundError(wizardContext);
throw new NoResourceFoundError(context);
} else {
return (!context.dontUnwrap && isWrapper(lastPickedItem)) ? lastPickedItem.unwrap() : lastPickedItem as unknown as TPick;
}
Expand Down
18 changes: 18 additions & 0 deletions utils/src/registerCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ function isTreeElementBase(object?: unknown): object is types.TreeElementBase {
return typeof object === 'object' && object !== null && 'getTreeItem' in object;
}

// if the firstArg has a resource property, it is a ResourceGroupsItem from the resource groups extension
function isResourceGroupsItem(object?: unknown): object is types.ResourceGroupsItem {
return typeof object === 'object' && object !== null && 'resource' in object;
}

// resource has a lot of properties but for the sake of telemetry, we are interested in the id and subscriptionId
type Resource = {
id?: string;
subscription?: {
subscriptionId?: string;
};
}

export function registerCommand(commandId: string, callback: (context: types.IActionContext, ...args: unknown[]) => unknown, debounce?: number, telemetryId?: string): void {
let lastClickTime: number | undefined; /* Used for debounce */
ext.context.subscriptions.push(commands.registerCommand(commandId, async (...args: unknown[]): Promise<unknown> => {
Expand All @@ -37,6 +50,11 @@ export function registerCommand(commandId: string, callback: (context: types.IAc
context.telemetry.properties.contextValue = (await firstArg.getTreeItem()).contextValue;
}

if (isResourceGroupsItem(firstArg)) {
context.resourceId = (firstArg as { resource: Resource })?.resource?.id;
context.subscriptionId = (firstArg as { resource: Resource })?.resource?.subscription?.subscriptionId;
}

for (const arg of args) {
if (arg instanceof AzExtTreeItem) {
addTreeItemValuesToMask(context, arg, 'command');
Expand Down
2 changes: 1 addition & 1 deletion utils/src/tree/AzExtTreeDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export class AzExtTreeDataProvider implements IAzExtTreeDataProviderInternal, ty

const result: AzExtTreeItem[] = Array.from(resultMap.values());
result.push(...duplicateChildren.map(c => {
const message: string = l10n.t('An element with the following id already exists: {0}', c.fullId);
const message: string = l10n.t('An element with the following id already exists: {id}', { id: c.fullId });
return new InvalidTreeItem(treeItem, new Error(message), { contextValue: 'azureextensionui.duplicate', label: c.label });
}));

Expand Down

0 comments on commit 03fbc55

Please sign in to comment.