Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add document drop handler and telemetry #2448

Merged
merged 35 commits into from Jul 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
929d71d
Add language service features to extension
bwateratmsft Jun 9, 2023
93097ba
Update to version 2 of `@microsoft/vscode-azext-utils`
bwateratmsft Jun 9, 2023
9ac3f5e
Merge branch 'bmw/utils' into bmw/lang
bwateratmsft Jun 9, 2023
ab8b3cc
Make a diagnostic provider
bwateratmsft Jun 9, 2023
50d188d
Merge branch 'main' into bmw/lang
bwateratmsft Jun 9, 2023
ff3cb82
Add diagnostic provider for invalid paths
bwateratmsft Jun 12, 2023
4fa504f
This TODO is fixed
bwateratmsft Jun 12, 2023
b483291
Add trailing newlines
bwateratmsft Jun 12, 2023
7e517e8
Add back completion provider
bwateratmsft Jun 12, 2023
d81d6ca
Add back code action provider
bwateratmsft Jun 12, 2023
da31daf
Karol's feedback
bwateratmsft Jun 12, 2023
71d6a78
Merge branch 'bmw/diagnostics' into bmw/lang
bwateratmsft Jun 12, 2023
2eb80d2
Commit current progress
bwateratmsft Jun 13, 2023
f72cde4
Matt's feedback
bwateratmsft Jun 13, 2023
2d92b8f
Please stop deleting my files
bwateratmsft Jun 13, 2023
6da3ad9
But seriously
bwateratmsft Jun 13, 2023
a7abbc3
Minor changes
bwateratmsft Jun 13, 2023
e6771da
Commit current progress
bwateratmsft Jun 13, 2023
e89db69
Use a proactive handler
bwateratmsft Jun 13, 2023
7dbc2c3
Minor changes
bwateratmsft Jun 14, 2023
4a32798
Add a mind-blowing document drop provider
bwateratmsft Jun 14, 2023
accaf4b
General cleanup refactoring
bwateratmsft Jun 14, 2023
fdad218
Fix minor issue
bwateratmsft Jun 14, 2023
7bb9da1
Cleanup whitespace situation
bwateratmsft Jun 15, 2023
0e83590
Move comment
bwateratmsft Jun 15, 2023
2606f65
Merge branch 'main' into bmw/lang
bwateratmsft Jun 20, 2023
0e5f98b
Remove completion provider
bwateratmsft Jun 20, 2023
e9cbde8
Add handler for folder rename events
bwateratmsft Jun 21, 2023
f59c528
Pass cancellation token and max at one result
bwateratmsft Jun 21, 2023
5a48953
Git is making me sad
bwateratmsft Jun 21, 2023
42fec1e
Fix cspell dictionary
bwateratmsft Jun 21, 2023
8b8046f
Slightly improve tab behavior
bwateratmsft Jun 21, 2023
85129a2
Merge branch 'main' into bmw/lang
bwateratmsft Jun 22, 2023
5910ca5
Extra newline
bwateratmsft Jun 22, 2023
5465674
Add telemetry
bwateratmsft Jul 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions ext/vscode/.vscode/cspell-dictionary.txt
Expand Up @@ -7,3 +7,6 @@ azureresources
walkthrough
walkthroughs
DEBUGTELEMETRY
appservice
containerapp
staticwebapp
43 changes: 23 additions & 20 deletions ext/vscode/src/language/AzureYamlDiagnosticProvider.ts
@@ -1,10 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import { AzExtFsExtra, IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { documentDebounce } from './documentDebounce';
import { getAzureYamlProjectInformation } from './azureYamlUtils';
import { TelemetryId } from '../telemetry/telemetryId';

// Time between when the user stops typing and when we send diagnostics
const DiagnosticDelay = 1000;
Expand Down Expand Up @@ -33,31 +34,33 @@ export class AzureYamlDiagnosticProvider extends vscode.Disposable {
this.diagnosticCollection = diagnosticCollection;
}

public async provideDiagnostics(document: vscode.TextDocument, token?: vscode.CancellationToken): Promise<vscode.Diagnostic[] | undefined> {
const results: vscode.Diagnostic[] = [];
public provideDiagnostics(document: vscode.TextDocument, token?: vscode.CancellationToken): Promise<vscode.Diagnostic[] | undefined> {
return callWithTelemetryAndErrorHandling(TelemetryId.AzureYamlProvideDiagnostics, async (context: IActionContext) => {
const results: vscode.Diagnostic[] = [];

try {
const projectInformation = await getAzureYamlProjectInformation(document);
try {
const projectInformation = await getAzureYamlProjectInformation(document);

for (const project of projectInformation) {
if (await AzExtFsExtra.pathExists(project.projectUri)) {
continue;
}
for (const project of projectInformation) {
if (await AzExtFsExtra.pathExists(project.projectUri)) {
continue;
}

const diagnostic = new vscode.Diagnostic(
project.projectValueNodeRange,
vscode.l10n.t('The project path must be an existing folder or file path relative to the azure.yaml file.'),
vscode.DiagnosticSeverity.Error
);
const diagnostic = new vscode.Diagnostic(
project.projectValueNodeRange,
vscode.l10n.t('The project path must be an existing folder or file path relative to the azure.yaml file.'),
vscode.DiagnosticSeverity.Error
);

results.push(diagnostic);
results.push(diagnostic);
}
} catch {
// Best effort--the YAML extension will show parsing errors for us if it is present
}
} catch {
// Best effort--the YAML extension will show parsing errors for us if it is present
return results;
}

return results;
context.telemetry.measurements.diagnosticCount = results.length;
return results;
});
}

private async updateDiagnosticsFor(document: vscode.TextDocument, delay: boolean = true): Promise<void> {
Expand Down
40 changes: 40 additions & 0 deletions ext/vscode/src/language/AzureYamlDocumentDropEditProvider.ts
@@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AzExtFsExtra, IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
import * as path from 'path';
import * as vscode from 'vscode';
import { getProjectRelativePath } from './azureYamlUtils';
import { TelemetryId } from '../telemetry/telemetryId';

export class AzureYamlDocumentDropEditProvider implements vscode.DocumentDropEditProvider {
public provideDocumentDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken): Promise<vscode.DocumentDropEdit | undefined> {
return callWithTelemetryAndErrorHandling(TelemetryId.AzureYamlProvideDocumentDropEdits, async (context: IActionContext) => {
const maybeFolder = dataTransfer.get('text/uri-list')?.value;
const maybeFolderUri = vscode.Uri.parse(maybeFolder);

if (await AzExtFsExtra.pathExists(maybeFolderUri) && await AzExtFsExtra.isDirectory(maybeFolderUri)) {
const basename = path.basename(maybeFolderUri.fsPath);
const newRelativePath = getProjectRelativePath(document.uri, maybeFolderUri);

const initialWhitespace = position.character === 0 ? '\n\t' : '\n';

const snippet = new vscode.SnippetString(initialWhitespace)
.appendPlaceholder(basename).appendText(':\n')
.appendText(`\t\tproject: ${newRelativePath}\n`)
.appendText('\t\tlanguage: ')
.appendChoice(['dotnet', 'csharp', 'fsharp', 'py', 'python', 'js', 'ts', 'java'])
.appendText('\n')
.appendText('\t\thost: ')
.appendChoice(['appservice', 'containerapp', 'function', 'staticwebapp', 'aks'])
.appendText('\n');

context.telemetry.properties.editProvided = 'true';
return new vscode.DocumentDropEdit(snippet);
}

context.telemetry.properties.editProvided = 'false';
return undefined;
});
}
}
61 changes: 35 additions & 26 deletions ext/vscode/src/language/AzureYamlProjectRenameProvider.ts
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { AzExtFsExtra } from '@microsoft/vscode-azext-utils';
import { AzExtFsExtra, IActionContext, callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { getAzureYamlProjectInformation, getProjectRelativePath } from './azureYamlUtils';
import { TelemetryId } from '../telemetry/telemetryId';

export class AzureYamlProjectRenameProvider extends vscode.Disposable {
public constructor() {
Expand All @@ -15,31 +16,39 @@ export class AzureYamlProjectRenameProvider extends vscode.Disposable {
});
}

public async provideWorkspaceEdits(oldUri: vscode.Uri, newUri: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
// When a folder is renamed, only the folder is passed in as the old URI
// At the time this is called, the rename has not happened yet
if (!await AzExtFsExtra.isDirectory(oldUri)) {
return undefined;
}

const azureYamlUris = await vscode.workspace.findFiles('**/azure.{yml,yaml}', undefined, 1, token);
if (azureYamlUris.length === 0) {
return undefined;
}

const azureYamlUri = azureYamlUris[0];
const azureYaml = await vscode.workspace.openTextDocument(azureYamlUri);
const projectInformation = await getAzureYamlProjectInformation(azureYaml);

const projectToUpdate = projectInformation.find(pi => pi.projectUri.toString() === oldUri.toString());
if (!projectToUpdate) {
return undefined;
}

const newRelativePath = getProjectRelativePath(azureYamlUri, newUri);
const projectUriEdit = new vscode.WorkspaceEdit();
projectUriEdit.replace(azureYamlUri, projectToUpdate.projectValueNodeRange, newRelativePath);
return projectUriEdit;
public provideWorkspaceEdits(oldUri: vscode.Uri, newUri: vscode.Uri, token: vscode.CancellationToken): Promise<vscode.WorkspaceEdit | undefined> {
return callWithTelemetryAndErrorHandling(TelemetryId.AzureYamlProjectRenameProvideWorkspaceEdits, async (context: IActionContext) => {
// When a folder is renamed, only the folder is passed in as the old URI
// At the time this is called, the rename has not happened yet
if (!await AzExtFsExtra.isDirectory(oldUri)) {
context.telemetry.properties.result = 'Canceled';
context.telemetry.properties.cancelStep = 'SourceNotDirectory';
return undefined;
}

const azureYamlUris = await vscode.workspace.findFiles('**/azure.{yml,yaml}', undefined, 1, token);
if (azureYamlUris.length === 0) {
context.telemetry.properties.result = 'Canceled';
context.telemetry.properties.cancelStep = 'NoAzureYaml';
return undefined;
}

const azureYamlUri = azureYamlUris[0];
const azureYaml = await vscode.workspace.openTextDocument(azureYamlUri);
const projectInformation = await getAzureYamlProjectInformation(azureYaml);

const projectToUpdate = projectInformation.find(pi => pi.projectUri.toString() === oldUri.toString());
if (!projectToUpdate) {
context.telemetry.properties.result = 'Canceled';
context.telemetry.properties.cancelStep = 'ProjectNotInAzureYaml';
return undefined;
}

const newRelativePath = getProjectRelativePath(azureYamlUri, newUri);
const projectUriEdit = new vscode.WorkspaceEdit();
projectUriEdit.replace(azureYamlUri, projectToUpdate.projectValueNodeRange, newRelativePath);
return projectUriEdit;
});
}

private handleWillRenameFile(evt: vscode.FileWillRenameEvent): void {
Expand Down
5 changes: 5 additions & 0 deletions ext/vscode/src/language/languageFeatures.ts
Expand Up @@ -5,6 +5,7 @@ import * as vscode from 'vscode';
import ext from '../ext';
import { AzureYamlDiagnosticProvider } from './AzureYamlDiagnosticProvider';
import { AzureYamlProjectRenameProvider } from './AzureYamlProjectRenameProvider';
import { AzureYamlDocumentDropEditProvider } from './AzureYamlDocumentDropEditProvider';

export const AzureYamlSelector: vscode.DocumentSelector = { language: 'yaml', scheme: 'file', pattern: '**/azure.{yml,yaml}' };

Expand All @@ -16,4 +17,8 @@ export function registerLanguageFeatures(): void {
ext.context.subscriptions.push(
new AzureYamlProjectRenameProvider()
);

ext.context.subscriptions.push(
vscode.languages.registerDocumentDropEditProvider(AzureYamlSelector, new AzureYamlDocumentDropEditProvider())
);
}
21 changes: 15 additions & 6 deletions ext/vscode/src/telemetry/telemetryId.ts
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

// Defines telemetry IDs for custom telemetry events exposed by the extension.
// Note that command invocations are covered by the AzExtUtils library,
// Note that command invocations are covered by the AzExtUtils library,
// which automatically captures the duration and success/failure information for every command.
// Every event includes duration and success/failure information.
// Some additional data is captured for certain events; see comments for individual enum members.
Expand Down Expand Up @@ -57,18 +57,27 @@ export enum TelemetryId {

// Reported when 'env refresh' CLI command is invoked.
EnvRefreshCli = 'azure-dev.commands.cli.env-refresh.task',

// Reported when the product evaluates whether to prompt the user for a survey.
// We capture
// - whether the user was already offered the survey,
// We capture
// - whether the user was already offered the survey,
// - whether the user was prompted during current session (for any survey)
// - whether the user is eligible for given survey
// - whether the user is flighted for the survey
// - whether the user is flighted for the survey
SurveyCheck = 'azure-dev.survey-check',

// Captures the result of a survey prompt
SurveyPromptResponse = 'azure-dev.survey-prompt-response',

WorkspaceViewApplicationResolve = 'azure-dev.views.workspace.application.resolve',
WorkspaceViewEnvironmentResolve = 'azure-dev.views.workspace.environment.resolve'
WorkspaceViewEnvironmentResolve = 'azure-dev.views.workspace.environment.resolve',

// Reported when diagnostics are provided on an azure.yaml document
AzureYamlProvideDiagnostics = 'azure-dev.azureYaml.provideDiagnostics',

// Reported when the document drop edit provider is invoked
AzureYamlProvideDocumentDropEdits = 'azure-dev.azureYaml.provideDocumentDropEdits',

// Reported when the project rename provider is invoked
AzureYamlProjectRenameProvideWorkspaceEdits = 'azure-dev.azureYaml.projectRename.provideWorkspaceEdits',
}