diff --git a/packages/composer-playground-api/routes/npm.js b/packages/composer-playground-api/routes/npm.js index de3f401f60..2b15bc6840 100644 --- a/packages/composer-playground-api/routes/npm.js +++ b/packages/composer-playground-api/routes/npm.js @@ -158,10 +158,12 @@ module.exports = (app) => { name : metadata.name, description : metadata.description, version : metadata.version, + //TODO update this after release + networkImage : 'https://hyperledger.github.io/composer-sample-networks/packages/' + metadata.name + '/networkimage.svg', + networkImageanimated: 'https://hyperledger.github.io/composer-sample-networks/packages/' + metadata.name + '/networkimageanimated.svg', tarball : metadata.dist.tarball }); } - }); LOG.exit(method, options); diff --git a/packages/composer-playground-api/test/npm.js b/packages/composer-playground-api/test/npm.js index 6afa81c734..aa92000432 100644 --- a/packages/composer-playground-api/test/npm.js +++ b/packages/composer-playground-api/test/npm.js @@ -248,8 +248,8 @@ describe('npm routes', () => { beforeEach(() => { mock = { - 'composer-common/package.json': { - version: composerVersion + 'composer-common/package.json' : { + version : composerVersion }, 'npm-registry-client' : RegClient }; @@ -276,6 +276,8 @@ describe('npm routes', () => { name : 'bob', description : 'bob package', version : '1.0', + networkImage : 'https://hyperledger.github.io/composer-sample-networks/packages/bob/networkimage.svg', + networkImageanimated: 'https://hyperledger.github.io/composer-sample-networks/packages/bob/networkimageanimated.svg', tarball : 'my tar' }]); }); @@ -293,17 +295,23 @@ describe('npm routes', () => { name : 'ant', description : 'ant package', version : '1.0', - tarball : 'my tar' + tarball : 'my tar', + networkImage : 'https://hyperledger.github.io/composer-sample-networks/packages/ant/networkimage.svg', + networkImageanimated: 'https://hyperledger.github.io/composer-sample-networks/packages/ant/networkimageanimated.svg', }, { name : 'bat', description : 'bat package', version : '1.0', - tarball : 'my tar' + tarball : 'my tar', + networkImage : 'https://hyperledger.github.io/composer-sample-networks/packages/bat/networkimage.svg', + networkImageanimated: 'https://hyperledger.github.io/composer-sample-networks/packages/bat/networkimageanimated.svg', }, { name : 'cat', description : 'cat package', version : '1.0', - tarball : 'my tar' + tarball : 'my tar', + networkImage : 'https://hyperledger.github.io/composer-sample-networks/packages/cat/networkimage.svg', + networkImageanimated: 'https://hyperledger.github.io/composer-sample-networks/packages/cat/networkimageanimated.svg', }]); }); }); @@ -362,9 +370,6 @@ describe('npm routes', () => { }); }); }); - }); - }); - }); diff --git a/packages/composer-playground/src/app/app.component.html b/packages/composer-playground/src/app/app.component.html index 633d329cfa..5db312f432 100644 --- a/packages/composer-playground/src/app/app.component.html +++ b/packages/composer-playground/src/app/app.component.html @@ -46,7 +46,7 @@ - - + +
{{ v1FormErrors.orderers.cert }} -
+
-
- + + +
Channel* -
+
@@ -266,26 +266,26 @@ (click)="openAddCertificateModal(i,'peers');"> + -
+
Peer Event URL* -
+
{{ v1FormErrors.peers.eventURL }} -
+
- + - - + + @@ -310,8 +310,7 @@ - - +
Advanced
diff --git a/packages/composer-playground/src/app/editor/editor.component.spec.ts b/packages/composer-playground/src/app/editor/editor.component.spec.ts index 8738176417..dd1b6679fb 100644 --- a/packages/composer-playground/src/app/editor/editor.component.spec.ts +++ b/packages/composer-playground/src/app/editor/editor.component.spec.ts @@ -14,14 +14,16 @@ import { ClientService } from '../services/client.service'; import { EditorService } from './editor.service'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { AlertService } from '../basic-modals/alert.service'; -import { ModelFile, Script, AclManager, AclFile, QueryFile } from 'composer-common'; +import { ModelFile, Script, AclFile, QueryFile } from 'composer-common'; import { ScrollToElementDirective } from '../directives/scroll/scroll-to-element.directive'; +import { BehaviorSubject } from 'rxjs/Rx'; import * as sinon from 'sinon'; import * as chai from 'chai'; import 'rxjs/add/operator/takeWhile'; import * as fileSaver from 'file-saver'; +import { DrawerService } from '../common/drawer/drawer.service'; let should = chai.should(); @@ -60,6 +62,7 @@ describe('EditorComponent', () => { let mockAlertService; let mockClientService; let mockModal; + let mockDrawer; let mockModelFile; let mockScriptFile; let mockRuleFile; @@ -71,6 +74,7 @@ describe('EditorComponent', () => { mockAlertService = sinon.createStubInstance(AlertService); mockClientService = sinon.createStubInstance(ClientService); mockModal = sinon.createStubInstance(NgbModal); + mockDrawer = sinon.createStubInstance(DrawerService); mockModelFile = sinon.createStubInstance(ModelFile); mockScriptFile = sinon.createStubInstance(Script); mockRuleFile = sinon.createStubInstance(AclFile); @@ -91,7 +95,8 @@ describe('EditorComponent', () => { {provide: ClientService, useValue: mockClientService}, {provide: NgbModal, useValue: mockModal}, {provide: AlertService, useValue: mockAlertService}, - {provide: EditorService, useValue: editorService}] + {provide: EditorService, useValue: editorService}, + {provide: DrawerService, useValue: mockDrawer}] }); fixture = TestBed.createComponent(EditorComponent); @@ -1013,13 +1018,19 @@ describe('EditorComponent', () => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); let mockUpdateFiles = sinon.stub(component, 'updateFiles'); - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve() + let finishedImport = new BehaviorSubject(true); + + mockDrawer.open = sinon.stub().returns({ + componentInstance: { + finishedSampleImport: finishedImport + }, + close: sinon.stub() }); component.openImportModal(); + finishedImport.next({deployed: true}); + tick(); mockUpdatePackage.should.have.been.called; @@ -1033,13 +1044,19 @@ describe('EditorComponent', () => { component['files'] = [{readme: true}, {model: true}]; - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve() + let finishedImport = new BehaviorSubject(true); + + mockDrawer.open = sinon.stub().returns({ + componentInstance: { + finishedSampleImport: finishedImport + }, + close: sinon.stub() }); component.openImportModal(); + finishedImport.next({deployed: true}); + tick(); mockUpdatePackage.should.have.been.called; @@ -1055,13 +1072,19 @@ describe('EditorComponent', () => { component['files'] = [{model: true}, {script: true}]; - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve() + let finishedImport = new BehaviorSubject(true); + + mockDrawer.open = sinon.stub().returns({ + componentInstance: { + finishedSampleImport: finishedImport + }, + close: sinon.stub() }); component.openImportModal(); + finishedImport.next({deployed: true}); + tick(); mockUpdatePackage.should.have.been.called; @@ -1074,13 +1097,19 @@ describe('EditorComponent', () => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); let mockUpdateFiles = sinon.stub(component, 'updateFiles'); - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.reject('some error') + let finishedImport = new BehaviorSubject(true); + + mockDrawer.open = sinon.stub().returns({ + componentInstance: { + finishedSampleImport: finishedImport + }, + close: sinon.stub() }); component.openImportModal(); + finishedImport.next({deployed: false, error: 'some error'}); + tick(); mockUpdatePackage.should.not.have.been.called; @@ -1093,13 +1122,19 @@ describe('EditorComponent', () => { let mockUpdatePackage = sinon.stub(component, 'updatePackageInfo'); let mockUpdateFiles = sinon.stub(component, 'updateFiles'); - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.reject(1) + let finishedImport = new BehaviorSubject(true); + + mockDrawer.open = sinon.stub().returns({ + componentInstance: { + finishedSampleImport: finishedImport + }, + close: sinon.stub() }); component.openImportModal(); + finishedImport.next({deployed: false}); + tick(); mockUpdatePackage.should.not.have.been.called; diff --git a/packages/composer-playground/src/app/editor/editor.component.ts b/packages/composer-playground/src/app/editor/editor.component.ts index cef73bc63b..eb45a60dc8 100644 --- a/packages/composer-playground/src/app/editor/editor.component.ts +++ b/packages/composer-playground/src/app/editor/editor.component.ts @@ -1,17 +1,27 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { ImportComponent } from './import/import.component'; +import { ImportComponent } from '../import/import.component'; import { AddFileComponent } from './add-file/add-file.component'; import { DeleteComponent } from '../basic-modals/delete-confirm/delete-confirm.component'; import { ReplaceComponent } from '../basic-modals/replace-confirm'; +import { DrawerService } from '../common/drawer/drawer.service'; import { AdminService } from '../services/admin.service'; import { ClientService } from '../services/client.service'; import { AlertService } from '../basic-modals/alert.service'; import { EditorService } from './editor.service'; -import { ModelFile, Script, ScriptManager, ModelManager, AclManager, AclFile, QueryFile, QueryManager } from 'composer-common'; +import { + ModelFile, + Script, + ScriptManager, + ModelManager, + AclManager, + AclFile, + QueryFile, + QueryManager +} from 'composer-common'; import 'rxjs/add/operator/takeWhile'; import { saveAs } from 'file-saver'; @@ -60,7 +70,8 @@ export class EditorComponent implements OnInit, OnDestroy { private clientService: ClientService, private modalService: NgbModal, private alertService: AlertService, - private editorService: EditorService) { + private editorService: EditorService, + private drawerService: DrawerService) { } @@ -375,29 +386,35 @@ export class EditorComponent implements OnInit, OnDestroy { } openImportModal() { - const importModalRef = this.modalService.open(ImportComponent); + const importModalRef = this.drawerService.open(ImportComponent); // only want to update here not deploy importModalRef.componentInstance.deployNetwork = false; - importModalRef.result.then((result) => { - this.updatePackageInfo(); - this.updateFiles(); - if (this.files.length) { - let currentFile = this.files.find((file) => { - return file.readme; - }); - if (!currentFile) { - currentFile = this.files[0]; + + importModalRef.componentInstance.finishedSampleImport.subscribe((result) => { + + importModalRef.close(); + + if (result.deployed) { + this.updatePackageInfo(); + this.updateFiles(); + if (this.files.length) { + let currentFile = this.files.find((file) => { + return file.readme; + }); + if (!currentFile) { + currentFile = this.files[0]; + } + this.setCurrentFile(currentFile); + this.alertService.successStatus$.next({ + title: 'Deploy Successful', + text: 'Business network imported deployed successfully', + icon: '#icon-deploy_24' + }); } - this.setCurrentFile(currentFile); - this.alertService.successStatus$.next({ - title: 'Deploy Successful', - text: 'Business network imported deployed successfully', - icon: '#icon-deploy_24' - }); - } - }, (reason) => { - if (reason && reason !== 1) { - this.alertService.errorStatus$.next(reason); + } else { + if (result.error) { + this.alertService.errorStatus$.next(result.error); + } } }); } @@ -670,13 +687,13 @@ export class EditorComponent implements OnInit, OnDestroy { file.invalid = false; } } else if (file.query) { - let query = this.clientService.getQueryFile(); - if (this.clientService.validateFile(file.id, query.getDefinitions(), 'query') !== null) { - allValid = false; - file.invalid = true; - } else { - file.invalid = false; - } + let query = this.clientService.getQueryFile(); + if (this.clientService.validateFile(file.id, query.getDefinitions(), 'query') !== null) { + allValid = false; + file.invalid = true; + } else { + file.invalid = false; + } } } return allValid; diff --git a/packages/composer-playground/src/app/editor/editor.module.ts b/packages/composer-playground/src/app/editor/editor.module.ts index bbdff5e87a..6a145ea05f 100644 --- a/packages/composer-playground/src/app/editor/editor.module.ts +++ b/packages/composer-playground/src/app/editor/editor.module.ts @@ -10,15 +10,15 @@ import { EditorFileComponent } from './editor-file/editor-file.component'; import { EditorService } from './editor.service'; import { EditorRoutingModule } from './editor-routing.module'; import { AddFileComponent } from './add-file/add-file.component'; -import { ImportComponent } from './import/import.component'; import { FileImporterModule } from '../common/file-importer/file-importer.module'; import { DirectivesModule } from '../directives/directives.module'; +import { ImportModule } from '../import/import.module'; import { FooterModule } from '../footer/footer.module'; @NgModule({ - imports: [CommonModule, FormsModule, NgbModule, PerfectScrollbarModule, CodemirrorModule, DirectivesModule, FileImporterModule, EditorRoutingModule, FooterModule], - entryComponents: [AddFileComponent, ImportComponent], - declarations: [EditorComponent, EditorFileComponent, AddFileComponent, ImportComponent], + imports: [CommonModule, FormsModule, NgbModule, PerfectScrollbarModule, CodemirrorModule, DirectivesModule, FileImporterModule, ImportModule, EditorRoutingModule, FooterModule], + entryComponents: [AddFileComponent], + declarations: [EditorComponent, EditorFileComponent, AddFileComponent], providers: [EditorService] }) diff --git a/packages/composer-playground/src/app/editor/import/import.component.html b/packages/composer-playground/src/app/editor/import/import.component.html deleted file mode 100644 index 2ba38edb08..0000000000 --- a/packages/composer-playground/src/app/editor/import/import.component.html +++ /dev/null @@ -1,114 +0,0 @@ -
- - -
- - -
-
diff --git a/packages/composer-playground/src/app/editor/import/import.component.scss b/packages/composer-playground/src/app/editor/import/import.component.scss deleted file mode 100644 index 600b7cac67..0000000000 --- a/packages/composer-playground/src/app/editor/import/import.component.scss +++ /dev/null @@ -1,129 +0,0 @@ -@import '../../../assets/styles/base/colors'; -@import '../../../assets/styles/base/variables'; - -import-modal { - .import { - width: 700px; - height: 525px; - - display: flex; - flex-direction: column; - - section { - font-size: 0.9em; - - file-importer { - margin-top: $space-medium; - } - - span { - color: $secondary-text; - } - - .chosen-file { - flex: 1; - display: flex; - flex-direction: column; - background-color: $third-highlight; - padding: $space-medium; - - .title { - font-weight: bold; - } - - .file-info { - display: flex; - justify-content: space-between; - border-bottom: 1px solid $fifth-highlight; - padding-bottom: $space-medium; - align-items: center; - - svg { - fill: $first-highlight; - width: 76px; - height: 76px; - margin-right: $space-medium; - } - - .file-title { - display: flex; - flex-direction: column; - justify-content: center; - - span { - color: $primary-text; - font-style: italic; - } - } - } - - .file-content { - margin-top: $space-medium; - flex: 1; - display: flex; - justify-content: space-between; - - .network-part { - margin-bottom: $space-smedium; - } - } - } - - .github-spinner { - height: 200px; - width: 100%; - display: flex; - align-items: center; - justify-content: center; - - .circle-path { - stroke: $first-highlight; - } - } - - .sample-network-list-container { - max-height: 200px; - overflow-y: scroll; - margin-top: $space-medium; - } - - .sample-network-list { - background-color: $third-highlight; - padding: $space-medium; - &:nth-child(even) { - background-color: $white; - } - - .description { - font-style: italic; - padding-left: 2em; - } - - label.radio-label { - font-weight: bold; - - &::before { - top: 8px; - } - - &::after { - top: 13px; - } - } - } - } - - footer { - .circle-path { - stroke: $white; - } - - width: 100%; - } - } - - .ps.ps--active-x>.ps__scrollbar-x-rail, - .ps.ps--active-y>.ps__scrollbar-y-rail{ - opacity: 0.6; - } -} diff --git a/packages/composer-playground/src/app/editor/import/import.component.ts b/packages/composer-playground/src/app/editor/import/import.component.ts index d6c8817267..e69de29bb2 100644 --- a/packages/composer-playground/src/app/editor/import/import.component.ts +++ b/packages/composer-playground/src/app/editor/import/import.component.ts @@ -1,235 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; - -import { AdminService } from '../../services/admin.service'; -import { ClientService } from '../../services/client.service'; -import { SampleBusinessNetworkService } from '../../services/samplebusinessnetwork.service'; -import { AlertService } from '../../basic-modals/alert.service'; -import { ReplaceComponent } from '../../basic-modals/replace-confirm'; - -import { BusinessNetworkDefinition } from 'composer-common'; -import { ErrorComponent } from '../../basic-modals/error'; - -const fabricComposerOwner = 'hyperledger'; -const fabricComposerRepository = 'composer-sample-networks'; - -@Component({ - selector: 'import-modal', - templateUrl: './import.component.html', - styleUrls: ['./import.component.scss'.toString()] -}) -export class ImportComponent implements OnInit { - - // choose whether to deploy or update the business network - @Input() deployNetwork: boolean; - - private deployInProgress: boolean = false; - private gitHubInProgress: boolean = false; - private sampleNetworks = []; - private primaryNetworkNames = ['basic-sample-network']; - private chosenNetwork = null; - private expandInput: boolean = false; - - private maxFileSize: number = 5242880; - private supportedFileTypes: string[] = ['.bna']; - - private _currentBusinessNetwork = null; - - set currentBusinessNetwork(businessNetwork) { - this._currentBusinessNetwork = businessNetwork; - if (businessNetwork instanceof BusinessNetworkDefinition) { - this.currentAssets = businessNetwork.getModelManager().getAssetDeclarations().filter((d) => !d.isAbstract() && !d.isSystemType()); - this.currentParticipants = businessNetwork.getModelManager().getParticipantDeclarations().filter((d) => !d.isAbstract() && !d.isSystemType()); - this.currentTransactions = businessNetwork.getModelManager().getTransactionDeclarations().filter((d) => !d.isAbstract() && !d.isSystemType()); - } - } - - get currentBusinessNetwork() { - return this._currentBusinessNetwork; - } - - private currentAssets = []; - private currentParticipants = []; - private currentTransactions = []; - - private NAME = 'Empty Business Network'; - private DESC = 'Start from scratch with a blank business network'; - private EMPTY_BIZNET = {name: this.NAME, description: this.DESC}; - - constructor(private clientService: ClientService, - public activeModal: NgbActiveModal, - public modalService: NgbModal, - private sampleBusinessNetworkService: SampleBusinessNetworkService, - private alertService: AlertService, - private adminService: AdminService) { - - } - - ngOnInit(): Promise { - this.currentBusinessNetwork = null; - - return this.adminService.connectWithoutNetwork(false) - .then(() => { - this.onShow(); - }); - } - - onShow() { - this.gitHubInProgress = true; - this.sampleBusinessNetworkService.getSampleList() - .then((sampleNetworkList) => { - this.sampleNetworks = this.addEmptyNetworkOption(sampleNetworkList); - this.gitHubInProgress = false; - - }) - .catch((error) => { - this.gitHubInProgress = false; - this.alertService.errorStatus$.next(error); - }); - } - - addEmptyNetworkOption(networks: any[]): any[] { - - let newOrder = []; - - // Append new network option to the list. - newOrder.push(this.EMPTY_BIZNET); - - for (let i = 0; i < networks.length; i++) { - newOrder.push(networks[i]); - } - - return newOrder; - } - - removeFile() { - this.expandInput = false; - this.currentBusinessNetwork = null; - } - - deploy() { - const confirmModalRef = this.modalService.open(ReplaceComponent); - confirmModalRef.componentInstance.mainMessage = 'Your Business Network Definition currently in the Playground will be removed & replaced.'; - confirmModalRef.componentInstance.supplementaryMessage = 'Please ensure that you have exported any current model files in the Playground.'; - confirmModalRef.result.then((result) => { - if (result === true) { - this.deployInProgress = true; - let deployPromise; - if (this.currentBusinessNetwork) { - deployPromise = this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork, this.deployNetwork); - } else { - deployPromise = this.deployFromNpm(); - } - - deployPromise.then(() => { - this.deployInProgress = false; - let deployedBusinessNetwork = this.currentBusinessNetwork ? this.currentBusinessNetwork.getName() : this.chosenNetwork; - this.activeModal.close(deployedBusinessNetwork); - }) - .catch((error) => { - this.deployInProgress = false; - this.alertService.busyStatus$.next(null); - this.alertService.errorStatus$.next(error); - }); - - return deployPromise; - } - }) - .catch((error) => { - this.deployInProgress = false; - if (error && error !== 1) { - this.alertService.errorStatus$.next(error); - } - }); - } - - deployFromNpm(): Promise { - - if (this.chosenNetwork === this.NAME - ) { - let readme = 'This is the readme file for the Business Network Definition created in Playground'; - let packageJson = { - name: 'unnamed-network', - author: 'author', - description: 'Empty Business Network', - version: '0.0.1', - devDependencies: { - 'browserfs': '^1.2.0', - 'chai': '^3.5.0', - 'composer-admin': 'latest', - 'composer-cli': 'latest', - 'composer-client': 'latest', - 'composer-connector-embedded': 'latest', - 'eslint': '^3.6.1', - 'istanbul': '^0.4.5', - 'jsdoc': '^3.4.1', - 'mkdirp': '^0.5.1', - 'mocha': '^3.2.0', - 'moment': '^2.17.1' - }, - keywords: [], - license: 'Apache 2.0', - repository: { - type: 'e.g. git', - url: 'URL' - }, - scripts: { - deploy: './scripts/deploy.sh', - doc: 'jsdoc --pedantic --recurse -c jsdoc.conf', - lint: 'eslint .', - postlicchk: 'npm run doc', - postlint: 'npm run licchk', - prepublish: 'mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/unnamed-network.bna', - pretest: 'npm run lint', - test: 'mocha --recursive' - } - }; - let emptyBizNetDef = new BusinessNetworkDefinition('', '', packageJson, readme); - this.currentBusinessNetwork = emptyBizNetDef; - return this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork, this.deployNetwork); - - } else { - - let chosenSampleNetwork = this.sampleNetworks.find((sampleNetwork) => { - return sampleNetwork.name === this.chosenNetwork; - }); - - return this.sampleBusinessNetworkService.deployChosenSample(chosenSampleNetwork, this.deployNetwork); - - } - } - - private fileDetected() { - this.expandInput = true; - } - - private fileLeft() { - this.expandInput = false; - - } - - private fileAccepted(file: File) { - let fileReader = new FileReader(); - fileReader.onload = () => { - let dataBuffer = Buffer.from(fileReader.result); - this.clientService.getBusinessNetworkFromArchive(dataBuffer) - .then((businessNetwork) => { - this.currentBusinessNetwork = businessNetwork; - // needed for if browse file - this.expandInput = true; - }) - .catch((error) => { - let failMessage = 'Cannot import an invalid Business Network Definition. Found ' + error.toString(); - this.alertService.errorStatus$.next(failMessage); - this.expandInput = false; - }); - }; - - fileReader.readAsArrayBuffer(file); - } - - private fileRejected(reason: string) { - this.alertService.errorStatus$.next(reason); - this.expandInput = false; - } -} diff --git a/packages/composer-playground/src/app/import/import.component.html b/packages/composer-playground/src/app/import/import.component.html new file mode 100644 index 0000000000..0ae0f98a7c --- /dev/null +++ b/packages/composer-playground/src/app/import/import.component.html @@ -0,0 +1,152 @@ +
+

{{deployNetwork ? 'Deploy New Business Network' : 'Import/Replace Network'}}

+
+

1. Basic Information

+
+
+ + +
+
+ + +
+
+
+
+

2. Model Network Starter Template

+
+

Choose a Business Network Definition to {{deployNetwork ? 'start with:' : 'replace + your current + one:'}}

+ +

Choose a sample to play with, start a new project, or import your previous work

+ +
+
+
+ + + +
+
+
+
+
+
+ +
{{sampleNetworks[1].name}}
+
+
+ + + +
{{sampleNetworks[0].name}}
+
+
+ + + + + +
{{chosenNetwork.name}}
+
+ +
+ +

Samples on npm

+ +
+
+ + + + +
{{sampleNetwork.name}}
+
+
+
+
+
+
+
+
+ The files in the {{clientService.getBusinessNetworkName()}} will be replace with files from: +
+
+
+ + + + +
+
+
+

{{networkName ? networkName : 'Business Network Name'}}

+

{{networkDescription ? networkDescription : 'Business network description will be previewed here when + entered in the basic information section.'}}

+
+
+
+ connection profile +
+

{{currentConnectionProfile}}

+
+
+
+
+ + + +
+
+
+
+
+
+ based on +
+

{{chosenNetwork.name}}

+

{{chosenNetwork.description}}

+
+ +
+

+ Contains: + {{bn.getModelManager().getParticipantDeclarations().length}} Participant Types, + {{bn.getModelManager().getAssetDeclarations().length}} Asset Types, and + {{bn.getModelManager().getTransactionDeclarations().length}} Transaction Types +

+
+
+
+
+ +
+ + +
+
diff --git a/packages/composer-playground/src/app/import/import.component.scss b/packages/composer-playground/src/app/import/import.component.scss new file mode 100644 index 0000000000..c706c80c0c --- /dev/null +++ b/packages/composer-playground/src/app/import/import.component.scss @@ -0,0 +1,247 @@ +@import '../../assets/styles/base/colors'; +@import '../../assets/styles/base/variables'; + +import-business-network { + + display: flex; + flex-direction: column; + height: 100%; + overflow-y: auto; + + &.deploy { + flex-direction: row; + height: inherit; + overflow-y: inherit; + + div.choose-network { + margin-right: $space-large; + + section { + & > div { + flex-basis: 70%; + padding: $space-large; + } + + h3.title { + padding: $space-large; + } + + h3.sub-title { + padding: 0 $space-large; + } + + .information { + padding: $space-medium $space-large; + } + } + + .sample-network-list-container { + background-color: $white; + padding: 0 $space-large; + } + } + + div.chosen-network { + .image { + margin: 0 auto; + width: 50%; + padding-top: $space-large; + } + + div.chosen-network-info { + box-shadow: 3px 6px 15px 0 rgba(0, 0, 0, 0.20); + width: 275px; + display: block; + padding: 0; + border-bottom: none; + } + + div.deploy { + width: 50%; + margin: 0 auto; + display: block; + } + } + } + + div.choose-network { + background-color: $white; + margin-right: 0; + flex: 1; + + h2 { + padding: $space-large $space-large 0; + } + + section { + display: flex; + border-bottom: 1px solid $fourth-highlight; + + h3.title { + flex-basis: 30%; + text-transform: uppercase; + color: $second-highlight; + } + + h3.sub-title { + padding: $space-medium $space-large; + } + + p { + padding: 0 $space-large; + } + + & > div { + flex-basis: initial; + } + + .information { + + & > * { + display: flex; + align-items: baseline; + + & > * { + flex-basis: 50%; + } + + & > *:last-child { + width: inherit; + } + } + + // description property + & > *:last-child { + margin-top: $space-medium; + + & > * { + vertical-align: middle; + height: 100%; + } + } + } + + .sample-network-list-container { + + background-color: $fourth-highlight; + padding: $space-large; + + .sample-network-list { + display: flex; + flex-wrap: wrap; + margin: $space-medium 0; + + file-importer.sample-network-list-item { + &:hover { + border: 1px dashed $second-highlight; + } + } + + .sample-network-list-item { + background-color: $white; + border: 1px solid $fourth-highlight; + padding: $space-smedium; + text-align: center; + margin-right: $space-smedium; + margin-bottom: $space-smedium; + width: 150px; + display: flex; + flex-direction: column; + justify-content: flex-end; + position: relative; + + &.file-importer { + svg.ibm-icon { + width: 100px; + height: 100px; + margin-bottom: $space-large; + } + } + + &.selected-network { + border-color: $second-highlight; + } + + &:hover { + border-color: $second-highlight; + } + + &.drag { + border: 1px dashed $second-highlight; + } + + .sample-network-name { + margin-top: $space-medium; + flex-basis: 20%; + } + + button.close-dropped { + position: absolute; + top: -18px; + right: -9px; + + svg { + fill: $first-highlight; + width: $space-medium; + height: $space-medium; + } + } + } + } + } + } + } + + div.chosen-network { + .replace-message { + padding: $space-large; + } + + div.chosen-network-info { + display: flex; + background-color: $white; + margin-bottom: $space-large; + padding: $space-large $space-large 0 $space-large; + border-bottom: 1px solid $fourth-highlight; + + .animated-image { + width: 125px; + height: 125px; + } + + section { + &:first-child { + padding-top: $space-large + } + + &:last-child { + padding-bottom: $space-large; + } + + padding: $space-medium $space-large; + text-align: center; + + .title { + color: $secondary-text; + text-transform: uppercase; + } + + .network-info { + color: $secondary-text; + + span { + display: inline-block; + } + } + + } + } + + div.deploy { + display: flex; + justify-content: flex-end; + margin-bottom: $space-large; + margin-right: $space-large; + } + } +} diff --git a/packages/composer-playground/src/app/editor/import/import.component.spec.ts b/packages/composer-playground/src/app/import/import.component.spec.ts similarity index 61% rename from packages/composer-playground/src/app/editor/import/import.component.spec.ts rename to packages/composer-playground/src/app/import/import.component.spec.ts index 71100b41d5..362bd0fd6f 100644 --- a/packages/composer-playground/src/app/editor/import/import.component.spec.ts +++ b/packages/composer-playground/src/app/import/import.component.spec.ts @@ -9,15 +9,16 @@ import { By } from '@angular/platform-browser'; import { ImportComponent } from './import.component'; -import { AdminService } from '../../services/admin.service'; -import { ClientService } from '../../services/client.service'; -import { SampleBusinessNetworkService } from '../../services/samplebusinessnetwork.service'; +import { AdminService } from '../services/admin.service'; +import { ClientService } from '../services/client.service'; +import { SampleBusinessNetworkService } from '../services/samplebusinessnetwork.service'; import { NgbActiveModal, NgbModal } from '@ng-bootstrap/ng-bootstrap'; -import { AlertService } from '../../basic-modals/alert.service'; +import { AlertService } from '../basic-modals/alert.service'; import { BusinessNetworkDefinition, ClassDeclaration } from 'composer-common'; import * as sinon from 'sinon'; import * as chai from 'chai'; +import { ConnectionProfileService } from '../services/connectionprofile.service'; let should = chai.should(); @@ -80,21 +81,16 @@ describe('ImportComponent', () => { let mockAdminService; let mockAlertService; let mockClientService; - let mockActiveModal; let mockNgbModal; - - const EMPTY_NETWORK = { - name: 'Empty Business Network', - description: 'Start from scratch with a blank business network' - }; + let mockConnectionProfileService; beforeEach(() => { mockBusinessNetworkService = sinon.createStubInstance(SampleBusinessNetworkService); mockAdminService = sinon.createStubInstance(AdminService); mockAlertService = sinon.createStubInstance(AlertService); mockClientService = sinon.createStubInstance(ClientService); - mockActiveModal = sinon.createStubInstance(NgbActiveModal); mockNgbModal = sinon.createStubInstance(NgbModal); + mockConnectionProfileService = sinon.createStubInstance(ConnectionProfileService); mockAlertService.errorStatus$ = { next: sinon.stub() @@ -111,9 +107,9 @@ describe('ImportComponent', () => { {provide: SampleBusinessNetworkService, useValue: mockBusinessNetworkService}, {provide: AdminService, useValue: mockAdminService}, {provide: ClientService, useValue: mockClientService}, - {provide: NgbActiveModal, useValue: mockActiveModal}, {provide: AlertService, useValue: mockAlertService}, - {provide: NgbModal, useValue: mockNgbModal}] + {provide: NgbModal, useValue: mockNgbModal}, + {provide: ConnectionProfileService, useValue: mockConnectionProfileService}], }); sandbox = sinon.sandbox.create(); @@ -131,33 +127,6 @@ describe('ImportComponent', () => { sandbox.restore(); }); - describe('currentBusinessNetwork', () => { - let mockBusinessNetworkDefinition; - let stub1 = sinon.createStubInstance(ClassDeclaration); - beforeEach(() => { - stub1.isAbstract.returns(true); - stub1.isSystemType.returns(false); - - mockBusinessNetworkDefinition = sinon.createStubInstance(BusinessNetworkDefinition); - mockBusinessNetworkDefinition.getModelManager.returns({ - getAssetDeclarations: () => { - return [stub1]; - }, - getParticipantDeclarations: () => { - return [stub1]; - }, - getTransactionDeclarations: () => { - return [stub1]; - } - }); - }); - - it('should set the correct values for _currentBusinessNetwork', () => { - component['currentBusinessNetwork'] = mockBusinessNetworkDefinition; - component['currentAssets'].should.deep.equal([]); - }); - }); - describe('ngInit', () => { let onShowMock; @@ -182,14 +151,18 @@ describe('ImportComponent', () => { describe('onShow', () => { it('should get the list of sample networks', fakeAsync(() => { - mockBusinessNetworkService.getSampleList.returns(Promise.resolve([{name: 'modelOne'}])); + let selectNetworkStub = sinon.stub(component, 'selectNetwork'); + let addEmptyNetworkOption = sinon.stub(component, 'addEmptyNetworkOption').returns([{name: 'empty'}, {name: 'modelOne'}, {name: 'modelTwo'}]); + mockBusinessNetworkService.getSampleList.returns(Promise.resolve([{name: 'modelTwo'}, {name: 'modelOne'}])); component.onShow(); - component['gitHubInProgress'].should.equal(true); + component['npmInProgress'].should.equal(true); tick(); - component['gitHubInProgress'].should.equal(false); - component['sampleNetworks'].should.deep.equal([EMPTY_NETWORK, {name: 'modelOne'}]); + addEmptyNetworkOption.should.have.been.calledWith([{name: 'modelTwo'}, {name: 'modelOne'}]); + selectNetworkStub.should.have.been.calledWith({name: 'modelOne'}); + component['npmInProgress'].should.equal(false); + component['sampleNetworks'].should.deep.equal([{name: 'empty'}, {name: 'modelOne'}, {name: 'modelTwo'}]); })); it('should handle error', fakeAsync(() => { @@ -197,10 +170,10 @@ describe('ImportComponent', () => { component.onShow(); - component['gitHubInProgress'].should.equal(true); + component['npmInProgress'].should.equal(true); tick(); - component['gitHubInProgress'].should.equal(false); + component['npmInProgress'].should.equal(false); mockAlertService.errorStatus$.next.should.have.been.called; })); @@ -253,7 +226,16 @@ describe('ImportComponent', () => { }); it('should read a file', fakeAsync(() => { - mockClientService.getBusinessNetworkFromArchive.returns(Promise.resolve({network: 'mockNetwork'})); + let metaDataMock = { + getPackageJson: sinon.stub().returns({json: 'some json'}) + }; + + let businessNetworkMock = { + network: 'mockNetwork', + getMetadata: sinon.stub().returns(metaDataMock) + }; + + mockClientService.getBusinessNetworkFromArchive.returns(Promise.resolve(businessNetworkMock)); mockDragDropComponent.fileDragDropFileAccepted.emit(file); @@ -264,9 +246,10 @@ describe('ImportComponent', () => { tick(); mockClientService.getBusinessNetworkFromArchive.should.have.been.called; - - component['currentBusinessNetwork'].should.deep.equal({network: 'mockNetwork'}); - component['expandInput'].should.equal(true); + component['currentBusinessNetwork'].network.should.equal('mockNetwork'); + component['expandInput'].should.equal(false); + component['chosenNetwork'].should.deep.equal({json: 'some json'}); + component['sampleDropped'].should.equal(true); })); it('should handle error', fakeAsync(() => { @@ -307,116 +290,145 @@ describe('ImportComponent', () => { }); describe('deploy', () => { - it('should deploy a business network from npm', fakeAsync(() => { - - let deployNpmMock = sinon.stub(component, 'deployFromNpm').returns(Promise.resolve()); + let finishedSampleImportSpy; + beforeEach(() => { mockNgbModal.open = sinon.stub().returns({ componentInstance: {}, result: Promise.resolve(true) }); + finishedSampleImportSpy = sinon.spy(component.finishedSampleImport, 'emit'); + }); + + it('should deploy a business network', fakeAsync(() => { + component['currentBusinessNetwork'] = {network: 'my network'}; + component['networkName'] = 'newNetwork'; + component['networkDescription'] = 'myDescription'; + component['deployNetwork'] = true; + + component.finishedSampleImport.subscribe((result) => { + result.should.deep.equal({deployed: true}); + }); + component.deploy(); tick(); - deployNpmMock.should.have.been.called; + mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network'}, 'newNetwork', 'myDescription'); + + mockNgbModal.open.should.not.have.been.called; component['deployInProgress'].should.equal(false); - mockActiveModal.close.should.have.been.called; + + finishedSampleImportSpy.should.have.been.calledWith({deployed: true}); })); - it('should deploy a business network from business network', fakeAsync(() => { + it('should update a business network from business network', fakeAsync(() => { + component['deployNetwork'] = false; + component['currentBusinessNetwork'] = {network: 'my network'}; + mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.resolve()); - mockNgbModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve(true) + component.finishedSampleImport.subscribe((result) => { + result.should.deep.equal({deployed: true}); }); - component['deployNetwork'] = true; - component['currentBusinessNetwork'] = {network: 'my network', getName : sinon.stub()}; - mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.resolve()); - component.deploy(); tick(); - mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network', getName : sinon.match.func}, true); + mockNgbModal.open.should.have.been.called; + + mockBusinessNetworkService.updateBusinessNetwork.should.have.been.calledWith({network: 'my network'}); component['deployInProgress'].should.equal(false); - mockActiveModal.close.should.have.been.called; + finishedSampleImportSpy.should.have.been.calledWith({deployed: true}); })); - it('should update a business network from business network', fakeAsync(() => { + it('should do nothing if not replace', fakeAsync(() => { + component['deployNetwork'] = false; + component['currentBusinessNetwork'] = {network: 'my network'}; mockNgbModal.open = sinon.stub().returns({ componentInstance: {}, - result: Promise.resolve(true) + result: Promise.resolve(false) }); - component['deployNetwork'] = false; - component['currentBusinessNetwork'] = {network: 'my network', getName : sinon.stub()}; - mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.resolve()); + component.finishedSampleImport.subscribe((result) => { + result.should.deep.equal({deployed: false}); + }); component.deploy(); tick(); - mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network', getName : sinon.match.func}, false); + mockNgbModal.open.should.have.been.called; + + mockBusinessNetworkService.updateBusinessNetwork.should.not.have.been.called; + mockBusinessNetworkService.deployBusinessNetwork.should.not.have.been.called; component['deployInProgress'].should.equal(false); - mockActiveModal.close.should.have.been.called; + finishedSampleImportSpy.should.have.been.calledWith({deployed: false}); })); it('should handle error', fakeAsync(() => { + component['deployNetwork'] = true; + component['currentBusinessNetwork'] = {network: 'my network'}; + mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.reject('some error')); - mockNgbModal.open = sinon.stub().returns({ - result: Promise.resolve(true), - componentInstance: {} + component.finishedSampleImport.subscribe((result) => { + result.should.deep.equal({deployed: false, error: 'some error'}); }); - component['currentBusinessNetwork'] = {network: 'my network', getName : sinon.stub()}; - mockBusinessNetworkService.deployBusinessNetwork.returns(Promise.reject({message: 'some error'})); - - component.deploy(); + let deployPromise = component.deploy(); tick(); - mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network', getName : sinon.match.func}); + mockBusinessNetworkService.deployBusinessNetwork.should.have.been.calledWith({network: 'my network'}); component['deployInProgress'].should.equal(false); - component.modalService.open.should.have.been.called; + finishedSampleImportSpy.should.have.been.calledWith({deployed: false, error: 'some error'}); + mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); })); }); - describe('deployFromNpm', () => { - it('should get sample from npm and deploy', () => { - component['sampleNetworks'] = [{name: 'bob'}, {name: 'fred'}]; - component['chosenNetwork'] = 'fred'; + describe('deployEmptyNetwork', () => { + beforeEach(() => { + mockBusinessNetworkService.createNewBusinessDefinition.returns({network: 'myNetwork'}); + }); + + it('should create the empty business network if chosen', fakeAsync(() => { + component['deployNetwork'] = true; + component['networkName'] = 'myName'; + component['networkDescription'] = 'myDescription'; - component.deployFromNpm(); + mockBusinessNetworkService.createNewBusinessDefinition.returns({network : 'myNetwork'}); - mockBusinessNetworkService.deployChosenSample.should.have.been.calledWith({name: 'fred'}, true); - }); + component.deployEmptyNetwork(); - it('should get sample from npm and update', () => { - component['sampleNetworks'] = [{name: 'bob'}, {name: 'fred'}]; - component['chosenNetwork'] = 'fred'; - component['deployNetwork'] = false; + tick(); + mockBusinessNetworkService.createNewBusinessDefinition.should.have.been.calledWith('', '', sinon.match.object, sinon.match.string); + component['currentBusinessNetwork'].should.deep.equal({network : 'myNetwork'}); + })); + }); - component.deployFromNpm(); + describe('selectNetwork', () => { + it('should select the network', fakeAsync(() => { + mockBusinessNetworkService.getChosenSample.returns(Promise.resolve({network: 'myNetwork'})); + component.selectNetwork('bob'); - mockBusinessNetworkService.deployChosenSample.should.have.been.calledWith({name: 'fred'}, false); - }); + tick(); + + component['chosenNetwork']; + component['currentBusinessNetwork'].should.deep.equal({network: 'myNetwork'}); + })); - it('should deploy the empty business network if chosen', () => { - component['sampleNetworks'] = [EMPTY_NETWORK]; - component['chosenNetwork'] = EMPTY_NETWORK.name; - component['owner'] = 'my owner'; - component['repository'] = 'my repository'; - // TODO: figure out why this causes Error: Cannot find module "." - // component.deployFromGitHub(); - // mockBusinessNetworkService.deploySample.should.have.been.calledWith('my owner', 'my repository', EMPTY_NETWORK); + it('should select the empty network', () => { + let empty = sinon.stub(component, 'deployEmptyNetwork'); + + component.selectNetwork({name : 'Empty Business Network'}); + + empty.should.have.been.called; }); }); @@ -430,11 +442,38 @@ describe('ImportComponent', () => { let INPUT_NETWORKS = [{name: BASIC_SAMPLE}, {name: BAR}, {name: FOO}]; let result = component.addEmptyNetworkOption(INPUT_NETWORKS); result.length.should.equal(4); - result[0].name.should.equal(EMPTY_NETWORK.name); + result[0].name.should.equal('Empty Business Network'); result[1].name.should.equal(BASIC_SAMPLE); result[2].name.should.equal(BAR); result[3].name.should.equal(FOO); }); }); + describe('closeSample', (() => { + it('should set the dragged sample back to empty', () => { + let selectStub = sinon.stub(component, 'selectNetwork'); + component['sampleDropped'] = true; + + component['sampleNetworks'] = [{network: 'one'}, {network: 'two'}]; + + component.closeSample(); + + component['sampleDropped'].should.equal(false); + selectStub.should.have.been.calledWith({network: 'two'}); + }); + })); + + describe('cancel', () => { + it('should close importing', () => { + let emitSpy = sinon.spy(component.finishedSampleImport, 'emit'); + + component.finishedSampleImport.subscribe((result) => { + result.should.deep.equal({deployed: false}); + }); + + component.cancel(); + + emitSpy.should.have.been.calledWith({deployed: false}); + }); + }); }); diff --git a/packages/composer-playground/src/app/import/import.component.ts b/packages/composer-playground/src/app/import/import.component.ts new file mode 100644 index 0000000000..25ecf16361 --- /dev/null +++ b/packages/composer-playground/src/app/import/import.component.ts @@ -0,0 +1,242 @@ +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { AdminService } from '../services/admin.service'; +import { ClientService } from '../services/client.service'; +import { SampleBusinessNetworkService } from '../services/samplebusinessnetwork.service'; +import { AlertService } from '../basic-modals/alert.service'; +import { ReplaceComponent } from '../basic-modals/replace-confirm'; + +import { BusinessNetworkDefinition } from 'composer-common'; +import { ConnectionProfileService } from '../services/connectionprofile.service'; + +@Component({ + selector: 'import-business-network', + templateUrl: './import.component.html', + styleUrls: ['./import.component.scss'.toString()] +}) +export class ImportComponent implements OnInit { + + // choose whether to deploy or update the business network + @Input() deployNetwork: boolean; + @Output() finishedSampleImport = new EventEmitter(); + + private deployInProgress: boolean = false; + private npmInProgress: boolean = true; + private sampleNetworks = []; + private primaryNetworkNames = ['basic-sample-network', 'carauction-network']; + private chosenNetwork = null; + private expandInput: boolean = false; + private sampleDropped: boolean = false; + + private maxFileSize: number = 5242880; + private supportedFileTypes: string[] = ['.bna']; + + private currentBusinessNetwork = null; + private currentConnectionProfile: string = null; + private currentBusinessNetworkPromise: Promise; + private networkName: string; + private networkDescription: string; + + private NAME = 'Empty Business Network'; + private DESC = 'Start from scratch with a blank business network'; + private EMPTY_BIZNET = {name: this.NAME, description: this.DESC}; + + constructor(private clientService: ClientService, + public modalService: NgbModal, + private sampleBusinessNetworkService: SampleBusinessNetworkService, + private alertService: AlertService, + private adminService: AdminService, + private connectionProfileService: ConnectionProfileService) { + + } + + ngOnInit(): Promise { + this.currentBusinessNetwork = null; + this.currentConnectionProfile = this.connectionProfileService.getCurrentConnectionProfile(); + + return this.adminService.connectWithoutNetwork(false) + .then(() => { + this.onShow(); + }); + } + + onShow(): Promise { + this.npmInProgress = true; + return this.sampleBusinessNetworkService.getSampleList() + .then((sampleNetworkList) => { + this.sampleNetworks = this.addEmptyNetworkOption(sampleNetworkList); + this.selectNetwork(this.sampleNetworks[1]); + this.npmInProgress = false; + + }) + .catch((error) => { + this.npmInProgress = false; + this.alertService.errorStatus$.next(error); + }); + } + + selectNetwork(network): void { + this.chosenNetwork = network; + if (this.chosenNetwork.name !== this.NAME) { + this.currentBusinessNetworkPromise = this.sampleBusinessNetworkService.getChosenSample(this.chosenNetwork).then((result) => { + this.currentBusinessNetwork = result; + return result; + }); + } else { + this.deployEmptyNetwork(); + } + + } + + removeFile() { + this.expandInput = false; + this.currentBusinessNetwork = null; + } + + deploy() { + let replacePromise; + + let deployed: boolean = true; + + if (this.deployNetwork) { + replacePromise = Promise.resolve(true); + } else { + const confirmModalRef = this.modalService.open(ReplaceComponent); + confirmModalRef.componentInstance.mainMessage = 'Your Business Network Definition currently in the Playground will be removed & replaced.'; + confirmModalRef.componentInstance.supplementaryMessage = 'Please ensure that you have exported any current model files in the Playground.'; + replacePromise = confirmModalRef.result; + } + + replacePromise.then((result) => { + if (result === true) { + this.deployInProgress = true; + let deployPromise; + if (this.deployNetwork) { + return this.sampleBusinessNetworkService.deployBusinessNetwork(this.currentBusinessNetwork, this.networkName, this.networkDescription); + } else { + return this.sampleBusinessNetworkService.updateBusinessNetwork(this.currentBusinessNetwork); + } + } else { + deployed = false; + } + }) + .then(() => { + this.deployInProgress = false; + this.finishedSampleImport.emit({deployed: deployed}); + }) + .catch((error) => { + this.deployInProgress = false; + this.alertService.errorStatus$.next(error); + this.finishedSampleImport.emit({deployed: false, error: error}); + }); + + return replacePromise; + } + + deployEmptyNetwork(): void { + let readme = 'This is the readme file for the Business Network Definition created in Playground'; + let packageJson = { + name: 'unnamed-network', + author: 'author', + description: 'Empty Business Network', + version: '0.0.1', + devDependencies: { + 'browserfs': '^1.2.0', + 'chai': '^3.5.0', + 'composer-admin': 'latest', + 'composer-cli': 'latest', + 'composer-client': 'latest', + 'composer-connector-embedded': 'latest', + 'eslint': '^3.6.1', + 'istanbul': '^0.4.5', + 'jsdoc': '^3.4.1', + 'mkdirp': '^0.5.1', + 'mocha': '^3.2.0', + 'moment': '^2.17.1' + }, + keywords: [], + license: 'Apache 2.0', + repository: { + type: 'e.g. git', + url: 'URL' + }, + scripts: { + deploy: './scripts/deploy.sh', + doc: 'jsdoc --pedantic --recurse -c jsdoc.conf', + lint: 'eslint .', + postlicchk: 'npm run doc', + postlint: 'npm run licchk', + prepublish: 'mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/unnamed-network.bna', + pretest: 'npm run lint', + test: 'mocha --recursive' + } + }; + + this.currentBusinessNetworkPromise = Promise.resolve().then(() => { + this.currentBusinessNetwork = this.sampleBusinessNetworkService.createNewBusinessDefinition('', '', packageJson, readme); + return this.currentBusinessNetwork; + }); + } + + closeSample() { + this.sampleDropped = false; + this.selectNetwork(this.sampleNetworks[1]); + } + + cancel() { + this.finishedSampleImport.emit({deployed: false}); + } + + addEmptyNetworkOption(networks: any[]): any[] { + + let newOrder = []; + + // Append new network option to the list. + newOrder.push(this.EMPTY_BIZNET); + + for (let i = 0; i < networks.length; i++) { + newOrder.push(networks[i]); + } + + return newOrder; + } + + private fileDetected() { + this.expandInput = true; + } + + private fileLeft() { + this.expandInput = false; + + } + + private fileAccepted(file: File): void { + let fileReader = new FileReader(); + fileReader.onload = () => { + let dataBuffer = Buffer.from(fileReader.result); + this.currentBusinessNetworkPromise = this.clientService.getBusinessNetworkFromArchive(dataBuffer) + .then((businessNetwork) => { + this.chosenNetwork = businessNetwork.getMetadata().getPackageJson(); + this.currentBusinessNetwork = businessNetwork; + // needed for if browse file + this.sampleDropped = true; + this.expandInput = false; + return businessNetwork; + }) + .catch((error) => { + let failMessage = 'Cannot import an invalid Business Network Definition. Found ' + error.toString(); + this.alertService.errorStatus$.next(failMessage); + this.expandInput = false; + return null; + }); + }; + + fileReader.readAsArrayBuffer(file); + } + + private fileRejected(reason: string): void { + this.alertService.errorStatus$.next(reason); + this.expandInput = false; + } +} diff --git a/packages/composer-playground/src/app/import/import.module.ts b/packages/composer-playground/src/app/import/import.module.ts new file mode 100644 index 0000000000..ec061a7490 --- /dev/null +++ b/packages/composer-playground/src/app/import/import.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; + +import { ImportComponent } from './import.component'; +import { FileImporterModule } from '../common/file-importer/file-importer.module'; +import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar'; + +@NgModule({ + imports: [CommonModule, FormsModule, FileImporterModule, PerfectScrollbarModule], + entryComponents: [ImportComponent], + declarations: [ImportComponent], + exports : [ImportComponent] +}) + +export class ImportModule { +} diff --git a/packages/composer-playground/src/app/editor/import/index.ts b/packages/composer-playground/src/app/import/index.ts similarity index 100% rename from packages/composer-playground/src/app/editor/import/index.ts rename to packages/composer-playground/src/app/import/index.ts diff --git a/packages/composer-playground/src/app/login/login.component.html b/packages/composer-playground/src/app/login/login.component.html index d41a7d5319..8c8db5f584 100644 --- a/packages/composer-playground/src/app/login/login.component.html +++ b/packages/composer-playground/src/app/login/login.component.html @@ -1,4 +1,4 @@ -
+

My Wallet

@@ -6,17 +6,20 @@

My Wallet

Import ID Card
- +
+

Identities for {{connectionProfile.name === '$default' ? 'Web Browser' : connectionProfile.name }}
- -

-
+
-
- +
+ +
+
+ +
diff --git a/packages/composer-playground/src/app/login/login.component.scss b/packages/composer-playground/src/app/login/login.component.scss index 27ecaa3b38..cc99322663 100644 --- a/packages/composer-playground/src/app/login/login.component.scss +++ b/packages/composer-playground/src/app/login/login.component.scss @@ -5,9 +5,13 @@ app-login { width: 100vw; background-color: $fourth-highlight; + section.header { + padding: $space-large $space-large $space-medium $space-large; + } + section.main-view { overflow-y: auto; - height: calc(100vh - 63px - 70px); + height: calc(100vh - 63px - 95px - 55px); padding: $space-medium $space-large; div.connection-profile:first-child { @@ -43,9 +47,9 @@ app-login { } } - section.edit-connection-profile { - padding: $space-large ; - height: calc(100vh - 63px); + section.sub-view { + padding: $space-large; + height: calc(100vh - 63px - 55px); overflow-y: auto; .header { diff --git a/packages/composer-playground/src/app/login/login.component.spec.ts b/packages/composer-playground/src/app/login/login.component.spec.ts index 1ed8fa4ca1..8aa1080f8e 100644 --- a/packages/composer-playground/src/app/login/login.component.spec.ts +++ b/packages/composer-playground/src/app/login/login.component.spec.ts @@ -78,11 +78,15 @@ class MockConnectionProfileComponent { } @Component({ - selector: 'identity-card', + selector: 'import-business-network', template: '' }) -class MockIdentityCardComponent { - @Input() identity: any; +class MockImportComponent { + + @Input() + public deployNetwork; + @Output() + public finishedSampleImport: EventEmitter = new EventEmitter(); } @Component({ @@ -94,240 +98,287 @@ class MockFooterComponent { } describe(`LoginComponent`, () => { + @Component({ + selector: 'identity-card', + template: '' + }) + class MockIdentityCardComponent { + @Input() identity: any; + } - let component: LoginComponent; - let fixture: ComponentFixture; - - let mockAdminService; - let mockIdentityService; - let mockClientService; - let mockConnectionProfileService; - let mockInitializationService; - let routerStub; - let mockAlertService; - let mockWalletService; - let mockModal; - let mockDrawer; - - beforeEach(() => { - - mockIdentityService = sinon.createStubInstance(IdentityService); - mockClientService = sinon.createStubInstance(ClientService); - mockConnectionProfileService = sinon.createStubInstance(ConnectionProfileService); - mockAdminService = sinon.createStubInstance(AdminService); - mockInitializationService = sinon.createStubInstance(InitializationService); - mockAlertService = sinon.createStubInstance(AlertService); - mockWalletService = sinon.createStubInstance(WalletService); - mockDrawer = sinon.createStubInstance(DrawerService); - mockModal = sinon.createStubInstance(NgbModal); - - routerStub = new RouterStub(); - - mockAlertService.successStatus$ = {next: sinon.stub()}; - mockAlertService.busyStatus$ = {next: sinon.stub()}; - mockAlertService.errorStatus$ = {next: sinon.stub()}; - - mockWalletService.removeFromWallet = sinon.stub().returns(Promise.resolve(true)); - - TestBed.configureTestingModule({ - declarations: [ - LoginComponent, - MockConnectionProfileComponent, - MockIdentityCardComponent, - MockFooterComponent - ], - providers: [ - {provide: IdentityService, useValue: mockIdentityService}, - {provide: ClientService, useValue: mockClientService}, - {provide: ConnectionProfileService, useValue: mockConnectionProfileService}, - {provide: Router, useValue: routerStub}, - {provide: AdminService, useValue: mockAdminService}, - {provide: InitializationService, useValue: mockInitializationService}, - {provide: AlertService, useValue: mockAlertService}, - {provide: WalletService, useValue: mockWalletService}, - {provide: DrawerService, useValue: mockDrawer}, - {provide: NgbModal, useValue: mockModal} - ] - }); - - fixture = TestBed.createComponent(LoginComponent); + describe(`LoginComponent`, () => { + + let component: LoginComponent; + let fixture: ComponentFixture; + + let mockAdminService; + let mockIdentityService; + let mockClientService; + let mockConnectionProfileService; + let mockInitializationService; + let routerStub; + let mockAlertService; + let mockWalletService; + let mockModal; + let mockDrawer; + + beforeEach(() => { + + mockIdentityService = sinon.createStubInstance(IdentityService); + mockClientService = sinon.createStubInstance(ClientService); + mockConnectionProfileService = sinon.createStubInstance(ConnectionProfileService); + mockAdminService = sinon.createStubInstance(AdminService); + mockInitializationService = sinon.createStubInstance(InitializationService); + mockAlertService = sinon.createStubInstance(AlertService); + mockWalletService = sinon.createStubInstance(WalletService); + mockDrawer = sinon.createStubInstance(DrawerService); + mockModal = sinon.createStubInstance(NgbModal); + + routerStub = new RouterStub(); + + mockAlertService.successStatus$ = {next: sinon.stub()}; + mockAlertService.busyStatus$ = {next: sinon.stub()}; + mockAlertService.errorStatus$ = {next: sinon.stub()}; + + mockWalletService.removeFromWallet = sinon.stub().returns(Promise.resolve(true)); + + TestBed.configureTestingModule({ + declarations: [ + LoginComponent, + MockConnectionProfileComponent, + MockIdentityCardComponent, + MockImportComponent, + MockFooterComponent + ], + providers: [ + {provide: IdentityService, useValue: mockIdentityService}, + {provide: ClientService, useValue: mockClientService}, + {provide: ConnectionProfileService, useValue: mockConnectionProfileService}, + {provide: Router, useValue: routerStub}, + {provide: AdminService, useValue: mockAdminService}, + {provide: InitializationService, useValue: mockInitializationService}, + {provide: AlertService, useValue: mockAlertService}, + {provide: WalletService, useValue: mockWalletService}, + {provide: DrawerService, useValue: mockDrawer}, + {provide: NgbModal, useValue: mockModal} + ] + }); - component = fixture.componentInstance; - }); + fixture = TestBed.createComponent(LoginComponent); - describe('ngOnInit', () => { - it('should create the component', () => { - component.should.be.ok; + component = fixture.componentInstance; }); - it('should load identities', fakeAsync(() => { - mockInitializationService.initialize.returns(Promise.resolve()); - let loadConnectionProfilesStub = sinon.stub(component, 'loadConnectionProfiles'); - component.ngOnInit(); - - tick(); + describe('ngOnInit', () => { + it('should create the component', () => { + component.should.be.ok; + }); - mockInitializationService.initialize.should.have.been.called; - loadConnectionProfilesStub.should.have.been.called; - })); - }); + it('should load identities', fakeAsync(() => { + mockInitializationService.initialize.returns(Promise.resolve()); + let loadConnectionProfilesStub = sinon.stub(component, 'loadConnectionProfiles'); + component.ngOnInit(); - describe('loadConnectionProfiles', () => { - it('should load the connection profile', fakeAsync(() => { - mockConnectionProfileService.getAllProfiles.returns(Promise.resolve({myProfile: {name: 'myProfile'}})); + tick(); - mockIdentityService.getIdentities.returns(Promise.resolve(['bob'])); + mockInitializationService.initialize.should.have.been.called; + loadConnectionProfilesStub.should.have.been.called; + })); + }); - component.loadConnectionProfiles(); + describe('loadConnectionProfiles', () => { + it('should load the connection profile', fakeAsync(() => { + mockConnectionProfileService.getAllProfiles.returns(Promise.resolve({myProfile: {name: 'myProfile'}})); - tick(); + mockIdentityService.getIdentities.returns(Promise.resolve(['bob'])); - mockConnectionProfileService.getAllProfiles.should.have.been.called; + component.loadConnectionProfiles(); - mockIdentityService.getIdentities.should.have.been.calledWith('myProfile'); + tick(); - component['connectionProfiles'].should.deep.equal([{ - name: 'myProfile', - profile: {name: 'myProfile'}, - default: false, - identities: [{ - userId: 'bob', - businessNetwork: 'org-acme-biznet' - }] - }]); - })); - }); + mockConnectionProfileService.getAllProfiles.should.have.been.called; - describe('changeIdentity', () => { - it('should change identity', fakeAsync(() => { - mockAdminService.list.returns(Promise.resolve(['myNetwork'])); - mockClientService.ensureConnected.returns(Promise.resolve()); + mockIdentityService.getIdentities.should.have.been.calledWith('myProfile'); - component.changeIdentity('myProfile', 'bob'); + component['connectionProfiles'].should.deep.equal([{ + name: 'myProfile', + profile: {name: 'myProfile'}, + default: false, + identities: [{ + userId: 'bob', + businessNetwork: 'org-acme-biznet' + }] + }]); + })); + }); - tick(); + describe('changeIdentity', () => { + it('should change identity', fakeAsync(() => { + mockAdminService.list.returns(Promise.resolve(['myNetwork'])); + mockClientService.ensureConnected.returns(Promise.resolve()); - mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); - mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); - mockAdminService.list.should.have.been.called; - mockClientService.ensureConnected.should.have.been.calledWith('myNetwork', true); + component.changeIdentity('myProfile', 'bob'); - mockIdentityService.setLoggedIn.should.have.been.calledWith(true); - routerStub.navigate.should.have.been.calledWith(['editor']); - })); + tick(); - it('should handle error', fakeAsync(() => { - mockAdminService.list.returns(Promise.reject('some error')); - mockClientService.ensureConnected.returns(Promise.resolve()); + mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); + mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); + mockAdminService.list.should.have.been.called; + mockClientService.ensureConnected.should.have.been.calledWith('myNetwork', true); - component.changeIdentity('myProfile', 'bob'); + mockIdentityService.setLoggedIn.should.have.been.calledWith(true); + routerStub.navigate.should.have.been.calledWith(['editor']); + })); - tick(); + it('should handle error', fakeAsync(() => { + mockAdminService.list.returns(Promise.reject('some error')); + mockClientService.ensureConnected.returns(Promise.resolve()); - mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); - mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); - mockAdminService.list.should.have.been.called; - mockClientService.ensureConnected.should.not.have.been.called; + component.changeIdentity('myProfile', 'bob'); - mockIdentityService.setLoggedIn.should.not.have.been.called; - routerStub.navigate.should.not.have.been.called; + tick(); - mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); - })); - }); + mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('myProfile'); + mockIdentityService.setCurrentIdentity.should.have.been.calledWith('bob'); + mockAdminService.list.should.have.been.called; + mockClientService.ensureConnected.should.not.have.been.called; - describe('editConnectionProfile', () => { - it('should edit the connection profile', () => { - component.should.be.ok; - component.editConnectionProfile('myProfile'); + mockIdentityService.setLoggedIn.should.not.have.been.called; + routerStub.navigate.should.not.have.been.called; - component['editingConectionProfile'].should.equal('myProfile'); + mockAlertService.errorStatus$.next.should.have.been.calledWith('some error'); + })); }); - }); - describe('finishedEditingConnectionProfile', () => { - it('should close editing connection profile screen', () => { - let loadConnectionProfilesStub = sinon.stub(component, 'loadConnectionProfiles'); - component.finishedEditingConnectionProfile(); + describe('editConnectionProfile', () => { + it('should edit the connection profile', () => { + component.should.be.ok; + component.editConnectionProfile('myProfile'); - should.not.exist(component['editingConectionProfile']); - loadConnectionProfilesStub.should.have.been.called; - }); - }); - - describe('removeIdentity', () => { - it('should open the delete-confirm modal', fakeAsync(() => { - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve(0) + component['editingConectionProfile'].should.equal('myProfile'); }); + }); - component.removeIdentity('profile', 'name'); - tick(); - mockModal.open.should.have.been.called; - })); + describe('finishedEditingConnectionProfile', () => { + it('should close editing connection profile screen', () => { + let loadConnectionProfilesStub = sinon.stub(component, 'loadConnectionProfiles'); + component.finishedEditingConnectionProfile(); - it('should open delete-confirm modal and handle error', fakeAsync(() => { - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.reject('some error') + should.not.exist(component['editingConectionProfile']); + loadConnectionProfilesStub.should.have.been.called; }); + }); - component.removeIdentity('profile', 'name'); - tick(); - mockAlertService.busyStatus$.next.should.have.been.called; - mockAlertService.errorStatus$.next.should.have.been.called; - })); - - it('should open delete-confirm modal and handle cancel', fakeAsync(() => { - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.reject(null) - }); + describe('removeIdentity', () => { + it('should open the delete-confirm modal', fakeAsync(() => { + mockModal.open = sinon.stub().returns({ + componentInstance: {}, + result: Promise.resolve(0) + }); + + component.removeIdentity('profile', 'name'); + tick(); + mockModal.open.should.have.been.called; + })); + + it('should open delete-confirm modal and handle error', fakeAsync(() => { + mockModal.open = sinon.stub().returns({ + componentInstance: {}, + result: Promise.reject('some error') + }); + + component.removeIdentity('profile', 'name'); + tick(); + mockAlertService.busyStatus$.next.should.have.been.called; + mockAlertService.errorStatus$.next.should.have.been.called; + })); + + it('should open delete-confirm modal and handle cancel', fakeAsync(() => { + mockModal.open = sinon.stub().returns({ + componentInstance: {}, + result: Promise.reject(null) + }); + + component.removeIdentity('profile', 'name'); + tick(); + mockAlertService.busyStatus$.next.should.not.have.been.called; + mockAlertService.errorStatus$.next.should.not.have.been.called; + })); + + it('should refresh the connection profiles after successfully calling walletService.removeFromWallet()', fakeAsync(() => { + mockModal.open = sinon.stub().returns({ + componentInstance: {}, + result: Promise.resolve(true) + }); + + component.loadConnectionProfiles = sinon.stub(); + + component.removeIdentity('profile', 'name'); + tick(); + + // check services called + component.loadConnectionProfiles.should.have.been.called; + mockAlertService.busyStatus$.next.should.have.been.called; + mockAlertService.successStatus$.next.should.have.been.called; + + mockAlertService.errorStatus$.next.should.not.have.been.called; + })); + + it('should handle errors when calling walletService.removeFromWallet()', fakeAsync(() => { + mockModal.open = sinon.stub().returns({ + componentInstance: {}, + result: Promise.resolve(true) + }); + + component.loadConnectionProfiles = sinon.stub(); + mockWalletService.removeFromWallet = sinon.stub().returns(Promise.reject('some error')); + + component.removeIdentity('profile', 'name'); + tick(); + + // check services called + mockAlertService.busyStatus$.next.should.have.been.called; + mockAlertService.errorStatus$.next.should.have.been.called; + + mockAlertService.successStatus$.next.should.not.have.been.called; + component.loadConnectionProfiles.should.not.have.been.called; + })); + }); - component.removeIdentity('profile', 'name'); - tick(); - mockAlertService.busyStatus$.next.should.not.have.been.called; - mockAlertService.errorStatus$.next.should.not.have.been.called; - })); + describe('closeSubView', () => { + it('should close the subview', () => { + component['showSubScreen'] = true; + component['showDeployNetwork'] = true; + component['editingConnectionProfile'] = {profile: 'myProfile'}; + component.closeSubView(); - it('should refresh the connection profiles after successfully calling walletService.removeFromWallet()', fakeAsync(() => { - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve(true) + component['showSubScreen'].should.equal(false); + should.not.exist(component['editingConectionProfile']); + component['showDeployNetwork'].should.equal(false); }); + }); - component.loadConnectionProfiles = sinon.stub(); - - component.removeIdentity('profile', 'name'); - tick(); - - // check services called - component.loadConnectionProfiles.should.have.been.called; - mockAlertService.busyStatus$.next.should.have.been.called; - mockAlertService.successStatus$.next.should.have.been.called; - - mockAlertService.errorStatus$.next.should.not.have.been.called; - })); + describe('deployNetwork', () => { + it('should deploy a new business network', () => { + component.deployNetwork({name: 'bob'}); + mockConnectionProfileService.setCurrentConnectionProfile.should.have.been.calledWith('bob'); - it('should handle errors when calling walletService.removeFromWallet()', fakeAsync(() => { - mockModal.open = sinon.stub().returns({ - componentInstance: {}, - result: Promise.resolve(true) + mockIdentityService.setCurrentIdentity.should.have.been.calledWith('admin'); + component['showSubScreen'].should.equal(true); + component['showDeployNetwork'].should.equal(true); }); + }); - component.loadConnectionProfiles = sinon.stub(); - mockWalletService.removeFromWallet = sinon.stub().returns(Promise.reject('some error')); - - component.removeIdentity('profile', 'name'); - tick(); + describe('finishedDeploying', () => { + it('should finish deploying', () => { + component['showSubScreen'] = true; - // check services called - mockAlertService.busyStatus$.next.should.have.been.called; - mockAlertService.errorStatus$.next.should.have.been.called; + component['showDeployNetwork'] = true; + component.finishedDeploying(); - mockAlertService.successStatus$.next.should.not.have.been.called; - component.loadConnectionProfiles.should.not.have.been.called; - })); + component['showSubScreen'].should.equal(false); + component['showDeployNetwork'].should.equal(false); + }); + }); }); }); diff --git a/packages/composer-playground/src/app/login/login.component.ts b/packages/composer-playground/src/app/login/login.component.ts index 4101b37e33..fdd1fd1835 100644 --- a/packages/composer-playground/src/app/login/login.component.ts +++ b/packages/composer-playground/src/app/login/login.component.ts @@ -23,6 +23,8 @@ export class LoginComponent implements OnInit { private connectionProfiles = []; private editingConectionProfile = null; + private showSubScreen: boolean = false; + private showDeployNetwork: boolean = false; constructor(private identityService: IdentityService, private router: Router, @@ -66,9 +68,9 @@ export class LoginComponent implements OnInit { profile: connectionProfile, default: key === '$default', identities: identityList + }); }); }); - }); this.connectionProfiles = newConnectionProfiles; }); @@ -92,14 +94,35 @@ export class LoginComponent implements OnInit { } editConnectionProfile(connectionProfile): void { + this.showSubScreen = true; this.editingConectionProfile = connectionProfile; } finishedEditingConnectionProfile(): Promise { + this.showSubScreen = false; delete this.editingConectionProfile; return this.loadConnectionProfiles(); } + closeSubView(): void { + this.showSubScreen = false; + delete this.editingConectionProfile; + this.showDeployNetwork = false; + } + + deployNetwork(connectionProfile) { + this.connectionProfileService.setCurrentConnectionProfile(connectionProfile.name); + // TODO this needs to be done dynmaically + this.identityService.setCurrentIdentity('admin'); + this.showSubScreen = true; + this.showDeployNetwork = true; + } + + finishedDeploying() { + this.showSubScreen = false; + this.showDeployNetwork = false; + } + removeIdentity(connectionProfile, userId): void { const confirmModalRef = this.modalService.open(DeleteComponent); confirmModalRef.componentInstance.headerMessage = 'Remove ID Card'; diff --git a/packages/composer-playground/src/app/login/login.module.ts b/packages/composer-playground/src/app/login/login.module.ts index 4fe3ab9173..beee766b8a 100644 --- a/packages/composer-playground/src/app/login/login.module.ts +++ b/packages/composer-playground/src/app/login/login.module.ts @@ -10,10 +10,11 @@ import { IdentityCardComponent } from './identity-card'; import { DrawerModule } from '../common/drawer'; import { FileImporterModule } from '../common/file-importer/file-importer.module'; import { ConnectionProfileModule } from '../connection-profile/connection-profile.module'; +import { ImportModule } from '../import/import.module'; import { FooterModule } from '../footer/footer.module'; @NgModule({ - imports: [CommonModule, FormsModule, NgbModule, LoginRoutingModule, ConnectionProfileModule, FooterModule, FileImporterModule, DrawerModule], + imports: [CommonModule, FormsModule, NgbModule, LoginRoutingModule, ConnectionProfileModule, FooterModule, FileImporterModule, DrawerModule, ImportModule], entryComponents: [IdentityCardComponent], declarations: [LoginComponent, IdentityCardComponent] }) diff --git a/packages/composer-playground/src/app/services/connectionprofile.service.ts b/packages/composer-playground/src/app/services/connectionprofile.service.ts index 54036c0a26..4d7b260c6b 100644 --- a/packages/composer-playground/src/app/services/connectionprofile.service.ts +++ b/packages/composer-playground/src/app/services/connectionprofile.service.ts @@ -46,14 +46,14 @@ export class ConnectionProfileService { console.log('Currently running version ' + version); console.log('Checking for $default connection profile'); return this.getAdminConnection().getProfile('$default') - .catch((error) => { - // It doesn't exist, so create it. - console.log('$default connection profile does not exist, creating'); - return this.getAdminConnection().createProfile('$default', {type: 'web'}) - .then(() => { - return this.walletService.getWallet('$default').add('admin', 'adminpw'); + .catch((error) => { + // It doesn't exist, so create it. + console.log('$default connection profile does not exist, creating'); + return this.getAdminConnection().createProfile('$default', {type: 'web'}) + .then(() => { + return this.walletService.getWallet('$default').add('admin', 'adminpw'); + }); }); - }); }; getAllProfiles(): Promise { diff --git a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts index 12366399b1..6671d21b73 100644 --- a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts +++ b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.spec.ts @@ -41,6 +41,8 @@ describe('SampleBusinessNetworkService', () => { aclFileMock = sinon.createStubInstance(AclFile); alertMock = sinon.createStubInstance(AlertService); + alertMock.busyStatus$ = {next: sinon.stub()}; + TestBed.configureTestingModule({ imports: [HttpModule], providers: [SampleBusinessNetworkService, @@ -88,9 +90,8 @@ describe('SampleBusinessNetworkService', () => { }))); }); - describe('deployChosenSample', () => { + describe('getChosenSample', () => { it('should deploy the chosen sample', fakeAsync(inject([SampleBusinessNetworkService, XHRBackend], (service: SampleBusinessNetworkService, mockBackend) => { - let mockDeployNetwork = sinon.stub(service, 'deployBusinessNetwork'); let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({name: 'myNetwork'})); mockBackend.connections.subscribe((connection) => { @@ -99,43 +100,24 @@ describe('SampleBusinessNetworkService', () => { }))); }); - service.deployChosenSample({name: 'bob'}, true); - - tick(); - - businessNetworkFromArchiveMock.should.have.been.called; - mockDeployNetwork.should.have.been.calledWith({name: 'myNetwork'}, true); - - }))); - - it('should update the chosen sample', fakeAsync(inject([SampleBusinessNetworkService, XHRBackend], (service: SampleBusinessNetworkService, mockBackend) => { - let mockDeployNetwork = sinon.stub(service, 'deployBusinessNetwork'); - let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({name: 'myNetwork'})); - - mockBackend.connections.subscribe((connection) => { - connection.mockRespond(new Response(new ResponseOptions({ - body: '1234' - }))); + service.getChosenSample({name: 'bob'}).then((result: any) => { + result.should.deep.equal({name: 'myNetwork'}); }); - service.deployChosenSample({name: 'bob'}, false); - tick(); businessNetworkFromArchiveMock.should.have.been.called; - mockDeployNetwork.should.have.been.calledWith({name: 'myNetwork'}, false); }))); it('should handle error', fakeAsync(inject([SampleBusinessNetworkService, XHRBackend], (service: SampleBusinessNetworkService, mockBackend) => { - let mockDeployNetwork = sinon.stub(service, 'deployBusinessNetwork'); let businessNetworkFromArchiveMock = sandbox.stub(BusinessNetworkDefinition, 'fromArchive').returns(Promise.resolve({name: 'myNetwork'})); mockBackend.connections.subscribe((connection) => { connection.mockError(new Error('some error')); }); - service.deployChosenSample({name: 'bob'}, true) + service.getChosenSample({name: 'bob'}) .then(() => { throw('should not get here'); }) @@ -146,29 +128,67 @@ describe('SampleBusinessNetworkService', () => { tick(); businessNetworkFromArchiveMock.should.not.have.been.called; - mockDeployNetwork.should.not.have.been.called; }))); }); describe('deployBusinessNetwork', () => { it('should deploy the business network definition', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { + let modalManagerMock = {addModelFiles: sinon.stub(), getModelFiles: sinon.stub().returns(['model'])}; + let scriptManagerMock = {getScripts: sinon.stub().returns(['script']), addScript: sinon.stub()}; + let aclManagerMock = {getAclFile: sinon.stub().returns('acl'), setAclFile: sinon.stub()}; + let metaData = {getPackageJson: sinon.stub().returns({}), getREADME: sinon.stub()}; + + businessNetworkMock.getModelManager.returns(modalManagerMock); + businessNetworkMock.getScriptManager.returns(scriptManagerMock); + businessNetworkMock.getAclManager.returns(aclManagerMock); + businessNetworkMock.getMetadata.returns(metaData); + + let mockCreateBN = sinon.stub(service, 'createNewBusinessDefinition').returns(businessNetworkMock); adminMock.deploy.returns(Promise.resolve()); clientMock.refresh.returns(Promise.resolve()); - alertMock.busyStatus$ = {next: sinon.stub()}; - - service.deployBusinessNetwork(businessNetworkMock, true); + service.deployBusinessNetwork(businessNetworkMock, 'myNetwork', 'myDescription'); tick(); + mockCreateBN.should.have.been.calledWith('myNetwork', 'myDescription', sinon.match.any, sinon.match.any); adminMock.deploy.should.have.been.called; clientMock.refresh.should.have.been.called; clientMock.reset.should.have.been.called; alertMock.busyStatus$.next.should.have.been.calledWith(null); }))); - it('should update the business network definition', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { + it('should handle error', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { + let modalManagerMock = {addModelFiles: sinon.stub(), getModelFiles: sinon.stub().returns(['model'])}; + let scriptManagerMock = {getScripts: sinon.stub().returns(['script']), addScript: sinon.stub()}; + let aclManagerMock = {getAclFile: sinon.stub().returns('acl'), setAclFile: sinon.stub()}; + let metaData = {getPackageJson: sinon.stub().returns({}), getREADME: sinon.stub()}; + businessNetworkMock.getModelManager.returns(modalManagerMock); + businessNetworkMock.getScriptManager.returns(scriptManagerMock); + businessNetworkMock.getAclManager.returns(aclManagerMock); + businessNetworkMock.getMetadata.returns(metaData); + + let mockCreateBN = sinon.stub(service, 'createNewBusinessDefinition').returns(businessNetworkMock); + + adminMock.deploy.returns(Promise.reject('some error')); + + service.deployBusinessNetwork(businessNetworkMock, 'myNetwork', 'myDescription').then(() => { + throw('should not get here'); + }) + .catch((error) => { + alertMock.busyStatus$.next.should.have.been.calledWith(null); + error.should.equal('some error'); + }); + tick(); + + mockCreateBN.should.have.been.calledWith('myNetwork', 'myDescription', sinon.match.any, sinon.match.any); + adminMock.deploy.should.have.been.called; + }))); + }); + + describe('updateBusinessNetwork', () => { + it('should update the business network definition', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { adminMock.update.returns(Promise.resolve()); clientMock.refresh.returns(Promise.resolve()); clientMock.getBusinessNetworkName.returns('myNetwork'); @@ -185,9 +205,7 @@ describe('SampleBusinessNetworkService', () => { let mockCreateBN = sinon.stub(service, 'createNewBusinessDefinition').returns(businessNetworkMock); - alertMock.busyStatus$ = {next: sinon.stub()}; - - service.deployBusinessNetwork(businessNetworkMock, false); + service.updateBusinessNetwork(businessNetworkMock); tick(); @@ -207,11 +225,18 @@ describe('SampleBusinessNetworkService', () => { }))); it('should handle error', fakeAsync(inject([SampleBusinessNetworkService], (service: SampleBusinessNetworkService) => { - adminMock.deploy.returns(Promise.reject('some error')); + clientMock.getBusinessNetworkName.returns('myNetwork'); + + let modalManagerMock = {addModelFiles: sinon.stub(), getModelFiles: sinon.stub().returns(['model'])}; + let scriptManagerMock = {getScripts: sinon.stub().returns(['script']), addScript: sinon.stub()}; + let aclManagerMock = {getAclFile: sinon.stub().returns('acl'), setAclFile: sinon.stub()}; + let metaData = {getPackageJson: sinon.stub().returns({}), getREADME: sinon.stub()}; + + let mockCreateBN = sinon.stub(service, 'createNewBusinessDefinition').returns(businessNetworkMock); - alertMock.busyStatus$ = {next: sinon.stub()}; + adminMock.update.returns(Promise.reject('some error')); - service.deployBusinessNetwork(businessNetworkMock, true).then(() => { + service.updateBusinessNetwork(businessNetworkMock).then(() => { throw('should not get here'); }) .catch((error) => { diff --git a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts index c9f4afe870..a29913f586 100644 --- a/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts +++ b/packages/composer-playground/src/app/services/samplebusinessnetwork.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Http, RequestOptions, URLSearchParams } from '@angular/http'; +import { Http, RequestOptions, URLSearchParams } from '@angular/http'; import { AdminService } from './admin.service'; import { ClientService } from './client.service'; @@ -15,7 +15,7 @@ export class SampleBusinessNetworkService { private http: Http) { } - createNewBusinessDefinition(name, description, packageJson, readme) { + public createNewBusinessDefinition(name, description, packageJson, readme): BusinessNetworkDefinition { return new BusinessNetworkDefinition(name, description, packageJson, readme); } @@ -30,7 +30,7 @@ export class SampleBusinessNetworkService { }); } - public deployChosenSample(chosenNetwork: object, deployNetwork: boolean): Promise { + public getChosenSample(chosenNetwork): Promise { let params: URLSearchParams = new URLSearchParams(); let paramNames = Object.keys(chosenNetwork); @@ -47,42 +47,67 @@ export class SampleBusinessNetworkService { .then((response) => { return BusinessNetworkDefinition.fromArchive(( response)._body); }) - .then((businessNetwork) => { - return this.deployBusinessNetwork(businessNetwork, deployNetwork); - }) .catch((error) => { throw(error); }); } - public deployBusinessNetwork(businessNetworkDefinition: BusinessNetworkDefinition, deployNetwork: boolean): Promise { - let deployPromise; + public deployBusinessNetwork(businessNetworkDefinition: BusinessNetworkDefinition, networkName: string, networkDescription: string): Promise { + let packageJson = businessNetworkDefinition.getMetadata().getPackageJson(); + packageJson.name = networkName; + packageJson.description = networkDescription; - if (deployNetwork) { - deployPromise = this.adminService.deploy(businessNetworkDefinition); - } else { - let currentBusinessNetworkName = this.clientService.getBusinessNetworkName(); + let newNetwork = this.createNewBusinessDefinition(networkName, networkDescription, packageJson, businessNetworkDefinition.getMetadata().getREADME()); - let packageJson = businessNetworkDefinition.getMetadata().getPackageJson(); - packageJson.name = currentBusinessNetworkName; + let modelFiles = this.clientService.filterModelFiles(businessNetworkDefinition.getModelManager().getModelFiles()); - let newNetwork = this.createNewBusinessDefinition(currentBusinessNetworkName, businessNetworkDefinition.getDescription(), packageJson, businessNetworkDefinition.getMetadata().getREADME()); + newNetwork.getModelManager().addModelFiles(modelFiles); + businessNetworkDefinition.getScriptManager().getScripts().forEach((script) => { + newNetwork.getScriptManager().addScript(script); + }); - let modelFiles = this.clientService.filterModelFiles(businessNetworkDefinition.getModelManager().getModelFiles()); + if (businessNetworkDefinition.getAclManager().getAclFile()) { + newNetwork.getAclManager().setAclFile(businessNetworkDefinition.getAclManager().getAclFile()); + } - newNetwork.getModelManager().addModelFiles(modelFiles); - businessNetworkDefinition.getScriptManager().getScripts().forEach((script) => { - newNetwork.getScriptManager().addScript(script); + return this.adminService.deploy(businessNetworkDefinition) + .then(() => { + return this.clientService.refresh(businessNetworkDefinition.getName()); + }) + .then(() => { + return this.clientService.reset(); + }) + .then(() => { + this.alertService.busyStatus$.next(null); + }) + .catch((error) => { + this.alertService.busyStatus$.next(null); + throw error; }); + } - if (businessNetworkDefinition.getAclManager().getAclFile()) { - newNetwork.getAclManager().setAclFile(businessNetworkDefinition.getAclManager().getAclFile()); - } + public updateBusinessNetwork(businessNetworkDefinition: BusinessNetworkDefinition): Promise { + let currentBusinessNetworkName = this.clientService.getBusinessNetworkName(); + let currentBusinessNetworkDescription = businessNetworkDefinition.getDescription(); + + let packageJson = businessNetworkDefinition.getMetadata().getPackageJson(); + packageJson.name = currentBusinessNetworkName; + packageJson.description = currentBusinessNetworkDescription; + + let newNetwork = this.createNewBusinessDefinition(currentBusinessNetworkName, currentBusinessNetworkDescription, packageJson, businessNetworkDefinition.getMetadata().getREADME()); + + let modelFiles = this.clientService.filterModelFiles(businessNetworkDefinition.getModelManager().getModelFiles()); + + newNetwork.getModelManager().addModelFiles(modelFiles); + businessNetworkDefinition.getScriptManager().getScripts().forEach((script) => { + newNetwork.getScriptManager().addScript(script); + }); - deployPromise = this.adminService.update(newNetwork); + if (businessNetworkDefinition.getAclManager().getAclFile()) { + newNetwork.getAclManager().setAclFile(businessNetworkDefinition.getAclManager().getAclFile()); } - return deployPromise + return this.adminService.update(newNetwork) .then(() => { return this.clientService.refresh(businessNetworkDefinition.getName()); }) diff --git a/packages/composer-playground/src/assets/styles/components/_model.scss b/packages/composer-playground/src/assets/styles/components/_model.scss index 62d6e0d39c..0caea6e365 100644 --- a/packages/composer-playground/src/assets/styles/components/_model.scss +++ b/packages/composer-playground/src/assets/styles/components/_model.scss @@ -13,7 +13,7 @@ right: 0; bottom: 0; left: 0; - z-index: 1000; + z-index: 100000; display: none; overflow: hidden; outline: 0; diff --git a/packages/composer-playground/src/assets/svg/other/default-network-animated.svg b/packages/composer-playground/src/assets/svg/other/default-network-animated.svg new file mode 100644 index 0000000000..383cd84a03 --- /dev/null +++ b/packages/composer-playground/src/assets/svg/other/default-network-animated.svg @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/packages/composer-playground/src/assets/svg/other/default-network.svg b/packages/composer-playground/src/assets/svg/other/default-network.svg new file mode 100644 index 0000000000..36885d89d0 --- /dev/null +++ b/packages/composer-playground/src/assets/svg/other/default-network.svg @@ -0,0 +1 @@ +9 \ No newline at end of file diff --git a/packages/composer-playground/src/assets/svg/other/upload.svg b/packages/composer-playground/src/assets/svg/other/upload.svg new file mode 100644 index 0000000000..85be079e58 --- /dev/null +++ b/packages/composer-playground/src/assets/svg/other/upload.svg @@ -0,0 +1 @@ +10 \ No newline at end of file