TSK-1214: refactored taskana-classification
TSK-1214 Trying to make drag'n drop in tree possible TSK-1214 Removed refreshClassification output from tree TSK-1214 New action in store updates a classification and refetches all, saving now correctly refreshes the classification-list TSK-1214 Fixed tests in tree component TSK-1214 Removed tree service and corresponding test in class-details TSK-1214 fixed issues in tree where multiple actions to store are fired incorrectly TSK-1214 added accessibility action, use space to select a tree node TSK-1214 swapped space and enter in tree component, cleaned code TSK-1214 fixed bug where page isn't updated dynamically according to browser path TSK-1214 workaround circular dependency. service uses snapshot of store, does not actually access the state in store TSK-1214 fixed eslint. TODO: circular dependency between classification.service and classification.state TSK-1214 changed first() to take(1) to fix Observable dying during test TSK-1214 fixed test cases and lint issues TSK-1214 fixed circular dependency TSK-1214 devmode = false TSK-1214: fixed merge problems with notificationService and removed some warnings TSK-1214: fixed merge problems with notificationService TSK-1214 remove wrong imports from before merge
This commit is contained in:
parent
ddf7589c4c
commit
493e25b565
|
|
@ -23,7 +23,7 @@ taskana.schemaName=TASKANA
|
||||||
########spring.jpa.generate-ddl=true
|
########spring.jpa.generate-ddl=true
|
||||||
########spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
|
########spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
|
||||||
####### property that control rest api security deploy use true for no security.
|
####### property that control rest api security deploy use true for no security.
|
||||||
devMode=false
|
devMode=true
|
||||||
|
|
||||||
####### property that control if the database is cleaned and sample data is generated
|
####### property that control if the database is cleaned and sample data is generated
|
||||||
generateSampleData=true
|
generateSampleData=true
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { ClassificationListComponent } from 'app/administration/components/class
|
||||||
import { ClassificationDetailsComponent } from 'app/administration/components/classification-details/classification-details.component';
|
import { ClassificationDetailsComponent } from 'app/administration/components/classification-details/classification-details.component';
|
||||||
import { DomainGuard } from 'app/shared/guards/domain.guard';
|
import { DomainGuard } from 'app/shared/guards/domain.guard';
|
||||||
import { AccessItemsManagementComponent } from './components/access-items-management/access-items-management.component';
|
import { AccessItemsManagementComponent } from './components/access-items-management/access-items-management.component';
|
||||||
|
import { ClassificationOverviewComponent } from './components/classification-overview/classification-overview.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
@ -38,17 +39,17 @@ const routes: Routes = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'classifications',
|
path: 'classifications',
|
||||||
component: MasterAndDetailComponent,
|
component: ClassificationOverviewComponent,
|
||||||
canActivate: [DomainGuard],
|
canActivate: [DomainGuard],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ClassificationListComponent,
|
component: ClassificationOverviewComponent,
|
||||||
outlet: 'master'
|
outlet: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: ClassificationDetailsComponent,
|
component: ClassificationOverviewComponent,
|
||||||
outlet: 'detail'
|
outlet: 'detail'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -83,4 +84,5 @@ const routes: Routes = [
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [RouterModule.forChild(routes)],
|
||||||
exports: [RouterModule]
|
exports: [RouterModule]
|
||||||
})
|
})
|
||||||
export class AdministrationRoutingModule {}
|
export class AdministrationRoutingModule {
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import { SavingWorkbasketService } from './services/saving-workbaskets.service';
|
||||||
import { ClassificationDefinitionService } from './services/classification-definition.service';
|
import { ClassificationDefinitionService } from './services/classification-definition.service';
|
||||||
import { WorkbasketDefinitionService } from './services/workbasket-definition.service';
|
import { WorkbasketDefinitionService } from './services/workbasket-definition.service';
|
||||||
import { ImportExportService } from './services/import-export.service';
|
import { ImportExportService } from './services/import-export.service';
|
||||||
|
import { ClassificationOverviewComponent } from './components/classification-overview/classification-overview.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
|
@ -53,10 +54,11 @@ const DECLARATIONS = [
|
||||||
WorkbasketInformationComponent,
|
WorkbasketInformationComponent,
|
||||||
WorkbasketDistributionTargetsComponent,
|
WorkbasketDistributionTargetsComponent,
|
||||||
WorkbasketDualListComponent,
|
WorkbasketDualListComponent,
|
||||||
|
ClassificationOverviewComponent,
|
||||||
ClassificationListComponent,
|
ClassificationListComponent,
|
||||||
ImportExportComponent,
|
|
||||||
ClassificationTypesSelectorComponent,
|
ClassificationTypesSelectorComponent,
|
||||||
ClassificationDetailsComponent,
|
ClassificationDetailsComponent,
|
||||||
|
ImportExportComponent,
|
||||||
AccessItemsManagementComponent
|
AccessItemsManagementComponent
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,48 +1,43 @@
|
||||||
<div class="container-scrollable">
|
<div class="container-scrollable">
|
||||||
<taskana-spinner [isRunning]="requestInProgress" class="floating" (spinnerIsRunning)="spinnerRunning($event)"></taskana-spinner>
|
<taskana-spinner [isRunning]="requestInProgress" class="floating" (spinnerIsRunning)="spinnerRunning($event)"></taskana-spinner>
|
||||||
<div id="classification-details" *ngIf="classification && !spinnerIsRunning">
|
<div id="classification-details" *ngIf="classification && !spinnerIsRunning">
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
|
||||||
<li *ngIf="showDetail" class="visible-xs visible-sm hidden">
|
|
||||||
<a (click)="backClicked()">
|
|
||||||
<span class="material-icons md-20 blue">arrow_back</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div id="classification" class="panel panel-default classification">
|
<div id="classification" class="panel panel-default classification">
|
||||||
|
|
||||||
|
<!-- TITLE + ACTION BUTTONS -->
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<div *ngIf="showDetail" class="pull-left btn-group align-header">
|
|
||||||
<button (click)="backClicked()" class="btn btn-default no-style blue visible-xs visible-sm hidden">
|
|
||||||
<span class="material-icons md-20 blue">arrow_back</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="pull-right btn-group">
|
<div class="pull-right btn-group">
|
||||||
<button type="button" (click)="onSubmit()" class="btn btn-default btn-primary" data-toggle="tooltip" title="Save">
|
<button type="button" (click)="onSubmit()" class="btn btn-default btn-primary" data-toggle="tooltip" title="Save">
|
||||||
<span class="material-icons md-20">save</span>
|
<span class="material-icons md-20">save</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" (click)="onClear()" class="btn btn-default" data-toggle="tooltip" title="Undo Changes">
|
<button type="button" (click)="onRestore()" class="btn btn-default" data-toggle="tooltip" title="Restore Previous Version">
|
||||||
<span class="material-icons md-20 blue">undo</span>
|
<span class="material-icons md-20 blue">restore</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" (click)="removeClassification()" data-toggle="tooltip" title="Delete" class="btn btn-default">
|
<button type="button" (click)="removeClassification()" data-toggle="tooltip" title="Delete" class="btn btn-default">
|
||||||
<span class="material-icons md-20 red">delete</span>
|
<span class="material-icons md-20 red">delete</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<h4 class="panel-header">{{classification.name}} [{{classification.type}}]
|
<h4 class="panel-header">{{classification.name}} [{{classification.type}}]
|
||||||
<span *ngIf="action=== 'CREATE'" class="badge warning"> {{badgeMessage}}</span>
|
<span *ngIf="action=== 0" class="badge warning"> {{badgeMessage}}</span>
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DETAILED FIELDS -->
|
||||||
<div class="panel-body" style="padding: 0">
|
<div class="panel-body" style="padding: 0">
|
||||||
<form #ClassificationForm="ngForm">
|
<form #ClassificationForm="ngForm">
|
||||||
<div class="row" style="padding: 15px">
|
<div class="row" style="padding: 15px">
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
<!-- KEY -->
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<label for="classification-key" class="control-label">Key</label>
|
<label for="classification-key" class="control-label">Key</label>
|
||||||
<input type="text" required #key="ngModel" [disabled]="action!== 'CREATE'? true : false" class="form-control"
|
<input type="text" required #key="ngModel" [disabled]="action!== 0" class="form-control"
|
||||||
id="classification-key" placeholder="Key" [(ngModel)]="classification.key" name="classification.key">
|
id="classification-key" placeholder="Key" [(ngModel)]="classification.key" name="classification.key">
|
||||||
<taskana-field-error-display *ngIf="action === 'CREATE'" [displayError]="!isFieldValid('classification.key')"
|
<taskana-field-error-display *ngIf="action === 0" [displayError]="!isFieldValid('classification.key')"
|
||||||
[validationTrigger]="this.toogleValidationMap.get('classification.key')" errorMessage="* Key is required">
|
[validationTrigger]="this.toogleValidationMap.get('classification.key')" errorMessage="* Key is required">
|
||||||
</taskana-field-error-display>
|
</taskana-field-error-display>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- NAME -->
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<label for="classification-name" class="control-label">Name</label>
|
<label for="classification-name" class="control-label">Name</label>
|
||||||
<input type="text" required #name="ngModel" class="form-control" id="classification-name" placeholder="Name"
|
<input type="text" required #name="ngModel" class="form-control" id="classification-name" placeholder="Name"
|
||||||
|
|
@ -51,11 +46,12 @@
|
||||||
errorMessage="* Name is required">
|
errorMessage="* Name is required">
|
||||||
</taskana-field-error-display>
|
</taskana-field-error-display>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DOMAIN -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="classification-domain" class="control-label">Domain</label>
|
<label for="classification-domain" class="control-label">Domain</label>
|
||||||
<input type="text" disabled #domain="ngModel" class="form-control" id="classification-domain"
|
<input type="text" disabled #domain="ngModel" class="form-control" id="classification-domain"
|
||||||
placeholder="Domain" [(ngModel)]="classification.domain" name="classification.domain">
|
placeholder="Domain" [(ngModel)]="classification.domain" name="classification.domain">
|
||||||
|
|
||||||
<a *ngIf="!masterDomainSelected()" (click)="validChanged()">
|
<a *ngIf="!masterDomainSelected()" (click)="validChanged()">
|
||||||
<label>
|
<label>
|
||||||
<b>Valid in Domain:</b>
|
<b>Valid in Domain:</b>
|
||||||
|
|
@ -64,6 +60,8 @@
|
||||||
</label>
|
</label>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PRIORITY AND CATEGORY -->
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="form-group required col-xs-6">
|
<div class="form-group required col-xs-6">
|
||||||
<label for="classification-priority" class="control-label">Priority</label>
|
<label for="classification-priority" class="control-label">Priority</label>
|
||||||
|
|
@ -100,16 +98,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
||||||
|
<!-- SERVICE LEVEL -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="classification-service-level" class="control-label">Service Level</label>
|
<label for="classification-service-level" class="control-label">Service Level</label>
|
||||||
<input type="text" class="form-control" id="classification-service-level" placeholder="Service Level"
|
<input type="text" class="form-control" id="classification-service-level" placeholder="Service Level"
|
||||||
[(ngModel)]="classification.serviceLevel" name="classification.serviceLevel">
|
[(ngModel)]="classification.serviceLevel" name="classification.serviceLevel">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- APPLICATION ENTRY POINT -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="classification-application-entry-point" class="control-label">Application entry point</label>
|
<label for="classification-application-entry-point" class="control-label">Application entry point</label>
|
||||||
<input type="text" class="form-control" id="classification-application-entry-point" placeholder="Application entry point"
|
<input type="text" class="form-control" id="classification-application-entry-point" placeholder="Application entry point"
|
||||||
[(ngModel)]="classification.applicationEntryPoint" name="classification.applicationEntryPoint">
|
[(ngModel)]="classification.applicationEntryPoint" name="classification.applicationEntryPoint">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DESCRIPTION -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="classification-description" class="control-label">Description</label>
|
<label for="classification-description" class="control-label">Description</label>
|
||||||
<textarea class="form-control" rows="5" id="classification-description" placeholder="Description"
|
<textarea class="form-control" rows="5" id="classification-description" placeholder="Description"
|
||||||
|
|
@ -118,17 +122,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- CUSTOM FIELDS -->
|
||||||
<div class="row custom-field-row">
|
<div class="row custom-field-row">
|
||||||
|
|
||||||
<div class="custom-classification-form" *ngFor="let customField of (customFields$ | async), let i = index"
|
<div class="custom-classification-form" *ngFor="let customField of (customFields$ | async), let i = index"
|
||||||
style="width: 50%;">
|
style="width: 50%;">
|
||||||
|
|
||||||
<div *ngIf="customField.visible" class="form-group custom-field-wrapper">
|
<div *ngIf="customField.visible" class="form-group custom-field-wrapper">
|
||||||
<label for="classification-custom-{{i + 1}}" class="control-label">{{customField.field}}</label>
|
<label for="classification-custom-{{i + 1}}" class="control-label">{{customField.field}}</label>
|
||||||
<input type="text" class="form-control" id="classification-custom-{{i + 1}}" placeholder="{{customField.field}}"
|
<input type="text" class="form-control" id="classification-custom-{{i + 1}}" placeholder="{{customField.field}}"
|
||||||
[(ngModel)]="classification[getClassificationCustom(i + 1)]" name="classification.custom{{i + 1}}">
|
[(ngModel)]="classification[getClassificationCustom(i + 1)]" name="classification.custom{{i + 1}}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -11,3 +11,6 @@
|
||||||
height: 70px;
|
height: 70px;
|
||||||
padding: 0 15px
|
padding: 0 15px
|
||||||
}
|
}
|
||||||
|
.dropdown-menu > li {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
import { Routes } from '@angular/router';
|
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { AngularSvgIconModule } from 'angular-svg-icon';
|
import { AngularSvgIconModule } from 'angular-svg-icon';
|
||||||
import { configureTests } from 'app/app.test.configuration';
|
import { configureTests } from 'app/app.test.configuration';
|
||||||
import { NgxsModule, Store } from '@ngxs/store';
|
import { NgxsModule, Store } from '@ngxs/store';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
|
||||||
|
|
||||||
import { ClassificationDefinition } from 'app/shared/models/classification-definition';
|
import { ClassificationDefinition } from 'app/shared/models/classification-definition';
|
||||||
|
|
@ -17,15 +16,11 @@ import { MasterAndDetailService } from 'app/shared/services/master-and-detail/ma
|
||||||
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
|
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
|
||||||
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
||||||
import { TreeNodeModel } from 'app/shared/models/tree-node';
|
import { TreeNodeModel } from 'app/shared/models/tree-node';
|
||||||
import { TreeService } from 'app/shared/services/tree/tree.service';
|
|
||||||
import { ImportExportService } from 'app/administration/services/import-export.service';
|
import { ImportExportService } from 'app/administration/services/import-export.service';
|
||||||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||||
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
||||||
import { dispatchKeyboardEvent } from '@angular/cdk/testing';
|
|
||||||
import { TabDirective } from 'ngx-bootstrap';
|
|
||||||
import { KEYS } from 'angular-tree-component';
|
|
||||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
|
||||||
import { ClassificationDetailsComponent } from './classification-details.component';
|
import { ClassificationDetailsComponent } from './classification-details.component';
|
||||||
|
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -35,26 +30,25 @@ import { ClassificationDetailsComponent } from './classification-details.compone
|
||||||
class DummyDetailComponent {
|
class DummyDetailComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{ path: 'taskana/administration/classifications', component: DummyDetailComponent }
|
|
||||||
];
|
|
||||||
|
|
||||||
describe('ClassificationDetailsComponent', () => {
|
describe('ClassificationDetailsComponent', () => {
|
||||||
let component: ClassificationDetailsComponent;
|
let component: ClassificationDetailsComponent;
|
||||||
let fixture: ComponentFixture<ClassificationDetailsComponent>;
|
let fixture: ComponentFixture<ClassificationDetailsComponent>;
|
||||||
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
|
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
|
||||||
|
|
||||||
let classificationsService;
|
let classificationsService;
|
||||||
let treeService;
|
let removeConfirmationService;
|
||||||
|
|
||||||
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select']);
|
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go']);
|
||||||
|
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select', 'dispatch']);
|
||||||
const configure = (testBed: TestBed) => {
|
const configure = (testBed: TestBed) => {
|
||||||
testBed.configureTestingModule({
|
testBed.configureTestingModule({
|
||||||
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes), AngularSvgIconModule, NgxsModule.forRoot()],
|
imports: [FormsModule, HttpClientModule, AngularSvgIconModule, NgxsModule.forRoot()],
|
||||||
declarations: [ClassificationDetailsComponent, DummyDetailComponent],
|
declarations: [ClassificationDetailsComponent, DummyDetailComponent],
|
||||||
providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService,
|
providers: [MasterAndDetailService, RequestInProgressService, ClassificationsService,
|
||||||
HttpClient, NotificationService,
|
HttpClient, NotificationService,
|
||||||
TreeService, ImportExportService, { provide: Store, useValue: storeSpy }]
|
ImportExportService,
|
||||||
|
{ provide: Location, useValue: locationSpy },
|
||||||
|
{ provide: Store, useValue: storeSpy }]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -71,6 +65,7 @@ describe('ClassificationDetailsComponent', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
fixture = testBed.createComponent(ClassificationDetailsComponent);
|
fixture = testBed.createComponent(ClassificationDetailsComponent);
|
||||||
|
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
@ -79,7 +74,6 @@ describe('ClassificationDetailsComponent', () => {
|
||||||
spyOn(classificationsService, 'deleteClassification').and.returnValue(of(true));
|
spyOn(classificationsService, 'deleteClassification').and.returnValue(of(true));
|
||||||
component.classification = new ClassificationDefinition('id1');
|
component.classification = new ClassificationDefinition('id1');
|
||||||
component.classification._links = new LinksClassification({ self: '' });
|
component.classification._links = new LinksClassification({ self: '' });
|
||||||
treeService = testBed.get(TreeService);
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
@ -89,16 +83,4 @@ describe('ClassificationDetailsComponent', () => {
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trigger treeService remove node id after removing a node', done => {
|
|
||||||
const treeServiceSpy = spyOn(treeService, 'setRemovedNodeId');
|
|
||||||
const ref = component.removeClassification();
|
|
||||||
ref.afterOpened().subscribe(() => {
|
|
||||||
ref.close(ref.componentInstance.callback);
|
|
||||||
});
|
|
||||||
ref.afterClosed().subscribe(() => {
|
|
||||||
expect(treeServiceSpy).toHaveBeenCalledWith('id1');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,35 @@
|
||||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
|
||||||
import { Select, Store } from '@ngxs/store';
|
import { Select, Store } from '@ngxs/store';
|
||||||
import { Observable, Subscription, zip } from 'rxjs';
|
import { Observable, Subject, zip } from 'rxjs';
|
||||||
|
|
||||||
import { ClassificationDefinition, customFieldCount } from 'app/shared/models/classification-definition';
|
import { ClassificationDefinition,
|
||||||
|
customFieldCount } from 'app/shared/models/classification-definition';
|
||||||
import { ACTION } from 'app/shared/models/action';
|
import { ACTION } from 'app/shared/models/action';
|
||||||
|
|
||||||
import { highlight } from 'theme/animations/validation.animation';
|
import { highlight } from 'theme/animations/validation.animation';
|
||||||
import { TaskanaDate } from 'app/shared/util/taskana.date';
|
import { TaskanaDate } from 'app/shared/util/taskana.date';
|
||||||
|
|
||||||
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
||||||
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
|
|
||||||
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
|
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
|
||||||
import { TreeService } from 'app/shared/services/tree/tree.service';
|
|
||||||
|
|
||||||
import { DomainService } from 'app/shared/services/domain/domain.service';
|
import { DomainService } from 'app/shared/services/domain/domain.service';
|
||||||
import { Pair } from 'app/shared/models/pair';
|
import { Pair } from 'app/shared/models/pair';
|
||||||
import { NgForm } from '@angular/forms';
|
import { NgForm } from '@angular/forms';
|
||||||
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
|
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
|
||||||
import { ImportExportService } from 'app/administration/services/import-export.service';
|
import { ImportExportService } from 'app/administration/services/import-export.service';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { map, take, takeUntil } from 'rxjs/operators';
|
||||||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||||
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { Location } from '@angular/common';
|
||||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
||||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||||
import { ClassificationCategoryImages,
|
import { ClassificationCategoryImages,
|
||||||
CustomField,
|
CustomField,
|
||||||
getCustomFields } from '../../../shared/models/customisation';
|
getCustomFields } from '../../../shared/models/customisation';
|
||||||
import { DialogPopUpComponent } from '../../../shared/components/popup/dialog-pop-up.component';
|
|
||||||
|
import { CreateClassification,
|
||||||
|
RestoreSelectedClassification,
|
||||||
|
SaveClassification } from '../../../shared/store/classification-store/classification.actions';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'taskana-classification-details',
|
selector: 'taskana-classification-details',
|
||||||
|
|
@ -38,42 +39,33 @@ import { DialogPopUpComponent } from '../../../shared/components/popup/dialog-po
|
||||||
})
|
})
|
||||||
export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
classification: ClassificationDefinition;
|
classification: ClassificationDefinition;
|
||||||
classificationClone: ClassificationDefinition;
|
|
||||||
classificationCategories: string[];
|
|
||||||
showDetail = false;
|
|
||||||
badgeMessage = '';
|
badgeMessage = '';
|
||||||
requestInProgress = false;
|
requestInProgress = false;
|
||||||
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
|
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
|
||||||
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
|
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
|
||||||
@Select(ClassificationSelectors.selectedClassificationType) selectedClassificationType$: Observable<string>;
|
@Select(ClassificationSelectors.selectedClassificationType) selectedClassificationType$: Observable<string>;
|
||||||
@Select(ClassificationSelectors.selectClassificationTypesObject) classificationTypes$: Observable<Object>;
|
@Select(ClassificationSelectors.selectClassificationTypesObject) classificationTypes$: Observable<Object>;
|
||||||
|
@Select(ClassificationSelectors.selectedClassification) selectedClassification$: Observable<ClassificationDefinition>;
|
||||||
|
@Select(ClassificationSelectors.activeAction) action$: Observable<ACTION>;
|
||||||
|
|
||||||
spinnerIsRunning = false;
|
spinnerIsRunning = false;
|
||||||
customFields$: Observable<CustomField[]>;
|
customFields$: Observable<CustomField[]>;
|
||||||
|
|
||||||
@ViewChild('ClassificationForm', { static: false }) classificationForm: NgForm;
|
@ViewChild('ClassificationForm', { static: false }) classificationForm: NgForm;
|
||||||
toogleValidationMap = new Map<string, boolean>();
|
toogleValidationMap = new Map<string, boolean>();
|
||||||
private action: any;
|
action: ACTION;
|
||||||
private classificationServiceSubscription: Subscription;
|
destroy$ = new Subject<void>();
|
||||||
private classificationSelectedSubscription: Subscription;
|
|
||||||
private routeSubscription: Subscription;
|
|
||||||
private masterAndDetailSubscription: Subscription;
|
|
||||||
private classificationSavingSubscription: Subscription;
|
|
||||||
private classificationRemoveSubscription: Subscription;
|
|
||||||
private domainSubscription: Subscription;
|
|
||||||
private importingExportingSubscription: Subscription;
|
|
||||||
|
|
||||||
constructor(private classificationsService: ClassificationsService,
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private classificationsService: ClassificationsService,
|
||||||
private router: Router,
|
private location: Location,
|
||||||
private masterAndDetailService: MasterAndDetailService,
|
|
||||||
private requestInProgressService: RequestInProgressService,
|
private requestInProgressService: RequestInProgressService,
|
||||||
private treeService: TreeService,
|
|
||||||
private domainService: DomainService,
|
private domainService: DomainService,
|
||||||
private formsValidatorService: FormsValidatorService,
|
private formsValidatorService: FormsValidatorService,
|
||||||
private notificationService: NotificationService,
|
private notificationsService: NotificationService,
|
||||||
private importExportService: ImportExportService,
|
private importExportService: ImportExportService,
|
||||||
private store: Store) {
|
private store: Store
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
@ -81,57 +73,34 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
map(customisation => customisation.information),
|
map(customisation => customisation.information),
|
||||||
getCustomFields(customFieldCount)
|
getCustomFields(customFieldCount)
|
||||||
);
|
);
|
||||||
this.classificationSelectedSubscription = this.classificationsService.getSelectedClassification()
|
this.selectedClassification$.pipe(takeUntil(this.destroy$)).subscribe(data => {
|
||||||
.subscribe(classificationSelected => {
|
this.fillClassificationInformation(data);
|
||||||
if (classificationSelected && this.classification
|
|
||||||
&& this.classification.classificationId === classificationSelected.classificationId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.initProperties();
|
|
||||||
if (classificationSelected) {
|
|
||||||
this.fillClassificationInformation(classificationSelected);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
this.action$.pipe(takeUntil(this.destroy$)).subscribe(data => {
|
||||||
this.routeSubscription = this.route.params.subscribe(params => {
|
this.action = data;
|
||||||
let { id } = params;
|
if (this.action === ACTION.CREATE) {
|
||||||
delete this.action;
|
this.initClassificationOnCreation();
|
||||||
if (id && id.indexOf('new-classification') !== -1) {
|
|
||||||
this.action = ACTION.CREATE;
|
|
||||||
this.badgeMessage = 'Creating new classification';
|
this.badgeMessage = 'Creating new classification';
|
||||||
id = id.replace('new-classification/', '');
|
} else {
|
||||||
if (id === 'undefined') {
|
this.badgeMessage = '';
|
||||||
id = '';
|
|
||||||
}
|
|
||||||
this.fillClassificationInformation(this.classification ? this.classification : new ClassificationDefinition());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.classification || (this.classification.classificationId !== id && id)) {
|
|
||||||
this.selectClassification(id);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.masterAndDetailSubscription = this.masterAndDetailService.getShowDetail().subscribe(showDetail => {
|
this.importExportService.getImportingFinished().pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||||
this.showDetail = showDetail;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe((value: Boolean) => {
|
|
||||||
if (this.classification.classificationId) {
|
if (this.classification.classificationId) {
|
||||||
this.selectClassification(this.classification.classificationId);
|
this.selectClassification(this.classification.classificationId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
backClicked(): void {
|
backClicked(): void {
|
||||||
this.classificationsService.selectClassification();
|
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
|
||||||
this.router.navigate(['./'], { relativeTo: this.route.parent });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
removeClassification(): MatDialogRef<DialogPopUpComponent> {
|
removeClassification() {
|
||||||
return this.notificationService.showDialog(
|
this.notificationsService.showDialog(`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`,
|
||||||
`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`,
|
this.removeClassificationConfirmation.bind(this));
|
||||||
this.removeClassificationConfirmation.bind(this)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isFieldValid(field: string): boolean {
|
isFieldValid(field: string): boolean {
|
||||||
|
|
@ -147,10 +116,18 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onClear() {
|
onRestore() {
|
||||||
this.formsValidatorService.formSubmitAttempt = false;
|
this.formsValidatorService.formSubmitAttempt = false;
|
||||||
this.notificationService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
if (this.action === ACTION.CREATE) {
|
||||||
this.classification = { ...this.classificationClone };
|
this.classification = new ClassificationDefinition();
|
||||||
|
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||||
|
} else {
|
||||||
|
this.store.dispatch(
|
||||||
|
new RestoreSelectedClassification(this.classification.classificationId)
|
||||||
|
).pipe(take(1)).subscribe(ctx => {
|
||||||
|
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectCategory(category: string) {
|
selectCategory(category: string) {
|
||||||
|
|
@ -177,41 +154,31 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
return this.domainService.getSelectedDomainValue() === '';
|
return this.domainService.getSelectedDomainValue() === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private initProperties() {
|
|
||||||
delete this.classification;
|
|
||||||
delete this.action;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async 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.store.dispatch(
|
||||||
.subscribe((classification: ClassificationDefinition) => {
|
new CreateClassification(this.classification)
|
||||||
this.classification = classification;
|
).pipe(take(1)).subscribe(store => {
|
||||||
this.classificationsService.selectClassification(classification);
|
this.notificationsService.showToast(
|
||||||
this.notificationService.showToast(
|
|
||||||
NOTIFICATION_TYPES.SUCCESS_ALERT_2,
|
NOTIFICATION_TYPES.SUCCESS_ALERT_2,
|
||||||
new Map<string, string>([['classificationKey', classification.key]])
|
new Map<string, string>([['classificationKey', store.classification.selectedClassification.key]])
|
||||||
);
|
);
|
||||||
this.afterRequest();
|
this.afterRequest();
|
||||||
},
|
}, error => {
|
||||||
error => {
|
this.notificationsService.triggerError(NOTIFICATION_TYPES.CREATE_ERR, error);
|
||||||
this.notificationService.triggerError(NOTIFICATION_TYPES.CREATE_ERR, error);
|
|
||||||
this.afterRequest();
|
this.afterRequest();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
this.classification = (<ClassificationDefinition> await this.classificationsService.putClassification(
|
this.store.dispatch(new SaveClassification(this.classification));
|
||||||
this.classification._links.self.href, this.classification
|
|
||||||
));
|
|
||||||
this.afterRequest();
|
this.afterRequest();
|
||||||
this.notificationService.showToast(
|
this.notificationsService.showToast(
|
||||||
NOTIFICATION_TYPES.SUCCESS_ALERT_3,
|
NOTIFICATION_TYPES.SUCCESS_ALERT_3,
|
||||||
new Map<string, string>([['classificationKey', this.classification.key]])
|
new Map<string, string>([['classificationKey', this.classification.key]])
|
||||||
);
|
);
|
||||||
this.cloneClassification(this.classification);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.notificationService.triggerError(NOTIFICATION_TYPES.SAVE_ERR, error);
|
this.notificationsService.triggerError(NOTIFICATION_TYPES.SAVE_ERR, error);
|
||||||
this.afterRequest();
|
this.afterRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,9 +192,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
private async selectClassification(id: string) {
|
private async selectClassification(id: string) {
|
||||||
if (!this.classificationIsAlreadySelected()) {
|
if (!this.classificationIsAlreadySelected()) {
|
||||||
this.requestInProgress = true;
|
this.requestInProgress = true;
|
||||||
const classification = await this.classificationsService.getClassification(id);
|
const classification = await this.classificationsService.getClassification(id).toPromise();
|
||||||
this.fillClassificationInformation(classification);
|
this.fillClassificationInformation(classification);
|
||||||
this.classificationsService.selectClassification(classification);
|
|
||||||
this.requestInProgress = false;
|
this.requestInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -237,13 +203,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fillClassificationInformation(classificationSelected: ClassificationDefinition) {
|
private fillClassificationInformation(classificationSelected: ClassificationDefinition) {
|
||||||
if (this.action === ACTION.CREATE) { // CREATE
|
this.classification = { ...classificationSelected };
|
||||||
this.initClassificationOnCreation(classificationSelected);
|
|
||||||
} else {
|
|
||||||
this.classification = classificationSelected;
|
|
||||||
this.cloneClassification(classificationSelected);
|
|
||||||
this.checkDomainAndRedirect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private addDateToClassification(classification: ClassificationDefinition) {
|
private addDateToClassification(classification: ClassificationDefinition) {
|
||||||
|
|
@ -252,58 +212,31 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
classification.modified = date;
|
classification.modified = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initClassificationOnCreation(classificationSelected: ClassificationDefinition) {
|
private initClassificationOnCreation() {
|
||||||
zip(this.categories$, this.selectedClassificationType$).pipe(take(1)).subscribe(([categories, selectedType]: [string[], string]) => {
|
zip(this.categories$, this.selectedClassificationType$).pipe(take(1)).subscribe(([categories, selectedType]: [string[], string]) => {
|
||||||
const tempClassification: ClassificationDefinition = new ClassificationDefinition();
|
const tempClassification: ClassificationDefinition = new ClassificationDefinition();
|
||||||
// tempClassification.parentId = classificationSelected.classificationId;
|
|
||||||
// tempClassification.parentKey = classificationSelected.key;
|
|
||||||
[tempClassification.category] = categories;
|
[tempClassification.category] = categories;
|
||||||
tempClassification.domain = this.domainService.getSelectedDomainValue();
|
tempClassification.domain = this.domainService.getSelectedDomainValue();
|
||||||
tempClassification.type = selectedType;
|
tempClassification.type = selectedType;
|
||||||
this.addDateToClassification(tempClassification);
|
this.addDateToClassification(tempClassification);
|
||||||
this.classification = tempClassification;
|
this.classification = tempClassification;
|
||||||
this.cloneClassification(this.classification);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkDomainAndRedirect() {
|
|
||||||
this.domainSubscription = this.domainService.getSelectedDomain().subscribe(domain => {
|
|
||||||
if (domain !== '' && this.classification && this.classification.domain !== domain) {
|
|
||||||
this.backClicked();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeClassificationConfirmation() {
|
private removeClassificationConfirmation() {
|
||||||
if (!this.classification || !this.classification.classificationId) {
|
if (!this.classification || !this.classification.classificationId) {
|
||||||
this.notificationService.triggerError(NOTIFICATION_TYPES.SELECT_ERR);
|
this.notificationsService.triggerError(NOTIFICATION_TYPES.SELECT_ERR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.requestInProgressService.setRequestInProgress(true);
|
this.requestInProgressService.setRequestInProgress(true);
|
||||||
this.treeService.setRemovedNodeId(this.classification.classificationId);
|
|
||||||
|
|
||||||
this.classificationRemoveSubscription = this.classificationsService
|
this.store.dispatch(new RemoveSelectedClassification()).pipe(take(1)).subscribe(() => {
|
||||||
.deleteClassification(this.classification._links.self.href)
|
this.notificationsService.showToast(NOTIFICATION_TYPES.SUCCESS_ALERT_4,
|
||||||
.subscribe(() => {
|
new Map<string, string>([['classificationKey', this.classification.key]]));
|
||||||
const { key } = this.classification;
|
|
||||||
delete this.classification;
|
|
||||||
this.afterRequest();
|
|
||||||
this.classificationsService.selectClassification();
|
|
||||||
this.router.navigate(['taskana/administration/classifications']);
|
|
||||||
this.notificationService.showToast(
|
|
||||||
NOTIFICATION_TYPES.SUCCESS_ALERT_4,
|
|
||||||
new Map<string, string>([['classificationKey', key]])
|
|
||||||
);
|
|
||||||
}, error => {
|
|
||||||
this.notificationService.triggerError(NOTIFICATION_TYPES.REMOVE_ERR, error);
|
|
||||||
this.afterRequest();
|
this.afterRequest();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private cloneClassification(classification: ClassificationDefinition) {
|
|
||||||
this.classificationClone = { ...classification };
|
|
||||||
}
|
|
||||||
|
|
||||||
getClassificationCustom(customNumber: number): string {
|
getClassificationCustom(customNumber: number): string {
|
||||||
return `custom${customNumber}`;
|
return `custom${customNumber}`;
|
||||||
}
|
}
|
||||||
|
|
@ -311,7 +244,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
// TODO: Remove when classification is in store
|
// TODO: Remove when classification is in store
|
||||||
getAvailableCategories(type: string) {
|
getAvailableCategories(type: string) {
|
||||||
let returnCategories: string[] = [];
|
let returnCategories: string[] = [];
|
||||||
this.classificationTypes$.subscribe(classTypes => {
|
this.classificationTypes$.pipe(take(1)).subscribe(classTypes => {
|
||||||
returnCategories = classTypes[type];
|
returnCategories = classTypes[type];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -319,29 +252,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.masterAndDetailSubscription) {
|
this.destroy$.next();
|
||||||
this.masterAndDetailSubscription.unsubscribe();
|
this.destroy$.complete();
|
||||||
}
|
|
||||||
if (this.routeSubscription) {
|
|
||||||
this.routeSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.classificationSelectedSubscription) {
|
|
||||||
this.classificationSelectedSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.classificationServiceSubscription) {
|
|
||||||
this.classificationServiceSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.classificationSavingSubscription) {
|
|
||||||
this.classificationSavingSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.classificationRemoveSubscription) {
|
|
||||||
this.classificationRemoveSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.domainSubscription) {
|
|
||||||
this.domainSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
if (this.importingExportingSubscription) {
|
|
||||||
this.importingExportingSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,43 @@
|
||||||
<div class="classification-list-full-height">
|
<div class="classification-list-full-height">
|
||||||
<li id="wb-action-toolbar" class="list-group-item tab-align">
|
|
||||||
|
<!-- ACTION TOOLBAR -->
|
||||||
|
<li class="list-group-item tab-align" id="wb-action-toolbar">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6 btn-group">
|
<div class="col-xs-6 btn-group">
|
||||||
<button type="button" (click)="addClassification()" data-toggle="tooltip" title="Add" class="btn btn-default">
|
|
||||||
|
<button class="btn btn-default" type="button" (click)="addClassification()" data-toggle="tooltip" title="Add">
|
||||||
<span class="material-icons md-20 green-blue">add_circle_outline</span>
|
<span class="material-icons md-20 green-blue">add_circle_outline</span>
|
||||||
</button>
|
</button>
|
||||||
<taskana-import-export-component class ="btn-group" [currentSelection]="selectionToImport">
|
|
||||||
|
<taskana-import-export-component
|
||||||
|
class ="btn-group" [currentSelection]="taskanaType.CLASSIFICATIONS">
|
||||||
</taskana-import-export-component>
|
</taskana-import-export-component>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<taskana-classification-types-selector class="pull-right"></taskana-classification-types-selector>
|
<taskana-classification-types-selector
|
||||||
|
class="pull-right">
|
||||||
|
</taskana-classification-types-selector>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- FILTER -->
|
||||||
<div class="col-xs-2 category-filter">
|
<div class="col-xs-2 category-filter">
|
||||||
<button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdown-classification-filter" data-toggle="dropdown"
|
<button class="btn btn-default" data-toggle="dropdown" type="button" id="dropdown-classification-filter" data-toggle="dropdown"
|
||||||
aria-haspopup="true" aria-expanded="true">
|
aria-haspopup="true" aria-expanded="true">
|
||||||
<svg-icon *ngIf="selectedCategory else category_unselected" class="blue" [src]="(getCategoryIcon(selectedCategory) | async)?.name"
|
<svg-icon class="blue" *ngIf="selectedCategory else category_unselected" [src]="(getCategoryIcon(selectedCategory) | async)?.name"
|
||||||
data-toggle="tooltip" [title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
|
data-toggle="tooltip"></svg-icon>
|
||||||
<ng-template #category_unselected>
|
<ng-template #category_unselected>
|
||||||
<svg-icon data-toggle="tooltip" title="All" class="blue " src="./assets/icons/asterisk.svg"></svg-icon>
|
<svg-icon data-toggle="tooltip" title="All" class="blue" src="./assets/icons/asterisk.svg"></svg-icon>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu dropdown-menu-classification" role="menu">
|
<ul class="dropdown-menu dropdown-menu-classification" role="menu">
|
||||||
<li>
|
<li>
|
||||||
<a type="button" (click)="selectCategory('');" data-toggle="tooltip" title="All">
|
<a type="button" (click)="selectCategory('');" data-toggle="tooltip">
|
||||||
<svg-icon class="blue" src="./assets/icons/asterisk.svg"></svg-icon>
|
<svg-icon class="blue" src="./assets/icons/asterisk.svg"></svg-icon>
|
||||||
All
|
All
|
||||||
</a>
|
</a>
|
||||||
<a *ngFor="let category of categories$ | async" type="button" (click)="selectCategory(category);" data-toggle="tooltip"
|
<a *ngFor="let category of categories$ | async" type="button" (click)="selectCategory(category);" data-toggle="tooltip">
|
||||||
[title]="category">
|
|
||||||
<svg-icon class="blue" [src]="(getCategoryIcon(category) | async)?.name" data-toggle="tooltip" [title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
|
<svg-icon class="blue" [src]="(getCategoryIcon(category) | async)?.name" data-toggle="tooltip" [title]="(getCategoryIcon(category) | async)?.text"></svg-icon>
|
||||||
{{category}}
|
{{category}}
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -39,13 +47,13 @@
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-8">
|
||||||
<input class="filter-input" [ngModel]="inputValue" (ngModelChange)="inputValue = $event" placeholder="Filter classifications">
|
<input class="filter-input" [ngModel]="inputValue" (ngModelChange)="inputValue = $event" placeholder="Filter classifications">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 horizontal-bottom-divider">
|
|
||||||
</div>
|
<div class="col-xs-12 horizontal-bottom-divider"></div>
|
||||||
|
|
||||||
|
<!-- CLASSIFICATION TREE -->
|
||||||
<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"
|
[filterText]="inputValue" [filterIcon]="selectedCategory"></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>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
import { HttpClient, HttpClientModule } from '@angular/common/http';
|
||||||
import { of } from 'rxjs';
|
import { of } from 'rxjs';
|
||||||
import { Routes } from '@angular/router';
|
import { Routes } from '@angular/router';
|
||||||
|
|
@ -18,7 +18,6 @@ import { ClassificationDefinitionService } from 'app/administration/services/cla
|
||||||
import { DomainService } from 'app/shared/services/domain/domain.service';
|
import { DomainService } from 'app/shared/services/domain/domain.service';
|
||||||
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
|
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
|
||||||
import { configureTests } from 'app/app.test.configuration';
|
import { configureTests } from 'app/app.test.configuration';
|
||||||
import { TreeService } from 'app/shared/services/tree/tree.service';
|
|
||||||
import { ImportExportService } from 'app/administration/services/import-export.service';
|
import { ImportExportService } from 'app/administration/services/import-export.service';
|
||||||
import { NgxsModule } from '@ngxs/store';
|
import { NgxsModule } from '@ngxs/store';
|
||||||
import { MatRadioModule } from '@angular/material/radio';
|
import { MatRadioModule } from '@angular/material/radio';
|
||||||
|
|
@ -51,7 +50,7 @@ describe('ClassificationListComponent', () => {
|
||||||
providers: [
|
providers: [
|
||||||
HttpClient, WorkbasketDefinitionService, NotificationService,
|
HttpClient, WorkbasketDefinitionService, NotificationService,
|
||||||
ClassificationsService, DomainService, ClassificationDefinitionService,
|
ClassificationsService, DomainService, ClassificationDefinitionService,
|
||||||
RequestInProgressService, TreeService, ImportExportService
|
RequestInProgressService, ImportExportService
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,84 +1,108 @@
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Select } from '@ngxs/store';
|
import { Actions, ofActionCompleted, ofActionDispatched, Select, Store } from '@ngxs/store';
|
||||||
|
|
||||||
|
import { ImportExportService } from 'app/administration/services/import-export.service';
|
||||||
|
|
||||||
import { TaskanaType } from 'app/shared/models/taskana-type';
|
import { TaskanaType } from 'app/shared/models/taskana-type';
|
||||||
import { Classification } from 'app/shared/models/classification';
|
|
||||||
import { TreeNodeModel } from 'app/shared/models/tree-node';
|
|
||||||
|
|
||||||
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
|
||||||
import { Pair } from 'app/shared/models/pair';
|
import { Pair } from 'app/shared/models/pair';
|
||||||
import { ImportExportService } from 'app/administration/services/import-export.service';
|
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
|
||||||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||||
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
||||||
import { ClassificationDefinition } from '../../../shared/models/classification-definition';
|
import { Location } from '@angular/common';
|
||||||
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
|
|
||||||
|
|
||||||
import { ClassificationCategoryImages } from '../../../shared/models/customisation';
|
import { ClassificationCategoryImages } from '../../../shared/models/customisation';
|
||||||
|
|
||||||
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
import { NotificationService } from '../../../shared/services/notifications/notification.service';
|
||||||
|
|
||||||
|
import { GetClassifications, SetActiveAction } from '../../../shared/store/classification-store/classification.actions';
|
||||||
|
import { ACTION } from '../../../shared/models/action';
|
||||||
|
import { TreeNodeModel } from '../../../shared/models/tree-node';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'taskana-classification-list',
|
selector: 'taskana-classification-list',
|
||||||
templateUrl: './classification-list.component.html',
|
templateUrl: './classification-list.component.html',
|
||||||
styleUrls: ['./classification-list.component.scss']
|
styleUrls: ['./classification-list.component.scss']
|
||||||
})
|
})
|
||||||
export class ClassificationListComponent implements OnInit, OnDestroy {
|
export class ClassificationListComponent implements OnInit, OnDestroy {
|
||||||
selectedCategory = '';
|
taskanaType = TaskanaType;
|
||||||
selectedId: string;
|
requestInProgress = true;
|
||||||
selectionToImport = TaskanaType.CLASSIFICATIONS;
|
|
||||||
requestInProgress = false;
|
|
||||||
initialized = false;
|
|
||||||
inputValue: string;
|
inputValue: string;
|
||||||
classifications: Classification[] = [];
|
selectedCategory = '';
|
||||||
|
|
||||||
@Select(ClassificationSelectors.classificationTypes) classificationTypes$: Observable<string[]>;
|
@Select(ClassificationSelectors.classificationTypes) classificationTypes$: Observable<string[]>;
|
||||||
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
|
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
|
||||||
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
|
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
|
||||||
|
@Select(ClassificationSelectors.classifications) classifications$: Observable<TreeNodeModel[]>;
|
||||||
|
@Select(ClassificationSelectors.activeAction) activeAction$: Observable<ACTION>;
|
||||||
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
|
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
|
||||||
classificationServiceSubscription: Subscription;
|
|
||||||
classificationTypeSubscription: Subscription;
|
action: ACTION;
|
||||||
classificationSelectedSubscription: Subscription;
|
destroy$ = new Subject<void>();
|
||||||
classificationSavedSubscription: Subscription;
|
classifications: TreeNodeModel[];
|
||||||
importingExportingSubscription: Subscription;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private classificationService: ClassificationsService,
|
private classificationService: ClassificationsService,
|
||||||
private router: Router,
|
private location: Location,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private importExportService: ImportExportService,
|
private importExportService: ImportExportService,
|
||||||
private notificationsService: NotificationService
|
private notificationsService: NotificationService,
|
||||||
|
private store: Store,
|
||||||
|
private ngxsActions$: Actions
|
||||||
) {
|
) {
|
||||||
|
this.ngxsActions$.pipe(ofActionDispatched(GetClassifications),
|
||||||
|
takeUntil(this.destroy$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.requestInProgress = true;
|
||||||
|
});
|
||||||
|
this.ngxsActions$.pipe(ofActionCompleted(GetClassifications),
|
||||||
|
takeUntil(this.destroy$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.requestInProgress = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.classificationSavedSubscription = this.classificationService
|
this.classifications$.pipe(takeUntil(this.destroy$)).subscribe(classifications => {
|
||||||
.classificationSavedTriggered()
|
if (classifications) {
|
||||||
.subscribe(() => {
|
this.classifications = [...classifications];
|
||||||
this.performRequest(true);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.classificationTypeSubscription = this.classificationTypeSelected$.subscribe(() => {
|
this.classificationService
|
||||||
this.performRequest();
|
.classificationSavedTriggered()
|
||||||
this.selectClassification();
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.store.dispatch(new GetClassifications());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.classificationTypeSelected$
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.store.dispatch(new GetClassifications());
|
||||||
this.selectedCategory = '';
|
this.selectedCategory = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
this.importingExportingSubscription = this.importExportService.getImportingFinished().subscribe(() => {
|
this.importExportService.getImportingFinished()
|
||||||
this.performRequest(true);
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.store.dispatch(new GetClassifications());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.activeAction$
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(action => {
|
||||||
|
this.action = action;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectClassification(id?: string) {
|
|
||||||
this.selectedId = id;
|
|
||||||
|
|
||||||
if (id) {
|
|
||||||
this.router.navigate([{ outlets: { detail: [this.selectedId] } }], { relativeTo: this.route });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addClassification() {
|
addClassification() {
|
||||||
this.router.navigate([{ outlets: { detail: [`new-classification/${this.selectedId}`] } }], { relativeTo: this.route });
|
if (this.action !== ACTION.CREATE) {
|
||||||
|
this.store.dispatch(new SetActiveAction(ACTION.CREATE));
|
||||||
|
}
|
||||||
|
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications/new-classification'));
|
||||||
}
|
}
|
||||||
|
|
||||||
selectCategory(category: string) {
|
selectCategory(category: string) {
|
||||||
|
|
@ -95,55 +119,8 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private performRequest(forceRequest = false) {
|
|
||||||
if (this.initialized && !forceRequest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.requestInProgress = true;
|
|
||||||
this.classifications = [];
|
|
||||||
|
|
||||||
if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); }
|
|
||||||
if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); }
|
|
||||||
|
|
||||||
this.classificationServiceSubscription = this.classificationService.getClassifications()
|
|
||||||
.subscribe((classifications: TreeNodeModel[]) => {
|
|
||||||
this.requestInProgress = false;
|
|
||||||
this.classifications = classifications;
|
|
||||||
});
|
|
||||||
this.classificationSelectedSubscription = this.classificationService.getSelectedClassification()
|
|
||||||
.subscribe((classificationSelected: ClassificationDefinition) => {
|
|
||||||
this.selectedId = classificationSelected ? classificationSelected.classificationId : undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.initialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getClassifications(key?: string) {
|
|
||||||
this.requestInProgress = true;
|
|
||||||
this.classificationService.getClassifications()
|
|
||||||
.subscribe((classifications: TreeNodeModel[]) => {
|
|
||||||
this.classifications = classifications;
|
|
||||||
this.requestInProgress = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (key) {
|
|
||||||
this.notificationsService.showToast(
|
|
||||||
NOTIFICATION_TYPES.SUCCESS_ALERT_5,
|
|
||||||
new Map<string, string>([['classificationKey', key]])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private switchTaskanaSpinner($event) {
|
|
||||||
this.requestInProgress = $event;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (this.classificationServiceSubscription) { this.classificationServiceSubscription.unsubscribe(); }
|
this.destroy$.next();
|
||||||
if (this.classificationSelectedSubscription) { this.classificationSelectedSubscription.unsubscribe(); }
|
this.destroy$.complete();
|
||||||
if (this.classificationSavedSubscription) { this.classificationSavedSubscription.unsubscribe(); }
|
|
||||||
if (this.importingExportingSubscription) { this.importingExportingSubscription.unsubscribe(); }
|
|
||||||
if (this.classificationTypeSubscription) { this.classificationTypeSubscription.unsubscribe(); }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="no-gutter">
|
||||||
|
<div class="col-xs-12 col-md-4 vertical-right-divider">
|
||||||
|
<taskana-classification-list></taskana-classification-list>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-8" *ngIf="showDetail; else showEmptyPage">
|
||||||
|
<taskana-classification-details></taskana-classification-details>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-template #showEmptyPage>
|
||||||
|
<div class="hidden-xs hidden-sm col-md-8 container-no-items">
|
||||||
|
<div class="center-block no-detail">
|
||||||
|
<h3 class="grey">Select a classification</h3>
|
||||||
|
<svg-icon class="img-responsive empty-icon" src="./assets/icons/classification-empty.svg"></svg-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { NgxsModule } from '@ngxs/store';
|
||||||
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { Router, Routes } from '@angular/router';
|
||||||
|
import { ClassificationOverviewComponent } from './classification-overview.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'taskana-dummy-detail',
|
||||||
|
template: 'dummydetail'
|
||||||
|
})
|
||||||
|
export class DummyDetailComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ClassificationOverviewComponent', () => {
|
||||||
|
let component: ClassificationOverviewComponent;
|
||||||
|
let fixture: ComponentFixture<ClassificationOverviewComponent>;
|
||||||
|
let router;
|
||||||
|
let debugElement;
|
||||||
|
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go']);
|
||||||
|
|
||||||
|
beforeEach((() => {
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: ':id', component: DummyDetailComponent }
|
||||||
|
];
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
ClassificationOverviewComponent,
|
||||||
|
DummyDetailComponent],
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule.withRoutes(routes),
|
||||||
|
NgxsModule.forRoot()],
|
||||||
|
providers: [
|
||||||
|
{ provide: Location, useValue: locationSpy },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ClassificationOverviewComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
debugElement = fixture.debugElement.nativeElement;
|
||||||
|
router = TestBed.get(Router);
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
import { Select, Store } from '@ngxs/store';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { ClassificationSelectors } from '../../../shared/store/classification-store/classification.selectors';
|
||||||
|
import { ClassificationDefinition } from '../../../shared/models/classification-definition';
|
||||||
|
import { ACTION } from '../../../shared/models/action';
|
||||||
|
import { SelectClassification,
|
||||||
|
SetActiveAction } from '../../../shared/store/classification-store/classification.actions';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-classification-overview',
|
||||||
|
templateUrl: './classification-overview.component.html',
|
||||||
|
styleUrls: ['./classification-overview.component.scss']
|
||||||
|
})
|
||||||
|
export class ClassificationOverviewComponent implements OnInit, OnDestroy {
|
||||||
|
showDetail = false;
|
||||||
|
@Select(ClassificationSelectors.selectedClassification) selectedClassification$: Observable<ClassificationDefinition>;
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
routerParams: any;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private store: Store
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
if (this.route.firstChild) {
|
||||||
|
this.route.firstChild.params
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(params => {
|
||||||
|
this.routerParams = params;
|
||||||
|
|
||||||
|
if (this.routerParams.id) {
|
||||||
|
this.showDetail = true;
|
||||||
|
this.store.dispatch(new SelectClassification(this.routerParams.id));
|
||||||
|
}
|
||||||
|
if (this.routerParams.id && this.routerParams.id.indexOf('new-classification') !== -1) {
|
||||||
|
this.store.dispatch(new SetActiveAction(ACTION.CREATE));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedClassification$
|
||||||
|
.pipe(takeUntil(this.destroy$))
|
||||||
|
.subscribe(selectedClassification => {
|
||||||
|
this.showDetail = !!selectedClassification;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
|
||||||
workbasket: Workbasket;
|
workbasket: Workbasket;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
action: string;
|
action: ACTION;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
active: string;
|
active: string;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
|
||||||
selectedId: string;
|
selectedId: string;
|
||||||
showDetail = false;
|
showDetail = false;
|
||||||
requestInProgress = false;
|
requestInProgress = false;
|
||||||
action: string;
|
action: ACTION;
|
||||||
tabSelected = 'information';
|
tabSelected = 'information';
|
||||||
|
|
||||||
private workbasketSelectedSubscription: Subscription;
|
private workbasketSelectedSubscription: Subscription;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ export class WorkbasketDistributionTargetsComponent implements OnChanges, OnDest
|
||||||
workbasket: Workbasket;
|
workbasket: Workbasket;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
action: string;
|
action: ACTION;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
active: string;
|
active: string;
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ implements OnInit, OnChanges, OnDestroy {
|
||||||
workbasketClone: Workbasket;
|
workbasketClone: Workbasket;
|
||||||
workbasketErrors;
|
workbasketErrors;
|
||||||
@Input()
|
@Input()
|
||||||
action: string;
|
action: ACTION;
|
||||||
|
|
||||||
allTypes: Map<string, string>;
|
allTypes: Map<string, string>;
|
||||||
requestInProgress = false;
|
requestInProgress = false;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import { SelectedRouteService } from 'app/shared/services/selected-route/selecte
|
||||||
import { DomainService } from 'app/shared/services/domain/domain.service';
|
import { DomainService } from 'app/shared/services/domain/domain.service';
|
||||||
import { StartupService } from 'app/shared/services/startup/startup.service';
|
import { StartupService } from 'app/shared/services/startup/startup.service';
|
||||||
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
|
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
|
||||||
import { TreeService } from 'app/shared/services/tree/tree.service';
|
|
||||||
import { WindowRefService } from 'app/shared/services/window/window.service';
|
import { WindowRefService } from 'app/shared/services/window/window.service';
|
||||||
import { TaskanaEngineService } from 'app/shared/services/taskana-engine/taskana-engine.service';
|
import { TaskanaEngineService } from 'app/shared/services/taskana-engine/taskana-engine.service';
|
||||||
import { NavBarComponent } from 'app/shared/components/nav-bar/nav-bar.component';
|
import { NavBarComponent } from 'app/shared/components/nav-bar/nav-bar.component';
|
||||||
|
|
@ -50,7 +49,6 @@ import { UserGuard } from './shared/guards/user.guard';
|
||||||
import { ClassificationCategoriesService } from './shared/services/classification-categories/classification-categories.service';
|
import { ClassificationCategoriesService } from './shared/services/classification-categories/classification-categories.service';
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { STATES } from './shared/store';
|
import { STATES } from './shared/store';
|
||||||
import { ToastComponent } from './shared/components/toast/toast.component';
|
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
TabsModule.forRoot(),
|
TabsModule.forRoot(),
|
||||||
|
|
@ -101,7 +99,6 @@ export function startupServiceFactory(startupService: StartupService): () => Pro
|
||||||
multi: true
|
multi: true
|
||||||
},
|
},
|
||||||
MasterAndDetailService,
|
MasterAndDetailService,
|
||||||
TreeService,
|
|
||||||
TaskanaEngineService,
|
TaskanaEngineService,
|
||||||
FormsValidatorService,
|
FormsValidatorService,
|
||||||
UploadService,
|
UploadService,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ declare let $: any;
|
||||||
export class SpinnerComponent implements OnDestroy {
|
export class SpinnerComponent implements OnDestroy {
|
||||||
showSpinner: boolean;
|
showSpinner: boolean;
|
||||||
@Input()
|
@Input()
|
||||||
delay = 250;
|
delay = 0;
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
isModal = false;
|
isModal = false;
|
||||||
|
|
@ -50,9 +50,6 @@ export class SpinnerComponent implements OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.currentTimeout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.runSpinner(value);
|
this.runSpinner(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<tree-root #tree [nodes]="treeNodes" [options]="options" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"
|
<tree-root #tree [nodes]="classifications" [options]="options" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"
|
||||||
(moveNode)="onMoveNode($event)" (treeDrop)="onDrop($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">
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,14 @@ 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 { TreeService } from 'app/shared/services/tree/tree.service';
|
|
||||||
import { configureTests } from 'app/app.test.configuration';
|
import { configureTests } from 'app/app.test.configuration';
|
||||||
import { NgxsModule } from '@ngxs/store';
|
import { NgxsModule, Store } from '@ngxs/store';
|
||||||
|
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { ACTION } from 'app/shared/models/action';
|
||||||
|
import { TreeNodeModel } from 'app/shared/models/tree-node';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { UpdateClassification } from 'app/shared/store/classification-store/classification.actions';
|
||||||
import { TaskanaTreeComponent } from './tree.component';
|
import { TaskanaTreeComponent } from './tree.component';
|
||||||
import { ClassificationDefinition } from '../../models/classification-definition';
|
import { ClassificationDefinition } from '../../models/classification-definition';
|
||||||
import { LinksClassification } from '../../models/links-classfication';
|
import { LinksClassification } from '../../models/links-classfication';
|
||||||
|
|
@ -28,52 +33,46 @@ describe('TaskanaTreeComponent', () => {
|
||||||
let moveNodeEvent;
|
let moveNodeEvent;
|
||||||
let dropEvent;
|
let dropEvent;
|
||||||
|
|
||||||
|
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go', 'path']);
|
||||||
|
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select', 'dispatch']);
|
||||||
|
|
||||||
const configure = (testBed: TestBed) => {
|
const configure = (testBed: TestBed) => {
|
||||||
testBed.configureTestingModule({
|
testBed.configureTestingModule({
|
||||||
imports: [AngularSvgIconModule, HttpClientModule, NgxsModule.forRoot()],
|
imports: [AngularSvgIconModule, HttpClientModule, NgxsModule.forRoot()],
|
||||||
declarations: [TreeVendorComponent],
|
declarations: [TreeVendorComponent],
|
||||||
providers: [TreeService, ClassificationsService]
|
providers: [ClassificationsService,
|
||||||
|
{ provide: Location, useValue: locationSpy },
|
||||||
|
{ provide: Store, useValue: storeSpy }]
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(done => {
|
beforeEach(done => {
|
||||||
configureTests(configure).then(testBed => {
|
configureTests(configure).then(testBed => {
|
||||||
|
locationSpy.path.and.callFake(() => '');
|
||||||
|
storeSpy.select.and.callFake(selector => {
|
||||||
|
switch (selector) {
|
||||||
|
case ClassificationSelectors.selectedClassificationId:
|
||||||
|
return of('id4');
|
||||||
|
case ClassificationSelectors.activeAction:
|
||||||
|
return of(ACTION.CREATE);
|
||||||
|
case ClassificationSelectors.classifications:
|
||||||
|
return of([new TreeNodeModel('id4')]);
|
||||||
|
default:
|
||||||
|
return of();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
fixture = testBed.createComponent(TaskanaTreeComponent);
|
fixture = testBed.createComponent(TaskanaTreeComponent);
|
||||||
classificationsService = testBed.get(ClassificationsService);
|
classificationsService = testBed.get(ClassificationsService);
|
||||||
spyOn(classificationsService, 'putClassification').and.callFake((url, classification) => classification);
|
|
||||||
moveNodeEvent = {
|
moveNodeEvent = {
|
||||||
eventName: 'moveNode',
|
eventName: 'moveNode',
|
||||||
node: {
|
node: { classificationId: 'id4', parentId: '', parentKey: '', _links: { self: { href: 'url' } } },
|
||||||
classificationId: 'id4',
|
to: { parent: { classificationId: 'id3', key: 'key3' } }
|
||||||
parentId: '',
|
|
||||||
parentKey: '',
|
|
||||||
_links: {
|
|
||||||
self: {
|
|
||||||
href: 'url'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
to: {
|
|
||||||
parent: {
|
|
||||||
classificationId: 'id3',
|
|
||||||
key: 'key3'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dropEvent = {
|
dropEvent = {
|
||||||
event: {
|
event: { target: { tagName: 'TREE-VIEWPORT' } },
|
||||||
target: {
|
element: { data: { classificationId: 'id3', parentId: 'id1', parentKey: 'key1' } }
|
||||||
tagName: 'TREE-VIEWPORT'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
element: {
|
|
||||||
data: {
|
|
||||||
classificationId: 'id3',
|
|
||||||
parentId: 'id1',
|
|
||||||
parentKey: 'key1'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
|
@ -87,12 +86,15 @@ describe('TaskanaTreeComponent', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be change the classification parent (onMoveNode)', async () => {
|
it('should be change the classification parent (onMoveNode)', async () => {
|
||||||
spyOn(classificationsService, 'getClassification').and.returnValue(new ClassificationDefinition('id4',
|
const classification = new ClassificationDefinition('id4',
|
||||||
'key4', '', '', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
|
'key4', '', '', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
|
||||||
'classification4', 'description', 1, 'level', '', '', '', '', '', '',
|
'classification4', 'description', 1, 'level', '', '', '', '', '', '',
|
||||||
'', '', '', new LinksClassification({ href: '' }, '', '', { href: '' }, { href: '' }, { href: '' })));
|
'', '', '', new LinksClassification({ href: '' }, '', '', { href: '' }, { href: '' }, { href: '' }));
|
||||||
|
|
||||||
|
// using parameter 'any' since getClassification is a private method
|
||||||
|
spyOn<any>(component, 'getClassification').and.returnValue(classification);
|
||||||
spyOn(component, 'switchTaskanaSpinner');
|
spyOn(component, 'switchTaskanaSpinner');
|
||||||
const classification = classificationsService.getClassification();
|
|
||||||
expect(classification.parentId).toEqual('');
|
expect(classification.parentId).toEqual('');
|
||||||
expect(classification.parentKey).toEqual('');
|
expect(classification.parentKey).toEqual('');
|
||||||
|
|
||||||
|
|
@ -100,18 +102,21 @@ describe('TaskanaTreeComponent', () => {
|
||||||
|
|
||||||
expect(classification.parentId).toEqual('id3');
|
expect(classification.parentId).toEqual('id3');
|
||||||
expect(classification.parentKey).toEqual('key3');
|
expect(classification.parentKey).toEqual('key3');
|
||||||
expect(classificationsService.putClassification).toHaveBeenCalledWith(classification._links.self.href, classification);
|
expect(storeSpy.dispatch).toHaveBeenCalledWith(new UpdateClassification(classification));
|
||||||
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(true);
|
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(true);
|
||||||
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(false);
|
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be changed the parent classification to root node (onDrop)', async () => {
|
it('should be changed the parent classification to root node (onDrop)', async () => {
|
||||||
spyOn(classificationsService, 'getClassification').and.returnValue(new ClassificationDefinition('id3',
|
const classification = new ClassificationDefinition('id3',
|
||||||
'key3', 'id1', 'key1', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
|
'key3', 'id1', 'key1', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
|
||||||
'classification3', 'description', 1, 'level', '', '', '', '', '', '',
|
'classification3', 'description', 1, 'level', '', '', '', '', '', '',
|
||||||
'', '', '', new LinksClassification({ href: '' }, '', '', { href: '' }, { href: '' }, { href: '' })));
|
'', '', '', new LinksClassification({ href: '' }, '', '', { href: '' }, { href: '' }, { href: '' }));
|
||||||
|
|
||||||
|
// using parameter 'any' since getClassification is a private method
|
||||||
|
spyOn<any>(component, 'getClassification').and.returnValue(classification);
|
||||||
spyOn(component, 'switchTaskanaSpinner');
|
spyOn(component, 'switchTaskanaSpinner');
|
||||||
const classification = classificationsService.getClassification();
|
|
||||||
expect(classification.parentId).toEqual('id1');
|
expect(classification.parentId).toEqual('id1');
|
||||||
expect(classification.parentKey).toEqual('key1');
|
expect(classification.parentKey).toEqual('key1');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,18 +10,25 @@ import { AfterViewChecked,
|
||||||
ViewChild } from '@angular/core';
|
ViewChild } from '@angular/core';
|
||||||
import { TreeNodeModel } from 'app/shared/models/tree-node';
|
import { TreeNodeModel } from 'app/shared/models/tree-node';
|
||||||
|
|
||||||
import { ITreeOptions, KEYS, TreeComponent, TreeNode } from 'angular-tree-component';
|
import { ITreeOptions, KEYS, TREE_ACTIONS, TreeComponent } from 'angular-tree-component';
|
||||||
import { Pair } from 'app/shared/models/pair';
|
import { Pair } from 'app/shared/models/pair';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map, takeUntil } from 'rxjs/operators';
|
||||||
import { Select } from '@ngxs/store';
|
import { Select, Store } from '@ngxs/store';
|
||||||
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
|
||||||
|
|
||||||
import { TreeService } from '../../services/tree/tree.service';
|
import { Location } from '@angular/common';
|
||||||
|
import { NOTIFICATION_TYPES } from 'app/shared/models/notifications';
|
||||||
|
import { NotificationService } from 'app/shared/services/notifications/notification.service';
|
||||||
import { Classification } from '../../models/classification';
|
import { Classification } from '../../models/classification';
|
||||||
import { ClassificationDefinition } from '../../models/classification-definition';
|
import { ClassificationDefinition } from '../../models/classification-definition';
|
||||||
import { ClassificationsService } from '../../services/classifications/classifications.service';
|
import { ClassificationsService } from '../../services/classifications/classifications.service';
|
||||||
import { ClassificationCategoryImages } from '../../models/customisation';
|
import { ClassificationCategoryImages } from '../../models/customisation';
|
||||||
|
import { ClassificationSelectors } from '../../store/classification-store/classification.selectors';
|
||||||
|
import { DeselectClassification,
|
||||||
|
SelectClassification,
|
||||||
|
UpdateClassification } from '../../store/classification-store/classification.actions';
|
||||||
|
import { ACTION } from '../../models/action';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'taskana-tree',
|
selector: 'taskana-tree',
|
||||||
|
|
@ -29,23 +36,23 @@ import { ClassificationCategoryImages } from '../../models/customisation';
|
||||||
styleUrls: ['./tree.component.scss'],
|
styleUrls: ['./tree.component.scss'],
|
||||||
})
|
})
|
||||||
export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy {
|
export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy {
|
||||||
@Input() treeNodes: Array<TreeNodeModel>;
|
classifications: TreeNodeModel[];
|
||||||
@Output() treeNodesChange = new EventEmitter<Array<TreeNodeModel>>();
|
|
||||||
@Input() selectNodeId: string;
|
@Input() selectNodeId: 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>();
|
@Output() switchTaskanaSpinnerEmit = new EventEmitter<boolean>();
|
||||||
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
|
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
|
||||||
|
@Select(ClassificationSelectors.selectedClassificationId) selectedClassificationId$: Observable<string>;
|
||||||
|
@Select(ClassificationSelectors.activeAction) activeAction$: Observable<ACTION>;
|
||||||
|
@Select(ClassificationSelectors.classifications) classifications$: Observable<TreeNodeModel[]>;
|
||||||
|
|
||||||
options: ITreeOptions = {
|
options: ITreeOptions = {
|
||||||
displayField: 'name',
|
displayField: 'name',
|
||||||
idField: 'classificationId',
|
idField: 'classificationId',
|
||||||
actionMapping: {
|
actionMapping: {
|
||||||
keys: {
|
keys: {
|
||||||
[KEYS.ENTER]: (tree, node, $event) => {
|
[KEYS.ENTER]: TREE_ACTIONS.TOGGLE_ACTIVE,
|
||||||
node.toggleExpanded();
|
[KEYS.SPACE]: TREE_ACTIONS.TOGGLE_EXPANDED
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
useVirtualScroll: true,
|
useVirtualScroll: true,
|
||||||
|
|
@ -61,36 +68,56 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
|
|
||||||
private filterTextOld: string;
|
private filterTextOld: string;
|
||||||
private filterIconOld = '';
|
private filterIconOld = '';
|
||||||
private removedNodeIdSubscription: Subscription;
|
private action: ACTION;
|
||||||
|
private destroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private treeService: TreeService,
|
|
||||||
private elementRef: ElementRef,
|
private elementRef: ElementRef,
|
||||||
private classificationsService: ClassificationsService,
|
private classificationsService: ClassificationsService,
|
||||||
|
private location: Location,
|
||||||
|
private store: Store,
|
||||||
|
private notificationsService: NotificationService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:click', ['$event'])
|
@HostListener('document:click', ['$event'])
|
||||||
onDocumentClick(event) {
|
onDocumentClick(event) {
|
||||||
if (this.checkValidElements(event) && this.tree.treeModel.getActiveNode()) {
|
if (this.checkValidElements(event) && this.tree.treeModel.getActiveNode()) {
|
||||||
this.unSelectActiveNode();
|
this.deselectActiveNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.removedNodeIdSubscription = this.treeService.getRemovedNodeId().subscribe(value => {
|
this.activeAction$.pipe(takeUntil(this.destroy$)).subscribe(action => {
|
||||||
const removedNode = this.getNode(value);
|
this.action = action;
|
||||||
if (removedNode.parent) {
|
});
|
||||||
removedNode.parent.collapse();
|
|
||||||
|
this.selectedClassificationId$.pipe(takeUntil(this.destroy$)).subscribe(selectedClassificationId => {
|
||||||
|
this.selectNodeId = typeof selectedClassificationId !== 'undefined' ? selectedClassificationId : undefined;
|
||||||
|
if (typeof this.tree.treeModel.getActiveNode() !== 'undefined') {
|
||||||
|
if (this.tree.treeModel.getActiveNode().data.classificationId !== this.selectNodeId) {
|
||||||
|
this.selectNode(this.selectNodeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.classifications$.pipe(takeUntil(this.destroy$)).subscribe(classifications => {
|
||||||
|
if (typeof (classifications) !== 'undefined') {
|
||||||
|
this.classifications = classifications.map(this.classificationsDeepCopy.bind(this));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
classificationsDeepCopy(classification: TreeNodeModel) {
|
||||||
|
const ret: TreeNodeModel = { ...classification };
|
||||||
|
ret.children = ret.children ? [...ret.children] : [];
|
||||||
|
ret.children = ret.children.map(this.classificationsDeepCopy.bind(this));
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
} else if (!this.selectNodeId && this.tree.treeModel.getActiveNode()) {
|
|
||||||
this.unSelectActiveNode();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.filterTextOld !== this.filterText
|
if (this.filterTextOld !== this.filterText
|
||||||
|
|
@ -103,11 +130,17 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
onActivate(treeNode: any) {
|
onActivate(treeNode: any) {
|
||||||
this.selectNodeIdChanged.emit(`${treeNode.node.data.classificationId}`);
|
const id = treeNode.node.data.classificationId;
|
||||||
|
this.selectNodeId = id;
|
||||||
|
this.store.dispatch(new SelectClassification(id));
|
||||||
|
this.location.go(this.location.path().replace(/(classifications).*/g, `classifications/(detail:${id})`));
|
||||||
}
|
}
|
||||||
|
|
||||||
onDeactivate(treeNode: any) {
|
onDeactivate(event: any) {
|
||||||
this.selectNodeIdChanged.emit();
|
if (!event.treeModel.activeNodes.length && this.action !== ACTION.CREATE) {
|
||||||
|
this.store.dispatch(new DeselectClassification());
|
||||||
|
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async onMoveNode($event) {
|
async onMoveNode($event) {
|
||||||
|
|
@ -116,7 +149,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
classification.parentId = $event.to.parent.classificationId;
|
classification.parentId = $event.to.parent.classificationId;
|
||||||
classification.parentKey = $event.to.parent.key;
|
classification.parentKey = $event.to.parent.key;
|
||||||
this.collapseParentNodeIfItIsTheLastChild($event.node);
|
this.collapseParentNodeIfItIsTheLastChild($event.node);
|
||||||
await this.updateClassification(classification);
|
this.updateClassification(classification);
|
||||||
}
|
}
|
||||||
|
|
||||||
async onDrop($event) {
|
async onDrop($event) {
|
||||||
|
|
@ -126,7 +159,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
this.collapseParentNodeIfItIsTheLastChild($event.element.data);
|
this.collapseParentNodeIfItIsTheLastChild($event.element.data);
|
||||||
classification.parentId = '';
|
classification.parentId = '';
|
||||||
classification.parentKey = '';
|
classification.parentKey = '';
|
||||||
await this.updateClassification(classification);
|
this.updateClassification(classification);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,23 +175,16 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
this.switchTaskanaSpinnerEmit.emit(active);
|
this.switchTaskanaSpinnerEmit.emit(active);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
if (this.removedNodeIdSubscription) {
|
|
||||||
this.removedNodeIdSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unSelectActiveNode() {
|
private deselectActiveNode() {
|
||||||
const activeNode = this.tree.treeModel.getActiveNode();
|
const activeNode = this.tree.treeModel.getActiveNode();
|
||||||
delete this.selectNodeId;
|
delete this.selectNodeId;
|
||||||
activeNode.setIsActive(false);
|
activeNode.setIsActive(false);
|
||||||
|
|
@ -169,14 +195,6 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
return this.tree.treeModel.getNodeById(nodeId);
|
return this.tree.treeModel.getNodeById(nodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private expandParent(node: TreeNode) {
|
|
||||||
if (!node.parent || node.isRoot) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
node.parent.expand();
|
|
||||||
this.expandParent(node.parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private filterNodes(text, iconText) {
|
private filterNodes(text, iconText) {
|
||||||
this.tree.treeModel.filterNodes(node => this.checkNameAndKey(node, text)
|
this.tree.treeModel.filterNodes(node => this.checkNameAndKey(node, text)
|
||||||
&& this.checkIcon(node, iconText));
|
&& this.checkIcon(node, iconText));
|
||||||
|
|
@ -206,12 +224,15 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
}
|
}
|
||||||
|
|
||||||
private getClassification(classificationId: string): Promise<ClassificationDefinition> {
|
private getClassification(classificationId: string): Promise<ClassificationDefinition> {
|
||||||
return this.classificationsService.getClassification(classificationId);
|
return this.classificationsService.getClassification(classificationId).toPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateClassification(classification: Classification) {
|
private updateClassification(classification: Classification) {
|
||||||
await this.classificationsService.putClassification(classification._links.self.href, classification);
|
this.store.dispatch(new UpdateClassification(classification));
|
||||||
this.refreshClassification.emit(classification.key);
|
this.notificationsService.showToast(
|
||||||
|
NOTIFICATION_TYPES.SUCCESS_ALERT_5,
|
||||||
|
new Map<string, string>([['classificationKey', classification.key]])
|
||||||
|
);
|
||||||
this.switchTaskanaSpinner(false);
|
this.switchTaskanaSpinner(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,4 +242,9 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|
||||||
this.getNode(node.parentId).collapse();
|
this.getNode(node.parentId).collapse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
export enum ACTION {
|
export enum ACTION {
|
||||||
CREATE = 'CREATE',
|
CREATE,
|
||||||
COPY = 'COPY'
|
COPY
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { LinksClassification } from 'app/shared/models/links-classfication';
|
import { LinksClassification } from 'app/shared/models/links-classfication';
|
||||||
|
|
||||||
export class ClassificationDefinition {
|
export class ClassificationDefinition {
|
||||||
constructor(public classificationId?: string,
|
constructor(
|
||||||
|
public classificationId?: string,
|
||||||
public key?: string,
|
public key?: string,
|
||||||
public parentId?: string,
|
public parentId?: string,
|
||||||
public parentKey?: string,
|
public parentKey?: string,
|
||||||
|
|
@ -24,7 +25,8 @@ export class ClassificationDefinition {
|
||||||
public custom6?: string,
|
public custom6?: string,
|
||||||
public custom7?: string,
|
public custom7?: string,
|
||||||
public custom8?: string,
|
public custom8?: string,
|
||||||
public _links?: LinksClassification) {
|
public _links?: LinksClassification
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Links } from './links';
|
||||||
|
|
||||||
export class ClassificationResource {
|
export class ClassificationResource {
|
||||||
constructor(
|
constructor(
|
||||||
public classifications: Array<Classification> = [],
|
public classifications: Classification[] = [],
|
||||||
public _links?: Links
|
public _links?: Links
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Links } from 'app/shared/models/links';
|
import { Links } from 'app/shared/models/links';
|
||||||
|
|
||||||
export class Classification {
|
export class Classification {
|
||||||
constructor(public classificationId?: string, // newly created classifications don't have an id yet.
|
constructor(
|
||||||
|
public classificationId?: string, // newly created classifications don't have an id yet.
|
||||||
public key?: string,
|
public key?: string,
|
||||||
public category?: string,
|
public category?: string,
|
||||||
public type?: string,
|
public type?: string,
|
||||||
|
|
@ -10,6 +11,7 @@ export class Classification {
|
||||||
public parentId?: string,
|
public parentId?: string,
|
||||||
public priority?: number,
|
public priority?: number,
|
||||||
public serviceLevel?: string,
|
public serviceLevel?: string,
|
||||||
public _links?: Links) {
|
public _links?: Links
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ export const notifications = new Map<NOTIFICATION_TYPES, Pair>([
|
||||||
// taskdetails.component
|
// taskdetails.component
|
||||||
[NOTIFICATION_TYPES.INFO_ALERT, new Pair(
|
[NOTIFICATION_TYPES.INFO_ALERT, new Pair(
|
||||||
'',
|
'',
|
||||||
'Reset edited fields'
|
'Refreshed selected classification'
|
||||||
)],
|
)],
|
||||||
// classification-details.component
|
// classification-details.component
|
||||||
[NOTIFICATION_TYPES.SUCCESS_ALERT_4, new Pair(
|
[NOTIFICATION_TYPES.SUCCESS_ALERT_4, new Pair(
|
||||||
|
|
@ -310,5 +310,5 @@ export const notifications = new Map<NOTIFICATION_TYPES, Pair>([
|
||||||
[NOTIFICATION_TYPES.INFO_ALERT_2, new Pair(
|
[NOTIFICATION_TYPES.INFO_ALERT_2, new Pair(
|
||||||
'',
|
'',
|
||||||
'The selected Workbasket is empty!'
|
'The selected Workbasket is empty!'
|
||||||
)],
|
)]
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { Classification } from 'app/shared/models/classification';
|
import { Classification } from 'app/shared/models/classification';
|
||||||
|
|
||||||
export class TreeNodeModel extends Classification {
|
export class TreeNodeModel extends Classification {
|
||||||
constructor(public id?: string,
|
constructor(
|
||||||
|
public id?: string,
|
||||||
public key?: string,
|
public key?: string,
|
||||||
public category?: string,
|
public category?: string,
|
||||||
public type?: string,
|
public type?: string,
|
||||||
|
|
@ -10,7 +11,8 @@ export class TreeNodeModel extends Classification {
|
||||||
public parentId?: string,
|
public parentId?: string,
|
||||||
public priority?: number,
|
public priority?: number,
|
||||||
public serviceLevel?: string,
|
public serviceLevel?: string,
|
||||||
public children: Array<TreeNodeModel> = []) {
|
public children: TreeNodeModel[] = []
|
||||||
|
) {
|
||||||
super(id, key, category, type, domain, name, parentId, priority, serviceLevel);
|
super(id, key, category, type, domain, name, parentId, priority, serviceLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ import { WorkbasketAccessItems } from './workbasket-access-items';
|
||||||
import { Workbasket } from './workbasket';
|
import { Workbasket } from './workbasket';
|
||||||
|
|
||||||
export class WorkbasketDefinition {
|
export class WorkbasketDefinition {
|
||||||
constructor(public distributionTargets: string[],
|
constructor(
|
||||||
|
public distributionTargets: string[],
|
||||||
public workbasketAccessItems: WorkbasketAccessItems[],
|
public workbasketAccessItems: WorkbasketAccessItems[],
|
||||||
public workbasket: Workbasket) {
|
public workbasket: Workbasket
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
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 { Observable, Subject } from 'rxjs';
|
||||||
import { map, mergeMap, tap } from 'rxjs/operators';
|
import { map, mergeMap, tap } from 'rxjs/operators';
|
||||||
import { Select, Store } from '@ngxs/store';
|
|
||||||
|
|
||||||
import { Classification } from 'app/shared/models/classification';
|
import { Classification } from 'app/shared/models/classification';
|
||||||
import { ClassificationDefinition } from 'app/shared/models/classification-definition';
|
import { ClassificationDefinition } from 'app/shared/models/classification-definition';
|
||||||
|
|
@ -13,23 +12,17 @@ import { DomainService } from 'app/shared/services/domain/domain.service';
|
||||||
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
|
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
|
||||||
import { Direction } from 'app/shared/models/sorting';
|
import { Direction } from 'app/shared/models/sorting';
|
||||||
import { QueryParameters } from 'app/shared/models/query-parameters';
|
import { QueryParameters } from 'app/shared/models/query-parameters';
|
||||||
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
|
|
||||||
import { SetSelectedClassificationType } from 'app/shared/store/classification-store/classification.actions';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClassificationsService {
|
export class ClassificationsService {
|
||||||
private url = `${environment.taskanaRestUrl}/v1/classifications/`;
|
private url = `${environment.taskanaRestUrl}/v1/classifications/`;
|
||||||
private classificationSelected = new Subject<ClassificationDefinition>();
|
|
||||||
private classificationSaved = new Subject<number>();
|
private classificationSaved = new Subject<number>();
|
||||||
private classificationResourcePromise: Promise<ClassificationResource>;
|
private classificationResourcePromise: Promise<ClassificationResource>;
|
||||||
private lastDomain: string;
|
private lastDomain: string;
|
||||||
// TODO: this should not be here in the service
|
|
||||||
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private domainService: DomainService,
|
private domainService: DomainService,
|
||||||
private store: Store
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private static classificationParameters(domain: string): QueryParameters {
|
private static classificationParameters(domain: string): QueryParameters {
|
||||||
|
|
@ -44,11 +37,11 @@ export class ClassificationsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
getClassifications(): Observable<Array<Classification>> {
|
getClassifications(classificationType?: string): Observable<Array<Classification>> {
|
||||||
return this.domainService.getSelectedDomain().pipe(
|
return this.domainService.getSelectedDomain().pipe(
|
||||||
mergeMap(domain => this.getClassificationObservable(this.httpClient.get<ClassificationResource>(
|
mergeMap(domain => this.getClassificationObservable(this.httpClient.get<ClassificationResource>(
|
||||||
`${this.url}${TaskanaQueryParameters.getQueryParameters(ClassificationsService.classificationParameters(domain))}`
|
`${this.url}${TaskanaQueryParameters.getQueryParameters(ClassificationsService.classificationParameters(domain))}`
|
||||||
))),
|
), classificationType)),
|
||||||
tap(() => {
|
tap(() => {
|
||||||
this.domainService.domainChangedComplete();
|
this.domainService.domainChangedComplete();
|
||||||
})
|
})
|
||||||
|
|
@ -67,13 +60,8 @@ export class ClassificationsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
getClassification(id: string): Promise<ClassificationDefinition> {
|
getClassification(id: string): Observable<ClassificationDefinition> {
|
||||||
return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`)
|
return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`);
|
||||||
.pipe(tap((classification: ClassificationDefinition) => {
|
|
||||||
if (classification) {
|
|
||||||
this.store.dispatch(new SetSelectedClassificationType(classification.type));
|
|
||||||
}
|
|
||||||
})).toPromise();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST
|
// POST
|
||||||
|
|
@ -82,23 +70,16 @@ export class ClassificationsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT
|
// PUT
|
||||||
putClassification(url: string, classification: Classification): Promise<Classification> {
|
putClassification(classification: Classification): Observable<Classification> {
|
||||||
return this.httpClient.put<Classification>(url, classification).toPromise();
|
return this.httpClient.put<Classification>(`${this.url}${classification.classificationId}`, classification);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
deleteClassification(url: string): Observable<string> {
|
deleteClassification(id: string): Observable<string> {
|
||||||
return this.httpClient.delete<string>(url);
|
return this.httpClient.delete<string>(`${this.url}${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// #region "Service extras"
|
// #region "Service extras"
|
||||||
selectClassification(classification?: ClassificationDefinition) {
|
|
||||||
this.classificationSelected.next(classification);
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedClassification(): Observable<ClassificationDefinition> {
|
|
||||||
return this.classificationSelected.asObservable();
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerClassificationSaved() {
|
triggerClassificationSaved() {
|
||||||
this.classificationSaved.next(Date.now());
|
this.classificationSaved.next(Date.now());
|
||||||
|
|
@ -110,17 +91,15 @@ export class ClassificationsService {
|
||||||
|
|
||||||
// #endregion
|
// #endregion
|
||||||
|
|
||||||
private getClassificationObservable(classificationRef: Observable<ClassificationResource>): Observable<Array<Classification>> {
|
private getClassificationObservable(
|
||||||
return combineLatest(
|
classificationRef: Observable<ClassificationResource>,
|
||||||
[classificationRef,
|
classificationType: string
|
||||||
this.classificationTypeSelected$]
|
): Observable<Array<Classification>> {
|
||||||
).pipe(
|
return classificationRef.pipe(map(
|
||||||
map(
|
(resource: ClassificationResource) => (
|
||||||
([resource, type]: [ClassificationResource, string]) => (
|
resource.classifications ? this.buildHierarchy(resource.classifications, classificationType) : []
|
||||||
resource.classifications ? this.buildHierarchy(resource.classifications, type) : []
|
|
||||||
)
|
)
|
||||||
)
|
));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildHierarchy(classifications: Array<Classification>, type: string): Array<Classification> {
|
private buildHierarchy(classifications: Array<Classification>, type: string): Array<Classification> {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import { Injectable } from '@angular/core';
|
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class TreeService {
|
|
||||||
public removedNodeId = new Subject<string>();
|
|
||||||
|
|
||||||
setRemovedNodeId(value: string) {
|
|
||||||
this.removedNodeId.next(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRemovedNodeId() {
|
|
||||||
return this.removedNodeId.asObservable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,57 @@
|
||||||
|
import { ClassificationDefinition } from '../../models/classification-definition';
|
||||||
|
import { ACTION } from '../../models/action';
|
||||||
|
import { Classification } from '../../models/classification';
|
||||||
|
|
||||||
export class SetSelectedClassificationType {
|
export class SetSelectedClassificationType {
|
||||||
static readonly type = '[Classification-Types-Selector] Set selected classification type';
|
static readonly type = '[Classification-Selected Type] Set selected classification type';
|
||||||
constructor(public selectedType: string) {
|
constructor(public selectedType: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class SelectClassification {
|
||||||
|
static readonly type = '[Classification] Select a classification';
|
||||||
|
constructor(public classificationId: string) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeselectClassification {
|
||||||
|
static readonly type = '[Classification] Deselect a classification';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateClassification {
|
||||||
|
static readonly type = '[Classification] Create a new classification';
|
||||||
|
constructor(public classification: ClassificationDefinition) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SaveClassification {
|
||||||
|
static readonly type = '[Classification] Save a classification and select it';
|
||||||
|
constructor(public classification: ClassificationDefinition) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RestoreSelectedClassification {
|
||||||
|
static readonly type = '[Classification] Fetch and restore a classification';
|
||||||
|
constructor(public classificationId: string) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SetActiveAction {
|
||||||
|
static readonly type = '[Classification] Set the currently active Action';
|
||||||
|
constructor(public action?: ACTION) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RemoveSelectedClassification {
|
||||||
|
static readonly type = '[Classification] Remove the selected Classification';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetClassifications {
|
||||||
|
static readonly type = '[Classification] Get all classifications';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateClassification {
|
||||||
|
static readonly type = '[Tree] Update a classification and refetch all classifications';
|
||||||
|
constructor(public classification: Classification) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { Selector } from '@ngxs/store';
|
import { Selector } from '@ngxs/store';
|
||||||
import { ClassificationStateModel, ClassificationState } from './classification.state';
|
import { ClassificationStateModel, ClassificationState } from './classification.state';
|
||||||
|
import { ClassificationDefinition } from '../../models/classification-definition';
|
||||||
|
import { ACTION } from '../../models/action';
|
||||||
|
|
||||||
export class ClassificationSelectors {
|
export class ClassificationSelectors {
|
||||||
@Selector([ClassificationState])
|
@Selector([ClassificationState])
|
||||||
|
|
@ -21,4 +23,24 @@ export class ClassificationSelectors {
|
||||||
static selectClassificationTypesObject(state: ClassificationStateModel): Object {
|
static selectClassificationTypesObject(state: ClassificationStateModel): Object {
|
||||||
return state.classificationTypes;
|
return state.classificationTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Selector([ClassificationState])
|
||||||
|
static classifications(state: ClassificationStateModel): ClassificationDefinition[] {
|
||||||
|
return state.classifications;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Selector([ClassificationState])
|
||||||
|
static selectedClassification(state: ClassificationStateModel): ClassificationDefinition {
|
||||||
|
return state.selectedClassification;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Selector([ClassificationState])
|
||||||
|
static activeAction(state: ClassificationStateModel): ACTION {
|
||||||
|
return state.action;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Selector([ClassificationState])
|
||||||
|
static selectedClassificationId(state: ClassificationStateModel): string {
|
||||||
|
return state.selectedClassification.classificationId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,30 @@
|
||||||
import { Action, State, StateContext } from '@ngxs/store';
|
import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { take, tap } from 'rxjs/operators';
|
||||||
import { CategoriesResponse, ClassificationCategoriesService } from '../../services/classification-categories/classification-categories.service';
|
import { CategoriesResponse,
|
||||||
import { SetSelectedClassificationType } from './classification.actions';
|
ClassificationCategoriesService } from '../../services/classification-categories/classification-categories.service';
|
||||||
|
import { CreateClassification,
|
||||||
|
DeselectClassification,
|
||||||
|
GetClassifications,
|
||||||
|
RemoveSelectedClassification,
|
||||||
|
RestoreSelectedClassification,
|
||||||
|
SaveClassification,
|
||||||
|
SelectClassification,
|
||||||
|
SetActiveAction,
|
||||||
|
SetSelectedClassificationType,
|
||||||
|
UpdateClassification } from './classification.actions';
|
||||||
|
import { ClassificationsService } from '../../services/classifications/classifications.service';
|
||||||
|
import { ClassificationDefinition } from '../../models/classification-definition';
|
||||||
|
import { ACTION } from '../../models/action';
|
||||||
|
|
||||||
class InitializeStore {
|
class InitializeStore {
|
||||||
static readonly type = '[ClassificationState] Initializing state';
|
static readonly type = '[ClassificationState] Initializing state';
|
||||||
}
|
}
|
||||||
|
|
||||||
@State<ClassificationStateModel>({ name: 'classification' })
|
@State<ClassificationStateModel>({ name: 'classification' })
|
||||||
export class ClassificationState {
|
export class ClassificationState implements NgxsAfterBootstrap {
|
||||||
constructor(private categoryService: ClassificationCategoriesService) {
|
constructor(private categoryService: ClassificationCategoriesService,
|
||||||
|
private classificationsService: ClassificationsService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Action(SetSelectedClassificationType)
|
@Action(SetSelectedClassificationType)
|
||||||
|
|
@ -21,19 +35,143 @@ export class ClassificationState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Action(SelectClassification)
|
||||||
|
selectClassification(ctx: StateContext<ClassificationStateModel>, action: SelectClassification): Observable<any> | void {
|
||||||
|
if (typeof action.classificationId !== 'undefined') {
|
||||||
|
return this.classificationsService.getClassification(action.classificationId).pipe(take(1), tap(
|
||||||
|
selectedClassification => {
|
||||||
|
ctx.patchState({
|
||||||
|
selectedClassification,
|
||||||
|
action: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(DeselectClassification)
|
||||||
|
deselectClassification(ctx: StateContext<ClassificationStateModel>): Observable<any> | void {
|
||||||
|
ctx.patchState({
|
||||||
|
selectedClassification: undefined,
|
||||||
|
action: null
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Action(InitializeStore)
|
@Action(InitializeStore)
|
||||||
initializeStore(ctx: StateContext<ClassificationStateModel>): Observable<any> {
|
initializeStore(ctx: StateContext<ClassificationStateModel>): Observable<any> {
|
||||||
return this.categoryService.getClassificationCategoriesByType().pipe(
|
return this.categoryService.getClassificationCategoriesByType().pipe(
|
||||||
tap(classificationTypes => {
|
take(1), tap(classificationTypes => {
|
||||||
ctx.setState({
|
ctx.setState({
|
||||||
...ctx.getState(),
|
...ctx.getState(),
|
||||||
classificationTypes,
|
classificationTypes,
|
||||||
|
classifications: undefined,
|
||||||
selectedClassificationType: Object.keys(classificationTypes)[0],
|
selectedClassificationType: Object.keys(classificationTypes)[0],
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Action(GetClassifications)
|
||||||
|
getClassifications(ctx: StateContext<ClassificationStateModel>): Observable<any> {
|
||||||
|
const { selectedClassificationType } = ctx.getState();
|
||||||
|
return this.classificationsService.getClassifications(selectedClassificationType).pipe(
|
||||||
|
take(1), tap(classifications => {
|
||||||
|
classifications.forEach(classification => {
|
||||||
|
classification.children = !classification.children ? [] : classification.children;
|
||||||
|
});
|
||||||
|
ctx.patchState({
|
||||||
|
classifications: [...classifications]
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(CreateClassification)
|
||||||
|
createClassification(ctx: StateContext<ClassificationStateModel>, action: CreateClassification): Observable<any> {
|
||||||
|
return this.classificationsService.postClassification(action.classification).pipe(
|
||||||
|
take(1), tap(classification => {
|
||||||
|
ctx.patchState(
|
||||||
|
{
|
||||||
|
classifications: [...ctx.getState().classifications, classification],
|
||||||
|
selectedClassification: classification,
|
||||||
|
action: null
|
||||||
|
}
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(SaveClassification)
|
||||||
|
saveClassification(ctx: StateContext<ClassificationStateModel>, action: SaveClassification): Observable<any> {
|
||||||
|
return this.classificationsService.putClassification(action.classification).pipe(
|
||||||
|
// TODO remove this call when backend is fixed modified dates are not same
|
||||||
|
take(1), tap(retClassification => this.classificationsService.getClassification(retClassification.classificationId).subscribe(
|
||||||
|
savedClassification => {
|
||||||
|
ctx.patchState({
|
||||||
|
classifications: ctx.getState().classifications.map(currentClassification => {
|
||||||
|
if (currentClassification.classificationId === savedClassification.classificationId) { // TODO there has to be a better way
|
||||||
|
return savedClassification;
|
||||||
|
}
|
||||||
|
return currentClassification;
|
||||||
|
}),
|
||||||
|
selectedClassification: savedClassification
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)), tap(() => this.classificationsService.getClassifications(
|
||||||
|
ctx.getState().selectedClassificationType
|
||||||
|
).subscribe( // TODO find a better way because 3 calls are way too much
|
||||||
|
classifications => {
|
||||||
|
ctx.patchState({
|
||||||
|
classifications
|
||||||
|
});
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(RestoreSelectedClassification)
|
||||||
|
restoreSelectedClassification(ctx: StateContext<ClassificationStateModel>, action: RestoreSelectedClassification): Observable<any> {
|
||||||
|
return this.classificationsService.getClassification(action.classificationId).pipe(
|
||||||
|
take(1), tap(selectedClassification => {
|
||||||
|
ctx.patchState({ selectedClassification });
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(SetActiveAction)
|
||||||
|
setActiveAction(ctx: StateContext<ClassificationStateModel>, action: SetActiveAction): void {
|
||||||
|
if (action.action === ACTION.CREATE) {
|
||||||
|
ctx.patchState({ selectedClassification: new ClassificationDefinition(), action: action.action });
|
||||||
|
} else {
|
||||||
|
ctx.patchState({ action: action.action });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(RemoveSelectedClassification)
|
||||||
|
removeSelectedClassification(ctx: StateContext<ClassificationStateModel>): Observable<any> {
|
||||||
|
const sel = ctx.getState().selectedClassification;
|
||||||
|
return this.classificationsService.deleteClassification(sel.classificationId).pipe(take(1), tap(() => {
|
||||||
|
const classifications = ctx.getState().classifications.filter(el => el.classificationId !== sel.classificationId);
|
||||||
|
ctx.patchState({ selectedClassification: undefined, classifications });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Action(UpdateClassification)
|
||||||
|
updateClassification(ctx: StateContext<ClassificationStateModel>, action: SaveClassification): Observable<any> {
|
||||||
|
return this.classificationsService.putClassification(action.classification).pipe(
|
||||||
|
// TODO remove this call when backend is fixed modified dates are not same
|
||||||
|
take(1), tap(() => this.classificationsService.getClassifications(ctx.getState().selectedClassificationType).subscribe(
|
||||||
|
classifications => {
|
||||||
|
ctx.patchState({
|
||||||
|
classifications
|
||||||
|
});
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// initialize after Startup service has configured the taskanaRestUrl properly.
|
// initialize after Startup service has configured the taskanaRestUrl properly.
|
||||||
ngxsAfterBootstrap(ctx: StateContext<ClassificationStateModel>): void {
|
ngxsAfterBootstrap(ctx: StateContext<ClassificationStateModel>): void {
|
||||||
ctx.dispatch(new InitializeStore());
|
ctx.dispatch(new InitializeStore());
|
||||||
|
|
@ -41,6 +179,9 @@ export class ClassificationState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClassificationStateModel {
|
export interface ClassificationStateModel {
|
||||||
|
classifications: ClassificationDefinition[],
|
||||||
|
selectedClassification: ClassificationDefinition,
|
||||||
selectedClassificationType: string;
|
selectedClassificationType: string;
|
||||||
classificationTypes: CategoriesResponse,
|
classificationTypes: CategoriesResponse,
|
||||||
|
action: ACTION,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,9 @@ export class TaskComponent implements OnInit, OnDestroy {
|
||||||
async getTask(id: string) {
|
async getTask(id: string) {
|
||||||
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(this.task.classificationSummaryResource.classificationId);
|
const classification = await this.classificationService.getClassification(
|
||||||
|
this.task.classificationSummaryResource.classificationId
|
||||||
|
).toPromise();
|
||||||
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();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
"node_modules/@types"
|
"node_modules/@types"
|
||||||
],
|
],
|
||||||
"lib": [
|
"lib": [
|
||||||
"es2016",
|
"es2018",
|
||||||
"dom"
|
"dom"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue