TSK-179 Create filtering component and apply filtering on workbasket list.

This commit is contained in:
Martin Rojas Miguel Angel 2018-02-20 12:45:36 +01:00 committed by Holger Hagen
parent 2652810461
commit 274fa67a53
14 changed files with 366 additions and 196 deletions

View File

@ -9,7 +9,7 @@
"build:prod": "ng build --environment=prod --no-progress", "build:prod": "ng build --environment=prod --no-progress",
"test": "./node_modules/.bin/karma start --single-run --browsers Firefox", "test": "./node_modules/.bin/karma start --single-run --browsers Firefox",
"test-phantom": "./node_modules/.bin/karma start --single-run --browsers PhantomJS", "test-phantom": "./node_modules/.bin/karma start --single-run --browsers PhantomJS",
"test:watch": "./node_modules/.bin/karma start --browsers Firefox", "test:watch": "./node_modules/.bin/karma start --browsers Chrome",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e" "e2e": "ng e2e"
}, },

View File

@ -26,6 +26,7 @@ import { WorkbasketDetailsComponent } from './workbasket/details/workbasket-deta
import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component'; import { WorkbasketInformationComponent } from './workbasket/details/information/workbasket-information.component';
import { NoAccessComponent } from './workbasket/noAccess/no-access.component'; import { NoAccessComponent } from './workbasket/noAccess/no-access.component';
import { SpinnerComponent } from './shared/spinner/spinner.component'; import { SpinnerComponent } from './shared/spinner/spinner.component';
import { FilterComponent } from './shared/filter/filter.component';
//Shared //Shared
import { MasterAndDetailComponent} from './shared/masterAndDetail/master-and-detail.component'; import { MasterAndDetailComponent} from './shared/masterAndDetail/master-and-detail.component';
@ -56,7 +57,7 @@ const MODULES = [
HttpClientModule HttpClientModule
]; ];
const COMPONENTS = [ const DECLARATIONS = [
AppComponent, AppComponent,
WorkbasketListComponent, WorkbasketListComponent,
CategorieslistComponent, CategorieslistComponent,
@ -70,10 +71,12 @@ const COMPONENTS = [
WorkbasketInformationComponent, WorkbasketInformationComponent,
NoAccessComponent, NoAccessComponent,
SpinnerComponent, SpinnerComponent,
FilterComponent,
MapValuesPipe MapValuesPipe
]; ];
@NgModule({ @NgModule({
declarations: COMPONENTS, declarations: DECLARATIONS,
imports: MODULES, imports: MODULES,
providers: [ providers: [
WorkbasketService, WorkbasketService,

View File

@ -10,8 +10,8 @@ import { Subject } from 'rxjs/Subject';
//sort direction //sort direction
export enum Direction{ export enum Direction{
ASC = "asc", ASC = 'asc',
DESC = "desc" DESC = 'desc'
}; };
@Injectable() @Injectable()
@ -22,20 +22,21 @@ export class WorkbasketService {
constructor(private httpClient: HttpClient) { } constructor(private httpClient: HttpClient) { }
//Sorting //Sorting
readonly SORTBY="sortBy"; readonly SORTBY='sortBy';
readonly ORDER="order"; readonly ORDER='order';
//Filtering //Filtering
readonly NAME="name"; readonly NAME='name';
readonly NAMELIKE="nameLike"; readonly NAMELIKE='nameLike';
readonly DESCLIKE="descLike"; readonly DESCLIKE='descLike';
readonly OWNER="owner"; readonly OWNER='owner';
readonly OWNERLIKE="ownerLike"; readonly OWNERLIKE='ownerLike';
readonly TYPE="type"; readonly TYPE='type';
readonly KEY="key"; readonly KEY='key';
readonly KEYLIKE='keyLike';
//Access //Access
readonly REQUIREDPERMISSION="requiredPermission"; readonly REQUIREDPERMISSION='requiredPermission';
httpOptions = { httpOptions = {
headers: new HttpHeaders({ headers: new HttpHeaders({
@ -53,10 +54,12 @@ export class WorkbasketService {
owner:string = undefined, owner:string = undefined,
ownerLike:string = undefined, ownerLike:string = undefined,
type:string = undefined, type:string = undefined,
key:string = undefined,
keyLike:string = undefined,
requiredPermission: string = undefined): Observable<WorkbasketSummary[]> { requiredPermission: string = undefined): Observable<WorkbasketSummary[]> {
return this.httpClient.get<WorkbasketSummary[]>(`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(sortBy, order, name, return this.httpClient.get<WorkbasketSummary[]>(`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, requiredPermission)}`,this.httpOptions) nameLike, descLike, owner, ownerLike, type, key, keyLike, requiredPermission)}`,this.httpOptions)
} }
@ -65,31 +68,31 @@ export class WorkbasketService {
} }
createWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> { createWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> {
return this.httpClient.post<WorkbasketSummary>(environment.taskanaRestUrl + "/v1/workbaskets", workbasket, this.httpOptions); return this.httpClient.post<WorkbasketSummary>(environment.taskanaRestUrl + '/v1/workbaskets', workbasket, this.httpOptions);
} }
deleteWorkbasket(id: string) { deleteWorkbasket(id: string) {
return this.httpClient.delete(environment.taskanaRestUrl + "/v1/workbaskets/" + id, this.httpOptions); return this.httpClient.delete(environment.taskanaRestUrl + '/v1/workbaskets/' + id, this.httpOptions);
} }
updateWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> { updateWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> {
return this.httpClient.put<WorkbasketSummary>(environment.taskanaRestUrl + "/v1/workbaskets/" + workbasket.workbasketId, workbasket, this.httpOptions); return this.httpClient.put<WorkbasketSummary>(environment.taskanaRestUrl + '/v1/workbaskets/' + workbasket.workbasketId, workbasket, this.httpOptions);
} }
getAllWorkBasketAuthorizations(id: String): Observable<WorkbasketAuthorization[]> { getAllWorkBasketAuthorizations(id: String): Observable<WorkbasketAuthorization[]> {
return this.httpClient.get<WorkbasketAuthorization[]>(environment.taskanaRestUrl + "/v1/workbaskets/" + id + "/authorizations", this.httpOptions); return this.httpClient.get<WorkbasketAuthorization[]>(environment.taskanaRestUrl + '/v1/workbaskets/' + id + '/authorizations', this.httpOptions);
} }
createWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> { createWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> {
return this.httpClient.post<WorkbasketAuthorization>(environment.taskanaRestUrl + "/v1/workbaskets/authorizations", workbasketAuthorization, this.httpOptions); return this.httpClient.post<WorkbasketAuthorization>(environment.taskanaRestUrl + '/v1/workbaskets/authorizations', workbasketAuthorization, this.httpOptions);
} }
updateWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> { updateWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> {
return this.httpClient.put<WorkbasketAuthorization>(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, workbasketAuthorization, this.httpOptions) return this.httpClient.put<WorkbasketAuthorization>(environment.taskanaRestUrl + '/v1/workbaskets/authorizations/' + workbasketAuthorization.id, workbasketAuthorization, this.httpOptions)
} }
deleteWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization) { deleteWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization) {
return this.httpClient.delete(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, this.httpOptions); return this.httpClient.delete(environment.taskanaRestUrl + '/v1/workbaskets/authorizations/' + workbasketAuthorization.id, this.httpOptions);
} }
//Service extras //Service extras
@ -106,11 +109,13 @@ export class WorkbasketService {
name: string, name: string,
nameLike: string, nameLike: string,
descLike: string, descLike: string,
owner:string, owner: string,
ownerLike:string, ownerLike: string,
type:string, type: string,
key: string,
keyLike: string,
requiredPermission: string): string{ requiredPermission: string): string{
let query: string = "?"; let query: string = '?';
query += sortBy? `${this.SORTBY}=${sortBy}&`:''; query += sortBy? `${this.SORTBY}=${sortBy}&`:'';
query += order? `${this.ORDER}=${order}&`:''; query += order? `${this.ORDER}=${order}&`:'';
query += name? `${this.NAME}=${name}&`:''; query += name? `${this.NAME}=${name}&`:'';
@ -119,6 +124,8 @@ export class WorkbasketService {
query += owner? `${this.OWNER}=${owner}&`:''; query += owner? `${this.OWNER}=${owner}&`:'';
query += ownerLike? `${this.OWNERLIKE}=${ownerLike}&`:''; query += ownerLike? `${this.OWNERLIKE}=${ownerLike}&`:'';
query += type? `${this.TYPE}=${type}&`:''; query += type? `${this.TYPE}=${type}&`:'';
query += key? `${this.KEY}=${key}&`:'';
query += keyLike? `${this.KEYLIKE}=${keyLike}&`:'';
query += requiredPermission? `${this.REQUIREDPERMISSION}=${requiredPermission}&`:''; query += requiredPermission? `${this.REQUIREDPERMISSION}=${requiredPermission}&`:'';
if(query.lastIndexOf('&') === query.length-1){ if(query.lastIndexOf('&') === query.length-1){

View File

@ -0,0 +1,51 @@
<div type="text" id="{{target}}" class="list-group-seach collapse">
<div class="row">
<div class="dropdown col-xs-2">
<button class="btn btn-default {{!filter.type? 'glyphicon glyphicon-asterisk blue' : '' }}" type="button" id="dropdownMenufilter" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<svg-icon *ngIf="filter.type" class="small blue" src="./assets/icons/{{(filter.type === 'PERSONAL')? 'user.svg': (filter.type === 'GROUP')? 'users.svg' :''}}"></svg-icon>
</button>
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<button type="button" (click)="selectType(-1)" placeholder="individual" class="btn btn-default glyphicon glyphicon-asterisk btn-users-list blue" data-toggle="tooltip" title="all">
</button>
</li>
<li>
<button type="button" (click)="selectType(0)" placeholder="individual" class="btn btn-default btn-users-list" data-toggle="tooltip" title="individual">
<svg-icon class="small blue" src="./assets/icons/user.svg"></svg-icon>
</button>
</li>
<li>
<button type="button" (click)="selectType(1)" placeholder="group" data-toggle="tooltip" title="group" class="btn btn-default btn-users-list">
<svg-icon class="small blue" src="./assets/icons/users.svg"></svg-icon>
</button>
</li>
</ul>
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]= "filter.name" (keyup.enter)="search()" class="form-control" id="wb-display-name-filter" placeholder="Filter name">
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]= "filter.key" (keyup.enter)="search()" class="form-control" id="wb-display-key-filter" placeholder="Filter key">
</div>
<button (click)="clear()" type="button"
class="btn btn-default glyphicon glyphicon-remove-circle blue pull-right margin-right" data-toggle="tooltip" title="Clear">
</button>
</div>
<div class="row">
<div class="col-xs-2">
</div>
<div class="col-xs-8">
<input type="text" [(ngModel)]= "filter.description" (keyup.enter)="search()" class="form-control" id="wb-display-description-filter" placeholder="Filter by description">
</div>
</div>
<div class="row">
<div class="col-xs-2">
</div>
<div class="col-xs-8">
<input type="text" [(ngModel)]= "filter.owner" (keyup.enter)="search()" class="form-control" id="wb-display-task-owner-filter" placeholder="Filter by Task owner">
</div>
<button (click)="search()" type="button"
class="btn btn-default glyphicon glyphicon-search blue pull-right margin-right" data-toggle="tooltip" title="Search">
</button>
</div>
</div>

View File

@ -0,0 +1,7 @@
.dropdown-menu-users {
&>li{
margin-bottom: 5px;
}
margin-left: 15px;
}

View File

@ -0,0 +1,73 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { FilterComponent, FilterModel } from './filter.component';
describe('FilterComponent', () => {
let component: FilterComponent,
fixture: ComponentFixture<FilterComponent>,
debugElement: any;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FilterComponent ],
imports: [AngularSvgIconModule, FormsModule, HttpClientModule, HttpModule ]
})
.compileComponents();
fixture = TestBed.createComponent(FilterComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
}));
afterEach(() => {
document.body.removeChild(debugElement);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create a component with id target', () => {
expect(debugElement.querySelector('#some-id')).toBeNull();
component.target = 'some-id'
fixture.detectChanges();
expect(debugElement.querySelector('#some-id')).toBeDefined();
});
it('should have filter by: name, description, key, owner and type defined', () => {
expect(debugElement.querySelector('#wb-display-name-filter')).toBeDefined();
expect(debugElement.querySelector('#wb-display-description-filter')).toBeDefined();
expect(debugElement.querySelector('#wb-display-key-filter')).toBeDefined();
expect(debugElement.querySelector('#wb-display-owner-filter')).toBeDefined();
expect(debugElement.querySelector('#wb-display-type-filter')).toBeDefined();
});
it('should be able to clear all fields after pressing clear button', () => {
component.filter = new FilterModel('a','a','a','a');
debugElement.querySelector('[title="Clear"]').click();
expect(component.filter.name).toBe('');
expect(component.filter.description).toBe('');
expect(component.filter.owner).toBe('');
expect(component.filter.type).toBe('');
});
it('should be able to select a type and return it based on a number', () => {
expect(component).toBeTruthy();
});
it('should be able to emit a filter after clicking on search button', (done) => {
component.filter = new FilterModel('a', 'name1', 'a', 'a');
component.performFilter.subscribe(filter => {
expect(filter.name).toBe('name1');
done();
})
debugElement.querySelector('[title="Search"]').click();
});
});

View File

@ -0,0 +1,45 @@
import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
export class FilterModel {
type:string;
name:string;
description:string;
owner:string;
constructor(type:string = '', name:string = '', description:string = '', owner:string = ''){
this.type = type;
this.name = name;
this.description= description;
this.owner = owner;
}
}
@Component({
selector: 'taskana-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.scss']
})
export class FilterComponent{
constructor() { }
filter: FilterModel = new FilterModel();
@Input()
target:string;
@Output()
performFilter = new EventEmitter<FilterModel>();
selectType(type: number){
this.filter.type = type === 0 ? 'PERSONAL': type === 1? 'GROUP': '';
}
clear(){
this.filter = new FilterModel();
}
search(){
this.performFilter.emit(this.filter);
}
}

View File

@ -1,6 +1,6 @@
<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>
<app-no-access *ngIf="!hasPermission" ></app-no-access> <app-no-access *ngIf="!hasPermission || !workbasket && selectedId" ></app-no-access>
<div id ="workbasket-details" class="workbasket-details" *ngIf="workbasket"> <div id ="workbasket-details" class="workbasket-details" *ngIf="workbasket">
<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">

View File

@ -13,7 +13,7 @@ import { Subscription } from 'rxjs';
}) })
export class WorkbasketDetailsComponent implements OnInit { export class WorkbasketDetailsComponent implements OnInit {
selectedId: number = -1;
workbasket: Workbasket; workbasket: Workbasket;
workbasketClone: Workbasket; workbasketClone: Workbasket;
showDetail: boolean = false; showDetail: boolean = false;
@ -46,8 +46,10 @@ export class WorkbasketDetailsComponent implements OnInit {
}); });
this.routeSubscription = this.route.params.subscribe(params => { this.routeSubscription = this.route.params.subscribe(params => {
if( params['id'] && params['id'] !== '') { let id = params['id'];
this.service.selectWorkBasket( params['id']); if( id && id !== '') {
this.selectedId = id;
this.service.selectWorkBasket(id);
} }
}); });

View File

@ -2,7 +2,7 @@
<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"> <li id="wb-action-toolbar" class="list-group-item">
<div class="row"> <div class="row">
<div class="col-xs-9 mod-col-9"> <div class="col-xs-9">
<button type="button" data-toggle="tooltip" title="Add" class="btn btn-default"> <button type="button" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green" aria-hidden="true"></span> <span class="glyphicon glyphicon-plus green" aria-hidden="true"></span>
</button> </button>
@ -18,68 +18,48 @@
<button type="button" data-toggle="tooltip" title="Remove" class="btn btn-default remove"> <button type="button" data-toggle="tooltip" title="Remove" class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span> <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button> </button>
</div>
<div class="clearfix btn-group">
<button class="btn btn-default collapsed" type="button" id="collapsedMenuSearchWb" data-toggle="collapse" data-target="#wb-search-bar" aria-expanded="false">
<span class="glyphicon glyphicon-filter blue wb-search-toggle"></span>
</button>
</div> </div>
<div class="dropdown clearfix btn-group"> <div class = "pull-right margin-right">
<button class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <div class="dropdown clearfix btn-group">
<span class="glyphicon {{sortDirection === 'asc'? 'glyphicon-sort-by-attributes-alt' : 'glyphicon-sort-by-attributes' }} blue" <button class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
data-toggle= "tooltip" title="{{sortDirection === 'asc'? 'A-Z' : 'Z-A' }}"></span> <span class="glyphicon {{sortDirection === 'asc'? 'glyphicon-sort-by-attributes-alt' : 'glyphicon-sort-by-attributes' }} blue"
</button> data-toggle= "tooltip" title="{{sortDirection === 'asc'? 'A-Z' : 'Z-A' }}"></span>
<ul class="dropdown-menu dropdown-menu-right sortby-dropdown popup" aria-labelledby="sortingDropdown"> </button>
<li> <ul class="dropdown-menu dropdown-menu-right sortby-dropdown popup" aria-labelledby="sortingDropdown">
<div class="col-xs-6"> <li>
<h5>Sort By</h5> <div class="col-xs-6">
</div> <h5>Sort By</h5>
<button id="sort-by-direction-asc" type="button" (click)="changeOrder('asc')" data-toggle="tooltip" title="A-Z" class="btn btn-default {{sortDirection === 'asc'? 'selected' : '' }}"> </div>
<span class="glyphicon glyphicon-sort-by-attributes-alt blue" aria-hidden="true"></span> <button id="sort-by-direction-asc" type="button" (click)="changeOrder('asc')" data-toggle="tooltip" title="A-Z" class="btn btn-default {{sortDirection === 'asc'? 'selected' : '' }}">
</button> <span class="glyphicon glyphicon-sort-by-attributes-alt blue" aria-hidden="true"></span>
<button id= "sort-by-direction-desc" type="button" (click)="changeOrder('desc')" data-toggle="tooltip" title="Z-A" class="btn btn-default {{sortDirection === 'desc'? 'selected' : '' }}" > </button>
<span class="glyphicon glyphicon-sort-by-attributes blue" aria-hidden="true"></span> <button id= "sort-by-direction-desc" type="button" (click)="changeOrder('desc')" data-toggle="tooltip" title="Z-A" class="btn btn-default {{sortDirection === 'desc'? 'selected' : '' }}" >
</button> <span class="glyphicon glyphicon-sort-by-attributes blue" aria-hidden="true"></span>
</li> </button>
<li role="separator" class="divider"></li> </li>
<li id="sort-by-{{sortingField.key}}"(click)="changeSortBy(sortingField.key)" *ngFor="let sortingField of sortingFields | mapValues"> <li role="separator" class="divider"></li>
<a> <li id="sort-by-{{sortingField.key}}"(click)="changeSortBy(sortingField.key)" *ngFor="let sortingField of sortingFields | mapValues">
<label> <a>
<span class="glyphicon {{sortBy === sortingField.key? 'glyphicon-check': 'glyphicon-unchecked'}} blue" aria-hidden="true"></span> <label>
{{sortingField.value}} <span class="glyphicon {{sortBy === sortingField.key? 'glyphicon-check': 'glyphicon-unchecked'}} blue" aria-hidden="true"></span>
</label> {{sortingField.value}}
</a> </label>
</li> </a>
</ul> </li>
</ul>
</div>
<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 wb-filter-toggle"></span>
</button>
</div>
</div> </div>
</div> </div>
<div class = "row">
<taskana-filter target="wb-filter-bar" (performFilter)="performFilter($event)"></taskana-filter>
</div>
</li> </li>
<taskana-spinner [isRunning]="requestInProgress" class = "centered-horizontally"></taskana-spinner> <taskana-spinner [isRunning]="requestInProgress" class = "centered-horizontally"></taskana-spinner>
<li type="text" id="wb-search-bar" class="list-group-seach collapse">
<div class="row">
<dl class="pull-left padding-left-5">
<dt class="dropdown"> <button class="btn btn-default" type="button" id="dropdownMenuSearch" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<svg-icon class="small blue" src="./assets/icons/user.svg"></svg-icon>
</button> <ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<button type="button" placeholder="individual" class="btn btn-default btn-users-list" data-toggle="tooltip" title="individual">
<svg-icon class="small blue" src="./assets/icons/user.svg"></svg-icon>
</button>
</li>
<li>
<button type="button" placeholder="multiple" data-toggle="tooltip" title="multiple" class="btn btn-default btn-users-list">
<svg-icon class="small blue" src="./assets/icons/users.svg"></svg-icon>
</button>
</li>
</ul></dt>
</dl>
<dl class="col-xs-10">
<dt><input type="text" class="form-control" id="wb-display-name-search" placeholder="Filter by name"> </dt>
<dt><input type="text" class="form-control" id="wb-display-description-search" placeholder="Filter by description"> </dt>
<dt><input type="text" class="form-control" id="wb-display-task-owner-search" placeholder="Filter by Task owner"> </dt>
</dl>
</div>
</li>
<li class="list-group-item" *ngFor= "let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId" type="text" (click) ="selectWorkbasket(workbasket.workbasketId)" [routerLink]="[ {outlets: { detail: [workbasket.workbasketId] } }]"> <li class="list-group-item" *ngFor= "let workbasket of workbaskets" [class.active]="workbasket.workbasketId == selectedId" type="text" (click) ="selectWorkbasket(workbasket.workbasketId)" [routerLink]="[ {outlets: { detail: [workbasket.workbasketId] } }]">
<div class="row"> <div class="row">
<dl class="col-xs-1"> <dl class="col-xs-1">

View File

@ -1,7 +1,3 @@
.padding-left-5 {
padding-left:5px;
}
.workbasket-list-full-height{ .workbasket-list-full-height{
height: calc(100vh - 55px); height: calc(100vh - 55px);
} }

View File

@ -11,6 +11,7 @@ import { RouterTestingModule } from '@angular/router/testing';
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 { MapValuesPipe } from '../../pipes/map-values.pipe'; import { MapValuesPipe } from '../../pipes/map-values.pipe';
import { FilterModel } from '../../shared/filter/filter.component';
@Component({ @Component({
selector: 'dummy-detail', selector: 'dummy-detail',
@ -20,6 +21,14 @@ export class DummyDetailComponent {
} }
@Component({
selector: 'taskana-filter',
template: ''
})
export class FilterComponent {
}
const workbasketSummary: WorkbasketSummary[] = [ new WorkbasketSummary("1", "key1", "NAME1", "description 1", "owner 1", "", "", "PERSONAL", "", "", "", ""), const workbasketSummary: WorkbasketSummary[] = [ new WorkbasketSummary("1", "key1", "NAME1", "description 1", "owner 1", "", "", "PERSONAL", "", "", "", ""),
new WorkbasketSummary("2", "key2", "NAME2", "description 2", "owner 2", "", "", "MULTIPLE", "", "", "", "") new WorkbasketSummary("2", "key2", "NAME2", "description 2", "owner 2", "", "", "MULTIPLE", "", "", "", "")
]; ];
@ -38,7 +47,7 @@ describe('WorkbasketListComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ WorkbasketListComponent, DummyDetailComponent, MapValuesPipe, SpinnerComponent], declarations: [ WorkbasketListComponent, DummyDetailComponent, MapValuesPipe, SpinnerComponent, FilterComponent],
imports:[ imports:[
AngularSvgIconModule, AngularSvgIconModule,
HttpModule, HttpModule,
@ -76,23 +85,25 @@ describe('WorkbasketListComponent', () => {
}) })
}); });
it('should have wb-action-toolbar, wb-search-bar, wb-list-container and wb-pagination created in the html',() => { it('should have wb-action-toolbar, wb-search-bar, wb-list-container, wb-pagination, collapsedMenufilterWb and taskana-filter created in the html',() => {
expect(debugElement.querySelector('#wb-action-toolbar')).toBeDefined(); expect(debugElement.querySelector('#wb-action-toolbar')).toBeDefined();
expect(debugElement.querySelector('#wb-search-bar')).toBeDefined(); expect(debugElement.querySelector('#wb-search-bar')).toBeDefined();
expect(debugElement.querySelector('#wb-pagination')).toBeDefined(); expect(debugElement.querySelector('#wb-pagination')).toBeDefined();
expect(debugElement.querySelector('#wb-list-container')).toBeDefined(); expect(debugElement.querySelector('#wb-list-container')).toBeDefined();
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(4); expect(debugElement.querySelector('#collapsedMenufilterWb')).toBeDefined();
expect(debugElement.querySelector('taskana-filter')).toBeDefined();
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
}); });
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(4); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class')).toBe('list-group-item'); expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class')).toBe('list-group-item');
expect(debugElement.querySelectorAll('#wb-list-container > li')[3].getAttribute('class')).toBe('list-group-item active'); expect(debugElement.querySelectorAll('#wb-list-container > li')[2].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')[2].querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/user.svg'); expect(debugElement.querySelectorAll('#wb-list-container > li')[1].querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/user.svg');
expect(debugElement.querySelectorAll('#wb-list-container > li')[3].querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg'); expect(debugElement.querySelectorAll('#wb-list-container > li')[2].querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg');
}); });
it('should have rendered sort by: name, id, description, owner and type', () => { it('should have rendered sort by: name, id, description, owner and type', () => {
@ -104,24 +115,12 @@ describe('WorkbasketListComponent', () => {
}); });
it('should have changed sort direction and perform a request after clicking sort direction buttons', fakeAsync( () => { it('should have performRequest after performFilter is triggered', fakeAsync( () => {
debugElement.querySelector('#sort-by-direction-desc').click(); let type='PERSONAL', name = 'someName', description = 'someDescription', owner = 'someOwner'
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('key', Direction.DESC); let filter = new FilterModel(type, name, description, owner);
debugElement.querySelector('#sort-by-direction-asc').click(); component.performFilter(filter);
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('key', Direction.ASC); expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('key', 'asc', undefined, name, description, undefined, owner, type );
}));
it('should have changed sortBy field and perform a request after clicking on to any sort by rows', fakeAsync( () => {
debugElement.querySelector('#sort-by-key').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('key', jasmine.any(String));
debugElement.querySelector('#sort-by-name').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('name', jasmine.any(String));
debugElement.querySelector('#sort-by-description').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('description', jasmine.any(String));
debugElement.querySelector('#sort-by-owner').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('owner', jasmine.any(String));
debugElement.querySelector('#sort-by-type').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('type', jasmine.any(String));
})); }));
}); });

View File

@ -2,95 +2,105 @@ import { Component, OnInit, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from '../../model/workbasketSummary'; import { WorkbasketSummary } from '../../model/workbasketSummary';
import { WorkbasketService, Direction } from '../../services/workbasketservice.service' import { WorkbasketService, Direction } from '../../services/workbasketservice.service'
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
import { FilterModel } from '../../shared/filter/filter.component'
import { filter } from 'rxjs/operator/filter';
@Component({ @Component({
selector: 'workbasket-list', selector: 'workbasket-list',
outputs: ['selectedWorkbasket'], outputs: ['selectedWorkbasket'],
templateUrl: './workbasket-list.component.html', templateUrl: './workbasket-list.component.html',
styleUrls: ['./workbasket-list.component.scss'] styleUrls: ['./workbasket-list.component.scss']
}) })
export class WorkbasketListComponent implements OnInit { export class WorkbasketListComponent implements OnInit {
public selectedWorkbasket: EventEmitter<WorkbasketSummary> = new EventEmitter(); public selectedWorkbasket: EventEmitter<WorkbasketSummary> = new EventEmitter();
newWorkbasket: WorkbasketSummary; newWorkbasket: WorkbasketSummary;
selectedId: string = undefined; selectedId: string = undefined;
workbaskets : Array<WorkbasketSummary> = []; workbaskets: Array<WorkbasketSummary> = [];
requestInProgress: boolean = false; requestInProgress: boolean = false;
sortBy: string = 'key'; sortBy: string = 'key';
sortDirection: Direction = Direction.ASC; sortDirection: Direction = Direction.ASC;
sortingFields : Map<string, string> = new Map([['name', 'Name'], ['key', 'Key'], ['description', 'Description'], ['owner', 'Owner'], ['type', 'Type']]); sortingFields: Map<string, string> = new Map([['name', 'Name'], ['key', 'Id'], ['description', 'Description'], ['owner', 'Owner'], ['type', 'Type']]);
filterBy: FilterModel = new FilterModel();
private workBasketSummarySubscription: Subscription; private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription; private workbasketServiceSubscription: Subscription;
constructor(private workbasketService: WorkbasketService) { } constructor(private workbasketService: WorkbasketService) { }
ngOnInit() { ngOnInit() {
this.requestInProgress = true; this.requestInProgress = true;
this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => { this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => {
this.workbaskets = resultList; this.workbaskets = resultList;
this.requestInProgress = false; this.requestInProgress = false;
}); });
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe( workbasketIdSelected => { this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
this.selectedId = workbasketIdSelected; this.selectedId = workbasketIdSelected;
}); });
} }
selectWorkbasket(id:string){ selectWorkbasket(id: string) {
this.selectedId = id; this.selectedId = id;
} }
changeOrder(sortDirection: string) { changeOrder(sortDirection: string) {
this.sortDirection = (sortDirection === Direction.ASC)? Direction.ASC: Direction.DESC; this.sortDirection = (sortDirection === Direction.ASC) ? Direction.ASC : Direction.DESC;
this.performRequest(); this.performRequest();
} }
changeSortBy(sortBy: string){ changeSortBy(sortBy: string) {
this.sortBy = sortBy; this.sortBy = sortBy;
this.performRequest(); this.performRequest();
} }
onDelete(workbasket: WorkbasketSummary) { performFilter(filterBy: FilterModel) {
this.workbasketService.deleteWorkbasket(workbasket.workbasketId).subscribe(result => { this.filterBy = filterBy;
var index = this.workbaskets.indexOf(workbasket); this.performRequest();
if (index > -1) { }
this.workbaskets.splice(index, 1);
}
});
}
onAdd() { onDelete(workbasket: WorkbasketSummary) {
this.workbasketService.createWorkbasket(this.newWorkbasket).subscribe(result => { this.workbasketService.deleteWorkbasket(workbasket.workbasketId).subscribe(result => {
this.workbaskets.push(result); var index = this.workbaskets.indexOf(workbasket);
this.onClear(); if (index > -1) {
}); this.workbaskets.splice(index, 1);
} }
});
}
onClear() { onAdd() {
this.newWorkbasket.workbasketId = ""; this.workbasketService.createWorkbasket(this.newWorkbasket).subscribe(result => {
this.newWorkbasket.name = ""; this.workbaskets.push(result);
this.newWorkbasket.description = ""; this.onClear();
this.newWorkbasket.owner = ""; });
} }
getEmptyObject() { onClear() {
return new WorkbasketSummary("", "", "", "", "", "", "", "", "", "", "", ""); this.newWorkbasket.workbasketId = "";
} this.newWorkbasket.name = "";
this.newWorkbasket.description = "";
this.newWorkbasket.owner = "";
}
private performRequest(): void{ getEmptyObject() {
this.requestInProgress = true; return new WorkbasketSummary("", "", "", "", "", "", "", "", "", "", "", "");
this.workbaskets = undefined; }
this.workbasketServiceSubscription.add(this.workbasketService.getWorkBasketsSummary(this.sortBy,this.sortDirection).subscribe(resultList => {
this.workbaskets = resultList;
this.requestInProgress = false;
}));
}
private ngOnDestroy(){ private performRequest(): void {
this.workBasketSummarySubscription.unsubscribe(); this.requestInProgress = true;
this.workbasketServiceSubscription.unsubscribe(); this.workbaskets = undefined;
} this.workbasketServiceSubscription.add(this.workbasketService.getWorkBasketsSummary(this.sortBy, this.sortDirection, undefined,
this.filterBy.name, this.filterBy.description, undefined, this.filterBy.owner,
this.filterBy.type).subscribe(resultList => {
this.workbaskets = resultList;
this.requestInProgress = false;
}));
}
private ngOnDestroy() {
this.workBasketSummarySubscription.unsubscribe();
this.workbasketServiceSubscription.unsubscribe();
}
} }

View File

@ -115,8 +115,9 @@
overflow-x: hidden; overflow-x: hidden;
} }
.margin-right { .margin-right
margin-right: 2px; {
margin-right: 5px;
} }
.no-border-bottom { .no-border-bottom {
@ -190,10 +191,6 @@ li > div.row > dl {
color: crimson; color: crimson;
} }
.dropdown-menu-users >li {
margin-bottom: 5px;
}
.btn-users-list { .btn-users-list {
border: 0px solid transparent; border: 0px solid transparent;
/* this was 1px earlier */ /* this was 1px earlier */