task/827 Modify classification's parent

Create a drag and drop test
This commit is contained in:
Jose Ignacio Recuerda Cambil 2019-04-05 14:39:19 +02:00 committed by Holger Hagen
parent aea64975db
commit b09e0b789a
14 changed files with 770 additions and 879 deletions

1307
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
"@angular/router": "7.1.3", "@angular/router": "7.1.3",
"ajv": "6.5.2", "ajv": "6.5.2",
"angular-svg-icon": "6.0.0", "angular-svg-icon": "6.0.0",
"angular-tree-component": "7.1.0", "angular-tree-component": "8.2.0",
"bootstrap": "4.3.1", "bootstrap": "4.3.1",
"bootstrap-sass": "3.4.1", "bootstrap-sass": "3.4.1",
"chart.js": "2.7.2", "chart.js": "2.7.2",
@ -42,8 +42,8 @@
"zone.js": "0.8.26" "zone.js": "0.8.26"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "0.13.2", "@angular-devkit/build-angular": "^0.13.8",
"@angular/cli": "7.1.3", "@angular/cli": "^7.3.8",
"@angular/compiler-cli": "7.2.7", "@angular/compiler-cli": "7.2.7",
"@types/jasmine": "2.8.4", "@types/jasmine": "2.8.4",
"@types/node": "9.3.0", "@types/node": "9.3.0",

View File

@ -165,7 +165,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
this.action = undefined this.action = undefined
} }
private onSave() { private async onSave() {
this.requestInProgressService.setRequestInProgress(true); this.requestInProgressService.setRequestInProgress(true);
if (this.action === ACTION.CREATE) { if (this.action === ACTION.CREATE) {
this.classificationSavingSubscription = this.classificationsService.postClassification(this.classification) this.classificationSavingSubscription = this.classificationsService.postClassification(this.classification)
@ -180,17 +180,17 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
this.afterRequest(); this.afterRequest();
}); });
} else { } else {
this.classificationSavingSubscription = this.classificationsService try {
.putClassification(this.classification._links.self.href, this.classification) this.classification = (<ClassificationDefinition> await this.classificationsService.putClassification(
.subscribe((classification: ClassificationDefinition) => { this.classification._links.self.href, this.classification));
this.classification = classification; this.afterRequest();
this.afterRequest(); this.alertService.triggerAlert(
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Classification ${classification.key} was saved successfully`)); new AlertModel(AlertType.SUCCESS, `Classification ${this.classification.key} was saved successfully`));
this.cloneClassification(classification); this.cloneClassification(this.classification);
}, error => { } catch (error) {
this.generalModalService.triggerMessage(new MessageModal('There was error while saving your classification', error)) this.generalModalService.triggerMessage(new MessageModal('There was error while saving your classification', error))
this.afterRequest(); this.afterRequest();
}) }
} }
} }
@ -216,16 +216,15 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
this.classificationsService.triggerClassificationSaved(); this.classificationsService.triggerClassificationSaved();
} }
private selectClassification(id: string) { private async selectClassification(id: string) {
if (this.classificationIsAlreadySelected()) { if (this.classificationIsAlreadySelected()) {
return true; return true;
} }
this.requestInProgress = true; this.requestInProgress = true;
this.selectedClassificationSubscription = this.classificationsService.getClassification(id).subscribe(classification => { const classification = await this.classificationsService.getClassification(id);
this.fillClassificationInformation(classification) this.fillClassificationInformation(classification)
this.classificationsService.selectClassification(classification); this.classificationsService.selectClassification(classification);
this.requestInProgress = false; this.requestInProgress = false;
});
} }
private classificationIsAlreadySelected(): boolean { private classificationIsAlreadySelected(): boolean {
@ -285,7 +284,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
this.classification = undefined; this.classification = undefined;
this.afterRequest(); this.afterRequest();
this.classificationsService.selectClassification(undefined); this.classificationsService.selectClassification(undefined);
this.router.navigate(['administration/classifications']); this.router.navigate(['taskana/administration/classifications']);
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Classification ${key} was removed successfully`)) this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Classification ${key} was removed successfully`))
}, error => { }, error => {
this.generalModalService.triggerMessage(new MessageModal('There was error while removing your classification', error)) this.generalModalService.triggerMessage(new MessageModal('There was error while removing your classification', error))

View File

@ -45,11 +45,12 @@
<taskana-spinner class="col-xs-12" [isRunning]="requestInProgress" positionClass="centered-spinner-whole-screen"></taskana-spinner> <taskana-spinner class="col-xs-12" [isRunning]="requestInProgress" positionClass="centered-spinner-whole-screen"></taskana-spinner>
<taskana-tree class="col-xs-12" *ngIf="(classifications && classifications.length) else empty_classifications" <taskana-tree class="col-xs-12" *ngIf="(classifications && classifications.length) else empty_classifications"
[treeNodes]="classifications" [selectNodeId]="selectedId" [filterText]="inputValue" [filterIcon]="selectedCategory" [treeNodes]="classifications" [selectNodeId]="selectedId" [filterText]="inputValue" [filterIcon]="selectedCategory"
(selectNodeIdChanged)="selectClassification($event)"></taskana-tree> (selectNodeIdChanged)="selectClassification($event)" (refreshClassification)="getClassifications($event)"
(switchTaskanaSpinnerEmit)="switchTaskanaSpinner($event)"></taskana-tree>
<ng-template #empty_classifications> <ng-template #empty_classifications>
<div *ngIf="!requestInProgress" class="col-xs-12 container-no-items center-block"> <div *ngIf="!requestInProgress" class="col-xs-12 container-no-items center-block">
<h3 class="grey">There are no classifications</h3> <h3 class="grey">There are no classifications</h3>
<svg-icon class="img-responsive empty-icon" src="./assets/icons/classification-empty.svg"></svg-icon> <svg-icon class="img-responsive empty-icon" src="./assets/icons/classification-empty.svg"></svg-icon>
</div> </div>
</ng-template> </ng-template>
</div> </div>

View File

@ -13,6 +13,8 @@ import {
import { Pair } from 'app/models/pair'; import { Pair } from 'app/models/pair';
import { ClassificationDefinition } from '../../../../models/classification-definition'; import { ClassificationDefinition } from '../../../../models/classification-definition';
import { ImportExportService } from 'app/administration/services/import-export/import-export.service'; import { ImportExportService } from 'app/administration/services/import-export/import-export.service';
import {AlertModel, AlertType} from '../../../../models/alert';
import {AlertService} from '../../../../services/alert/alert.service';
@Component({ @Component({
selector: 'taskana-classification-list', selector: 'taskana-classification-list',
@ -45,7 +47,8 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
private router: Router, private router: Router,
private route: ActivatedRoute, private route: ActivatedRoute,
private categoryService: ClassificationCategoriesService, private categoryService: ClassificationCategoriesService,
private importExportService: ImportExportService) { private importExportService: ImportExportService,
private alertService: AlertService) {
} }
ngOnInit() { ngOnInit() {
@ -70,20 +73,15 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
selectClassificationType(classificationTypeSelected: string) { selectClassificationType(classificationTypeSelected: string) {
this.classifications = []; this.classifications = [];
this.requestInProgress = true;
this.categoryService.selectClassificationType(classificationTypeSelected); this.categoryService.selectClassificationType(classificationTypeSelected);
this.classificationService.getClassifications() this.getClassifications();
.subscribe((classifications: Array<TreeNodeModel>) => {
this.classifications = classifications;
this.requestInProgress = false;
});
this.selectClassification(undefined); this.selectClassification(undefined);
} }
selectClassification(id: string) { selectClassification(id: string) {
this.selectedId = id; this.selectedId = id;
if (!id) { if (!id) {
this.router.navigate(['administration/classifications']); this.router.navigate(['taskana/administration/classifications']);
return; return;
} }
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route }); this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
@ -129,6 +127,24 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
this.initialized = true; this.initialized = true;
} }
private getClassifications(key: string = undefined) {
this.requestInProgress = true;
this.classificationService.getClassifications()
.subscribe((classifications: Array<TreeNodeModel>) => {
this.classifications = classifications;
this.requestInProgress = false;
});
if (key) {
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Classification ${key} was saved successfully`));
}
}
private switchTaskanaSpinner($event) {
this.requestInProgress = $event;
}
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); } if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); }
if (this.classificationTypeServiceSubscription) { this.classificationTypeServiceSubscription.unsubscribe(); } if (this.classificationTypeServiceSubscription) { this.classificationTypeServiceSubscription.unsubscribe(); }

View File

@ -288,7 +288,7 @@ export class WorkbasketInformationComponent
new AlertModel(AlertType.SUCCESS, 'The Workbasket ' + this.workbasket.workbasketId + ' has been marked for deletion') new AlertModel(AlertType.SUCCESS, 'The Workbasket ' + this.workbasket.workbasketId + ' has been marked for deletion')
); );
} }
this.router.navigate(['administration/workbaskets']); this.router.navigate(['taskana/administration/workbaskets']);
} }
); );
} }

View File

@ -8,14 +8,14 @@ import {WindowRefService} from '../window/window.service';
import {environment} from '../../../environments/environment'; import {environment} from '../../../environments/environment';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
fdescribe('StartupService', () => { describe('StartupService', () => {
const environmentFile = '/environments/data-sources/environment-information.json'; const environmentFile = '/environments/data-sources/environment-information.json';
const someRestUrl = 'someRestUrl'; const someRestUrl = 'someRestUrl';
const someLogoutUrl = 'someLogoutUrl'; const someLogoutUrl = 'someLogoutUrl';
const dummyEnvironmentInformation = { const dummyEnvironmentInformation = {
'taskanaRestUrl': someRestUrl, 'taskanaRestUrl': someRestUrl,
'taskanaLogoutUrl': someLogoutUrl 'taskanaLogoutUrl': someLogoutUrl
} };
let httpMock, service; let httpMock, service;

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable , Subject } from 'rxjs'; import { Subject } from 'rxjs';
@Injectable() @Injectable()
export class TreeService { export class TreeService {

View File

@ -1,18 +1,18 @@
import { Injectable } from '@angular/core'; import {Injectable} from '@angular/core';
import { HttpClient } from '@angular/common/http'; import {HttpClient} from '@angular/common/http';
import { environment } from 'environments/environment'; import {environment} from 'environments/environment';
import { combineLatest, Observable, Subject} from 'rxjs'; import {combineLatest, Observable, Subject} from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators'; import {mergeMap, tap} from 'rxjs/operators';
import { Classification } from 'app/models/classification'; import {Classification} from 'app/models/classification';
import { ClassificationDefinition } from 'app/models/classification-definition'; import {ClassificationDefinition} from 'app/models/classification-definition';
import { ClassificationResource } from 'app/models/classification-resource'; import {ClassificationResource} from 'app/models/classification-resource';
import { ClassificationCategoriesService } from './classification-categories.service'; import {ClassificationCategoriesService} from './classification-categories.service';
import { DomainService } from 'app/services/domain/domain.service'; import {DomainService} from 'app/services/domain/domain.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import {TaskanaQueryParameters} from 'app/shared/util/query-parameters';
import { Direction } from 'app/models/sorting'; import {Direction} from 'app/models/sorting';
import { QueryParametersModel } from 'app/models/query-parameters'; import {QueryParametersModel} from 'app/models/query-parameters';
@Injectable() @Injectable()
export class ClassificationsService { export class ClassificationsService {
@ -37,7 +37,9 @@ export class ClassificationsService {
`${this.url}${TaskanaQueryParameters.getQueryParameters(this.classificationParameters(domain))}`)); `${this.url}${TaskanaQueryParameters.getQueryParameters(this.classificationParameters(domain))}`));
}), }),
tap(() => { this.domainService.domainChangedComplete(); }) tap(() => {
this.domainService.domainChangedComplete();
})
) )
} }
@ -52,13 +54,13 @@ export class ClassificationsService {
} }
// GET // GET
getClassification(id: string): Observable<ClassificationDefinition> { getClassification(id: string): Promise<ClassificationDefinition> {
return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`) return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`)
.pipe(tap((classification: ClassificationDefinition) => { .pipe(tap((classification: ClassificationDefinition) => {
if (classification) { if (classification) {
this.classificationCategoriesService.selectClassificationType(classification.type); this.classificationCategoriesService.selectClassificationType(classification.type);
} }
})); })).toPromise();
} }
// POST // POST
@ -67,8 +69,8 @@ export class ClassificationsService {
} }
// PUT // PUT
putClassification(url: string, classification: Classification): Observable<Classification> { putClassification(url: string, classification: Classification): Promise<Classification> {
return this.httpClient.put<Classification>(url, classification); return this.httpClient.put<Classification>(url, classification).toPromise();
} }
// DELETE // DELETE

View File

@ -60,7 +60,7 @@ const MODULES = [
AngularSvgIconModule, AngularSvgIconModule,
HttpClientModule, HttpClientModule,
RouterModule, RouterModule,
TreeModule TreeModule.forRoot()
]; ];
const DECLARATIONS = [ const DECLARATIONS = [

View File

@ -1,4 +1,5 @@
<tree-root #tree [nodes]="treeNodes" [options]="options" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"> <tree-root #tree [nodes]="treeNodes" [options]="options" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"
(moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)">
<ng-template #treeNodeTemplate let-node let-index="index"> <ng-template #treeNodeTemplate let-node let-index="index">
<span class="text-top"> <span class="text-top">
<svg-icon *ngIf="node.data.category" class="blue fa-fw" [src]="getCategoryIcon(node.data.category).name" data-toggle="tooltip" <svg-icon *ngIf="node.data.category" class="blue fa-fw" [src]="getCategoryIcon(node.data.category).name" data-toggle="tooltip"
@ -9,4 +10,4 @@
</span> </span>
<span> - {{ node.data.name }}</span> <span> - {{ node.data.name }}</span>
</ng-template> </ng-template>
</tree-root> </tree-root>

View File

@ -1,16 +1,17 @@
import { Input, Component } from '@angular/core'; import {Component, Input} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import {ComponentFixture, TestBed} from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon'; import {AngularSvgIconModule} from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http'; import {HttpClientModule} from '@angular/common/http';
import { TaskanaTreeComponent } from './tree.component'; import {TaskanaTreeComponent} from './tree.component';
import { TreeService } from 'app/services/tree/tree.service'; import {TreeService} from 'app/services/tree/tree.service';
import { import {configureTests} from 'app/app.test.configuration';
ClassificationCategoriesService import {Pair} from 'app/models/pair';
} from 'app/shared/services/classifications/classification-categories.service'; import {ClassificationDefinition} from '../../models/classification-definition';
import { configureTests } from 'app/app.test.configuration'; import {LinksClassification} from '../../models/links-classfication';
import { Pair } from 'app/models/pair'; import {ClassificationCategoriesService} from '../services/classifications/classification-categories.service';
import {ClassificationsService} from '../services/classifications/classifications.service';
// tslint:disable:component-selector // tslint:disable:component-selector
@Component({ @Component({
@ -22,23 +23,27 @@ class TreeVendorComponent {
@Input() state; @Input() state;
@Input() nodes; @Input() nodes;
treeModel = { treeModel = {
getActiveNode() { } getActiveNode() {
}
} }
} }
// tslint:enable:component-selector // tslint:enable:component-selector
describe('TaskanaTreeComponent', () => { describe('TaskanaTreeComponent', () => {
let component: TaskanaTreeComponent; let component: TaskanaTreeComponent;
let fixture: ComponentFixture<TaskanaTreeComponent>; let fixture: ComponentFixture<TaskanaTreeComponent>;
let classificationCategoriesService; let classificationCategoriesService;
let classificationsService;
let moveNodeEvent;
let dropEvent;
beforeEach(done => { beforeEach(done => {
const configure = (testBed: TestBed) => { const configure = (testBed: TestBed) => {
testBed.configureTestingModule({ testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule], imports: [AngularSvgIconModule, HttpClientModule],
declarations: [TreeVendorComponent], declarations: [TreeVendorComponent],
providers: [TreeService, ClassificationCategoriesService] providers: [TreeService, ClassificationCategoriesService, ClassificationsService]
}) })
}; };
@ -46,6 +51,45 @@ describe('TaskanaTreeComponent', () => {
fixture = testBed.createComponent(TaskanaTreeComponent); fixture = testBed.createComponent(TaskanaTreeComponent);
classificationCategoriesService = testBed.get(ClassificationCategoriesService); classificationCategoriesService = testBed.get(ClassificationCategoriesService);
spyOn(classificationCategoriesService, 'getCategoryIcon').and.returnValue(new Pair('assets/icons/categories/external.svg')); spyOn(classificationCategoriesService, 'getCategoryIcon').and.returnValue(new Pair('assets/icons/categories/external.svg'));
classificationsService = TestBed.get(ClassificationsService);
spyOn(classificationsService, 'putClassification').and.callFake(function (url, classification) {
return classification;
});
moveNodeEvent = {
eventName: 'moveNode',
node: {
classificationId: 'id4',
parentId: '',
parentKey: '',
_links: {
self: {
href: 'url'
}
}
},
to: {
parent: {
classificationId: 'id3',
key: 'key3'
}
}
};
dropEvent = {
event: {
target: {
tagName: 'TREE-VIEWPORT'
}
},
element: {
data: {
classificationId: 'id3',
parentId: 'id1',
parentKey: 'key1'
}
}
};
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
done(); done();
@ -55,4 +99,41 @@ describe('TaskanaTreeComponent', () => {
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should be change the classification parent (onMoveNode)', async () => {
spyOn(classificationsService, 'getClassification').and.returnValue(new ClassificationDefinition('id4',
'key4', '', '', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
'classification4', 'description', 1, 'level', '', '', '', '', '', ''
, '', '', '', new LinksClassification({href: ''}, '', '', {href: ''}, {href: ''}, {href: ''})));
spyOn(component, 'switchTaskanaSpinner');
const classification = classificationsService.getClassification();
expect(classification.parentId).toEqual('');
expect(classification.parentKey).toEqual('');
await component.onMoveNode(moveNodeEvent);
expect(classification.parentId).toEqual('id3');
expect(classification.parentKey).toEqual('key3');
expect(classificationsService.putClassification).toHaveBeenCalledWith(classification._links.self.href, classification);
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(true);
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(false);
});
it('should be changed the parent classification to root node (onDrop)', async () => {
spyOn(classificationsService, 'getClassification').and.returnValue(new ClassificationDefinition('id3',
'key3', 'id1', 'key1', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
'classification3', 'description', 1, 'level', '', '', '', '', '', ''
, '', '', '', new LinksClassification({href: ''}, '', '', {href: ''}, {href: ''}, {href: ''})));
spyOn(component, 'switchTaskanaSpinner');
const classification = classificationsService.getClassification();
expect(classification.parentId).toEqual('id1');
expect(classification.parentKey).toEqual('key1');
await component.onDrop(dropEvent);
expect(classification.parentId).toEqual('');
expect(classification.parentKey).toEqual('');
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(true);
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(false);
});
}); });

View File

@ -1,16 +1,25 @@
import { import {
Component, OnInit, Input, Output, EventEmitter, ViewChild, AfterViewChecked, AfterViewChecked,
OnDestroy, ElementRef, HostListener Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnDestroy,
OnInit,
Output,
ViewChild
} from '@angular/core'; } from '@angular/core';
import { TreeNodeModel } from 'app/models/tree-node'; import {TreeNodeModel} from 'app/models/tree-node';
import { KEYS, ITreeOptions, TreeComponent, TreeNode } from 'angular-tree-component'; import {ITreeOptions, KEYS, TreeComponent, TreeNode} from 'angular-tree-component';
import { TreeService } from '../../services/tree/tree.service'; import {TreeService} from '../../services/tree/tree.service';
import { import {ClassificationCategoriesService} from 'app/shared/services/classifications/classification-categories.service';
ClassificationCategoriesService import {Pair} from 'app/models/pair';
} from 'app/shared/services/classifications/classification-categories.service'; import {Subscription} from 'rxjs';
import { Pair } from 'app/models/pair'; import {Classification} from '../../models/classification';
import { Subscription } from 'rxjs'; import {ClassificationDefinition} from '../../models/classification-definition';
import {ClassificationsService} from '../services/classifications/classifications.service';
@Component({ @Component({
selector: 'taskana-tree', selector: 'taskana-tree',
@ -19,19 +28,19 @@ import { Subscription } from 'rxjs';
}) })
export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy { export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy {
@ViewChild('tree') @ViewChild('tree')
private tree: TreeComponent; private tree: TreeComponent;
@Input() treeNodes: TreeNodeModel; @Input() treeNodes: Array<TreeNodeModel>;
@Output() treeNodesChange = new EventEmitter<Array<TreeNodeModel>>(); @Output() treeNodesChange = new EventEmitter<Array<TreeNodeModel>>();
@Input() selectNodeId: string; @Input() selectNodeId: string;
@Output() selectNodeIdChanged = new EventEmitter<string>(); @Output() selectNodeIdChanged = new EventEmitter<string>();
@Input() filterText: string; @Input() filterText: string;
@Input() filterIcon = ''; @Input() filterIcon = '';
@Output() refreshClassification = new EventEmitter<string>();
@Output() switchTaskanaSpinnerEmit = new EventEmitter<boolean>();
private filterTextOld: string;
private filterTextOld: string
private filterIconOld = ''; private filterIconOld = '';
private removedNodeIdSubscription: Subscription; private removedNodeIdSubscription: Subscription;
@ -47,8 +56,10 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
}, },
animateExpand: true, animateExpand: true,
animateSpeed: 20, animateSpeed: 20,
levelPadding: 20 levelPadding: 20,
} allowDrag: true,
allowDrop: true
};
@HostListener('document:click', ['$event']) @HostListener('document:click', ['$event'])
onDocumentClick(event) { onDocumentClick(event) {
@ -60,7 +71,9 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
constructor( constructor(
private treeService: TreeService, private treeService: TreeService,
private categoryService: ClassificationCategoriesService, private categoryService: ClassificationCategoriesService,
private elementRef: ElementRef) { } private elementRef: ElementRef,
private classificationsService: ClassificationsService) {
}
ngOnInit() { ngOnInit() {
this.removedNodeIdSubscription = this.treeService.getRemovedNodeId().subscribe(value => { this.removedNodeIdSubscription = this.treeService.getRemovedNodeId().subscribe(value => {
@ -71,7 +84,6 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
}); });
} }
ngAfterViewChecked(): void { ngAfterViewChecked(): void {
if (this.selectNodeId && !this.tree.treeModel.getActiveNode()) { if (this.selectNodeId && !this.tree.treeModel.getActiveNode()) {
this.selectNode(this.selectNodeId); this.selectNode(this.selectNodeId);
@ -96,6 +108,25 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
this.selectNodeIdChanged.emit(undefined); this.selectNodeIdChanged.emit(undefined);
} }
async onMoveNode($event) {
this.switchTaskanaSpinner(true);
const classification = await this.getClassification($event.node.classificationId);
classification.parentId = $event.to.parent.classificationId;
classification.parentKey = $event.to.parent.key;
this.collapseParentNodeIfItIsTheLastChild($event.node);
await this.updateClassification(classification);
}
async onDrop($event) {
if ($event.event.target.tagName === 'TREE-VIEWPORT') {
this.switchTaskanaSpinner(true);
const classification = await this.getClassification($event.element.data.classificationId);
this.collapseParentNodeIfItIsTheLastChild($event.element.data);
classification.parentId = '';
classification.parentKey = '';
await this.updateClassification(classification);
}
}
getCategoryIcon(category: string): Pair { getCategoryIcon(category: string): Pair {
return this.categoryService.getCategoryIcon(category); return this.categoryService.getCategoryIcon(category);
@ -103,9 +134,9 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
private selectNode(nodeId: string) { private selectNode(nodeId: string) {
if (nodeId) { if (nodeId) {
const selectedNode = this.getNode(nodeId) const selectedNode = this.getNode(nodeId);
if (selectedNode) { if (selectedNode) {
selectedNode.setIsActive(true) selectedNode.setIsActive(true);
this.expandParent(selectedNode); this.expandParent(selectedNode);
} }
} }
@ -141,6 +172,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
return (node.data.name.toUpperCase().includes(text.toUpperCase()) return (node.data.name.toUpperCase().includes(text.toUpperCase())
|| node.data.key.toUpperCase().includes(text.toUpperCase())) || node.data.key.toUpperCase().includes(text.toUpperCase()))
} }
private checkIcon(node: any, iconText: string): boolean { private checkIcon(node: any, iconText: string): boolean {
return (node.data.category.toUpperCase() === iconText.toUpperCase() return (node.data.category.toUpperCase() === iconText.toUpperCase()
|| iconText === '') || iconText === '')
@ -160,9 +192,31 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
event.target.localName === 'taskana-tree') event.target.localName === 'taskana-tree')
} }
private getClassification(classificationId: string): Promise<ClassificationDefinition> {
return this.classificationsService.getClassification(classificationId);
}
private async updateClassification(classification: Classification) {
await this.classificationsService.putClassification(classification._links.self.href, classification);
this.refreshClassification.emit(classification.key);
this.switchTaskanaSpinner(false);
}
private collapseParentNodeIfItIsTheLastChild(node: any) {
if (node.parentId.length > 0 && this.getNode(node.parentId) && this.getNode(node.parentId).children.length < 2) {
this.tree.treeModel.update();
this.getNode(node.parentId).collapse();
}
}
switchTaskanaSpinner(active: boolean) {
this.switchTaskanaSpinnerEmit.emit(active);
}
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.removedNodeIdSubscription) { this.removedNodeIdSubscription.unsubscribe() } if (this.removedNodeIdSubscription) {
this.removedNodeIdSubscription.unsubscribe()
}
} }
} }

View File

@ -46,7 +46,7 @@ export class TaskComponent implements OnInit, OnDestroy {
this.requestInProgress = true; this.requestInProgress = true;
this.task = await this.taskService.getTask(id).toPromise(); this.task = await this.taskService.getTask(id).toPromise();
const classification = await this.classificationService.getClassification const classification = await this.classificationService.getClassification
(this.task.classificationSummaryResource.classificationId).toPromise(); (this.task.classificationSummaryResource.classificationId);
this.address = this.extractUrl(classification.applicationEntryPoint) || `${this.address}/?q=${this.task.name}`; this.address = this.extractUrl(classification.applicationEntryPoint) || `${this.address}/?q=${this.task.name}`;
this.link = this.sanitizer.bypassSecurityTrustResourceUrl(this.address); this.link = this.sanitizer.bypassSecurityTrustResourceUrl(this.address);
this.getWorkbaskets(); this.getWorkbaskets();