From 1c93702063120fc9ae2c5f236f31dd98860dbef8 Mon Sep 17 00:00:00 2001 From: Martin Rojas Miguel Angel Date: Mon, 21 May 2018 14:39:09 +0200 Subject: [PATCH] TSK-492 Access validation, type ahead component created. --- web/package-lock.json | 56 ++++++-- web/package.json | 12 +- .../administration/administration.module.ts | 5 +- .../access-items/access-items.component.html | 14 +- .../access-items/access-items.component.scss | 13 ++ .../access-items.component.spec.ts | 37 ++++- .../access-items/access-items.component.ts | 12 +- .../workbasket-information.component.html | 16 ++- .../workbasket-information.component.spec.ts | 37 ++++- .../workbasket-information.component.ts | 2 - .../workbasket-details.component.spec.ts | 38 ++++- web/src/app/app.module.ts | 6 +- web/src/app/guards/admin-guard.ts | 37 ----- web/src/app/guards/business-admin-guard.ts | 10 +- web/src/app/guards/monitor-guard.ts | 10 +- web/src/app/models/access-id.ts | 8 ++ .../access-ids/access-ids.service.spec.ts | 18 +++ .../services/access-ids/access-ids.service.ts | 24 ++++ web/src/app/shared/shared.module.ts | 15 +- .../app/shared/spinner/spinner.component.scss | 9 +- .../type-ahead/type-ahead.component.html | 38 +++++ .../type-ahead/type-ahead.component.scss | 47 +++++++ .../shared/type-ahead/type-ahead.component.ts | 130 ++++++++++++++++++ web/src/assets/_styles.scss | 3 +- web/src/assets/_type-ahead.scss | 12 ++ web/taskana-web.iml | 12 ++ 26 files changed, 527 insertions(+), 94 deletions(-) delete mode 100644 web/src/app/guards/admin-guard.ts create mode 100644 web/src/app/models/access-id.ts create mode 100644 web/src/app/shared/services/access-ids/access-ids.service.spec.ts create mode 100644 web/src/app/shared/services/access-ids/access-ids.service.ts create mode 100644 web/src/app/shared/type-ahead/type-ahead.component.html create mode 100644 web/src/app/shared/type-ahead/type-ahead.component.scss create mode 100644 web/src/app/shared/type-ahead/type-ahead.component.ts create mode 100644 web/src/assets/_type-ahead.scss create mode 100644 web/taskana-web.iml diff --git a/web/package-lock.json b/web/package-lock.json index 27da3c403..9eb81ac5f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -38,10 +38,19 @@ "requires": { "ajv": "5.5.2", "chokidar": "1.7.0", - "rxjs": "5.5.6", + "rxjs": "5.5.10", "source-map": "0.5.7" }, "dependencies": { + "rxjs": { + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -57,7 +66,18 @@ "dev": true, "requires": { "@ngtools/json-schema": "1.2.0", - "rxjs": "5.5.6" + "rxjs": "5.5.10" + }, + "dependencies": { + "rxjs": { + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + } } }, "@angular/animations": { @@ -117,7 +137,7 @@ "postcss-url": "7.3.1", "raw-loader": "0.5.1", "resolve": "1.5.0", - "rxjs": "5.5.6", + "rxjs": "5.5.10", "sass-loader": "6.0.7", "semver": "5.5.0", "silent-error": "1.1.0", @@ -176,6 +196,15 @@ "osenv": "0.1.4" } }, + "rxjs": { + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + }, "supports-color": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", @@ -345,9 +374,20 @@ "integrity": "sha512-7aVP4994Hu8vRdTTohXkfGWEwLhrdNP3EZnWyBootm5zshWqlQojUGweZe5zwewsKcixeVOiy2YtW+aI4aGSLA==", "dev": true, "requires": { - "rxjs": "5.5.6", + "rxjs": "5.5.10", "semver": "5.5.0", "semver-intersect": "1.3.1" + }, + "dependencies": { + "rxjs": { + "version": "5.5.10", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.10.tgz", + "integrity": "sha512-SRjimIDUHJkon+2hFo7xnvNC4ZEHGzCRwh9P7nzX3zPkCGFEg/tuElrNR7L/rZMagnK2JeH2jQwPRpmyXyLB6A==", + "dev": true, + "requires": { + "symbol-observable": "1.0.1" + } + } } }, "@types/jasmine": { @@ -2096,7 +2136,7 @@ }, "compression": { "version": "1.7.2", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz", + "resolved": "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz", "integrity": "sha1-qv+81qr4VLROuygDU9WtFlH1mmk=", "dev": true, "requires": { @@ -9482,9 +9522,9 @@ } }, "rxjs": { - "version": "5.5.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.6.tgz", - "integrity": "sha512-v4Q5HDC0FHAQ7zcBX7T2IL6O5ltl1a2GX4ENjPXg6SjDY69Cmx9v4113C99a4wGF16ClPv5Z8mghuYorVkg/kg==", + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", + "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", "requires": { "symbol-observable": "1.0.1" } diff --git a/web/package.json b/web/package.json index 743baaca0..d4a8c0f55 100644 --- a/web/package.json +++ b/web/package.json @@ -23,21 +23,21 @@ "@angular/platform-browser": "5.2.10", "@angular/platform-browser-dynamic": "5.2.10", "@angular/router": "5.2.10", - "file-saver": "1.3.3", "angular-svg-icon": "5.0.0", "angular-tree-component": "7.1.0", "bootstrap": "3.3.7", "bootstrap-sass": "3.3.7", + "chart.js": "2.7.1", "core-js": "2.5.3", + "file-saver": "1.3.3", "jquery": "3.3.1", "magic-string": "0.22.4", + "ng2-auto-complete": "0.12.0", + "ng2-charts": "1.6.0", "ngx-bootstrap": "2.0.1", "node-sass": "4.7.2", - "rxjs": "5.5.6", - "zone.js": "0.8.20", - "chart.js": "2.7.1", - "ng2-charts": "1.6.0", - "ng2-auto-complete": "0.12.0" + "rxjs": "5.5.9", + "zone.js": "0.8.20" }, "devDependencies": { "@angular/cli": "1.7.3", diff --git a/web/src/app/administration/administration.module.ts b/web/src/app/administration/administration.module.ts index 7abeb9776..96f3b6d7b 100644 --- a/web/src/app/administration/administration.module.ts +++ b/web/src/app/administration/administration.module.ts @@ -7,6 +7,7 @@ import { AngularSvgIconModule } from 'angular-svg-icon'; import { AlertModule } from 'ngx-bootstrap'; import { SharedModule } from 'app/shared/shared.module'; import { AdministrationRoutingModule } from './administration-routing.module'; +import { TypeaheadModule } from 'ngx-bootstrap'; /** * Components @@ -39,7 +40,6 @@ import { ClassificationsService } from './services/classifications/classificatio import { ClassificationTypesService } from './services/classification-types/classification-types.service'; import { ClassificationCategoriesService } from './services/classification-categories-service/classification-categories.service'; - const MODULES = [ CommonModule, FormsModule, @@ -47,7 +47,8 @@ const MODULES = [ AngularSvgIconModule, AlertModule, SharedModule, - AdministrationRoutingModule + AdministrationRoutingModule, + TypeaheadModule ]; const DECLARATIONS = [ diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.html b/web/src/app/administration/workbasket/details/access-items/access-items.component.html index f600442fe..287fc473f 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.html +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.html @@ -41,13 +41,13 @@ - -
- -
+ + + + diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.scss b/web/src/app/administration/workbasket/details/access-items/access-items.component.scss index 6f3248ece..f813f7f30 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.scss +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.scss @@ -5,9 +5,14 @@ td > input[type="checkbox"] { overflow-x: auto; } .text-width{ + width: 100%; min-width: 180px; } +.required-header { + width: 200px; +} .required-header:after { + content:" *"; color: red; } @@ -17,3 +22,11 @@ td { border-bottom: 1px solid #f0ad4e;; } } +.table > thead > tr > th { + max-width: 150px; + border-bottom: none; +} + +.has-warning.taskana-type-ahead { + border-bottom: 1px solid #f0ad4e; +} \ No newline at end of file diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts b/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts index 54cbdda7f..ddc172a54 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.spec.ts @@ -1,6 +1,6 @@ -import { SimpleChange } from '@angular/core'; +import { SimpleChange, Component, forwardRef, Input } from '@angular/core'; import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { HttpModule, JsonpModule } from '@angular/http'; import { AngularSvgIconModule } from 'angular-svg-icon'; @@ -26,6 +26,37 @@ import { DomainService } from 'app/services/domain/domain.service'; import { DomainServiceMock } from 'app/services/domain/domain.service.mock'; import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service'; + +@Component({ + selector: 'taskana-type-ahead', + template: 'dummydetail', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => TaskanaTypeAheadComponent), + } + ] +}) +export class TaskanaTypeAheadComponent implements ControlValueAccessor { + @Input() + placeHolderMessage; + + writeValue(obj: any): void { + + } + registerOnChange(fn: any): void { + + } + registerOnTouched(fn: any): void { + + } + setDisabledState?(isDisabled: boolean): void { + + } + +} + describe('AccessItemsComponent', () => { let component: AccessItemsComponent; let fixture: ComponentFixture; @@ -33,7 +64,7 @@ describe('AccessItemsComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent], + declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent, TaskanaTypeAheadComponent], imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule], providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService, RequestInProgressService, { diff --git a/web/src/app/administration/workbasket/details/access-items/access-items.component.ts b/web/src/app/administration/workbasket/details/access-items/access-items.component.ts index 938ef0efd..f1f351d91 100644 --- a/web/src/app/administration/workbasket/details/access-items/access-items.component.ts +++ b/web/src/app/administration/workbasket/details/access-items/access-items.component.ts @@ -15,9 +15,10 @@ import { AlertService } from 'app/services/alert/alert.service'; import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service'; import { TitlesService } from 'app/services/titles/titles.service'; import { CustomFieldsService } from '../../../../services/custom-fields/custom-fields.service'; +import { Observable } from 'rxjs/Observable'; +import { TypeaheadMatch } from 'ngx-bootstrap/typeahead'; declare var $: any; - @Component({ selector: 'taskana-workbasket-access-items', templateUrl: './access-items.component.html', @@ -25,7 +26,6 @@ declare var $: any; }) export class AccessItemsComponent implements OnChanges, OnDestroy { - @Input() workbasket: Workbasket; @Input() @@ -65,7 +65,11 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { private errorModalService: ErrorModalService, private savingWorkbaskets: SavingWorkbasketService, private requestInProgressService: RequestInProgressService, - private customFieldService: CustomFieldsService) { } + private customFieldService: CustomFieldsService) { + + } + + ngOnChanges(changes: SimpleChanges): void { if (!this.initialized && changes.active && changes.active.currentValue === 'accessItems') { @@ -75,6 +79,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { this.setBadge(); } } + private init() { this.initialized = true; if (!this.workbasket._links.accessItems) { @@ -133,6 +138,7 @@ export class AccessItemsComponent implements OnChanges, OnDestroy { }) return false; } + private setBadge() { if (this.action === ACTION.COPY) { this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`; diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html index 9bd0e07ec..b4fea6bfe 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html @@ -6,7 +6,7 @@ + @@ -39,9 +39,8 @@
- -
+ +
* Owner is required
@@ -98,15 +97,18 @@
- +
- +
- +
diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts index 304166566..e0d2fe94e 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.spec.ts @@ -1,13 +1,13 @@ import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; import { WorkbasketService } from 'app/administration/services/workbasket/workbasket.service'; import { WorkbasketInformationComponent } from './workbasket-information.component'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { HttpClientModule } from '@angular/common/http'; import { HttpModule } from '@angular/http'; import { RouterTestingModule } from '@angular/router/testing'; import { Observable } from 'rxjs/Observable'; -import { Component } from '@angular/core'; +import { Component, Input, forwardRef } from '@angular/core'; import { Routes } from '@angular/router'; import { AppModule } from 'app/app.module' @@ -37,6 +37,36 @@ import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.se export class DummyDetailComponent { } +@Component({ + selector: 'taskana-type-ahead', + template: 'dummydetail', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => TaskanaTypeAheadComponent), + } + ] +}) +export class TaskanaTypeAheadComponent implements ControlValueAccessor { + @Input() + placeHolderMessage; + + writeValue(obj: any): void { + + } + registerOnChange(fn: any): void { + + } + registerOnTouched(fn: any): void { + + } + setDisabledState?(isDisabled: boolean): void { + + } + +} + const routes: Routes = [ { path: ':id', component: DummyDetailComponent, outlet: 'detail' }, { path: 'someNewId', component: DummyDetailComponent } @@ -50,7 +80,8 @@ describe('InformationComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe, - RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent], + RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent, DummyDetailComponent, + TaskanaTypeAheadComponent], imports: [FormsModule, AngularSvgIconModule, HttpClientModule, diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts index 3c70b0a01..66ec053fb 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.ts @@ -42,7 +42,6 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest custom3Field = this.customFieldsService.getCustomField('Custom 3', 'workbaskets.information.custom3'); custom4Field = this.customFieldsService.getCustomField('Custom 4', 'workbaskets.information.custom4'); - private workbasketSubscription: Subscription; private routeSubscription: Subscription; @@ -132,7 +131,6 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest }); } - private beforeRequest() { this.requestInProgressService.setRequestInProgress(true); } diff --git a/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts b/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts index c6d722cee..88b847dd7 100644 --- a/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts +++ b/web/src/app/administration/workbasket/details/workbasket-details.component.spec.ts @@ -1,8 +1,8 @@ -import { Component, Input } from '@angular/core'; +import { Component, Input, forwardRef } from '@angular/core'; import { async, ComponentFixture, TestBed, } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { Router, Routes } from '@angular/router'; -import { FormsModule } from '@angular/forms'; +import { FormsModule, NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'; import { AngularSvgIconModule } from 'angular-svg-icon'; import { HttpClientModule } from '@angular/common/http'; import { HttpModule } from '@angular/http'; @@ -58,6 +58,37 @@ export class FilterComponent { export class DummyDetailComponent { } +@Component({ + selector: 'taskana-type-ahead', + template: 'dummydetail', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + multi: true, + useExisting: forwardRef(() => TaskanaTypeAheadComponent), + } + ] +}) +export class TaskanaTypeAheadComponent implements ControlValueAccessor { + + @Input() + placeHolderMessage; + + writeValue(obj: any): void { + + } + registerOnChange(fn: any): void { + + } + registerOnTouched(fn: any): void { + + } + setDisabledState?(isDisabled: boolean): void { + + } + +} + describe('WorkbasketDetailsComponent', () => { let component: WorkbasketDetailsComponent; let fixture: ComponentFixture; @@ -77,7 +108,8 @@ describe('WorkbasketDetailsComponent', () => { imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule], declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent, IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent, - DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe], + DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, + TaskanaTypeAheadComponent, SelectWorkBasketPipe], providers: [WorkbasketService, MasterAndDetailService, ErrorModalService, RequestInProgressService, AlertService, SavingWorkbasketService, { provide: DomainService, diff --git a/web/src/app/app.module.ts b/web/src/app/app.module.ts index f58dde95d..37984d1b3 100644 --- a/web/src/app/app.module.ts +++ b/web/src/app/app.module.ts @@ -50,11 +50,11 @@ import { APP_BASE_HREF } from '@angular/common'; const MODULES = [ + TabsModule.forRoot(), + AlertModule.forRoot(), BrowserModule, FormsModule, - TabsModule.forRoot(), AppRoutingModule, - AlertModule.forRoot(), AngularSvgIconModule, HttpClientModule, BrowserAnimationsModule, @@ -66,7 +66,7 @@ const MODULES = [ const DECLARATIONS = [ AppComponent, NavBarComponent, - UserInformationComponent + UserInformationComponent, ]; export function startupServiceFactory(startupService: StartupService): () => Promise { diff --git a/web/src/app/guards/admin-guard.ts b/web/src/app/guards/admin-guard.ts deleted file mode 100644 index d21a9513e..000000000 --- a/web/src/app/guards/admin-guard.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Observable } from 'rxjs/Observable'; -import { HttpClient } from '@angular/common/http'; -import { CanActivate, Router } from '@angular/router'; -import { Injectable } from '@angular/core'; -import { DomainService } from 'app/services/domain/domain.service'; -import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; -import { ErrorModel } from 'app/models/modal-error'; -import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service'; - -@Injectable() -export class AdminGuard implements CanActivate { - constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { } - - canActivate() { - return this.taskanaEngineService.getUserInformation().map(userInfo => { - if (userInfo.roles.length === 0) { - return this.navigateToWorplace(); - } - const adminRole = userInfo.roles.find(role => { - if (role === 'ADMIN') { - return true; - } - }); - if (adminRole) { - return true; - } - return this.navigateToWorplace(); - }).catch(() => { - return Observable.of(this.navigateToWorplace()) - }); - } - - navigateToWorplace(): boolean { - this.router.navigate(['workplace']); - return false - } -} diff --git a/web/src/app/guards/business-admin-guard.ts b/web/src/app/guards/business-admin-guard.ts index b1fd56379..a593e89d6 100644 --- a/web/src/app/guards/business-admin-guard.ts +++ b/web/src/app/guards/business-admin-guard.ts @@ -6,10 +6,12 @@ import { DomainService } from 'app/services/domain/domain.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModel } from 'app/models/modal-error'; import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service'; +import { WindowRefService } from 'app/services/window/window.service'; @Injectable() export class BusinessAdminGuard implements CanActivate { - constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { } + constructor(private taskanaEngineService: TaskanaEngineService, public router: Router, + private window: WindowRefService) { } canActivate() { return this.taskanaEngineService.getUserInformation().map(userInfo => { @@ -17,7 +19,7 @@ export class BusinessAdminGuard implements CanActivate { return this.navigateToWorplace(); } const adminRole = userInfo.roles.find(role => { - if (role === 'BUSINESS_ADMIN' || role === 'ADMIN' ) { + if (role === 'BUSINESS_ADMIN' || role === 'ADMIN') { return true; } }); @@ -32,7 +34,9 @@ export class BusinessAdminGuard implements CanActivate { } navigateToWorplace(): boolean { - this.router.navigate(['workplace']); + if (this.window.nativeWindow.location.href.indexOf('administration') !== -1) { + this.router.navigate(['workplace']); + } return false } } diff --git a/web/src/app/guards/monitor-guard.ts b/web/src/app/guards/monitor-guard.ts index 37f3eaf51..7add44c11 100644 --- a/web/src/app/guards/monitor-guard.ts +++ b/web/src/app/guards/monitor-guard.ts @@ -6,10 +6,12 @@ import { DomainService } from 'app/services/domain/domain.service'; import { ErrorModalService } from 'app/services/errorModal/error-modal.service'; import { ErrorModel } from 'app/models/modal-error'; import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service'; +import { WindowRefService } from 'app/services/window/window.service'; @Injectable() export class MonitorGuard implements CanActivate { - constructor(private taskanaEngineService: TaskanaEngineService, public router: Router) { } + constructor(private taskanaEngineService: TaskanaEngineService, public router: Router, + private window: WindowRefService) { } canActivate() { return this.taskanaEngineService.getUserInformation().map(userInfo => { @@ -17,7 +19,7 @@ export class MonitorGuard implements CanActivate { return this.navigateToWorplace(); } const adminRole = userInfo.roles.find(role => { - if (role === 'MONITOR' || role === 'ADMIN' ) { + if (role === 'MONITOR' || role === 'ADMIN') { return true; } }); @@ -32,7 +34,9 @@ export class MonitorGuard implements CanActivate { } navigateToWorplace(): boolean { - this.router.navigate(['workplace']); + if (this.window.nativeWindow.location.href.indexOf('monitor') !== -1) { + this.router.navigate(['workplace']); + } return false } } diff --git a/web/src/app/models/access-id.ts b/web/src/app/models/access-id.ts new file mode 100644 index 000000000..16cfb5e4d --- /dev/null +++ b/web/src/app/models/access-id.ts @@ -0,0 +1,8 @@ +import { LinksClassification } from 'app/models/links-classfication'; + +export class AccessIdDefinition { + constructor( + public accessId: string = undefined, + public name: string = undefined) { + } +} diff --git a/web/src/app/shared/services/access-ids/access-ids.service.spec.ts b/web/src/app/shared/services/access-ids/access-ids.service.spec.ts new file mode 100644 index 000000000..fc5019db4 --- /dev/null +++ b/web/src/app/shared/services/access-ids/access-ids.service.spec.ts @@ -0,0 +1,18 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { AccessIdsService } from './access-ids.service'; +import { HttpClientModule } from '@angular/common/http'; +import { HttpModule } from '@angular/http'; + +describe('ValidateAccessItemsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientModule, HttpModule], + providers: [AccessIdsService] + }); + }); + + it('should be created', inject([AccessIdsService], (service: AccessIdsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/web/src/app/shared/services/access-ids/access-ids.service.ts b/web/src/app/shared/services/access-ids/access-ids.service.ts new file mode 100644 index 000000000..1f5ffc3a6 --- /dev/null +++ b/web/src/app/shared/services/access-ids/access-ids.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { environment } from 'environments/environment'; + + +import { AccessIdDefinition } from 'app/models/access-id'; +import { Observable } from 'rxjs/Observable'; + +@Injectable() +export class AccessIdsService { + + private url = environment.taskanaRestUrl + '/v1/validate-access-id'; + + constructor( + private httpClient: HttpClient) { } + + getAccessItemsInformation(token): Observable> { + if (!token) { + return Observable.of([]); + } + return this.httpClient.get>(`${this.url}?search=${token}`); + }; + +} diff --git a/web/src/app/shared/shared.module.ts b/web/src/app/shared/shared.module.ts index 048b542c7..bcbff6135 100644 --- a/web/src/app/shared/shared.module.ts +++ b/web/src/app/shared/shared.module.ts @@ -6,12 +6,14 @@ import { AngularSvgIconModule } from 'angular-svg-icon'; import { AlertModule } from 'ngx-bootstrap'; import { RouterModule } from '@angular/router'; import { TreeModule } from 'angular-tree-component'; +import { TypeaheadModule } from 'ngx-bootstrap'; import { GeneralMessageModalComponent } from 'app/shared/general-message-modal/general-message-modal.component'; import { SpinnerComponent } from 'app/shared/spinner/spinner.component'; import { AlertComponent } from 'app/shared/alert/alert.component'; import { MasterAndDetailComponent } from 'app/shared/master-and-detail/master-and-detail.component'; import { TaskanaTreeComponent } from 'app/shared/tree/tree.component'; +import { TypeAheadComponent } from 'app/shared/type-ahead/type-ahead.component'; /** * Pipes @@ -22,17 +24,24 @@ import { SelectWorkBasketPipe } from './pipes/selectedWorkbasket/seleted-workbas import { SpreadNumberPipe } from './pipes/spreadNumber/spread-number'; import { OrderBy } from './pipes/orderBy/orderBy'; import { MapToIterable } from './pipes/mapToIterable/mapToIterable'; + +/** + * Services + */ import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service'; +import { AccessIdsService } from './services/access-ids/access-ids.service'; + const MODULES = [ CommonModule, FormsModule, AlertModule.forRoot(), + TypeaheadModule.forRoot(), AngularSvgIconModule, HttpClientModule, RouterModule, - TreeModule + TreeModule, ]; const DECLARATIONS = [ @@ -41,6 +50,7 @@ const DECLARATIONS = [ AlertComponent, MasterAndDetailComponent, TaskanaTreeComponent, + TypeAheadComponent, MapValuesPipe, RemoveNoneTypePipe, SelectWorkBasketPipe, @@ -58,7 +68,8 @@ const DECLARATIONS = [ provide: HTTP_INTERCEPTORS, useClass: HttpClientInterceptor, multi: true - } + }, + AccessIdsService ] }) export class SharedModule { diff --git a/web/src/app/shared/spinner/spinner.component.scss b/web/src/app/shared/spinner/spinner.component.scss index 7e15c1d82..472dff29d 100644 --- a/web/src/app/shared/spinner/spinner.component.scss +++ b/web/src/app/shared/spinner/spinner.component.scss @@ -133,4 +133,11 @@ .no-display{ display: none; -} \ No newline at end of file +} + +.type-ahead-spinner { + position: relative; + width: 20px; + height: 20px; + margin-right: 15px; +} diff --git a/web/src/app/shared/type-ahead/type-ahead.component.html b/web/src/app/shared/type-ahead/type-ahead.component.html new file mode 100644 index 000000000..e87e6f792 --- /dev/null +++ b/web/src/app/shared/type-ahead/type-ahead.component.html @@ -0,0 +1,38 @@ +
+ +
+
+ + +
+
+ + +
+
+
+
+ + + +
{{dataSource.selected?.name}}
+
+
+ + + +
+ +
+ + +
+
\ No newline at end of file diff --git a/web/src/app/shared/type-ahead/type-ahead.component.scss b/web/src/app/shared/type-ahead/type-ahead.component.scss new file mode 100644 index 000000000..0385320dd --- /dev/null +++ b/web/src/app/shared/type-ahead/type-ahead.component.scss @@ -0,0 +1,47 @@ +$blue: #2e9eca; +$grey: #ddd; +.wrapper-text { + height: 47px; + & label { + width: 100%; + margin-bottom: 0px; + } + & div { + width: 100%; + border-bottom: 1px solid $grey; + margin-top:6px; + } +} + +.input-text { + background: none; + box-shadow: none; + border: none; + padding: 0px; + border-radius: 0px; + //margin-top: 18px; + height: 22px; + border-bottom: 1px solid $grey; + &:focus{ + border-bottom: 1px solid $blue; + } + +} + +.field-label-wrapper{ + position: relative; + //left: 8px; + box-sizing: content-box; + overflow: hidden; + pointer-events: none; +} + + +.form-control:focus { + border-color: none; + box-shadow: none; +} +.loading { + position: absolute; + right: 0; +} diff --git a/web/src/app/shared/type-ahead/type-ahead.component.ts b/web/src/app/shared/type-ahead/type-ahead.component.ts new file mode 100644 index 000000000..3f4c557ba --- /dev/null +++ b/web/src/app/shared/type-ahead/type-ahead.component.ts @@ -0,0 +1,130 @@ +import { Component, OnInit, Input, EventEmitter, Output, ViewChild, ElementRef, forwardRef } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { TypeaheadMatch } from 'ngx-bootstrap/typeahead'; + +import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service'; +import { AccessItemsComponent } from 'app/administration/workbasket/details/access-items/access-items.component'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +const noop = () => { +}; + +@Component({ + selector: 'taskana-type-ahead', + templateUrl: './type-ahead.component.html', + styleUrls: ['./type-ahead.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TypeAheadComponent), + multi: true + } + + ] + +}) +export class TypeAheadComponent implements OnInit, ControlValueAccessor { + + dataSource: any; + typing = false; + + @Input() + placeHolderMessage; + + @ViewChild('inputTypeAhead') + private inputTypeAhead; + + typeaheadLoading = false; + typeaheadMinLength = 2; + typeaheadWaitMs = 500; + typeaheadOptionsInScrollableView = 6; + + // The internal data model + private innerValue: any = ''; + + // Placeholders for the callbacks which are later provided + // by the Control Value Accessor + private onTouchedCallback: () => void = noop; + private onChangeCallback: (_: any) => void = noop; + + // get accessor + get value(): any { + return this.innerValue; + }; + + // set accessor including call the onchange callback + set value(v: any) { + if (v !== this.innerValue) { + this.innerValue = v; + this.onChangeCallback(v); + } + } + + // From ControlValueAccessor interface + writeValue(value: any) { + if (value !== this.innerValue) { + this.innerValue = value; + this.initializeDataSource(); + } + } + + // From ControlValueAccessor interface + registerOnChange(fn: any) { + this.onChangeCallback = fn; + } + + // From ControlValueAccessor interface + registerOnTouched(fn: any) { + this.onTouchedCallback = fn; + } + + constructor(private accessIdsService: AccessIdsService) { + } + + ngOnInit() { + + } + + initializeDataSource() { + this.dataSource = Observable.create((observer: any) => { + observer.next(this.value); + }).mergeMap((token: string) => this.getUsersAsObservable(token)); + this.accessIdsService.getAccessItemsInformation(this.value).subscribe(items => { + if (items.length > 0) { + this.dataSource.selected = items.find(item => item.accessId === this.value); + } + }); + } + + + getUsersAsObservable(token: string): Observable { + return this.accessIdsService.getAccessItemsInformation(token); + } + + typeaheadOnSelect(event: TypeaheadMatch): void { + if (event && event.item) { + this.value = event.item.accessId; + this.dataSource.selected = event.item; + } + this.setTyping(false); + } + + setTyping(value) { + if (value) { + setTimeout(() => { + this.inputTypeAhead.nativeElement.focus(); + }, 1) + + } + this.typing = value; + } + + changeTypeaheadLoading(e: boolean): void { + this.typeaheadLoading = e; + } + + join(text: string, str: string) { + return text.toLocaleLowerCase().split(str).join(`${str}`); + } + +} diff --git a/web/src/assets/_styles.scss b/web/src/assets/_styles.scss index f5675116f..3b27c8fa9 100644 --- a/web/src/assets/_styles.scss +++ b/web/src/assets/_styles.scss @@ -3,4 +3,5 @@ @import '../../node_modules/angular-tree-component/dist/angular-tree-component.css'; @import 'site'; @import 'forms'; -@import 'tree'; \ No newline at end of file +@import 'tree'; +@import 'type-ahead'; \ No newline at end of file diff --git a/web/src/assets/_type-ahead.scss b/web/src/assets/_type-ahead.scss new file mode 100644 index 000000000..1a8404533 --- /dev/null +++ b/web/src/assets/_type-ahead.scss @@ -0,0 +1,12 @@ + +typeahead-container >ul.dropdown-menu{ + width: 215px; + & >li.active { + &>a { + background-color: $green; + } + &>a:hover { + background-color: $green; + } + } +} diff --git a/web/taskana-web.iml b/web/taskana-web.iml new file mode 100644 index 000000000..4fd5057cb --- /dev/null +++ b/web/taskana-web.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file