TSK-537 Filter Component for Tasks

TSK-537: Filter Component for Workplace
This commit is contained in:
Lars Leo Grätz 2018-07-05 13:36:21 +02:00 committed by Martin Rojas Miguel Angel
parent 0c5d540c56
commit 3968bd4f54
29 changed files with 776 additions and 565 deletions

View File

@ -219,9 +219,9 @@ public class TaskController extends AbstractPagingController {
params.remove(NAME);
}
if (params.containsKey(PRIORITY)) {
String[] prioritesInString = extractCommaSeparatedFields(params.get(PRIORITY));
int[] priorites = extractPriorities(prioritesInString);
taskQuery.priorityIn(priorites);
String[] prioritiesInString = extractCommaSeparatedFields(params.get(PRIORITY));
int[] priorities = extractPriorities(prioritiesInString);
taskQuery.priorityIn(priorities);
params.remove(PRIORITY);
}
if (params.containsKey(STATE)) {
@ -335,12 +335,12 @@ public class TaskController extends AbstractPagingController {
return taskQuery;
}
private int[] extractPriorities(String[] prioritesInString) {
int[] priorites = new int[prioritesInString.length];
for (int i = 0; i < prioritesInString.length; i++) {
priorites[i] = Integer.getInteger(prioritesInString[i]);
private int[] extractPriorities(String[] prioritiesInString) {
int[] priorities = new int[prioritiesInString.length];
for (int i = 0; i < prioritiesInString.length; i++) {
priorities[i] = Integer.valueOf(prioritiesInString[i]);
}
return priorites;
return priorities;
}
private TaskState[] extractStates(MultiValueMap<String, String> params) throws InvalidArgumentException {

View File

@ -1,42 +1,38 @@
// tslint:enable:max-line-length
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Ng2AutoCompleteModule } from 'ng2-auto-complete';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { AlertModule } from 'ngx-bootstrap';
import { SharedModule } from 'app/shared/shared.module';
import { AdministrationRoutingModule } from './administration-routing.module';
import { TypeaheadModule } from 'ngx-bootstrap';
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Ng2AutoCompleteModule} from 'ng2-auto-complete';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {AlertModule, TypeaheadModule} from 'ngx-bootstrap';
import {SharedModule} from 'app/shared/shared.module';
import {AdministrationRoutingModule} from './administration-routing.module';
/**
* Components
*/
import { WorkbasketListComponent } from './workbasket/master/list/workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket/master/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';
import { DualListComponent } from './workbasket/details/distribution-targets/dual-list/dual-list.component';
import { AccessItemsComponent } from './workbasket/details/access-items/access-items.component';
import { FilterComponent } from './components/filter/filter.component';
import { IconTypeComponent } from './components/type-icon/icon-type.component';
import { PaginationComponent } from './workbasket/master/list/pagination/pagination.component';
import { ClassificationListComponent } from './classification/master/list/classification-list.component';
import { ClassificationDetailsComponent } from './classification/details/classification-details.component';
import { ImportExportComponent } from './components/import-export/import-export.component';
import { ClassificationTypesSelectorComponent } from 'app/shared/classification-types-selector/classification-types-selector.component';
import {WorkbasketListComponent} from './workbasket/master/list/workbasket-list.component';
import {WorkbasketListToolbarComponent} from './workbasket/master/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';
import {DualListComponent} from './workbasket/details/distribution-targets/dual-list/dual-list.component';
import {AccessItemsComponent} from './workbasket/details/access-items/access-items.component';
import {IconTypeComponent} from './components/type-icon/icon-type.component';
import {PaginationComponent} from './workbasket/master/list/pagination/pagination.component';
import {ClassificationListComponent} from './classification/master/list/classification-list.component';
import {ClassificationDetailsComponent} from './classification/details/classification-details.component';
import {ImportExportComponent} from './components/import-export/import-export.component';
import {ClassificationTypesSelectorComponent} from 'app/shared/classification-types-selector/classification-types-selector.component';
/**
* Services
*/
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { SavingWorkbasketService } from './services/saving-workbaskets/saving-workbaskets.service';
import { ClassificationDefinitionService } from './services/classification-definition/classification-definition.service';
import { WorkbasketDefinitionService } from './services/workbasket-definition/workbasket-definition.service';
import { ClassificationsService } from './services/classifications/classifications.service';
import { ClassificationTypesService } from './services/classification-types/classification-types.service';
import { ClassificationCategoriesService } from './services/classification-categories-service/classification-categories.service';
import {WorkbasketService} from 'app/services/workbasket/workbasket.service';
import {SavingWorkbasketService} from './services/saving-workbaskets/saving-workbaskets.service';
import {ClassificationDefinitionService} from './services/classification-definition/classification-definition.service';
import {WorkbasketDefinitionService} from './services/workbasket-definition/workbasket-definition.service';
import {ClassificationsService} from './services/classifications/classifications.service';
import {ClassificationTypesService} from './services/classification-types/classification-types.service';
import {ClassificationCategoriesService} from './services/classification-categories-service/classification-categories.service';
const MODULES = [
CommonModule,
@ -56,8 +52,6 @@ const DECLARATIONS = [
AccessItemsComponent,
WorkbasketDetailsComponent,
WorkbasketInformationComponent,
FilterComponent,
IconTypeComponent,
DistributionTargetsComponent,
DualListComponent,
PaginationComponent,
@ -82,4 +76,5 @@ const DECLARATIONS = [
})
export class AdministrationModule {
}
// tslint:enable:max-line-length

View File

@ -2,7 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Router, ActivatedRoute } from '@angular/router';
import { ImportType } from 'app/models/import-type';
import { TaskanaType } from 'app/models/taskana-type';
import { Classification } from 'app/models/classification';
import { TreeNodeModel } from 'app/models/tree-node';
@ -24,7 +24,7 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
selectedCategory = '';
selectedId: string;
selectionToImport = ImportType.CLASSIFICATIONS;
selectionToImport = TaskanaType.CLASSIFICATIONS;
requestInProgress = false;
initialized = false;
inputValue: string;

View File

@ -1,46 +0,0 @@
<div id="{{target}}" class="list-group-search">
<div class="row">
<div class="dropdown col-xs-2">
<button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdownMenufilter" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<taskana-icon-type [type]="filter.type" class="vertical-align"></taskana-icon-type>
</button>
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<a *ngFor="let type of allTypes | mapValues" type="button" (click)="selectType(type.key); search()" data-toggle="tooltip"
[title]="type.value">
<taskana-icon-type class="vertical-align" [type]='type.key' [text]="type.value"></taskana-icon-type>
</a>
</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(); search()" type="button" class="btn btn-default glyphicon glyphicon-ban-circle blue pull-right margin-right"
data-toggle="tooltip" title="Clear">
</button>
</div>
<div class="row padding">
<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

@ -1,81 +0,0 @@
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 { FilterModel } from 'app/models/filter';
import { IconTypeComponent } from '../type-icon/icon-type.component';
import { FilterComponent } from './filter.component';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { configureTests } from 'app/app.test.configuration';
describe('FilterComponent', () => {
let component: FilterComponent,
fixture: ComponentFixture<FilterComponent>,
debugElement: any;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [FilterComponent, IconTypeComponent, MapValuesPipe],
imports: [AngularSvgIconModule, FormsModule, HttpClientModule, HttpModule]
})
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(FilterComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
done();
});
});
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', '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('');
expect(component.filter.key).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

@ -1,41 +0,0 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { ICONTYPES } from 'app/models/type';
import { FilterModel } from 'app/models/filter';
@Component({
selector: 'taskana-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.scss']
})
export class FilterComponent {
allTypes: Map<string, string>;
filter: FilterModel = new FilterModel();
@Input()
target: string;
@Output()
performFilter = new EventEmitter<FilterModel>();
toggleDropDown = false;
constructor() {
this.allTypes = new Map([['', 'All'], ['PERSONAL', 'Personal'], ['GROUP', 'Group'],
['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic']]);
}
selectType(type: ICONTYPES) {
this.filter.type = type;
}
clear() {
this.filter = new FilterModel();
}
search() {
this.performFilter.emit(this.filter);
}
}

View File

@ -2,7 +2,7 @@ import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service';
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
import { DomainService } from 'app/services/domain/domain.service';
import { ImportType } from 'app/models/import-type';
import { TaskanaType } from 'app/models/taskana-type';
import { ErrorModel } from 'app/models/modal-error';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
@ -13,7 +13,7 @@ import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
})
export class ImportExportComponent implements OnInit {
@Input() currentSelection: ImportType;
@Input() currentSelection: TaskanaType;
@Output() importSucessful = new EventEmitter();
@ -47,7 +47,7 @@ export class ImportExportComponent implements OnInit {
}
const reader = new FileReader();
if (this.currentSelection === ImportType.WORKBASKETS) {
if (this.currentSelection === TaskanaType.WORKBASKETS) {
reader.onload = <Event>(e) => this.workbasketDefinitionService.importWorkbasketDefinitions(e.target.result);
this.importSucessful.emit();
} else {
@ -58,7 +58,7 @@ export class ImportExportComponent implements OnInit {
}
export(domain = '') {
if (this.currentSelection === ImportType.WORKBASKETS) {
if (this.currentSelection === TaskanaType.WORKBASKETS) {
this.workbasketDefinitionService.exportWorkbaskets(domain);
} else {
this.classificationDefinitionService.exportClassifications(domain);

View File

@ -4,7 +4,8 @@
<button type="button" (click)="addWorkbasket()" data-toggle="tooltip" title="Add" class="btn btn-default">
<span class="glyphicon glyphicon-plus green-blue" aria-hidden="true"></span>
</button>
<taskana-import-export-component (importSucessful)="importEvent()" [currentSelection]="'selectionToImport'"></taskana-import-export-component>
<taskana-import-export-component (importSucessful)="importEvent()"
[currentSelection]="selectionToImport"></taskana-import-export-component>
</div>
<div class="pull-right margin-right">
<taskana-sort
@ -17,7 +18,9 @@
</div>
</div>
<div [@toggleDown]="toolbarState" *ngIf="toolbarState" class="row no-overflow">
<taskana-filter (performFilter)="filtering($event)"></taskana-filter>
<div [@toggle]="toolbarState" *ngIf="toolbarState" class="row no-overflow">
<taskana-filter [allTypes]="filteringTypes" [filterParams]="filterParams"
[filterType]="filterType"
(performFilter)="filtering($event)"></taskana-filter>
</div>
</li>

View File

@ -16,7 +16,7 @@ import { Links } from 'app/models/links';
import { FilterModel } from 'app/models/filter';
import { SortingModel } from 'app/models/sorting';
import { FilterComponent } from 'app/administration/components/filter/filter.component';
import { FilterComponent } from 'app/shared/filter/filter.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar.component';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
@ -48,8 +48,7 @@ describe('WorkbasketListToolbarComponent', () => {
testBed.configureTestingModule({
imports: [FormsModule, ReactiveFormsModule, AngularSvgIconModule, HttpModule,
HttpClientModule, RouterTestingModule.withRoutes(routes), SharedModule, AppModule],
declarations: [WorkbasketListToolbarComponent,
FilterComponent, IconTypeComponent, DummyDetailComponent, ImportExportComponent],
declarations: [WorkbasketListToolbarComponent, DummyDetailComponent, ImportExportComponent],
providers: [
WorkbasketService,
ClassificationDefinitionService,

View File

@ -1,61 +1,77 @@
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {animate, keyframes, style, transition, trigger} from '@angular/animations';
import {ActivatedRoute, Router} from '@angular/router';
import { SortingModel } from 'app/models/sorting';
import { FilterModel } from 'app/models/filter';
import { Subscription } from 'rxjs/Subscription';
import { WorkbasketSummary } from 'app/models/workbasket-summary';
import {SortingModel} from 'app/models/sorting';
import {FilterModel} from 'app/models/filter';
import {Subscription} from 'rxjs/Subscription';
import {WorkbasketSummary} from 'app/models/workbasket-summary';
import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { AlertService } from 'app/services/alert/alert.service';
import { ImportType } from 'app/models/import-type';
import { expandDown } from 'app/shared/animations/expand.animation';
import {ErrorModalService} from 'app/services/errorModal/error-modal.service';
import {RequestInProgressService} from 'app/services/requestInProgress/request-in-progress.service';
import {WorkbasketService} from 'app/services/workbasket/workbasket.service';
import {AlertService} from 'app/services/alert/alert.service';
import {TaskanaType} from 'app/models/taskana-type';
@Component({
selector: 'taskana-workbasket-list-toolbar',
animations: [expandDown],
templateUrl: './workbasket-list-toolbar.component.html',
styleUrls: ['./workbasket-list-toolbar.component.scss']
selector: 'taskana-workbasket-list-toolbar',
animations: [
trigger('toggle', [
transition('void => *', animate('300ms ease-in', keyframes([
style({height: '0px'}),
style({height: '50px'}),
style({height: '*'})]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({height: '*'}),
style({height: '50px'}),
style({height: '0px'})])))
]
)],
templateUrl: './workbasket-list-toolbar.component.html',
styleUrls: ['./workbasket-list-toolbar.component.scss']
})
export class WorkbasketListToolbarComponent implements OnInit {
@Input() workbaskets: Array<WorkbasketSummary>;
@Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>();
@Output() importSucessful = new EventEmitter();
workbasketServiceSubscription: Subscription;
selectionToImport = ImportType.WORKBASKETS;
sortingFields = new Map([['name', 'Name'], ['key', 'Key'], ['description', 'Description'], ['owner', 'Owner'], ['type', 'Type']]);
toolbarState = false;
@Input() workbaskets: Array<WorkbasketSummary>;
@Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>();
@Output() importSucessful = new EventEmitter();
workbasketServiceSubscription: Subscription;
selectionToImport = TaskanaType.WORKBASKETS;
sortingFields = new Map([['name', 'Name'], ['key', 'Key'], ['description', 'Description'], ['owner', 'Owner'], ['type', 'Type']]);
filteringTypes = new Map([['ALL', 'All'], ['PERSONAL', 'Personal'], ['GROUP', 'Group'],
['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic']]);
filterParams = {name: '', key: '', type: '', description: '', owner: ''};
toolbarState = false;
filterType = TaskanaType.WORKBASKETS;
constructor(
private workbasketService: WorkbasketService,
private route: ActivatedRoute,
private router: Router,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService,
private alertService: AlertService) { }
constructor(
private workbasketService: WorkbasketService,
private route: ActivatedRoute,
private router: Router,
private errorModalService: ErrorModalService,
private requestInProgressService: RequestInProgressService,
private alertService: AlertService) {
}
ngOnInit() {
}
ngOnInit() {
}
sorting(sort: SortingModel) {
this.performSorting.emit(sort);
}
sorting(sort: SortingModel) {
this.performSorting.emit(sort);
}
filtering(filterBy: FilterModel) {
this.performFilter.emit(filterBy);
}
filtering(filterBy: FilterModel) {
this.performFilter.emit(filterBy);
}
addWorkbasket() {
this.workbasketService.selectWorkBasket(undefined);
this.router.navigate([{ outlets: { detail: ['new-workbasket'] } }], { relativeTo: this.route });
}
addWorkbasket() {
this.workbasketService.selectWorkBasket(undefined);
this.router.navigate([{outlets: {detail: ['new-workbasket']}}], {relativeTo: this.route});
}
importEvent() {
this.importSucessful.emit();
}
importEvent() {
this.importSucessful.emit();
}
}

View File

@ -1,182 +1,182 @@
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { Observable } from 'rxjs/Observable';
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {Observable} from 'rxjs/Observable';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
import { Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { SharedModule } from 'app/shared/shared.module';
import { AppModule } from 'app/app.module';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {HttpClientModule} from '@angular/common/http';
import {HttpModule} from '@angular/http';
import {Routes} from '@angular/router';
import {RouterTestingModule} from '@angular/router/testing';
import {SharedModule} from 'app/shared/shared.module';
import {AppModule} from 'app/app.module';
import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { FilterModel } from 'app/models/filter';
import { LinksWorkbasketSummary } from 'app/models/links-workbasket-summary';
import {WorkbasketSummary} from 'app/models/workbasket-summary';
import {WorkbasketSummaryResource} from 'app/models/workbasket-summary-resource';
import {FilterModel} from 'app/models/filter';
import {LinksWorkbasketSummary} from 'app/models/links-workbasket-summary';
import { WorkbasketListComponent } from './workbasket-list.component';
import { WorkbasketListToolbarComponent } from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
import {WorkbasketListComponent} from './workbasket-list.component';
import {WorkbasketListToolbarComponent} from './workbasket-list-toolbar/workbasket-list-toolbar.component';
import {ImportExportComponent} from 'app/administration/components/import-export/import-export.component';
import { WorkbasketDefinitionService } from 'app/administration/services/workbasket-definition/workbasket-definition.service';
import { ClassificationDefinitionService } from 'app/administration/services/classification-definition/classification-definition.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { OrientationService } from 'app/services/orientation/orientation.service';
import { Orientation } from '../../../../models/orientation';
import { configureTests } from 'app/app.test.configuration';
import {WorkbasketDefinitionService} from 'app/administration/services/workbasket-definition/workbasket-definition.service';
import {ClassificationDefinitionService} from 'app/administration/services/classification-definition/classification-definition.service';
import {WorkbasketService} from 'app/services/workbasket/workbasket.service';
import {OrientationService} from 'app/services/orientation/orientation.service';
import {configureTests} from 'app/app.test.configuration';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
class DummyDetailComponent {
}
@Component({
selector: 'taskana-pagination',
template: 'dummydetail'
selector: 'taskana-pagination',
template: 'dummydetail'
})
class PaginationComponent {
@Input()
workbasketsResource: any;
@Output()
workbasketsResourceChange = new EventEmitter<any>();
@Output() changePage = new EventEmitter<any>();
@Input()
workbasketsResource: any;
@Output()
workbasketsResourceChange = new EventEmitter<any>();
@Output() changePage = new EventEmitter<any>();
}
@Component({
selector: 'taskana-filter',
template: ''
selector: 'taskana-filter',
template: ''
})
class FilterComponent {
}
const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSummaryResource({
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new LinksWorkbasketSummary({ 'href': 'url' }));
'workbaskets': new Array<WorkbasketSummary>(
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', ''))
}, new LinksWorkbasketSummary({'href': 'url'}));
describe('WorkbasketListComponent', () => {
let component: WorkbasketListComponent;
let fixture: ComponentFixture<WorkbasketListComponent>;
let debugElement: any = undefined;
let workbasketService: WorkbasketService;
let workbasketSummarySpy;
let component: WorkbasketListComponent;
let fixture: ComponentFixture<WorkbasketListComponent>;
let debugElement: any = undefined;
let workbasketService: WorkbasketService;
let workbasketSummarySpy;
const routes: Routes = [
{ path: ':id', component: DummyDetailComponent, outlet: 'detail' },
{ path: 'workbaskets', component: DummyDetailComponent }
];
const routes: Routes = [
{path: ':id', component: DummyDetailComponent, outlet: 'detail'},
{path: 'workbaskets', component: DummyDetailComponent}
];
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, FilterComponent, WorkbasketListToolbarComponent,
IconTypeComponent, PaginationComponent, ImportExportComponent],
imports: [
AngularSvgIconModule,
HttpModule,
HttpClientModule,
RouterTestingModule.withRoutes(routes),
SharedModule,
AppModule
],
providers: [
WorkbasketService,
WorkbasketDefinitionService,
ClassificationDefinitionService,
OrientationService
]
});
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(WorkbasketListComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService);
const orientationService = TestBed.get(OrientationService);
workbasketSummarySpy = spyOn(workbasketService, 'getWorkBasketsSummary').and.returnValue(Observable.of(workbasketSummaryResource));
spyOn(workbasketService, 'getSelectedWorkBasket').and.returnValue(Observable.of('2'));
spyOn(orientationService, 'getOrientation').and.returnValue(Observable.of(undefined));
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [WorkbasketListComponent, DummyDetailComponent, WorkbasketListToolbarComponent,
PaginationComponent, ImportExportComponent],
imports: [
AngularSvgIconModule,
HttpModule,
HttpClientModule,
RouterTestingModule.withRoutes(routes),
SharedModule,
AppModule
],
providers: [
WorkbasketService,
WorkbasketDefinitionService,
ClassificationDefinitionService,
OrientationService
]
});
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(WorkbasketListComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.nativeElement;
workbasketService = TestBed.get(WorkbasketService);
const orientationService = TestBed.get(OrientationService);
workbasketSummarySpy = spyOn(workbasketService, 'getWorkBasketsSummary').and.returnValue(Observable.of(workbasketSummaryResource));
spyOn(workbasketService, 'getSelectedWorkBasket').and.returnValue(Observable.of('2'));
spyOn(orientationService, 'getOrientation').and.returnValue(Observable.of(undefined));
fixture.detectChanges();
done();
});
fixture.detectChanges();
done();
});
});
});
afterEach(() => {
fixture.detectChanges();
document.body.removeChild(debugElement);
});
afterEach(() => {
fixture.detectChanges();
document.body.removeChild(debugElement);
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should be created', () => {
expect(component).toBeTruthy();
});
it('should call workbasketService.getWorkbasketsSummary method on init', () => {
component.ngOnInit();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalled();
workbasketService.getWorkBasketsSummary().subscribe(value => {
expect(value).toBe(workbasketSummaryResource);
})
});
it('should call workbasketService.getWorkbasketsSummary method on init', () => {
component.ngOnInit();
expect(workbasketService.getWorkBasketsSummary).toHaveBeenCalled();
workbasketService.getWorkBasketsSummary().subscribe(value => {
expect(value).toBe(workbasketSummaryResource);
})
});
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-search-bar')).toBeDefined();
expect(debugElement.querySelector('#wb-pagination')).toBeDefined();
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);
});
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-search-bar')).toBeDefined();
expect(debugElement.querySelector('#wb-pagination')).toBeDefined();
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);
});
// it('should have two workbasketsummary rows created with the second one selected.', fakeAsync(() => {
// tick(0);
// fixture.detectChanges();
// fixture.whenStable().then(() => {
// expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
// expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class'))
// .toBe('list-group-item ng-star-inserted');
// expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class'))
// .toBe('list-group-item ng-star-inserted active');
// })
//
// }));
// it('should have two workbasketsummary rows created with the second one selected.', fakeAsync(() => {
// tick(0);
// fixture.detectChanges();
// fixture.whenStable().then(() => {
// expect(debugElement.querySelectorAll('#wb-list-container > li').length).toBe(3);
// expect(debugElement.querySelectorAll('#wb-list-container > li')[1].getAttribute('class'))
// .toBe('list-group-item ng-star-inserted');
// expect(debugElement.querySelectorAll('#wb-list-container > li')[2].getAttribute('class'))
// .toBe('list-group-item ng-star-inserted active');
// })
//
// }));
it('should have two workbasketsummary rows created with two different icons: user and users', () => {
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')[2]
.querySelector('svg-icon').getAttribute('ng-reflect-src')).toBe('./assets/icons/users.svg');
});
it('should have two workbasketsummary rows created with two different icons: user and users', () => {
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')[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', () => {
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 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 performRequest with forced = true after performFilter is triggered', (() => {
const type = 'PERSONAL', name = 'someName', description = 'someDescription', owner = 'someOwner', key = 'someKey';
const filter = new FilterModel(type, name, description, owner, key);
component.performFilter(filter);
it('should have performRequest with forced = true after performFilter is triggered', (() => {
const filter = new FilterModel({
name: 'someName', owner: 'someOwner', description: 'someDescription',
key: 'someKey', type: 'PERSONAL'
});
component.performFilter(filter);
expect(workbasketSummarySpy.calls.all()[1].args).toEqual([true, 'key', 'asc',
undefined, 'someName', 'someDescription', undefined, 'someOwner', 'PERSONAL', undefined, 'someKey', undefined]);
expect(workbasketSummarySpy.calls.all()[1].args).toEqual([true, 'key', 'asc',
undefined, 'someName', 'someDescription', undefined, 'someOwner', 'PERSONAL', undefined, 'someKey', undefined]);
}));
}));
});

View File

@ -1,113 +1,121 @@
import {
Component, OnInit, OnDestroy,
ViewChild, ElementRef, ChangeDetectorRef
} from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import {ChangeDetectorRef, Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Subscription} from 'rxjs/Subscription';
import { WorkbasketSummaryResource } from 'app/models/workbasket-summary-resource';
import { WorkbasketSummary } from 'app/models/workbasket-summary';
import { FilterModel } from 'app/models/filter'
import { SortingModel } from 'app/models/sorting';
import { Orientation } from 'app/models/orientation';
import {WorkbasketSummaryResource} from 'app/models/workbasket-summary-resource';
import {WorkbasketSummary} from 'app/models/workbasket-summary';
import {FilterModel} from 'app/models/filter'
import {SortingModel} from 'app/models/sorting';
import {Orientation} from 'app/models/orientation';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service'
import { OrientationService } from 'app/services/orientation/orientation.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import {WorkbasketService} from 'app/services/workbasket/workbasket.service'
import {OrientationService} from 'app/services/orientation/orientation.service';
import {TaskanaQueryParameters} from 'app/shared/util/query-parameters';
@Component({
selector: 'taskana-workbasket-list',
templateUrl: './workbasket-list.component.html',
styleUrls: ['./workbasket-list.component.scss']
selector: 'taskana-workbasket-list',
templateUrl: './workbasket-list.component.html',
styleUrls: ['./workbasket-list.component.scss']
})
export class WorkbasketListComponent implements OnInit, OnDestroy {
selectedId = '';
workbasketsResource: WorkbasketSummaryResource;
workbaskets: Array<WorkbasketSummary> = [];
requestInProgress = false;
selectedId = '';
workbasketsResource: WorkbasketSummaryResource;
workbaskets: Array<WorkbasketSummary> = [];
requestInProgress = false;
sort: SortingModel = new SortingModel();
filterBy: FilterModel = new FilterModel();
sort: SortingModel = new SortingModel();
filterBy: FilterModel = new FilterModel({name: '', owner: '', type: '', description: '', key: ''});
@ViewChild('wbToolbar')
private toolbarElement: ElementRef;
private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription;
private workbasketServiceSavedSubscription: Subscription;
private orientationSubscription: Subscription;
@ViewChild('wbToolbar')
private toolbarElement: ElementRef;
private workBasketSummarySubscription: Subscription;
private workbasketServiceSubscription: Subscription;
private workbasketServiceSavedSubscription: Subscription;
private orientationSubscription: Subscription;
constructor(
private workbasketService: WorkbasketService,
private router: Router,
private route: ActivatedRoute,
private orientationService: OrientationService,
private cd: ChangeDetectorRef) { }
constructor(
private workbasketService: WorkbasketService,
private router: Router,
private route: ActivatedRoute,
private orientationService: OrientationService,
private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.requestInProgress = true;
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
// TODO should be done in a different way.
setTimeout(() => { this.selectedId = workbasketIdSelected; }, 0);
});
ngOnInit() {
this.requestInProgress = true;
this.workbasketServiceSubscription = this.workbasketService.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
// TODO should be done in a different way.
setTimeout(() => {
this.selectedId = workbasketIdSelected;
}, 0);
});
this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => {
this.performRequest();
});
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
this.refreshWorkbasketList();
})
}
this.workbasketServiceSavedSubscription = this.workbasketService.workbasketSavedTriggered().subscribe(value => {
this.performRequest();
});
this.orientationSubscription = this.orientationService.getOrientation().subscribe((orientation: Orientation) => {
this.refreshWorkbasketList();
})
}
selectWorkbasket(id: string) {
this.selectedId = id;
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
}
selectWorkbasket(id: string) {
this.selectedId = id;
this.router.navigate([{outlets: {detail: [this.selectedId]}}], {relativeTo: this.route});
}
performSorting(sort: SortingModel) {
this.sort = sort;
this.performRequest();
}
performSorting(sort: SortingModel) {
this.sort = sort;
this.performRequest();
}
performFilter(filterBy: FilterModel) {
this.filterBy = filterBy;
this.performRequest();
}
performFilter(filterBy: FilterModel) {
this.filterBy = filterBy;
this.performRequest();
}
changePage(page) {
TaskanaQueryParameters.page = page;
this.performRequest();
}
changePage(page) {
TaskanaQueryParameters.page = page;
this.performRequest();
}
refreshWorkbasketList() {
const toolbarSize = this.toolbarElement.nativeElement.offsetHeight;
const cardHeight = 75;
const unusedHeight = 145
const totalHeight = window.innerHeight;
const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight);
TaskanaQueryParameters.pageSize = cards;
this.performRequest();
}
refreshWorkbasketList() {
const toolbarSize = this.toolbarElement.nativeElement.offsetHeight;
const cardHeight = 75;
const unusedHeight = 145;
const totalHeight = window.innerHeight;
const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight);
TaskanaQueryParameters.pageSize = cards;
this.performRequest();
}
private performRequest(): void {
this.requestInProgress = true;
this.workbaskets = [];
this.workbasketServiceSubscription = this.workbasketService.getWorkBasketsSummary(
true, this.sort.sortBy, this.sort.sortDirection, undefined,
this.filterBy.name, this.filterBy.description, undefined, this.filterBy.owner,
this.filterBy.type, undefined, this.filterBy.key, undefined)
.subscribe(resultList => {
this.workbasketsResource = resultList;
this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : [];
this.requestInProgress = false;
});
}
private performRequest(): void {
this.requestInProgress = true;
this.workbaskets = [];
this.workbasketServiceSubscription = this.workbasketService.getWorkBasketsSummary(
true, this.sort.sortBy, this.sort.sortDirection, undefined,
this.filterBy.filterParams.name, this.filterBy.filterParams.description, undefined, this.filterBy.filterParams.owner,
this.filterBy.filterParams.type, undefined, this.filterBy.filterParams.key, undefined)
.subscribe(resultList => {
this.workbasketsResource = resultList;
this.workbaskets = resultList._embedded ? resultList._embedded.workbaskets : [];
this.requestInProgress = false;
});
}
ngOnDestroy() {
if (this.workBasketSummarySubscription) { this.workBasketSummarySubscription.unsubscribe(); }
if (this.workbasketServiceSubscription) { this.workbasketServiceSubscription.unsubscribe(); }
if (this.workbasketServiceSavedSubscription) { this.workbasketServiceSavedSubscription.unsubscribe(); }
if (this.orientationSubscription) { this.orientationSubscription.unsubscribe(); }
ngOnDestroy() {
if (this.workBasketSummarySubscription) {
this.workBasketSummarySubscription.unsubscribe();
}
if (this.workbasketServiceSubscription) {
this.workbasketServiceSubscription.unsubscribe();
}
if (this.workbasketServiceSavedSubscription) {
this.workbasketServiceSavedSubscription.unsubscribe();
}
if (this.orientationSubscription) {
this.orientationSubscription.unsubscribe();
}
}
}
}

View File

@ -1,14 +1,7 @@
export class FilterModel {
type: string;
name: string;
description: string;
owner: string;
key: string;
constructor(type: string = '', name: string = '', description: string = '', owner: string = '', key: string = '') {
this.type = type;
this.name = name;
this.description = description;
this.owner = owner;
this.key = key;
}
filterParams: any;
constructor(filterParams?: any) {
this.filterParams = filterParams;
}
}

View File

@ -1,4 +0,0 @@
export enum ImportType {
WORKBASKETS,
CLASSIFICATIONS
}

View File

@ -0,0 +1,5 @@
export enum TaskanaType {
WORKBASKETS,
CLASSIFICATIONS,
TASKS
}

View File

@ -0,0 +1,77 @@
<div class="list-group-search">
<div class="row">
<div *ngIf="filterTypeIsWorkbasket(); else tasktype">
<div class="dropdown col-xs-2">
<button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdownMenufilter"
data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true">
<taskana-icon-type [type]="filter.filterParams?.type" class="vertical-align"></taskana-icon-type>
</button>
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<a *ngFor="let type of allTypes | mapValues" type="button" (click)="selectType(type.key); search()"
data-toggle="tooltip"
[title]="type.value">
<taskana-icon-type class="vertical-align" [type]='type.key' [text]="type.value"></taskana-icon-type>
</a>
</li>
</ul>
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter.filterParams.name" (keyup.enter)="search()" class="form-control"
id="display-name-filter" placeholder="Filter name">
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter.filterParams.key" (keyup.enter)="search()" class="form-control"
id="display-key-filter" placeholder="Filter key">
</div>
</div>
<ng-template #tasktype>
<div class="col-xs-2">
</div>
<div class="col-xs-8">
<input type="text" [(ngModel)]="filter.filterParams.name" (keyup.enter)="search()" class="form-control"
id="display-name-filter" placeholder="Filter name">
</div>
</ng-template>
<button (click)="clear(); search()" type="button"
class="btn btn-default glyphicon glyphicon-ban-circle blue pull-right margin-right"
data-toggle="tooltip" title="Clear">
</button>
</div>
<div class="filter-list">
<li class="" *ngFor="let filterType of getUnusedKeys()">
<div class="row padding">
<div class="col-xs-2">
</div>
<div class="col-xs-8">
<div *ngIf="checkUppercaseFilterType(filterType); else normalFilter">
<input type="text" [(ngModel)]="filter.filterParams[filterType]"
(ngModelChange)="filter.filterParams[filterType] = $event.toLocaleUpperCase()"
(keyup.enter)="search()"
class="form-control" id="display-{{filterType}}-filter" placeholder="Filter {{filterType}}">
</div>
<ng-template #normalFilter>
<input type="text" [(ngModel)]="filter.filterParams[filterType]"
(keyup.enter)="search()"
class="form-control" id="display-{{filterType}}-filter" placeholder="Filter {{filterType}}">
</ng-template>
</div>
<div *ngIf="filterType === lastFilterKey" class="pull-right">
<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>
</li>
</div>
</div>

View File

@ -14,4 +14,12 @@
}
row.padding {
padding: 1px 0px;
}
}
.filter-list {
list-style-type: none;
}
.filter-list > li {
padding-top: 5px;
}

View File

@ -0,0 +1,90 @@
import {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 {FilterModel} from 'app/models/filter';
import {IconTypeComponent} from '../../administration/components/type-icon/icon-type.component';
import {FilterComponent} from './filter.component';
import {MapValuesPipe} from 'app/shared/pipes/mapValues/map-values.pipe';
import {configureTests} from 'app/app.test.configuration';
describe('FilterComponent', () => {
let component: FilterComponent,
fixture: ComponentFixture<FilterComponent>,
debugElement: any;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [FilterComponent, IconTypeComponent, MapValuesPipe],
imports: [AngularSvgIconModule, FormsModule, HttpClientModule, HttpModule]
})
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(FilterComponent);
component = fixture.componentInstance;
component.filter = new FilterModel({
name: 'someName', owner: 'someOwner', description: 'someDescription',
key: 'someKey', type: 'PERSONAL'
});
debugElement = fixture.debugElement.nativeElement;
fixture.detectChanges();
done();
});
});
afterEach((done) => {
document.body.removeChild(debugElement);
});
it('should create', (done) => {
expect(component).toBeTruthy();
});
it('should create a component', (done) => {
expect(debugElement.querySelector('#some-id')).toBeNull();
fixture.detectChanges();
expect(debugElement.querySelector('#some-id')).toBeDefined();
});
it('should have filter by: name, description, key, owner and type defined', (done) => {
expect(debugElement.querySelector('#display-name-filter')).toBeDefined();
expect(debugElement.querySelector('#display-description-filter')).toBeDefined();
expect(debugElement.querySelector('#display-key-filter')).toBeDefined();
expect(debugElement.querySelector('#display-owner-filter')).toBeDefined();
expect(debugElement.querySelector('#display-type-filter')).toBeDefined();
});
it('should be able to clear all fields after pressing clear button', (done) => {
component.filter = new FilterModel({
name: 'someName', owner: 'someOwner', description: 'someDescription',
key: 'someKey', type: 'PERSONAL'
});
debugElement.querySelector('[title="Clear"]').click();
expect(component.filter.filterParams.name).toBe('');
expect(component.filter.filterParams.description).toBe('');
expect(component.filter.filterParams.owner).toBe('');
expect(component.filter.filterParams.type).toBe('');
expect(component.filter.filterParams.key).toBe('');
});
it('should be able to select a type and return it based on a number', (done) => {
expect(component).toBeTruthy();
});
it('should be able to emit a filter after clicking on search button', (done) => {
component.filter = new FilterModel({
name: 'someName', owner: 'someOwner', description: 'someDescription',
key: 'someKey', type: 'PERSONAL'
});
component.performFilter.subscribe(filter => {
expect(filter.name).toBe('someName');
done();
});
debugElement.querySelector('[title="Search"]').click();
});
});

View File

@ -0,0 +1,76 @@
import {AfterContentInit, Component, EventEmitter, Input, Output} from '@angular/core';
import {ICONTYPES} from 'app/models/type';
import {FilterModel} from 'app/models/filter';
import {TaskanaType} from 'app/models/taskana-type';
@Component({
selector: 'taskana-filter',
templateUrl: './filter.component.html',
styleUrls: ['./filter.component.scss']
})
export class FilterComponent implements AfterContentInit {
@Input() allTypes: Map<string, string>;
@Input() filterParams: any;
@Input() filterType: TaskanaType;
@Output() performFilter = new EventEmitter<FilterModel>();
filter: FilterModel;
filterParamKeys = [];
lastFilterKey: string;
toggleDropDown = false;
constructor() {
}
ngAfterContentInit(): void {
this.initializeFilterModel();
if (this.filterParams) {
this.filterParamKeys = Object.keys(this.filterParams);
this.lastFilterKey = this.filterParamKeys[this.filterParamKeys.length - 1];
}
}
selectType(type: ICONTYPES) {
this.filter.filterParams.type = type;
}
clear() {
for (const key of Object.keys(this.filterParams)) {
this.filterParams[key] = '';
}
this.initializeFilterModel();
}
search() {
this.performFilter.emit(this.filter);
}
initializeFilterModel(): void {
this.filter = new FilterModel(this.filterParams);
}
checkUppercaseFilterType(filterType: string) {
return filterType === 'type' || filterType === 'state';
}
filterTypeIsWorkbasket(): boolean {
return this.filterType === TaskanaType.WORKBASKETS;
}
/**
* keys that are hardcoded in the HTML need to be specified here
* @returns {string[]}
*/
getUnusedKeys(): string[] {
const unusedKeys = [];
for (const key of this.filterParamKeys) {
if (['name', 'key'].indexOf(key) < 0) {
unusedKeys.push(key);
}
}
return unusedKeys;
}
}

View File

@ -32,6 +32,9 @@ import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
*/
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service';
import { AccessIdsService } from './services/access-ids/access-ids.service';
import {SortComponent} from 'app/shared/sort/sort.component';
import {FilterComponent} from 'app/shared/filter/filter.component';
import {IconTypeComponent} from 'app/administration/components/type-icon/icon-type.component';
import { FormsValidatorService } from './services/forms/forms-validator.service';
@ -61,6 +64,8 @@ const DECLARATIONS = [
OrderBy,
MapToIterable,
SortComponent,
FilterComponent,
IconTypeComponent,
RemoveConfirmationComponent
];

View File

@ -1,5 +1,5 @@
<div class="dropdown clearfix btn-group">
<button class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<button [disabled]="!enabled" class="btn btn-default" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<span class="glyphicon {{sort.sortDirection === 'asc'? 'glyphicon-sort-by-attributes-alt' : 'glyphicon-sort-by-attributes' }} blue"
data-toggle= "tooltip" title="{{sort.sortDirection === 'asc'? 'A-Z' : 'Z-A' }}"></span>
</button>

View File

@ -1,5 +1,5 @@
import {Component, OnInit, Output, EventEmitter, Input} from '@angular/core';
import { SortingModel, Direction } from 'app/models/sorting';
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {Direction, SortingModel} from 'app/models/sorting';
@Component({
selector: 'taskana-sort',
@ -8,12 +8,14 @@ import { SortingModel, Direction } from 'app/models/sorting';
})
export class SortComponent implements OnInit {
@Input() sortingFields: Map<string, string>;
@Input() enabled = true;
@Output() performSorting = new EventEmitter<SortingModel>();
@Output()
performSorting = new EventEmitter<SortingModel>();
sort: SortingModel = new SortingModel();
constructor() { }
constructor() {
}
ngOnInit() {
}

View File

@ -9,6 +9,14 @@ import {Direction} from 'app/models/sorting';
@Injectable()
export class TaskService {
WORKBASKET_ID = 'workbasket-id';
SORT_BY = 'sortBy';
SORT_DIRECTION = 'order';
NAME = 'name';
OWNER = 'owner';
PRIORITY = 'priority';
STATE = 'state';
url = `${environment.taskanaRestUrl}/v1/tasks`;
taskChangedSource = new Subject<Task>();
@ -35,9 +43,12 @@ export class TaskService {
*/
findTasksWithWorkbasket(basketId: string,
sortBy = 'priority',
order: string = Direction.ASC): Observable<TaskResource> {
const url = `${this.url}?workbasket-id=${basketId}&sortBy=${sortBy}&order=${order}`;
sortDirection: string = Direction.ASC,
name: string,
owner: string,
priority: string,
state: string): Observable<TaskResource> {
const url = `${this.url}${this.getTaskQueryParameters(basketId, sortBy, sortDirection, name, owner, priority, state)}`;
return this.httpClient.get<TaskResource>(url);
}
@ -64,4 +75,26 @@ export class TaskService {
deleteTask(task: Task): Observable<Task> {
return this.httpClient.delete<Task>(`${this.url}/${task.taskId}`);
}
private getTaskQueryParameters(basketId: string,
sortBy: string,
sortDirection: string,
name: string,
owner: string,
priority: string,
state: string): string {
let query = '?';
query += basketId ? `${this.WORKBASKET_ID}=${basketId}&` : '';
query += `${this.SORT_BY}=${sortBy}&`;
query += `${this.SORT_DIRECTION}=${sortDirection}&`;
query += name ? `${this.NAME}=${name}&` : '';
query += owner ? `${this.OWNER}=${owner}&` : '';
query += priority ? `${this.PRIORITY}=${priority}&` : '';
query += state ? `${this.STATE}=${state}&` : '';
if (query.lastIndexOf('&') === query.length - 1) {
query = query.slice(0, query.lastIndexOf('&'))
}
return query;
}
}

View File

@ -30,6 +30,23 @@
<input type="text" class="form-control" id="task-owner" placeholder="Owner" [(ngModel)]="task.owner"
name="task.owner">
</div>
<div class="form-group">
<label for="task-priority" disabled class="control-label">Priority</label>
<input type="text" class="form-control" id="task-priority" placeholder="no priotity set"
[(ngModel)]="task.priority"
name="task.priority">
</div>
<div class="form-group">
<label for="task-state" class="control-label">State</label>
<input type="text" disabled class="form-control" id="task-state" placeholder="Task has no State"
[(ngModel)]="task.state"
name="task.state">
</div>
<div class="form-group">
<label for="task-note" class="control-label">Note</label>
<input type="text" class="form-control" id="task-note" placeholder="Task has no Note" [(ngModel)]="task.note"
name="task.note">
</div>
<div class="form-group">
<label for="task-created" class="control-label">Creation Date</label>
<input type="text" disabled class="form-control" id="task-created" [(ngModel)]="task.created"
@ -65,23 +82,6 @@
[(ngModel)]="task.due"
name="task.due">
</div>
<div class="form-group">
<label for="task-priority" disabled class="control-label">Priority</label>
<input type="text" class="form-control" id="task-priority" placeholder="no priotity set"
[(ngModel)]="task.priority"
name="task.priority">
</div>
<div class="form-group">
<label for="task-state" class="control-label">State</label>
<input type="text" disabled class="form-control" id="task-state" placeholder="Task has no State"
[(ngModel)]="task.state"
name="task.state">
</div>
<div class="form-group">
<label for="task-note" class="control-label">Note</label>
<input type="text" class="form-control" id="task-note" placeholder="Task has no Note" [(ngModel)]="task.note"
name="task.note">
</div>
<div class="form-group">
<label for="task-workbasket" class="control-label">Workbasket ID</label>
<input type="text" disabled class="form-control" id="task-workbasket"
@ -90,10 +90,10 @@
name="task.workbasket">
</div>
<div class="form-group">
<label for="task-classification" class="control-label">Classification ID</label>
<label for="task-classification" class="control-label">Classification Key</label>
<input type="text" disabled class="form-control" id="task-classification"
placeholder="Task does not belong to a Classification"
[(ngModel)]="task.classificationSummaryResource.classificationId"
[(ngModel)]="task.classificationSummaryResource.key"
name="task.classification">
</div>
<div class="form-group">

View File

@ -1,17 +1,35 @@
<div class="row">
<div class="col-md-6">
<div class="input-group">
<input [(ngModel)]="result" [typeahead]="workbasketNames" class="form-control" (typeaheadOnSelect)="workbasketSelected = true"
typeaheadMinLength="0" (typeaheadNoResults)="workbasketSelected = false" placeholder="Search for Workbasket ..." />
<span class="input-group-btn">
<button class="btn btn-primary" type="button" (click)="searchBasket()" [disabled]="!workbasketSelected">Go!</button>
<li id="tasklist-action-toolbar" class="list-group-item tab-align">
<div class="row">
<div class="col-xs-9">
<div class="input-group">
<input [(ngModel)]="result" [typeahead]="workbasketNames" class="form-control"
(typeaheadOnSelect)="workbasketSelected = true" (typeaheadNoResults)="workbasketSelected = false"
placeholder="Search for Workbasket ..."/>
<span class="input-group-btn">
<button class="btn btn-primary" type="button" (click)="searchBasket()"
[disabled]="!workbasketSelected">Go!</button>
</span>
</div>
</div>
<div class="pull-right margin-right">
<taskana-sort
[enabled]="tasks.length > 0"
[sortingFields]="sortingFields"
(performSorting)="sorting($event)"></taskana-sort>
<button [disabled]="tasks.length === 0" class="btn btn-default collapsed" type="button"
id="collapsedMenufilterWb" aria-expanded="false"
(click)="toolbarState=!toolbarState">
<span class="glyphicon glyphicon-filter blue"></span>
</button>
</div>
</div>
<div class="col-md-6">
<div class="pull-right margin-right">
<taskana-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)"></taskana-sort>
</div>
<div [@toggle]="toolbarState" *ngIf="toolbarState" class="row no-overflow">
<taskana-filter
[filterParams]="filterParams"
[filterType]="filterType"
(performFilter)="filtering($event)">
</taskana-filter>
</div>
</div>
</li>

View File

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

View File

@ -1,21 +1,39 @@
import {Component, EventEmitter, OnInit, Output} from '@angular/core';
import {animate, keyframes, style, transition, trigger} from '@angular/animations';
import {Task} from 'app/workplace/models/task';
import {Workbasket} from 'app/models/workbasket';
import {TaskService} from 'app/workplace/services/task.service';
import {WorkbasketService} from 'app/services/workbasket/workbasket.service';
import {SortingModel} from 'app/models/sorting';
import {FilterModel} from 'app/models/filter';
import {TaskanaType} from 'app/models/taskana-type';
@Component({
selector: 'taskana-tasklist-toolbar',
templateUrl: './tasklist-toolbar.component.html'
animations: [
trigger('toggle', [
transition('void => *', animate('300ms ease-in', keyframes([
style({height: '0px'}),
style({height: '50px'}),
style({height: '*'})]))),
transition('* => void', animate('300ms ease-out', keyframes([
style({height: '*'}),
style({height: '50px'}),
style({height: '0px'})])))
]
)],
templateUrl: './tasklist-toolbar.component.html',
styleUrls: ['./tasklist-toolbar.component.scss']
})
export class TaskListToolbarComponent implements OnInit {
@Output() tasksChanged = new EventEmitter<Task[]>();
@Output() basketChanged = new EventEmitter<Workbasket>();
@Output() performSorting = new EventEmitter<SortingModel>();
@Output() performFilter = new EventEmitter<FilterModel>();
sortingFields = new Map([['name', 'Name'], ['priority', 'Priority'], ['due', 'Due'], ['planned', 'Planned']]);
filterParams = {name: '', key: '', owner: '', priority: '', state: ''};
tasks: Task[] = [];
workbasketNames: string[] = [];
@ -24,6 +42,8 @@ export class TaskListToolbarComponent implements OnInit {
workbaskets: Workbasket[];
currentBasket: Workbasket;
workbasketSelected = false;
toolbarState = false;
filterType = TaskanaType.TASKS;
constructor(private taskService: TaskService,
private workbasketService: WorkbasketService) {
@ -39,6 +59,7 @@ export class TaskListToolbarComponent implements OnInit {
}
searchBasket() {
this.toolbarState = false;
if (this.workbaskets) {
this.workbaskets.forEach(workbasket => {
if (workbasket.name === this.result) {
@ -60,7 +81,8 @@ export class TaskListToolbarComponent implements OnInit {
}
getTasks(workbasketId: string) {
this.taskService.findTasksWithWorkbasket(workbasketId).subscribe(
this.taskService.findTasksWithWorkbasket(workbasketId, undefined, undefined,
undefined, undefined, undefined, undefined).subscribe(
tasks => {
this.tasks.length = 0;
if (!tasks || tasks._embedded === undefined) {
@ -74,4 +96,8 @@ export class TaskListToolbarComponent implements OnInit {
sorting(sort: SortingModel) {
this.performSorting.emit(sort);
}
filtering(filterBy: FilterModel) {
this.performFilter.emit(filterBy);
}
}

View File

@ -1,6 +1,8 @@
<div class="task-list-full-height">
<taskana-tasklist-toolbar (tasksChanged)="loadTasks($event)" (basketChanged)="loadBasketID($event)"
(performSorting)="performSorting($event)"></taskana-tasklist-toolbar>
(performSorting)="performSorting($event)"
(performFilter)="performFilter($event)">
</taskana-tasklist-toolbar>
<div *ngIf="!requestInProgress">
<ul #taskList id="task-list-container" class="list-group">
<li class="list-group-item" *ngIf="tasks === undefined || tasks.length === 0" type="text">
@ -10,10 +12,8 @@
type="text" (click)="selectTask(task.taskId)">
<div class="row">
<dl class="col-xs-10">
<dt data-toggle="tooltip" title="{{task.name}}">{{task.name}},
<i data-toggle="tooltip" title="{{task.taskId}}">{{task.taskId}} </i>
</dt>
<dd data-toggle="tooltip" title="{{task.due}}">Due: {{task.due}} &nbsp;</dd>
<dt data-toggle="tooltip" title="{{task.name}}">{{task.name}}</dt>
<dd data-toggle="tooltip" title="{{task.owner}}">Owner: {{task.owner}} &nbsp;</dd>
<dd data-toggle="tooltip" title="{{task.priority}}">Priority: {{task.priority}} &nbsp;</dd>
<dd data-toggle="tooltip" title="{{task.state}}">State: {{task.state}} &nbsp;</dd>
</dl>

View File

@ -5,6 +5,7 @@ import {TaskService} from 'app/workplace/services/task.service';
import {Subscription} from 'rxjs/Subscription';
import {SortingModel} from 'app/models/sorting';
import {Workbasket} from 'app/models/workbasket';
import {FilterModel} from 'app/models/filter';
@Component({
selector: 'taskana-task-list',
@ -17,7 +18,16 @@ export class TasklistComponent implements OnInit, OnDestroy {
currentBasket: Workbasket;
selectedId = '';
sort: SortingModel = new SortingModel();
sort: SortingModel = new SortingModel('priority');
filterBy: FilterModel = new FilterModel({
name: '',
owner: '',
priority: '',
state: '',
classificationKey: '',
workbasketId: '',
workbasketKey: ''
});
requestInProgress = false;
private taskChangeSubscription: Subscription;
@ -63,13 +73,20 @@ export class TasklistComponent implements OnInit, OnDestroy {
this.getTasks();
}
performFilter(filterBy: FilterModel) {
this.filterBy = filterBy;
this.getTasks();
}
getTasks(): void {
this.requestInProgress = true;
this.taskService.findTasksWithWorkbasket(this.currentBasket.workbasketId, this.sort.sortBy, this.sort.sortDirection)
this.taskService.findTasksWithWorkbasket(this.currentBasket.workbasketId, this.sort.sortBy, this.sort.sortDirection,
this.filterBy.filterParams.name, this.filterBy.filterParams.owner, this.filterBy.filterParams.priority,
this.filterBy.filterParams.state)
.subscribe(tasks => {
this.requestInProgress = false;
this.tasks = tasks._embedded ? tasks._embedded.tasks : this.tasks;
})
});
}
ngOnDestroy(): void {