From 4084fb39e0450a0eae3fbde9d3f2195521027edd Mon Sep 17 00:00:00 2001 From: PiotrP Date: Thu, 25 Oct 2018 02:40:37 -0700 Subject: [PATCH 1/8] initial implementation of projects importing --- package.json | 3 +- src/app/app.module.ts | 12 +- .../import-project-dialog.component.css | 9 ++ .../import-project-dialog.component.html | 26 ++++ .../import-project-dialog.component.spec.ts | 139 ++++++++++++++++++ .../import-project-dialog.component.ts | 79 ++++++++++ .../projects/projects.component.css | 3 + .../projects/projects.component.html | 3 + .../projects/projects.component.spec.ts | 3 +- .../components/projects/projects.component.ts | 18 ++- 10 files changed, 288 insertions(+), 7 deletions(-) create mode 100644 src/app/components/projects/import-project-dialog/import-project-dialog.component.css create mode 100644 src/app/components/projects/import-project-dialog/import-project-dialog.component.html create mode 100644 src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts create mode 100644 src/app/components/projects/import-project-dialog/import-project-dialog.component.ts diff --git a/package.json b/package.json index 54d7464a..6dc43fb3 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "d3-ng2-service": "^2.1.0", "electron-settings": "^3.2.0", "material-design-icons": "^3.0.1", + "ng2-file-upload": "^1.3.0", "ngx-electron": "^1.0.4", "notosans-fontface": "^1.1.0", "npm-check-updates": "^2.14.1", @@ -62,10 +63,10 @@ "@angular/cli": "^6.0.8", "@angular/compiler-cli": "^6.0.7", "@angular/language-service": "^6.0.7", + "@sentry/electron": "^0.7.0", "@types/jasmine": "~2.8.8", "@types/jasminewd2": "~2.0.2", "@types/node": "~10.5.2", - "@sentry/electron": "^0.7.0", "codelyzer": "~4.4.2", "electron": "2.0.4", "electron-builder": "^20.19.2", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index e3c4580f..b9426bae 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -11,6 +11,7 @@ import { MatCardModule, MatMenuModule, MatToolbarModule, + MatStepperModule, MatIconModule, MatFormFieldModule, MatInputModule, @@ -32,7 +33,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { HotkeyModule } from 'angular2-hotkeys'; import { PersistenceModule } from 'angular-persistence'; import { NgxElectronModule } from 'ngx-electron'; - +import { FileUploadModule } from 'ng2-file-upload'; import { AppRoutingModule } from './app-routing.module'; import { VersionService } from './services/version.service'; @@ -48,6 +49,7 @@ import { ApplianceService } from "./services/appliance.service"; import { LinkService } from "./services/link.service"; import { ProjectsComponent } from './components/projects/projects.component'; +import { ImportProjectDialogComponent } from './components/projects/import-project-dialog/import-project-dialog.component'; import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component'; import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component'; import { AppComponent } from './app.component'; @@ -106,6 +108,7 @@ if (environment.production) { AddServerDialogComponent, CreateSnapshotDialogComponent, ProjectsComponent, + ImportProjectDialogComponent, DefaultLayoutComponent, ProgressDialogComponent, NodeContextMenuComponent, @@ -148,10 +151,12 @@ if (environment.production) { MatSortModule, MatSelectModule, MatTooltipModule, + MatStepperModule, CartographyModule, HotkeyModule.forRoot(), PersistenceModule, - NgxElectronModule + NgxElectronModule, + FileUploadModule ], providers: [ SettingsService, @@ -184,7 +189,8 @@ if (environment.production) { AddServerDialogComponent, CreateSnapshotDialogComponent, ProgressDialogComponent, - ApplianceListDialogComponent + ApplianceListDialogComponent, + ImportProjectDialogComponent ], bootstrap: [ AppComponent ] }) diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.css b/src/app/components/projects/import-project-dialog/import-project-dialog.component.css new file mode 100644 index 00000000..3ae208bc --- /dev/null +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.css @@ -0,0 +1,9 @@ +.file-name-form { + margin-left: 10px; + margin-right: 10px; + width:250px; +} + +.non-visible { + display: none; +} \ No newline at end of file diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html new file mode 100644 index 00000000..f3fd3a31 --- /dev/null +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html @@ -0,0 +1,26 @@ +

Import project

+ + + + + + + + +
+ + +
+
+ +
+
+
+
+ {{errorMessage}} +
+
+ +
+
+
\ No newline at end of file diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts new file mode 100644 index 00000000..cd174be8 --- /dev/null +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -0,0 +1,139 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ImportProjectDialogComponent } from "./import-project-dialog.component"; +import { Server } from "../../../models/server"; +import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatStepperModule, MatFormFieldModule, MatDialogRef, MatDialog, MAT_DIALOG_DATA } from "@angular/material"; +import { RouterTestingModule } from "@angular/router/testing"; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { FileUploadModule, FileSelectDirective, FileItem, FileUploader } from "ng2-file-upload"; +import { FormsModule } from '@angular/forms'; +import { DebugElement } from '@angular/core'; +import { By } from '@angular/platform-browser'; + +describe('ImportProjectDialogComponent', () => { + let component: ImportProjectDialogComponent; + let fixture: ComponentFixture; + let server: Server; + let dialog: MatDialog; + let debugElement: DebugElement; + let fileSelectDirective: FileSelectDirective; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + MatTableModule, + MatTooltipModule, + MatIconModule, + MatSortModule, + MatDialogModule, + MatStepperModule, + MatFormFieldModule, + MatInputModule, + NoopAnimationsModule, + FileUploadModule, + FormsModule, + RouterTestingModule.withRoutes([]), + ], + providers: [ + { provide: MatDialogRef }, + { provide: MAT_DIALOG_DATA } + ], + declarations : [ImportProjectDialogComponent] + }) + .compileComponents(); + + dialog = TestBed.get(MatDialog); + server = new Server(); + server.ip = "localhost"; + server.port = 80; + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImportProjectDialogComponent); + debugElement = fixture.debugElement; + component = fixture.componentInstance; + component.server = server; + + fixture.detectChanges(); + + debugElement = fixture.debugElement.query(By.directive(FileSelectDirective)); + fileSelectDirective = debugElement.injector.get(FileSelectDirective) as FileSelectDirective; + }); + + it('should be created', () => { + expect(fixture).toBeDefined(); + expect(component).toBeTruthy(); + }); + + it('should set file uploader', () => { + expect(fileSelectDirective).toBeDefined(); + expect(fileSelectDirective.uploader).toBe(component.uploader); + }); + + it('should handle file adding', () => { + spyOn(fileSelectDirective.uploader, 'addToQueue'); + + fileSelectDirective.onChange(); + + const expectedArguments = [ debugElement.nativeElement.files, + fileSelectDirective.getOptions(), + fileSelectDirective.getFilters() ]; + expect(fileSelectDirective.uploader.addToQueue).toHaveBeenCalledWith(...expectedArguments); + }); + + it('should call uploading item', () => { + spyOn(fileSelectDirective.uploader, 'uploadItem'); + + component.onImportClick(); + + expect(fileSelectDirective.uploader.uploadItem).toHaveBeenCalled(); + }); + + it('should call uploading item with correct arguments', () => { + let fileItem = new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{}); + fileSelectDirective.uploader.queue.push(fileItem); + spyOn(fileSelectDirective.uploader, 'uploadItem'); + + component.onImportClick(); + + expect(fileSelectDirective.uploader.uploadItem).toHaveBeenCalledWith(fileItem); + }); + + it('should handle file change event', () => { + let input = fixture.debugElement.query(By.css('input[type=file]')).nativeElement; + spyOn(component, 'uploadProjectFile'); + + input.dispatchEvent(new Event('change')); + + expect(component.uploadProjectFile).toHaveBeenCalled(); + }); + + it('should clear queue after calling delete', () => { + fileSelectDirective.uploader.queue.push(new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{})); + spyOn(fileSelectDirective.uploader.queue, "pop"); + + component.onDeleteClick(); + + expect(fileSelectDirective.uploader.queue.pop).toHaveBeenCalled(); + expect(fileSelectDirective.uploader.queue[0]).toBeNull; + }); + + it('should prepare correct upload path for file', () => { + fileSelectDirective.uploader.queue.push(new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{})); + component.projectName = "newProject.gns3"; + + component.prepareUploadPath(); + + expect(fileSelectDirective.uploader.queue[0].url).toContain("localhost:80"); + expect(fileSelectDirective.uploader.queue[0].url).toContain("newProject"); + }); + + it('should navigate to next step after clicking import', () => { + let fileItem = new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{}); + fileSelectDirective.uploader.queue.push(fileItem); + spyOn(component.stepper, "next"); + + component.onImportClick(); + + expect(component.stepper.next).toHaveBeenCalled(); + }); +}); diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts new file mode 100644 index 00000000..909baf72 --- /dev/null +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -0,0 +1,79 @@ +import { Component, OnInit, Inject, ViewChild } from '@angular/core'; +import { MatStepper, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; +import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload'; +import { Server } from '../../../models/server'; +import { v4 as uuid } from 'uuid'; + +@Component({ + selector: 'app-import-project-dialog', + templateUrl: 'import-project-dialog.component.html', + styleUrls: ['import-project-dialog.component.css'], +}) +export class ImportProjectDialogComponent implements OnInit { + uploader: FileUploader; + server : Server; + projectName : string; + isImportEnabled : boolean = false; + isFinishEnabled : boolean = false; + errorMessage : string; + + @ViewChild('stepper') stepper: MatStepper; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any){} + + ngOnInit(){ + this.uploader = new FileUploader({}); + this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; }; + } + + uploadProjectFile(event) : void{ + this.projectName = event.target.files[0].name.split(".")[0]; + this.isImportEnabled = true; + } + + onImportClick() : void{ + if(this.validateProjectName()){ + this.prepareUploadPath(); + this.stepper.selected.completed = true; + this.stepper.next(); + let itemToUpload = this.uploader.queue[0]; + this.uploader.uploadItem(itemToUpload); + + this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { + this.errorMessage = response; + this.isFinishEnabled = true; + }; + + this.uploader.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { + this.isFinishEnabled = true; + }; + } + } + + onNoClick() : void{ + this.uploader.cancelAll(); + this.dialogRef.close(); + } + + onFinishClick() : void{ + this.dialogRef.close(); + } + + onDeleteClick() : void{ + this.uploader.queue.pop(); + this.isImportEnabled = false; + this.projectName = ""; + } + + prepareUploadPath() : void{ + let url = `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${this.projectName}`; + this.uploader.queue.forEach(elem => elem.url = url); + } + + validateProjectName() : boolean{ + var pattern = new RegExp(/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/); + return !pattern.test(this.projectName); + } +} diff --git a/src/app/components/projects/projects.component.css b/src/app/components/projects/projects.component.css index e69de29b..6884375a 100644 --- a/src/app/components/projects/projects.component.css +++ b/src/app/components/projects/projects.component.css @@ -0,0 +1,3 @@ +.import-button { + margin-right:10px +} \ No newline at end of file diff --git a/src/app/components/projects/projects.component.html b/src/app/components/projects/projects.component.html index 6716c27a..4ec801cc 100644 --- a/src/app/components/projects/projects.component.html +++ b/src/app/components/projects/projects.component.html @@ -34,5 +34,8 @@ +
+ +
diff --git a/src/app/components/projects/projects.component.spec.ts b/src/app/components/projects/projects.component.spec.ts index c9fc70f5..24bcf1bd 100644 --- a/src/app/components/projects/projects.component.spec.ts +++ b/src/app/components/projects/projects.component.spec.ts @@ -1,5 +1,5 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatIconModule, MatSortModule, MatTableModule, MatTooltipModule } from "@angular/material"; +import { MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule } from "@angular/material"; import { RouterTestingModule } from "@angular/router/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; @@ -34,6 +34,7 @@ describe('ProjectsComponent', () => { MatTooltipModule, MatIconModule, MatSortModule, + MatDialogModule, NoopAnimationsModule, RouterTestingModule.withRoutes([]), ], diff --git a/src/app/components/projects/projects.component.ts b/src/app/components/projects/projects.component.ts index 791f2398..571d8364 100644 --- a/src/app/components/projects/projects.component.ts +++ b/src/app/components/projects/projects.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; -import { MatSort, MatSortable } from "@angular/material"; +import { MatSort, MatSortable, MatDialog } from "@angular/material"; import { DataSource } from "@angular/cdk/collections"; @@ -14,6 +14,7 @@ import { ServerService } from "../../services/server.service"; import { SettingsService, Settings } from "../../services/settings.service"; import { ProgressService } from "../../common/progress/progress.service"; +import { ImportProjectDialogComponent } from './import-project-dialog/import-project-dialog.component'; @Component({ selector: 'app-projects', @@ -33,7 +34,8 @@ export class ProjectsComponent implements OnInit { private serverService: ServerService, private projectService: ProjectService, private settingsService: SettingsService, - private progressService: ProgressService + private progressService: ProgressService, + private dialog: MatDialog ) { } @@ -96,6 +98,18 @@ export class ProjectsComponent implements OnInit { this.progressService.deactivate(); }); } + + importProject(){ + const dialogRef = this.dialog.open(ImportProjectDialogComponent, { + width: '550px', + }); + let instance = dialogRef.componentInstance; + instance.server = this.server; + + dialogRef.afterClosed().subscribe(() => { + this.refresh(); + }); + } } From 668235936d9bcecd63bac0b89e0ea79f7cd80cf5 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Fri, 26 Oct 2018 05:35:02 -0700 Subject: [PATCH 2/8] Fixes after review --- src/app/app.module.ts | 3 +- .../import-project-dialog.component.css | 30 ++++++++- .../import-project-dialog.component.html | 32 ++++++---- .../import-project-dialog.component.spec.ts | 46 ++++++++++--- .../import-project-dialog.component.ts | 64 +++++++++++++------ 5 files changed, 131 insertions(+), 44 deletions(-) diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b9426bae..500b3518 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,7 +1,7 @@ import * as Raven from 'raven-js'; import { BrowserModule } from '@angular/platform-browser'; import { NgModule, ErrorHandler } from '@angular/core'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { CdkTableModule } from "@angular/cdk/table"; import { HttpClientModule } from '@angular/common/http'; @@ -131,6 +131,7 @@ if (environment.production) { HttpClientModule, AppRoutingModule, FormsModule, + ReactiveFormsModule, BrowserAnimationsModule, CdkTableModule, MatButtonModule, diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.css b/src/app/components/projects/import-project-dialog/import-project-dialog.component.css index 3ae208bc..b47db760 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.css +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.css @@ -1,9 +1,33 @@ +.non-visible { + display: none; +} + +.file-button{ + height: 50px; + width: 120px; + margin-top: 10px; +} + .file-name-form { + float: right; +} + +.file-name-form-field { margin-left: 10px; margin-right: 10px; width:250px; } -.non-visible { - display: none; -} \ No newline at end of file +.delete-button { + background: transparent; + border: none; + outline: 0 +} + +.delete-icon { + vertical-align: "middle"; +} + +.result-message-box { + margin-top: 10px; +} diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html index f3fd3a31..b27d2c13 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html @@ -1,26 +1,34 @@

Import project

- - - - - - -
- - +
+ + +
+ + + Project name is required + Project name is incorrect + + +
+ + +
+
-
- {{errorMessage}} +
+ {{resultMessage}}
- +
\ No newline at end of file diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts index cd174be8..7aa20d6a 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -1,11 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ImportProjectDialogComponent } from "./import-project-dialog.component"; +import { ImportProjectDialogComponent, Validator } from "./import-project-dialog.component"; import { Server } from "../../../models/server"; import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatStepperModule, MatFormFieldModule, MatDialogRef, MatDialog, MAT_DIALOG_DATA } from "@angular/material"; import { RouterTestingModule } from "@angular/router/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { FileUploadModule, FileSelectDirective, FileItem, FileUploader } from "ng2-file-upload"; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, FormBuilder, FormControl, Validators } from '@angular/forms'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; @@ -13,9 +13,9 @@ describe('ImportProjectDialogComponent', () => { let component: ImportProjectDialogComponent; let fixture: ComponentFixture; let server: Server; - let dialog: MatDialog; let debugElement: DebugElement; let fileSelectDirective: FileSelectDirective; + let formBuilder: FormBuilder; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -31,6 +31,7 @@ describe('ImportProjectDialogComponent', () => { NoopAnimationsModule, FileUploadModule, FormsModule, + ReactiveFormsModule, RouterTestingModule.withRoutes([]), ], providers: [ @@ -41,10 +42,10 @@ describe('ImportProjectDialogComponent', () => { }) .compileComponents(); - dialog = TestBed.get(MatDialog); server = new Server(); server.ip = "localhost"; server.port = 80; + formBuilder = new FormBuilder(); })); beforeEach(() => { @@ -52,7 +53,10 @@ describe('ImportProjectDialogComponent', () => { debugElement = fixture.debugElement; component = fixture.componentInstance; component.server = server; - + component.projectNameForm = formBuilder.group({ + projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator]) + }); + component.projectNameForm.controls['projectName'].setValue("ValidName"); fixture.detectChanges(); debugElement = fixture.debugElement.query(By.directive(FileSelectDirective)); @@ -119,16 +123,16 @@ describe('ImportProjectDialogComponent', () => { it('should prepare correct upload path for file', () => { fileSelectDirective.uploader.queue.push(new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{})); - component.projectName = "newProject.gns3"; + component.projectNameForm.controls['projectName'].setValue("newProject"); - component.prepareUploadPath(); + component.onImportClick(); expect(fileSelectDirective.uploader.queue[0].url).toContain("localhost:80"); expect(fileSelectDirective.uploader.queue[0].url).toContain("newProject"); }); it('should navigate to next step after clicking import', () => { - let fileItem = new FileItem(fileSelectDirective.uploader,new File([],"fileName"),{}); + let fileItem = new FileItem(fileSelectDirective.uploader, new File([],"fileName"),{}); fileSelectDirective.uploader.queue.push(fileItem); spyOn(component.stepper, "next"); @@ -136,4 +140,30 @@ describe('ImportProjectDialogComponent', () => { expect(component.stepper.next).toHaveBeenCalled(); }); + + it('should detect if file input is empty', () => { + component.projectNameForm.controls['projectName'].setValue(""); + fixture.detectChanges(); + spyOn(component.stepper, "next"); + spyOn(fileSelectDirective.uploader, 'uploadItem'); + + component.onImportClick(); + + expect(component.stepper.next).not.toHaveBeenCalled(); + expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled(); + expect(component.projectNameForm.valid).toBeFalsy(); + }); + + it('should sanitize file name input', () => { + component.projectNameForm.controls['projectName'].setValue("[][]"); + fixture.detectChanges(); + spyOn(component.stepper, "next"); + spyOn(fileSelectDirective.uploader, 'uploadItem'); + + component.onImportClick(); + + expect(component.stepper.next).not.toHaveBeenCalled(); + expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled(); + expect(component.projectNameForm.valid).toBeFalsy(); + }); }); diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts index 909baf72..c8f23d58 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -3,51 +3,80 @@ import { MatStepper, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload'; import { Server } from '../../../models/server'; import { v4 as uuid } from 'uuid'; +import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; + +export class Validator { + static projectNameValidator(projectName) { + var pattern = new RegExp(/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/); + + if(!pattern.test(projectName.value)) { + return null; + } + + return { invalidName: true } + } +} @Component({ selector: 'app-import-project-dialog', templateUrl: 'import-project-dialog.component.html', - styleUrls: ['import-project-dialog.component.css'], + styleUrls: ['import-project-dialog.component.css'] }) export class ImportProjectDialogComponent implements OnInit { uploader: FileUploader; server : Server; - projectName : string; isImportEnabled : boolean = false; isFinishEnabled : boolean = false; - errorMessage : string; + resultMessage : string = "The project is being imported... Please wait"; + projectNameForm: FormGroup; + submitted: boolean = false; @ViewChild('stepper') stepper: MatStepper; constructor( public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: any){} + @Inject(MAT_DIALOG_DATA) public data: any, + private formBuilder: FormBuilder){ + this.projectNameForm = this.formBuilder.group({ + projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator]) + }); + } ngOnInit(){ this.uploader = new FileUploader({}); this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; }; } + + get form() { + return this.projectNameForm.controls; + } uploadProjectFile(event) : void{ - this.projectName = event.target.files[0].name.split(".")[0]; + this.projectNameForm.controls['projectName'].setValue(event.target.files[0].name.split(".")[0]); this.isImportEnabled = true; } onImportClick() : void{ - if(this.validateProjectName()){ - this.prepareUploadPath(); + if (this.projectNameForm.invalid){ + this.submitted = true; + } else { + const url = this.prepareUploadPath(); + this.uploader.queue.forEach(elem => elem.url = url); + this.stepper.selected.completed = true; this.stepper.next(); - let itemToUpload = this.uploader.queue[0]; + + const itemToUpload = this.uploader.queue[0]; this.uploader.uploadItem(itemToUpload); this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { - this.errorMessage = response; - this.isFinishEnabled = true; + this.resultMessage = response; + this.isFinishEnabled = true; }; this.uploader.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { - this.isFinishEnabled = true; + this.resultMessage = "Project was imported succesfully!"; + this.isFinishEnabled = true; }; } } @@ -64,16 +93,11 @@ export class ImportProjectDialogComponent implements OnInit { onDeleteClick() : void{ this.uploader.queue.pop(); this.isImportEnabled = false; - this.projectName = ""; + this.projectNameForm.controls['projectName'].setValue(""); } - prepareUploadPath() : void{ - let url = `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${this.projectName}`; - this.uploader.queue.forEach(elem => elem.url = url); - } - - validateProjectName() : boolean{ - var pattern = new RegExp(/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/); - return !pattern.test(this.projectName); + prepareUploadPath() : string{ + const projectName = this.projectNameForm.controls['projectName'].value; + return `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${projectName}`; } } From cc401a584d1c56626bf37347b002a8a371ed6620 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Tue, 30 Oct 2018 04:13:37 -0700 Subject: [PATCH 3/8] Confirmation dialog for existing project added, error message handling added --- src/app/app.module.ts | 5 ++- .../appliance/appliance.component.ts | 1 - ...-project-confirmation-dialog.component.css | 0 ...project-confirmation-dialog.component.html | 10 +++++ ...ject-confirmation-dialog.component.spec.ts | 0 ...t-project-confirmation-dialog.component.ts | 40 +++++++++++++++++++ src/app/models/serverResponse.ts | 4 ++ 7 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.css create mode 100644 src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html create mode 100644 src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts create mode 100644 src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts create mode 100644 src/app/models/serverResponse.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 500b3518..331687b8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -50,6 +50,7 @@ import { LinkService } from "./services/link.service"; import { ProjectsComponent } from './components/projects/projects.component'; import { ImportProjectDialogComponent } from './components/projects/import-project-dialog/import-project-dialog.component'; +import { ImportProjectConfirmationDialogComponent} from './components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component'; import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component'; import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component'; import { AppComponent } from './app.component'; @@ -109,6 +110,7 @@ if (environment.production) { CreateSnapshotDialogComponent, ProjectsComponent, ImportProjectDialogComponent, + ImportProjectConfirmationDialogComponent, DefaultLayoutComponent, ProgressDialogComponent, NodeContextMenuComponent, @@ -191,7 +193,8 @@ if (environment.production) { CreateSnapshotDialogComponent, ProgressDialogComponent, ApplianceListDialogComponent, - ImportProjectDialogComponent + ImportProjectDialogComponent, + ImportProjectConfirmationDialogComponent ], bootstrap: [ AppComponent ] }) diff --git a/src/app/components/appliance/appliance.component.ts b/src/app/components/appliance/appliance.component.ts index 6eb7801f..aaed1faa 100644 --- a/src/app/components/appliance/appliance.component.ts +++ b/src/app/components/appliance/appliance.component.ts @@ -34,4 +34,3 @@ export class ApplianceComponent implements OnInit { }); } } - diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.css b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.css new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html new file mode 100644 index 00000000..74d874d7 --- /dev/null +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html @@ -0,0 +1,10 @@ + + {{confirmationMessage}} + +
+ + +
+
+ +
diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts new file mode 100644 index 00000000..b6b8fe4e --- /dev/null +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, Inject, ViewChild } from '@angular/core'; +import { MatStepper, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; +import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload'; +import { v4 as uuid } from 'uuid'; +import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; +import { Project } from '../../../../models/project'; + +@Component({ + selector: 'app-import-project-dialog', + templateUrl: 'import-project-confirmation-dialog.component.html', + styleUrls: ['import-project-confirmation-dialog.component.css'] +}) +export class ImportProjectConfirmationDialogComponent implements OnInit { + private existingProject : Project; + private confirmationMessage : string; + private isOpen : boolean; + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ){ + this.existingProject = data['existingProject'] + } + + ngOnInit(){ + if(this.existingProject.status === "opened"){ + this.confirmationMessage = `Project ${this.existingProject.name} is open. You can not overwrite it.` + this.isOpen = true; + } else { + this.confirmationMessage = `Project ${this.existingProject.name} already exist, overwrite it?` + } + } + + onNoClick() : void { + this.dialogRef.close(false); + } + + onYesClick() : void { + this.dialogRef.close(true); + } +} diff --git a/src/app/models/serverResponse.ts b/src/app/models/serverResponse.ts new file mode 100644 index 00000000..faf8e621 --- /dev/null +++ b/src/app/models/serverResponse.ts @@ -0,0 +1,4 @@ +export class ServerResponse { + message: string; + status: number; +} From 606b7fa01c681efbf48bebca5a72cac70a2286b9 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Tue, 30 Oct 2018 07:36:16 -0700 Subject: [PATCH 4/8] Unit tests for confirmation dialog component added --- ...project-confirmation-dialog.component.html | 8 +- ...ject-confirmation-dialog.component.spec.ts | 131 ++++++++++++++++++ .../import-project-dialog.component.spec.ts | 43 +++++- .../import-project-dialog.component.ts | 69 +++++++-- 4 files changed, 231 insertions(+), 20 deletions(-) diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html index 74d874d7..10cac4c9 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html @@ -1,9 +1,7 @@ - - {{confirmationMessage}} - +{{confirmationMessage}}
- - + +
diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts index e69de29b..b51e949d 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts @@ -0,0 +1,131 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogModule, MatDialog } from "@angular/material"; +import { NoopAnimationsModule } from "@angular/platform-browser/animations"; +import { Component, NgModule } from '@angular/core'; +import { Project } from '../../../../models/project'; +import { ImportProjectConfirmationDialogComponent } from './import-project-confirmation-dialog.component'; +import { OverlayContainer } from '@angular/cdk/overlay'; + +describe('ImportProjectConfirmationDialogComponent', () => { + let dialog: MatDialog; + let overlayContainerElement: HTMLElement; + + let noop: ComponentFixture; + let existingProject: Project = { + auto_close: false, + auto_open: false, + auto_start: false, + filename: "blank", + name: "blank", + path: "", + project_id: "", + scene_height: 100, + scene_width: 100, + status: "", + readonly: false, + show_interface_labels: false, + show_layers: false, + show_grid: false, + snap_to_grid: false, + }; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ DialogTestModule ], + providers: [ + { provide: OverlayContainer, useFactory: () => { + overlayContainerElement = document.createElement('div'); + return { getContainerElement: () => overlayContainerElement }; + }} + ] + }); + + dialog = TestBed.get(MatDialog); + + noop = TestBed.createComponent(NoopComponent); + }); + + it('should show correct message if project is open', () => { + existingProject.status = "opened"; + const config = { + data: { + 'existingProject' : existingProject + } + }; + + dialog.open(ImportProjectConfirmationDialogComponent, config); + noop.detectChanges(); + + const message = overlayContainerElement.querySelector('span'); + expect(message.textContent).toBe("Project blank is open. You can not overwrite it."); + }); + + it('should show correct message if project is closed', () => { + existingProject.status = "closed"; + const config = { + data: { + 'existingProject' : existingProject + } + }; + + dialog.open(ImportProjectConfirmationDialogComponent, config); + noop.detectChanges(); + + const message = overlayContainerElement.querySelector('span'); + expect(message.textContent).toBe("Project blank already exist, overwrite it?"); + }); + + it('should return false after closing when project is open', () => { + existingProject.status = "opened"; + const config = { + data: { + 'existingProject' : existingProject + } + }; + + let dialogRef = dialog.open(ImportProjectConfirmationDialogComponent, config); + noop.detectChanges(); + const button = overlayContainerElement.querySelector('button'); + spyOn(dialogRef.componentInstance.dialogRef, 'close'); + button.click(); + + expect(dialogRef.componentInstance.dialogRef.close).toHaveBeenCalledWith(false); + }); + + it('should return true after choosing overriding', () => { + existingProject.status = "closed"; + const config = { + data: { + 'existingProject' : existingProject + } + }; + + let dialogRef = dialog.open(ImportProjectConfirmationDialogComponent, config); + noop.detectChanges(); + const button: HTMLButtonElement = overlayContainerElement.querySelector('.confirmButton'); + spyOn(dialogRef.componentInstance.dialogRef, 'close'); + button.click(); + + expect(dialogRef.componentInstance.dialogRef.close).toHaveBeenCalledWith(true); + }); +}); + +@Component({ + template: '' +}) +class NoopComponent {} + +const TEST_DIRECTIVES = [ + ImportProjectConfirmationDialogComponent, + NoopComponent +]; + +@NgModule({ + imports: [MatDialogModule, NoopAnimationsModule], + exports: TEST_DIRECTIVES, + declarations: TEST_DIRECTIVES, + entryComponents: [ + ImportProjectConfirmationDialogComponent + ], +}) +class DialogTestModule { } diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts index 7aa20d6a..e89c30fd 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -4,10 +4,37 @@ import { Server } from "../../../models/server"; import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatStepperModule, MatFormFieldModule, MatDialogRef, MatDialog, MAT_DIALOG_DATA } from "@angular/material"; import { RouterTestingModule } from "@angular/router/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { FileUploadModule, FileSelectDirective, FileItem, FileUploader } from "ng2-file-upload"; +import { FileUploadModule, FileSelectDirective, FileItem, FileUploader, ParsedResponseHeaders } from "ng2-file-upload"; import { FormsModule, ReactiveFormsModule, FormBuilder, FormControl, Validators } from '@angular/forms'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; +import { ProjectService } from '../../../services/project.service'; +import { of } from 'rxjs/internal/observable/of'; +import { Project } from '../../../models/project'; + +export class MockedProjectService { + public projects: Project[] = [{ + auto_close: false, + auto_open: false, + auto_start: false, + filename: "blank", + name: "blank", + path: "", + project_id: "", + scene_height: 100, + scene_width: 100, + status: "opened", + readonly: false, + show_interface_labels: false, + show_layers: false, + show_grid: false, + snap_to_grid: false, + }]; + + list(server: Server) { + return of(this.projects); + } +} describe('ImportProjectDialogComponent', () => { let component: ImportProjectDialogComponent; @@ -36,7 +63,8 @@ describe('ImportProjectDialogComponent', () => { ], providers: [ { provide: MatDialogRef }, - { provide: MAT_DIALOG_DATA } + { provide: MAT_DIALOG_DATA }, + { provide: ProjectService, useClass: MockedProjectService} ], declarations : [ImportProjectDialogComponent] }) @@ -61,6 +89,7 @@ describe('ImportProjectDialogComponent', () => { debugElement = fixture.debugElement.query(By.directive(FileSelectDirective)); fileSelectDirective = debugElement.injector.get(FileSelectDirective) as FileSelectDirective; + component.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {}; }); it('should be created', () => { @@ -134,6 +163,7 @@ describe('ImportProjectDialogComponent', () => { it('should navigate to next step after clicking import', () => { let fileItem = new FileItem(fileSelectDirective.uploader, new File([],"fileName"),{}); fileSelectDirective.uploader.queue.push(fileItem); + spyOn(component.stepper, "next"); component.onImportClick(); @@ -166,4 +196,13 @@ describe('ImportProjectDialogComponent', () => { expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled(); expect(component.projectNameForm.valid).toBeFalsy(); }); + + it('should open confirmation dialog if project with the same exists', () => { + component.projectNameForm.controls['projectName'].setValue("blank"); + spyOn(component, "openConfirmationDialog"); + + component.onImportClick(); + + expect(component.openConfirmationDialog).toHaveBeenCalled(); + }); }); diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts index c8f23d58..e2ff62f1 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -1,9 +1,13 @@ import { Component, OnInit, Inject, ViewChild } from '@angular/core'; -import { MatStepper, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; +import { MatStepper, MatDialogRef, MAT_DIALOG_DATA, MatDialog } from "@angular/material"; import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload'; import { Server } from '../../../models/server'; import { v4 as uuid } from 'uuid'; import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; +import { ProjectService } from '../../../services/project.service'; +import { Project } from '../../../models/project'; +import { ImportProjectConfirmationDialogComponent } from './import-project-confirmation-dialog/import-project-confirmation-dialog.component'; +import { ServerResponse } from '../../../models/serverResponse'; export class Validator { static projectNameValidator(projectName) { @@ -34,9 +38,11 @@ export class ImportProjectDialogComponent implements OnInit { @ViewChild('stepper') stepper: MatStepper; constructor( + private dialog: MatDialog, public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, - private formBuilder: FormBuilder){ + private formBuilder: FormBuilder, + private projectService: ProjectService){ this.projectNameForm = this.formBuilder.group({ projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator]) }); @@ -45,6 +51,17 @@ export class ImportProjectDialogComponent implements OnInit { ngOnInit(){ this.uploader = new FileUploader({}); this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; }; + + this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { + let serverResponse : ServerResponse = JSON.parse(response); + this.resultMessage = "An error occured: " + serverResponse.message; + this.isFinishEnabled = true; + }; + + this.uploader.onCompleteItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { + this.resultMessage = "Project was imported succesfully!"; + this.isFinishEnabled = true; + }; } get form() { @@ -60,6 +77,22 @@ export class ImportProjectDialogComponent implements OnInit { if (this.projectNameForm.invalid){ this.submitted = true; } else { + this.projectService + .list(this.server) + .subscribe((projects: Project[]) => { + const projectName = this.projectNameForm.controls['projectName'].value; + let existingProject = projects.find(project => project.name === projectName); + + if (existingProject){ + this.openConfirmationDialog(existingProject); + } else { + this.importProject(); + } + }); + } + } + + importProject(){ const url = this.prepareUploadPath(); this.uploader.queue.forEach(elem => elem.url = url); @@ -67,18 +100,27 @@ export class ImportProjectDialogComponent implements OnInit { this.stepper.next(); const itemToUpload = this.uploader.queue[0]; - this.uploader.uploadItem(itemToUpload); + this.uploader.uploadItem(itemToUpload); + } - this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { - this.resultMessage = response; - this.isFinishEnabled = true; - }; - - this.uploader.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { - this.resultMessage = "Project was imported succesfully!"; - this.isFinishEnabled = true; - }; - } + openConfirmationDialog(existingProject: Project) { + const dialogRef = this.dialog.open(ImportProjectConfirmationDialogComponent, { + width: '300px', + height: '150px', + data: { + 'existingProject': existingProject + } + }); + + dialogRef.afterClosed().subscribe((answer: boolean) => { + if (answer) { + this.projectService.close(this.server, existingProject.project_id).subscribe(() => { + this.projectService.delete(this.server, existingProject.project_id).subscribe(() => { + this.importProject(); + }); + }); + } + }); } onNoClick() : void{ @@ -101,3 +143,4 @@ export class ImportProjectDialogComponent implements OnInit { return `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${projectName}`; } } + \ No newline at end of file From 35f16d3eff6c9a35e08424acca87fc003881d5ad Mon Sep 17 00:00:00 2001 From: PiotrP Date: Tue, 30 Oct 2018 07:50:54 -0700 Subject: [PATCH 5/8] Fix for tests --- .../import-project-confirmation-dialog.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts index b6b8fe4e..0b640c6d 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts @@ -12,8 +12,8 @@ import { Project } from '../../../../models/project'; }) export class ImportProjectConfirmationDialogComponent implements OnInit { private existingProject : Project; - private confirmationMessage : string; - private isOpen : boolean; + public confirmationMessage : string; + public isOpen : boolean; constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any From c4486ee1049edc5473c1d54399c760a840877ff0 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Fri, 2 Nov 2018 02:22:04 -0700 Subject: [PATCH 6/8] Fixes after cr --- ...project-confirmation-dialog.component.html | 4 +-- .../import-project-dialog.component.css | 17 ++++++++---- .../import-project-dialog.component.html | 18 ++++++------- .../import-project-dialog.component.spec.ts | 27 +++++++++++++++++++ .../import-project-dialog.component.ts | 6 +++-- .../projects/projects.component.css | 5 ++-- .../projects/projects.component.html | 9 +++---- 7 files changed, 61 insertions(+), 25 deletions(-) diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html index 10cac4c9..6d10a1db 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html @@ -1,8 +1,8 @@ {{confirmationMessage}} -
+
-
+
diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.css b/src/app/components/projects/import-project-dialog/import-project-dialog.component.css index b47db760..6d26582e 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.css +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.css @@ -4,24 +4,26 @@ .file-button{ height: 50px; - width: 120px; + width: 20%; margin-top: 10px; + padding: 0px; } .file-name-form { float: right; + width: 100%; } .file-name-form-field { - margin-left: 10px; - margin-right: 10px; - width:250px; + margin-left: 5%; + width: 65%; } .delete-button { background: transparent; border: none; - outline: 0 + outline: 0; + width: 10%; } .delete-icon { @@ -30,4 +32,9 @@ .result-message-box { margin-top: 10px; + text-align: center; +} + +.progress-bar { + background-color : #0097a7; } diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html index b27d2c13..99fe4238 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html @@ -2,32 +2,32 @@
- -
+ + Project name is required Project name is incorrect - -
+ +
-
+
-
+
{{resultMessage}}
-
+
diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts index e89c30fd..5c32d7e1 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -205,4 +205,31 @@ describe('ImportProjectDialogComponent', () => { expect(component.openConfirmationDialog).toHaveBeenCalled(); }); + + it('should show delete button after selecting project', () => { + let fileItem = new FileItem(fileSelectDirective.uploader, new File([],"fileName"),{}); + fileSelectDirective.uploader.queue.push(fileItem); + let event = { + target: { + files: [ {name : "uploadedFile"} ] + } + }; + component.uploadProjectFile(event); + + expect(component.isDeleteVisible).toBe(true); + }); + + it('should hide delete button after deselecting project', () => { + let fileItem = new FileItem(fileSelectDirective.uploader, new File([],"fileName"),{}); + fileSelectDirective.uploader.queue.push(fileItem); + let event = { + target: { + files: [ {name : "uploadedFile"} ] + } + }; + component.uploadProjectFile(event); + component.onDeleteClick(); + + expect(component.isDeleteVisible).toBe(false); + }); }); diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts index e2ff62f1..9995299b 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -11,7 +11,7 @@ import { ServerResponse } from '../../../models/serverResponse'; export class Validator { static projectNameValidator(projectName) { - var pattern = new RegExp(/[~`!#$%\^&*+=\-\[\]\\';,/{}|\\":<>\?]/); + var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?]/); if(!pattern.test(projectName.value)) { return null; @@ -31,6 +31,7 @@ export class ImportProjectDialogComponent implements OnInit { server : Server; isImportEnabled : boolean = false; isFinishEnabled : boolean = false; + isDeleteVisible : boolean = false; resultMessage : string = "The project is being imported... Please wait"; projectNameForm: FormGroup; submitted: boolean = false; @@ -71,6 +72,7 @@ export class ImportProjectDialogComponent implements OnInit { uploadProjectFile(event) : void{ this.projectNameForm.controls['projectName'].setValue(event.target.files[0].name.split(".")[0]); this.isImportEnabled = true; + this.isDeleteVisible = true; } onImportClick() : void{ @@ -135,6 +137,7 @@ export class ImportProjectDialogComponent implements OnInit { onDeleteClick() : void{ this.uploader.queue.pop(); this.isImportEnabled = false; + this.isDeleteVisible = false; this.projectNameForm.controls['projectName'].setValue(""); } @@ -143,4 +146,3 @@ export class ImportProjectDialogComponent implements OnInit { return `http://${this.server.ip}:${this.server.port}/v2/projects/${uuid()}/import?name=${projectName}`; } } - \ No newline at end of file diff --git a/src/app/components/projects/projects.component.css b/src/app/components/projects/projects.component.css index 6884375a..d49aad69 100644 --- a/src/app/components/projects/projects.component.css +++ b/src/app/components/projects/projects.component.css @@ -1,3 +1,4 @@ .import-button { - margin-right:10px -} \ No newline at end of file + height: 40px; + margin: 20px; +} diff --git a/src/app/components/projects/projects.component.html b/src/app/components/projects/projects.component.html index 4ec801cc..e14ad2d6 100644 --- a/src/app/components/projects/projects.component.html +++ b/src/app/components/projects/projects.component.html @@ -1,6 +1,9 @@
-

Projects

+
+

Projects

+ +
@@ -33,9 +36,5 @@
- -
- -
From ce13d3a68cd86d84d6c06409c8f73fbef6000e81 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Wed, 7 Nov 2018 05:43:19 -0800 Subject: [PATCH 7/8] Removing stepper --- ...t-project-confirmation-dialog.component.ts | 7 +-- .../import-project-dialog.component.html | 58 +++++++++---------- .../import-project-dialog.component.spec.ts | 12 +--- .../import-project-dialog.component.ts | 6 +- 4 files changed, 34 insertions(+), 49 deletions(-) diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts index 0b640c6d..48036d07 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts @@ -1,8 +1,5 @@ -import { Component, OnInit, Inject, ViewChild } from '@angular/core'; -import { MatStepper, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; -import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload'; -import { v4 as uuid } from 'uuid'; -import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; import { Project } from '../../../../models/project'; @Component({ diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html index 99fe4238..e9eedd0b 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.html +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.html @@ -1,34 +1,30 @@

Import project

- - -
-
- - - - - Project name is required - Project name is incorrect - - -
- - -
-
-
-
- -
-
-
-
- {{resultMessage}} -
+
+
+ + + + + Project name is required + Project name is incorrect + +
- + +
- - \ No newline at end of file +
+
+
+
+
+
+
+ {{resultMessage}} +
+
+ +
+
diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts index 5c32d7e1..45d3628e 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -36,7 +36,7 @@ export class MockedProjectService { } } -describe('ImportProjectDialogComponent', () => { +fdescribe('ImportProjectDialogComponent', () => { let component: ImportProjectDialogComponent; let fixture: ComponentFixture; let server: Server; @@ -160,26 +160,22 @@ describe('ImportProjectDialogComponent', () => { expect(fileSelectDirective.uploader.queue[0].url).toContain("newProject"); }); - it('should navigate to next step after clicking import', () => { + it('should navigate to progress view after clicking import', () => { let fileItem = new FileItem(fileSelectDirective.uploader, new File([],"fileName"),{}); fileSelectDirective.uploader.queue.push(fileItem); - spyOn(component.stepper, "next"); - component.onImportClick(); - expect(component.stepper.next).toHaveBeenCalled(); + expect(component.isFirstStepCompleted).toBe(true); }); it('should detect if file input is empty', () => { component.projectNameForm.controls['projectName'].setValue(""); fixture.detectChanges(); - spyOn(component.stepper, "next"); spyOn(fileSelectDirective.uploader, 'uploadItem'); component.onImportClick(); - expect(component.stepper.next).not.toHaveBeenCalled(); expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled(); expect(component.projectNameForm.valid).toBeFalsy(); }); @@ -187,12 +183,10 @@ describe('ImportProjectDialogComponent', () => { it('should sanitize file name input', () => { component.projectNameForm.controls['projectName'].setValue("[][]"); fixture.detectChanges(); - spyOn(component.stepper, "next"); spyOn(fileSelectDirective.uploader, 'uploadItem'); component.onImportClick(); - expect(component.stepper.next).not.toHaveBeenCalled(); expect(fileSelectDirective.uploader.uploadItem).not.toHaveBeenCalled(); expect(component.projectNameForm.valid).toBeFalsy(); }); diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts index 9995299b..e6a3ddbc 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -35,8 +35,7 @@ export class ImportProjectDialogComponent implements OnInit { resultMessage : string = "The project is being imported... Please wait"; projectNameForm: FormGroup; submitted: boolean = false; - - @ViewChild('stepper') stepper: MatStepper; + isFirstStepCompleted: boolean = false; constructor( private dialog: MatDialog, @@ -98,8 +97,7 @@ export class ImportProjectDialogComponent implements OnInit { const url = this.prepareUploadPath(); this.uploader.queue.forEach(elem => elem.url = url); - this.stepper.selected.completed = true; - this.stepper.next(); + this.isFirstStepCompleted = true; const itemToUpload = this.uploader.queue[0]; this.uploader.uploadItem(itemToUpload); From 58a4ed08b1e8eedc50332a90dd8d68199e3fffa2 Mon Sep 17 00:00:00 2001 From: PiotrP Date: Wed, 7 Nov 2018 05:43:57 -0800 Subject: [PATCH 8/8] Code cleaned up --- .../import-project-dialog.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts index 45d3628e..7ee72a8a 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -36,7 +36,7 @@ export class MockedProjectService { } } -fdescribe('ImportProjectDialogComponent', () => { +describe('ImportProjectDialogComponent', () => { let component: ImportProjectDialogComponent; let fixture: ComponentFixture; let server: Server;