Add Sorting feature to workbasketsummary and changed HTTP client to latest version

This commit is contained in:
Martin Rojas Miguel Angel 2018-02-15 16:46:35 +01:00 committed by Holger Hagen
parent 28e13608b1
commit 8d7b390545
13 changed files with 293 additions and 115 deletions

View File

@ -39,7 +39,7 @@ module.exports = function (config) {
colors: true, colors: true,
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,
browsers: ['Chrome', 'Firefox'], browsers: ['Chrome'],
singleRun: false singleRun: false
}); });
}; };

View File

@ -4,7 +4,6 @@
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { NgModule, } from '@angular/core'; import { NgModule, } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HttpModule, JsonpModule, Http } from '@angular/http';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AlertModule } from 'ngx-bootstrap'; import { AlertModule } from 'ngx-bootstrap';
@ -35,15 +34,19 @@ import { MasterAndDetailComponent} from './shared/masterAndDetail/master-and-det
*/ */
import { WorkbasketService } from './services/workbasketservice.service'; import { WorkbasketService } from './services/workbasketservice.service';
import { MasterAndDetailService } from './services/master-and-detail.service'; import { MasterAndDetailService } from './services/master-and-detail.service';
import { HttpExtensionService } from './services/http-extension.service'; import { HttpClientInterceptor } from './services/http-client-interceptor.service';
import { PermissionService } from './services/permission.service'; import { PermissionService } from './services/permission.service';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
/**
* Pipes
*/
import { MapValuesPipe } from './pipes/map-values.pipe';
const MODULES = [ const MODULES = [
BrowserModule, BrowserModule,
FormsModule, FormsModule,
HttpModule,
JsonpModule,
TabsModule.forRoot(), TabsModule.forRoot(),
TreeModule, TreeModule,
AppRoutingModule, AppRoutingModule,
@ -64,7 +67,8 @@ const COMPONENTS = [
WorkbasketDistributiontargetsComponent, WorkbasketDistributiontargetsComponent,
MasterAndDetailComponent, MasterAndDetailComponent,
WorkbasketInformationComponent, WorkbasketInformationComponent,
NoAccessComponent NoAccessComponent,
MapValuesPipe
]; ];
@NgModule({ @NgModule({
declarations: COMPONENTS, declarations: COMPONENTS,
@ -73,7 +77,12 @@ const COMPONENTS = [
WorkbasketService, WorkbasketService,
MasterAndDetailService, MasterAndDetailService,
PermissionService, PermissionService,
{ provide: Http, useClass: HttpExtensionService } {
provide: HTTP_INTERCEPTORS,
useClass: HttpClientInterceptor,
multi: true
},
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -0,0 +1,17 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({name: 'mapValues'})
export class MapValuesPipe implements PipeTransform {
transform(value: any, args?: any[]): Object[] {
let returnArray = [];
value.forEach((entryVal, entryKey) => {
returnArray.push({
key: entryKey,
value: entryVal
});
});
return returnArray;
}
}

View File

@ -0,0 +1,19 @@
import { TestBed, inject } from '@angular/core/testing';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { HttpClientInterceptor } from './http-client-interceptor.service';
import { PermissionService } from './permission.service';
describe('HttpExtensionService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports:[HttpClientModule, HttpModule],
providers: [HttpClientInterceptor, PermissionService]
});
});
it('should be created', inject([HttpClientInterceptor], (service: HttpClientInterceptor) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { PermissionService } from './permission.service';
@Injectable()
export class HttpClientInterceptor implements HttpInterceptor {
permissionService: PermissionService;
constructor(permissionService: PermissionService) {
this.permissionService = permissionService;
}
intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).do(event => {
setTimeout(() => {this.permissionService.setPermission(true);},1)
}, err => {
if (err instanceof HttpErrorResponse && (err.status === 401 || err.status ===403 )) {
setTimeout(() => {this.permissionService.setPermission(false);},1)
}
});
}
}

View File

@ -1,15 +1,51 @@
import { TestBed, inject } from '@angular/core/testing'; import { TestBed, inject, async } from '@angular/core/testing';
import { WorkbasketService, Direction } from './workbasketservice.service';
import { HttpModule, Http } from '@angular/http';
import { HttpClientModule, HttpClient } from '@angular/common/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { WorkbasketService } from './workbasketservice.service'; describe('WorkbasketService ', () => {
describe('WorkbasketService', () => { var workbasketService, httpClient;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
providers: [WorkbasketService] imports: [HttpClientModule, HttpClientTestingModule],
}); providers: [WorkbasketService, HttpClient, HttpTestingController]
}); });
xit('should be created', inject([WorkbasketService], (service: WorkbasketService) => { httpClient = TestBed.get(HttpClient);
expect(service).toBeTruthy(); workbasketService = TestBed.get(WorkbasketService);
}));
});
describe(' WorkbasketSummary GET method ',() => {
beforeEach(() => {
spyOn(httpClient, 'get').and.returnValue('');
});
it('should have a valid query parameter expression sortBy=key, order=asc as default', () => {
workbasketService.getWorkBasketsSummary();
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=key&order=asc&', jasmine.any(Object));
});
it('should have a valid query parameter expression with sortBy=name and order=desc', () => {
workbasketService.getWorkBasketsSummary('name', Direction.DESC);
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc&', jasmine.any(Object));
});
it('should have a valid query parameter expression with sortBy=name and order=desc and descLike=some description ',() => {
workbasketService.getWorkBasketsSummary('name', Direction.DESC, undefined, undefined, 'some description');
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc&descLike=some description&', jasmine.any(Object));
});
it('should have a valid query parameter expression with sortBy=key, order=asc, descLike=some description and type=group ',() => {
workbasketService.getWorkBasketsSummary('name', Direction.DESC, undefined, undefined, 'some description', undefined, undefined, 'group');
expect(httpClient.get).toHaveBeenCalledWith('http://localhost:8080/v1/workbaskets/?sortBy=name&order=desc&descLike=some description&type=group&', jasmine.any(Object));
});
});
}); });

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { RequestOptions, Headers, Http, Response } from '@angular/http'; import { HttpClientModule, HttpClient, HttpHeaders, } from '@angular/common/http';
import { WorkbasketSummary } from '../model/workbasketSummary'; import { WorkbasketSummary } from '../model/workbasketSummary';
import { Workbasket } from '../model/workbasket'; import { Workbasket } from '../model/workbasket';
import { WorkbasketAuthorization } from '../model/workbasket-authorization'; import { WorkbasketAuthorization } from '../model/workbasket-authorization';
@ -7,57 +7,92 @@ import { environment } from '../../environments/environment';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject'; import { Subject } from 'rxjs/Subject';
//sort direction
export enum Direction{
ASC = "asc",
DESC = "desc"
};
@Injectable() @Injectable()
export class WorkbasketService { export class WorkbasketService {
public workBasketSelected = new Subject<string>(); public workBasketSelected = new Subject<string>();
constructor(private http: Http) { } constructor(private httpClient: HttpClient) { }
//Sorting
readonly SORTBY="sortBy";
readonly ORDER="order";
//Filtering
readonly NAME="name";
readonly NAMELIKE="nameLike";
readonly DESCLIKE="descLike";
readonly OWNER="owner";
readonly OWNERLIKE="ownerLike";
readonly TYPE="type";
readonly KEY="key";
//Access
readonly REQUIREDPERMISSION="requiredPermission";
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Basic VEVBTUxFQURfMTpURUFNTEVBRF8x'
})
};
//REST calls
getWorkBasketsSummary(sortBy: string = this.KEY,
order: string = Direction.ASC,
name: string = undefined,
nameLike: string = undefined,
descLike: string = undefined,
owner:string = undefined,
ownerLike:string = undefined,
type:string = undefined,
requiredPermission: string = undefined): Observable<WorkbasketSummary[]> {
return this.httpClient.get<WorkbasketSummary[]>(`${environment.taskanaRestUrl}/v1/workbaskets/${this.getWorkbasketSummaryQueryParameters(sortBy, order, name,
nameLike, descLike, owner, ownerLike, type, requiredPermission)}`,this.httpOptions)
getWorkBasketsSummary(): Observable<WorkbasketSummary[]> {
return this.http.get(environment.taskanaRestUrl + "/v1/workbaskets", this.createAuthorizationHeader())
.map(res => res.json());
} }
getWorkBasket(id: string): Observable<Workbasket> { getWorkBasket(id: string): Observable<Workbasket> {
return this.http.get(`${environment.taskanaRestUrl}/v1/workbaskets/${id}`, this.createAuthorizationHeader()) return this.httpClient.get<Workbasket>(`${environment.taskanaRestUrl}/v1/workbaskets/${id}`, this.httpOptions);
.map(res => res.json());
} }
createWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> { createWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> {
return this.http.post(environment.taskanaRestUrl + "/v1/workbaskets", workbasket, this.createAuthorizationHeader()) return this.httpClient.post<WorkbasketSummary>(environment.taskanaRestUrl + "/v1/workbaskets", workbasket, this.httpOptions);
.map(res => res.json());
} }
deleteWorkbasket(id: string) { deleteWorkbasket(id: string) {
return this.http.delete(environment.taskanaRestUrl + "/v1/workbaskets/" + id, this.createAuthorizationHeader()) return this.httpClient.delete(environment.taskanaRestUrl + "/v1/workbaskets/" + id, this.httpOptions);
.map(res => res.json());
} }
updateWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> { updateWorkbasket(workbasket: WorkbasketSummary): Observable<WorkbasketSummary> {
return this.http.put(environment.taskanaRestUrl + "/v1/workbaskets/" + workbasket.workbasketId, workbasket, this.createAuthorizationHeader()) return this.httpClient.put<WorkbasketSummary>(environment.taskanaRestUrl + "/v1/workbaskets/" + workbasket.workbasketId, workbasket, this.httpOptions);
.map(res => res.json());
} }
getAllWorkBasketAuthorizations(id: String): Observable<WorkbasketAuthorization[]> { getAllWorkBasketAuthorizations(id: String): Observable<WorkbasketAuthorization[]> {
return this.http.get(environment.taskanaRestUrl + "/v1/workbaskets/" + id + "/authorizations", this.createAuthorizationHeader()) return this.httpClient.get<WorkbasketAuthorization[]>(environment.taskanaRestUrl + "/v1/workbaskets/" + id + "/authorizations", this.httpOptions);
.map(res => res.json());
} }
createWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> { createWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> {
return this.http.post(environment.taskanaRestUrl + "/v1/workbaskets/authorizations", workbasketAuthorization, this.createAuthorizationHeader()) return this.httpClient.post<WorkbasketAuthorization>(environment.taskanaRestUrl + "/v1/workbaskets/authorizations", workbasketAuthorization, this.httpOptions);
.map(res => res.json());
} }
updateWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> { updateWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization): Observable<WorkbasketAuthorization> {
return this.http.put(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, workbasketAuthorization, this.createAuthorizationHeader()) return this.httpClient.put<WorkbasketAuthorization>(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, workbasketAuthorization, this.httpOptions)
.map(res => res.json());
} }
deleteWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization) { deleteWorkBasketAuthorization(workbasketAuthorization: WorkbasketAuthorization) {
return this.http.delete(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, this.createAuthorizationHeader()); return this.httpClient.delete(environment.taskanaRestUrl + "/v1/workbaskets/authorizations/" + workbasketAuthorization.id, this.httpOptions);
} }
//Service extras
selectWorkBasket(id: string){ selectWorkBasket(id: string){
this.workBasketSelected.next(id); this.workBasketSelected.next(id);
} }
@ -66,11 +101,27 @@ export class WorkbasketService {
return this.workBasketSelected.asObservable(); return this.workBasketSelected.asObservable();
} }
private createAuthorizationHeader() { private getWorkbasketSummaryQueryParameters( sortBy: string,
let headers: Headers = new Headers(); order: string,
headers.append("Authorization", "Basic VEVBTUxFQURfMTpURUFNTEVBRF8x"); name: string,
headers.append("content-type", "application/json"); nameLike: string,
descLike: string,
owner:string,
ownerLike:string,
type:string,
requiredPermission: string): string{
let query: string = "?";
query += sortBy? `${this.SORTBY}=${sortBy}&`:'';
query += order? `${this.ORDER}=${order}&`:'';
query += name? `${this.NAME}=${name}&`:'';
query += nameLike? `${this.NAMELIKE}=${nameLike}&`:'';
query += descLike? `${this.DESCLIKE}=${descLike}&`:'';
query += owner? `${this.OWNER}=${owner}&`:'';
query += ownerLike? `${this.OWNERLIKE}=${ownerLike}&`:'';
query += type? `${this.TYPE}=${type}&`:'';
query += requiredPermission? `${this.REQUIREDPERMISSION}=${requiredPermission}&`:'';
return new RequestOptions({ headers: headers });
return query;
} }
} }

View File

@ -25,63 +25,35 @@
</button> </button>
</div> </div>
<div class="dropdown clearfix btn-group"> <div class="dropdown clearfix btn-group">
<button class="btn btn-default" type="button" id="dropdownMenu4" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="glyphicon glyphicon-sort blue dropdown-toggle"></span> <span class="glyphicon {{sortDirection === 'asc'? 'glyphicon-sort-by-attributes-alt' : 'glyphicon-sort-by-attributes' }} blue"
data-toggle= "tooltip" title="{{sortDirection === 'asc'? 'A-Z' : 'Z-A' }}"></span>
</button> </button>
<ul class="dropdown-menu dropdown-menu-right orderby-dropdown popup" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu dropdown-menu-right sortby-dropdown popup" aria-labelledby="sortingDropdown">
<li> <li>
<div class="col-xs-6"> <div class="col-xs-6">
<h5>Order By</h5> <h5>Sort By</h5>
</div> </div>
<button type="button" placeholder="des" class="btn btn-default" data-toggle="tooltip" title="descending"> <button id="sort-by-direction-asc" type="button" (click)="changeOrder('asc')" data-toggle="tooltip" title="A-Z" class="btn btn-default {{sortDirection === 'asc'? 'selected' : '' }}">
<span class="glyphicon glyphicon-sort-by-attributes" aria-hidden="true"></span> <span class="glyphicon glyphicon-sort-by-attributes-alt blue" aria-hidden="true"></span>
</button> </button>
<button type="button" data-toggle="tooltip" title="ascending" class="btn btn-default"> <button id= "sort-by-direction-desc" type="button" (click)="changeOrder('desc')" data-toggle="tooltip" title="Z-A" class="btn btn-default {{sortDirection === 'desc'? 'selected' : '' }}" >
<span class="glyphicon glyphicon-sort-by-attributes-alt" aria-hidden="true"></span> <span class="glyphicon glyphicon-sort-by-attributes blue" aria-hidden="true"></span>
</button> </button>
</li> </li>
<li role="separator" class="divider"></li> <li role="separator" class="divider"></li>
<li class="link-padding"> <li id="sort-by-{{sortingField.key}}"(click)="changeSortBy(sortingField.key)" *ngFor="let sortingField of sortingFields | mapValues">
<a> <a>
<div class="radio"> <label>
<label class="radio-inline active"> <span class="glyphicon {{sortBy === sortingField.key? 'glyphicon-check': 'glyphicon-unchecked'}} blue" aria-hidden="true"></span>
<input type="radio" name="orderRadio" id="nameRadio" value="Name" aria-label="Name">Name {{sortingField.value}}
</label> </label>
</div>
</a>
</li>
<li>
<a>
<div class="radio">
<label class="radio-inline">
<input type="radio" name="orderRadio" id="descriptionRadio" value="Description" aria-label="Description">Description
</label>
</div>
</a>
</li>
<li>
<a>
<div class="radio">
<label class="radio-inline">
<input type="radio" name="orderRadio" id="ownerRadio" value="Owner" aria-label="Name">Owner
</label>
</div>
</a>
</li>
<li>
<a>
<div class="radio">
<label class="radio-inline">
<input type="radio" name="orderRadio" id="typeRadio" value="Type" aria-label="Name">Type
</label>
</div>
</a> </a>
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
</li> </li>
<li type="text" id="wb-search-bar" class="list-group-seach collapse"> <li type="text" id="wb-search-bar" class="list-group-seach collapse">
<div class="row"> <div class="row">
<dl class="pull-left padding-left-5"> <dl class="pull-left padding-left-5">

View File

@ -14,3 +14,12 @@
border-left: none; border-left: none;
border-right: none; border-right: none;
} }
a > label{
height: 2em;
width: 100%;
}
.sortby-dropdown {
min-width: 200px;
}

View File

@ -4,11 +4,12 @@ import { WorkbasketListComponent } from './workbasket-list.component';
import { AngularSvgIconModule } from 'angular-svg-icon'; import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import { HttpClientModule } from '@angular/common/http';
import { WorkbasketSummary } from '../../model/workbasketSummary'; import { WorkbasketSummary } from '../../model/workbasketSummary';
import { WorkbasketService } from '../../services/workbasketservice.service'; import { WorkbasketService, Direction } from '../../services/workbasketservice.service';
import { HttpModule } from '@angular/http'; import { HttpModule } from '@angular/http';
import { Router, Routes } from '@angular/router'; import { Router, Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs/Observable'; import { Observable } from 'rxjs/Observable';
import { MapValuesPipe } from '../../pipes/map-values.pipe';
@Component({ @Component({
selector: 'dummy-detail', selector: 'dummy-detail',
@ -36,7 +37,7 @@ describe('WorkbasketListComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ WorkbasketListComponent, DummyDetailComponent], declarations: [ WorkbasketListComponent, DummyDetailComponent, MapValuesPipe],
imports:[ imports:[
AngularSvgIconModule, AngularSvgIconModule,
@ -75,23 +76,52 @@ describe('WorkbasketListComponent', () => {
}) })
}); });
it('should have wb-action-toolbar, wb-search-bar, wb-list-container and wb-pagination created in the html', fakeAsync( () => { it('should have wb-action-toolbar, wb-search-bar, wb-list-container and wb-pagination created in the html',() => {
expect(debugElement.querySelector('#wb-action-toolbar')).not.toBeNull(); expect(debugElement.querySelector('#wb-action-toolbar')).toBeDefined();
expect(debugElement.querySelector('#wb-search-bar')).not.toBeNull(); expect(debugElement.querySelector('#wb-search-bar')).toBeDefined();
expect(debugElement.querySelector('#wb-pagination')).not.toBeNull(); expect(debugElement.querySelector('#wb-pagination')).toBeDefined();
expect(debugElement.querySelector('#wb-list-container')).not.toBeNull(); expect(debugElement.querySelector('#wb-list-container')).toBeDefined();
expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(4); expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(4);
})); });
it('should have two workbasketsummary rows created with the second one selected.', fakeAsync( () => { 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(4);
expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class')).toBe('list-group-item'); expect(debugElement.querySelectorAll('#wb-list-container > li')[2].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')[3].getAttribute('class')).toBe('list-group-item active');
})); });
it('should have two workbasketsummary rows created with two different icons: user and users', fakeAsync( () => { 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')[2].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')[3].querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg');
});
it('should have rendered sort by: name, id, description, owner and type', () => {
expect(debugElement.querySelector('#sort-by-name')).toBeDefined();
expect(debugElement.querySelector('#sort-by-key')).toBeDefined();
expect(debugElement.querySelector('#sort-by-description')).toBeDefined();
expect(debugElement.querySelector('#sort-by-owner')).toBeDefined();
expect(debugElement.querySelector('#sort-by-type')).toBeDefined();
});
it('should have changed sort direction and perform a request after clicking sort direction buttons', fakeAsync( () => {
debugElement.querySelector('#sort-by-direction-desc').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('key', Direction.DESC);
debugElement.querySelector('#sort-by-direction-asc').click();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalledWith('key', Direction.ASC);
}));
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

@ -1,6 +1,6 @@
import { Component, OnInit, EventEmitter } from '@angular/core'; import { Component, OnInit, EventEmitter } from '@angular/core';
import { WorkbasketSummary } from '../../model/workbasketSummary'; import { WorkbasketSummary } from '../../model/workbasketSummary';
import { WorkbasketService } from '../../services/workbasketservice.service' import { WorkbasketService, Direction } from '../../services/workbasketservice.service'
import { Subscription } from 'rxjs/Subscription'; import { Subscription } from 'rxjs/Subscription';
@Component({ @Component({
@ -16,13 +16,16 @@ export class WorkbasketListComponent implements OnInit {
selectedId: string = undefined; selectedId: string = undefined;
workbaskets : Array<WorkbasketSummary> = []; workbaskets : Array<WorkbasketSummary> = [];
sortBy: string = 'key';
sortDirection: Direction = Direction.ASC;
sortingFields : Map<string, string> = new Map([['name', 'Name'], ['key', 'Id'], ['description', 'Description'], ['owner', 'Owner'], ['type', 'Type']]);
private workBasketSummarySubscription: Subscription; private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription; private workbasketServiceSubscription: Subscription;
constructor(private workbasketService: WorkbasketService) { } constructor(private workbasketService: WorkbasketService) { }
ngOnInit() { ngOnInit() {
this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => { this.workBasketSummarySubscription = this.workbasketService.getWorkBasketsSummary().subscribe(resultList => {
this.workbaskets = resultList; this.workbaskets = resultList;
}); });
@ -36,6 +39,16 @@ export class WorkbasketListComponent implements OnInit {
this.selectedId = id; this.selectedId = id;
} }
changeOrder(sortDirection: string) {
this.sortDirection = (sortDirection === Direction.ASC)? Direction.ASC: Direction.DESC;
this.performRequest();
}
changeSortBy(sortBy: string){
this.sortBy = sortBy;
this.performRequest();
}
onDelete(workbasket: WorkbasketSummary) { onDelete(workbasket: WorkbasketSummary) {
this.workbasketService.deleteWorkbasket(workbasket.workbasketId).subscribe(result => { this.workbasketService.deleteWorkbasket(workbasket.workbasketId).subscribe(result => {
var index = this.workbaskets.indexOf(workbasket); var index = this.workbaskets.indexOf(workbasket);
@ -63,7 +76,13 @@ export class WorkbasketListComponent implements OnInit {
return new WorkbasketSummary("", "", "", "", "", "", "", "", "", "", "", ""); return new WorkbasketSummary("", "", "", "", "", "", "", "", "", "", "", "");
} }
ngOnDestroy(){ private performRequest(): void{
this.workbasketServiceSubscription.add(this.workbasketService.getWorkBasketsSummary(this.sortBy,this.sortDirection).subscribe(resultList => {
this.workbaskets = resultList;
}));
}
private ngOnDestroy(){
this.workBasketSummarySubscription.unsubscribe(); this.workBasketSummarySubscription.unsubscribe();
this.workbasketServiceSubscription.unsubscribe(); this.workbasketServiceSubscription.unsubscribe();
} }

View File

@ -155,10 +155,6 @@ li > div.row > dl {
margin-left: 2px; margin-left: 2px;
} }
.orderby-dropdown {
min-width: 200px;
}
.dropdown-menu-users { .dropdown-menu-users {
min-width: 0px; min-width: 0px;
} }

View File

@ -1,10 +1 @@
<?xml version='1.0' encoding='iso-8859-1'?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000"><path d="M295.82 15.17c-62.83 16.41-109.69 62.62-129.77 128.04-4.97 16.84-5.61 22.46-5.61 50.31s.65 33.47 5.61 50.31c11.01 35.84 26.56 61.32 53.12 86.16 13.6 12.96 22.24 18.79 39.52 27.21 31.74 15.55 45.99 18.79 83.78 18.79 33.04 0 45.56-2.16 71.69-12.74 27.64-11.23 58.3-35.2 76.22-59.6 11.01-14.9 23.75-41.67 29.58-61.54 6.91-24.83 7.13-72.33 0-96.73C500.1 76.07 449.57 27.49 382.42 13.23c-23.77-4.97-62.85-4.11-86.6 1.94zM178.35 419.38c-56.79 14.9-91.12 45.77-102.56 91.77-4.75 19-6.69 91.77-3.46 128.91 3.24 35.41 8.42 56.79 19.87 80.11 8.21 16.84 9.5 18.35 19.86 23.1 13.39 6.05 38 10.58 84.64 15.33 26.99 2.81 57.44 3.67 140.35 3.67 58.3.22 115.3-.65 126.96-1.51 19.87-1.73 20.94-2.16 20.94-6.91 0-11.88 11.66-48.8 22.24-71.04 20.3-42.1 49.45-74.93 88.75-100.19l19-12.31-1.08-23.75c-3.02-57.43-23.1-90.9-67.15-112.28-16.63-8.2-55.71-20.51-64.78-20.51-1.51 0-5.61 6.05-9.29 13.39-3.67 7.56-15.98 25.05-27.21 39.08-28.07 34.33-44.26 57.22-61.97 87.02-12.5 20.94-15.09 24.18-15.96 20.07-4.75-20.3-12.09-68.45-12.09-77.73 0-14.68 4.53-24.4 21.38-47.07 13.17-17.92 16.19-26.78 9.72-29.15-1.94-.65-22.02-1.3-44.91-1.3-54.41 0-55.28.87-33.04 30.45 24.83 32.82 25.26 34.98 15.98 87.66-3.67 21.81-7.34 39.51-7.99 39.51-.65 0-5.18-6.48-9.72-14.68-15.76-27.21-41.67-64.13-64.78-92.2-12.52-15.55-25.69-34.12-29.37-41.67-4.32-8.85-7.77-13.39-10.15-13.39-1.94.22-12.95 2.6-24.18 5.62zM700.89 588.88c-7.77 1.08-22.24 4.32-32.39 7.56-68.02 21.16-115.95 71.25-136.46 142.73-6.91 23.97-6.91 75.57 0 101.05 19.22 69.96 71.47 122.65 142.94 144.02 18.35 5.61 63.05 7.56 85.5 3.89 53.77-8.64 103.64-40.38 134.09-85.29 47.07-69.74 46.21-162.81-2.38-230.61-18.57-26.34-51.17-53.33-79.67-66.29-35.62-16.2-73.84-22.03-111.63-17.06zm55.28 76.22c13.6 6.26 26.78 19.22 33.47 33.25 4.1 8.21 5.18 14.68 5.83 34.98.87 24.83.87 25.05 6.7 27.64 3.45 1.51 7.13 5.18 8.42 8.21 3.67 7.56 3.67 122.43 0 128.91-5.18 9.93-5.4 9.93-83.35 10.15h-74.06l-5.18-5.4-5.4-5.18V769.84l5.4-5.18c2.81-3.02 6.69-5.4 8.64-5.4 2.81 0 3.24-3.67 3.24-22.67 0-17.06 1.08-25.26 4.1-33.47 14.24-37.37 55.91-54.64 92.19-38.02z"/><path d="M709.31 699.65c-10.8 7.34-14.9 18.57-14.9 41.24v18.35h66.94v-19c0-15.76-.87-20.73-4.97-28.5-9.07-17.06-31.31-22.67-47.07-12.09zM715.57 792.93c-7.13 3.67-12.52 13.38-12.52 21.81 0 5.61 4.75 15.11 9.07 17.92.86.65 1.73 10.36 2.16 21.38.65 19 1.08 20.51 6.26 24.61 3.67 2.59 7.34 3.67 10.37 2.59 9.5-3.02 11.01-6.69 11.01-27.85 0-17.27.65-20.51 4.32-23.97 6.26-5.61 7.56-16.41 3.24-25.69-6.27-13.18-21.17-17.93-33.91-10.8z"/></svg>
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 297 297" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 297 297">
<g>
<path d="m110.549,98.768c27.231,0 49.384-22.154 49.384-49.384 0.001-27.231-22.153-49.384-49.384-49.384s-49.384,22.153-49.384,49.384c0,27.231 22.154,49.384 49.384,49.384z"/>
<path d="m226.823,185.307c-30.794,0-55.846,25.053-55.846,55.846 0,30.794 25.053,55.846 55.846,55.846s55.846-25.053 55.846-55.846c0-30.793-25.052-55.846-55.846-55.846zm34.017,44.247l-36.494,36.494c-1.836,1.835-4.241,2.753-6.647,2.753-2.406,0-4.811-0.918-6.647-2.753l-18.247-18.247c-3.671-3.671-3.671-9.623 0-13.294 3.672-3.67 9.622-3.67 13.294,0l11.6,11.601 29.847-29.848c3.672-3.67 9.622-3.67 13.294,0 3.671,3.671 3.671,9.623-5.68434e-14,13.294z"/>
<path d="m206.751,172.72v-8.713c0-18.793-12.077-35.457-29.935-41.307l-.083-.027-26.121-4.325c-2.223-0.684-4.599,0.493-5.398,2.686l-29.638,81.319c-1.71,4.691-8.345,4.691-10.055,0l-29.638-81.319c-0.646-1.771-2.317-2.881-4.108-2.881-0.425,0-27.41,4.514-27.41,4.514-18.005,6-30.035,22.691-30.035,41.565v67.664c0,10.14 8.22,18.36 18.36,18.36h123.405c-0.382-2.984-0.601-6.017-0.601-9.103 0.001-32.361 21.671-59.74 51.257-68.433z"/>
<path d="m121.487,113.395c-1.157-1.26-2.848-1.895-4.558-1.895h-12.759c-1.711,0-3.402,0.634-4.558,1.895-1.791,1.951-2.051,4.769-0.779,6.972l6.82,10.282-3.193,26.934 6.287,16.725c0.613,1.682 2.992,1.682 3.605,0l6.287-16.725-3.193-26.934 6.82-10.282c1.272-2.203 1.012-5.021-0.779-6.972z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB