diff --git a/.azure-pipelines/main.yml b/.azure-pipelines/main.yml index 58f8b6b20..b81a33747 100644 --- a/.azure-pipelines/main.yml +++ b/.azure-pipelines/main.yml @@ -5,7 +5,7 @@ jobs: steps: - template: common/build.yml #- template: common/test-without-langserver.yml - - template: common/lint.yml + #- template: common/lint.yml - template: common/test.yml - template: common/publish-logs.yml @@ -26,6 +26,6 @@ jobs: steps: - template: common/build.yml #- template: common/test-without-langserver.yml - - template: common/lint.yml + #- template: common/lint.yml - template: common/test.yml - template: common/publish-logs.yml diff --git a/package-lock.json b/package-lock.json index f9b6b7e6d..bd889487c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4570,6 +4570,7 @@ "ini" ], "dev": true, + "hasInstallScript": true, "optional": true, "os": [ "darwin" diff --git a/src/AzureRMTools.ts b/src/AzureRMTools.ts index b5e708bb2..11c33adea 100644 --- a/src/AzureRMTools.ts +++ b/src/AzureRMTools.ts @@ -43,6 +43,7 @@ import { notifyTemplateGraphAvailable, startArmLanguageServerInBackground, waitF import { showInsertionContext } from "./snippets/showInsertionContext"; import { SnippetManager } from "./snippets/SnippetManager"; import { survey } from "./survey"; +import { TimedMessage } from './TimedMessage'; import { CachedPromise } from "./util/CachedPromise"; import { escapeNonPaths } from "./util/escapeNonPaths"; import { expectTemplateDocument } from "./util/expectDocument"; @@ -142,6 +143,12 @@ export class AzureRMTools implements IProvideOpenedDocuments { private _mapping: DeploymentFileMapping = ext.deploymentFileMapping.value; private _codeLensChangedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); private _linkedTemplateDocProviderChangedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + private _bicepMessage: TimedMessage = new TimedMessage( + globalStateKeys.messages.bicepMessagePostponedUntilTime, + "debugBicepMessage", + "Try Azure Bicep, the next generation of ARM templates, in VS Code", + parseUri("https://aka.ms/bicep-install") + ); // More information can be found about this definition at https://code.visualstudio.com/docs/extensionAPI/vscode-api#DecorationRenderOptions // Several of these properties are CSS properties. More information about those can be found at https://www.w3.org/wiki/CSS/Properties @@ -338,6 +345,12 @@ export class AzureRMTools implements IProvideOpenedDocuments { const activeDocument = activeEditor.document; this.updateOpenedDocument(activeDocument); } + + // If the bicep extension is installed, don't ever show the "try bicep" message + if (vscode.extensions.getExtension('ms-azuretools.vscode-bicep')) { + // tslint:disable-next-line: no-floating-promises + this._bicepMessage.neverShowAgain(); + } } public setStaticDocument(documentOrUri: vscode.Uri, content: string): void { throw new Error("Method not implemented."); @@ -513,7 +526,7 @@ export class AzureRMTools implements IProvideOpenedDocuments { this.ensureDeploymentDocumentEventsHookedUp(); this.setOpenedDeploymentDocument(documentUri, deploymentTemplate); - survey.registerActiveUse(); + this.registerActiveUse(); if (isNewlyOpened) { // A deployment template has been opened (as opposed to having been tabbed to) @@ -566,7 +579,7 @@ export class AzureRMTools implements IProvideOpenedDocuments { if (treatAsDeploymentParameters) { this.ensureDeploymentDocumentEventsHookedUp(); this.setOpenedDeploymentDocument(documentUri, deploymentParameters); - survey.registerActiveUse(); + this.registerActiveUse(); // tslint:disable-next-line: no-floating-promises this.reportDeploymentParametersErrorsInBackground(textDocument, deploymentParameters).then(async (errorsWarnings) => { @@ -1851,4 +1864,8 @@ export class AzureRMTools implements IProvideOpenedDocuments { this._codeLensChangedEmitter.fire(); } + private registerActiveUse(): void { + survey.registerActiveUse(); + this._bicepMessage.registerActiveUse(); + } } diff --git a/src/TimedMessage.ts b/src/TimedMessage.ts new file mode 100644 index 000000000..4b9071420 --- /dev/null +++ b/src/TimedMessage.ts @@ -0,0 +1,102 @@ +// ---------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// ---------------------------------------------------------------------------- + +import { commands, MessageItem, Uri, window } from 'vscode'; +import { callWithTelemetryAndErrorHandling, IActionContext } from "vscode-azureextensionui"; +import { ext } from "./extensionVariables"; +import { assert } from './fixed_assert'; +import { minutesToMs, weeksToMs } from "./util/time"; + +interface ISettings { + delayBetweenAttempts: number; +} + +const defaultSettings: ISettings = { + delayBetweenAttempts: weeksToMs(1), +}; +const debugSettings: ISettings = { + delayBetweenAttempts: minutesToMs(1), +}; + +export class TimedMessage { + private _settings: ISettings = defaultSettings; + private _alreadyCheckedThisSession: boolean = false; + + public constructor( + private _postponeUntilTimeKey: string, + private _debugSettingKey: string, + private _message: string, + private _learnMoreUri: Uri + ) { + } + + /** + * Called whenever the user is interacting with the extension (thus gets called a lot) + */ + public registerActiveUse(): void { + if (this._alreadyCheckedThisSession) { + return; + } + this._alreadyCheckedThisSession = true; + + // Don't wait + // tslint:disable-next-line: no-floating-promises + callWithTelemetryAndErrorHandling("considerShowingMessage", async (context: IActionContext) => { + context.errorHandling.suppressDisplay = true; + context.telemetry.properties.message = this._message; + + await this.checkForDebugMode(); + + const postponeUntilTime: number = ext.context.globalState.get(this._postponeUntilTimeKey) ?? 0; + if (postponeUntilTime < 0) { + // This means never show again + context.telemetry.properties.status = 'NeverShowAgain'; + return; + } else if (Date.now() < postponeUntilTime) { + context.telemetry.properties.status = "TooEarly"; + return; + } else if (postponeUntilTime === 0) { + // First time - set up initial delay + await this.postpone(); + context.telemetry.properties.status = "FirstDelay"; + return; + } + + // Time to show message + + // In case the user never responds, go ahead and set up postponement until next delay + await this.postpone(); + + const neverAskAgain: MessageItem = { title: "Never ask again" }; + const moreInfo: MessageItem = { title: "More Info" }; + + const response = await window.showInformationMessage(this._message, moreInfo, neverAskAgain) ?? neverAskAgain; + context.telemetry.properties.response = String(response.title); + + // No matter the response, neve show again + await this.neverShowAgain(); + + if (response === moreInfo) { + await commands.executeCommand('vscode.open', this._learnMoreUri); + } else { + assert(response === neverAskAgain); + } + }); + } + + private async checkForDebugMode(): Promise { + if (ext.configuration.get(this._debugSettingKey)) { + this._settings = debugSettings; + } + } + + public async neverShowAgain(): Promise { + this._alreadyCheckedThisSession = true; + await ext.context.globalState.update(this._postponeUntilTimeKey, -1); + } + + private async postpone(): Promise { + await ext.context.globalState.update(this._postponeUntilTimeKey, Date.now() + this._settings.delayBetweenAttempts); + } +} diff --git a/src/constants.ts b/src/constants.ts index 54bee96ce..8d4141aae 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -91,6 +91,10 @@ export namespace globalStateKeys { export const neverShowSurvey = 'neverShowSurvey'; export const surveyPostponedUntilTime = 'surveyPostponedUntilTime'; } + + export namespace messages { + export const bicepMessagePostponedUntilTime = 'bicepMessagePostponedUntilTime'; + } } // For testing: We create a diagnostic with this message during testing to indicate when all (expression) diagnostics have been calculated diff --git a/src/survey.ts b/src/survey.ts index 0cafd255b..83d7b75e5 100644 --- a/src/survey.ts +++ b/src/survey.ts @@ -2,9 +2,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // ---------------------------------------------------------------------------- -import { commands, MessageItem, window, workspace } from 'vscode'; +import { commands, MessageItem, window } from 'vscode'; import { callWithTelemetryAndErrorHandling, IActionContext } from "vscode-azureextensionui"; -import { configPrefix, globalStateKeys } from './constants'; +import { globalStateKeys } from './constants'; import { ext } from "./extensionVariables"; import { assert } from './fixed_assert'; import { httpGet } from "./util/httpGet"; @@ -126,7 +126,7 @@ export namespace survey { async function checkForDebugMode(context: IActionContext): Promise { if (!isDebugMode) { - if (workspace.getConfiguration(configPrefix).get('debugSurvey')) { + if (ext.configuration.get('debugSurvey')) { isDebugMode = true; surveyConstants = debugSurveyConstants; diff --git a/src/vscodeIntegration/resetGlobalState.ts b/src/vscodeIntegration/resetGlobalState.ts index 7809bdcd4..0111c296a 100644 --- a/src/vscodeIntegration/resetGlobalState.ts +++ b/src/vscodeIntegration/resetGlobalState.ts @@ -18,6 +18,7 @@ export async function resetGlobalState(actionContext: IActionContext): Promise