From 00a83888cb628abb992b2202c7994e67d35859dd Mon Sep 17 00:00:00 2001 From: Akshat Shah Date: Mon, 20 Jan 2020 13:13:40 +0000 Subject: [PATCH 1/2] Added functionality to allow user to generate a private data smart contract Signed-off-by: Akshat Shah --- .../cucumber/data/collection.json | 24 ++++ .../cucumber/features/create.feature | 11 ++ .../cucumber/features/instantiate.feature | 18 ++- .../cucumber/features/submit.feature | 24 +++- .../cucumber/helpers/gatewayHelper.ts | 5 +- .../cucumber/helpers/smartContractHelper.ts | 18 ++- .../cucumber/steps/smartContract.steps.ts | 27 +++- .../extension/commands/UserInputUtil.ts | 4 + .../createSmartContractProjectCommand.ts | 48 ++++++- .../extension/util/ExtensionUtil.ts | 2 +- .../createSmartContractProjectCommand.test.ts | 133 +++++++++++++----- .../test/webview/TutorialGalleryView.test.ts | 4 +- .../test/webview/View.test.ts | 2 +- packages/blockchain-extension/tutorials.json | 2 +- .../createNewIdentityAttributes.md | 12 +- .../tutorials/developer-tutorials/debug.md | 22 +-- .../local-dev.md | 18 +-- 17 files changed, 290 insertions(+), 84 deletions(-) diff --git a/packages/blockchain-extension/cucumber/data/collection.json b/packages/blockchain-extension/cucumber/data/collection.json index 63d92faea5..6d02603f2c 100644 --- a/packages/blockchain-extension/cucumber/data/collection.json +++ b/packages/blockchain-extension/cucumber/data/collection.json @@ -38,5 +38,29 @@ "requiredPeerCount": 1, "maxPeerCount": 1, "blockToLive": 100 + }, + { + "name": "CollectionOne", + "policy": { + "identities": [ + { + "role": { + "name": "member", + "mspId": "Org1MSP" + } + } + ], + "policy": { + "1-of": [ + { + "signed-by": 0 + } + ] + } + }, + "requiredPeerCount": 1, + "maxPeerCount": 1, + "blockToLive": 0, + "memberOnlyRead": true } ] \ No newline at end of file diff --git a/packages/blockchain-extension/cucumber/features/create.feature b/packages/blockchain-extension/cucumber/features/create.feature index 9ecbe09224..c036569c4d 100644 --- a/packages/blockchain-extension/cucumber/features/create.feature +++ b/packages/blockchain-extension/cucumber/features/create.feature @@ -12,3 +12,14 @@ Feature: Created Smart Contracts | TypeScript | Conga | TypeScriptContract | 0.0.1 | | Java | Conga | JavaContract | 0.0.1 | | Go | null | GoContract | 0.0.1 | + + Scenario Outline: Create a private data smart contract + Given a private smart contract for assets with the name and version and mspid + And the contract hasn't been created already + When I create the private contract + Then a new contract directory should exist + Examples: + | language | assetType | name | mspid | version | + | JavaScript | PrivateConga | PrivateJavaScriptContract | Org1MSP | 0.0.1 | + | TypeScript | PrivateConga | PrivateTypeScriptContract | Org1MSP | 0.0.1 | + | Java | PrivateConga | PrivateJavaContract | Org1MSP | 0.0.1 | diff --git a/packages/blockchain-extension/cucumber/features/instantiate.feature b/packages/blockchain-extension/cucumber/features/instantiate.feature index 54837dfaf5..028433fb42 100644 --- a/packages/blockchain-extension/cucumber/features/instantiate.feature +++ b/packages/blockchain-extension/cucumber/features/instantiate.feature @@ -9,7 +9,7 @@ Feature: Instantiate Smart Contracts And the contract has been packaged And the package has been installed When I instantiate the installed package with the transaction '' and args '', not using private data on channel 'mychannel' - Then there should be a instantiated smart contract tree item with a label '' in the 'Fabric Environments' panel + Then there should be an instantiated smart contract tree item with a label '' in the 'Fabric Environments' panel And the tree item should have a tooltip equal to 'Instantiated on: mychannel' Examples: | language | assetType | name | instantiatedName | version | @@ -18,3 +18,19 @@ Feature: Instantiate Smart Contracts | Java | Conga | JavaContract | JavaContract@0.0.1 | 0.0.1 | | Go | null | GoContract | GoContract@0.0.1 | 0.0.1 | + + Scenario Outline: Instantiate a private smart contract + Given a private smart contract for assets with the name and version and mspid + And the Local Fabric is running + And the 'Local Fabric' environment is connected + And the private contract has been created + And the contract has been packaged + And the package has been installed + When I instantiate the installed package with the transaction '' and args '', using private data on channel 'mychannel' + Then there should be an instantiated smart contract tree item with a label '' in the 'Fabric Environments' panel + And the tree item should have a tooltip equal to 'Instantiated on: mychannel' + Examples: + | language | assetType | name | instantiatedName | mspid | version | + | JavaScript | PrivateConga | PrivateJavaScriptContract | PrivateJavaScriptContract@0.0.1 | Org1MSP | 0.0.1 | + | TypeScript | PrivateConga | PrivateTypeScriptContract | PrivateTypeScriptContract@0.0.1 | Org1MSP | 0.0.1 | + | Java | PrivateConga | PrivateJavaContract | PrivateJavaContract@0.0.1 | Org1MSP | 0.0.1 | diff --git a/packages/blockchain-extension/cucumber/features/submit.feature b/packages/blockchain-extension/cucumber/features/submit.feature index 19ca9d07fd..ad5c3387ec 100644 --- a/packages/blockchain-extension/cucumber/features/submit.feature +++ b/packages/blockchain-extension/cucumber/features/submit.feature @@ -22,7 +22,7 @@ Feature: Submit transaction | Go | null | GoContract | 0.0.1 | - Scenario Outline: Submit a transaction for a smart contract using generated transaction data + Scenario Outline: Submit a transaction for a smart contract using generated transaction data Given a smart contract for assets with the name and version And the Local Fabric is running And the 'Local Fabric' environment is connected @@ -40,6 +40,28 @@ Feature: Submit transaction | language | assetType | name | version | | TypeScript | Conga | TypeScriptContract | 0.0.1 | + Scenario Outline: Submit a verify transaction for a private data smart contract + Given a private smart contract for assets with the name and version and mspid + And the Local Fabric is running + And the 'Local Fabric' environment is connected + And the 'Org1' wallet + And the 'Local Fabric Admin' identity + And I'm connected to the 'Local Fabric - Org1' gateway + And the private contract has been created + And the contract has been packaged + And the package has been installed + And the contract has been instantiated with the transaction '' and args '', using private data on channel 'mychannel' + When I submit the transaction 'createPrivateConga' on the channel 'mychannel' with args '["001"]' and with the transient data '{"privateValue":"125"}' + Then the logger should have been called with 'SUCCESS', 'Successfully submitted transaction' and 'No value returned from createPrivateConga' + When I submit the transaction 'verifyPrivateConga' on the channel 'mychannel' with args '["001", "{\"privateValue\":\"125\"}"]' + Then the logger should have been called with 'SUCCESS', 'Successfully submitted transaction' and 'Returned value from verifyPrivateConga: true' + Examples: + | language | assetType | name | mspid | version | + | JavaScript | PrivateConga | PrivateJavaScriptContract | Org1MSP | 0.0.1 | + | TypeScript | PrivateConga | PrivateTypeScriptContract | Org1MSP | 0.0.1 | + | Java | PrivateConga | PrivateJavaContract | Org1MSP | 0.0.1 | + + @otherFabric Scenario Outline: Submit a transaction for a smart contract (other fabric) Given an environment 'myFabric' exists diff --git a/packages/blockchain-extension/cucumber/helpers/gatewayHelper.ts b/packages/blockchain-extension/cucumber/helpers/gatewayHelper.ts index af7e366208..72b99b5999 100644 --- a/packages/blockchain-extension/cucumber/helpers/gatewayHelper.ts +++ b/packages/blockchain-extension/cucumber/helpers/gatewayHelper.ts @@ -176,7 +176,10 @@ export class GatewayHelper { if (!transientData) { transientData = ''; } - this.userInputUtilHelper.inputBoxStub.withArgs('optional: What is the transient data for the transaction, e.g. {"key": "value"}', '{}').resolves(transientData); + + const transientDataWithoutQuotes: string = transientData.slice(1, -1); + + this.userInputUtilHelper.inputBoxStub.withArgs('optional: What is the transient data for the transaction, e.g. {"key": "value"}', '{}').resolves(transientDataWithoutQuotes); this.userInputUtilHelper.showQuickPickStub.withArgs('Select a peer-targeting policy for this transaction', [UserInputUtil.DEFAULT, UserInputUtil.CUSTOM]).resolves(UserInputUtil.DEFAULT); diff --git a/packages/blockchain-extension/cucumber/helpers/smartContractHelper.ts b/packages/blockchain-extension/cucumber/helpers/smartContractHelper.ts index 94ded8ef44..139bb91906 100644 --- a/packages/blockchain-extension/cucumber/helpers/smartContractHelper.ts +++ b/packages/blockchain-extension/cucumber/helpers/smartContractHelper.ts @@ -49,7 +49,7 @@ export class SmartContractHelper { this.userInputUtilHelper = userInputUtilHelper; } - public async createSmartContract(language: string, assetType: string, contractName: string): Promise { + public async createSmartContract(language: string, assetType: string, contractName: string, mspid?: string): Promise { let type: LanguageType; if (language === 'Go') { @@ -60,9 +60,16 @@ export class SmartContractHelper { throw new Error(`You must update this test to support the ${language} language`); } - this.userInputUtilHelper.showLanguagesQuickPickStub.resolves({ label: language, type }); + if (contractName.includes('Private')) { + this.userInputUtilHelper.showQuickPickItemStub.resolves({label: UserInputUtil.GENERATE_PD_CONTRACT, description: UserInputUtil.GENERATE_PD_CONTRACT_DESCRIPTION, data: 'private'}); + this.userInputUtilHelper.inputBoxStub.withArgs('Name the type of asset managed by this smart contract', 'MyPrivateAsset').resolves(assetType); + this.userInputUtilHelper.inputBoxStub.withArgs('Please provide an mspID for the private data collection', 'Org1MSP').resolves(mspid); + } else { + this.userInputUtilHelper.showQuickPickItemStub.resolves({label: UserInputUtil.GENERATE_DEFAULT_CONTRACT, description: UserInputUtil.GENERATE_DEFAULT_CONTRACT_DESCRIPTION, data: 'default'}); + this.userInputUtilHelper.inputBoxStub.withArgs('Name the type of asset managed by this smart contract', 'MyAsset').resolves(assetType); + } - this.userInputUtilHelper.inputBoxStub.withArgs('Name the type of asset managed by this smart contract', 'MyAsset').resolves(assetType); + this.userInputUtilHelper.showLanguagesQuickPickStub.resolves({ label: language, type }); this.userInputUtilHelper.showFolderOptionsStub.withArgs('Choose how to open your new project').resolves(UserInputUtil.ADD_TO_WORKSPACE); @@ -206,7 +213,8 @@ export class SmartContractHelper { this.userInputUtilHelper.showYesNoQuickPick.resolves(UserInputUtil.NO); if (privateData) { this.userInputUtilHelper.showYesNoQuickPick.resolves(UserInputUtil.YES); - const collectionPath: string = path.join(__dirname, '../../integrationTest/data/collection.json'); + this.userInputUtilHelper.getWorkspaceFoldersStub.callThrough(); + const collectionPath: string = path.join(__dirname, '../../../cucumber/data/collection.json'); this.userInputUtilHelper.browseStub.resolves(collectionPath); } @@ -258,7 +266,7 @@ export class SmartContractHelper { this.userInputUtilHelper.showYesNoQuickPick.resolves(UserInputUtil.NO); if (privateData) { this.userInputUtilHelper.showYesNoQuickPick.resolves(UserInputUtil.YES); - const collectionPath: string = path.join(__dirname, '../../integrationTest/data/collection.json'); + const collectionPath: string = path.join(__dirname, '../../../cucumber/data/collection.json'); this.userInputUtilHelper.browseStub.resolves(collectionPath); } diff --git a/packages/blockchain-extension/cucumber/steps/smartContract.steps.ts b/packages/blockchain-extension/cucumber/steps/smartContract.steps.ts index 11f3495b68..bc94f4c897 100644 --- a/packages/blockchain-extension/cucumber/steps/smartContract.steps.ts +++ b/packages/blockchain-extension/cucumber/steps/smartContract.steps.ts @@ -29,18 +29,25 @@ module.exports = function(): any { * Given */ - this.Given('a {string} smart contract for {string} assets with the name {string} and version {string}', this.timeout, async (language: string, assetType: string, name: string, version: string) => { + this.Given(/^a (private )?(.+) smart contract for (.+) assets with the name (.+) and version (.\S+)( and mspid)? ?(.+)?$/, this.timeout, async (privateOrNot: string, language: string, assetType: string, name: string, version: string, _ignore: string, mspid: string) => { this.contractLanguage = language; if (assetType === 'null') { assetType = null; } + + if (privateOrNot === 'private ') { + this.mspid = mspid; + } else { + this.mspid = null; + } + this.contractAssetType = assetType; this.namespace = `${this.contractAssetType}Contract`; this.contractName = name; this.contractVersion = version; }); - this.Given("the contract hasn't been created already", this.timeout, async () => { + this.Given(/the contract hasn't been created already/, this.timeout, async () => { const contractDirectory: string = this.smartContractHelper.getContractDirectory(this.contractName, this.contractLanguage); const exists: boolean = await fs.pathExists(contractDirectory); if (exists) { @@ -48,11 +55,15 @@ module.exports = function(): any { } }); - this.Given('the contract has been created', this.timeout, async () => { + this.Given(/the( private)? contract has been created/, this.timeout, async (_privateOrNot: string) => { const contractDirectory: string = this.smartContractHelper.getContractDirectory(this.contractName, this.contractLanguage); const exists: boolean = await fs.pathExists(contractDirectory); if (!exists) { - this.contractDirectory = await this.smartContractHelper.createSmartContract(this.contractLanguage, this.contractAssetType, this.contractName); + if (_privateOrNot === ' private') { + this.contractDirectory = await this.smartContractHelper.createSmartContract(this.contractLanguage, this.contractAssetType, this.contractName, this.mspid); + } else { + this.contractDirectory = await this.smartContractHelper.createSmartContract(this.contractLanguage, this.contractAssetType, this.contractName); + } } else { this.contractDirectory = contractDirectory; } @@ -76,8 +87,12 @@ module.exports = function(): any { * When */ - this.When('I create the contract', this.timeout, async () => { - this.contractDirectory = await this.smartContractHelper.createSmartContract(this.contractLanguage, this.contractAssetType, this.contractName); + this.When(/I create the( private)? contract/, this.timeout, async (_privateOrNot: string) => { + if (_privateOrNot === ' private') { + this.contractDirectory = await this.smartContractHelper.createSmartContract(this.contractLanguage, this.contractAssetType, this.contractName, this.mspid); + } else { + this.contractDirectory = await this.smartContractHelper.createSmartContract(this.contractLanguage, this.contractAssetType, this.contractName); + } }); /** diff --git a/packages/blockchain-extension/extension/commands/UserInputUtil.ts b/packages/blockchain-extension/extension/commands/UserInputUtil.ts index 77b9182880..d7ce30543b 100644 --- a/packages/blockchain-extension/extension/commands/UserInputUtil.ts +++ b/packages/blockchain-extension/extension/commands/UserInputUtil.ts @@ -86,6 +86,10 @@ export class UserInputUtil { static readonly CANCEL_NO_CERT_CHAIN: string = 'Cancel'; static readonly CANCEL_NO_CERT_CHAIN_DESCRIPTION: string = '(CA certificate chain must be added to Operative System trusted root certificates)'; static readonly CONNECT_NO_CA_CERT_CHAIN: string = 'Proceed without certificate verification'; + static readonly GENERATE_DEFAULT_CONTRACT: string = 'Default Contract'; + static readonly GENERATE_DEFAULT_CONTRACT_DESCRIPTION: string = 'CRUD operations to a ledger shared by all network members'; + static readonly GENERATE_PD_CONTRACT: string = 'Private Data Contract'; + static readonly GENERATE_PD_CONTRACT_DESCRIPTION: string = 'CRUD and verify operations to a collection, private to a single network member'; public static async showQuickPick(prompt: string, items: string[], canPickMany: boolean = false): Promise { const quickPickOptions: vscode.QuickPickOptions = { diff --git a/packages/blockchain-extension/extension/commands/createSmartContractProjectCommand.ts b/packages/blockchain-extension/extension/commands/createSmartContractProjectCommand.ts index 34498ecf67..d20b79e8e6 100644 --- a/packages/blockchain-extension/extension/commands/createSmartContractProjectCommand.ts +++ b/packages/blockchain-extension/extension/commands/createSmartContractProjectCommand.ts @@ -17,7 +17,7 @@ import * as nls from 'vscode-nls'; import { Reporter } from '../util/Reporter'; import { VSCodeBlockchainOutputAdapter } from '../logging/VSCodeBlockchainOutputAdapter'; import * as path from 'path'; -import { UserInputUtil, LanguageQuickPickItem, LanguageType } from './UserInputUtil'; +import { UserInputUtil, LanguageQuickPickItem, LanguageType, IBlockchainQuickPickItem } from './UserInputUtil'; import { ExtensionUtil } from '../util/ExtensionUtil'; import { LogType } from 'ibm-blockchain-platform-common'; import * as GeneratorFabricPackageJSON from 'generator-fabric/package.json'; @@ -31,6 +31,31 @@ export async function createSmartContractProject(): Promise { const chaincodeLanguageOptions: string[] = getChaincodeLanguageOptions(); const smartContractLanguageOptions: string[] = getSmartContractLanguageOptions(); + let contractType: IBlockchainQuickPickItem; + let privateOrDefault: string; + let mspID: string; + const pdContract: string = ' Private Data '; + const defaultContract: string = ' '; + const typeDefault: string = 'default'; + const defaultAsset: string = 'MyAsset'; + const privateAsset: string = 'MyPrivateAsset'; + + contractType = await UserInputUtil.showQuickPickItem('Choose a contract type to generate:', [{label: UserInputUtil.GENERATE_DEFAULT_CONTRACT, description: UserInputUtil.GENERATE_DEFAULT_CONTRACT_DESCRIPTION, data: 'default'}, {label: UserInputUtil.GENERATE_PD_CONTRACT, description: UserInputUtil.GENERATE_PD_CONTRACT_DESCRIPTION, data: 'private'}] as IBlockchainQuickPickItem[]) as IBlockchainQuickPickItem; + + if (!contractType) { + return; + // User has cancelled the QuickPick box + } + if (contractType.data === typeDefault) { + privateOrDefault = defaultContract; + } else { + mspID = await UserInputUtil.showInputBox('Please provide an mspID for the private data collection', 'Org1MSP'); + if (!mspID) { + return; + } + + privateOrDefault = pdContract; + } const smartContractLanguagePrompt: string = localize('smartContractLanguage.prompt', 'Choose smart contract language (Esc to cancel)'); const smartContractLanguageItem: LanguageQuickPickItem = await UserInputUtil.showLanguagesQuickPick(smartContractLanguagePrompt, chaincodeLanguageOptions, smartContractLanguageOptions); @@ -44,7 +69,7 @@ export async function createSmartContractProject(): Promise { let assetType: string; if (smartContractLanguageItem.type === LanguageType.CONTRACT) { - assetType = await UserInputUtil.showInputBox('Name the type of asset managed by this smart contract', 'MyAsset'); + assetType = await UserInputUtil.showInputBox('Name the type of asset managed by this smart contract', contractType.data === typeDefault ? defaultAsset : privateAsset); const regexForAssetType: RegExp = /^[A-Z]+$/i; const validAssetType: boolean = regexForAssetType.test(assetType); if (!assetType) { @@ -92,33 +117,42 @@ export async function createSmartContractProject(): Promise { const runOptions: any = { 'destination': folderPath, + 'contractType': contractType.data, 'language': smartContractLanguage, 'name': folderName, 'version': '0.0.1', - 'description': 'My Smart Contract', + 'description': `My${privateOrDefault}Smart Contract`, 'author': 'John Doe', 'license': 'Apache-2.0', 'skip-install': skipInstall, 'asset': assetType }; + if (contractType.data === 'private') { + runOptions.mspId = mspID; + } + await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: 'IBM Blockchain Platform Extension', cancellable: false }, async (progress: vscode.Progress<{message: string}>): Promise => { - progress.report({message: 'Generating smart contract project'}); + progress.report({message: `Generating${privateOrDefault}Smart Contract Project`}); await YeomanUtil.run(generator, runOptions); }); - outputAdapter.log(LogType.SUCCESS, 'Successfully generated smart contract project'); + outputAdapter.log(LogType.SUCCESS, `Successfully generated${privateOrDefault}Smart Contract Project`); - Reporter.instance().sendTelemetryEvent('createSmartContractProject', {contractLanguage: smartContractLanguage}); + if (contractType.data === typeDefault) { + Reporter.instance().sendTelemetryEvent('createSmartContractProject', {contractLanguage: smartContractLanguage}); + } else { + Reporter.instance().sendTelemetryEvent('createPrivateDataSmartContractProject', {contractLanguage: smartContractLanguage}); + } // Open the returned folder in explorer, in a new window await UserInputUtil.openNewProject(openMethod, folderUri); await vscode.commands.executeCommand('workbench.files.action.focusFilesExplorer'); } catch (error) { - outputAdapter.log(LogType.ERROR, `Issue creating smart contract project: ${error.message}`, `Issue creating smart contract project: ${error.toString()}`); + outputAdapter.log(LogType.ERROR, `Issue creating${privateOrDefault}Smart Contract Project: ${error.message}`, `Issue creating${privateOrDefault}Smart Contract Project: ${error.toString()}`); return; } diff --git a/packages/blockchain-extension/extension/util/ExtensionUtil.ts b/packages/blockchain-extension/extension/util/ExtensionUtil.ts index fa8e9078a8..058589a562 100644 --- a/packages/blockchain-extension/extension/util/ExtensionUtil.ts +++ b/packages/blockchain-extension/extension/util/ExtensionUtil.ts @@ -267,7 +267,7 @@ export class ExtensionUtil { context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.ADD_GATEWAY, () => addGateway())); context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.DELETE_GATEWAY, (gateway: GatewayTreeItem) => deleteGateway(gateway))); context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.ADD_WALLET_IDENTITY, (walletItem: WalletTreeItem | FabricWalletRegistryEntry, mspid: string) => addWalletIdentity(walletItem, mspid))); - context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT, createSmartContractProject)); + context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT, () => createSmartContractProject())); context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.PACKAGE_SMART_CONTRACT, (workspace?: vscode.WorkspaceFolder, overrideName?: string, overrideVersion?: string) => packageSmartContract(workspace, overrideName, overrideVersion))); context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.REFRESH_PACKAGES, () => blockchainPackageExplorerProvider.refresh())); context.subscriptions.push(vscode.commands.registerCommand(ExtensionCommands.REFRESH_ENVIRONMENTS, (element: BlockchainTreeItem) => blockchainEnvironmentExplorerProvider.refresh(element))); diff --git a/packages/blockchain-extension/test/commands/createSmartContractProjectCommand.test.ts b/packages/blockchain-extension/test/commands/createSmartContractProjectCommand.test.ts index fed5145bd5..b7a204df4d 100644 --- a/packages/blockchain-extension/test/commands/createSmartContractProjectCommand.test.ts +++ b/packages/blockchain-extension/test/commands/createSmartContractProjectCommand.test.ts @@ -37,7 +37,11 @@ describe('CreateSmartContractProjectCommand', () => { // suite variables let mySandBox: sinon.SinonSandbox; let logSpy: sinon.SinonSpy; - let quickPickStub: sinon.SinonStub; + let inputBoxSpy: sinon.SinonSpy; + let showQuickPickItemStub: sinon.SinonStub; + let showLanguagesQuickPickStub: sinon.SinonStub; + let showFolderOptionsStub: sinon.SinonStub; + let showYesNoQuickPickStub: sinon.SinonStub; let showInputBoxStub: sinon.SinonStub; let browseStub: sinon.SinonStub; let executeCommandStub: sinon.SinonStub; @@ -53,8 +57,12 @@ describe('CreateSmartContractProjectCommand', () => { beforeEach(async () => { mySandBox.stub(CommandUtil, 'sendCommandWithOutputAndProgress'); + showQuickPickItemStub = mySandBox.stub(UserInputUtil, 'showQuickPickItem'); logSpy = mySandBox.spy(VSCodeBlockchainOutputAdapter.instance(), 'log'); - quickPickStub = mySandBox.stub(vscode.window, 'showQuickPick'); + inputBoxSpy = mySandBox.spy(UserInputUtil, 'showInputBox'); + showLanguagesQuickPickStub = mySandBox.stub(UserInputUtil, 'showLanguagesQuickPick'); + showFolderOptionsStub = mySandBox.stub(UserInputUtil, 'showFolderOptions'); + showYesNoQuickPickStub = mySandBox.stub(UserInputUtil, 'showQuickPickYesNo'); showInputBoxStub = mySandBox.stub(vscode.window, 'showInputBox'); mySandBox.stub(vscode.window, 'showOpenDialog'); browseStub = mySandBox.stub(UserInputUtil, 'browse'); @@ -73,6 +81,8 @@ describe('CreateSmartContractProjectCommand', () => { skipNpmInstallStub = mySandBox.stub(ExtensionUtil, 'skipNpmInstall'); skipNpmInstallStub.resolves(true); // we don't want npm install running during unit tests sendTelemetryEventStub = mySandBox.stub(Reporter.instance(), 'sendTelemetryEvent'); + + showQuickPickItemStub.resolves({label: UserInputUtil.GENERATE_DEFAULT_CONTRACT, description: UserInputUtil.GENERATE_DEFAULT_CONTRACT_DESCRIPTION, data: 'default'}); }); afterEach(() => { mySandBox.restore(); @@ -142,46 +152,46 @@ describe('CreateSmartContractProjectCommand', () => { } it(`should start a ${testLanguageItem.label} smart contract project, in a new window`, async () => { - quickPickStub.onFirstCall().resolves(testLanguageItem); + showLanguagesQuickPickStub.resolves(testLanguageItem); if (testLanguageItem.type === LanguageType.CONTRACT) { showInputBoxStub.onFirstCall().resolves('Conga'); } - quickPickStub.onSecondCall().resolves(UserInputUtil.OPEN_IN_NEW_WINDOW); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_NEW_WINDOW); browseStub.resolves(uri); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); executeCommandStub.should.have.been.calledThrice; executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); executeCommandStub.should.have.been.calledWith('vscode.openFolder', uri, true); - logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated smart contract project'); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Smart Contract Project'); await checkSmartContract(); sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createSmartContractProject', { contractLanguage: testLanguageItem.label }); }); it(`should start a ${testLanguageItem.label} smart contract project, in current window`, async () => { - quickPickStub.onFirstCall().resolves(testLanguageItem); + showLanguagesQuickPickStub.resolves(testLanguageItem); if (testLanguageItem.type === LanguageType.CONTRACT) { showInputBoxStub.onFirstCall().resolves('Conga'); } - quickPickStub.onSecondCall().resolves(UserInputUtil.OPEN_IN_CURRENT_WINDOW); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_CURRENT_WINDOW); browseStub.resolves(uri); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); executeCommandStub.should.have.been.calledThrice; executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); executeCommandStub.should.have.been.calledWith('vscode.openFolder', uri, false); - logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated smart contract project'); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Smart Contract Project'); await checkSmartContract(); sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createSmartContractProject', { contractLanguage: testLanguageItem.label }); }); it(`should start a ${testLanguageItem.label} smart contract project, in current window with unsaved files and save`, async () => { - quickPickStub.onFirstCall().resolves(testLanguageItem); + showLanguagesQuickPickStub.resolves(testLanguageItem); if (testLanguageItem.type === LanguageType.CONTRACT) { showInputBoxStub.onFirstCall().resolves('Conga'); } - quickPickStub.onSecondCall().resolves(UserInputUtil.OPEN_IN_CURRENT_WINDOW); - quickPickStub.onThirdCall().resolves(UserInputUtil.YES); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_CURRENT_WINDOW); + showYesNoQuickPickStub.resolves(UserInputUtil.YES); browseStub.resolves(uri); const saveDialogStub: sinon.SinonStub = mySandBox.stub(vscode.workspace, 'saveAll').resolves(true); @@ -193,18 +203,18 @@ describe('CreateSmartContractProjectCommand', () => { executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); executeCommandStub.should.have.been.calledWith('vscode.openFolder', uri, false); saveDialogStub.should.have.been.calledWith(true); - logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated smart contract project'); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Smart Contract Project'); await checkSmartContract(); sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createSmartContractProject', { contractLanguage: testLanguageItem.label }); }); it(`should start a ${testLanguageItem.label} smart contract project, in current window with unsaved files and not save`, async () => { - quickPickStub.onFirstCall().resolves(testLanguageItem); + showLanguagesQuickPickStub.resolves(testLanguageItem); if (testLanguageItem.type === LanguageType.CONTRACT) { showInputBoxStub.onFirstCall().resolves('Conga'); } - quickPickStub.onSecondCall().resolves(UserInputUtil.OPEN_IN_CURRENT_WINDOW); - quickPickStub.onThirdCall().resolves(UserInputUtil.NO); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_CURRENT_WINDOW); + showYesNoQuickPickStub.resolves(UserInputUtil.NO); browseStub.resolves(uri); const saveDialogStub: sinon.SinonStub = mySandBox.stub(vscode.workspace, 'saveAll'); @@ -216,17 +226,17 @@ describe('CreateSmartContractProjectCommand', () => { executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); executeCommandStub.should.have.been.calledWith('vscode.openFolder', uri, false); saveDialogStub.should.not.have.been.called; - logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated smart contract project'); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Smart Contract Project'); await checkSmartContract(); sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createSmartContractProject', { contractLanguage: testLanguageItem.label }); }); it(`should start a ${testLanguageItem.label} smart contract project, in a new workspace with no folders`, async () => { - quickPickStub.onFirstCall().resolves(testLanguageItem); + showLanguagesQuickPickStub.resolves(testLanguageItem); if (testLanguageItem.type === LanguageType.CONTRACT) { showInputBoxStub.onFirstCall().resolves('Conga'); } - quickPickStub.onSecondCall().resolves(UserInputUtil.ADD_TO_WORKSPACE); + showFolderOptionsStub.resolves(UserInputUtil.ADD_TO_WORKSPACE); browseStub.resolves(uri); mySandBox.stub(vscode.workspace, 'workspaceFolders').value(undefined); @@ -235,44 +245,95 @@ describe('CreateSmartContractProjectCommand', () => { executeCommandStub.should.have.been.calledTwice; executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); updateWorkspaceFoldersStub.should.have.been.calledWith(sinon.match.number, 0, { uri: uri }); - logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated smart contract project'); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Smart Contract Project'); await checkSmartContract(); sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createSmartContractProject', { contractLanguage: testLanguageItem.label }); }); it(`should start a ${testLanguageItem.label} smart contract project, in a new workspace with folders`, async () => { - quickPickStub.onFirstCall().resolves(testLanguageItem); + showLanguagesQuickPickStub.resolves(testLanguageItem); if (testLanguageItem.type === LanguageType.CONTRACT) { showInputBoxStub.onFirstCall().resolves('Conga'); } - quickPickStub.onSecondCall().resolves(UserInputUtil.ADD_TO_WORKSPACE); + showFolderOptionsStub.resolves(UserInputUtil.ADD_TO_WORKSPACE); browseStub.resolves(uri); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); executeCommandStub.should.have.been.calledTwice; executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); updateWorkspaceFoldersStub.should.have.been.calledWith(sinon.match.number, 0, { uri: uri }); - logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated smart contract project'); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Smart Contract Project'); await checkSmartContract(); sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createSmartContractProject', { contractLanguage: testLanguageItem.label }); }); } + it('should start a typescript private data smart contract project, in a new window', async () => { + showQuickPickItemStub.resolves({label: UserInputUtil.GENERATE_PD_CONTRACT, description: UserInputUtil.GENERATE_PD_CONTRACT_DESCRIPTION, data: 'private'}); + showInputBoxStub.onFirstCall().resolves('Org1MSP'); + showLanguagesQuickPickStub.resolves({ label: 'TypeScript', type: LanguageType.CONTRACT }); + showInputBoxStub.onSecondCall().resolves('Conga'); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_NEW_WINDOW); + browseStub.resolves(uri); + + await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); + executeCommandStub.should.have.been.calledThrice; + executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); + executeCommandStub.should.have.been.calledWith('vscode.openFolder', uri, true); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Private Data Smart Contract Project'); + sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createPrivateDataSmartContractProject', { contractLanguage: 'typescript' }); + }); + + it('should have the correct default asset name when user selects private data smart contract', async () => { + showQuickPickItemStub.resolves({label: UserInputUtil.GENERATE_PD_CONTRACT, description: UserInputUtil.GENERATE_PD_CONTRACT_DESCRIPTION, data: 'private'}); + showInputBoxStub.onFirstCall().resolves('Org1MSP'); + showLanguagesQuickPickStub.resolves({ label: 'TypeScript', type: LanguageType.CONTRACT }); + showInputBoxStub.onSecondCall().resolves('MyPrivateAsset'); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_NEW_WINDOW); + browseStub.resolves(uri); + + await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); + inputBoxSpy.should.have.been.calledWith('Name the type of asset managed by this smart contract', 'MyPrivateAsset'); + executeCommandStub.should.have.been.calledThrice; + executeCommandStub.should.have.been.calledWith('workbench.files.action.focusFilesExplorer'); + executeCommandStub.should.have.been.calledWith('vscode.openFolder', uri, true); + logSpy.should.have.been.calledWith(LogType.SUCCESS, 'Successfully generated Private Data Smart Contract Project'); + sendTelemetryEventStub.should.have.been.calledOnceWithExactly('createPrivateDataSmartContractProject', { contractLanguage: 'typescript' }); + }); + + it('should not do anything if the user cancels when asked for the mspID', async () => { + showQuickPickItemStub.resolves({label: UserInputUtil.GENERATE_PD_CONTRACT, description: UserInputUtil.GENERATE_PD_CONTRACT_DESCRIPTION, data: 'private'}); + showInputBoxStub.onFirstCall().resolves(undefined); + await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); + browseStub.should.not.have.been.called; + showLanguagesQuickPickStub.should.not.have.been.called; + sendTelemetryEventStub.should.not.have.been.called; + }); + + it('should not do anything if the user cancels the type of smart contract', async () => { + showQuickPickItemStub.resolves(undefined); + await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); + browseStub.should.not.have.been.called; + showLanguagesQuickPickStub.should.not.have.been.called; + showInputBoxStub.should.not.have.been.called; + sendTelemetryEventStub.should.not.have.been.called; + }); + it('should show error message if we fail to create a smart contract', async () => { mySandBox.stub(YeomanUtil, 'run').rejects(new Error('such error')); - quickPickStub.onCall(0).resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); + showLanguagesQuickPickStub.resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); showInputBoxStub.onFirstCall().resolves('Conga'); - quickPickStub.onCall(1).resolves(UserInputUtil.OPEN_IN_NEW_WINDOW); + showFolderOptionsStub.resolves(UserInputUtil.OPEN_IN_NEW_WINDOW); browseStub.resolves(uri); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); - logSpy.should.have.been.calledWith(LogType.ERROR, 'Issue creating smart contract project: such error'); + logSpy.should.have.been.calledWith(LogType.ERROR, 'Issue creating Smart Contract Project: such error'); sendTelemetryEventStub.should.not.have.been.called; }); it('should not do anything if the user cancels the open dialog', async () => { - quickPickStub.onCall(0).resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); + showLanguagesQuickPickStub.resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); showInputBoxStub.onFirstCall().resolves('Conga'); browseStub.resolves(); @@ -283,7 +344,7 @@ describe('CreateSmartContractProjectCommand', () => { }); it('should throw an error if the chosen folder has an invalid name', async () => { - quickPickStub.onCall(0).resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); + showLanguagesQuickPickStub.resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); showInputBoxStub.onFirstCall().resolves('Conga'); const badUri: vscode.Uri = vscode.Uri.file(' Invalid Directory! '); browseStub.resolves(badUri); @@ -295,9 +356,9 @@ describe('CreateSmartContractProjectCommand', () => { }); it('should not do anything if the user cancels the open project ', async () => { - quickPickStub.onCall(0).resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); + showLanguagesQuickPickStub.resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); showInputBoxStub.onFirstCall().resolves('Conga'); - quickPickStub.onCall(1).resolves(); + showFolderOptionsStub.resolves(); browseStub.resolves(uri); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); browseStub.should.have.been.calledOnce; @@ -306,26 +367,28 @@ describe('CreateSmartContractProjectCommand', () => { }); it('should not do anything if the user cancels chosing a smart contract language', async () => { - quickPickStub.resolves(undefined); + showLanguagesQuickPickStub.resolves(undefined); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); - quickPickStub.should.have.been.calledOnce; + showLanguagesQuickPickStub.should.have.been.calledOnce; + showQuickPickItemStub.should.have.been.calledOnce; showInputBoxStub.should.not.have.been.called; browseStub.should.not.have.been.called; }); it('should not do anything if the user cancels specifying an asset type', async () => { - quickPickStub.onCall(0).resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); + showLanguagesQuickPickStub.resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); showInputBoxStub.onCall(0).resolves(undefined); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); - quickPickStub.should.have.been.calledOnce; + showInputBoxStub.should.have.been.calledOnce; + showQuickPickItemStub.should.have.been.calledOnce; browseStub.should.not.have.been.called; }); it('should throw an error if the user specifies an invalid asset type', async () => { - quickPickStub.onCall(0).resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); + showLanguagesQuickPickStub.resolves({ label: 'JavaScript', type: LanguageType.CONTRACT }); showInputBoxStub.onCall(0).resolves('@xyz/myAsset'); await vscode.commands.executeCommand(ExtensionCommands.CREATE_SMART_CONTRACT_PROJECT); - quickPickStub.should.have.been.calledOnce; + showQuickPickItemStub.should.have.been.calledOnce; browseStub.should.not.have.been.called; logSpy.should.have.been.calledWith(LogType.ERROR, 'Invalid asset name, it should only contain lowercase and uppercase letters.'); }); diff --git a/packages/blockchain-extension/test/webview/TutorialGalleryView.test.ts b/packages/blockchain-extension/test/webview/TutorialGalleryView.test.ts index 8da9171e9c..762167f517 100644 --- a/packages/blockchain-extension/test/webview/TutorialGalleryView.test.ts +++ b/packages/blockchain-extension/test/webview/TutorialGalleryView.test.ts @@ -126,7 +126,7 @@ describe('TutorialGalleryView', () => { html.should.contain('
Tutorial 1
'); html.should.contain('

Local smart contract development

'); - html.should.contain(`
Follow the typical workflow from generating a new smart contract project, deploying code to the ${FabricRuntimeUtil.LOCAL_FABRIC} runtime, and testing your transactions via an application gateway.
`); + html.should.contain(`
Follow the typical workflow from generating a new default smart contract project, deploying code to the ${FabricRuntimeUtil.LOCAL_FABRIC} runtime, and testing your transactions via an application gateway.
`); html.should.contain('
Tutorial 2
'); html.should.contain('

Create a cloud blockchain deployment

'); @@ -169,7 +169,7 @@ describe('TutorialGalleryView', () => { result.should.contain('
Tutorial 1
'); result.should.contain('

Local smart contract development

'); - result.should.contain(`
Follow the typical workflow from generating a new smart contract project, deploying code to the ${FabricRuntimeUtil.LOCAL_FABRIC} runtime, and testing your transactions via an application gateway.
`); + result.should.contain(`
Follow the typical workflow from generating a new default smart contract project, deploying code to the ${FabricRuntimeUtil.LOCAL_FABRIC} runtime, and testing your transactions via an application gateway.
`); result.should.contain('
Tutorial 2
'); result.should.contain('

Create a cloud blockchain deployment

'); diff --git a/packages/blockchain-extension/test/webview/View.test.ts b/packages/blockchain-extension/test/webview/View.test.ts index 9b22497979..1240c79c3a 100644 --- a/packages/blockchain-extension/test/webview/View.test.ts +++ b/packages/blockchain-extension/test/webview/View.test.ts @@ -291,7 +291,7 @@ describe('View', () => { { title: 'Local smart contract development', length: '20-30 mins', - description: `Follow the typical workflow from generating a new smart contract project, deploying code to the ${FabricRuntimeUtil.LOCAL_FABRIC} runtime, and testing your transactions via an application gateway.`, + description: `Follow the typical workflow from generating a new default smart contract project, deploying code to the ${FabricRuntimeUtil.LOCAL_FABRIC} runtime, and testing your transactions via an application gateway.`, file: 'ibm-blockchain-platform-vscode-smart-contract/local-dev.md' }, { diff --git a/packages/blockchain-extension/tutorials.json b/packages/blockchain-extension/tutorials.json index 1b98091170..96440cc12b 100644 --- a/packages/blockchain-extension/tutorials.json +++ b/packages/blockchain-extension/tutorials.json @@ -41,7 +41,7 @@ { "title": "Local smart contract development", "length": "20-30 mins", - "description": "Follow the typical workflow from generating a new smart contract project, deploying code to the Local Fabric runtime, and testing your transactions via an application gateway.", + "description": "Follow the typical workflow from generating a new default smart contract project, deploying code to the Local Fabric runtime, and testing your transactions via an application gateway.", "file": "ibm-blockchain-platform-vscode-smart-contract/local-dev.md" }, { diff --git a/packages/blockchain-extension/tutorials/developer-tutorials/createNewIdentityAttributes.md b/packages/blockchain-extension/tutorials/developer-tutorials/createNewIdentityAttributes.md index 0ec8d288ad..46556eb137 100644 --- a/packages/blockchain-extension/tutorials/developer-tutorials/createNewIdentityAttributes.md +++ b/packages/blockchain-extension/tutorials/developer-tutorials/createNewIdentityAttributes.md @@ -48,17 +48,19 @@ As an example, suppose we have a smart contract which allows participants to rec > Command Palette alternative: `Create New Project` -3. You will first be asked to choose a smart contract language, choose `Typescript` +3. For this tutorial, choose the `Default Contract` option. -4. The extension will ask you if you want to name the asset in the generated contract. Next the extension will ask you for an asset name, for this tutorial enter `Car`. +4. You will now be asked to choose a smart contract language, choose `Typescript`. -5. Choose a location to save the project. Click `Browse`, then click `New Folder`, and give the project a name, for example `carContract`. +5. The extension will ask you if you want to name the asset in the generated contract. Next the extension will ask you for an asset name, for this tutorial enter `Car`. + +6. Choose a location to save the project. Click `Browse`, then click `New Folder`, and give the project a name, for example `carContract`. > __Pro Tip:__ Avoid using spaces when naming the project! -6. Click `Create` and then select the new folder you just created and click `Save`. +7. Click `Create` and then select the new folder you just created and click `Save`. -7. Finally you will be asked how you want to open the project, choose `Add to workspace` from the list of options. +8. Finally you will be asked how you want to open the project, choose `Add to workspace` from the list of options. diff --git a/packages/blockchain-extension/tutorials/developer-tutorials/debug.md b/packages/blockchain-extension/tutorials/developer-tutorials/debug.md index 1d304c8c2b..5204cf418c 100644 --- a/packages/blockchain-extension/tutorials/developer-tutorials/debug.md +++ b/packages/blockchain-extension/tutorials/developer-tutorials/debug.md @@ -23,25 +23,27 @@ For the purposes of this tutorial, we'll use TypeScript as the example language. 1. In the left sidebar, click on the __IBM Blockchain Platform__ icon (it looks like a square, and will probably be at the bottom of the set of icons if this was the latest extension you installed!) -2. Mouse-over the `SMART CONTRACT PACKAGES` panel, click the `...` menu, and select `Create Smart Contract Project` from the dropdown. +2. Mouse-over the `SMART CONTRACT PACKAGES` panel, click the `...` menu, and select `Create New Project` from the dropdown. -> Command Palette alternative: `Create Smart Contract Project` +> Command Palette alternative: `Create New Project` -3. Choose a smart contract language. JavaScript, TypeScript, Java and Go are all available. For the purpose of this tutorial, please choose `TypeScript`. +3. For this tutorial, choose the `Default Contract` option. -4. The extension will ask you if you want to name the asset in the generated contract. For this tutorial we’ll stick with the default of MyAsset. +4. Choose a smart contract language. JavaScript, TypeScript, Java and Go are all available. For the purpose of this tutorial, please choose `TypeScript`. -5. Choose a location to save the project. Click `Browse`, then click `New Folder`, and name the project what you want e.g. `demoContract`. +5. The extension will ask you if you want to name the asset in the generated contract. For this tutorial we’ll stick with the default of MyAsset. + +6. Choose a location to save the project. Click `Browse`, then click `New Folder`, and name the project what you want e.g. `demoContract`. > __Pro Tip:__ Avoid using spaces when naming the project! -6. Click `Create` and then select the new folder you just created and click `Save`. +7. Click `Create` and then select the new folder you just created and click `Save`. -7. Select `Add to workspace` from the list of options. +8. Select `Add to workspace` from the list of options. -8. The extension will generate you a skeleton contract based on your selected language and asset name. Once it's done, you can navigate to the __Explorer__ view (most-likely the top icon in the left sidebar, which looks like a "document" icon) and open the `src/my-asset-contract.ts` file to see your smart contract code scaffold. +9. The extension will generate you a skeleton contract based on your selected language and asset name. Once it's done, you can navigate to the __Explorer__ view (most-likely the top icon in the left sidebar, which looks like a "document" icon) and open the `src/my-asset-contract.ts` file to see your smart contract code scaffold. -9. Add a new function with the following code. This function creates an asset and added it to the world state. +10. Add a new function with the following code. This function creates an asset and added it to the world state. ``` @Transaction() @@ -53,7 +55,7 @@ For the purposes of this tutorial, we'll use TypeScript as the example language. } ``` -10. Save the file. +11. Save the file. diff --git a/packages/blockchain-extension/tutorials/ibm-blockchain-platform-vscode-smart-contract/local-dev.md b/packages/blockchain-extension/tutorials/ibm-blockchain-platform-vscode-smart-contract/local-dev.md index 8e62cd3628..9fb3b10717 100644 --- a/packages/blockchain-extension/tutorials/ibm-blockchain-platform-vscode-smart-contract/local-dev.md +++ b/packages/blockchain-extension/tutorials/ibm-blockchain-platform-vscode-smart-contract/local-dev.md @@ -2,12 +2,12 @@ ## **Local Smart Contract Development** `20-30 mins` -Follow the typical workflow from generating a new smart contract project, deploying code to the _Local Fabric_ environment, and testing out your transactions via an application gateway. +Follow the typical workflow from generating a new default smart contract project, deploying code to the _Local Fabric_ environment, and testing out your transactions via an application gateway. ## Learning Objectives -* Create a new smart contract project +* Create a new default smart contract project * Package a smart contract * Start and use the local, pre-configured Hyperledger Fabric environment * Deploy the smart contract on _Local Fabric_ @@ -16,7 +16,7 @@ Follow the typical workflow from generating a new smart contract project, deploy ---
-1. Create a new smart contract project +1. Create a new default smart contract project The extension can generate a smart contract skeleton in your chosen Hyperledger Fabric supported programming language. This means you start with a basic but useful smart contract rather than a blank-sheet. @@ -30,19 +30,21 @@ For the purposes of this tutorial, we'll use TypeScript as the main example lang > Command Palette alternative: `Create New Project` -3. Choose a smart contract language. JavaScript, TypeScript, Java and Go are all available. This tutorial will be easiest to follow if you choose `TypeScript` or `Java` (please remember to expand the Java sections if you choose Java). +3. For this tutorial, choose the `Default Contract` option. The `Private Data Contract` will be covered in a future tutorial. -4. The extension will ask you if you want to name the asset in the generated contract. This will default to `MyAsset`, but you're welcome to change it. What do you intend to use your blockchain for? This will determine what type of asset you create, update and read from the ledger: `Radish`? `Pineapple`? `Penguin`? Pick whatever you like! For this tutorial, we'll stick with `MyAsset`. +4. Choose a smart contract language. JavaScript, TypeScript, Java and Go are all available. This tutorial will be easiest to follow if you choose `TypeScript` or `Java` (please remember to expand the Java sections if you choose Java). + +5. The extension will ask you if you want to name the asset in the generated contract. This will default to `MyAsset`, but you're welcome to change it. What do you intend to use your blockchain for? This will determine what type of asset you create, update and read from the ledger: `Radish`? `Pineapple`? `Penguin`? Pick whatever you like! For this tutorial, we'll stick with `MyAsset`. > __Pro Tip:__ If you decide to change the name of your asset, remember to swap out `MyAsset` for whatever you named it in future steps! -5. Choose a location to save the project. Click `Browse`, then click `New Folder`, and name the project what you want e.g. `demoContract`. +6. Choose a location to save the project. Click `Browse`, then click `New Folder`, and name the project what you want e.g. `demoContract`. > __Pro Tip:__ Avoid using spaces when naming the project! -6. Click `Create` and then select the new folder you just created and click `Save`. +7. Click `Create` and then select the new folder you just created and click `Save`. -7. Finally, select `Add to workspace` from the list of options. +8. Finally, select `Add to workspace` from the list of options. The extension will generate you a skeleton contract based on your selected language and asset name. Once it's done, you can navigate to the __Explorer__ view (most-likely the top icon in the left sidebar, which looks like a "document" icon) and open the `src/my-asset-contract.ts` (alternatively, Java contracts are in `src/main/java` directory, but being a Java developer you might already have guessed that). Congratulations, you've got yourself a smart contract project. From 6f8872c61041a4d9507efc308e9c2a87feec448d Mon Sep 17 00:00:00 2001 From: Caroline Fletcher Date: Wed, 19 Feb 2020 15:07:29 +0000 Subject: [PATCH 2/2] Move private data cucumber tests Move from local fabric to ansible Signed-off-by: Caroline Fletcher --- .../cucumber/features/create.feature | 1 + .../cucumber/features/install.feature | 17 +++++++++++++++++ .../cucumber/features/instantiate.feature | 10 +++++----- .../cucumber/features/submit.feature | 12 ++++++------ 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/blockchain-extension/cucumber/features/create.feature b/packages/blockchain-extension/cucumber/features/create.feature index c036569c4d..8f7af52d2b 100644 --- a/packages/blockchain-extension/cucumber/features/create.feature +++ b/packages/blockchain-extension/cucumber/features/create.feature @@ -13,6 +13,7 @@ Feature: Created Smart Contracts | Java | Conga | JavaContract | 0.0.1 | | Go | null | GoContract | 0.0.1 | + @ansibleFabric Scenario Outline: Create a private data smart contract Given a private smart contract for assets with the name and version and mspid And the contract hasn't been created already diff --git a/packages/blockchain-extension/cucumber/features/install.feature b/packages/blockchain-extension/cucumber/features/install.feature index 5414e6f7f1..e0eadf839f 100644 --- a/packages/blockchain-extension/cucumber/features/install.feature +++ b/packages/blockchain-extension/cucumber/features/install.feature @@ -16,3 +16,20 @@ Feature: Install Smart Contract | TypeScript | Conga | TypeScriptContract | TypeScriptContract@0.0.1 | 0.0.1 | | Java | Conga | JavaContract | JavaContract@0.0.1 | 0.0.1 | | Go | null | GoContract | GoContract@0.0.1 | 0.0.1 | + + + @ansibleFabric + Scenario Outline: Install a smart contract + Given a private smart contract for assets with the name and version and mspid + Given an environment 'myAnsibleFabric' exists + And the 'myAnsibleFabric' environment is connected + And the private contract has been created + And the contract has been packaged + When I install the package + Then there should be a installed smart contract tree item with a label '' in the 'Fabric Environments' panel + And the tree item should have a tooltip equal to 'Installed on: Org1Peer1, Org1Peer2, Org2Peer1, Org2Peer2' + Examples: + | language | assetType | name | installedName | version | + | JavaScript | PrivateConga | PrivateJavaScriptContract | PrivateJavaScriptContract@0.0.1 | 0.0.1 | + | TypeScript | PrivateConga | PrivateTypeScriptContract | PrivateTypeScriptContract@0.0.1 | 0.0.1 | + | Java | PrivateConga | PrivateJavaContract | PrivateJavaContract@0.0.1 | 0.0.1 | diff --git a/packages/blockchain-extension/cucumber/features/instantiate.feature b/packages/blockchain-extension/cucumber/features/instantiate.feature index 028433fb42..730aa5b20d 100644 --- a/packages/blockchain-extension/cucumber/features/instantiate.feature +++ b/packages/blockchain-extension/cucumber/features/instantiate.feature @@ -18,17 +18,17 @@ Feature: Instantiate Smart Contracts | Java | Conga | JavaContract | JavaContract@0.0.1 | 0.0.1 | | Go | null | GoContract | GoContract@0.0.1 | 0.0.1 | - + @ansibleFabric Scenario Outline: Instantiate a private smart contract Given a private smart contract for assets with the name and version and mspid - And the Local Fabric is running - And the 'Local Fabric' environment is connected + Given an environment 'myAnsibleFabric' exists + And the 'myAnsibleFabric' environment is connected And the private contract has been created And the contract has been packaged And the package has been installed - When I instantiate the installed package with the transaction '' and args '', using private data on channel 'mychannel' + When I instantiate the installed package with the transaction '' and args '', using private data on channel 'channel1' Then there should be an instantiated smart contract tree item with a label '' in the 'Fabric Environments' panel - And the tree item should have a tooltip equal to 'Instantiated on: mychannel' + And the tree item should have a tooltip equal to 'Instantiated on: channel1' Examples: | language | assetType | name | instantiatedName | mspid | version | | JavaScript | PrivateConga | PrivateJavaScriptContract | PrivateJavaScriptContract@0.0.1 | Org1MSP | 0.0.1 | diff --git a/packages/blockchain-extension/cucumber/features/submit.feature b/packages/blockchain-extension/cucumber/features/submit.feature index ad5c3387ec..94093e5538 100644 --- a/packages/blockchain-extension/cucumber/features/submit.feature +++ b/packages/blockchain-extension/cucumber/features/submit.feature @@ -40,17 +40,17 @@ Feature: Submit transaction | language | assetType | name | version | | TypeScript | Conga | TypeScriptContract | 0.0.1 | + @ansibleFabric Scenario Outline: Submit a verify transaction for a private data smart contract Given a private smart contract for assets with the name and version and mspid - And the Local Fabric is running - And the 'Local Fabric' environment is connected - And the 'Org1' wallet - And the 'Local Fabric Admin' identity - And I'm connected to the 'Local Fabric - Org1' gateway + Given an environment 'myAnsibleFabric' exists + And the 'myAnsibleFabric' environment is connected + And the 'admin' identity + And I'm connected to the 'myAnsibleFabric - Org1 gateway' gateway And the private contract has been created And the contract has been packaged And the package has been installed - And the contract has been instantiated with the transaction '' and args '', using private data on channel 'mychannel' + And the contract has been instantiated with the transaction '' and args '', using private data on channel 'channel1' When I submit the transaction 'createPrivateConga' on the channel 'mychannel' with args '["001"]' and with the transient data '{"privateValue":"125"}' Then the logger should have been called with 'SUCCESS', 'Successfully submitted transaction' and 'No value returned from createPrivateConga' When I submit the transaction 'verifyPrivateConga' on the channel 'mychannel' with args '["001", "{\"privateValue\":\"125\"}"]'