bug/813 Fix performance issue while editing tasks details

This commit is contained in:
Martin Rojas Miguel Angel 2019-03-07 11:02:02 +01:00 committed by Holger Hagen
parent 774c65f338
commit 63c5f1d216
20 changed files with 231 additions and 168 deletions

View File

@ -17,28 +17,16 @@ export class TaskService {
taskChangedSource = new Subject<Task>();
taskChangedStream = this.taskChangedSource.asObservable();
taskDeletedSource = new Subject<Task>();
taskDeletedStream = this.taskDeletedSource.asObservable();
taskAddedSource = new Subject<Task>();
taskAddedStream = this.taskAddedSource.asObservable();
taskSelectedSource = new Subject<Task>();
taskSelectedStream = this.taskSelectedSource.asObservable();
constructor(private httpClient: HttpClient) {
}
publishUpdatedTask(task: Task) {
publishUpdatedTask(task: Task = undefined) {
this.taskChangedSource.next(task);
}
publishDeletedTask(task: Task) {
this.taskDeletedSource.next(task);
}
publishAddedTask(task: Task) {
this.taskAddedSource.next(task);
}
selectTask(task: Task) {
this.taskSelectedSource.next(task);
}

View File

@ -1,5 +1,5 @@
<ng-container *ngIf="task && !requestInProgress">
<form #TaskForm="ngForm">
<form #TaskForm="ngForm" autocomplete="off">
<div class="col-md-6">
<div class="row">
<div class="form-group col-xs-6 required">

View File

@ -61,16 +61,6 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges {
this.task.classificationSummaryResource = classification;
}
validate() {
this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService
.validateFormInformation(this.taskForm, this.toogleValidationMap)
.then(value => {
if (value) {
this.formValid.emit(true);
}
});
}
isFieldValid(field: string): boolean {
return this.formsValidatorService.isFieldValid(this.taskForm, field);
@ -82,4 +72,14 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges {
}
}
private validate() {
this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService
.validateFormInformation(this.taskForm, this.toogleValidationMap)
.then(value => {
if (value) {
this.formValid.emit(true);
}
});
}
}

View File

@ -34,6 +34,7 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
private routeSubscription: Subscription;
private workbasketSubscription: Subscription;
private masterAndDetailSubscription: Subscription;
private deleteTaskSubscription: Subscription;
constructor(private route: ActivatedRoute,
private taskService: TaskService,
@ -109,10 +110,14 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
}
deleteTaskConfirmation(): void {
this.taskService.deleteTask(this.task).subscribe();
this.taskService.publishDeletedTask(this.task);
this.task = null;
this.router.navigate([`/workplace/tasks`]);
this.deleteTaskSubscription = this.taskService.deleteTask(this.task).subscribe(() => {
this.taskService.publishUpdatedTask();
this.task = null;
this.router.navigate([`/workplace/tasks`]);
}, err => {
this.generalModalService.triggerMessage(
new MessageModal('An error occurred while deleting the task ', err));
});
}
selectTab(tab: string): void {
@ -152,7 +157,7 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Task ${this.currentId} was created successfully.`));
this.task = task;
this.taskService.selectTask(this.task);
this.taskService.publishAddedTask(task);
this.taskService.publishUpdatedTask(task);
this.router.navigate(['../' + task.taskId], { relativeTo: this.route });
}, err => {
this.requestInProgressService.setRequestInProgress(false);
@ -174,14 +179,9 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
}
ngOnDestroy(): void {
if (this.routeSubscription) {
this.routeSubscription.unsubscribe();
if (this.workbasketSubscription) {
this.workbasketSubscription.unsubscribe();
}
if (this.masterAndDetailSubscription) {
this.masterAndDetailSubscription.unsubscribe();
}
}
if (this.routeSubscription) { this.routeSubscription.unsubscribe(); }
if (this.workbasketSubscription) { this.workbasketSubscription.unsubscribe(); }
if (this.masterAndDetailSubscription) { this.masterAndDetailSubscription.unsubscribe(); }
if (this.deleteTaskSubscription) { this.deleteTaskSubscription.unsubscribe(); }
}
}

View File

@ -1,48 +0,0 @@
<div class="footer-space-pagination-list">
<div #wbToolbar>
<taskana-tasklist-toolbar (performSorting)="performSorting($event)" (performFilter)="performFilter($event)"
(selectSearchType)="selectSearchType($event)">
</taskana-tasklist-toolbar>
</div>
<div *ngIf="!requestInProgress">
<div *ngIf="(tasks && tasks.length > 0); else empty_list">
<ul #taskList id="task-list-container" class="list-group">
<li class="list-group-item" *ngFor="let task of tasks" [class.active]="task.taskId == selectedId" type="text"
(click)="selectTask(task.taskId)">
<div class="row">
<dl class="col-xs-2">
<span data-toggle="tooltip" class="badge" [ngClass]="{
'badge-red': task.priority <=5,
'badge-orange': (task.priority > 5 && task.priority <=15),
'badge-green': task.priority > 15}"
title="{{task.priority}}">{{task.priority}}</span>
</dl>
<dl class="col-xs-8">
<dt>
<span data-toggle="tooltip" title="{{task.name}}">{{task.name}}</span>
<i *ngIf="task.owner" data-toggle="tooltip" title="{{task.owner}}">, {{task.owner}}</i>
</dt>
</dl>
</div>
<div class="row">
<dl class="col-xs-3 col-xs-offset-2">
<dd data-toggle="tooltip" title="{{task.state}}">{{task.state}}</dd>
</dl>
<dl class="pull-right padding-right">
<i data-toggle="tooltip" title="{{task.due}}">Due: {{displayDate(task.due)}}</i>
</dl>
</div>
</li>
</ul>
</div>
<ng-template #empty_list>
<div class="col-xs-12 container-no-items center-block">
<h3 class="grey">Select a workbasket</h3>
<svg-icon class="img-responsive empty-icon workbasket-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
</div>
</ng-template>
</div>
</div>
<taskana-pagination [numberOfItems]="tasks.length" *ngIf="tasks && tasks.length > 0" [(page)]="tasksPageInformation" [type]="type" (changePage)="changePage($event)"></taskana-pagination>
<taskana-code></taskana-code>

View File

@ -1,16 +0,0 @@
.clickable {
cursor: pointer;
}
.list-group > li {
border-left: none;
border-right: none;
}
li > div.row > dl {
margin-bottom: 0px;
}
.list-group-item>div>dl>span.badge{
margin-left: 8px;
}

View File

@ -1,4 +1,4 @@
<li id="tasklist-action-toolbar" class="list-group-item tab-align">
<li id="task-list-action-toolbar" class="list-group-item tab-align">
<div class="row">
<div *ngIf="searched" class="col-xs-2">
<button (click)="createTask()" type="button" class="btn btn-default pull-left green-blue" data-toggle="tooltip" title="Add Task to current Workbasket">

View File

@ -59,6 +59,6 @@ button.btn.btn-primary {
height: 34px;
}
#tasklist-action-toolbar svg-icon.blue {
#task-list-action-toolbar svg-icon.blue {
position: initial;
}

View File

@ -1,4 +1,4 @@
import { TaskListToolbarComponent } from './tasklist-toolbar.component';
import { TaskListToolbarComponent } from './task-list-toolbar.component';
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { TypeaheadModule, ComponentLoaderFactory, PositioningService } from 'ngx-bootstrap';

View File

@ -16,10 +16,10 @@ export enum Search {
byTypeAndValue = 'type-and-value'
}
@Component({
selector: 'taskana-tasklist-toolbar',
selector: 'taskana-task-list-toolbar',
animations: [expandDown],
templateUrl: './tasklist-toolbar.component.html',
styleUrls: ['./tasklist-toolbar.component.scss']
templateUrl: './task-list-toolbar.component.html',
styleUrls: ['./task-list-toolbar.component.scss']
})
export class TaskListToolbarComponent implements OnInit {

View File

@ -0,0 +1,36 @@
<div *ngIf="(tasks && tasks.length > 0); else empty_list">
<ul #taskList id="task-list-container" class="list-group">
<li class="list-group-item" *ngFor="let task of tasks" [class.active]="task.taskId == selectedId" type="text"
(click)="selectTask(task.taskId)">
<div class="row">
<dl class="col-xs-2">
<span data-toggle="tooltip" class="badge" [ngClass]="{
'badge-red': task.priority <=5,
'badge-orange': (task.priority > 5 && task.priority <=15),
'badge-green': task.priority > 15}" title="{{task.priority}}">{{task.priority}}</span>
</dl>
<dl class="col-xs-8">
<dt>
<span data-toggle="tooltip" title="{{task.name}}">{{task.name}}</span>
<i *ngIf="task.owner" data-toggle="tooltip" title="{{task.owner}}">, {{task.owner}}</i>
</dt>
</dl>
</div>
<div class="row">
<dl class="col-xs-3 col-xs-offset-2">
<dd data-toggle="tooltip" title="{{task.state}}">{{task.state}}</dd>
</dl>
<dl class="pull-right padding-right">
<i data-toggle="tooltip" title="{{task.due}}">Due: {{displayDate(task.due)}}</i>
</dl>
</div>
</li>
</ul>
</div>
<ng-template #empty_list>
<div class="col-xs-12 container-no-items center-block">
<h3 class="grey">Select a workbasket</h3>
<svg-icon class="img-responsive empty-icon workbasket-icon" src="./assets/icons/wb-empty.svg"></svg-icon>
</div>
</ng-template>

View File

@ -0,0 +1,17 @@
.clickable {
cursor: pointer;
}
.list-group > li {
border-left: none;
border-right: none;
}
li > div.row > dl {
margin-bottom: 0px;
}
.list-group-item>div>dl>span.badge{
margin-left: 8px;
}

View File

@ -0,0 +1,52 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TaskListComponent } from './task-list.component';
import { FormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientModule } from '@angular/common/http';
import { Routes } from '@angular/router';
import { Component, ChangeDetectorRef } from '@angular/core';
import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { SvgIconComponent, SvgIconRegistryService } from 'angular-svg-icon';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
describe('TaskListComponent', () => {
let component: TaskListComponent;
let fixture: ComponentFixture<TaskListComponent>;
const routes: Routes = [
{ path: '*', component: DummyDetailComponent }
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
FormsModule,
RouterTestingModule.withRoutes(routes),
HttpClientModule],
declarations: [TaskListComponent, DummyDetailComponent, SvgIconComponent],
providers: [
WorkplaceService,
ChangeDetectorRef,
SvgIconRegistryService
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaskListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,52 @@
import {
Component, OnInit, Input, ChangeDetectionStrategy, Output,
EventEmitter, SimpleChanges, OnChanges, ChangeDetectorRef
} from '@angular/core';
import { Task } from 'app/workplace/models/task';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { Router, ActivatedRoute } from '@angular/router';
@Component({
selector: 'taskana-task-list',
templateUrl: './task-list.component.html',
styleUrls: ['./task-list.component.scss'],
// This is used to avoid angular detect changes automatically since displayDate is bein executed on every onChange.
// this cause a low performance in the screen.
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TaskListComponent implements OnInit, OnChanges {
@Input()
tasks: Array<Task>;
@Input()
selectedId: string;
@Output()
selectedIdChange = new EventEmitter<string>();
constructor(private router: Router,
private route: ActivatedRoute,
private workplaceService: WorkplaceService,
private changeDetector: ChangeDetectorRef) { }
ngOnInit() {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.tasks || changes.selectedId) {
this.changeDetector.detectChanges();
}
}
selectTask(taskId: string) {
this.workplaceService.selectObjectReference(undefined);
this.selectedId = taskId;
this.selectedIdChange.emit(taskId);
this.router.navigate([{ outlets: { detail: `taskdetail/${this.selectedId}` } }], { relativeTo: this.route });
}
displayDate(date: string): string {
return TaskanaDate.getDateToDisplay(date);
}
}

View File

@ -0,0 +1,11 @@
<div class="footer-space-pagination-list">
<div #wbToolbar>
<taskana-task-list-toolbar (performSorting)="performSorting($event)" (performFilter)="performFilter($event)"
(selectSearchType)="selectSearchType($event)">
</taskana-task-list-toolbar>
</div>
<taskana-task-list *ngIf="!requestInProgress" [tasks]="tasks" [(selectedId)]="selectedId"></taskana-task-list>
</div>
<taskana-pagination [numberOfItems]="tasks.length" *ngIf="tasks && tasks.length > 0" [(page)]="tasksPageInformation"
[type]="type" (changePage)="changePage($event)"></taskana-pagination>
<taskana-code></taskana-code>

View File

@ -1,7 +1,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TasklistComponent } from './tasklist.component';
import { TaskListToolbarComponent } from './tasklist-toolbar/tasklist-toolbar.component';
import { TaskMasterComponent } from './task-master.component';
import { TaskListToolbarComponent } from './task-list-toolbar/task-list-toolbar.component';
import { SvgIconComponent, SvgIconRegistryService } from 'angular-svg-icon';
import { PaginationComponent } from 'app/shared/pagination/pagination.component';
import { CodeComponent } from '../components/code/code.component';
@ -12,8 +12,6 @@ import { FilterComponent } from 'app/shared/filter/filter.component';
import { SpreadNumberPipe } from 'app/shared/pipes/spreadNumber/spread-number';
import { MapValuesPipe } from 'app/shared/pipes/mapValues/map-values.pipe';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { RouterTestingModule } from '@angular/router/testing';
import { Routes } from '@angular/router';
import { Component } from '@angular/core';
import { TaskService } from '../services/task.service';
import { HttpClient, HttpClientModule } from '@angular/common/http';
@ -27,37 +25,34 @@ import { SelectedRouteService } from 'app/services/selected-route/selected-route
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
selector: 'taskana-dummy-detail',
template: 'dummydetail'
})
export class DummyDetailComponent {
}
// TODO: test pending to test. Failing random
xdescribe('TasklistComponent', () => {
let component: TasklistComponent;
let fixture: ComponentFixture<TasklistComponent>;
xdescribe('TaskMasterComponent', () => {
let component: TaskMasterComponent;
let fixture: ComponentFixture<TaskMasterComponent>;
const routes: Routes = [
{ path: '*', component: DummyDetailComponent }
];
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, TypeaheadModule, RouterTestingModule.withRoutes(routes),
imports: [FormsModule, TypeaheadModule,
HttpClientModule, BrowserAnimationsModule],
declarations: [TasklistComponent, TaskListToolbarComponent, SvgIconComponent,
declarations: [TaskMasterComponent, TaskListToolbarComponent, SvgIconComponent,
PaginationComponent, CodeComponent, SortComponent, FilterComponent,
SpreadNumberPipe, MapValuesPipe, IconTypeComponent, DummyDetailComponent],
providers: [TaskService, HttpClient, WorkplaceService, AlertService, OrientationService,
WorkbasketService, DomainService, RequestInProgressService, SelectedRouteService,
ComponentLoaderFactory, PositioningService, SvgIconRegistryService]
})
.compileComponents();
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TasklistComponent);
fixture = TestBed.createComponent(TaskMasterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit, ViewChild, ElementRef } from '@angular/core';
import { Task } from 'app/workplace/models/task';
import { ActivatedRoute, Router } from '@angular/router';
import { TaskService } from 'app/workplace/services/task.service';
import { Subscription } from 'rxjs';
import { SortingModel } from 'app/models/sorting';
@ -13,16 +12,15 @@ import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { OrientationService } from 'app/services/orientation/orientation.service';
import { Orientation } from 'app/models/orientation';
import { Page } from 'app/models/page';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { ObjectReference } from '../models/object-reference';
import { Search } from './tasklist-toolbar/tasklist-toolbar.component';
import { Search } from './task-list-toolbar/task-list-toolbar.component';
@Component({
selector: 'taskana-task-list',
templateUrl: './tasklist.component.html',
styleUrls: ['./tasklist.component.scss']
selector: 'taskana-task-master',
templateUrl: './task-master.component.html',
styleUrls: ['./task-master.component.scss']
})
export class TasklistComponent implements OnInit, OnDestroy {
export class TaskMasterComponent implements OnInit, OnDestroy {
tasks: Task[];
tasksPageInformation: Page;
@ -51,37 +49,23 @@ export class TasklistComponent implements OnInit, OnDestroy {
private workbasketChangeSubscription: Subscription;
private orientationSubscription: Subscription;
private objectReferenceSubscription: Subscription;
constructor(private router: Router,
private route: ActivatedRoute,
constructor(
private taskService: TaskService,
private workplaceService: WorkplaceService,
private alertService: AlertService,
private orientationService: OrientationService) {
this.taskChangeSubscription = this.taskService.taskChangedStream.subscribe(task => {
for (let i = 0; i < this.tasks.length; i++) {
if (this.tasks[i].taskId === task.taskId) {
this.tasks[i] = task;
}
}
});
this.taskDeletedSubscription = this.taskService.taskDeletedStream.subscribe(task => {
for (let i = 0; i < this.tasks.length; i++) {
if (this.tasks[i].taskId === task.taskId) {
this.tasks.splice(i, 1);
}
}
this.getTasks();
this.selectedId = task ? task.taskId : undefined;
});
this.workbasketChangeSubscription = this.workplaceService.workbasketSelectedStream.subscribe(workbasket => {
this.currentBasket = workbasket;
if (this.selectedSearchType === Search.byWorkbasket) {
this.getTasks();
}
});
this.taskAddedSubscription = this.taskService.taskAddedStream.subscribe(task => {
this.getTasks();
this.selectedId = task.taskId;
});
this.objectReferenceSubscription = this.workplaceService.objectReferenceSelectedStream.subscribe(objectReference => {
this.objectReference = objectReference;
this.currentBasket = undefined;
@ -107,11 +91,6 @@ export class TasklistComponent implements OnInit, OnDestroy {
})
}
selectTask(taskId: string) {
this.workplaceService.selectObjectReference(undefined);
this.selectedId = taskId;
this.router.navigate([{ outlets: { detail: `taskdetail/${this.selectedId}` } }], { relativeTo: this.route });
}
performSorting(sort: SortingModel) {
this.sort = sort;
@ -133,12 +112,12 @@ export class TasklistComponent implements OnInit, OnDestroy {
this.getTasks();
}
refreshWorkbasketList() {
private refreshWorkbasketList() {
this.calculateHeightCard();
this.getTasks();
}
calculateHeightCard() {
private calculateHeightCard() {
if (this.toolbarElement) {
const toolbarSize = this.toolbarElement.nativeElement.offsetHeight;
const cardHeight = 53;
@ -150,7 +129,7 @@ export class TasklistComponent implements OnInit, OnDestroy {
}
}
getTasks(): void {
private getTasks(): void {
this.requestInProgress = true;
if (this.currentBasket === undefined && !this.objectReference) {
this.requestInProgress = false;
@ -174,10 +153,6 @@ export class TasklistComponent implements OnInit, OnDestroy {
}
}
displayDate(date: string): string {
return TaskanaDate.getDateToDisplay(date);
}
ngOnDestroy(): void {
this.taskChangeSubscription.unsubscribe();
this.taskDeletedSubscription.unsubscribe();

View File

@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { MasterAndDetailComponent } from '../shared/master-and-detail/master-and-detail.component';
import { TaskComponent } from './task/task.component';
import { TaskdetailsComponent } from './taskdetails/taskdetails.component';
import { TasklistComponent } from './tasklist/tasklist.component';
import { TaskMasterComponent } from './taskmaster/task-master.component';
const routes: Routes = [
{
@ -12,7 +12,7 @@ const routes: Routes = [
children: [
{
path: '',
component: TasklistComponent,
component: TaskMasterComponent,
outlet: 'master'
},
{

View File

@ -6,8 +6,8 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { WorkplaceRoutingModule } from './workplace-routing.module';
import { AlertModule, TypeaheadModule } from 'ngx-bootstrap';
import { TaskListToolbarComponent } from './tasklist/tasklist-toolbar/tasklist-toolbar.component';
import { TasklistComponent } from './tasklist/tasklist.component';
import { TaskListToolbarComponent } from './taskmaster/task-list-toolbar/task-list-toolbar.component';
import { TaskMasterComponent } from './taskmaster/task-master.component';
import { TaskdetailsComponent } from './taskdetails/taskdetails.component';
import { TaskdetailsGeneralFieldsComponent } from './taskdetails/general/general-fields.component';
import { TaskdetailsCustomFieldsComponent } from './taskdetails/custom/custom-fields.component';
@ -17,6 +17,7 @@ import { CodeComponent } from './components/code/code.component';
import { GeneralFieldsExtensionComponent } from './taskdetails/general-fields-extension/general-fields-extension.component';
import { AccordionModule } from 'ngx-bootstrap/accordion';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { TaskListComponent } from './taskmaster/task-list/task-list.component';
import { OrderTasksByPipe } from './util/orderTasksBy.pipe';
@ -28,7 +29,6 @@ import { CustomHttpClientInterceptor } from './services/custom-http-interceptor/
import { ClassificationCategoriesService } from 'app/services/classifications/classification-categories.service';
import { WorkplaceService } from './services/workplace.service';
const MODULES = [
TypeaheadModule.forRoot(),
AccordionModule.forRoot(),
@ -44,7 +44,7 @@ const MODULES = [
const DECLARATIONS = [
TaskListToolbarComponent,
TasklistComponent,
TaskMasterComponent,
TaskdetailsComponent,
TaskdetailsGeneralFieldsComponent,
TaskdetailsCustomFieldsComponent,
@ -52,6 +52,7 @@ const DECLARATIONS = [
TaskComponent,
CodeComponent,
GeneralFieldsExtensionComponent,
TaskListComponent,
OrderTasksByPipe
];