From f4b03c0d94f0b36795e55d1cc8a7b4ac71358f86 Mon Sep 17 00:00:00 2001 From: Jun Han Date: Thu, 25 Jul 2019 20:29:30 -0500 Subject: [PATCH] List PnP Interfaces in tree view (#354) * Interfaces list * Add BI and icon * Add refresh button * Wrap REST Api --- package.json | 5 ++++ resources/interface.svg | 1 + src/Model/InterfaceItem.ts | 11 ++++++++ src/Nodes/DeviceNode.ts | 2 ++ src/Nodes/InterfaceLabelNode.ts | 46 +++++++++++++++++++++++++++++++++ src/Nodes/InterfaceNode.ts | 19 ++++++++++++++ src/constants.ts | 4 ++- src/utility.ts | 13 ++++++++++ 8 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 resources/interface.svg create mode 100644 src/Model/InterfaceItem.ts create mode 100644 src/Nodes/InterfaceLabelNode.ts create mode 100644 src/Nodes/InterfaceNode.ts diff --git a/package.json b/package.json index 4ff684ff..f194994c 100644 --- a/package.json +++ b/package.json @@ -570,6 +570,11 @@ { "command": "azure-iot-toolkit.startMonitorIoTHubMessageWithAbbreviation", "when": "view == iotHubDevices && viewItem == events" + }, + { + "command": "azure-iot-toolkit.refresh", + "when": "view == iotHubDevices && viewItem == interfaces-label", + "group": "inline" } ], "editor/context": [ diff --git a/resources/interface.svg b/resources/interface.svg new file mode 100644 index 00000000..49381c1f --- /dev/null +++ b/resources/interface.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Model/InterfaceItem.ts b/src/Model/InterfaceItem.ts new file mode 100644 index 00000000..1ffe0c51 --- /dev/null +++ b/src/Model/InterfaceItem.ts @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import { TreeItem } from "vscode"; + +export class InterfaceItem extends TreeItem { + constructor(name: string, public readonly iconPath: string) { + super(name); + this.contextValue = "interface"; + } +} diff --git a/src/Nodes/DeviceNode.ts b/src/Nodes/DeviceNode.ts index 4bf98d6d..9a37500c 100644 --- a/src/Nodes/DeviceNode.ts +++ b/src/Nodes/DeviceNode.ts @@ -7,6 +7,7 @@ import { DeviceItem } from "../Model/DeviceItem"; import { TelemetryClient } from "../telemetryClient"; import { DistributedTracingLabelNode } from "./DistributedTracingLabelNode"; import { INode } from "./INode"; +import { InterfaceLabelNode } from "./InterfaceLabelNode"; import { ModuleLabelNode } from "./ModuleLabelNode"; export class DeviceNode implements INode { @@ -24,6 +25,7 @@ export class DeviceNode implements INode { public async getChildren(context: vscode.ExtensionContext, iotHubConnectionString: string): Promise { let nodeList: INode[] = []; nodeList.push(new ModuleLabelNode(this)); + nodeList.push(new InterfaceLabelNode(this)); if (this.deviceItem.contextValue === "device" && iotHubConnectionString.toLowerCase().indexOf("azure-devices.cn;") < 0) { nodeList.push(new DistributedTracingLabelNode(this)); } diff --git a/src/Nodes/InterfaceLabelNode.ts b/src/Nodes/InterfaceLabelNode.ts new file mode 100644 index 00000000..541e777c --- /dev/null +++ b/src/Nodes/InterfaceLabelNode.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import axios from "axios"; +import * as path from "path"; +import * as vscode from "vscode"; +import { Constants } from "../constants"; +import { TelemetryClient } from "../telemetryClient"; +import { Utility } from "../utility"; +import { DeviceNode } from "./DeviceNode"; +import { InfoNode } from "./InfoNode"; +import { INode } from "./INode"; +import { InterfaceNode } from "./InterfaceNode"; + +export class InterfaceLabelNode implements INode { + constructor(public deviceNode: DeviceNode) { + } + + public getTreeItem(): vscode.TreeItem { + return { + label: "Interfaces", + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + contextValue: "interfaces-label", + }; + } + + public async getChildren(context: vscode.ExtensionContext, iotHubConnectionString: string): Promise { + TelemetryClient.sendEvent(Constants.IoTHubAILoadInterfacesTreeStartEvent); + + try { + const interfaces = (await axios.request(Utility.generateIoTHubAxiosRequestConfig( + iotHubConnectionString, + `/digitalTwins/${this.deviceNode.deviceId}/interfaces?api-version=${Constants.IoTHubApiVersion}`, + "get", + ))).data; + TelemetryClient.sendEvent(Constants.IoTHubAILoadInterfacesTreeDoneEvent, { Result: "Success" }); + if (!interfaces || !interfaces.interfaces || Object.keys(interfaces.interfaces).length === 0) { + return [new InfoNode("No Interfaces")]; + } + return Object.keys(interfaces.interfaces).map((name) => new InterfaceNode(name, context.asAbsolutePath(path.join("resources", `interface.svg`)))); + } catch (err) { + TelemetryClient.sendEvent(Constants.IoTHubAILoadInterfacesTreeDoneEvent, { Result: "Fail", Message: err.message }); + return Utility.getErrorMessageTreeItems("interfaces", err.message); + } + } +} diff --git a/src/Nodes/InterfaceNode.ts b/src/Nodes/InterfaceNode.ts new file mode 100644 index 00000000..44160b79 --- /dev/null +++ b/src/Nodes/InterfaceNode.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +import * as vscode from "vscode"; +import { InterfaceItem } from "../Model/InterfaceItem"; +import { INode } from "./INode"; + +export class InterfaceNode implements INode { + constructor(private name: string, private iconPath: string) { + } + + public getTreeItem(): vscode.TreeItem { + return new InterfaceItem(this.name, this.iconPath); + } + + public getChildren(): INode[] { + return []; + } +} diff --git a/src/constants.ts b/src/constants.ts index 3e408e01..874e9214 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -123,6 +123,8 @@ export class Constants { public static CREATE_OPTIONS_MAX_CHUNKS = 8; public static StateKeySubsID = "subscriptionId"; public static StateKeyIoTHubID = "iothubid"; + public static IoTHubAILoadInterfacesTreeStartEvent = "AZ.LoadInterfacesTree.Start"; + public static IoTHubAILoadInterfacesTreeDoneEvent = "AZ.LoadInterfacesTree.Done"; public static DeleteLabel = "Delete"; public static DeleteMessage = "Are you sure you want to delete"; public static readonly DISTRIBUTED_TWIN_NAME: string = "azureiot*com^dtracing^1"; @@ -138,7 +140,7 @@ export class Constants { public static ShowIoTHubInfoKey = "showIoTHubInfo"; public static ShowConnectionStringInputBoxKey = "showConnectionStringInputBox"; - public static IoTHubApiVersion = "2018-06-30"; + public static IoTHubApiVersion = "2019-07-01-preview"; public static CodeTemplates = { [TemplateLanguage.CSharp]: { diff --git a/src/utility.ts b/src/utility.ts index 224a53ae..141051ae 100644 --- a/src/utility.ts +++ b/src/utility.ts @@ -20,6 +20,7 @@ import { INode } from "./Nodes/INode"; import { TelemetryClient } from "./telemetryClient"; import iothub = require("azure-iothub"); import { EventData } from "@azure/event-hubs"; +import { AxiosRequestConfig } from "axios"; import { IotHubDescription } from "azure-arm-iothub/lib/models"; import { AzureAccount } from "./azure-account.api"; import { CredentialStore } from "./credentialStore"; @@ -423,6 +424,18 @@ export class Utility { await Constants.ExtensionContext.globalState.update(Constants.StateKeyIoTHubID, ""); } + public static generateIoTHubAxiosRequestConfig(iotHubConnectionString: string, url: string, method: string, data?: any): AxiosRequestConfig { + return { + url, + method, + baseURL: `https://${Utility.getHostName(iotHubConnectionString)}`, + headers: { + Authorization: Utility.generateSasTokenForService(iotHubConnectionString), + }, + data, + }; + } + private static tryGetStringFromCharCode(source) { if (source instanceof Uint8Array) { try {