bug/TSK-809: Duplicated http call when selecting a task in workplaces

Fix the problem that the list doesnt show every classification

Create a new component (dropdown)

Change subscription to promise asyn/away

change location getclassification and add a test
This commit is contained in:
Jose Ignacio Recuerda Cambil 2019-03-12 11:10:09 +01:00 committed by Martin Rojas Miguel Angel
parent f34aeaae77
commit 8d63d775f9
14 changed files with 211 additions and 137 deletions

82
web/package-lock.json generated
View File

@ -504,8 +504,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -526,14 +525,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -548,20 +545,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -678,8 +672,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -691,7 +684,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -706,7 +698,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -714,14 +705,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -740,7 +729,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -821,8 +809,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -834,7 +821,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -920,8 +906,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -957,7 +942,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -977,7 +961,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -1021,14 +1004,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@ -4808,8 +4789,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@ -4830,14 +4810,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4852,20 +4830,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@ -4982,8 +4957,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@ -4995,7 +4969,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -5010,7 +4983,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -5018,14 +4990,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -5044,7 +5014,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -5125,8 +5094,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -5138,7 +5106,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -5224,8 +5191,7 @@
"safe-buffer": {
"version": "5.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@ -5261,7 +5227,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -5281,7 +5246,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -5325,14 +5289,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},

View File

@ -1,7 +1,6 @@
import { Classification } from './classification';
import { Links } from './links';
export class ClassificationResource {
constructor(
public _embedded: {

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { Observable, Subject, combineLatest } from 'rxjs';
import { combineLatest, Observable, Subject} from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';
import { Classification } from 'app/models/classification';
@ -20,6 +20,8 @@ export class ClassificationsService {
private url = `${environment.taskanaRestUrl}/v1/classifications/`;
private classificationSelected = new Subject<ClassificationDefinition>();
private classificationSaved = new Subject<number>();
private classificationResourcePromise: Promise<ClassificationResource>;
private lastDomain: string;
constructor(
private httpClient: HttpClient,
@ -39,6 +41,16 @@ export class ClassificationsService {
)
}
// GET
getClassificationsByDomain(domain: string, forceRefresh = false): Promise<ClassificationResource> {
if (this.lastDomain !== domain || !this.classificationResourcePromise || forceRefresh) {
this.lastDomain = domain;
this.classificationResourcePromise = this.httpClient.get<ClassificationResource>(
`${this.url}${TaskanaQueryParameters.getQueryParameters(this.classificationParameters(domain))}`).toPromise();
}
return this.classificationResourcePromise;
}
// GET
getClassification(id: string): Observable<ClassificationDefinition> {
return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`)
@ -49,7 +61,6 @@ export class ClassificationsService {
}));
}
// POST
postClassification(classification: Classification): Observable<Classification> {
return this.httpClient.post<Classification>(`${this.url}`, classification);
@ -72,7 +83,6 @@ export class ClassificationsService {
getSelectedClassification(): Observable<ClassificationDefinition> {
return this.classificationSelected.asObservable();
}
triggerClassificationSaved() {
@ -97,7 +107,7 @@ export class ClassificationsService {
return parameters;
}
private getClassificationObservable(classificationRef: Observable<any>): Observable<any> {
private getClassificationObservable(classificationRef: Observable<any>): Observable<Array<Classification>> {
const classificationTypes = this.classificationCategoriesService.getSelectedClassificationType();
return combineLatest(
classificationRef,
@ -111,7 +121,7 @@ export class ClassificationsService {
)
}
private buildHierarchy(classifications: Array<Classification>, type: string) {
private buildHierarchy(classifications: Array<Classification>, type: string): Array<Classification> {
const roots = [];
const children = [];
@ -139,4 +149,3 @@ export class ClassificationsService {
}
}
}

View File

@ -0,0 +1,18 @@
<div class="input-group dropdown" dropdown>
<button class="btn btn-default dropdown-toggle" type="button" dropdownToggle
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" name="">
{{ itemSelected?.name }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu height-and-scroll">
<li *ngFor="let item of list" (click)="selectItem(item)">
<a>
<label>
<span class="material-icons md-20 blue ">{{item.key === itemSelected?.key ?
'check_box' : 'check_box_outline_blank'}} </span>
<span> {{item.name}} </span>
</label>
</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,4 @@
.height-and-scroll {
max-height: 300px;
overflow-y: auto;
}

View File

@ -0,0 +1,44 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {DropdownComponent} from './dropdown.component';
describe('DropdownComponent', () => {
let component: DropdownComponent;
let fixture: ComponentFixture<DropdownComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [DropdownComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DropdownComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should have values', () => {
component.list = new Array<any>('first', 'second');
component.itemSelected = 'second';
expect(component.list).not.toBeNull();
expect(component.list).not.toBeNaN();
expect(component.itemSelected).not.toBeNull();
expect(component.itemSelected).not.toBeNaN();
});
it('should have the correct item selected', () => {
component.list = new Array<any>('first', 'second');
component.selectItem('first');
expect(component.itemSelected).toBe('first');
component.selectItem('second');
expect(component.itemSelected).toBe('second');
});
});

View File

@ -0,0 +1,26 @@
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
import {SortingModel} from '../../models/sorting';
import {Classification} from '../../models/classification';
@Component({
selector: 'taskana-dropdown',
templateUrl: './dropdown.component.html',
styleUrls: ['./dropdown.component.scss']
})
export class DropdownComponent implements OnInit {
@Input() itemSelected: any;
@Input() list: Array<any>;
@Output() performClassification = new EventEmitter<any>();
constructor() {
}
ngOnInit(): void {
}
selectItem(item: any) {
this.itemSelected = item;
this.performClassification.emit(item);
}
}

View File

@ -26,6 +26,7 @@ import {PaginationComponent} from './pagination/pagination.component';
import {NumberPickerComponent} from './number-picker/number-picker.component';
import {ProgressBarComponent} from './progress-bar/progress-bar.component';
import {DatePickerComponent} from './date-picker/date-picker.component';
import {DropdownComponent} from './dropdown/dropdown.component';
/**
* Pipes
@ -82,7 +83,8 @@ const DECLARATIONS = [
PaginationComponent,
NumberPickerComponent,
ProgressBarComponent,
DatePickerComponent
DatePickerComponent,
DropdownComponent
];
@NgModule({

View File

@ -57,25 +57,8 @@
</div>
<div class="row">
<div class="form-group col-xs-6 required">
<label for="wb-type" class="control-label">Classification</label>
<div class="input-group" dropdown>
<button class="btn btn-default" type="button" dropdownToggle id="task.classificationSummaryResource"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="true" name="">
{{task.classificationSummaryResource?.name }}
<span class="caret"></span>
</button>
<ul class="dropdown-menu" *dropdownMenu>
<li *ngFor="let classification of classifications" (click)="selectClassification(classification)">
<a>
<label>
<span class="material-icons md-20 blue ">{{classification.name === task.classificationSummaryResource?.name ?
'check_box': 'check_box_outline_blank'}} </span>
<span> {{classification.name}} </span>
</label>
</a>
</li>
</ul>
</div>
<label for="classification" class="control-label">Classification</label>
<taskana-dropdown [list]="classifications" (performClassification)="changedClassification($event)" [itemSelected]="task?.classificationSummaryResource" id="classification"></taskana-dropdown>
</div>
<div class="form-group col-xs-4">
<label for="task-due" class="control-label">Due date</label>

View File

@ -1,21 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {ComponentFixture, TestBed} from '@angular/core/testing';
import { TaskdetailsGeneralFieldsComponent } from './general-fields.component';
import { FormsModule } from '@angular/forms';
import { ClassificationsService } from 'app/services/classifications/classifications.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { ClassificationCategoriesService } from 'app/services/classifications/classification-categories.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { DomainService } from 'app/services/domain/domain.service';
import { RouterTestingModule } from '@angular/router/testing';
import { Routes } from '@angular/router';
import { Component } from '@angular/core';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { SelectedRouteService } from 'app/services/selected-route/selected-route';
import {TaskdetailsGeneralFieldsComponent} from './general-fields.component';
import {FormsModule} from '@angular/forms';
import {HttpClient, HttpClientModule} from '@angular/common/http';
import {ClassificationCategoriesService} from 'app/services/classifications/classification-categories.service';
import {CustomFieldsService} from 'app/services/custom-fields/custom-fields.service';
import {DomainService} from 'app/services/domain/domain.service';
import {RouterTestingModule} from '@angular/router/testing';
import {Routes} from '@angular/router';
import {Component} from '@angular/core';
import {RequestInProgressService} from 'app/services/requestInProgress/request-in-progress.service';
import {SelectedRouteService} from 'app/services/selected-route/selected-route';
import {ClassificationsService} from '../../../services/classifications/classifications.service';
import {configureTests} from '../../../app.test.configuration';
import {Classification} from '../../../models/classification';
import {Links} from '../../../models/links';
import {ClassificationResource} from '../../../models/classification-resource';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
@ -24,20 +28,35 @@ export class DummyDetailComponent {
xdescribe('GeneralComponent', () => {
let component: TaskdetailsGeneralFieldsComponent;
let fixture: ComponentFixture<TaskdetailsGeneralFieldsComponent>;
let classificationsService;
const routes: Routes = [
{ path: '*', component: DummyDetailComponent }
];
{path: '*', component: DummyDetailComponent}
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes)],
declarations: [TaskdetailsGeneralFieldsComponent, DummyDetailComponent],
providers: [ClassificationsService, HttpClient, ClassificationCategoriesService, CustomFieldsService,
DomainService, RequestInProgressService, SelectedRouteService]
})
.compileComponents();
}));
beforeEach(done => {
const configure = (testBed: TestBed) => {
TestBed.configureTestingModule({
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes)],
declarations: [TaskdetailsGeneralFieldsComponent, DummyDetailComponent],
providers: [HttpClient, ClassificationCategoriesService, CustomFieldsService,
DomainService, RequestInProgressService, SelectedRouteService, ClassificationsService]
})
};
configureTests(configure).then(testBed => {
classificationsService = TestBed.get(ClassificationsService);
spyOn(classificationsService, 'getClassificationsByDomain').and.returnValue(new ClassificationResource(
{
'classificationSummaryResourceList': new Array<Classification>(
new Classification('id1', '1', 'category', 'type', 'domain_a', 'classification1', 'parentId',
1, 'service', new Links({ 'href': 'someurl' })),
new Classification('id2', '2', 'category', 'type', 'domain_a', 'classification2', 'parentId2',
1, 'service', new Links({ 'href': 'someurl' })))
}, new Links({ 'href': 'someurl' })
));
done();
});
});
beforeEach(() => {
fixture = TestBed.createComponent(TaskdetailsGeneralFieldsComponent);
@ -48,4 +67,10 @@ xdescribe('GeneralComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
it('should call to getClassificationsByDomain', done => {
component.ngOnInit();
expect(classificationsService.getClassificationsByDomain).toHaveBeenCalled();
done();
});
});

View File

@ -1,10 +1,11 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewChild, SimpleChanges, OnChanges, HostListener } from '@angular/core';
import { Task } from 'app/workplace/models/task';
import { Classification } from '../../../models/classification';
import { ClassificationsService } from '../../../services/classifications/classifications.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { FormsValidatorService } from 'app/shared/services/forms/forms-validator.service';
import { NgForm } from '@angular/forms';
import {Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild} from '@angular/core';
import {Task} from 'app/workplace/models/task';
import {Classification} from '../../../models/classification';
import {ClassificationsService} from '../../../services/classifications/classifications.service';
import {CustomFieldsService} from 'app/services/custom-fields/custom-fields.service';
import {FormsValidatorService} from 'app/shared/services/forms/forms-validator.service';
import {NgForm} from '@angular/forms';
import {DomainService} from '../../../services/domain/domain.service';
@Component({
selector: 'taskana-task-details-general-fields',
@ -21,8 +22,6 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges {
saveToggleTriggered: boolean;
@Output() formValid: EventEmitter<boolean> = new EventEmitter<boolean>();
@Output() classificationsReceived: EventEmitter<Classification[]> = new EventEmitter<Classification[]>();
@ViewChild('TaskForm')
taskForm: NgForm;
@ -38,7 +37,12 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges {
constructor(
private classificationService: ClassificationsService,
private customFieldsService: CustomFieldsService,
private formsValidatorService: FormsValidatorService) {
private formsValidatorService: FormsValidatorService,
private domainService: DomainService) {
}
ngOnInit() {
this.getClassificationByDomain();
}
ngOnChanges(changes: SimpleChanges): void {
@ -47,20 +51,10 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges {
}
}
ngOnInit() {
this.requestInProgress = true;
this.classificationService.getClassifications().subscribe(classificationList => {
this.requestInProgress = false;
this.classifications = classificationList;
this.classificationsReceived.emit(this.classifications);
});
}
selectClassification(classification: Classification) {
this.task.classificationSummaryResource = classification;
}
isFieldValid(field: string): boolean {
return this.formsValidatorService.isFieldValid(this.taskForm, field);
}
@ -81,4 +75,15 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges {
}
});
}
private changedClassification(itemSelected: any) {
this.task.classificationSummaryResource = itemSelected;
}
private async getClassificationByDomain() {
this.requestInProgress = true;
this.classifications = (await this.classificationService.getClassificationsByDomain(this.domainService.getSelectedDomainValue()))
._embedded.classificationSummaryResourceList;
this.requestInProgress = false;
}
}

View File

@ -95,7 +95,6 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
this.onSave();
}
openTask() {
this.router.navigate([{outlets: {detail: `task/${this.currentId}`}}], {relativeTo: this.route.parent});
}
@ -124,7 +123,6 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
this.tabSelected = tab;
}
backClicked(): void {
this.task = undefined;
this.taskService.selectTask(this.task);

View File

@ -41,7 +41,6 @@ export class TaskListToolbarComponent implements OnInit {
toolbarState = false;
filterType = TaskanaType.TASKS;
searched = false;
searchParam = 'search';
search = Search;
searchSelected: Search = Search.byWorkbasket;

View File

@ -2,7 +2,7 @@
border-bottom-right-radius: 0;
border-top-right-radius: 0;
}
.btn-group ul + .btn, {
.btn-group ul + .btn {
margin-left: -1px;
}