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';
const appRoutes: Routes = [
{ path: 'workbaskets',
{
path: 'workbaskets',
component: MasterAndDetailComponent,
children: [
{
@ -27,7 +28,8 @@ const appRoutes: Routes = [
}
]
},
{ path: 'clasifications',
{
path: 'clasifications',
component: MasterAndDetailComponent,
children: [
{

View File

@ -1,37 +1,51 @@
<nav class="navbar navbar-fixed-top">
<div class="navbar show no-border-radius navbar-inverse no-gutter">
<div class="col-xs-3 col-md-4">
<svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon>
<ul class="nav logo">
<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>
</ul>
</div>
<div class="col-xs-8 col-md-7 logo-container">
<ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist">
<li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab" ><a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a></li>
<li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab" ><a routerLink="/clasifications" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a></li>
</ul>
</div>
<div class="col-xs-1">
<button type="button" class="navbar-toggle collapsed show" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</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>
<div class="navbar show no-border-radius navbar-inverse no-gutter">
<div class="col-xs-3 col-md-4">
<svg-icon class="logo hidden visible-xs visible-sm" src="./assets/icons/logo.svg"></svg-icon>
<ul class="nav logo">
<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>
</ul>
</div>
<div class="col-xs-8 col-md-7 logo-container">
<ul class="nav nav-tabs no-border-bottom" id="myTabs" role="tablist">
<li role="presentation" class="{{workbasketsRoute? 'active' : 'inactive'}}" role="tab" data-toggle="tab">
<a routerLink="/workbaskets" aria-controls="Work baskets" routerLinkActive="active">Workbaskets</a>
</li>
<li role="presentation" class="{{workbasketsRoute? 'inactive' : 'active'}}" role="tab" data-toggle="tab">
<a routerLink="/clasifications" aria-controls="Clasifications" routerLinkActive="active">Clasifications</a>
</li>
</ul>
</div>
<div class="col-xs-1">
<button type="button" class="navbar-toggle collapsed show" data-toggle="collapse" data-target="#navbar" aria-expanded="false"
aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</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>
<div class="container-fluid container-main">
<div class ="row ">
<div class="row ">
<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>

View File

@ -5,6 +5,12 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientModule } from '@angular/common/http';
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', () => {
@ -17,13 +23,14 @@ describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
AppComponent
AppComponent, GeneralMessageModalComponent, SpinnerComponent, AlertComponent
],
imports: [
AngularSvgIconModule,
RouterTestingModule.withRoutes(routes),
HttpClientModule
]
],
providers: [ErrorModalService, RequestInProgressService, AlertService]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);

View File

@ -1,6 +1,9 @@
import { Component, OnInit } from '@angular/core';
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({
selector: 'taskana-root',
@ -15,7 +18,15 @@ export class AppComponent implements OnInit {
workplaceUrl: string = environment.taskanaWorkplaceUrl;
workbasketsRoute = true;
constructor(private route: ActivatedRoute, private router: Router) {
modalErrorMessage = '';
modalTitle = '';
requestInProgress = false;
constructor(
private router: Router,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService) {
}
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 { 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 { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.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 { HTTP_INTERCEPTORS } from '@angular/common/http';
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
@ -66,6 +72,7 @@ const MODULES = [
const DECLARATIONS = [
AppComponent,
WorkbasketListComponent,
WorkbasketListToolbarComponent,
AccessItemsComponent,
WorkbasketDetailsComponent,
MasterAndDetailComponent,
@ -96,7 +103,10 @@ const DECLARATIONS = [
useClass: HttpClientInterceptor,
multi: true
},
AlertService
AlertService,
ErrorModalService,
RequestInProgressService,
SavingWorkbasketService
],
bootstrap: [AppComponent]
})

View File

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

View File

@ -1,6 +1,6 @@
export class Links {
constructor(
public self: { 'href': string },
public self: { 'href': string } = undefined,
public distributionTargets: { '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 { ICONTYPES } from './type';
export class WorkbasketSummary {
constructor(
public workbasketId: string,
public key: string,
public name: string,
public description: string,
public owner: string,
public modified: string,
public domain: string,
public type: string,
public orgLevel1: string,
public orgLevel2: string,
public orgLevel3: string,
public orgLevel4: string,
public workbasketId: string = undefined,
public key: string = undefined,
public name: string = undefined,
public description: string = undefined,
public owner: string = undefined,
public modified: string = undefined,
public domain: string = undefined,
public type: string = ICONTYPES.PERSONAL,
public orgLevel1: string = undefined,
public orgLevel2: string = undefined,
public orgLevel3: string = undefined,
public orgLevel4: string = undefined,
public _links: Links = undefined) {
}
}

View File

@ -1,4 +1,5 @@
import { Links } from './links';
import { ICONTYPES } from './type';
export class Workbasket {
public static equals(org: Workbasket, comp: Workbasket): boolean {
@ -28,7 +29,7 @@ export class Workbasket {
public created: string = undefined,
public key: string = undefined,
public domain: string = undefined,
public type: string = undefined,
public type: ICONTYPES = ICONTYPES.PERSONAL,
public modified: string = undefined,
public name: string = undefined,
public description: string = undefined,
@ -41,6 +42,6 @@ export class Workbasket {
public orgLevel2: string = undefined,
public orgLevel3: 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
createWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> {
return this.httpClient
.post<Workbasket>(url, this.httpOptions);
.post<Workbasket>(url, workbasket, this.httpOptions);
}
// PUT
updateWorkbasket(url: string, workbasket: Workbasket): Observable<Workbasket> {

View File

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

View File

@ -11,6 +11,8 @@
.list-group-search {
padding: 10px 15px;
margin-top: 12px;
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 { IconTypeComponent, ICONTYPES } from '../type-icon/icon-type.component'
import { IconTypeComponent } from '../type-icon/icon-type.component'
import { ICONTYPES } from '../../model/type';
export class FilterModel {
type: string;

View File

@ -1,14 +1,14 @@
<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>
</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>
</div>
<div class="{{showDetail? 'hidden': 'hidden-xs hidden-sm col-md-8 container-no-detail'}}">
<div class = "center-block no-detail" >
<h3 class = "grey">Select a worbasket</h3>
<div class="center-block no-detail">
<h3 class="grey">Select a worbasket</h3>
<svg-icon class="img-responsive no-detail-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
</div>
</div>

View File

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

View File

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

View File

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

View File

@ -1,12 +1,13 @@
<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 && accessItems" id="wb-information" class="panel panel-default">
<div *ngIf="workbasket" id="wb-information" class="panel panel-default">
<div class="panel-heading">
<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>
</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 class="panel-body">
<form #AccessItemsForm="ngForm">

View File

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

View File

@ -7,6 +7,10 @@ import { WorkbasketAccessItems } from '../../../model/workbasket-access-items';
import { WorkbasketService } from '../../../services/workbasket.service';
import { AlertService, AlertModel, AlertType } from '../../../services/alert.service';
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;
@ -19,6 +23,9 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
@Input()
workbasket: Workbasket;
@Input()
action: string;
badgeMessage = '';
accessItemsResource: WorkbasketAccessItemsResource;
accessItems: Array<WorkbasketAccessItems>;
@ -29,11 +36,19 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
modalTitle: string;
modalErrorMessage: string;
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() {
if (!this.workbasket._links.accessItems) {
return;
}
this.accessItemsubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href)
.subscribe((accessItemsResource: WorkbasketAccessItemsResource) => {
this.accessItemsResource = accessItemsResource;
@ -41,7 +56,17 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
this.accessItemsClone = 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() {
@ -70,12 +95,11 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
AlertType.SUCCESS, `Workbasket ${this.workbasket.name} Access items were saved successfully`));
this.requestInProgress = false;
return true;
},
error => {
this.modalErrorMessage = error.message;
this.requestInProgress = false;
return false;
})
}, error => {
this.errorModalService.triggerError(new ErrorModel(`There was error while saving your workbasket's access items`, error))
this.requestInProgress = false;
return false;
})
return false;
}
@ -86,8 +110,15 @@ export class AccessItemsComponent implements OnInit, OnDestroy {
});
return accessItemClone;
}
private setWorkbasketIdForCopy(workbasketId: string) {
this.accessItems.forEach(element => {
element.accessItemId = undefined;
element.workbasketId = workbasketId;
});
}
ngOnDestroy(): void {
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)"
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 class="panel-heading">
<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>
</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 class="panel-body">
<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 { FilterModel } from '../../../shared/filter/filter.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({
'workbaskets': new Array<WorkbasketSummary>(
@ -43,7 +46,7 @@ describe('DistributionTargetsComponent', () => {
let component: DistributionTargetsComponent;
let fixture: ComponentFixture<DistributionTargetsComponent>;
let workbasketService;
const workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
beforeEach(async(() => {
@ -51,7 +54,7 @@ describe('DistributionTargetsComponent', () => {
imports: [AngularSvgIconModule, HttpClientModule, HttpModule, JsonpModule],
declarations: [DistributionTargetsComponent, SpinnerComponent, GeneralMessageModalComponent,
FilterComponent, SelectWorkBasketPipe, IconTypeComponent, DualListComponent],
providers: [WorkbasketService, AlertService]
providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService]
})
.compileComponents();
}));

View File

@ -12,6 +12,10 @@ import { Subscription } from 'rxjs/Subscription';
import { element } from 'protractor';
import { WorkbasketSummaryResource } from '../../../model/workbasket-summary-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 {
LEFT,
@ -26,10 +30,14 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
@Input()
workbasket: Workbasket;
@Input()
action: string;
badgeMessage = '';
distributionTargetsSubscription: Subscription;
workbasketSubscription: Subscription;
workbasketFilterSubscription: Subscription;
savingDistributionTargetsSubscription: Subscription;
distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource;
distributionTargetsLeft: Array<WorkbasketSummary>;
@ -45,10 +53,17 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
modalErrorMessage: string;
side = Side;
constructor(private workbasketService: WorkbasketService, private alertService: AlertService) { }
constructor(
private workbasketService: WorkbasketService,
private alertService: AlertService,
private savingWorkbaskets: SavingWorkbasketService,
private errorModalService: ErrorModalService) { }
ngOnInit() {
this.onRequest(undefined);
if (!this.workbasket._links.distributionTargets) {
return;
}
this.distributionTargetsSubscription = this.workbasketService.getWorkBasketsDistributionTargets(
this.workbasket._links.distributionTargets.href).subscribe(
(distributionTargetsSelectedResource: WorkbasketDistributionTargetsResource) => {
@ -64,6 +79,18 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
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) {
@ -92,7 +119,7 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
return true;
},
error => {
this.modalErrorMessage = error.message;
this.errorModalService.triggerError(new ErrorModel(`There was error while saving your workbasket's distribution targets`, error))
this.requestInProgress = false;
return false;
}
@ -161,6 +188,8 @@ export class DistributionTargetsComponent implements OnInit, OnDestroy {
if (this.distributionTargetsSubscription) { this.distributionTargetsSubscription.unsubscribe(); }
if (this.workbasketSubscription) { this.workbasketSubscription.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-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 class="panel-heading">
<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" (click)="onClear()" class="btn btn-default">Undo changes</button>
</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 class="panel-body">
<form #WorkbasketForm="ngForm">
@ -24,7 +25,7 @@
<input type="text" required #name="ngModel" class="form-control" id="wb-name" placeholder="Name" [(ngModel)]="workbasket.name"
name="workbasket.name">
<div [hidden]="name.valid" class="required-text">
* Name is required
* Name is required
</div>
</div>
<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"
name="workbasket.owner">
<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 class="form-group">
@ -43,7 +52,7 @@
{{allTypes.get(workbasket.type)}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu1">
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu">
<li>
<a *ngFor="let type of allTypes | mapValues | removeEmptyType" (click)="selectType(type.key)">
<taskana-icon-type class="vertical-align" [type]='type.key'></taskana-icon-type>
@ -53,11 +62,6 @@
</ul>
</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">
<label for="wb-description" class="control-label">Description</label>
<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 { HttpModule, JsonpModule } from '@angular/http';
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 { GeneralMessageModalComponent } from '../../../shared/general-message-modal/general-message-modal.component';
import { MapValuesPipe } from '../../../pipes/map-values.pipe';
@ -15,18 +15,22 @@ import { AlertService } from '../../../services/alert.service';
import { RouterTestingModule } from '@angular/router/testing';
import { Links } from '../../../model/links';
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', () => {
let component: WorkbasketInformationComponent;
let fixture: ComponentFixture<WorkbasketInformationComponent>;
let debugElement, workbasketService;
let debugElement, workbasketService, alertService, savingWorkbasketService;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WorkbasketInformationComponent, IconTypeComponent, MapValuesPipe,
RemoveNoneTypePipe, SpinnerComponent, GeneralMessageModalComponent],
imports: [FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule, RouterTestingModule],
providers: [WorkbasketService, AlertService]
providers: [WorkbasketService, AlertService, SavingWorkbasketService, ErrorModalService]
})
.compileComponents();
@ -34,6 +38,9 @@ describe('InformationComponent', () => {
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService);
alertService = TestBed.get(AlertService);
savingWorkbasketService = TestBed.get(SavingWorkbasketService);
spyOn(alertService, 'triggerAlert');
}));
afterEach(() => {
@ -45,12 +52,12 @@ describe('InformationComponent', () => {
});
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',
'orgLevel1', 'orgLevel2', 'orgLevel3', 'orgLevel4', null);
fixture.detectChanges();
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);
fixture.whenStable().then(() => {
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', () => {
component.workbasket = new Workbasket('id1');
expect(component.workbasket.type).toEqual(undefined);
component.selectType(ICONTYPES.PERSONAL);
expect(component.workbasket.type).toEqual('PERSONAL');
component.selectType(ICONTYPES.GROUP);
expect(component.workbasket.type).toEqual('GROUP');
@ -70,7 +75,7 @@ describe('InformationComponent', () => {
it('should create a copy of workbasket when workbasket is selected', () => {
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);
component.ngOnInit();
fixture.detectChanges();
@ -78,20 +83,19 @@ describe('InformationComponent', () => {
});
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',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(Observable.of(component.workbasket));
component.onSave();
expect(component.modalSpinner).toBeTruthy();
expect(component.modalErrorMessage).toBeUndefined();
expect(component.requestInProgress).toBeFalsy();
}));
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',
'orgLevel3', 'orgLevel4', new Links({ 'href': 'someUrl' }));
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(Observable.of(component.workbasket));
@ -100,4 +104,43 @@ describe('InformationComponent', () => {
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 { Workbasket } from '../../../model/workbasket';
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 { AlertService, AlertModel, AlertType } from '../../../services/alert.service';
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({
selector: 'taskana-workbasket-information',
templateUrl: './workbasket-information.component.html',
@ -16,12 +25,15 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
@Input()
workbasket: Workbasket;
workbasketClone: Workbasket;
@Input()
action: string;
allTypes: Map<string, string>;
requestInProgress = false;
modalSpinner = false;
modalErrorMessage: string;
modalTitle = 'There was error while saving your workbasket';
hasChanges = false;
badgeMessage = '';
private workbasketSubscription: Subscription;
private routeSubscription: Subscription;
@ -29,17 +41,20 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
constructor(private workbasketService: WorkbasketService,
private alertService: AlertService,
private route: ActivatedRoute,
private router: Router, ) {
private router: Router,
private errorModalService: ErrorModalService,
private savingWorkbasket: SavingWorkbasketService) {
this.allTypes = IconTypeComponent.allTypes;
}
ngOnInit() {
this.workbasketClone = { ...this.workbasket };
this.routeSubscription = this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
this.checkForChanges();
}
});
if (this.action === ACTION.CREATE) {
this.badgeMessage = 'Creating new workbasket';
} else if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying workbasket: ${this.workbasket.key}`;
}
}
selectType(type: ICONTYPES) {
@ -48,6 +63,11 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
onSave() {
this.beforeRequest();
if (!this.workbasket.workbasketId) {
this.postNewWorkbasket();
return true;
}
this.workbasketSubscription = (this.workbasketService.updateWorkbasket(this.workbasket._links.self.href, this.workbasket).subscribe(
workbasketUpdated => {
this.afterRequest();
@ -57,7 +77,7 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
},
error => {
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() {
this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields'))
this.workbasket = { ...this.workbasketClone };
this.hasChanges = false;
}
private beforeRequest() {
this.requestInProgress = true;
this.modalSpinner = true;
this.modalErrorMessage = undefined;
}
private afterRequest() {
@ -79,13 +99,34 @@ export class WorkbasketInformationComponent implements OnInit, OnDestroy {
}
private checkForChanges() {
if (!Workbasket.equals(this.workbasket, this.workbasketClone)) {
this.openDiscardChangesModal();
}
private postNewWorkbasket() {
this.addDateToWorkbasket();
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" >
<taskana-spinner [isRunning]="requestInProgress" class = "centered-horizontally"></taskana-spinner>
<taskana-no-access *ngIf="!requestInProgress && (!hasPermission || !workbasket && selectedId)" ></taskana-no-access>
<div id ="workbasket-details" *ngIf="workbasket && !requestInProgress">
<div class="container-scrollable">
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<taskana-no-access *ngIf="!requestInProgress && (!hasPermission && !workbasket || !workbasket && selectedId)"></taskana-no-access>
<div id="workbasket-details" *ngIf="workbasket && !requestInProgress">
<ul class="nav nav-tabs" role="tablist">
<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 role="presentation" class="active">
<a href="#work-baskets" aria-controls="work baskets" role="tab" data-toggle="tab" aria-expanded="true">Information</a>
</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>
</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>
</li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="work-baskets">
<taskana-workbasket-information [workbasket]="workbasket"></taskana-workbasket-information>
<div role="tabpanel" class="tab-pane fade in active" id="work-baskets">
<taskana-workbasket-information [workbasket]="workbasket" [action]="action"></taskana-workbasket-information>
</div>
<div role="tabpanel" class="tab-pane inactive" id="access-items">
<taskana-workbasket-access-items [workbasket]="workbasket"></taskana-workbasket-access-items>
<div role="tabpanel" class="tab-pane fade" id="access-items">
<taskana-workbasket-access-items [workbasket]="workbasket" [action]="action"></taskana-workbasket-access-items>
</div>
<div role="tabpanel" class="tab-pane inactive" id="distribution-targets">
<taskana-workbaskets-distribution-targets [workbasket]="workbasket"></taskana-workbaskets-distribution-targets>
<div role="tabpanel" class="tab-pane fade" id="distribution-targets">
<taskana-workbaskets-distribution-targets [workbasket]="workbasket" [action]="action"></taskana-workbaskets-distribution-targets>
</div>
</div>
</div>
<taskana-alert></taskana-alert>
</div>

View File

@ -21,4 +21,11 @@
& > p{
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 { Observable } from 'rxjs/Observable';
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 { RemoveNoneTypePipe } from '../../pipes/remove-none-type';
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 { PermissionService } from '../../services/permission.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 { FormsModule } from '@angular/forms';
@ -31,101 +33,116 @@ import { HttpModule } from '@angular/http';
import { WorkbasketSummary } from '../../model/workbasket-summary';
import { WorkbasketSummaryResource } from '../../model/workbasket-summary-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({
selector: 'taskana-filter',
template: ''
selector: 'taskana-filter',
template: ''
})
export class FilterComponent {
@Input()
target: string;
@Input()
target: string;
}
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
describe('WorkbasketDetailsComponent', () => {
let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>;
let debugElement;
let masterAndDetailService;
let workbasketService;
const workbasket = new Workbasket('1', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' }, { 'href': 'someurl' }, { 'href': 'someurl' }));
let component: WorkbasketDetailsComponent;
let fixture: ComponentFixture<WorkbasketDetailsComponent>;
let debugElement;
let masterAndDetailService;
let workbasketService;
let router;
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(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService, AlertService]
})
.compileComponents();
}));
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule.withRoutes(routes), FormsModule, AngularSvgIconModule, HttpClientModule, HttpModule],
declarations: [WorkbasketDetailsComponent, NoAccessComponent, WorkbasketInformationComponent, SpinnerComponent,
IconTypeComponent, MapValuesPipe, RemoveNoneTypePipe, AlertComponent, GeneralMessageModalComponent, AccessItemsComponent,
DistributionTargetsComponent, FilterComponent, DualListComponent, DummyDetailComponent, SelectWorkBasketPipe],
providers: [WorkbasketService, MasterAndDetailService, PermissionService,
AlertService, ErrorModalService, SavingWorkbasketService]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketDetailsComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
masterAndDetailService = TestBed.get(MasterAndDetailService);
workbasketService = TestBed.get(WorkbasketService);
spyOn(masterAndDetailService, 'getShowDetail').and.callFake(() => { return Observable.of(true) })
spyOn(workbasketService, 'getSelectedWorkBasket').and.callFake(() => { return Observable.of('id1') })
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource(
{
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
})
beforeEach(() => {
fixture = TestBed.createComponent(WorkbasketDetailsComponent);
component = fixture.componentInstance;
component.hasPermission = false;
debugElement = fixture.debugElement.nativeElement;
router = TestBed.get(Router)
fixture.detectChanges();
masterAndDetailService = TestBed.get(MasterAndDetailService);
workbasketService = TestBed.get(WorkbasketService);
spyOn(masterAndDetailService, 'getShowDetail').and.callFake(() => { return Observable.of(true) })
spyOn(workbasketService, 'getSelectedWorkBasket').and.callFake(() => { return Observable.of('id1') })
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource(
{
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })))
})
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) })
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => {
return Observable.of(new WorkbasketAccessItemsResource(
{ 'accessItems': new Array<WorkbasketAccessItems>() }, new Links({ 'href': 'url' })))
})
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource(
{ 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' })))
})
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => { return Observable.of(workbasket) })
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => {
return Observable.of(new WorkbasketAccessItemsResource(
{ 'accessItems': new Array<WorkbasketAccessItems>() }, new Links({ 'href': 'url' })))
})
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => {
return Observable.of(new WorkbasketSummaryResource(
{ 'workbaskets': new Array<WorkbasketSummary>() }, new Links({ 'href': 'url' })))
})
});
});
afterEach(() => {
document.body.removeChild(debugElement);
});
afterEach(() => {
document.body.removeChild(debugElement);
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should has created taskana-no-access if workbasket is not defined', () => {
expect(component.workbasket).toBeUndefined();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
});
it('should has created taskana-no-access if workbasket is not defined and hasPermission is false', () => {
expect(component.workbasket).toBeUndefined();
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', () => {
expect(component.workbasket).toBeUndefined();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
it('should has created taskana-workbasket-details if workbasket is defined and taskana-no-access should dissapear', () => {
expect(component.workbasket).toBeUndefined();
component.hasPermission = false;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeTruthy();
component.workbasket = workbasket;
fixture.detectChanges();
component.workbasket = workbasket;
fixture.detectChanges();
expect(debugElement.querySelector('taskana-no-access')).toBeFalsy();
expect(debugElement.querySelector('taskana-workbasket-information')).toBeTruthy();
expect(debugElement.querySelector('taskana-no-access')).toBeFalsy();
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 { WorkbasketSummary } from '../../model/workbasket-summary';
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({
selector: 'taskana-workbasket-details',
@ -17,10 +21,12 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
workbasket: Workbasket;
selectedId = -1;
workbasketCopy: Workbasket;
selectedId: string = undefined;
showDetail = false;
hasPermission = true;
requestInProgress = false;
action: string;
private workbasketSelectedSubscription: Subscription;
private workbasketSubscription: Subscription;
@ -32,22 +38,37 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private route: ActivatedRoute,
private router: Router,
private masterAndDetailService: MasterAndDetailService,
private permissionService: PermissionService) { }
private permissionService: PermissionService,
private errorModalService: ErrorModalService) { }
ngOnInit() {
this.workbasketSelectedSubscription = this.service.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
this.workbasket = undefined;
this.requestInProgress = true;
this.getWorkbasketInformation(workbasketIdSelected);
});
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 !== '') {
this.selectedId = id;
this.service.selectWorkBasket(id);
this.selectWorkbasket(id);
}
});
@ -69,8 +90,25 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
this.router.navigate(['./'], { relativeTo: this.route.parent });
}
private getWorkbasketInformation(workbasketIdSelected: string) {
this.service.getWorkBasketsSummary().subscribe((workbasketSummary: WorkbasketSummaryResource) => {
private selectWorkbasket(id: string) {
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);
if (workbasketSummarySelected && workbasketSummarySelected._links) {
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">
<ul id="wb-list-container" class="list-group footer-space">
<li id="wb-action-toolbar" class="list-group-item tab-align">
<div class="row">
<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-workbasket-list-toolbar [workbaskets]="workbaskets" (performFilter)="performFilter($event)" (performSorting)="performSorting ($event)"
[(workbasketIdSelected)]="selectedId"></taskana-workbasket-list-toolbar>
<taskana-spinner [isRunning]="requestInProgress" class="centered-horizontally"></taskana-spinner>
<li class="list-group-item" *ngFor="let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId"
type="text" (click)="selectWorkbasket(workbasket.workbasketId)">
<div class="row">
@ -44,8 +12,10 @@
</dt>
</dl>
<dl class="col-xs-10">
<dt>{{workbasket.name}} ({{workbasket.key}}) </dt>
<dd>{{workbasket.description}}</dd>
<dt>{{workbasket.name}},
<i>{{workbasket.key}} </i>
</dt>
<dd>{{workbasket.description}} &nbsp;</dd>
<dd>{{workbasket.owner}} &nbsp;</dd>
</dl>
</div>

View File

@ -16,7 +16,13 @@ a > label{
width: 100%;
}
.tab-align{
border-bottom: 1px solid #ddd;
padding-bottom: 12px;
dt > i {
font-weight: normal;
}
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 { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { WorkbasketListComponent } from './workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
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 { WorkbasketSummaryResource } from '../../model/workbasket-summary-resource';
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({
selector: 'taskana-dummy-detail',
@ -55,7 +58,7 @@ describe('WorkbasketListComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent,
declarations: [WorkbasketListComponent, DummyDetailComponent, SpinnerComponent, FilterComponent, WorkbasketListToolbarComponent,
RemoveNoneTypePipe, IconTypeComponent, SortComponent, MapValuesPipe],
imports: [
AngularSvgIconModule,
@ -63,7 +66,7 @@ describe('WorkbasketListComponent', () => {
HttpClientModule,
RouterTestingModule.withRoutes(routes)
],
providers: [WorkbasketService]
providers: [WorkbasketService, ErrorModalService, RequestInProgressService, AlertService]
})
.compileComponents();
@ -102,19 +105,19 @@ describe('WorkbasketListComponent', () => {
expect(debugElement.querySelector('#wb-list-container')).toBeDefined();
expect(debugElement.querySelector('#collapsedMenufilterWb')).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.', () => {
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
expect(debugElement.querySelectorAll('#wb-list-container > li')[1].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').length).toBe(2);
expect(debugElement.querySelectorAll('#wb-list-container > li')[0].getAttribute('class')).toBe('list-group-item');
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', () => {
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');
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');
});

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

View File

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