diff --git a/.editorconfig b/.editorconfig index 5a1f1df..a4f2817 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,3 +13,4 @@ max_line_length = 120 [*.html] indent_size = 4 tab_width = 4 +max_line_length = off diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b5438b..029c8fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,23 @@ All notable changes to the "azureblockchain" extension will be documented in this file. -## 0.1.0 -- Initial release -## 0.1.1 +## 0.1.3 -- Updated menu options +- various bug fixes +- moved logic app/function/flow generators out of Truffle build directory into their own directory +- refactoring of Welcome/Requirements pages ## 0.1.2 - doc and bug fixes for contract code generation +## 0.1.1 + +- Updated menu options + +## 0.1.0 + +- Initial release + diff --git a/package.json b/package.json index 905f455..e48b577 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "publisher": "AzBlockchain", "preview": true, "icon": "images/blockchain-service-logo.png", - "version": "0.1.2", + "version": "0.1.3", "repository": { "type": "git", "url": "https://github.com/Microsoft/vscode-azure-blockchain-ethereum" @@ -51,7 +51,6 @@ "onCommand:azureBlockchainService.connectConsortium", "onCommand:drizzle.generateSmartContractUI", "onCommand:azureBlockchainService.copyRPCEndpointAddress", - "onCommand:azureBlockchainService.copyAccessKey", "onCommand:azureBlockchainService.createConsortium", "onCommand:azureBlockchainService.disconnectConsortium" ], @@ -166,11 +165,6 @@ "title": "Copy RPC Endpoint Address", "category": "Azure Blockchain" }, - { - "command": "azureBlockchainService.copyAccessKey", - "title": "Copy Access Key", - "category": "Azure Blockchain" - }, { "command": "azureBlockchainService.createConsortium", "title": "Create Azure Blockchain Service", @@ -178,7 +172,7 @@ }, { "command": "azureBlockchainService.disconnectConsortium", - "title": "Disconnect Consortium", + "title": "Disconnect", "category": "Azure Blockchain" }, { @@ -238,10 +232,6 @@ "when": "false", "command": "azureBlockchainService.copyRPCEndpointAddress" }, - { - "when": "false", - "command": "azureBlockchainService.copyAccessKey" - }, { "when": "false", "command": "azureBlockchainService.disconnectConsortium" @@ -308,11 +298,6 @@ "when": "view == AzureBlockchain && viewItem == consortium", "group": "azureBlockchain-0@0" }, - { - "command": "azureBlockchainService.copyAccessKey", - "when": "view == AzureBlockchain && viewItem == consortium", - "group": "azureBlockchain-0@1" - }, { "command": "azureBlockchainService.startGanacheServer", "when": "view == AzureBlockchain && viewItem == localconsortium && azureBlockchainService:isGanacheRunning == false", @@ -326,7 +311,7 @@ { "command": "azureBlockchainService.disconnectConsortium", "when": "view == AzureBlockchain && viewItem == consortium", - "group": "azureBlockchain-1@1" + "group": "azureBlockchain-0@1" }, { "command": "azureBlockchainService.copyRPCEndpointAddress", @@ -336,7 +321,7 @@ { "command": "azureBlockchainService.disconnectConsortium", "when": "view == AzureBlockchain && viewItem == localconsortium", - "group": "azureBlockchain-1@1" + "group": "azureBlockchain-0@1" } ], "explorer/context": [ diff --git a/resources/welcome/index.html b/resources/welcome/index.html index 1329300..8a36700 100644 --- a/resources/welcome/index.html +++ b/resources/welcome/index.html @@ -6,152 +6,157 @@ -
-
-

Azure Blockchain Development Kit

-

Interact with your private, public, and local ledgers. Develop smart contracts, test and integrate with business back ends with Logic Apps and Flow.

-
-
-
-
-
-
-

Quick Starts

-
-
-

Create a New Azure Blockchain Service

-

Click below button to create an Azure Blockchain Service

- Create Azure Blockchain Service -
-
-
-
-

Connect to an Existing Consortium

-

Click below button to connect to an Azure, public or local consortium.

- Connect to Consortium -
-
-
-
-

Say Hello to Azure Blockchain in 1 Minute

+
+
+

Azure Blockchain Development Kit

+

Interact with your private, public, and local ledgers. Develop smart contracts, test and integrate with business back ends with Logic Apps and Flow.

+
+
+
+
+
+
+

Quick Starts

+
-
-
-
-

Create a New Azure Blockchain Service

-
-
- To create a new Azure Blockchain Service if you don't have one. -
    -
  1. -
    From the Command Palette, or Azure Blockchain tab, click on "Create Azure Blockchain Service
    - -
  2. -
- If you have an existing Azure Blockchain Service, you may connect to it. -
    -
  1. -
    From the Command Palette, or Azure Blockchain tab, click on "Connect to Consortium" and select Azure Blockchain Service
    - -
  2. -
-
-
-
-
-
-

Create a Smart Contract and Project Folder

-
-
- To create a basic smart contract folder structure and contract. -
    -
  1. -
    From the Command Palette click on "New Solidity Project"
    - -
  2. -
-
-
-
-
-
-

Interact with your Smart Contract

-
-
- To interact with your contract -
    -
  1. -
    Coming soon!
    - -
  2. -
-
-
+

Create a New Azure Blockchain Service

+

Click below button to create an Azure Blockchain Service

+ Create Azure Blockchain Service
-

Learn more about our partners

- +
+
+

Connect to an Existing Consortium

+

Click below button to connect to an Azure, public or local consortium.

+ Connect to Consortium
-
+
+

Say Hello to Azure Blockchain in 1 Minute

- Having trouble with the extension? - - Browse the documentation - - Report an issue +
+
+
+

Create a New Azure Blockchain Service

+
+
+ To create a new Azure Blockchain Service if you don't have one. +
    +
  1. +
    From the Command Palette, or Azure Blockchain tab, click on "Create Azure Blockchain Service"
    + +
  2. +
+ If you have an existing Azure Blockchain Service, you may connect to it. +
    +
  1. +
    From the Command Palette, or Azure Blockchain tab, click on "Connect to Consortium" and select Azure Blockchain Service
    + +
  2. +
+
+
+
+
+
+

Create a Smart Contract and Project Folder

+
+
+ To create a basic smart contract folder structure and contract. +
    +
  1. +
    From the Command Palette click on "New Solidity Project"
    + +
  2. +
+
+
+
+
+
+

Interact with your Smart Contract

+
+
+ To interact with your contract +
    +
  1. +
    Coming soon!
    +
  2. +
+
+
-
+

Learn more about our partners

+ +
- + - - - +
+ + + diff --git a/resources/welcome/main.css b/resources/welcome/main.css index 68474fe..fd2679b 100644 --- a/resources/welcome/main.css +++ b/resources/welcome/main.css @@ -74,13 +74,12 @@ header p { display: flex; flex-direction: row; align-content: stretch; - min-width: 620px; + min-width: 660px; } #main-content { max-width: 740px; - /* float: left; */ - padding-right: 40px; + margin-right: 40px; min-width: 400px; } @@ -243,9 +242,12 @@ header p { width: 40px; } +.showOnStartup { + margin: 40px 0; +} + footer { - padding-top: 60px; - padding-bottom: 40px; + margin: 40px 0; text-align: center } @@ -260,7 +262,6 @@ footer { align-items: stretch; align-content: center; min-width: 400px; - max-width: 600px; } .required-app { @@ -269,13 +270,18 @@ footer { text-align: center; align-items: center; align-content: space-between; - flex: 0 1 185px; + flex: 1; margin-top: 25px; } +.required-app:not(:last-child) { + margin-right: 25px; +} + .required-app img { flex: 1; - width: 60px; + max-width: 60px; + max-height: 60px; } .required-app div { @@ -347,11 +353,14 @@ footer { align-items: stretch; align-content: center; min-width: 400px; - max-width: 600px; } .partners > a { - flex: 0 1 185px; + flex: 1; +} + +.partners > a:not(:last-child) { + margin-right: 15px; } .specific-partner { @@ -363,6 +372,10 @@ footer { flex: 1; } +.specific-partner > div:last-child { + height: 30px; +} + .specific-partner > div { text-align: center; } diff --git a/resources/welcome/main.js b/resources/welcome/main.js index efadca7..d530aa5 100644 --- a/resources/welcome/main.js +++ b/resources/welcome/main.js @@ -36,14 +36,18 @@ function main() { }); $(document).ready(() => { - vscode.postMessage({ command: 'documentready'}); + vscode.postMessage({ command: 'documentReady'}); + }); + + $('#showOnStartup').change(function() { + vscode.postMessage({ command: 'toggleShowPage', value: this.checked}); }); window.addEventListener('message', (event) => { const message = event.data; // The JSON data our extension sent - if (message.versions) { - const versions = message.versions; + if (message.command === 'versions') { + const versions = message.value; if (Array.isArray(versions)) { versions.forEach((version) => { const element = $(`#${version.app}`); @@ -53,5 +57,8 @@ function main() { }); } } + if (message.command === 'showOnStartup') { + $('#showOnStartup').attr('checked', !!message.value); + } }); } diff --git a/resources/welcome/prereqs.html b/resources/welcome/prereqs.html index f17e84c..1cee6cc 100644 --- a/resources/welcome/prereqs.html +++ b/resources/welcome/prereqs.html @@ -1,60 +1,73 @@ - - - - -
-
-
-
-

Required apps

-
This extension requires your development machine have the following tools already installed. These cannot be installed directly by the extension. - If you do not have these tools already installed, the links below will take you to the download page. -
    -
  • Node JS
  • -
  • Git
  • -
  • Python
  • -
-

Click here to learn more about this extension, and view the users guide

-
-
- -
Required version: 10.15.0
- Install Node.js -
-
- -
Required version: 2.10.0 or higher
- Install Git -
-
- -
Required version: 2.7.15
- Install Python -
-

At this time, while the extension is still in public-preview, you will also need to install the Truffle Suite of developer tools. Click the links below to install the Truffle tools directly from this extension

-
- -
Required version: 6.4.1
- Install NPM -
-
- -
Required version: 5.0.0
- Install Truffle Suite -
-
- -
Required version: 6.0.0
- Install Ganache CLI -
+ + + + +
+
+
+
+

Required apps

+
+ This extension requires your development machine have the following tools already installed. These cannot be installed directly by the extension. + If you do not have these tools already installed, the links below will take you to the download page +
    +
  • Node JS
  • +
  • Git
  • +
  • Python
  • +
+

+ Click here to learn more about this extension, and view the users guide +

+
+
+ +
Required version: 10.15.0
+ Install Node.js +
+
+ +
Required version: 2.10.0
+ Install Git +
+
+ +
Required version: 2.7.15
+ Install Python +
+
+

+ At this time, while the extension is still in public-preview, you will also need to install the Truffle Suite of developer tools. + Click the links below to install the Truffle tools directly from this extension +

+
+
+ +
Required version: 6.4.1
+ Install NPM +
+
+ +
Required version: 5.0.0
+ Install Truffle Suite +
+
+ +
Required version: 6.0.0
+ Install Ganache CLI
+ +

+ + +

- - +
+ + - \ No newline at end of file + diff --git a/src/ARMBlockchain/AzureBlockchainServiceClient.ts b/src/ARMBlockchain/AzureBlockchainServiceClient.ts index b2352c4..27ecf0f 100644 --- a/src/ARMBlockchain/AzureBlockchainServiceClient.ts +++ b/src/ARMBlockchain/AzureBlockchainServiceClient.ts @@ -53,7 +53,7 @@ export class AzureBlockchainServiceClient extends AzureServiceClient { Output.outputLine(Constants.outputChannel.azureBlockchainServiceClient, err.message); } - env.openExternal(Uri.parse(`${Constants.azurePortalBasUri}/resource/${urlDetailsOfConsortium}`)); + env.openExternal(Uri.parse(`${Constants.azureResourceExplorer.portalBasUri}/resource/${urlDetailsOfConsortium}`)); }); } @@ -98,7 +98,7 @@ export class AzureBlockchainServiceClient extends AzureServiceClient { callback: (error: Error | null, result?: any) => void, ): Promise { // @ts-ignore - return await this.pipeline(httpRequest, (err: ServiceError, response: IncomingMessage, responseBody: string) => { + return this.pipeline(httpRequest, (err: ServiceError, response: IncomingMessage, responseBody: string) => { if (err) { window.showErrorMessage(err.message); return callback(err); diff --git a/src/AzureBlockchain.ts b/src/AzureBlockchain.ts index 3445dc2..8491f7b 100644 --- a/src/AzureBlockchain.ts +++ b/src/AzureBlockchain.ts @@ -11,11 +11,6 @@ export namespace AzureBlockchain { await AzureBlockchain.addDataInClipboard(Constants.rpcEndpointAddress, rpcEndpointAddress); } - export async function copyAccessKey(consortiumNode: ConsortiumView): Promise { - const accessKey = await consortiumNode.getAccessKey(); - await AzureBlockchain.addDataInClipboard(Constants.accessKey, accessKey); - } - export async function addDataInClipboard(typeOfData: string, data?: string | null) { if (data) { await env.clipboard.writeText(data); diff --git a/src/ConsortiumResourceExplorer.ts b/src/ConsortiumResourceExplorer.ts index b0e03c9..b04180f 100644 --- a/src/ConsortiumResourceExplorer.ts +++ b/src/ConsortiumResourceExplorer.ts @@ -1,37 +1,23 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; -import { commands, extensions, ProgressLocation, QuickPickItem, window } from 'vscode'; +import { ProgressLocation, QuickPickItem, window } from 'vscode'; import { AzureBlockchainServiceClient, IAzureMemberDto, ICreateQuorumMember } from './ARMBlockchain'; -import { AzureAccount } from './azure-account.api'; import { Constants } from './Constants'; import { showInputBox, showQuickPick } from './helpers'; -import { AzureConsortium, LocationItem, Member, ResourceGroupItem, SubscriptionItem, TransactionNode } from './Models'; -import { getAzureRegions } from './Regions'; +import { AzureConsortium, Member, ResourceGroupItem, SubscriptionItem, TransactionNode } from './Models'; +import { ConsortiumItem } from './Models/ConsortiumItem'; +import { ResourceExplorerAndGenerator } from './ResourceExplorerAndGenerator'; import { WestlakeInputValidator } from './validators/WestlakeInputValidator'; -export class ConsortiumResourceExplorer { - private readonly _accountApi: AzureAccount; - - constructor() { - this._accountApi = extensions.getExtension('ms-vscode.azure-account')!.exports; - } - - public async selectConsortium(childrenFilters?: string[]): Promise { +export class ConsortiumResourceExplorer extends ResourceExplorerAndGenerator { + public async selectOrCreateConsortium(childrenFilters?: string[]): Promise { await this.waitForLogin(); const subscriptionItem = await this.getOrSelectSubscriptionItem(); - const resourceGroupItem = await this.getOrSelectResourceGroup(subscriptionItem); - return await this.getOrSelectConsortiumItem(subscriptionItem, resourceGroupItem, childrenFilters); - } + const resourceGroupItem = await this.getOrCreateResourceGroupItem(subscriptionItem); - public async createConsortium(): Promise { - await this.waitForLogin(); - - const subscriptionItem = await this.getOrSelectSubscriptionItem(); - const resourceGroupItem = await this.getOrCreateResourceGroup(subscriptionItem); - return await this.createConsortiumItem(subscriptionItem, resourceGroupItem); + return this.getOrCreateConsortiumItem(subscriptionItem, resourceGroupItem, childrenFilters); } public async getAccessKeys(consortium: AzureConsortium): Promise { @@ -64,10 +50,10 @@ export class ConsortiumResourceExplorer { subscriptionItem.session.credentials, subscriptionItem.subscriptionId, resourceGroupItem.label, - Constants.requestBaseUri, - Constants.requestApiVersion, + Constants.azureResourceExplorer.requestBaseUri, + Constants.azureResourceExplorer.requestApiVersion, { - acceptLanguage: Constants.requestAcceptLanguage, + acceptLanguage: Constants.azureResourceExplorer.requestAcceptLanguage, filters: [], generateClientRequestId: true, longRunningOperationRetryTimeout: 30, @@ -80,175 +66,82 @@ export class ConsortiumResourceExplorer { ); } - private async getResourceClient(subscriptionItem: SubscriptionItem) - : Promise { - return new ResourceManagementClient.ResourceManagementClient( - subscriptionItem.session.credentials, - subscriptionItem.subscriptionId, - subscriptionItem.session.environment.resourceManagerEndpointUrl, - ); - } - - private async getOrSelectSubscriptionItem(): Promise { - return showQuickPick( - await this.loadSubscriptionItems(), - { placeHolder: Constants.placeholders.selectSubscription, ignoreFocusOut: true }, - ); - } - - private async loadSubscriptionItems(): Promise { - await this._accountApi.waitForFilters(); - - const subscriptionItems = this._accountApi.filters - .map((filter) => new SubscriptionItem( - filter.subscription.displayName || '', - filter.subscription.subscriptionId || '', - filter.session, - )); - - if (subscriptionItems.length === 0) { - throw new Error(Constants.errorMessageStrings.NoSubscriptionFoundClick); - } - - return subscriptionItems; - } - - private async getOrSelectResourceGroup(subscriptionItem: SubscriptionItem): Promise { - return showQuickPick( - this.getResourceGroupItems(subscriptionItem), - { placeHolder: Constants.placeholders.selectResourceGroup, ignoreFocusOut: true }, - ); - } - - private async getOrCreateResourceGroup(subscriptionItem: SubscriptionItem): Promise { - const createGroupItem: QuickPickItem = { - label: '$(plus) Create Resource Group', - }; - const items: QuickPickItem[] = []; - items.push(createGroupItem); - items.push(...await this.getResourceGroupItems(subscriptionItem)); + private async getOrCreateConsortiumItem( + subscriptionItem: SubscriptionItem, + resourceGroupItem: ResourceGroupItem, + excludedItems?: string[], + ): Promise { const pick = await showQuickPick( - items, - { placeHolder: Constants.placeholders.selectResourceGroup, ignoreFocusOut: true }, - ); - - if (pick instanceof (ResourceGroupItem)) { - return pick; - } else { - return this.createResourceGroup(subscriptionItem); - } - } - - private async createResourceGroup(subscriptionItem: SubscriptionItem): Promise { - const resourceGroupName = await showInputBox({ - ignoreFocusOut: true, - placeHolder: 'Resource Group Name', - prompt: 'Provide a resource group name', - validateInput: WestlakeInputValidator.validateNames, - }); - - const locationItem = await showQuickPick( - this.getLocationItems(subscriptionItem), - { placeHolder: 'Select a location to create your Resource Group in...', ignoreFocusOut: true }, - ); - - return window.withProgress({ - location: ProgressLocation.Notification, - title: `Creating resource group '${resourceGroupName}'`, - }, async () => { - if (subscriptionItem.subscriptionId === undefined) { - throw new Error(Constants.errorMessageStrings.NoSubscriptionFound); - } else { - const resourceManagementClient = await this.getResourceClient(subscriptionItem); - const resourceGroup = await resourceManagementClient.resourceGroups.createOrUpdate( - resourceGroupName, - { location: locationItem.description }, - ); - return new ResourceGroupItem(resourceGroup.name, resourceGroup.location); - } - }); - } - - private async getResourceGroupItems(subscriptionItem: SubscriptionItem): Promise { - const resourceManagementClient = await this.getResourceClient(subscriptionItem); - const resourceGroups = await resourceManagementClient.resourceGroups.list(); - return resourceGroups.map((resourceGroup) => new ResourceGroupItem(resourceGroup.name, resourceGroup.location)); - } - - private async getLocationItems(subscriptionItem: SubscriptionItem): Promise { - const subscriptionClient = new SubscriptionClient.SubscriptionClient( - subscriptionItem.session.credentials, - subscriptionItem.session.environment.resourceManagerEndpointUrl, - ); - - const locations = await subscriptionClient.subscriptions.listLocations(subscriptionItem.subscriptionId); - return locations.map((location: SubscriptionModels.Location) => new LocationItem(location)); - } - - private async getOrSelectConsortiumItem( - subscriptionItem: SubscriptionItem, - resourceGroupItem: ResourceGroupItem, - childrenFilters?: string[]) - : Promise { - const consortiumItems = this.getNewConsortiumItems(subscriptionItem, resourceGroupItem, childrenFilters); - - return showQuickPick(consortiumItems, + this.getConsortiumItems(subscriptionItem, resourceGroupItem, excludedItems), { placeHolder: Constants.placeholders.selectConsortium, ignoreFocusOut: true }); + + if (pick instanceof ConsortiumItem) { + return this.getAzureConsortium(pick, subscriptionItem, resourceGroupItem); + } else { + return this.createAzureConsortium(subscriptionItem, resourceGroupItem); + } } - private async getNewConsortiumItems( + private async getConsortiumItems( subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem, - childrenFilters?: string[]) - : Promise { - const consortiumItems = await this.loadConsortiumItems(subscriptionItem, resourceGroupItem); + excludedItems?: string[], + ): Promise { + const items: QuickPickItem[] = []; + const createConsortiumItem: QuickPickItem = { label: '$(plus) Create Consortium' }; + const consortiumItems = await this.loadConsortiumItems(subscriptionItem, resourceGroupItem, excludedItems); - if (childrenFilters) { - return consortiumItems.filter((item) => !childrenFilters.includes(item.label)); - } + items.push(createConsortiumItem, ...consortiumItems); - return consortiumItems; + return items; } - private async loadConsortiumItems(subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem) - : Promise { + private async loadConsortiumItems( + subscriptionItem: SubscriptionItem, + resourceGroupItem: ResourceGroupItem, + excludedItems: string[] = [], + ): Promise { const client = await this.getClient(subscriptionItem, resourceGroupItem); - const members: IAzureMemberDto[] = await client.memberResource.getListMember(); - if (!members.length) { - throw new Error(`No members found in resource group ${resourceGroupItem.label}.`); - } - - const consortiumItems = members - .map((member) => new AzureConsortium( + return members + .map((member) => new ConsortiumItem( member.properties.consortium, subscriptionItem.subscriptionId, resourceGroupItem.label, member.name, member.properties.dns, )) + .filter((item) => !excludedItems.includes(item.label)) .sort((a, b) => a.label.localeCompare(b.label)); - - for (const consortium of consortiumItems) { - const filterMembers = members.filter((x: IAzureMemberDto) => x.properties.consortium === consortium.label); - - for (const member of filterMembers) { - const transactionNodes = await client.transactionNodeResource.getListTransactionNode(member.name); - const memberItem = new Member(member.name); - - await consortium.setChildren([ - memberItem, - ...transactionNodes.map((transactionNode) => { - return new TransactionNode(transactionNode.name, transactionNode.properties.dns); - }), - ]); - } - } - - return consortiumItems; } - private async createConsortiumItem(subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem) + private async getAzureConsortium( + consortiumItems: ConsortiumItem, + subscriptionItem: SubscriptionItem, + resourceGroupItem: ResourceGroupItem, + ): Promise { + const client = await this.getClient(subscriptionItem, resourceGroupItem); + const transactionNodes = await client.transactionNodeResource.getListTransactionNode(consortiumItems.memberName); + const memberItem = new Member(consortiumItems.memberName); + + const azureConsortium = new AzureConsortium( + consortiumItems.consortiumName, + consortiumItems.subscriptionId, + consortiumItems.resourcesGroup, + consortiumItems.memberName, + consortiumItems.url, + ); + await azureConsortium.setChildren([ + memberItem, + ...transactionNodes.map((transactionNode) => { + return new TransactionNode(transactionNode.name, transactionNode.properties.dns); + }), + ]); + + return azureConsortium; + } + + private async createAzureConsortium(subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem) : Promise { const client = await this.getClient(subscriptionItem, resourceGroupItem); @@ -265,7 +158,7 @@ export class ConsortiumResourceExplorer { }); const protocol = await showQuickPick( - [{label: 'Quorum'}], + [{ label: 'Quorum' }], { ignoreFocusOut: true, placeHolder: Constants.paletteWestlakeLabels.selectConsortiumProtocol, @@ -287,7 +180,7 @@ export class ConsortiumResourceExplorer { }); const region = await showQuickPick( - getAzureRegions(), + this.getLocationItems(subscriptionItem), { placeHolder: Constants.paletteWestlakeLabels.selectConsortiumRegion, ignoreFocusOut: true }, ); @@ -296,24 +189,16 @@ export class ConsortiumResourceExplorer { consortiumName, consortiumPassword, protocol: protocol.label, - region: region.key, + region: region.description, }; - await client.consortiumResource.createConsortium(memberName, bodyParams); + return window.withProgress({ + location: ProgressLocation.Window, + title: Constants.statusBarMessages.creatingConsortium, + }, async () => { + await client.consortiumResource.createConsortium(memberName, bodyParams); - return new AzureConsortium(consortiumName, subscriptionItem.subscriptionId, resourceGroupItem.label, memberName); - } - - private async waitForLogin(): Promise { - let result = await this._accountApi.waitForLogin(); - if (!result) { - await commands.executeCommand('azure-account.askForLogin'); - result = await this._accountApi.waitForLogin(); - if (!result) { - throw new Error(Constants.errorMessageStrings.WaitForLogin); - } - } - - return true; + return new AzureConsortium(consortiumName, subscriptionItem.subscriptionId, resourceGroupItem.label, memberName); + }); } } diff --git a/src/Constants.ts b/src/Constants.ts index 23f5676..9784ade 100644 --- a/src/Constants.ts +++ b/src/Constants.ts @@ -8,6 +8,9 @@ import { ExtensionContext } from 'vscode'; export class Constants { public static extensionContext: ExtensionContext; public static temporaryDirectory = ''; + public static logicAppOutputDir = 'generatedLogicApp'; + public static flowAppOutputDir = 'generatedFlowApp'; + public static azureFunctionOutputDir = 'generatedAzureFunction'; public static extensionId = ''; public static extensionVersion = ''; @@ -23,7 +26,8 @@ export class Constants { telemetryClient: 'Telemetry Client', }; - public static isWelcomePageShown = 'isWelcomePageShown'; + public static showOnStartupWelcomePage = 'showOnStartupWelcomePage'; + public static showOnStartupRequirementsPage = 'showOnStartupRequirementsPage'; public static defaultTruffleBox = 'Azure-Samples/Blockchain-Ethereum-Template'; public static tempPath = 'tempPath'; @@ -45,6 +49,11 @@ export class Constants { truffle: '5.0.0', }; + public static webViewPages = { + requirements: 'Azure Blockchain Development Kit - Preview', + welcome: 'Welcome to Azure Blockchain', + }; + public static contractExtension = { json: '.json', sol: '.sol', @@ -82,8 +91,6 @@ export class Constants { gasPrice: 100000000000, }; - public static telemetry = 'Telemetry'; - public static networkName = { azure: 'Azure Blockchain Service', local: 'Local Network', @@ -110,6 +117,7 @@ export class Constants { enterTruffleBoxName: 'Enter pre-built Truffle project', enterWestlakeUrl: 'Enter Westlake url', enterYourGanacheUrl: 'Enter your Ganache url', + provideResourceGroupName: 'Provide a resource group name', selectConsortiumProtocol: 'Select protocol', selectConsortiumRegion: 'Select region', selectResourceGroup: 'Select resource group', @@ -138,11 +146,13 @@ export class Constants { deployedUrlStructure: 'host:port', generateMnemonic: 'Generate mnemonic', pasteMnemonic: 'Paste mnemonic', + resourceGroupName: 'Resource Group Name', selectConsortium: 'Select consortium', selectDeployDestination: 'Select deploy destination', selectLedgerEventsDestination: 'Select ledger events destination', selectNewProjectPath: 'Select new project path', - selectResourceGroup: 'Select a resource group to select or create your consortium in...', + selectResourceGroup: 'Select a resource group', + selectRgLocation: 'Select a location to create your Resource Group in...', selectSubscription: 'Select subscription', selectTypeOfMnemonic: 'Select type of mnemonic', selectTypeOfSolidityProject: 'Select type of solidity project', @@ -170,22 +180,6 @@ export class Constants { runningCommand: 'Running command', }; - public static mnemonicMessages = { - invalidStorageTypeSelected: 'Invalid storage type selected', - whereStored: 'Where are your mnemonics stored?', - }; - - public static mnemonicTypeQuickPick = { - text: { - local: 'In local file', - truffle: 'In truffle-config', - }, - types: { - local: 'local', - truffle: 'truffle', - }, - }; - public static typeOfSolidityProject = { action: { emptyProject: 'createEmptyProject', @@ -210,10 +204,15 @@ export class Constants { }, }; - public static requestApiVersion = '2018-06-01-preview'; - public static requestAcceptLanguage = 'en-US'; - public static requestBaseUri = 'https://management.azure.com'; - public static azurePortalBasUri = 'https://portal.azure.com/#@microsoft.onmicrosoft.com'; + public static statusBarMessages = { + buildingContracts: 'Building contracts', + checkingRequirementDependencies: 'Checking requirement dependencies version', + creatingConsortium: 'Creating new consortium', + creatingProject: 'Creating new project', + deployingContracts: (destination: string) => { + return `Deploying contracts to '${destination}'`; + }, + }; public static welcomePagePath = ''; public static requirementsPagePath = ''; @@ -221,11 +220,6 @@ export class Constants { public static dataCopied = ' copied to clipboard'; public static rpcEndpointAddress = 'RPCEndpointAddress'; - public static accessKey = 'AccessKey'; - - public static folderStrings = { - folderNotSelected: 'Folder not selected', - }; public static ganacheCommandStrings = { serverAlreadyRunning: 'Ganache server already running', @@ -249,6 +243,7 @@ export class Constants { public static errorMessageStrings = { ActionAborted: 'Action aborted', GitIsNotInstalled: 'Git is not installed', + NewProjectCreationFailed: 'Command createProject has failed.', NoSubscriptionFound: 'No subscription found.', NoSubscriptionFoundClick: 'No subscription found, click an Azure account ' + 'at the bottom left corner and choose Select All', @@ -263,10 +258,32 @@ export class Constants { newProjectCreationFinished: 'New project was created successfully', newProjectCreationStarted: 'New project creation is started', seeDetailsRequirementsPage: 'Please see details on the Requirements Page', - seeDetailsWelcomePage: 'Please see details on Welcome Page', + }; + + public static microservicesWorkflows = { + Data: 'Data', + Messaging: 'Messaging', + Reporting: 'Reporting', + Service: 'Service', + }; + + public static logicApp = { + AzureFunction: 'Azure Function', + FlowApp: 'Flow App', + LogicApp: 'Logic App', }; public static gitCommand = 'git'; + public static truffleCommand = 'truffle'; + + public static azureResourceExplorer = { + portalBasUri: 'https://portal.azure.com/#@microsoft.onmicrosoft.com', + providerName: 'Microsoft.Blockchain', + requestAcceptLanguage: 'en-US', + requestApiVersion: '2018-06-01-preview', + requestBaseUri: 'https://management.azure.com', + resourceType: 'blockchainMembers', + }; public static initialize(context: ExtensionContext) { this.extensionContext = context; diff --git a/src/Generators/AbiDeserialiser.ts b/src/Generators/AbiDeserialiser.ts index 28b0038..4c77de2 100644 --- a/src/Generators/AbiDeserialiser.ts +++ b/src/Generators/AbiDeserialiser.ts @@ -1,30 +1,30 @@ import './Nethereum.Generators.DuoCode'; function buildConstructor(item: any): Nethereum.Generators.Model.ConstructorABI { - var constructorItem = new Nethereum.Generators.Model.ConstructorABI(); + const constructorItem = new Nethereum.Generators.Model.ConstructorABI(); constructorItem.set_InputParameters(buildFunctionParameters(item.inputs)); return constructorItem; } function buildFunction(item: any): Nethereum.Generators.Model.FunctionABI { - var functionItem = new Nethereum.Generators.Model.FunctionABI(item.name, item.constant, false); + const functionItem = new Nethereum.Generators.Model.FunctionABI(item.name, item.constant, false); functionItem.set_InputParameters(buildFunctionParameters(item.inputs)); functionItem.set_OutputParameters(buildFunctionParameters(item.outputs)); return functionItem; } function buildEvent(item: any): Nethereum.Generators.Model.EventABI { - var eventItem = new Nethereum.Generators.Model.EventABI (item.name); + const eventItem = new Nethereum.Generators.Model.EventABI (item.name); eventItem.set_InputParameters(buildEventParameters(item.inputs)); return eventItem; } function buildFunctionParameters(items: any): Nethereum.Generators.Model.ParameterABI[] { - var parameterOrder = 0; - var parameters = []; - for (var i = 0, len = items.length; i < len; i++) { + let parameterOrder = 0; + const parameters = []; + for (let i = 0, len = items.length; i < len; i++) { parameterOrder = parameterOrder + 1; - var parameter = new Nethereum.Generators.Model.ParameterABI + const parameter = new Nethereum.Generators.Model.ParameterABI .ctor$1(items[i].type, items[i].name, parameterOrder); parameters.push(parameter); } @@ -32,11 +32,11 @@ function buildFunctionParameters(items: any): Nethereum.Generators.Model.Paramet } function buildEventParameters(items: any): Nethereum.Generators.Model.ParameterABI[] { - var parameterOrder = 0; - var parameters = []; - for (var i = 0, len = items.length; i < len; i++) { + let parameterOrder = 0; + const parameters = []; + for (let i = 0, len = items.length; i < len; i++) { parameterOrder = parameterOrder + 1; - var parameter = new Nethereum.Generators.Model.ParameterABI + const parameter = new Nethereum.Generators.Model.ParameterABI .ctor$1(items[i].type, items[i].name, parameterOrder); parameter.set_Indexed(items[i].indexed); parameters.push(parameter); @@ -46,25 +46,25 @@ function buildEventParameters(items: any): Nethereum.Generators.Model.ParameterA export function buildContract(abiStr: string): Nethereum.Generators.Model.ContractABI { const abi = JSON.parse(abiStr); - let functions = []; - let events = []; + const functions = []; + const events = []; let constructor = new Nethereum.Generators.Model.ConstructorABI(); - for (var i = 0, len = abi.length; i < len; i++) { - if (abi[i].type === "function") { + for (let i = 0, len = abi.length; i < len; i++) { + if (abi[i].type === 'function') { functions.push(buildFunction(abi[i])); } - if (abi[i].type === "event") { + if (abi[i].type === 'event') { events.push(buildEvent(abi[i])); } - if (abi[i].type === "constructor") { + if (abi[i].type === 'constructor') { constructor = buildConstructor(abi[i]); } } - let contract = new Nethereum.Generators.Model.ContractABI(); + const contract = new Nethereum.Generators.Model.ContractABI(); contract.set_Constructor(constructor); contract.set_Functions(functions); contract.set_Events(events); diff --git a/src/Generators/LogicAppGenerator.ts b/src/Generators/LogicAppGenerator.ts index f84b200..544b098 100644 --- a/src/Generators/LogicAppGenerator.ts +++ b/src/Generators/LogicAppGenerator.ts @@ -1,61 +1,43 @@ -import { ResourceManagementClient, ResourceModels } from 'azure-arm-resource'; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + import { mkdirp, readdir, readJson, writeFile } from 'fs-extra'; import * as path from 'path'; -import { commands, extensions, QuickPickItem, Uri, window } from 'vscode'; -import { AzureAccount } from '../azure-account.api'; +import { QuickPickItem, Uri, window } from 'vscode'; import { Constants } from '../Constants'; import { getWorkspaceRoot } from '../helpers'; import { showInputBox, showQuickPick } from '../helpers/userInteraction'; import { ResourceGroupItem, SubscriptionItem } from '../Models'; import { Output } from '../Output'; +import { ResourceExplorerAndGenerator } from '../ResourceExplorerAndGenerator'; import { buildContract } from './AbiDeserialiser'; import './Nethereum.Generators.DuoCode'; -export class LogicAppGenerator { - private readonly _accountApi: AzureAccount; - - constructor() { - this._accountApi = extensions.getExtension('ms-vscode.azure-account')!.exports; - } - +export class LogicAppGenerator extends ResourceExplorerAndGenerator { public async generateMicroservicesWorkflows(filePath: Uri | undefined): Promise { - return this.generateWorkflows('Service', filePath); + return this.generateWorkflows(Constants.microservicesWorkflows.Service, filePath); } public async generateDataPublishingWorkflows(filePath: Uri | undefined): Promise { - return this.generateWorkflows('Data', filePath); + return this.generateWorkflows(Constants.microservicesWorkflows.Data, filePath); } + public async generateEventPublishingWorkflows(filePath: Uri | undefined): Promise { - return this.generateWorkflows('Messaging', filePath); + return this.generateWorkflows(Constants.microservicesWorkflows.Messaging, filePath); } public async generateReportPublishingWorkflows(filePath: Uri | undefined): Promise { - return this.generateWorkflows('Reporting', filePath); + return this.generateWorkflows(Constants.microservicesWorkflows.Reporting, filePath); } private async generateWorkflows(workflowType: string, filePath: Uri | undefined): Promise { - const workspaceDir: Uri = Uri.parse(getWorkspaceRoot()); - const dirPath: string = workspaceDir.fsPath + '/build/contracts/'; + const dirPath: string = path.join(getWorkspaceRoot(), 'build/contracts'); if (filePath) { const fileName = path.basename(filePath.fsPath); const contractName = fileName.Remove(fileName.length - 4); - const compiledContractPath = dirPath + contractName + '.json'; - let picks: QuickPickItem[]; - - if (workflowType === 'Service') { - picks = [ - { label: 'Logic App' }, - { label: 'Flow App' }, - { label: 'Azure Function' }, - ]; - } else { - picks = [ - { label: 'Logic App' }, - { label: 'Flow App' }, - ]; - } - + const compiledContractPath = path.join(dirPath, `${contractName}.json`); + const picks = this.getLogicAppItems(workflowType); const serviceTypeSelection: string = (await showQuickPick(picks, { })).label; const serviceType: int = this.getServiceTypeFromString(serviceTypeSelection); const contractAddress: string = await showInputBox({ value: 'contract address' }); @@ -64,7 +46,6 @@ export class LogicAppGenerator { async (err2: Error, contents: any) => await this.handleContractJsonFile( err2, contents, - dirPath, contractAddress, subscriptionItem, resourceGroupItem, @@ -83,20 +64,7 @@ export class LogicAppGenerator { return; } - let picks: QuickPickItem[]; - if (workflowType === 'Service') { - picks = [ - { label: 'Logic App' }, - { label: 'Flow App' }, - { label: 'Azure Function' }, - ]; - } else { - picks = [ - { label: 'Logic App' }, - { label: 'Flow App' }, - ]; - } - + const picks = this.getLogicAppItems(workflowType); const serviceTypeSelection: string = (await showQuickPick(picks, { })).label; const serviceType: int = this.getServiceTypeFromString(serviceTypeSelection); const contractAddress: string = await showInputBox({ value: 'contract address' }); @@ -106,7 +74,6 @@ export class LogicAppGenerator { async (err2: Error, contents: any) => await this.handleContractJsonFile( err2, contents, - dirPath, contractAddress, subscriptionItem, resourceGroupItem, @@ -118,12 +85,27 @@ export class LogicAppGenerator { window.showInformationMessage('Generated the logic app!'); } + private getLogicAppItems(workflowType: string): QuickPickItem[] { + if (workflowType === Constants.microservicesWorkflows.Service) { + return [ + { label: Constants.logicApp.LogicApp }, + { label: Constants.logicApp.FlowApp }, + { label: Constants.logicApp.AzureFunction }, + ]; + } else { + return [ + { label: Constants.logicApp.LogicApp }, + { label: Constants.logicApp.FlowApp }, + ]; + } + } + private getServiceTypeFromString(serviceTypeSelection: string): int { - if (serviceTypeSelection === 'Logic App') { + if (serviceTypeSelection === Constants.logicApp.LogicApp) { return 1; - } else if (serviceTypeSelection === 'Flow App') { + } else if (serviceTypeSelection === Constants.logicApp.FlowApp) { return 0; - } else if (serviceTypeSelection === 'Azure Function') { + } else if (serviceTypeSelection === Constants.logicApp.AzureFunction) { return 2; } else { throw new Error('Service type string not valid'); @@ -133,7 +115,6 @@ export class LogicAppGenerator { private async handleContractJsonFile( err: Error, contents: any, - dirPath: string, contractAddress: string, subscriptionItem: SubscriptionItem, resourceGroupItem: ResourceGroupItem, @@ -144,7 +125,7 @@ export class LogicAppGenerator { return; } await this.createLogicAppFromAbi(contents, - dirPath, + this.getOutputDir(serviceType), contractAddress, subscriptionItem, resourceGroupItem.description, @@ -162,13 +143,13 @@ export class LogicAppGenerator { serviceType: int) { let generator; - if (workflowType === 'Service') { + if (workflowType === Constants.microservicesWorkflows.Service) { generator = new Nethereum.Generators.ServiceWorkflow.ServiceWorkflowProjectGenerator( buildContract(JSON.stringify(contract.abi)), contract.contractName, contract.bytecode, contract.contractName, - contract.contractName + '.Service', + contract.contractName + `.${workflowType}`, dirPath, '/', serviceType, @@ -179,13 +160,13 @@ export class LogicAppGenerator { location, '', ); - } else if (workflowType === 'Data') { + } else if (workflowType === Constants.microservicesWorkflows.Data) { generator = new Nethereum.Generators.DataWorkflow.DataWorkflowProjectGenerator( buildContract(JSON.stringify(contract.abi)), contract.contractName, contract.bytecode, contract.contractName, - contract.contractName + '.Data', + contract.contractName + `.${workflowType}`, dirPath, '/', serviceType, @@ -195,7 +176,7 @@ export class LogicAppGenerator { location, JSON.stringify(contract.abi), ); - } else if (workflowType === 'Messaging') { + } else if (workflowType === Constants.microservicesWorkflows.Messaging) { const topicName: string = await showInputBox({ value: 'topic name' }); const picks: QuickPickItem[] = [ { label: 'Service Bus' }, @@ -208,7 +189,7 @@ export class LogicAppGenerator { contract.contractName, contract.bytecode, contract.contractName, - contract.contractName + '.Messaging', + contract.contractName + `.${workflowType}`, dirPath, '/', serviceType, @@ -220,13 +201,13 @@ export class LogicAppGenerator { topicName, this.getMessagingType(messagingType), ); - } else if (workflowType === 'Reporting') { + } else if (workflowType === Constants.microservicesWorkflows.Reporting) { generator = new Nethereum.Generators.ReportingWorkflow.ReportingWorkflowProjectGenerator( buildContract(JSON.stringify(contract.abi)), contract.contractName, contract.bytecode, contract.contractName, - contract.contractName + '.Reporting', + contract.contractName + `.${workflowType}`, dirPath, '/', serviceType, @@ -268,65 +249,25 @@ export class LogicAppGenerator { }); } + private getOutputDir(serviceType: int): string { + switch (serviceType) { + case 0: + return path.join(getWorkspaceRoot(), Constants.flowAppOutputDir); + case 1: + return path.join(getWorkspaceRoot(), Constants.logicAppOutputDir); + case 2: + return path.join(getWorkspaceRoot(), Constants.azureFunctionOutputDir); + default: + throw new Error('Invalid service type.'); + } + } + private async selectSubscriptionAndResourceGroup(): Promise<[SubscriptionItem, ResourceGroupItem]> { await this.waitForLogin(); const subscriptionItem = await this.getOrSelectSubscriptionItem(); - const resourceGroupItem = await this.getOrCreateResourceGroup(subscriptionItem); + const resourceGroupItem = await this.getOrCreateResourceGroupItem(subscriptionItem); return [subscriptionItem, resourceGroupItem]; } - - private async getOrSelectSubscriptionItem(): Promise { - return await showQuickPick( - await this.loadSubscriptionItems(), - { placeHolder: Constants.placeholders.selectSubscription, ignoreFocusOut: true }, - ); - } - - private async loadSubscriptionItems(): Promise { - await this._accountApi.waitForFilters(); - - const subscriptionItems = this._accountApi.filters - .map((filter: any) => new SubscriptionItem(filter.subscription.displayName, - filter.subscription.subscriptionId, filter.session)); - - if (subscriptionItems.length === 0) { - throw new Error('No subscription found, click an Azure account at the \ - bottom left corner and choose Select All.'); - } - - return subscriptionItems; - } - - private async getOrCreateResourceGroup(subscriptionItem: SubscriptionItem): Promise { - return await showQuickPick( - this.getResourceGroupItems(subscriptionItem), - { placeHolder: Constants.placeholders.selectResourceGroup, ignoreFocusOut: true }, - ); - } - - private async getResourceGroupItems(subscriptionItem: SubscriptionItem): Promise { - // @ts-ignore - const resourceManagementClient = new ResourceManagementClient( - subscriptionItem.session.credentials, - subscriptionItem.subscriptionId, - subscriptionItem.session.environment.resourceManagerEndpointUrl, - ); - const resourceGroups = await resourceManagementClient.resourceGroups.list(); - return resourceGroups.map((resourceGroup: ResourceModels.ResourceGroup) => - new ResourceGroupItem(resourceGroup.name, resourceGroup.location)); - } - private async waitForLogin(): Promise { - let result = await this._accountApi.waitForLogin(); - if (!result) { - await commands.executeCommand('azure-account.askForLogin'); - result = await this._accountApi.waitForLogin(); - if (!result) { - throw new Error(Constants.errorMessageStrings.WaitForLogin); - } - } - - return true; - } } diff --git a/src/Models/AzureConsortium.ts b/src/Models/AzureConsortium.ts index a3839c3..6bd49bf 100644 --- a/src/Models/AzureConsortium.ts +++ b/src/Models/AzureConsortium.ts @@ -61,13 +61,9 @@ export class AzureConsortium extends ProtectedConsortium { url.port = `${Constants.defaultAzureBSPort}`; } - return url.origin; - } - - public async getAccessKey(): Promise { const consortiumResourceExplorer = new ConsortiumResourceExplorer(); const keys = await consortiumResourceExplorer.getAccessKeys(this); - return keys ? keys[0] : ''; + return keys ? `${url.origin}/${keys[0]}` : url.origin; } public toJSON(): { [p: string]: any } { diff --git a/src/Models/Consortium.ts b/src/Models/Consortium.ts index 939d908..5e7fc4e 100644 --- a/src/Models/Consortium.ts +++ b/src/Models/Consortium.ts @@ -56,16 +56,12 @@ export abstract class Consortium extends ExtensionItem { } public async getRPCAddress(): Promise { - return this.urls.length === 0 ? '' : this.urls[0].origin; - } - - public async getAccessKey(): Promise { if (this.urls.length === 0) { return ''; } const url = this.urls[0]; - return url.pathname === '/' ? '' : url.pathname || ''; + return url.pathname === '/' ? url.origin : `${url.origin}/${url.pathname}`; } public getConsortiumId(): number { diff --git a/src/Models/LocationItem.ts b/src/Models/LocationItem.ts index 218c561..00bfac7 100644 --- a/src/Models/LocationItem.ts +++ b/src/Models/LocationItem.ts @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { SubscriptionModels } from 'azure-arm-resource'; import { QuickPickItem } from 'vscode'; export class LocationItem implements QuickPickItem { public readonly label: string; public readonly description: string; - constructor(public readonly location: SubscriptionModels.Location) { - this.label = location.displayName || ''; - this.description = location.name || ''; + constructor(label?: string, description?: string) { + this.label = label || ''; + this.description = description || ''; } } diff --git a/src/Models/ProtectedConsortium.ts b/src/Models/ProtectedConsortium.ts index 5b9897b..e26bc9a 100644 --- a/src/Models/ProtectedConsortium.ts +++ b/src/Models/ProtectedConsortium.ts @@ -20,13 +20,12 @@ export abstract class ProtectedConsortium extends Consortium { const network = await super.getTruffleNetwork(); const targetURL = await this.getRPCAddress(); - const accessKey = await this.getAccessKey(); const mnemonic = await this.getMnemonic(); await config.importFs(); network.options.provider = { mnemonic: mnemonic.path, - url: `${targetURL}/${accessKey}`, + url: `${targetURL}`, }; return network; diff --git a/src/ViewItems/ConsortiumView.ts b/src/ViewItems/ConsortiumView.ts index 6318904..cdf2f24 100644 --- a/src/ViewItems/ConsortiumView.ts +++ b/src/ViewItems/ConsortiumView.ts @@ -12,8 +12,4 @@ export class ConsortiumView extends ExtensionView { public async getRPCAddress(): Promise { return this.extensionItem.getRPCAddress(); } - - public async getAccessKey(): Promise { - return this.extensionItem.getAccessKey(); - } } diff --git a/src/commands/ConsortiumCommands.ts b/src/commands/ConsortiumCommands.ts index f65cb49..b6eef6e 100644 --- a/src/commands/ConsortiumCommands.ts +++ b/src/commands/ConsortiumCommands.ts @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { GanacheCommands } from '../commands/GanacheCommands'; +import { ConsortiumResourceExplorer } from '../ConsortiumResourceExplorer'; import { Constants } from '../Constants'; import { showInputBox, showQuickPick } from '../helpers'; import { + AzureConsortium, Consortium, ItemType, LocalNetworkConsortium, @@ -15,26 +16,29 @@ import { import { ConsortiumTreeManager } from '../treeService/ConsortiumTreeManager'; import { UrlValidator } from '../validators/UrlValidator'; import { ConsortiumView } from '../ViewItems'; -import { WestlakeCommands } from './WestlakeCommands'; +import { GanacheCommands } from './GanacheCommands'; interface IConsortiumDestination { - cmd: (childrenFilters?: string[]) => Promise; + cmd: (excludedItems?: string[]) => Promise; itemType: ItemType; label: string; } export namespace ConsortiumCommands { - export async function createConsortium(): Promise { + export async function createConsortium(consortiumTreeManager: ConsortiumTreeManager): Promise { const createConsortiumDestination: IConsortiumDestination[] = [ { - cmd: WestlakeCommands.createWestlakeConsortium, + cmd: selectOrCreateConsortium, itemType: ItemType.AZURE_BLOCKCHAIN, label: Constants.uiCommandStrings.CreateConsortiumAzureBlockchainService, }, ]; const destination = await selectDestination(createConsortiumDestination); - return await destination.cmd(); + const networkItem = await getNetwork(consortiumTreeManager, destination.itemType); + const childrenFilters = await getChildrenFilters(networkItem); + + return destination.cmd(childrenFilters); } export async function connectConsortium(consortiumTreeManager: ConsortiumTreeManager): Promise { @@ -45,7 +49,7 @@ export namespace ConsortiumCommands { label: Constants.uiCommandStrings.ConnectConsortiumLocalGanache, }, { - cmd: WestlakeCommands.selectWestlakeConsortium, + cmd: selectOrCreateConsortium, itemType: ItemType.AZURE_BLOCKCHAIN, label: Constants.uiCommandStrings.ConnectConsortiumAzureBlockchainService, }, @@ -67,7 +71,7 @@ export namespace ConsortiumCommands { export async function disconnectConsortium(consortiumTreeManager: ConsortiumTreeManager, viewItem: ConsortiumView) : Promise { if (viewItem.extensionItem instanceof LocalNetworkConsortium) { - GanacheCommands.stopGanacheServer(); + await GanacheCommands.stopGanacheServer(); } return consortiumTreeManager.removeItem(viewItem.extensionItem); } @@ -114,6 +118,11 @@ async function getNetwork(consortiumTreeManager: ConsortiumTreeManager, itemType return networkItem; } +async function selectOrCreateConsortium(excludedItems?: string[]): Promise { + const azureResourceExplorer = new ConsortiumResourceExplorer(); + return azureResourceExplorer.selectOrCreateConsortium(excludedItems); +} + async function connectLocalNetwork(): Promise { await GanacheCommands.startGanacheServer(); diff --git a/src/commands/ProjectCommands.ts b/src/commands/ProjectCommands.ts index bb0e3b7..853759b 100644 --- a/src/commands/ProjectCommands.ts +++ b/src/commands/ProjectCommands.ts @@ -2,7 +2,7 @@ // Licensed under the MIT license. import * as fs from 'fs-extra'; -import { Uri, window, workspace } from 'vscode'; +import { ProgressLocation, Uri, window, workspace } from 'vscode'; import { Constants } from '../Constants'; import { createTemporaryDir, @@ -50,7 +50,12 @@ export namespace ProjectCommands { } async function createNewEmptyProject(projectPath: string): Promise { - await createProject(projectPath, Constants.defaultTruffleBox); + return window.withProgress({ + location: ProgressLocation.Window, + title: Constants.statusBarMessages.creatingProject, + }, async () => { + return createProject(projectPath, Constants.defaultTruffleBox); + }); } async function createProjectFromTruffleBox(projectPath: string): Promise { @@ -65,9 +70,8 @@ async function createProject(projectPath: string, truffleBoxName: string): Promi const path = (arrayFiles.length) ? createTemporaryDir(projectPath) : projectPath; try { - window.showInformationMessage(Constants.informationMessage.newProjectCreationStarted); Output.show(); - await outputCommandHelper.executeCommand(path, 'npx', 'truffle', 'unbox', truffleBoxName); + await outputCommandHelper.executeCommand(path, 'npx', Constants.truffleCommand, 'unbox', truffleBoxName); if (arrayFiles.length) { fs.moveSync(path, projectPath); } @@ -75,10 +79,9 @@ async function createProject(projectPath: string, truffleBoxName: string): Promi 0, workspace.workspaceFolders ? workspace.workspaceFolders.length : null, {uri: Uri.file(projectPath)}); - window.showInformationMessage(Constants.informationMessage.newProjectCreationFinished); } catch (error) { - // TODO: cleanup files after failed truffle unbox - throw Error(error); + arrayFiles.length ? fs.removeSync(path) : fs.emptyDirSync(path); + throw Error([Constants.errorMessageStrings.NewProjectCreationFailed, error.message].join(' ')); } } diff --git a/src/commands/TruffleCommands.ts b/src/commands/TruffleCommands.ts index cea0ffd..ef4daff 100644 --- a/src/commands/TruffleCommands.ts +++ b/src/commands/TruffleCommands.ts @@ -4,7 +4,7 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { format } from 'url'; -import { env, Uri } from 'vscode'; +import { env, ProgressLocation, Uri, window } from 'vscode'; import { Constants } from '../Constants'; import { getWorkspaceRoot, @@ -35,16 +35,21 @@ const localGanacheRegexp = new RegExp(`127\.0\.0\.1\:${Constants.defaultLocalhos export namespace TruffleCommands { export async function buildContracts(): Promise { - if (!await required.checkRequiredApps()) { - return; - } + await window.withProgress({ + location: ProgressLocation.Window, + title: Constants.statusBarMessages.buildingContracts, + }, async () => { + if (!await required.checkRequiredApps()) { + return; + } - try { - Output.show(); - await outputCommandHelper.executeCommand(getWorkspaceRoot(), 'npx', 'truffle', 'compile'); - } catch (error) { - throw Error(error); - } + try { + Output.show(); + await outputCommandHelper.executeCommand(getWorkspaceRoot(), 'npx', 'truffle', 'compile'); + } catch (error) { + throw Error(error); + } + }); } export async function deployContracts(consortiumTreeManager: ConsortiumTreeManager): Promise { @@ -197,7 +202,8 @@ async function execute(deployDestination: IDeployDestination[]): Promise { async function createNewDeploymentNetwork(consortiumTreeManager: ConsortiumTreeManager, truffleConfigPath: string) : Promise { const consortium = await ConsortiumCommands.connectConsortium(consortiumTreeManager); - return createNetwork(consortium, truffleConfigPath); + + await createNetwork(consortium, truffleConfigPath); } async function createNetwork(consortium: Consortium, truffleConfigPath: string): Promise { @@ -206,29 +212,28 @@ async function createNetwork(consortium: Consortium, truffleConfigPath: string): await truffleConfig.setNetworks(network); await deployToNetwork(network.name, truffleConfigPath); - - if (network.options.provider) { - network.options.provider.mnemonic = undefined; - } - - return await truffleConfig.setNetworks(network); } async function createMainNetwork(consortium: Consortium, truffleConfigPath: string): Promise { await showConfirmPaidOperationDialog(); - return await createNetwork(consortium, truffleConfigPath); + await createNetwork(consortium, truffleConfigPath); } async function deployToNetwork(networkName: string, truffleConfigPath: string): Promise { - const workspaceRoot = path.dirname(truffleConfigPath); + return window.withProgress({ + location: ProgressLocation.Window, + title: Constants.statusBarMessages.deployingContracts(networkName), + }, async () => { + const workspaceRoot = path.dirname(truffleConfigPath); - await fs.ensureDir(workspaceRoot); - await outputCommandHelper.executeCommand( - workspaceRoot, - 'npx', - 'truffle', 'migrate', '--reset', '--network', networkName, - ); + await fs.ensureDir(workspaceRoot); + await outputCommandHelper.executeCommand( + workspaceRoot, + 'npx', + 'truffle', 'migrate', '--reset', '--network', networkName, + ); + }); } async function createLocalGanacheNetwork(consortium: Consortium, truffleConfigPath: string): Promise { diff --git a/src/commands/WestlakeCommands.ts b/src/commands/WestlakeCommands.ts index 2252764..80799b3 100644 --- a/src/commands/WestlakeCommands.ts +++ b/src/commands/WestlakeCommands.ts @@ -2,23 +2,10 @@ // Licensed under the MIT license. import { Uri, window } from 'vscode'; -import { ConsortiumResourceExplorer } from '../ConsortiumResourceExplorer'; import { Constants } from '../Constants'; -import { AzureConsortium } from '../Models'; import { TruffleCommands } from './TruffleCommands'; export namespace WestlakeCommands { - - export async function createWestlakeConsortium(): Promise { - const azureResourceExplorer = new ConsortiumResourceExplorer(); - return azureResourceExplorer.createConsortium(); - } - - export async function selectWestlakeConsortium(childrenFilters?: string[]): Promise { - const azureResourceExplorer = new ConsortiumResourceExplorer(); - return azureResourceExplorer.selectConsortium(childrenFilters); - } - export async function showLedgerEventsDialog(uri: Uri): Promise { const ledgerEventsDestination = [ { @@ -44,7 +31,7 @@ export namespace WestlakeCommands { throw new Error('Action aborted'); } - return await target.cmd(uri); + return target.cmd(uri); } } diff --git a/src/extension.ts b/src/extension.ts index 81684a2..a7581fb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,12 +15,11 @@ import { CommandContext, isWorkspaceOpen, required, setCommandContext } from './ import { MnemonicRepository } from './MnemonicService/MnemonicRepository'; import { CancellationEvent } from './Models'; import { Output } from './Output'; -import { RequirementsPage } from './RequirementsPage'; +import { RequirementsPage, WelcomePage } from './pages'; import { TelemetryClient } from './TelemetryClient'; import { ConsortiumTree } from './treeService/ConsortiumTree'; import { ConsortiumTreeManager } from './treeService/ConsortiumTreeManager'; import { ConsortiumView } from './ViewItems'; -import { WelcomePage } from './WelcomePage'; export async function activate(context: ExtensionContext) { Constants.initialize(context); @@ -35,27 +34,24 @@ export async function activate(context: ExtensionContext) { const consortiumTreeManager = new ConsortiumTreeManager(context); const consortiumTree = new ConsortiumTree(consortiumTreeManager); - welcomePage.checkAndShow(); + await welcomePage.checkAndShow(); window.registerTreeDataProvider('AzureBlockchain', consortiumTree); //#region azureBlockchain extension commands const refresh = commands.registerCommand('azureBlockchainService.refresh', (element) => { consortiumTree.refresh(element); }); - const showWelcomePage = commands.registerCommand('azureBlockchainService.showWelcomePage', () => { - welcomePage.show(); + const showWelcomePage = commands.registerCommand('azureBlockchainService.showWelcomePage', async () => { + return welcomePage.show(); }); - const showRequirementsPage = commands.registerCommand('azureBlockchainService.showRequirementsPage', () => { - requirementsPage.show(); + const showRequirementsPage = commands.registerCommand('azureBlockchainService.showRequirementsPage', + async (checkShowOnStartup: boolean) => { + return checkShowOnStartup ? requirementsPage.checkAndShow() : requirementsPage.show(); }); const copyRPCEndpointAddress = commands.registerCommand('azureBlockchainService.copyRPCEndpointAddress', async (viewItem: ConsortiumView) => { await tryExecute(() => AzureBlockchain.copyRPCEndpointAddress(viewItem)); }); - const copyAccessKey = commands.registerCommand('azureBlockchainService.copyAccessKey', - async (viewItem: ConsortiumView) => { - await tryExecute(() => AzureBlockchain.copyAccessKey(viewItem)); - }); const installNpm = commands.registerCommand('azureBlockchainService.required.installNpm', async () => { await tryExecute(() => required.installNpm()); }); @@ -102,7 +98,7 @@ export async function activate(context: ExtensionContext) { //#region commands with dialog const createConsortium = commands.registerCommand('azureBlockchainService.createConsortium', async () => { - await tryExecute(() => ConsortiumCommands.createConsortium()); + await tryExecute(() => ConsortiumCommands.createConsortium(consortiumTreeManager)); }); const connectConsortium = commands.registerCommand('azureBlockchainService.connectConsortium', async () => { await tryExecute(() => ConsortiumCommands.connectConsortium(consortiumTreeManager)); @@ -175,7 +171,6 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(copyByteCode); context.subscriptions.push(copyABI); context.subscriptions.push(copyRPCEndpointAddress); - context.subscriptions.push(copyAccessKey); context.subscriptions.push(startGanacheServer); context.subscriptions.push(stopGanacheServer); context.subscriptions.push(generateMicroservicesWorkflows); diff --git a/src/helpers/required.ts b/src/helpers/required.ts index 8733a5d..46a3d14 100644 --- a/src/helpers/required.ts +++ b/src/helpers/required.ts @@ -4,9 +4,6 @@ import { Constants } from '../Constants'; import { Output } from '../Output'; import { executeCommand, tryExecuteCommand } from './command'; import { CommandContext, setCommandContext } from './commandContext'; -import Timeout = NodeJS.Timeout; - -let timeoutID: NodeJS.Timeout; export namespace required { export interface IRequiredVersion { @@ -18,7 +15,6 @@ export namespace required { const currentState: {[key: string]: IRequiredVersion} = {}; const requiredApps = [ 'node', 'npm', 'git' ]; - // const auxiliaryApps = [ 'python', 'truffle', 'ganache' ]; export function isValid(version: string, minVersion: string, maxVersion?: string): boolean { return !!semver.valid(version) && @@ -27,37 +23,47 @@ export namespace required { } /** - * Function check all apps: - * Node.js, npm, git, truffle, ganache, python + * Function check all apps: Node.js, npm, git, truffle, ganache, python + * Show Requirements Page with checking showOnStartup flag */ export async function checkAllApps(): Promise { const versions = await getAllVersions(); const invalid = versions.some((version) => !version.isValid); if (invalid) { - showRequiredAppsMessage(); - return false; + const message = Constants.informationMessage.invalidRequiredVersion; + const details = Constants.informationMessage.seeDetailsRequirementsPage; + window + .showErrorMessage(`${message}. ${details}`, Constants.informationMessage.detailsButton) + .then((answer) => { + if (answer) { + commands.executeCommand('azureBlockchainService.showRequirementsPage'); + } + }); + commands.executeCommand('azureBlockchainService.showRequirementsPage', true); } - return true; + return !invalid; } /** - * Function check only required apps: - * Node.js, npm, git + * Function check only required apps: Node.js, npm, git + * Show Requirements Page */ - export async function checkRequiredApps(message?: string): Promise { + export async function checkRequiredApps(): Promise { const versions = await getAllVersions(); const invalid = versions .filter((version) => requiredApps.includes(version.app)) .some((version) => !version.isValid); if (invalid) { - showRequiredAppsMessage(message || Constants.errorMessageStrings.RequiredAppsAreNotInstalled); - return false; + const message = Constants.errorMessageStrings.RequiredAppsAreNotInstalled; + const details = Constants.informationMessage.seeDetailsRequirementsPage; + window.showErrorMessage(`${message}. ${details}`); + commands.executeCommand('azureBlockchainService.showRequirementsPage'); } - return true; + return !invalid; } export async function getAllVersions(): Promise { @@ -79,21 +85,6 @@ export namespace required { return Object.values(currentState); } - export function showRequiredAppsMessage(message?: string): void { - commands.executeCommand('azureBlockchainService.showRequirementsPage'); - - clearTimeout(timeoutID as Timeout); - timeoutID = setTimeout(async () => { - try { - message = message || Constants.informationMessage.invalidRequiredVersion; - - window.showErrorMessage(`${message} ${Constants.informationMessage.seeDetailsRequirementsPage}`); - } catch (e) { - // ignore - } - }, 500); - } - export async function getNodeVersion(): Promise { return getVersion('node', '--version', /v(\d+.\d+.\d+)/); }