TSK-185 Add create, copy and delete workbasket item.

This commit is contained in:
Martin Rojas Miguel Angel 2018-03-22 13:37:13 +01:00 committed by Holger Hagen
parent 32449a4d52
commit 40c670991f
46 changed files with 965 additions and 372 deletions

View File

@ -7,7 +7,8 @@ import { MasterAndDetailComponent } from './shared/masterAndDetail/master-and-de
import { NoAccessComponent } from './workbasket/noAccess/no-access.component'; import { NoAccessComponent } from './workbasket/noAccess/no-access.component';
const appRoutes: Routes = [ const appRoutes: Routes = [
{ path: 'workbaskets', {
path: 'workbaskets',
component: MasterAndDetailComponent, component: MasterAndDetailComponent,
children: [ children: [
{ {
@ -27,7 +28,8 @@ const appRoutes: Routes = [
} }
] ]
}, },
{ path: 'clasifications', {
path: 'clasifications',
component: MasterAndDetailComponent, component: MasterAndDetailComponent,
children: [ children: [
{ {

View File

@ -1,37 +1,51 @@
<nav class="navbar navbar-fixed-top"> <nav class="navbar navbar-fixed-top">
<div class="navbar show no-border-radius navbar-inverse no-gutter"> <div class="navbar show no-border-radius navbar-inverse no-gutter">
<div class="col-xs-3 col-md-4"> <div class="col-xs-3 col-md-4">
<svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon> <svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon>
<ul class="nav logo"> <ul class="nav logo">
<svg-icon class="logo hidden-xs hidden-sm" src="./assets/icons/logo.svg"></svg-icon> <svg-icon class="logo hidden-xs hidden-sm" src="./assets/icons/logo.svg"></svg-icon>
<p class="navbar-brand no-margin hidden-xs hidden-sm" > <a [href]="adminUrl">{{title}}</a></p> <p class="navbar-brand no-margin hidden-xs hidden-sm">
</ul> <a [href]="adminUrl">{{title}}</a>
</div> </p>
<div class="col-xs-8 col-md-7 logo-container"> </ul>
<ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist"> </div>
<li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab" ><a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a></li> <div class="col-xs-8 col-md-7 logo-container">
<li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab" ><a routerLink="/clasifications" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a></li> <ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist">
</ul> <li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab">
</div> <a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a>
<div class="col-xs-1"> </li>
<button type="button" class="navbar-toggle collapsed show" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar"> <li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab">
<span class="sr-only">Toggle navigation</span> <a routerLink="/clasifications" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a>
<span class="icon-bar"></span> </li>
<span class="icon-bar"></span> </ul>
<span class="icon-bar"></span> </div>
</button> <div class="col-xs-1">
</div> <button type="button" class="navbar-toggle collapsed show" data-toggle="collapse" data-target="#navbar" aria-expanded="false"
</div> aria-controls="navbar">
<div id="navbar" class="collapse pull-right navbar-inverse" data-html="false"> <span class="sr-only">Toggle navigation</span>
<ul class="nav navbar-nav navbar-right content-margin"> <span class="icon-bar"></span>
<li><a [href]="monitorUrl">Monitor</a></li> <span class="icon-bar"></span>
<li><a [href]="workplaceUrl">Workplace</a></li> <span class="icon-bar"></span>
</ul> </button>
</div> </div>
</div>
<div id="navbar" class="collapse pull-right navbar-inverse" data-html="false">
<ul class="nav navbar-nav navbar-right content-margin">
<li>
<a [href]="monitorUrl">Monitor</a>
</li>
<li>
<a [href]="workplaceUrl">Workplace</a>
</li>
</ul>
</div>
</nav> </nav>
<div class="container-fluid container-main"> <div class="container-fluid container-main">
<div class ="row "> <div class="row ">
<router-outlet></router-outlet> <router-outlet></router-outlet>
<taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<taskana-spinner [isRunning]="requestInProgress" isModal="true"></taskana-spinner>
<taskana-alert></taskana-alert>
</div> </div>
</div> </div>

View File

@ -5,6 +5,12 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { Router, Routes } from '@angular/router'; import { Router, Routes } from '@angular/router';
import { GeneralMessageModalComponent } from './shared/general-message-modal/general-message-modal.component'
import { SpinnerComponent } from './shared/spinner/spinner.component'
import { ErrorModalService } from './services/error-modal.service';
import { RequestInProgressService } from './services/request-in-progress.service';
import { AlertComponent } from './shared/alert/alert.component';
import { AlertService } from './services/alert.service';
describe('AppComponent', () => { describe('AppComponent', () => {
@ -17,13 +23,14 @@ describe('AppComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ declarations: [
AppComponent AppComponent, GeneralMessageModalComponent, SpinnerComponent, AlertComponent
], ],
imports: [ imports: [
AngularSvgIconModule, AngularSvgIconModule,
RouterTestingModule.withRoutes(routes), RouterTestingModule.withRoutes(routes),
HttpClientModule HttpClientModule
] ],
providers: [ErrorModalService, RequestInProgressService, AlertService]
}).compileComponents(); }).compileComponents();
fixture = TestBed.createComponent(AppComponent); fixture = TestBed.createComponent(AppComponent);

View File

@ -1,6 +1,9 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { Router, ActivatedRoute, NavigationStart } from '@angular/router'; import { Router, NavigationStart } from '@angular/router';
import { ErrorModalService } from './services/error-modal.service';
import { ErrorModel } from './model/modal-error';
import { RequestInProgressService } from './services/request-in-progress.service';
@Component({ @Component({
selector: 'taskana-root', selector: 'taskana-root',
@ -15,7 +18,15 @@ export class AppComponent implements OnInit {
workplaceUrl: string = environment.taskanaWorkplaceUrl; workplaceUrl: string = environment.taskanaWorkplaceUrl;
workbasketsRoute = true; workbasketsRoute = true;
constructor(private route: ActivatedRoute, private router: Router) { modalErrorMessage = '';
modalTitle = '';
requestInProgress = false;
constructor(
private router: Router,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService) {
} }
ngOnInit() { ngOnInit() {
@ -26,5 +37,14 @@ export class AppComponent implements OnInit {
} }
} }
}); });
this.errorModalService.getError().subscribe((error: ErrorModel) => {
this.modalErrorMessage = error.message;
this.modalTitle = error.title;
})
this.requestInProgressService.getRequestInProgress().subscribe((value: boolean) => {
this.requestInProgress = value;
})
} }
} }

View File

@ -17,6 +17,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
*/ */
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { WorkbasketListComponent } from './workbasket/list/workbasket-list.component'; import { WorkbasketListComponent } from './workbasket/list/workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket/list/workbasket-list-toolbar/workbasket-list-toolbar.component'
import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component'; import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-details.component';
import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component'; import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component';
import { DistributionTargetsComponent } from './workbasket/details/distribution-targets/distribution-targets.component'; import { DistributionTargetsComponent } from './workbasket/details/distribution-targets/distribution-targets.component';
@ -42,6 +43,11 @@ import { HttpClientInterceptor } from './services/http-client-interceptor.servic
import { PermissionService } from './services/permission.service'; import { PermissionService } from './services/permission.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AlertService } from './services/alert.service'; import { AlertService } from './services/alert.service';
import { ErrorModalService } from './services/error-modal.service';
import { RequestInProgressService } from './services/request-in-progress.service';
import { SavingWorkbasketService } from './services/saving-workbaskets/saving-workbaskets.service';
/** /**
* Pipes * Pipes
@ -66,6 +72,7 @@ const MODULES = [
const DECLARATIONS = [ const DECLARATIONS = [
AppComponent, AppComponent,
WorkbasketListComponent, WorkbasketListComponent,
WorkbasketListToolbarComponent,
AccessItemsComponent, AccessItemsComponent,
WorkbasketDetailsComponent, WorkbasketDetailsComponent,
MasterAndDetailComponent, MasterAndDetailComponent,
@ -96,7 +103,10 @@ const DECLARATIONS = [
useClass: HttpClientInterceptor, useClass: HttpClientInterceptor,
multi: true multi: true
}, },
AlertService AlertService,
ErrorModalService,
RequestInProgressService,
SavingWorkbasketService
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -0,0 +1,5 @@
export enum ACTION {
CREATE = 'CREATE',
COPY = 'COPY'
}

View File

@ -1,6 +1,6 @@
export class Links { export class Links {
constructor( constructor(
public self: { 'href': string }, public self: { 'href': string } = undefined,
public distributionTargets: { 'href': string } = undefined, public distributionTargets: { 'href': string } = undefined,
public accessItems: { 'href': string } = undefined public accessItems: { 'href': string } = undefined
) { } ) { }

View File

@ -0,0 +1,6 @@
export class ErrorModel {
constructor(
public title: string = undefined,
public message: string = undefined
) { }
}

View File

@ -0,0 +1,9 @@
export enum ICONTYPES {
NONE = '',
PERSONAL = 'PERSONAL',
GROUP = 'GROUP',
CLEARANCE = 'CLEARANCE',
TOPIC = 'TOPIC'
}

View File

@ -1,19 +1,20 @@
import { Links } from './links'; import { Links } from './links';
import { ICONTYPES } from './type';
export class WorkbasketSummary { export class WorkbasketSummary {
constructor( constructor(
public workbasketId: string, public workbasketId: string = undefined,
public key: string, public key: string = undefined,
public name: string, public name: string = undefined,
public description: string, public description: string = undefined,
public owner: string, public owner: string = undefined,
public modified: string, public modified: string = undefined,
public domain: string, public domain: string = undefined,
public type: string, public type: string = ICONTYPES.PERSONAL,
public orgLevel1: string, public orgLevel1: string = undefined,
public orgLevel2: string, public orgLevel2: string = undefined,
public orgLevel3: string, public orgLevel3: string = undefined,
public orgLevel4: string, public orgLevel4: string = undefined,
public _links: Links = undefined) { public _links: Links = undefined) {
} }
} }

View File

@ -1,4 +1,5 @@
import { Links } from './links'; import { Links } from './links';
import { ICONTYPES } from './type';
export class Workbasket { export class Workbasket {
public static equals(org: Workbasket, comp: Workbasket): boolean { public static equals(org: Workbasket, comp: Workbasket): boolean {
@ -28,7 +29,7 @@ export class Workbasket {
public created: string = undefined, public created: string = undefined,
public key: string = undefined, public key: string = undefined,
public domain: string = undefined, public domain: string = undefined,
public type: string = undefined, public type: ICONTYPES = ICONTYPES.PERSONAL,
public modified: string = undefined, public modified: string = undefined,
public name: string = undefined, public name: string = undefined,
public description: string = undefined, public description: string = undefined,
@ -41,6 +42,6 @@ export class Workbasket {
public orgLevel2: string = undefined, public orgLevel2: string = undefined,
public orgLevel3: string = undefined, public orgLevel3: string = undefined,
public orgLevel4: string = undefined, public orgLevel4: string = undefined,
public _links: Links = undefined) { public _links: Links = new Links()) {
} }
} }

View File

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { ErrorModel } from '../model/modal-error';
@Injectable()
export class ErrorModalService {
public errorTriggered = new Subject<ErrorModel>();
constructor() { }
triggerError(error: ErrorModel) {
this.errorTriggered.next(error);
}
getError(): Observable<ErrorModel> {
return this.errorTriggered.asObservable();
}
}

View File

@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class RequestInProgressService {
public requestInProgressTriggered = new Subject<boolean>();
constructor() { }
setRequestInProgress(value: boolean) {
this.requestInProgressTriggered.next(value);
}
getRequestInProgress(): Observable<boolean> {
return this.requestInProgressTriggered.asObservable();
}
}

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { SavingWorkbasketService } from './saving-workbaskets.service';
describe('SavingWorkbasketsService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [SavingWorkbasketService]
});
});
it('should be created', inject([SavingWorkbasketService], (service: SavingWorkbasketService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,33 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
export class SavingInformation {
constructor(public url: string,
public workbasketId: string) {
}
}
@Injectable()
export class SavingWorkbasketService {
public distributionTargetsSavingInformation = new Subject<SavingInformation>();
public accessItemsSavingInformation = new Subject<SavingInformation>();
constructor() { }
triggerDistributionTargetSaving(distributionTargetInformation: SavingInformation) {
this.distributionTargetsSavingInformation.next(distributionTargetInformation);
}
triggerAccessItemsSaving(accessItemsInformation: SavingInformation) {
this.accessItemsSavingInformation.next(accessItemsInformation);
}
triggeredDistributionTargetsSaving(): Observable<SavingInformation> {
return this.distributionTargetsSavingInformation.asObservable();
}
triggeredAccessItemsSaving(): Observable<SavingInformation> {
return this.accessItemsSavingInformation.asObservable();
}
}

View File

@ -75,7 +75,7 @@ export class WorkbasketService {
// POST // POST
createWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> { createWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> {
return this.httpClient return this.httpClient
.post<Workbasket>(url, this.httpOptions); .post<Workbasket>(url, workbasket, this.httpOptions);
} }
// PUT // PUT
updateWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> { updateWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> {

View File

@ -4,4 +4,5 @@
bottom: 0; bottom: 0;
width: 100%; width: 100%;
margin-bottom: 0px; margin-bottom: 0px;
text-align: center;
} }

View File

@ -11,6 +11,8 @@
.list-group-search { .list-group-search {
padding: 10px 15px; padding: 10px 15px;
margin-top: 12px;
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
} }
.list-group-search {
border-top: none;
}

View File

@ -1,5 +1,6 @@
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { IconTypeComponent, ICONTYPES } from '../type-icon/icon-type.component' import { IconTypeComponent } from '../type-icon/icon-type.component'
import { ICONTYPES } from '../../model/type';
export class FilterModel { export class FilterModel {
type: string; type: string;

View File

@ -1,14 +1,14 @@
<div class="no-gutter"> <div class="no-gutter">
<div class="{{showDetail? 'col-md-4 hidden-xs hidden-sm':'col-xs-12 col-md-4'}} vertical-right-divider" > <div class="{{showDetail? 'col-md-4 hidden-xs hidden-sm':'col-xs-12 col-md-4'}} vertical-right-divider">
<router-outlet name="master"></router-outlet> <router-outlet name="master"></router-outlet>
</div> </div>
<div class="{{showDetail? 'col-xs-12 col-md-8': 'hidden'}}" > <div class="{{showDetail? 'col-xs-12 col-md-8': 'hidden'}}">
<router-outlet name="detail"> <router-outlet name="detail">
</router-outlet> </router-outlet>
</div> </div>
<div class="{{showDetail? 'hidden': 'hidden-xs hidden-sm col-md-8 container-no-detail'}}"> <div class="{{showDetail? 'hidden': 'hidden-xs hidden-sm col-md-8 container-no-detail'}}">
<div class = "center-block no-detail" > <div class="center-block no-detail">
<h3 class = "grey">Select a worbasket</h3> <h3 class="grey">Select a worbasket</h3>
<svg-icon class="img-responsive no-detail-icon" src="./assets/icons/wb-empty.svg"></svg-icon> <svg-icon class="img-responsive no-detail-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
</div> </div>
</div> </div>

View File

@ -11,6 +11,7 @@
.container-no-detail{ .container-no-detail{
top:30vh; top:30vh;
height: 65vh;
} }
.center-block.no-detail { .center-block.no-detail {

View File

@ -8,17 +8,18 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { MasterAndDetailService } from '../../services/master-and-detail.service' import { MasterAndDetailService } from '../../services/master-and-detail.service'
@Component({ @Component({
selector: 'taskana-dummy-master', selector: 'taskana-dummy-master',
template: 'dummymaster' template: 'dummymaster'
}) })
export class DummyMasterComponent { export class DummyMasterComponent {
} }
@Component({ @Component({
selector: 'taskana-dummy-detail', selector: 'taskana-dummy-detail',
template: 'dummydetail' template: 'dummydetail'
}) })
export class DummyDetailComponent { export class DummyDetailComponent {
@ -26,83 +27,86 @@ export class DummyDetailComponent {
describe('MasterAndDetailComponent ', () => { describe('MasterAndDetailComponent ', () => {
let component, fixture, debugElement, location, router; let component, fixture, debugElement, location, router;
const routes: Routes = [ const routes: Routes = [
{ {
path: 'workbaskets', path: 'workbaskets',
component: MasterAndDetailComponent, component: MasterAndDetailComponent,
children: [ children: [
{ {
path: '', path: '',
component: DummyMasterComponent, component: DummyMasterComponent,
outlet: 'master' outlet: 'master'
}, },
{ {
path: ':id', path: ':id',
component: DummyDetailComponent, component: DummyDetailComponent,
outlet: 'detail' outlet: 'detail'
} }
] ]
} }
]; ];
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [MasterAndDetailComponent, DummyMasterComponent, DummyDetailComponent], declarations: [
imports: [ MasterAndDetailComponent,
RouterTestingModule.withRoutes(routes), DummyMasterComponent,
AngularSvgIconModule, DummyDetailComponent],
HttpClientModule imports: [
], RouterTestingModule.withRoutes(routes),
providers: [MasterAndDetailService] AngularSvgIconModule,
}) HttpClientModule
.compileComponents(); ],
providers: [MasterAndDetailService]
})
.compileComponents();
fixture = TestBed.createComponent(MasterAndDetailComponent); fixture = TestBed.createComponent(MasterAndDetailComponent);
component = fixture.debugElement.componentInstance; component = fixture.debugElement.componentInstance;
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
location = TestBed.get(Location); location = TestBed.get(Location);
router = TestBed.get(Router); router = TestBed.get(Router);
router.initialNavigation(); router.initialNavigation();
})); }));
afterEach(async(() => { afterEach(async(() => {
document.body.removeChild(debugElement); document.body.removeChild(debugElement);
})); }));
it('should be created', () => { it('should be created', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should call Router.navigateByUrl("/wokbaskets") and showDetail property should be false', async(() => { it('should call Router.navigateByUrl("/wokbaskets") and showDetail property should be false', async(() => {
expect(component.showDetail).toBe(false); expect(component.showDetail).toBe(false);
fixture.detectChanges(); fixture.detectChanges();
router.navigateByUrl('/workbaskets'); router.navigateByUrl('/workbaskets');
expect(component.showDetail).toBe(false); expect(component.showDetail).toBe(false);
})); }));
it('should call Router.navigateByUrl("/wokbaskets/(detail:Id)") and showDetail property should be true', async(() => { it('should call Router.navigateByUrl("/wokbaskets/(detail:Id)") and showDetail property should be true', async(() => {
expect(component.showDetail).toBe(false); expect(component.showDetail).toBe(false);
fixture.detectChanges(); fixture.detectChanges();
router.navigateByUrl('/workbaskets/(detail:2)'); router.navigateByUrl('/workbaskets/(detail:2)');
expect(component.showDetail).toBe(true); expect(component.showDetail).toBe(true);
})); }));
it('should navigate to parent state when backIsClicked', async(() => { it('should navigate to parent state when backIsClicked', async(() => {
const spy = spyOn(router, 'navigateByUrl'); const spy = spyOn(router, 'navigateByUrl');
router.navigateByUrl('/workbaskets/(detail:2)'); router.navigateByUrl('/workbaskets/(detail:2)');
fixture.detectChanges(); fixture.detectChanges();
expect(spy.calls.first().args[0]).toBe('/workbaskets/(detail:2)'); expect(spy.calls.first().args[0]).toBe('/workbaskets/(detail:2)');
component.backClicked(); component.backClicked();
expect(spy.calls.mostRecent().args.length).toBe(2); expect(spy.calls.mostRecent().args.length).toBe(2);
})); }));
}); });

View File

@ -1,14 +1,5 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { ICONTYPES } from '../../model/type';
export enum ICONTYPES {
NONE = '',
PERSONAL = 'PERSONAL',
GROUP = 'GROUP',
CLEARANCE = 'CLEARANCE',
TOPIC = 'TOPIC'
}
@Component({ @Component({
selector: 'taskana-icon-type', selector: 'taskana-icon-type',

View File

@ -1,12 +1,13 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div *ngIf="workbasket && accessItems" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<button type="button" (click)="onSave()" [disabled]="!AccessItemsForm.form.valid" class="btn btn-default btn-primary">Save</button> <button type="button" (click)="onSave()" [disabled]="!AccessItemsForm.form.valid || action === 'COPY'" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="clear()" class="btn btn-default">Undo changes</button> <button type="button" (click)="clear()" class="btn btn-default">Undo changes</button>
</div> </div>
<h4 class="panel-header">{{workbasket.name}}</h4> <h4 class="panel-header">{{workbasket.name}}
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form #AccessItemsForm="ngForm"> <form #AccessItemsForm="ngForm">

View File

@ -13,6 +13,9 @@ import { Observable } from 'rxjs/Observable';
import { AccessItemsComponent } from './access-items.component'; import { AccessItemsComponent } from './access-items.component';
import { WorkbasketAccessItems } from '../../../model/workbasket-access-items'; import { WorkbasketAccessItems } from '../../../model/workbasket-access-items';
import { WorkbasketAccessItemsResource } from '../../../model/workbasket-access-items-resource'; import { WorkbasketAccessItemsResource } from '../../../model/workbasket-access-items-resource';
import { ICONTYPES } from '../../../model/type';
import { ErrorModalService } from '../../../services/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from '../../../services/saving-workbaskets/saving-workbaskets.service';
describe('AccessItemsComponent', () => { describe('AccessItemsComponent', () => {
let component: AccessItemsComponent; let component: AccessItemsComponent;
@ -23,7 +26,7 @@ describe('AccessItemsComponent', () => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent], declarations: [SpinnerComponent, AccessItemsComponent, GeneralMessageModalComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule], imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, ReactiveFormsModule],
providers: [WorkbasketService, AlertService] providers: [WorkbasketService, AlertService, ErrorModalService, SavingWorkbasketService]
}) })
.compileComponents(); .compileComponents();
@ -32,7 +35,7 @@ describe('AccessItemsComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(AccessItemsComponent); fixture = TestBed.createComponent(AccessItemsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', component.workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
new Links(undefined, undefined, { 'href': 'someurl' })); new Links(undefined, undefined, { 'href': 'someurl' }));
workbasketService = TestBed.get(WorkbasketService); workbasketService = TestBed.get(WorkbasketService);
alertService = TestBed.get(AlertService); alertService = TestBed.get(AlertService);

View File

@ -7,6 +7,10 @@ import { WorkbasketAccessItems } from '../../../model/workbasket-access-items';
import { WorkbasketService } from '../../../services/workbasket.service'; import { WorkbasketService } from '../../../services/workbasket.service';
import { AlertService, AlertModel, AlertType } from '../../../services/alert.service'; import { AlertService, AlertModel, AlertType } from '../../../services/alert.service';
import { WorkbasketAccessItemsResource } from '../../../model/workbasket-access-items-resource'; import { WorkbasketAccessItemsResource } from '../../../model/workbasket-access-items-resource';
import { ErrorModalService } from '../../../services/error-modal.service';
import { ErrorModel } from '../../../model/modal-error';
import { SavingWorkbasketService, SavingInformation } from '../../../services/saving-workbaskets/saving-workbaskets.service';
import { ACTION } from '../../../model/action';
declare var $: any; declare var $: any;
@ -19,6 +23,9 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
@Input() @Input()
workbasket: Workbasket; workbasket: Workbasket;
@Input()
action: string;
badgeMessage = '';
accessItemsResource: WorkbasketAccessItemsResource; accessItemsResource: WorkbasketAccessItemsResource;
accessItems: Array<WorkbasketAccessItems>; accessItems: Array<WorkbasketAccessItems>;
@ -29,11 +36,19 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
modalTitle: string; modalTitle: string;
modalErrorMessage: string; modalErrorMessage: string;
accessItemsubscription: Subscription; accessItemsubscription: Subscription;
savingAccessItemsSubscription: Subscription;
constructor(private workbasketService: WorkbasketService, private alertService: AlertService) { } constructor(
private workbasketService: WorkbasketService,
private alertService: AlertService,
private errorModalService: ErrorModalService,
private savingWorkbaskets: SavingWorkbasketService) { }
ngOnInit() { ngOnInit() {
if (!this.workbasket._links.accessItems) {
return;
}
this.accessItemsubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href) this.accessItemsubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href)
.subscribe((accessItemsResource: WorkbasketAccessItemsResource) => { .subscribe((accessItemsResource: WorkbasketAccessItemsResource) => {
this.accessItemsResource = accessItemsResource; this.accessItemsResource = accessItemsResource;
@ -41,7 +56,17 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
this.accessItemsClone = this.cloneAccessItems(this.accessItems); this.accessItemsClone = this.cloneAccessItems(this.accessItems);
this.accessItemsResetClone = this.cloneAccessItems(this.accessItems); this.accessItemsResetClone = this.cloneAccessItems(this.accessItems);
}) })
this.savingAccessItemsSubscription = this.savingWorkbaskets.triggeredAccessItemsSaving()
.subscribe((savingInformation: SavingInformation) => {
if (this.action === ACTION.COPY) {
this.accessItemsResource._links.self.href = savingInformation.url;
this.setWorkbasketIdForCopy(savingInformation.workbasketId);
this.onSave();
}
})
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
} }
addAccessItem() { addAccessItem() {
@ -70,12 +95,11 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
AlertType.SUCCESS, `Workbasket ${this.workbasket.name} Access items were saved successfully`)); AlertType.SUCCESS, `Workbasket ${this.workbasket.name} Access items were saved successfully`));
this.requestInProgress = false; this.requestInProgress = false;
return true; return true;
}, }, error => {
error => { this.errorModalService.triggerError(new ErrorModel(`There was error while saving your workbasket's access items`, error))
this.modalErrorMessage = error.message; this.requestInProgress = false;
this.requestInProgress = false; return false;
return false; })
})
return false; return false;
} }
@ -86,8 +110,15 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
}); });
return accessItemClone; return accessItemClone;
} }
private setWorkbasketIdForCopy(workbasketId: string) {
this.accessItems.forEach(element => {
element.accessItemId = undefined;
element.workbasketId = workbasketId;
});
}
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.accessItemsubscription) { this.accessItemsubscription.unsubscribe(); } if (this.accessItemsubscription) { this.accessItemsubscription.unsubscribe(); }
if (this.savingAccessItemsSubscription) { this.savingAccessItemsSubscription.unsubscribe(); }
} }
} }

View File

@ -1,13 +1,14 @@
<taskana-spinner [isRunning]="requestInProgress" isModal="true" (requestTimeoutExceeded)="requestTimeoutExceeded($event)" <taskana-spinner [isRunning]="requestInProgress" isModal="true" (requestTimeoutExceeded)="requestTimeoutExceeded($event)"
class="centered-horizontally floating"></taskana-spinner> class="centered-horizontally floating"></taskana-spinner>
<taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<button type="button" (click)="onSave()" class="btn btn-default btn-primary">Save</button> <button type="button" (click)="onSave()" [disabled]="action === 'COPY'" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button> <button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button>
</div> </div>
<h4 class="panel-header">{{workbasket.name}}</h4> <h4 class="panel-header">{{workbasket.name}}
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<taskana-dual-list id="dual-list-Left" [(distributionTargets)]="distributionTargetsLeft" [distributionTargetsSelected]="distributionTargetsSelected" <taskana-dual-list id="dual-list-Left" [(distributionTargets)]="distributionTargetsLeft" [distributionTargetsSelected]="distributionTargetsSelected"

View File

@ -20,6 +20,9 @@ import { Workbasket } from '../../../model/workbasket';
import { WorkbasketDistributionTargetsResource } from '../../../model/workbasket-distribution-targets-resource'; import { WorkbasketDistributionTargetsResource } from '../../../model/workbasket-distribution-targets-resource';
import { FilterModel } from '../../../shared/filter/filter.component'; import { FilterModel } from '../../../shared/filter/filter.component';
import { DualListComponent } from './dual-list/dual-list.component'; import { DualListComponent } from './dual-list/dual-list.component';
import { ICONTYPES } from '../../../model/type';
import { ErrorModalService } from '../../../services/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from '../../../services/saving-workbaskets/saving-workbaskets.service';
const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({ const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({
'workbaskets': new Array<WorkbasketSummary>( 'workbaskets': new Array<WorkbasketSummary>(
@ -43,7 +46,7 @@ describe('DistributionTargetsComponent', () => {
let component: DistributionTargetsComponent; let component: DistributionTargetsComponent;
let fixture: ComponentFixture<DistributionTargetsComponent>; let fixture: ComponentFixture<DistributionTargetsComponent>;
let workbasketService; let workbasketService;
const workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' })); new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
beforeEach(async(() => { beforeEach(async(() => {
@ -51,7 +54,7 @@ describe('DistributionTargetsComponent', () => {
imports: [AngularSvgIconModule, HttpClientModule, HttpModule, JsonpModule], imports: [AngularSvgIconModule, HttpClientModule, HttpModule, JsonpModule],
declarations: [DistributionTargetsComponent, SpinnerComponent, GeneralMessageModalComponent, declarations: [DistributionTargetsComponent, SpinnerComponent, GeneralMessageModalComponent,
FilterComponent, SelectWorkBasketPipe, IconTypeComponent, DualListComponent], FilterComponent, SelectWorkBasketPipe, IconTypeComponent, DualListComponent],
providers: [WorkbasketService, AlertService] providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService]
}) })
.compileComponents(); .compileComponents();
})); }));

View File

@ -12,6 +12,10 @@ import { Subscription } from 'rxjs/Subscription';
import { element } from 'protractor'; import { element } from 'protractor';
import { WorkbasketSummaryResource } from '../../../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../../../model/workbasket-summary-resource';
import { WorkbasketDistributionTargetsResource } from '../../../model/workbasket-distribution-targets-resource'; import { WorkbasketDistributionTargetsResource } from '../../../model/workbasket-distribution-targets-resource';
import { SavingWorkbasketService, SavingInformation } from '../../../services/saving-workbaskets/saving-workbaskets.service';
import { ErrorModalService } from '../../../services/error-modal.service';
import { ErrorModel } from '../../../model/modal-error';
import { ACTION } from '../../../model/action';
export enum Side { export enum Side {
LEFT, LEFT,
@ -26,10 +30,14 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
@Input() @Input()
workbasket: Workbasket; workbasket: Workbasket;
@Input()
action: string;
badgeMessage = '';
distributionTargetsSubscription: Subscription; distributionTargetsSubscription: Subscription;
workbasketSubscription: Subscription; workbasketSubscription: Subscription;
workbasketFilterSubscription: Subscription; workbasketFilterSubscription: Subscription;
savingDistributionTargetsSubscription: Subscription;
distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource; distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource;
distributionTargetsLeft: Array<WorkbasketSummary>; distributionTargetsLeft: Array<WorkbasketSummary>;
@ -45,10 +53,17 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
modalErrorMessage: string; modalErrorMessage: string;
side = Side; side = Side;
constructor(private workbasketService: WorkbasketService, private alertService: AlertService) { } constructor(
private workbasketService: WorkbasketService,
private alertService: AlertService,
private savingWorkbaskets: SavingWorkbasketService,
private errorModalService: ErrorModalService) { }
ngOnInit() { ngOnInit() {
this.onRequest(undefined); this.onRequest(undefined);
if (!this.workbasket._links.distributionTargets) {
return;
}
this.distributionTargetsSubscription = this.workbasketService.getWorkBasketsDistributionTargets( this.distributionTargetsSubscription = this.workbasketService.getWorkBasketsDistributionTargets(
this.workbasket._links.distributionTargets.href).subscribe( this.workbasket._links.distributionTargets.href).subscribe(
(distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource) => { (distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource) => {
@ -64,6 +79,18 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
this.onRequest(undefined, true); this.onRequest(undefined, true);
}); });
}); });
this.savingDistributionTargetsSubscription = this.savingWorkbaskets.triggeredDistributionTargetsSaving()
.subscribe((savingInformation: SavingInformation) => {
if (this.action === ACTION.COPY) {
this.distributionTargetsSelectedResource._links.self.href = savingInformation.url;
this.onSave();
}
});
if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
} }
moveDistributionTargets(side: number) { moveDistributionTargets(side: number) {
@ -92,7 +119,7 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
return true; return true;
}, },
error => { error => {
this.modalErrorMessage = error.message; this.errorModalService.triggerError(new ErrorModel(`There was error while saving your workbasket's distribution targets`, error))
this.requestInProgress = false; this.requestInProgress = false;
return false; return false;
} }
@ -161,6 +188,8 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
if (this.distributionTargetsSubscription) { this.distributionTargetsSubscription.unsubscribe(); } if (this.distributionTargetsSubscription) { this.distributionTargetsSubscription.unsubscribe(); }
if (this.workbasketSubscription) { this.workbasketSubscription.unsubscribe(); } if (this.workbasketSubscription) { this.workbasketSubscription.unsubscribe(); }
if (this.workbasketFilterSubscription) { this.workbasketFilterSubscription.unsubscribe(); } if (this.workbasketFilterSubscription) { this.workbasketFilterSubscription.unsubscribe(); }
if (this.savingDistributionTargetsSubscription) { this.savingDistributionTargetsSubscription.unsubscribe(); }
} }
} }

View File

@ -1,12 +1,13 @@
<taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" [isModal]="modalSpinner" class="centered-horizontally floating"></taskana-spinner>
<taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<div *ngIf="workbasket" id="wb-information" class="panel panel-default"> <div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<div class="btn-group pull-right"> <div class="btn-group pull-right">
<button type="button" [disabled]="!WorkbasketForm.form.valid" (click)="onSave()" class="btn btn-default btn-primary">Save</button> <button type="button" [disabled]="!WorkbasketForm.form.valid" (click)="onSave()" class="btn btn-default btn-primary">Save</button>
<button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button> <button type="button" (click)="onClear()" class="btn btn-default">Undo changes</button>
</div> </div>
<h4 class="panel-header">{{workbasket.name}}</h4> <h4 class="panel-header">{{workbasket.name}}&nbsp;
<span *ngIf="!workbasket.workbasketId" class="badge warning"> {{badgeMessage}}</span>
</h4>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<form #WorkbasketForm="ngForm"> <form #WorkbasketForm="ngForm">
@ -24,7 +25,7 @@
<input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name" [(ngModel)]="workbasket.name" <input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name" [(ngModel)]="workbasket.name"
name="workbasket.name"> name="workbasket.name">
<div [hidden]="name.valid" class="required-text"> <div [hidden]="name.valid" class="required-text">
* Name is required * Name is required
</div> </div>
</div> </div>
<div class="form-group required"> <div class="form-group required">
@ -32,7 +33,15 @@
<input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner" [(ngModel)]="workbasket.owner" <input type="text" required #owner="ngModel" class="form-control" id="wb-owner" placeholder="Owner" [(ngModel)]="workbasket.owner"
name="workbasket.owner"> name="workbasket.owner">
<div [hidden]="owner.valid" class="required-text"> <div [hidden]="owner.valid" class="required-text">
* Owner is required * Owner is required
</div>
</div>
<div class="form-group required">
<label for="wb-domain" class="control-label">Domain</label>
<input type="text" required #domain="ngModel" class="form-control" id="wb-domain" placeholder="Domain" [(ngModel)]="workbasket.domain"
name="workbasket.domain">
<div [hidden]="domain.valid" class="required-text">
* Domain is required
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -43,7 +52,7 @@
{{allTypes.get(workbasket.type)}} {{allTypes.get(workbasket.type)}}
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu">
<li> <li>
<a *ngFor="let type of allTypes | mapValues | removeEmptyType" (click)="selectType(type.key)"> <a *ngFor="let type of allTypes | mapValues | removeEmptyType" (click)="selectType(type.key)">
<taskana-icon-type class="vertical-align" [type]='type.key'></taskana-icon-type> <taskana-icon-type class="vertical-align" [type]='type.key'></taskana-icon-type>
@ -53,11 +62,6 @@
</ul> </ul>
</div> </div>
</div> </div>
<div class="form-group">
<label for="wb-domain" class="control-label">Domain</label>
<input type="text" class="form-control" id="wb-domain" placeholder="Domain" [(ngModel)]="workbasket.domain" name="workbasket.domain"
disabled="">
</div>
<div class="form-group"> <div class="form-group">
<label for="wb-description" class="control-label">Description</label> <label for="wb-description" class="control-label">Description</label>
<textarea class="form-control" rows="5" id="wb-description" placeholder="Description" [(ngModel)]="workbasket.description" <textarea class="form-control" rows="5" id="wb-description" placeholder="Description" [(ngModel)]="workbasket.description"

View File

@ -6,7 +6,7 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { HttpModule, JsonpModule } from '@angular/http'; import { HttpModule, JsonpModule } from '@angular/http';
import { Workbasket } from 'app/model/workbasket'; import { Workbasket } from 'app/model/workbasket';
import { ICONTYPES, IconTypeComponent } from '../../../shared/type-icon/icon-type.component'; import { IconTypeComponent } from '../../../shared/type-icon/icon-type.component';
import { SpinnerComponent } from '../../../shared/spinner/spinner.component'; import { SpinnerComponent } from '../../../shared/spinner/spinner.component';
import { GeneralMessageModalComponent } from '../../../shared/general-message-modal/general-message-modal.component'; import { GeneralMessageModalComponent } from '../../../shared/general-message-modal/general-message-modal.component';
import { MapValuesPipe } from '../../../pipes/map-values.pipe'; import { MapValuesPipe } from '../../../pipes/map-values.pipe';
@ -15,18 +15,22 @@ import { AlertService } from '../../../services/alert.service';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { Links } from '../../../model/links'; import { Links } from '../../../model/links';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { ICONTYPES } from '../../../model/type';
import { ErrorModalService } from '../../../services/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from '../../../services/saving-workbaskets/saving-workbaskets.service';
import { ACTION } from '../../../model/action';
describe('InformationComponent', () => { describe('InformationComponent', () => {
let component: WorkbasketInformationComponent; let component: WorkbasketInformationComponent;
let fixture: ComponentFixture<WorkbasketInformationComponent>; let fixture: ComponentFixture<WorkbasketInformationComponent>;
let debugElement, workbasketService; let debugElement, workbasketService, alertService, savingWorkbasketService;
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe, declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent], RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, RouterTestingModule], imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, RouterTestingModule],
providers: [WorkbasketService, AlertService] providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService]
}) })
.compileComponents(); .compileComponents();
@ -34,6 +38,9 @@ describe('InformationComponent', () => {
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement; debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService); workbasketService = TestBed.get(WorkbasketService);
alertService = TestBed.get(AlertService);
savingWorkbasketService = TestBed.get(SavingWorkbasketService);
spyOn(alertService, 'triggerAlert');
})); }));
afterEach(() => { afterEach(() => {
@ -45,12 +52,12 @@ describe('InformationComponent', () => {
}); });
it('should create a panel with heading and form with all fields', async(() => { it('should create a panel with heading and form with all fields', async(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', 'type', component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'modified', 'name', 'description', 'owner', 'custom1', 'custom2', 'custom3', 'custom4',
'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', null); 'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', null);
fixture.detectChanges(); fixture.detectChanges();
expect(debugElement.querySelector('#wb-information')).toBeDefined(); expect(debugElement.querySelector('#wb-information')).toBeDefined();
expect(debugElement.querySelector('#wb-information > .panel-heading > h4').textContent).toBe('name'); expect(debugElement.querySelector('#wb-information > .panel-heading > h4').textContent.trim()).toBe('name');
expect(debugElement.querySelectorAll('#wb-information > .panel-body > form').length).toBe(1); expect(debugElement.querySelectorAll('#wb-information > .panel-body > form').length).toBe(1);
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(debugElement.querySelector('#wb-information > .panel-body > form > div > div > input ').value).toBe('keyModified'); expect(debugElement.querySelector('#wb-information > .panel-body > form > div > div > input ').value).toBe('keyModified');
@ -60,8 +67,6 @@ describe('InformationComponent', () => {
it('selectType should set workbasket.type to personal with 0 and group in other case', () => { it('selectType should set workbasket.type to personal with 0 and group in other case', () => {
component.workbasket = new Workbasket('id1'); component.workbasket = new Workbasket('id1');
expect(component.workbasket.type).toEqual(undefined);
component.selectType(ICONTYPES.PERSONAL);
expect(component.workbasket.type).toEqual('PERSONAL'); expect(component.workbasket.type).toEqual('PERSONAL');
component.selectType(ICONTYPES.GROUP); component.selectType(ICONTYPES.GROUP);
expect(component.workbasket.type).toEqual('GROUP'); expect(component.workbasket.type).toEqual('GROUP');
@ -70,7 +75,7 @@ describe('InformationComponent', () => {
it('should create a copy of workbasket when workbasket is selected', () => { it('should create a copy of workbasket when workbasket is selected', () => {
expect(component.workbasketClone).toBeUndefined(); expect(component.workbasketClone).toBeUndefined();
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', 'type', 'modified', 'name', 'description', component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', null); 'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', null);
component.ngOnInit(); component.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
@ -78,20 +83,19 @@ describe('InformationComponent', () => {
}); });
it('should reset requestInProgress after saving request is done', fakeAsync(() => { it('should reset requestInProgress after saving request is done', fakeAsync(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', 'type', 'modified', 'name', 'description', component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' })); 'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket));
component.onSave(); component.onSave();
expect(component.modalSpinner).toBeTruthy(); expect(component.modalSpinner).toBeTruthy();
expect(component.modalErrorMessage).toBeUndefined();
expect(component.requestInProgress).toBeFalsy(); expect(component.requestInProgress).toBeFalsy();
})); }));
it('should trigger triggerWorkBasketSaved method after saving request is done', () => { it('should trigger triggerWorkBasketSaved method after saving request is done', () => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', 'type', 'modified', 'name', 'description', component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2', 'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' })); 'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket)); spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
@ -100,4 +104,43 @@ describe('InformationComponent', () => {
expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled(); expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled();
}); });
it('should post a new workbasket when no workbasketId is defined and update workbasket', () => {
const workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
component.workbasket = workbasket
spyOn(workbasketService, 'createWorkbasket').and.returnValue(Observable.of(
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }))));
component.onSave();
expect(alertService.triggerAlert).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId');
});
it('should post a new workbasket, new distribution targets and new access ' +
'items when no workbasketId is defined and action is copy', () => {
const workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2',
'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
component.workbasket = workbasket
component.action = ACTION.COPY;
spyOn(workbasketService, 'createWorkbasket').and.returnValue(Observable.of(
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }, { 'href': 'someUrl' }, { 'href': 'someUrl' }))));
spyOn(savingWorkbasketService, 'triggerDistributionTargetSaving');
spyOn(savingWorkbasketService, 'triggerAccessItemsSaving');
component.onSave();
expect(alertService.triggerAlert).toHaveBeenCalled();
expect(component.workbasket.workbasketId).toBe('someNewId');
expect(savingWorkbasketService.triggerDistributionTargetSaving).toHaveBeenCalled();
expect(savingWorkbasketService.triggerAccessItemsSaving).toHaveBeenCalled();
});
}); });

View File

@ -1,11 +1,20 @@
import { Component, OnInit, Input, Output, OnDestroy } from '@angular/core'; import { Component, OnInit, Input, Output, OnDestroy } from '@angular/core';
import { Workbasket } from '../../../model/workbasket'; import { Workbasket } from '../../../model/workbasket';
import { WorkbasketService } from '../../../services/workbasket.service'; import { WorkbasketService } from '../../../services/workbasket.service';
import { IconTypeComponent, ICONTYPES } from '../../../shared/type-icon/icon-type.component'; import { IconTypeComponent } from '../../../shared/type-icon/icon-type.component';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { AlertService, AlertModel, AlertType } from '../../../services/alert.service'; import { AlertService, AlertModel, AlertType } from '../../../services/alert.service';
import { ActivatedRoute, Params, Router, NavigationStart } from '@angular/router'; import { ActivatedRoute, Params, Router, NavigationStart } from '@angular/router';
import { ICONTYPES } from '../../../model/type';
import { ErrorModalService } from '../../../services/error-modal.service';
import { ErrorModel } from '../../../model/modal-error';
import { DatePipe } from '@angular/common';
import { FormGroup } from '@angular/forms';
import { ACTION } from '../../../model/action';
import { SavingWorkbasketService, SavingInformation } from '../../../services/saving-workbaskets/saving-workbaskets.service';
const dateFormat = 'yyyy-MM-ddTHH:mm:ss';
const dateLocale = 'en-US';
@Component({ @Component({
selector: 'taskana-workbasket-information', selector: 'taskana-workbasket-information',
templateUrl: './workbasket-information.component.html', templateUrl: './workbasket-information.component.html',
@ -16,12 +25,15 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
@Input() @Input()
workbasket: Workbasket; workbasket: Workbasket;
workbasketClone: Workbasket; workbasketClone: Workbasket;
@Input()
action: string;
allTypes: Map<string, string>; allTypes: Map<string, string>;
requestInProgress = false; requestInProgress = false;
modalSpinner = false; modalSpinner = false;
modalErrorMessage: string; hasChanges = false;
modalTitle = 'There was error while saving your workbasket'; badgeMessage = '';
private workbasketSubscription: Subscription; private workbasketSubscription: Subscription;
private routeSubscription: Subscription; private routeSubscription: Subscription;
@ -29,17 +41,20 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
constructor(private workbasketService: WorkbasketService, constructor(private workbasketService: WorkbasketService,
private alertService: AlertService, private alertService: AlertService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, ) { private router: Router,
private errorModalService: ErrorModalService,
private savingWorkbasket: SavingWorkbasketService) {
this.allTypes = IconTypeComponent.allTypes; this.allTypes = IconTypeComponent.allTypes;
} }
ngOnInit() { ngOnInit() {
this.workbasketClone = { ...this.workbasket }; this.workbasketClone = { ...this.workbasket };
this.routeSubscription = this.router.events.subscribe(event => { if (this.action === ACTION.CREATE) {
if (event instanceof NavigationStart) { this.badgeMessage = 'Creating new workbasket';
this.checkForChanges(); } else if (this.action === ACTION.COPY) {
} this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}); }
} }
selectType(type: ICONTYPES) { selectType(type: ICONTYPES) {
@ -48,6 +63,11 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
onSave() { onSave() {
this.beforeRequest(); this.beforeRequest();
if (!this.workbasket.workbasketId) {
this.postNewWorkbasket();
return true;
}
this.workbasketSubscription = (this.workbasketService.updateWorkbasket(this.workbasket._links.self.href, this.workbasket).subscribe( this.workbasketSubscription = (this.workbasketService.updateWorkbasket(this.workbasket._links.self.href, this.workbasket).subscribe(
workbasketUpdated => { workbasketUpdated => {
this.afterRequest(); this.afterRequest();
@ -57,7 +77,7 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
}, },
error => { error => {
this.afterRequest(); this.afterRequest();
this.modalErrorMessage = error; this.errorModalService.triggerError(new ErrorModel('There was error while saving your workbasket', error))
} }
)); ));
} }
@ -65,12 +85,12 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
onClear() { onClear() {
this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields')) this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields'))
this.workbasket = { ...this.workbasketClone }; this.workbasket = { ...this.workbasketClone };
this.hasChanges = false;
} }
private beforeRequest() { private beforeRequest() {
this.requestInProgress = true; this.requestInProgress = true;
this.modalSpinner = true; this.modalSpinner = true;
this.modalErrorMessage = undefined;
} }
private afterRequest() { private afterRequest() {
@ -79,13 +99,34 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
} }
private checkForChanges() { private postNewWorkbasket() {
if (!Workbasket.equals(this.workbasket, this.workbasketClone)) { this.addDateToWorkbasket();
this.openDiscardChangesModal(); this.workbasketService.createWorkbasket(this.workbasket._links.self.href,
} this.workbasket)
.subscribe((workbasketUpdated: Workbasket) => {
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Workbasket ${workbasketUpdated.key} was created successfully`))
this.workbasket = workbasketUpdated;
this.afterRequest();
this.workbasketService.triggerWorkBasketSaved();
this.workbasketService.selectWorkBasket(this.workbasket.workbasketId);
this.router.navigate(['../' + this.workbasket.workbasketId], { relativeTo: this.route });
if (this.action === ACTION.COPY) {
this.savingWorkbasket.triggerDistributionTargetSaving(
new SavingInformation(this.workbasket._links.distributionTargets.href, this.workbasket.workbasketId));
this.savingWorkbasket.triggerAccessItemsSaving(
new SavingInformation(this.workbasket._links.accessItems.href, this.workbasket.workbasketId));
}
}, error => {
this.errorModalService.triggerError(new ErrorModel('There was an error creating a workbasket', error.error.message))
this.requestInProgress = false;
});
} }
private openDiscardChangesModal() { private addDateToWorkbasket() {
const datePipe = new DatePipe(dateLocale);
const date = datePipe.transform(Date.now(), dateFormat) + 'Z';
this.workbasket.created = date;
this.workbasket.modified = date;
} }

View File

@ -1,32 +1,32 @@
<div class="container-scrollable" > <div class="container-scrollable">
<taskana-spinner [isRunning]="requestInProgress" class = "centered-horizontally"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<taskana-no-access *ngIf="!requestInProgress && (!hasPermission || !workbasket && selectedId)" ></taskana-no-access> <taskana-no-access *ngIf="!requestInProgress && (!hasPermission && !workbasket || !workbasket && selectedId)"></taskana-no-access>
<div id ="workbasket-details" *ngIf="workbasket && !requestInProgress"> <div id="workbasket-details" *ngIf="workbasket && !requestInProgress">
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li *ngIf="showDetail" class="visible-xs visible-sm hidden"> <li *ngIf="showDetail" class="visible-xs visible-sm hidden">
<a (click) = "backClicked()"><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a> <a (click)="backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li> </li>
<li role="presentation" class="active"> <li role="presentation" class="active">
<a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Information</a> <a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Information</a>
</li> </li>
<li role="presentation" class=""> <li role="presentation" [ngClass]="{'disabled': !workbasket._links?.accessItems}">
<a href="#access-items" aria-controls="Acccess" role="tab" data-toggle="tab" aria-expanded="true">Access</a> <a href="#access-items" aria-controls="Acccess" role="tab" data-toggle="tab" aria-expanded="true">Access</a>
</li> </li>
<li role="presentation" class=""> <li role="presentation" [ngClass]="{'disabled': !workbasket._links?.distributionTargets}">
<a href="#distribution-targets" aria-controls="distribution targets" role="tab" data-toggle="tab" aria-expanded="true">Distribution targets</a> <a href="#distribution-targets" aria-controls="distribution targets" role="tab" data-toggle="tab" aria-expanded="true">Distribution targets</a>
</li> </li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="work-baskets"> <div role="tabpanel" class="tab-pane fade in active" id="work-baskets">
<taskana-workbasket-information [workbasket]="workbasket"></taskana-workbasket-information> <taskana-workbasket-information [workbasket]="workbasket" [action]="action"></taskana-workbasket-information>
</div> </div>
<div role="tabpanel" class="tab-pane inactive" id="access-items"> <div role="tabpanel" class="tab-pane fade" id="access-items">
<taskana-workbasket-access-items [workbasket]="workbasket"></taskana-workbasket-access-items> <taskana-workbasket-access-items [workbasket]="workbasket" [action]="action"></taskana-workbasket-access-items>
</div> </div>
<div role="tabpanel" class="tab-pane inactive" id="distribution-targets"> <div role="tabpanel" class="tab-pane fade" id="distribution-targets">
<taskana-workbaskets-distribution-targets [workbasket]="workbasket"></taskana-workbaskets-distribution-targets> <taskana-workbaskets-distribution-targets [workbasket]="workbasket" [action]="action"></taskana-workbaskets-distribution-targets>
</div> </div>
</div> </div>
</div> </div>
<taskana-alert></taskana-alert>
</div> </div>

View File

@ -21,4 +21,11 @@
& > p{ & > p{
margin: 0px; margin: 0px;
} }
& > li.disabled {
cursor: not-allowed;
}
& > li.disabled > a {
pointer-events: none;
}
} }

View File

@ -9,7 +9,7 @@ import { DualListComponent } from './distribution-targets//dual-list/dual-list.c
import { Workbasket } from 'app/model/workbasket'; import { Workbasket } from 'app/model/workbasket';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { SpinnerComponent } from '../../shared/spinner/spinner.component'; import { SpinnerComponent } from '../../shared/spinner/spinner.component';
import { ICONTYPES, IconTypeComponent } from '../../shared/type-icon/icon-type.component'; import { IconTypeComponent } from '../../shared/type-icon/icon-type.component';
import { MapValuesPipe } from '../../pipes/map-values.pipe'; import { MapValuesPipe } from '../../pipes/map-values.pipe';
import { RemoveNoneTypePipe } from '../../pipes/remove-none-type'; import { RemoveNoneTypePipe } from '../../pipes/remove-none-type';
import { SelectWorkBasketPipe } from '../../pipes/seleted-workbasket.pipe'; import { SelectWorkBasketPipe } from '../../pipes/seleted-workbasket.pipe';
@ -22,6 +22,8 @@ import { WorkbasketService } from '../../services/workbasket.service';
import { MasterAndDetailService } from '../../services/master-and-detail.service'; import { MasterAndDetailService } from '../../services/master-and-detail.service';
import { PermissionService } from '../../services/permission.service'; import { PermissionService } from '../../services/permission.service';
import { AlertService } from '../../services/alert.service'; import { AlertService } from '../../services/alert.service';
import { ErrorModalService } from '../../services/error-modal.service';
import { SavingWorkbasketService } from '../../services/saving-workbaskets/saving-workbaskets.service';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
@ -31,101 +33,116 @@ import { HttpModule } from '@angular/http';
import { WorkbasketSummary } from '../../model/workbasket-summary'; import { WorkbasketSummary } from '../../model/workbasket-summary';
import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource';
import { WorkbasketAccessItemsResource } from '../../model/workbasket-access-items-resource'; import { WorkbasketAccessItemsResource } from '../../model/workbasket-access-items-resource';
import { ICONTYPES } from '../../model/type';
import { Router, Routes } from '@angular/router';
import { ACTION } from '../../model/action';
@Component({ @Component({
selector: 'taskana-filter', selector: 'taskana-filter',
template: '' template: ''
}) })
export class FilterComponent { export class FilterComponent {
@Input() @Input()
target: string; target: string;
}
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
} }
describe('WorkbasketDetailsComponent', () => { describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent; let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>; let fixture: ComponentFixture<WorkbasketDetailsComponent>;
let debugElement; let debugElement;
let masterAndDetailService; let masterAndDetailService;
let workbasketService; let workbasketService;
const workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', let router;
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' })); const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' }
];
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [RouterTestingModule, FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule], imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent, declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent, IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, SelectWorkBasketPipe], DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService, AlertService] providers: [WorkbasketService, MasterAndDetailService, PermissionService,
}) AlertService, ErrorModalService, SavingWorkbasketService]
.compileComponents(); })
})); .compileComponents();
}));
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketDetailsComponent); fixture = TestBed.createComponent(WorkbasketDetailsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement; component.hasPermission = false;
fixture.detectChanges(); debugElement = fixture.debugElement.nativeElement;
masterAndDetailService = TestBed.get(MasterAndDetailService); router = TestBed.get(Router)
workbasketService = TestBed.get(WorkbasketService); fixture.detectChanges();
spyOn(masterAndDetailService, 'getShowDetail').and.callFake(() => { return Observable.of(true) }) masterAndDetailService = TestBed.get(MasterAndDetailService);
spyOn(workbasketService, 'getSelectedWorkBasket').and.callFake(() => { return Observable.of('id1') }) workbasketService = TestBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => { spyOn(masterAndDetailService, 'getShowDetail').and.callFake(() => { return Observable.of(true) })
return Observable.of(new WorkbasketSummaryResource( spyOn(workbasketService, 'getSelectedWorkBasket').and.callFake(() => { return Observable.of('id1') })
{ spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => {
'workbaskets': new Array<WorkbasketSummary>( return Observable.of(new WorkbasketSummaryResource(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', {
new Links({ 'href': 'someurl' }))) 'workbaskets': new Array<WorkbasketSummary>(
}, new Links({ 'href': 'someurl' }))) new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
}) new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
})
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) }) spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) })
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => { spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => {
return Observable.of(new WorkbasketAccessItemsResource( return Observable.of(new WorkbasketAccessItemsResource(
{ 'accessItems': new Array<WorkbasketAccessItems>() }, new Links({ 'href': 'url' }))) { 'accessItems': new Array<WorkbasketAccessItems>() }, new Links({ 'href': 'url' })))
}) })
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => { spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource( return Observable.of(new WorkbasketSummaryResource(
{ 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' }))) { 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' })))
}) })
}); });
afterEach(() => { afterEach(() => {
document.body.removeChild(debugElement); document.body.removeChild(debugElement);
}); });
it('should be created', () => { it('should be created', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should has created taskana-no-access if workbasket is not defined', () => { it('should has created taskana-no-access if workbasket is not defined and hasPermission is false', () => {
expect(component.workbasket).toBeUndefined(); expect(component.workbasket).toBeUndefined();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy(); component.hasPermission = false;
}); fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
});
it('should has created taskana-workbasket-details if workbasket is defined and taskana-no-access should dissapear', () => { it('should has created taskana-workbasket-details if workbasket is defined and taskana-no-access should dissapear', () => {
expect(component.workbasket).toBeUndefined(); expect(component.workbasket).toBeUndefined();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy(); component.hasPermission = false;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
component.workbasket = workbasket; component.workbasket = workbasket;
fixture.detectChanges(); fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeFalsy(); expect(debugElement.querySelector('taskana-no-access')).toBeFalsy();
expect(debugElement.querySelector('taskana-workbasket-information')).toBeTruthy(); expect(debugElement.querySelector('taskana-workbasket-information')).toBeTruthy();
}); });
it('should show back button with classes "visible-xs visible-sm hidden" when showDetail property is true', () => {
component.workbasket = workbasket;
component.ngOnInit();
fixture.detectChanges();
expect(debugElement.querySelector('.visible-xs.visible-sm.hidden > a').textContent).toBe('Back');
});
}); });

View File

@ -7,6 +7,10 @@ import { PermissionService } from '../../services/permission.service';
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { WorkbasketSummary } from '../../model/workbasket-summary'; import { WorkbasketSummary } from '../../model/workbasket-summary';
import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource';
import { ICONTYPES } from '../../model/type';
import { ErrorModel } from '../../model/modal-error';
import { ErrorModalService } from '../../services/error-modal.service';
import { ACTION } from '../../model/action';
@Component({ @Component({
selector: 'taskana-workbasket-details', selector: 'taskana-workbasket-details',
@ -17,10 +21,12 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
workbasket: Workbasket; workbasket: Workbasket;
selectedId = -1; workbasketCopy: Workbasket;
selectedId: string = undefined;
showDetail = false; showDetail = false;
hasPermission = true; hasPermission = true;
requestInProgress = false; requestInProgress = false;
action: string;
private workbasketSelectedSubscription: Subscription; private workbasketSelectedSubscription: Subscription;
private workbasketSubscription: Subscription; private workbasketSubscription: Subscription;
@ -32,22 +38,37 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private masterAndDetailService: MasterAndDetailService, private masterAndDetailService: MasterAndDetailService,
private permissionService: PermissionService) { } private permissionService: PermissionService,
private errorModalService: ErrorModalService) { }
ngOnInit() { ngOnInit() {
this.workbasketSelectedSubscription = this.service.getSelectedWorkBasket().subscribe(workbasketIdSelected => { this.workbasketSelectedSubscription = this.service.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
this.workbasket = undefined; this.workbasket = undefined;
this.requestInProgress = true;
this.getWorkbasketInformation(workbasketIdSelected); this.getWorkbasketInformation(workbasketIdSelected);
}); });
this.routeSubscription = this.route.params.subscribe(params => { this.routeSubscription = this.route.params.subscribe(params => {
const id = params['id']; let id = params['id'];
this.action = undefined;
if (id && id.indexOf('new-workbasket') !== -1) {
this.action = ACTION.CREATE;
id = undefined;
this.getWorkbasketInformation(id);
} else if (id && id.indexOf('copy-workbasket') !== -1) {
if (!this.selectedId) {
this.router.navigate(['./'], { relativeTo: this.route.parent });
return;
}
this.action = ACTION.COPY;
this.workbasketCopy = this.workbasket;
id = undefined
this.getWorkbasketInformation(id, this.selectedId);
}
if (id && id !== '') { if (id && id !== '') {
this.selectedId = id; this.selectWorkbasket(id);
this.service.selectWorkBasket(id);
} }
}); });
@ -69,8 +90,25 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
this.router.navigate(['./'], { relativeTo: this.route.parent }); this.router.navigate(['./'], { relativeTo: this.route.parent });
} }
private getWorkbasketInformation(workbasketIdSelected: string) { private selectWorkbasket(id: string) {
this.service.getWorkBasketsSummary().subscribe((workbasketSummary: WorkbasketSummaryResource) => { this.selectedId = id;
this.service.selectWorkBasket(id);
}
private getWorkbasketInformation(workbasketIdSelected: string, copyId: string = undefined) {
this.requestInProgress = true;
this.service.getWorkBasketsSummary(true).subscribe((workbasketSummary: WorkbasketSummaryResource) => {
if (!workbasketIdSelected && this.action === ACTION.CREATE) { // CREATE
this.workbasket = new Workbasket(undefined);
this.workbasket._links.self = workbasketSummary._links.self;
this.requestInProgress = false;
} else if (!workbasketIdSelected && this.action === ACTION.COPY) { // COPY
this.workbasket = { ...this.workbasketCopy };
this.workbasket._links.self = workbasketSummary._links.self;
this.workbasket.workbasketId = undefined;
this.requestInProgress = false;
}
const workbasketSummarySelected = this.getWorkbasketSummaryById(workbasketSummary._embedded.workbaskets, workbasketIdSelected); const workbasketSummarySelected = this.getWorkbasketSummaryById(workbasketSummary._embedded.workbaskets, workbasketIdSelected);
if (workbasketSummarySelected && workbasketSummarySelected._links) { if (workbasketSummarySelected && workbasketSummarySelected._links) {
this.workbasketSubscription = this.service.getWorkBasket(workbasketSummarySelected._links.self.href).subscribe(workbasket => { this.workbasketSubscription = this.service.getWorkBasket(workbasketSummarySelected._links.self.href).subscribe(workbasket => {

View File

@ -0,0 +1,33 @@
<li id="wb-action-toolbar" class="list-group-item tab-align">
<div class="row">
<div class="col-xs-9">
<button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button>
<button type="button" data-toggle="tooltip" title="Edit" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" (click)="copyWorkbasket()" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" data-toggle="tooltip" title="Remove distibution target" class="btn btn-default">
<span class="glyphicon glyphicon-erase" aria-hidden="true"></span>
</button>
<button *ngIf="workbasketIdSelected" type="button" (click)="removeWorkbasket()" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</div>
<div class="pull-right margin-right">
<taskana-sort (performSorting)="sorting($event)"></taskana-sort>
<div class="clearfix btn-group">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" data-toggle="collapse" data-target="#wb-filter-bar"
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
</div>
</div>
<div class="row">
<taskana-filter target="wb-filter-bar" (performFilter)="filtering($event)"></taskana-filter>
</div>
</li>

View File

@ -0,0 +1,12 @@
.list-group-item {
padding: 0px 15px;
border: none;
}
.tab-align{
margin-bottom: 0px;
padding: 0px;
&>div{
margin: 6px 0px;
}
}

View File

@ -0,0 +1,120 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpModule } from '@angular/http';
import { HttpClientModule } from '@angular/common/http';
import { Router, Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { ErrorModalService } from '../../../services/error-modal.service';
import { WorkbasketService } from '../../../services/workbasket.service';
import { RequestInProgressService } from '../../../services/request-in-progress.service';
import { AlertService } from '../../../services/alert.service';
import { SortComponent, SortingModel } from '../../../shared/sort/sort.component';
import { FilterComponent, FilterModel } from '../../../shared/filter/filter.component';
import { IconTypeComponent } from '../../../shared/type-icon/icon-type.component';
import { MapValuesPipe } from '../../../pipes/map-values.pipe';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar.component';
import { Component } from '@angular/core';
import { WorkbasketSummary } from '../../../model/workbasket-summary';
import { Links } from '../../../model/links';
import { Observable } from 'rxjs/Observable';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
describe('WorkbasketListToolbarComponent', () => {
let component: WorkbasketListToolbarComponent;
let fixture: ComponentFixture<WorkbasketListToolbarComponent>;
let debugElement, workbasketService, requestInProgressService, router;
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' }
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, AngularSvgIconModule, HttpModule,
HttpClientModule, RouterTestingModule.withRoutes(routes)],
declarations: [WorkbasketListToolbarComponent, SortComponent,
FilterComponent, IconTypeComponent, DummyDetailComponent, MapValuesPipe],
providers: [ErrorModalService, WorkbasketService, RequestInProgressService, AlertService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketListToolbarComponent);
workbasketService = TestBed.get(WorkbasketService);
requestInProgressService = TestBed.get(RequestInProgressService);
router = TestBed.get(Router);
spyOn(workbasketService, 'deleteWorkbasket').and.returnValue(Observable.of(''));
spyOn(workbasketService, 'triggerWorkBasketSaved');
spyOn(requestInProgressService, 'setRequestInProgress');
debugElement = fixture.debugElement.nativeElement;
component = fixture.componentInstance;
component.workbaskets = new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1',
undefined, undefined, undefined, undefined, undefined, undefined, undefined, new Links({ 'href': 'selfLink' })));
component.workbasketIdSelected = '1';
fixture.detectChanges();
});
afterEach(() => {
document.body.removeChild(debugElement);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should navigate to new-workbasket when click on add new workbasket', () => {
const spy = spyOn(router, 'navigate');
component.addWorkbasket();
expect(spy.calls.first().args[0][0].outlets.detail[0]).toBe('new-workbasket');
});
it('should navigate to copy-workbasket when click on add copy workbasket', () => {
const spy = spyOn(router, 'navigate');
component.copyWorkbasket();
expect(spy.calls.first().args[0][0].outlets.detail[0]).toBe('copy-workbasket');
});
it('should call to workbasket service to remove workbasket after click on remove workbasket', () => {
const spy = spyOn(router, 'navigate');
component.removeWorkbasket();
expect(requestInProgressService.setRequestInProgress).toHaveBeenCalledWith(true);
expect(workbasketService.deleteWorkbasket).toHaveBeenCalledWith('selfLink');
expect(requestInProgressService.setRequestInProgress).toHaveBeenCalledWith(false);
expect(workbasketService.triggerWorkBasketSaved).toHaveBeenCalled();
expect(spy.calls.first().args[0][0]).toBe('/workbaskets');
});
it('should emit performSorting when sorting is triggered', () => {
let sort: SortingModel;
const compareSort = new SortingModel();
component.performSorting.subscribe((value) => { sort = value })
component.sorting(compareSort);
expect(sort).toBe(compareSort);
});
it('should emit performFilter when filter is triggered', () => {
let filter: FilterModel;
const compareFilter = new FilterModel();
component.performFilter.subscribe((value) => { filter = value })
component.filtering(compareFilter);
expect(filter).toBe(compareFilter);
});
});

View File

@ -0,0 +1,73 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { SortingModel } from '../../../shared/sort/sort.component';
import { FilterModel } from '../../../shared/filter/filter.component';
import { WorkbasketService } from '../../../services/workbasket.service';
import { Subscription } from 'rxjs/Subscription';
import { WorkbasketSummary } from '../../../model/workbasket-summary';
import { Router, ActivatedRoute } from '@angular/router';
import { ErrorModel } from '../../../model/modal-error';
import { ErrorModalService } from '../../../services/error-modal.service';
import { RequestInProgressService } from '../../../services/request-in-progress.service';
import { AlertService, AlertModel, AlertType } from '../../../services/alert.service';
@Component({
selector: 'taskana-workbasket-list-toolbar',
templateUrl: './workbasket-list-toolbar.component.html',
styleUrls: ['./workbasket-list-toolbar.component.scss']
})
export class WorkbasketListToolbarComponent implements OnInit {
@Input() workbaskets: Array<WorkbasketSummary>;
@Input() workbasketIdSelected: string;
@Input() workbasketIdSelectedChanged: string;
@Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>();
workbasketServiceSubscription: Subscription;
constructor(
private workbasketService: WorkbasketService,
private route: ActivatedRoute,
private router: Router,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService,
private alertService: AlertService) { }
ngOnInit() {
}
sorting(sort: SortingModel) {
this.performSorting.emit(sort);
}
filtering(filterBy: FilterModel) {
this.performFilter.emit(filterBy);
}
addWorkbasket() {
this.router.navigate([{ outlets: { detail: ['new-workbasket'] } }], { relativeTo: this.route });
}
removeWorkbasket() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.deleteWorkbasket(this.findWorkbasketSelectedObject()._links.self.href).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS,
`Workbasket ${this.workbasketIdSelected} was removed successfully`))
this.router.navigate(['/workbaskets']);
}, error => {
this.requestInProgressService.setRequestInProgress(false);
this.errorModalService.triggerError(new ErrorModel(
`There was an error deleting workbasket ${this.workbasketIdSelected}`, error.error.message))
});
}
copyWorkbasket() {
this.router.navigate([{ outlets: { detail: ['copy-workbasket'] } }], { relativeTo: this.route });
}
private findWorkbasketSelectedObject() {
return this.workbaskets.find(element => element.workbasketId === this.workbasketIdSelected);
}
}

View File

@ -1,40 +1,8 @@
<div class="workbasket-list-full-height"> <div class="workbasket-list-full-height">
<ul id="wb-list-container" class="list-group footer-space"> <ul id="wb-list-container" class="list-group footer-space">
<li id="wb-action-toolbar" class="list-group-item tab-align"> <taskana-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)"
<div class="row"> [(workbasketIdSelected)]="selectedId"></taskana-workbasket-list-toolbar>
<div class="col-xs-9">
<button type="button" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button>
<button type="button" data-toggle="tooltip" title="Edit" class="btn btn-default">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
</button>
<button type="button" data-toggle="tooltip" title="copy" class="btn btn-default">
<span class="glyphicon glyphicon-copy" aria-hidden="true"></span>
</button>
<button type="button" data-toggle="tooltip" title="Remove distibution target" class="btn btn-default">
<span class="glyphicon glyphicon-erase" aria-hidden="true"></span>
</button>
<button type="button" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</div>
<div class="pull-right margin-right">
<taskana-sort (performSorting)="performSorting($event)"></taskana-sort>
<div class="clearfix btn-group">
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" data-toggle="collapse" data-target="#wb-filter-bar"
aria-expanded="false">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
</div>
</div>
<div class="row">
<taskana-filter target="wb-filter-bar" (performFilter)="performFilter($event)"></taskana-filter>
</div>
</li>
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId" <li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId"
type="text" (click)="selectWorkbasket(workbasket.workbasketId)"> type="text" (click)="selectWorkbasket(workbasket.workbasketId)">
<div class="row"> <div class="row">
@ -44,8 +12,10 @@
</dt> </dt>
</dl> </dl>
<dl class="col-xs-10"> <dl class="col-xs-10">
<dt>{{workbasket.name}} ({{workbasket.key}}) </dt> <dt>{{workbasket.name}},
<dd>{{workbasket.description}}</dd> <i>{{workbasket.key}} </i>
</dt>
<dd>{{workbasket.description}} &nbsp;</dd>
<dd>{{workbasket.owner}} &nbsp;</dd> <dd>{{workbasket.owner}} &nbsp;</dd>
</dl> </dl>
</div> </div>

View File

@ -16,7 +16,13 @@ a > label{
width: 100%; width: 100%;
} }
.tab-align{ dt > i {
border-bottom: 1px solid #ddd; font-weight: normal;
padding-bottom: 12px; }
li > div.row > dl {
margin-bottom: 0px;
}
li > div.row > dl:first-child {
margin-left: 10px;
} }

View File

@ -1,6 +1,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing'; import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { WorkbasketListComponent } from './workbasket-list.component'; import { WorkbasketListComponent } from './workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { WorkbasketSummary } from '../../model/workbasket-summary'; import { WorkbasketSummary } from '../../model/workbasket-summary';
@ -17,7 +18,9 @@ import { RemoveNoneTypePipe } from '../../pipes/remove-none-type';
import { MapValuesPipe } from '../../pipes/map-values.pipe'; import { MapValuesPipe } from '../../pipes/map-values.pipe';
import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource';
import { Links } from '../../model/links'; import { Links } from '../../model/links';
import { ErrorModalService } from '../../services/error-modal.service';
import { RequestInProgressService } from '../../services/request-in-progress.service';
import { AlertService } from '../../services/alert.service';
@Component({ @Component({
selector: 'taskana-dummy-detail', selector: 'taskana-dummy-detail',
@ -55,7 +58,7 @@ describe('WorkbasketListComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent,
RemoveNoneTypePipe, IconTypeComponent, SortComponent, MapValuesPipe], RemoveNoneTypePipe, IconTypeComponent, SortComponent, MapValuesPipe],
imports: [ imports: [
AngularSvgIconModule, AngularSvgIconModule,
@ -63,7 +66,7 @@ describe('WorkbasketListComponent', () => {
HttpClientModule, HttpClientModule,
RouterTestingModule.withRoutes(routes) RouterTestingModule.withRoutes(routes)
], ],
providers: [WorkbasketService] providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService]
}) })
.compileComponents(); .compileComponents();
@ -102,19 +105,19 @@ describe('WorkbasketListComponent', () => {
expect(debugElement.querySelector('#wb-list-container')).toBeDefined(); expect(debugElement.querySelector('#wb-list-container')).toBeDefined();
expect(debugElement.querySelector('#collapsedMenufilterWb')).toBeDefined(); expect(debugElement.querySelector('#collapsedMenufilterWb')).toBeDefined();
expect(debugElement.querySelector('taskana-filter')).toBeDefined(); expect(debugElement.querySelector('taskana-filter')).toBeDefined();
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2);
}); });
it('should have two workbasketsummary rows created with the second one selected.', () => { it('should have two workbasketsummary rows created with the second one selected.', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(2);
expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item'); expect(debugElement.querySelectorAll('#wb-list-container > li')[0].getAttribute('class')).toBe('list-group-item');
expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class')).toBe('list-group-item active'); expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item active');
}); });
it('should have two workbasketsummary rows created with two different icons: user and users', () => { it('should have two workbasketsummary rows created with two different icons: user and users', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li')[1] expect(debugElement.querySelectorAll('#wb-list-container > li')[0]
.querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/user.svg'); .querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/user.svg');
expect(debugElement.querySelectorAll('#wb-list-container > li')[2] expect(debugElement.querySelectorAll('#wb-list-container > li')[1]
.querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg'); .querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg');
}); });

View File

@ -1,4 +1,4 @@
import { Component, OnInit, EventEmitter, OnDestroy } from '@angular/core'; import { Component, OnInit, EventEmitter, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource'; import { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource';
import { WorkbasketSummary } from '../../model/workbasket-summary'; import { WorkbasketSummary } from '../../model/workbasket-summary';
import { WorkbasketService } from '../../services/workbasket.service' import { WorkbasketService } from '../../services/workbasket.service'
@ -14,8 +14,7 @@ import { Router, ActivatedRoute } from '@angular/router';
}) })
export class WorkbasketListComponent implements OnInit, OnDestroy { export class WorkbasketListComponent implements OnInit, OnDestroy {
newWorkbasket: WorkbasketSummary; selectedId = '';
selectedId: string = undefined;
workbaskets: Array<WorkbasketSummary> = []; workbaskets: Array<WorkbasketSummary> = [];
requestInProgress = false; requestInProgress = false;
@ -26,7 +25,11 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
private workbasketServiceSubscription: Subscription; private workbasketServiceSubscription: Subscription;
private workbasketServiceSavedSubscription: Subscription; private workbasketServiceSavedSubscription: Subscription;
constructor(private workbasketService: WorkbasketService, private router: Router, private route: ActivatedRoute) { } constructor(
private workbasketService: WorkbasketService,
private router: Router,
private route: ActivatedRoute,
private cdRef: ChangeDetectorRef) { }
ngOnInit() { ngOnInit() {
this.requestInProgress = true; this.requestInProgress = true;
@ -37,6 +40,7 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => { this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
this.selectedId = workbasketIdSelected; this.selectedId = workbasketIdSelected;
this.cdRef.detectChanges();
}); });
this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => { this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => {
@ -51,13 +55,11 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
return return
} }
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route }); this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
} }
performSorting(sort: SortingModel) { performSorting(sort: SortingModel) {
this.sort = sort; this.sort = sort;
this.performRequest(); this.performRequest();
} }
performFilter(filterBy: FilterModel) { performFilter(filterBy: FilterModel) {
@ -65,17 +67,6 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
this.performRequest(); this.performRequest();
} }
onDelete(workbasket: WorkbasketSummary) {
}
onAdd() {
}
onClear() {
}
private performRequest(): void { private performRequest(): void {
this.requestInProgress = true; this.requestInProgress = true;
this.workbaskets = []; this.workbaskets = [];

View File

@ -34,8 +34,7 @@
.container-scrollable { .container-scrollable {
max-height: calc(100vh - 55px); max-height: calc(100vh - 55px);
height: calc(100vh - 55px); height: calc(100vh - 55px);
overflow-y: auto; overflow: hidden;
overflow-x: hidden;
} }
.vertical-center { .vertical-center {
@ -99,6 +98,12 @@
} }
.panel-default > .panel-heading .badge.warning {
background-color: #f0ad4e;
}
/* /*
*Remove bootstrap cols padding for master and detail component *Remove bootstrap cols padding for master and detail component
*/ */
@ -131,10 +136,6 @@
border-bottom: none; border-bottom: none;
} }
li > div.row > dl {
margin-bottom: 0px;
}
.selected { .selected {
z-index: 2; z-index: 2;
background-color: #f5f5f5; background-color: #f5f5f5;