Add codeLens to Update ASA job info (#492)

* Add codeLens to Update ASA job info

* Disable command palette, and add module name in progress tips

* Move ASA module update logic to streamAnalyticsManager, update Codelens pattern, remove unnecessary command in package.json

* Use api-version=2019-06-01

* Use singleton for streamAnalyticsManager

* Remove unnecessary code, and reinstall packages to update package-lock.json

* rename queryASAJobInfo to publishAndQueryASAJobInfo, fix ci failed issue
This commit is contained in:
Renlong Tu 2019-08-23 15:32:47 +08:00 коммит произвёл GitHub
Родитель b249394625
Коммит 94068b777b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 1634 добавлений и 2878 удалений

4296
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -455,7 +455,7 @@
"@types/node": "^6.0.40",
"@types/request-promise": "^4.1.41",
"@types/semver": "^5.5.0",
"@types/sinon": "^5.0.5",
"@types/sinon": "^7.0.13",
"@types/strip-json-comments": "0.0.30",
"@types/tmp": "0.0.33",
"azure-arm-resource": "^3.1.1-preview",
@ -475,19 +475,20 @@
"azure-arm-containerregistry": "^2.2.0",
"azure-arm-machinelearningservices": "^1.0.0-preview",
"azure-arm-streamanalytics": "^1.0.0-preview",
"body-parser": "^1.18.2",
"dotenv": "^5.0.1",
"download-git-repo": "^1.0.2",
"express": "^4.16.3",
"fs-extra": "^4.0.2",
"is-port-reachable": "^2.0.0",
"json-source-map": "^0.6.1",
"jsonc-parser": "^1.0.1",
"request": "^2.88.0",
"request-promise": "^4.2.2",
"semver": "^5.6.0",
"strip-json-comments": "^2.0.1",
"tmp": "0.0.33",
"vscode-extension-telemetry": "0.0.18",
"body-parser": "^1.18.2",
"express": "^4.16.3"
"vscode-extension-telemetry": "0.0.18"
},
"extensionDependencies": [
"ms-vscode.azure-account",

Просмотреть файл

@ -187,6 +187,14 @@ export class Constants {
public static extModuleKeyPrefixTemplate(dir: string): string {
return `MODULEDIR<${dir}>`;
}
public static newASAJobAvailableMsg(asaModuleName: string): string {
return `Configurations of Stream Analytics Job "${asaModuleName}" have been changed, do you want to update them now?`;
}
public static noNewASAJobFoundMsg(asaModuleName: string): string {
return `No configuration changes has been found for Stream Analytics Job: "${asaModuleName}".`;
}
}
export enum ContainerState {

Просмотреть файл

@ -4,40 +4,128 @@
import StreamAnalyticsManagementClient = require("azure-arm-streamanalytics");
import { StreamingJob } from "azure-arm-streamanalytics/lib/models";
import { StreamingJobs } from "azure-arm-streamanalytics/lib/operations";
import * as fse from "fs-extra";
import * as request from "request-promise";
import * as vscode from "vscode";
import { Constants } from "../common/constants";
import { UserCancelledError } from "../common/UserCancelledError";
import { Utility } from "../common/utility";
import { AzureAccount } from "../typings/azure-account.api";
import { AzureAccount, AzureSession, AzureSubscription } from "../typings/azure-account.api";
import { StreamAnalyticsPickItem } from "./models/streamAnalyticsPickItem";
export enum ASAUpdateStatus {
Idle,
CheckingUpdate,
Updating,
}
export class StreamAnalyticsManager {
public static getInstance(): StreamAnalyticsManager {
if (!StreamAnalyticsManager.instance) {
StreamAnalyticsManager.instance = new StreamAnalyticsManager();
}
return StreamAnalyticsManager.instance;
}
private static instance: StreamAnalyticsManager;
private readonly azureAccount: AzureAccount;
private readonly MaximumRetryCount: number = 3;
private asaUpdateStatus: ASAUpdateStatus;
constructor() {
private constructor() {
this.azureAccount = vscode.extensions.getExtension<AzureAccount>("ms-vscode.azure-account")!.exports;
this.asaUpdateStatus = ASAUpdateStatus.Idle;
}
public async selectStreamingJob(): Promise<StreamAnalyticsPickItem> {
public async getJobInfo(): Promise<any> {
await Utility.waitForAzLogin(this.azureAccount);
const job: StreamAnalyticsPickItem = await vscode.window.showQuickPick(this.loadAllStreamingJobs(), { placeHolder: `Select ${Constants.asaJobDesc}`, ignoreFocusOut: true });
if (!job) {
const streamingJob: StreamAnalyticsPickItem = await vscode.window.showQuickPick(this.loadAllStreamingJobs(), { placeHolder: `Select ${Constants.asaJobDesc}`, ignoreFocusOut: true });
if (!streamingJob) {
throw new UserCancelledError();
}
return job;
return await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Querying Stream Analytics Job information",
}, async (): Promise<object> => {
const ASAJobResourceId: string = streamingJob.job.id;
return await this.publishAndQueryASAJobInfo(ASAJobResourceId, streamingJob.azureSubscription.session);
});
}
public async getJobInfo(streamingJob: StreamAnalyticsPickItem): Promise<object> {
public async checkAndUpdateASAJob(templateFile: string, moduleName: string) {
if (this.asaUpdateStatus === ASAUpdateStatus.Idle) {
await Utility.waitForAzLogin(this.azureAccount);
try {
this.asaUpdateStatus = ASAUpdateStatus.CheckingUpdate;
const isUpdateAvailable = await this.isASAJobUpdateAvailable(templateFile, moduleName);
this.asaUpdateStatus = ASAUpdateStatus.Idle;
if (isUpdateAvailable) {
const yesOption = "Yes";
const option = await vscode.window.showInformationMessage(Constants.newASAJobAvailableMsg(moduleName), yesOption);
if (option === yesOption) {
this.asaUpdateStatus = ASAUpdateStatus.Updating;
await this.updateASAJobInfoModuleTwin(templateFile, moduleName);
this.asaUpdateStatus = ASAUpdateStatus.Idle;
}
} else {
await vscode.window.showInformationMessage(Constants.noNewASAJobFoundMsg(moduleName));
}
} catch (err) {
this.asaUpdateStatus = ASAUpdateStatus.Idle;
throw err;
}
}
}
private async updateJobInfo(templateFile: string, moduleName: string): Promise<any> {
return await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Updating Stream Analytics Job information",
}, async (): Promise<object> => {
const ASAInfo = await this.getJobInfoFromDeploymentTemplate(templateFile, moduleName);
const subscription = await this.getJobSubscription(ASAInfo);
const ASAJobResourceId: string = ASAInfo.ASAJobResourceId;
return await this.publishAndQueryASAJobInfo(ASAJobResourceId, subscription.session);
});
}
private async isASAJobUpdateAvailable(templateFile: string, moduleName: string): Promise<boolean> {
return await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "Checking configurations update for Stream Analytics Job",
}, async (): Promise<boolean> => {
const ASAInfo = await this.getJobInfoFromDeploymentTemplate(templateFile, moduleName);
const GetASAJobApiUrl: string = `https://management.azure.com${ASAInfo.ASAJobResourceId}?api-version=2019-06-01`;
const curEtag = ASAInfo.ASAJobEtag;
const subscription = await this.getJobSubscription(ASAInfo);
const { aadAccessToken } = await Utility.acquireAadToken(subscription.session);
const jobInfo = await request.get(GetASAJobApiUrl, {
auth: {
bearer: aadAccessToken,
},
resolveWithFullResponse: true,
});
const latestETag = jobInfo.headers.etag;
return latestETag !== curEtag;
});
}
private async updateASAJobInfoModuleTwin(templateFile: string, moduleName: string) {
const jobInfo = await this.updateJobInfo(templateFile, moduleName);
const moduleTwin = jobInfo.twin.content;
const templateJson = await fse.readJson(templateFile);
templateJson.modulesContent[moduleName] = moduleTwin;
await fse.writeFile(templateFile, JSON.stringify(templateJson, null, 2), { encoding: "utf8" });
}
private async publishAndQueryASAJobInfo(resourceId: string, session: AzureSession) {
try {
const id: string = streamingJob.job.id;
const publishJobUrl: string = `https://management.azure.com${id}/publishedgepackage?api-version=2017-04-01-preview`;
const apiUrl: string = `https://management.azure.com${resourceId}/publishedgepackage?api-version=2019-06-01`;
const { aadAccessToken } = await Utility.acquireAadToken(session);
const { aadAccessToken } = await Utility.acquireAadToken(streamingJob.azureSubscription.session);
const publishResponse = await request.post(publishJobUrl, {
const publishResponse = await request.post(apiUrl, {
auth: {
bearer: aadAccessToken,
},
@ -74,11 +162,32 @@ export class StreamAnalyticsManager {
}
}
} catch (error) {
error.message = `Cannot parse Stream Analytics publish job information: ${error.message}`;
error.message = `Parse Stream Analytics jobs info failed: ${error.message}`;
throw error;
}
}
private async getJobSubscription(ASAInfo): Promise<AzureSubscription> {
for (const azureSubscription of this.azureAccount.subscriptions) {
if (ASAInfo.ASAJobResourceId.indexOf(azureSubscription.subscription.id) >= 0) {
return azureSubscription;
}
}
const ASAJobName: string = ASAInfo.ASAJobResourceId.split("/").pop();
throw new Error(`Cannot find Stream Analytics Job '${ASAJobName}' in your Azure account, please make sure to login to the right acount.`);
}
private async getJobInfoFromDeploymentTemplate(templateFile: string, moduleName) {
try {
const templateJson = await fse.readJson(templateFile);
const ASAInfo = templateJson.modulesContent[moduleName]["properties.desired"];
return ASAInfo;
} catch (err) {
throw new Error("Cannot parse Stream Analytics Job information from module twin: " + err.message);
}
}
private async loadAllStreamingJobs(): Promise<StreamAnalyticsPickItem[]> {
try {
await this.azureAccount.waitForFilters();

Просмотреть файл

@ -271,6 +271,11 @@ export class EdgeManager {
}
}
public async checkAndUpdateASAJob(templateFile: string, moduleName: string) {
const saManager = StreamAnalyticsManager.getInstance();
await saManager.checkAndUpdateASAJob(templateFile, moduleName);
}
private async generateDebugCreateOptions(moduleName: string, template: string): Promise<{ debugImageName: string, debugCreateOptions: any }> {
let debugCreateOptions = {};
switch (template) {
@ -634,9 +639,8 @@ export class EdgeManager {
debugImageName = imageName = await amlManager.selectAmlImage();
repositoryName = Utility.getRepositoryNameFromImageName(imageName);
} else if (template === Constants.STREAM_ANALYTICS) {
const saManager = new StreamAnalyticsManager();
const job = await saManager.selectStreamingJob();
const JobInfo: any = await saManager.getJobInfo(job);
const saManager = StreamAnalyticsManager.getInstance();
const JobInfo: any = await saManager.getJobInfo();
debugImageName = imageName = JobInfo.settings.image;
moduleTwin = JobInfo.twin.content;
env = JobInfo.env;

Просмотреть файл

@ -18,6 +18,7 @@ import { ContainerManager } from "./container/containerManager";
import { EdgeManager } from "./edge/edgeManager";
import { Simulator } from "./edge/simulator";
import { Gallery } from "./gallery/gallery";
import { ASAModuleUpdateCodeLensProvider } from "./intelliSense/ASAModuleUpdateCodeLensProvider";
import { ConfigCompletionItemProvider } from "./intelliSense/configCompletionItemProvider";
import { ConfigDefinitionProvider } from "./intelliSense/configDefinitionProvider";
import { ConfigDiagnosticProvider } from "./intelliSense/configDiagnosticProvider";
@ -77,6 +78,8 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("edge-coreclr", {resolveDebugConfiguration}));
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("edge-node", {resolveDebugConfiguration}));
context.subscriptions.push(vscode.languages.registerCodeLensProvider({ pattern: `**/{deployment.*.template.json,deployment.template.json}` }, new ASAModuleUpdateCodeLensProvider()));
initCommandAsync(context, outputChannel,
"azure-iot-edge.buildModuleImage",
(fileUri?: vscode.Uri): Promise<void> => {
@ -187,6 +190,12 @@ export function activate(context: vscode.ExtensionContext) {
return edgeManager.addModuleInfo(templateFile, outputChannel, isNewSolution, template, moduleInfo);
});
initCommandAsync(context, outputChannel,
"azure-iot-edge.internal.checkUpdateForASAModule",
async (templateFile: string, moduleName: string): Promise<void> => {
return edgeManager.checkAndUpdateASAJob(templateFile, moduleName);
});
context.subscriptions.push(vscode.window.onDidCloseTerminal((closedTerminal: vscode.Terminal) => {
Executor.onDidCloseTerminal(closedTerminal);
}));

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
"use strict";
import * as jsonMap from "json-source-map";
import * as vscode from "vscode";
export class ASAModuleUpdateCodeLensProvider implements vscode.CodeLensProvider {
private templateFilePath: string;
private ASAModuleTwinPathRoot: string = "/modulesContent/";
public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
const deploymentJsonString = document.getText();
this.templateFilePath = document.uri.fsPath;
return Promise.resolve(this.generateCodeLens(deploymentJsonString));
}
public generateCodeLens(deploymentJsonString: string): vscode.CodeLens[] {
const codeLensArr: vscode.CodeLens[] = [];
const result = jsonMap.parse(deploymentJsonString);
const deploymentJson = result.data;
const ASAModuleNamesArr = [];
for (const name in deploymentJson.modulesContent) {
if (deploymentJson.modulesContent[name]["properties.desired"].ASAJobEtag) {
ASAModuleNamesArr.push(name);
}
}
for (const name of ASAModuleNamesArr) {
const lineNum = result.pointers[this.ASAModuleTwinPathRoot + name].key.line;
const range = new vscode.Range(lineNum, 0, lineNum, 100);
const cmd: vscode.Command = {
title: "Check Configurations Update for ASA Job",
command: "azure-iot-edge.internal.checkUpdateForASAModule",
arguments: [this.templateFilePath, name],
};
codeLensArr.push(new vscode.CodeLens(range, cmd));
}
return codeLensArr;
}
}