TSK-1298: TSK-1292: fixed several bugs in taskana-admin > classifications

This commit is contained in:
Mustapha Zorgati 2020-06-30 07:26:52 +02:00
parent db9175a3f6
commit 135d78ff54
89 changed files with 1265 additions and 3468 deletions

View File

@ -5,12 +5,10 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/** Use this annotation to test with a spring context and a standardized configuration. */
@Target(ElementType.TYPE)
@ -20,7 +18,6 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
@Inherited
@ActiveProfiles({"test"})
@ExtendWith(SpringExtension.class)
@SpringBootTest(
classes = CommonTestConfiguration.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

View File

@ -5,12 +5,10 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import pro.taskana.TestConfiguration;
@ -22,7 +20,6 @@ import pro.taskana.TestConfiguration;
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
@Inherited
@ActiveProfiles({"test"})
@ExtendWith(SpringExtension.class)
@SpringBootTest(
classes = TestConfiguration.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

3700
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,8 @@
"build:prod-silent": "ng build --prod=true --no-progress",
"test": "ng test --karma-config karma.conf.js --watch=false --browsers Firefox",
"test:watch": "ng test --karma-config karma.conf.js --browsers Chrome",
"lint": "eslint --ext .ts src"
"lint": "eslint --ext .ts src",
"lint:fix": "eslint --ext .ts src --fix"
},
"private": true,
"dependencies": {

View File

@ -9,7 +9,6 @@ import { of } from 'rxjs';
import { NgxsModule } from '@ngxs/store';
import { AccessItemsManagementComponent } from './access-items-management.component';
describe('AccessItemsManagementComponent', () => {
let component: AccessItemsManagementComponent;
let fixture: ComponentFixture<AccessItemsManagementComponent>;
@ -23,7 +22,6 @@ describe('AccessItemsManagementComponent', () => {
});
};
beforeEach(done => {
configureTests(configure).then(testBed => {
fixture = testBed.createComponent(AccessItemsManagementComponent);

View File

@ -87,7 +87,7 @@
</button>
<ul class="dropdown-menu dropdown-menu" aria-labelledby="dropdownMenu">
<li>
<a *ngFor="let category of getAvailableCategories(classification.type)" (click)="selectCategory(category)">
<a *ngFor="let category of getAvailableCategories(classification.type) | async" (click)="selectCategory(category)">
<span class="text-top">
<svg-icon class="blue fa-fw" src="{{(getCategoryIcon(category) | async)?.name}}" data-toggle="tooltip"
[title]="(getCategoryIcon(category) | async)?.text"></svg-icon>

View File

@ -7,7 +7,6 @@ import { AngularSvgIconModule } from 'angular-svg-icon';
import { configureTests } from 'app/app.test.configuration';
import { NgxsModule, Store } from '@ngxs/store';
import { Location } from '@angular/common';
import { LinksClassification } from 'app/shared/models/links-classfication';
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';
@ -20,7 +19,6 @@ import { ClassificationDetailsComponent } from './classification-details.compone
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { ACTION } from '../../../shared/models/action';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
@ -31,9 +29,10 @@ class DummyDetailComponent {
describe('ClassificationDetailsComponent', () => {
let component: ClassificationDetailsComponent;
let fixture: ComponentFixture<ClassificationDetailsComponent>;
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
const treeNodes: TreeNodeModel[] = [];
let classificationsService;
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go']);
const storeSpy: jasmine.SpyObj<Store> = jasmine.createSpyObj('Store', ['select', 'dispatch']);
const configure = (testBed: TestBed) => {
@ -61,7 +60,6 @@ describe('ClassificationDetailsComponent', () => {
}
});
fixture = testBed.createComponent(ClassificationDetailsComponent);
component = fixture.componentInstance;
@ -93,13 +91,11 @@ describe('ClassificationDetailsComponent', () => {
modified: '2020-06-22T12:51:31.164Z',
description: 'Beratungsprotokoll'
};
component.classification._links = new LinksClassification({ self: '' });
fixture.detectChanges();
done();
});
});
it('should create', () => {
expect(component).toBeTruthy();
});

View File

@ -1,8 +1,7 @@
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable, Subject, zip, combineLatest } from 'rxjs';
import { combineLatest, Observable, Subject } from 'rxjs';
import { ClassificationDefinition, customFieldCount } from 'app/shared/models/classification-definition';
import { ACTION } from 'app/shared/models/action';
import { highlight } from 'theme/animations/validation.animation';
@ -24,11 +23,15 @@ import { NotificationService } from '../../../shared/services/notifications/noti
import { ClassificationCategoryImages,
CustomField,
getCustomFields } from '../../../shared/models/customisation';
import { Classification } from '../../../shared/models/classification';
import { customFieldCount } from '../../../shared/models/classification-summary';
import { CategoriesResponse } from '../../../shared/services/classification-categories/classification-categories.service';
import { CreateClassification,
RemoveSelectedClassification,
RestoreSelectedClassification,
SaveClassification, SelectClassification,
SaveClassification,
SelectClassification,
SetActiveAction } from '../../../shared/store/classification-store/classification.actions';
@Component({
@ -38,14 +41,14 @@ import { CreateClassification,
styleUrls: ['./classification-details.component.scss']
})
export class ClassificationDetailsComponent implements OnInit, OnDestroy {
classification: ClassificationDefinition;
classification: Classification;
badgeMessage = '';
requestInProgress = false;
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
@Select(ClassificationSelectors.selectedClassificationType) selectedClassificationType$: Observable<string>;
@Select(ClassificationSelectors.selectClassificationTypesObject) classificationTypes$: Observable<Object>;
@Select(ClassificationSelectors.selectedClassification) selectedClassification$: Observable<ClassificationDefinition>;
@Select(ClassificationSelectors.selectClassificationTypesObject) classificationTypes$: Observable<CategoriesResponse>;
@Select(ClassificationSelectors.selectedClassification) selectedClassification$: Observable<Classification>;
@Select(ClassificationSelectors.activeAction) action$: Observable<ACTION>;
spinnerIsRunning = false;
@ -74,7 +77,7 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
getCustomFields(customFieldCount)
);
combineLatest(this.selectedClassification$, this.action$).pipe(takeUntil(this.destroy$))
combineLatest([this.selectedClassification$, this.action$]).pipe(takeUntil(this.destroy$))
.subscribe(([classification, action]) => {
this.action = action;
if (this.action === ACTION.CREATE) {
@ -92,30 +95,11 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
}
});
this.action$.pipe(takeUntil(this.destroy$)).subscribe(data => {
this.action = data;
if (this.action === ACTION.CREATE) {
this.selectedClassification$.pipe(take(1)).subscribe(initialClassification => {
this.classification = { ...initialClassification };
});
this.badgeMessage = 'Creating new classification';
} else if (this.action === ACTION.COPY) {
this.badgeMessage = `Copying Classification: ${this.classification.name}`;
} else {
this.badgeMessage = '';
}
});
this.importExportService.getImportingFinished().pipe(takeUntil(this.destroy$)).subscribe(() => {
this.store.dispatch(new SelectClassification(this.classification.classificationId));
});
}
backClicked(): void {
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
}
removeClassification() {
this.notificationsService.showDialog(`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`,
this.removeClassificationConfirmation.bind(this));
@ -137,8 +121,12 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
onRestore() {
this.formsValidatorService.formSubmitAttempt = false;
if (this.action === ACTION.CREATE) {
this.classification = new ClassificationDefinition();
this.categories$.pipe(take(1)).subscribe(categories => {
const category = categories[0];
const { type, created, modified, domain, parentId, parentKey } = this.classification;
this.classification = { type, created, category, modified, domain, parentId, parentKey };
this.notificationsService.showToast(NOTIFICATION_TYPES.INFO_ALERT);
});
} else {
this.store.dispatch(
new RestoreSelectedClassification(this.classification.classificationId)
@ -149,8 +137,12 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
}
onCopy() {
if (this.action !== ACTION.CREATE) {
this.store.dispatch(new SetActiveAction(ACTION.COPY));
this.classification.key = null;
} else {
this.notificationsService.showToast(NOTIFICATION_TYPES.WARNING_CANT_COPY);
}
}
selectCategory(category: string) {
@ -177,6 +169,19 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
return this.domainService.getSelectedDomainValue() === '';
}
getClassificationCustom(customNumber: number): string {
return `custom${customNumber}`;
}
getAvailableCategories(type: string): Observable<string[]> {
return this.classificationTypes$.pipe(take(1), map(classTypes => classTypes[type]));
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
private async onSave() {
this.requestInProgressService.setRequestInProgress(true);
if (this.action) {
@ -188,6 +193,10 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
NOTIFICATION_TYPES.SUCCESS_ALERT_2,
new Map<string, string>([['classificationKey', store.classification.selectedClassification.key]])
);
this.location.go(this.location.path().replace(
/(classifications).*/g,
`classifications/(detail:${store.classification.selectedClassification.classificationId})`
));
this.afterRequest();
}, error => {
this.notificationsService.triggerError(NOTIFICATION_TYPES.CREATE_ERR, error);
@ -195,12 +204,14 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
});
} else {
try {
this.store.dispatch(new SaveClassification(this.classification));
this.store.dispatch(new SaveClassification(this.classification))
.pipe(take(1)).subscribe(() => {
this.afterRequest();
this.notificationsService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_3,
new Map<string, string>([['classificationKey', this.classification.key]])
);
});
} catch (error) {
this.notificationsService.triggerError(NOTIFICATION_TYPES.SAVE_ERR, error);
this.afterRequest();
@ -210,7 +221,6 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private afterRequest() {
this.requestInProgressService.setRequestInProgress(false);
this.classificationsService.triggerClassificationSaved();
}
private removeClassificationConfirmation() {
@ -227,22 +237,4 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
});
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
}
getClassificationCustom(customNumber: number): string {
return `custom${customNumber}`;
}
getAvailableCategories(type: string) {
let returnCategories: string[] = [];
this.classificationTypes$.pipe(take(1)).subscribe(classTypes => {
returnCategories = classTypes[type];
});
return returnCategories;
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -53,7 +53,7 @@
<!-- CLASSIFICATION TREE -->
<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"
[filterText]="inputValue" [filterIcon]="selectedCategory"></taskana-tree>
[filterText]="inputValue" [filterIcon]="selectedCategory" (switchTaskanaSpinnerEmit)="requestInProgress=$event"></taskana-tree>
<ng-template #empty_classifications>
<div *ngIf="!requestInProgress" class="col-xs-12 container-no-items center-block">
<h3 class="grey">There are no classifications</h3>

View File

@ -24,7 +24,6 @@ import { MatRadioModule } from '@angular/material/radio';
import { ClassificationListComponent } from './classification-list.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'
@ -39,7 +38,7 @@ const routes: Routes = [
describe('ClassificationListComponent', () => {
let component: ClassificationListComponent;
let fixture: ComponentFixture<ClassificationListComponent>;
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
const treeNodes: TreeNodeModel[] = [{ children: [] }];
let classificationsService;
const configure = (testBed: TestBed) => {

View File

@ -13,10 +13,10 @@ import { ClassificationSelectors } from 'app/shared/store/classification-store/c
import { Location } from '@angular/common';
import { ClassificationCategoryImages } from '../../../shared/models/customisation';
import { GetClassifications, SetActiveAction } from '../../../shared/store/classification-store/classification.actions';
import { GetClassifications,
SetActiveAction } from '../../../shared/store/classification-store/classification.actions';
import { ACTION } from '../../../shared/models/action';
import { TreeNodeModel } from '../../../shared/models/tree-node';
import { ClassificationSummary } from '../../../shared/models/classification-summary';
@Component({
selector: 'taskana-classification-list',
@ -32,13 +32,13 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
@Select(ClassificationSelectors.classificationTypes) classificationTypes$: Observable<string[]>;
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
@Select(ClassificationSelectors.selectCategories) categories$: Observable<string[]>;
@Select(ClassificationSelectors.classifications) classifications$: Observable<TreeNodeModel[]>;
@Select(ClassificationSelectors.classifications) classifications$: Observable<ClassificationSummary[]>;
@Select(ClassificationSelectors.activeAction) activeAction$: Observable<ACTION>;
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
action: ACTION;
destroy$ = new Subject<void>();
classifications: TreeNodeModel[];
classifications: ClassificationSummary[];
constructor(
private classificationService: ClassificationsService,
@ -61,16 +61,7 @@ export class ClassificationListComponent implements OnInit, OnDestroy {
ngOnInit() {
this.classifications$.pipe(takeUntil(this.destroy$)).subscribe(classifications => {
if (classifications) {
this.classifications = [...classifications];
}
});
this.classificationService
.classificationSavedTriggered()
.pipe(takeUntil(this.destroy$))
.subscribe(() => {
this.store.dispatch(new GetClassifications());
this.classifications = classifications;
});
this.classificationTypeSelected$

View File

@ -4,11 +4,11 @@ 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 { GetClassifications,
SelectClassification,
SetActiveAction } from '../../../shared/store/classification-store/classification.actions';
import { Classification } from '../../../shared/models/classification';
@Component({
selector: 'app-classification-overview',
@ -17,7 +17,7 @@ import { GetClassifications,
})
export class ClassificationOverviewComponent implements OnInit, OnDestroy {
showDetail = false;
@Select(ClassificationSelectors.selectedClassification) selectedClassification$: Observable<ClassificationDefinition>;
@Select(ClassificationSelectors.selectedClassification) selectedClassification$: Observable<Classification>;
private destroy$ = new Subject<void>();
routerParams: any;

View File

@ -2,17 +2,21 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NgxsModule } from '@ngxs/store';
import { MatRadioModule } from '@angular/material/radio';
import { Location } from '@angular/common';
import { ClassificationTypesSelectorComponent } from './classification-types-selector.component';
describe('ClassificationTypesSelectorComponent', () => {
let component: ClassificationTypesSelectorComponent;
let fixture: ComponentFixture<ClassificationTypesSelectorComponent>;
const locationSpy: jasmine.SpyObj<Location> = jasmine.createSpyObj('Location', ['go']);
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [NgxsModule.forRoot(), MatRadioModule],
declarations: [ClassificationTypesSelectorComponent],
providers: []
providers: [
{ provide: Location, useValue: locationSpy },
]
}).compileComponents();
}));

View File

@ -3,7 +3,7 @@ import { Observable } from 'rxjs';
import { Store, Select } from '@ngxs/store';
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
import { SetSelectedClassificationType } from 'app/shared/store/classification-store/classification.actions';
import { Location } from '@angular/common';
@Component({
selector: 'taskana-classification-types-selector',
@ -14,9 +14,10 @@ export class ClassificationTypesSelectorComponent {
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
@Select(ClassificationSelectors.classificationTypes) classificationTypes$: Observable<string[]>;
constructor(private store: Store) {}
constructor(private store: Store, private location: Location) {}
select(value: string): void {
this.store.dispatch(new SetSelectedClassificationType(value));
this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications'));
}
}

View File

@ -41,7 +41,7 @@
[ngClass]="{
'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId }">
<taskana-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required" [validationValue]="toogleValidationAccessIdMap.get(index)"
<taskana-type-ahead formControlName="accessId" placeHolderMessage="* Access id is required" [validationValue]="toggleValidationAccessIdMap.get(index)"
[displayError]="!isFieldValid('accessItem.value.accessId', index)" (selectedItem)="accessItemSelected($event, index)" (inputField)="focusNewInput($event)"></taskana-type-ahead>
</td>
<ng-template #accessIdInput>
@ -50,7 +50,7 @@
!accessItem.value.accessId && formSubmitAttempt}">
<input type="text" class="form-control" formControlName="accessId" placeholder="{{accessItem.invalid?
'* Access id is required': ''}}"
[@validation]="toogleValidationAccessIdMap.get(index)" #htmlInputElement>
[@validation]="toggleValidationAccessIdMap.get(index)" #htmlInputElement>
</div>
</td>
</ng-template>

View File

@ -1,5 +1,5 @@
import { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AngularSvgIconModule } from 'angular-svg-icon';
@ -7,7 +7,6 @@ import { of } from 'rxjs';
import { configureTests } from 'app/app.test.configuration';
import { Workbasket } from 'app/shared/models/workbasket';
import { Links } from 'app/shared/models/links';
import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
import { WorkbasketAccessItemsResource } from 'app/shared/models/workbasket-access-items-resource';
import { ICONTYPES } from 'app/shared/models/icon-types';
@ -64,8 +63,7 @@ describe('WorkbasketAccessItemsComponent', () => {
component = fixture.componentInstance;
component.workbasket = new Workbasket('1');
component.workbasket.type = ICONTYPES.TOPIC;
component.workbasket._links = new Links();
component.workbasket._links.accessItems = { href: 'someurl' };
component.workbasket._links = { accessItems: { href: 'someurl' } };
workbasketService = testBed.get(WorkbasketService);
notificationsService = testBed.get(NotificationService);
@ -74,8 +72,7 @@ describe('WorkbasketAccessItemsComponent', () => {
new WorkbasketAccessItems('id1', '1', 'accessID1', '', false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, false),
new WorkbasketAccessItems('id2', '1', 'accessID2')
),
new Links({ href: 'someurl' })
), { self: { href: 'someurl' } }
)));
spyOn(workbasketService, 'updateWorkBasketAccessItem').and.returnValue(of(true));
spyOn(notificationsService, 'showToast').and.returnValue(of(true));
@ -97,7 +94,6 @@ describe('WorkbasketAccessItemsComponent', () => {
document.body.removeChild(debugElement);
});
it('should create', () => {
expect(component).toBeTruthy();
});

View File

@ -1,4 +1,4 @@
import { Component,
import { AfterViewInit, Component,
ElementRef,
Input,
OnChanges,
@ -7,7 +7,7 @@ import { Component,
ViewChildren } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Select } from '@ngxs/store';
import { FormArray, FormBuilder, FormControlDirective, Validators } from '@angular/forms';
import { FormArray, FormBuilder, Validators } from '@angular/forms';
import { Workbasket } from 'app/shared/models/workbasket';
import { customFieldCount, WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
@ -34,7 +34,7 @@ import { AccessItemsCustomisation,
animations: [highlight],
styleUrls: ['./workbasket-access-items.component.scss']
})
export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy, AfterViewInit {
@Input()
workbasket: Workbasket;
@ -46,7 +46,6 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
@ViewChildren('htmlInputElement') inputs: QueryList<ElementRef>;
badgeMessage = '';
@Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable<AccessItemsCustomisation>;
@ -56,13 +55,13 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
accessItemsClone: Array<WorkbasketAccessItems>;
accessItemsResetClone: Array<WorkbasketAccessItems>;
requestInProgress = false;
accessItemsubscription: Subscription;
accessItemSubscription: Subscription;
savingAccessItemsSubscription: Subscription;
AccessItemsForm = this.formBuilder.group({
accessItemsGroups: this.formBuilder.array([])
});
toogleValidationAccessIdMap = new Map<number, boolean>();
toggleValidationAccessIdMap = new Map<number, boolean>();
private initialized = false;
private added = false;
@ -104,7 +103,7 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
return;
}
this.requestInProgress = true;
this.accessItemsubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href)
this.accessItemSubscription = this.workbasketService.getWorkBasketAccessItems(this.workbasket._links.accessItems.href)
.subscribe((accessItemsResource: WorkbasketAccessItemsResource) => {
this.accessItemsResource = accessItemsResource;
this.setAccessItemsGroups(accessItemsResource.accessItems);
@ -162,7 +161,7 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
onSubmit() {
this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService.validateFormAccess(this.accessItemsGroups, this.toogleValidationAccessIdMap).then(value => {
this.formsValidatorService.validateFormAccess(this.accessItemsGroups, this.toggleValidationAccessIdMap).then(value => {
if (value) {
this.onSave();
}
@ -230,10 +229,9 @@ export class WorkbasketAccessItemsComponent implements OnChanges, OnDestroy {
}
}
ngOnDestroy(): void {
if (this.accessItemsubscription) {
this.accessItemsubscription.unsubscribe();
if (this.accessItemSubscription) {
this.accessItemSubscription.unsubscribe();
}
if (this.savingAccessItemsSubscription) {
this.savingAccessItemsSubscription.unsubscribe();

View File

@ -14,7 +14,6 @@ import { WorkbasketAccessItemsResource } from 'app/shared/models/workbasket-acce
import { ICONTYPES } from 'app/shared/models/icon-types';
import { Links } from 'app/shared/models/links';
import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items';
import { LinksWorkbasketSummary } from 'app/shared/models/links-workbasket-summary';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { MasterAndDetailService } from 'app/shared/services/master-and-detail/master-and-detail.service';
@ -46,7 +45,7 @@ describe('WorkbasketDetailsComponent', () => {
let workbasketService;
let router;
const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
new Links({ href: 'someurl' }, { href: 'someurl' }, { href: 'someurl' }));
{});
const routes: Routes = [
{ path: '*', component: DummyDetailComponent }
@ -77,17 +76,17 @@ describe('WorkbasketDetailsComponent', () => {
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => of(new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '',
false, new Links({ href: 'someurl' }))
false, {})
),
new LinksWorkbasketSummary({ href: 'someurl' })
{}
)));
spyOn(workbasketService, 'getWorkBasket').and.callFake(() => of(workbasket));
spyOn(workbasketService, 'getWorkBasketAccessItems').and.callFake(() => of(new WorkbasketAccessItemsResource(
new Array<WorkbasketAccessItems>(), new Links({ href: 'url' })
new Array<WorkbasketAccessItems>(), {}
)));
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => of(new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(), new LinksWorkbasketSummary({ href: 'url' })
new Array<WorkbasketSummary>(), {}
)));
done();
});

View File

@ -41,7 +41,6 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy {
private errorsService: NotificationService,
private importExportService: ImportExportService) { }
ngOnInit() {
this.workbasketSelectedSubscription = this.service.getSelectedWorkBasket().subscribe(workbasketIdSelected => {
delete this.workbasket;

View File

@ -16,10 +16,10 @@ import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.ser
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { LinksWorkbasketSummary } from 'app/shared/models/links-workbasket-summary';
import { configureTests } from 'app/app.test.configuration';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { WorkbasketDistributionTargetsComponent, Side } from './workbasket-distribution-targets.component';
import { Side,
WorkbasketDistributionTargetsComponent } from './workbasket-distribution-targets.component';
import { WorkbasketDualListComponent } from '../workbasket-dual-list/workbasket-dual-list.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
@ -28,10 +28,10 @@ describe('WorkbasketDistributionTargetsComponent', () => {
let fixture: ComponentFixture<WorkbasketDistributionTargetsComponent>;
let workbasketService;
const workbasket = new Workbasket('1', '', '', '', ICONTYPES.TOPIC, '', '', '', '', '', '', '', '', '', '', '', '',
new Links({ href: 'someurl' }, { href: 'someurl' }, { href: 'someurl' }));
{ distributionTargets: { href: 'someurl' } });
beforeEach(done => {
const configure = (testBed: TestBed) => {
const configure = testBed => {
testBed.configureTestingModule({
imports: [AngularSvgIconModule, HttpClientModule, InfiniteScrollModule],
declarations: [WorkbasketDistributionTargetsComponent, WorkbasketDualListComponent],
@ -40,23 +40,23 @@ describe('WorkbasketDistributionTargetsComponent', () => {
});
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(WorkbasketDistributionTargetsComponent);
fixture = testBed.createComponent(WorkbasketDistributionTargetsComponent);
component = fixture.componentInstance;
component.workbasket = workbasket;
workbasketService = TestBed.get(WorkbasketService);
workbasketService = testBed.get(WorkbasketService);
spyOn(workbasketService, 'getWorkBasketsSummary').and.callFake(() => of(new WorkbasketSummaryResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' })),
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' })),
new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' }))
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, {}),
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, {}),
new WorkbasketSummary('id3', '', '', '', '', '', '', '', '', '', '', '', false, {})
),
new LinksWorkbasketSummary({ href: 'someurl' })
{}
)));
spyOn(workbasketService, 'getWorkBasketsDistributionTargets').and.callFake(() => of(new WorkbasketDistributionTargetsResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' }))
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, {})
),
new LinksWorkbasketSummary({ href: 'someurl' })
{ self: { href: 'someurl' } }
)));
component.ngOnChanges({
active: new SimpleChange(undefined, 'distributionTargets', true)
@ -85,7 +85,9 @@ describe('WorkbasketDistributionTargetsComponent', () => {
expect(component.distributionTargetsRight.length).toBe(1);
component.distributionTargetsLeft.forEach(leftElement => {
component.distributionTargetsRight.forEach(rightElement => {
if (leftElement.workbasketId === rightElement.workbasketId) { repeteadElemens = true; }
if (leftElement.workbasketId === rightElement.workbasketId) {
repeteadElemens = true;
}
});
});
expect(repeteadElemens).toBeFalsy();
@ -99,7 +101,7 @@ describe('WorkbasketDistributionTargetsComponent', () => {
side: Side.LEFT
});
component.distributionTargetsLeft = new Array<WorkbasketSummary>(
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' }))
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, {})
);
expect(component.distributionTargetsLeft.length).toBe(1);
expect(component.distributionTargetsLeft[0].workbasketId).toBe('id1');
@ -109,10 +111,10 @@ describe('WorkbasketDistributionTargetsComponent', () => {
it('should reset distribution target and distribution target selected on reset', () => {
component.distributionTargetsLeft.push(
new WorkbasketSummary('id4', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' }))
new WorkbasketSummary('id4', '', '', '', '', '', '', '', '', '', '', '', false, {})
);
component.distributionTargetsRight.push(
new WorkbasketSummary('id5', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' }))
new WorkbasketSummary('id5', '', '', '', '', '', '', '', '', '', '', '', false, {})
);
expect(component.distributionTargetsLeft.length).toBe(3);
@ -129,10 +131,10 @@ describe('WorkbasketDistributionTargetsComponent', () => {
expect(component.distributionTargetsSelectedClone.length).toBe(1);
spyOn(workbasketService, 'updateWorkBasketsDistributionTargets').and.callFake(() => of(new WorkbasketDistributionTargetsResource(
new Array<WorkbasketSummary>(
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' })),
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, new Links({ href: 'someurl' }))
new WorkbasketSummary('id2', '', '', '', '', '', '', '', '', '', '', '', false, {}),
new WorkbasketSummary('id1', '', '', '', '', '', '', '', '', '', '', '', false, {})
),
new LinksWorkbasketSummary({ href: 'someurl' })
{}
)));
component.onSave();
fixture.detectChanges();

View File

@ -11,7 +11,6 @@ import { Routes } from '@angular/router';
import { Workbasket } from 'app/shared/models/workbasket';
import { ICONTYPES } from 'app/shared/models/icon-types';
import { ACTION } from 'app/shared/models/action';
import { Links } from 'app/shared/models/links';
import { SavingWorkbasketService } from 'app/administration/services/saving-workbaskets.service';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
@ -125,7 +124,7 @@ describe('WorkbasketInformationComponent', () => {
it('should reset requestInProgress after saving request is done', async(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' }));
'orgLevel3', 'orgLevel4', { self: { href: 'someUrl' } });
fixture.detectChanges();
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(of(component.workbasket));
@ -136,7 +135,7 @@ describe('WorkbasketInformationComponent', () => {
it('should trigger triggerWorkBasketSaved method after saving request is done', async(() => {
component.workbasket = new Workbasket('id', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' }));
'orgLevel3', 'orgLevel4', { self: { href: 'someurl' } });
spyOn(workbasketService, 'updateWorkbasket').and.returnValue(of(component.workbasket));
spyOn(workbasketService, 'triggerWorkBasketSaved').and.returnValue(of(component.workbasket));
fixture.detectChanges();
@ -150,14 +149,13 @@ describe('WorkbasketInformationComponent', () => {
}));
it('should post a new workbasket when no workbasketId is defined and update workbasket', async(() => {
const workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
component.workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' }));
component.workbasket = workbasket;
'orgLevel3', 'orgLevel4', {});
spyOn(workbasketService, 'createWorkbasket').and.returnValue(of(
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' }))
'orgLevel3', 'orgLevel4', {})
));
fixture.detectChanges();
spyOn(formsValidatorService, 'validateFormAccess').and.returnValue(Promise.resolve(true));
@ -171,17 +169,16 @@ describe('WorkbasketInformationComponent', () => {
it('should post a new workbasket, new distribution targets and new access '
+ 'items when no workbasketId is defined and action is copy', async(() => {
const workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
component.workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2',
'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' }));
component.workbasket = workbasket;
'orgLevel3', 'orgLevel4', {});
component.action = ACTION.COPY;
spyOn(workbasketService, 'createWorkbasket').and.returnValue(of(
new Workbasket('someNewId', 'created', 'keyModified', 'domain', ICONTYPES.TOPIC, 'modified', 'name', 'description',
'owner', 'custom1', 'custom2', 'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', new Links({ href: 'someUrl' }, { href: 'someUrl' }, { href: 'someUrl' }))
'orgLevel3', 'orgLevel4', { distributionTargets: { href: 'someurl' }, accessItems: { href: 'someurl' } })
));
spyOn(savingWorkbasketService, 'triggerDistributionTargetSaving');
@ -199,13 +196,10 @@ describe('WorkbasketInformationComponent', () => {
}));
it('should trigger requestInProgress service true before and requestInProgress false after remove a workbasket', () => {
const links = new Links({ href: 'someUrl' });
links.removeDistributionTargets = { href: 'someUrl' };
const workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
component.workbasket = new Workbasket(undefined, 'created', 'keyModified', 'domain', ICONTYPES.TOPIC,
'modified', 'name', 'description', 'owner', 'custom1', 'custom2',
'custom3', 'custom4', 'orgLevel1', 'orgLevel2',
'orgLevel3', 'orgLevel4', links);
component.workbasket = workbasket;
'orgLevel3', 'orgLevel4', { removeDistributionTargets: { href: 'someurl' } });
spyOn(workbasketService, 'removeDistributionTarget').and.returnValue(of(undefined));
const requestInProgressServiceSpy = spyOn(requestInProgressService, 'setRequestInProgress');
@ -213,7 +207,7 @@ describe('WorkbasketInformationComponent', () => {
expect(requestInProgressServiceSpy).toHaveBeenCalledWith(true);
workbasketService.removeDistributionTarget().subscribe(() => {
}, error => { }, complete => {
}, () => { }, () => {
expect(requestInProgressServiceSpy).toHaveBeenCalledWith(false);
});
});

View File

@ -58,19 +58,18 @@ describe('WorkbasketListToolbarComponent', () => {
});
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(WorkbasketListToolbarComponent);
workbasketService = TestBed.get(WorkbasketService);
router = TestBed.get(Router);
fixture = testBed.createComponent(WorkbasketListToolbarComponent);
workbasketService = testBed.get(WorkbasketService);
router = testBed.get(Router);
spyOn(workbasketService, 'markWorkbasketForDeletion').and.returnValue(of(''));
spyOn(workbasketService, 'triggerWorkBasketSaved');
debugElement = fixture.debugElement.nativeElement;
component = fixture.componentInstance;
component.workbaskets = new Array<WorkbasketSummary>(
component.workbaskets = [
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1')
);
];
component.workbaskets[0].markedForDeletion = false;
component.workbaskets[0]._links = new Links({ href: 'selfLink' });
fixture.detectChanges();
done();
@ -91,7 +90,6 @@ describe('WorkbasketListToolbarComponent', () => {
expect(spy.calls.first().args[0][0].outlets.detail[0]).toBe('new-workbasket');
});
it('should emit performSorting when sorting is triggered', () => {
let sort: Sorting;
const compareSort = new Sorting();

View File

@ -10,7 +10,6 @@ import { RouterTestingModule } from '@angular/router/testing';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { WorkbasketSummaryResource } from 'app/shared/models/workbasket-summary-resource';
import { Filter } from 'app/shared/models/filter';
import { LinksWorkbasketSummary } from 'app/shared/models/links-workbasket-summary';
import { ImportExportComponent } from 'app/administration/components/import-export/import-export.component';
@ -50,10 +49,9 @@ const workbasketSummaryResource: WorkbasketSummaryResource = new WorkbasketSumma
new WorkbasketSummary('1', 'key1', 'NAME1', 'description 1', 'owner 1', '', '', 'PERSONAL', '', '', '', ''),
new WorkbasketSummary('2', 'key2', 'NAME2', 'description 2', 'owner 2', '', '', 'GROUP', '', '', '', '')
),
new LinksWorkbasketSummary({ href: 'url' })
{}
);
describe('WorkbasketListComponent', () => {
let component: WorkbasketListComponent;
let fixture: ComponentFixture<WorkbasketListComponent>;
@ -66,7 +64,6 @@ describe('WorkbasketListComponent', () => {
{ path: 'workbaskets', component: DummyDetailComponent }
];
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({

View File

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'app/../environments/environment';
import { ClassificationDefinition } from 'app/shared/models/classification-definition';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import { BlobGenerator } from 'app/shared/util/blob-generator';
import { Classification } from '../../shared/models/classification';
@Injectable()
export class ClassificationDefinitionService {
@ -13,7 +13,7 @@ export class ClassificationDefinitionService {
// GET
async exportClassifications(domain: string) {
const domainRequest = (domain ? '' : `?domain=${domain}`);
const classificationDefinitions = await this.httpClient.get<ClassificationDefinition[]>(this.url + domainRequest).toPromise();
const classificationDefinitions = await this.httpClient.get<Classification[]>(this.url + domainRequest).toPromise();
BlobGenerator.saveFile(classificationDefinitions, `Classifications_${TaskanaDate.getDate()}.json`);
}
}

View File

@ -7,7 +7,6 @@ export class SavingInformation {
}
}
@Injectable()
export class SavingWorkbasketService {
public distributionTargetsSavingInformation = new Subject<SavingInformation>();

View File

@ -43,7 +43,6 @@ describe('AppComponent', () => {
expect(app).toBeTruthy();
}));
it('should render title in a <a> tag', (() => {
fixture.detectChanges();
expect(debugElement.querySelector('ul p a').textContent).toContain('Taskana administration');

View File

@ -77,7 +77,6 @@ export function startupServiceFactory(startupService: StartupService): () => Pro
return (): Promise<any> => startupService.load();
}
@NgModule({
declarations: DECLARATIONS,
imports: MODULES,

View File

@ -1,4 +1,3 @@
import { getTestBed, TestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';

View File

@ -41,7 +41,6 @@ export class TaskQueryComponent implements OnInit {
this.initTaskQueryForm();
}
getHeaderFieldDescription(property: string): string {
switch (property) {
case 'parentBusinessProcessId':

View File

@ -13,7 +13,6 @@ import { RequestInProgressService } from '../../../shared/services/request-in-pr
export class ClassificationReportComponent implements OnInit {
reportData: ReportData;
lineChartLabels: Array<any>;
lineChartLegend = true;
lineChartType = 'line';

View File

@ -1,6 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
@Component({
selector: 'taskana-monitor',
templateUrl: './monitor.component.html',

View File

@ -17,7 +17,6 @@ export class WorkbasketReportDueDateComponent implements OnInit {
reportData: ReportData;
lineChartLabels: Array<any>;
lineChartLegend = true;
lineChartType = 'line';
@ -34,7 +33,6 @@ export class WorkbasketReportDueDateComponent implements OnInit {
) {
}
async ngOnInit() {
this.requestInProgressService.setRequestInProgress(true);
this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByDueDate().toPromise();

View File

@ -17,7 +17,6 @@ export class WorkbasketReportPlannedDateComponent implements OnInit {
reportData: ReportData;
lineChartLabels: Array<any>;
lineChartLegend = true;
lineChartType = 'line';

View File

@ -23,7 +23,6 @@ import { WorkbasketReportPlannedDateComponent } from './components/workbasket-re
import { WorkbasketReportDueDateComponent } from './components/workbasket-report-due-date/workbasket-report-due-date.component';
import { WorkbasketReportQuerySwitcherComponent } from './components/workbasket-report-query-switcher/workbasket-report-query-switcher.component';
const MODULES = [
CommonModule,
MonitorRoutingModule,

View File

@ -13,7 +13,6 @@ describe('FilterComponent', () => {
let fixture: ComponentFixture<FilterComponent>;
let debugElement: any;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({

View File

@ -8,7 +8,6 @@ import { MasterAndDetailService } from '../../services/master-and-detail/master-
import { MasterAndDetailComponent } from './master-and-detail.component';
@Component({
selector: 'taskana-dummy-master',
template: 'dummymaster'

View File

@ -86,7 +86,6 @@ export class NavBarComponent implements OnInit, OnDestroy {
this.showNavbar = !this.showNavbar;
}
logout() {
this.taskanaEngineService.logout().subscribe(() => {
});

View File

@ -11,7 +11,6 @@ describe('NoAccessComponent', () => {
let fixture: ComponentFixture<NoAccessComponent>;
let debugElement;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({

View File

@ -12,7 +12,6 @@ describe('PaginationComponent', () => {
let fixture: ComponentFixture<PaginationComponent>;
let debugElement;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({

View File

@ -1,6 +1,5 @@
import { Component, ElementRef, Inject, OnInit, ViewChild } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { HttpErrorResponse } from '@angular/common/http';
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { notifications } from '../../models/notifications';
@Component({
@ -33,7 +32,8 @@ export class DialogPopUpComponent implements OnInit {
initError() {
this.title = notifications.get(this.data.key).name || '';
this.message = notifications.get(this.data.key).text || this.data.passedError.error ? this.data.passedError.error.message : '';
this.message = notifications.get(this.data.key).text
|| (this.data && this.data.passedError && this.data.passedError.error) ? this.data.passedError.error.message : '';
if (this.data.additions) {
this.data.additions.forEach((value: string, replacementKey: string) => {
this.message = this.message.replace(`{${replacementKey}}`, value);

View File

@ -4,7 +4,6 @@ import { NotificationService } from '../../services/notifications/notification.s
declare let $: any;
@Component({
selector: 'taskana-spinner',
templateUrl: './spinner.component.html',

View File

@ -1,4 +1,4 @@
<tree-root #tree [nodes]="classifications" [options]="options" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"
<tree-root #tree [nodes]="treeNodes" [options]="options" (activate)="onActivate($event)" (deactivate)="onDeactivate($event)"
(moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)">
<ng-template #treeNodeTemplate let-node let-index="index">
<span class="text-top">

View File

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed, tick } from '@angular/core/testing';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { HttpClientModule } from '@angular/common/http';
@ -12,9 +12,8 @@ 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 { ClassificationDefinition } from '../../models/classification-definition';
import { LinksClassification } from '../../models/links-classfication';
import { ClassificationsService } from '../../services/classifications/classifications.service';
import { Classification } from '../../models/classification';
@Component({
selector: 'tree-root',
@ -56,11 +55,12 @@ describe('TaskanaTreeComponent', () => {
case ClassificationSelectors.activeAction:
return of(ACTION.CREATE);
case ClassificationSelectors.classifications:
return of([new TreeNodeModel('id4')]);
return of([{ classificationId: 'id4' }]);
default:
return of();
}
});
storeSpy.dispatch.and.callFake(() => of());
fixture = testBed.createComponent(TaskanaTreeComponent);
classificationsService = testBed.get(ClassificationsService);
@ -86,10 +86,22 @@ describe('TaskanaTreeComponent', () => {
});
it('should be change the classification parent (onMoveNode)', async () => {
const classification = new ClassificationDefinition('id4',
'key4', '', '', 'MANUAL', 'DOMAIN_A', 'TASK', true, '019-04-10T10:23:34.985Z', '2019-04-10T10:23:34.985Z',
'classification4', 'description', 1, 'level', '', '', '', '', '', '',
'', '', '', new LinksClassification({ href: '' }, '', '', { href: '' }, { href: '' }, { href: '' }));
const classification: Classification = {
classificationId: 'id4',
key: 'key4',
category: 'MANUAL',
domain: 'DOMAIN_A',
parentId: '',
parentKey: '',
type: 'TASK',
isValidInDomain: true,
created: '019-04-10T10:23:34.985Z',
modified: '2019-04-10T10:23:34.985Z',
name: 'classification4',
description: 'description',
priority: 1,
serviceLevel: 'level',
};
// using parameter 'any' since getClassification is a private method
spyOn<any>(component, 'getClassification').and.returnValue(classification);
@ -103,15 +115,25 @@ describe('TaskanaTreeComponent', () => {
expect(classification.parentId).toEqual('id3');
expect(classification.parentKey).toEqual('key3');
expect(storeSpy.dispatch).toHaveBeenCalledWith(new UpdateClassification(classification));
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(true);
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(false);
});
it('should be changed the parent classification to root node (onDrop)', async () => {
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',
'classification3', 'description', 1, 'level', '', '', '', '', '', '',
'', '', '', new LinksClassification({ href: '' }, '', '', { href: '' }, { href: '' }, { href: '' }));
const classification: Classification = {
classificationId: 'id3',
key: 'key3',
parentId: 'id1',
parentKey: 'key1',
category: 'MANUAL',
domain: 'DOMAIN_A',
type: 'TASK',
isValidInDomain: true,
created: '019-04-10T10:23:34.985Z',
modified: '2019-04-10T10:23:34.985Z',
name: 'classification3',
description: 'description',
priority: 1,
serviceLevel: 'level'
};
// using parameter 'any' since getClassification is a private method
spyOn<any>(component, 'getClassification').and.returnValue(classification);
@ -124,7 +146,5 @@ describe('TaskanaTreeComponent', () => {
expect(classification.parentId).toEqual('');
expect(classification.parentKey).toEqual('');
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(true);
expect(component.switchTaskanaSpinner).toHaveBeenCalledWith(false);
});
});

View File

@ -1,4 +1,10 @@
import { AfterViewChecked, Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy,
import { AfterViewChecked,
Component,
ElementRef,
EventEmitter,
HostListener,
Input,
OnDestroy,
OnInit,
Output,
ViewChild } from '@angular/core';
@ -6,8 +12,8 @@ import { TreeNodeModel } from 'app/shared/models/tree-node';
import { ITreeOptions, KEYS, TREE_ACTIONS, TreeComponent } from 'angular-tree-component';
import { Pair } from 'app/shared/models/pair';
import { Observable, Subject, combineLatest } from 'rxjs';
import { map, takeUntil, filter, tap } from 'rxjs/operators';
import { combineLatest, Observable, Subject } from 'rxjs';
import { filter, map, takeUntil } from 'rxjs/operators';
import { Select, Store } from '@ngxs/store';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
@ -15,7 +21,6 @@ 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 { ClassificationDefinition } from '../../models/classification-definition';
import { ClassificationsService } from '../../services/classifications/classifications.service';
import { ClassificationCategoryImages } from '../../models/customisation';
import { ClassificationSelectors } from '../../store/classification-store/classification.selectors';
@ -23,6 +28,7 @@ import { DeselectClassification,
SelectClassification,
UpdateClassification } from '../../store/classification-store/classification.actions';
import { ACTION } from '../../models/action';
import { ClassificationTreeService } from '../../services/classification-tree/classification-tree.service';
@Component({
selector: 'taskana-tree',
@ -30,7 +36,8 @@ import { ACTION } from '../../models/action';
styleUrls: ['./tree.component.scss'],
})
export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy {
classifications: TreeNodeModel[];
treeNodes: TreeNodeModel[];
@Input() selectNodeId: string;
@Input() filterText: string;
@Input() filterIcon = '';
@ -38,7 +45,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
@Select(EngineConfigurationSelectors.selectCategoryIcons) categoryIcons$: Observable<ClassificationCategoryImages>;
@Select(ClassificationSelectors.selectedClassificationId) selectedClassificationId$: Observable<string>;
@Select(ClassificationSelectors.activeAction) activeAction$: Observable<ACTION>;
@Select(ClassificationSelectors.classifications) classifications$: Observable<TreeNodeModel[]>;
@Select(ClassificationSelectors.classifications) classifications$: Observable<Classification[]>;
@Select(ClassificationSelectors.selectedClassificationType) classificationTypeSelected$: Observable<string>;
options: ITreeOptions = {
@ -70,6 +77,7 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
private location: Location,
private store: Store,
private notificationsService: NotificationService,
private classificationTreeService: ClassificationTreeService
) {
}
@ -85,32 +93,29 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
this.action = action;
});
const classificationCopy$: Observable<TreeNodeModel[]> = this.classifications$.pipe(
const computedTreeNodes$: Observable<TreeNodeModel[]> = this.classifications$.pipe(
filter(classifications => typeof (classifications) !== 'undefined'),
map(classifications => classifications.map(c => this.classificationsDeepCopy(c)))
map(classifications => this.classificationTreeService.transformToTreeNode(classifications))
);
combineLatest(this.selectedClassificationId$, classificationCopy$).pipe(takeUntil(this.destroy$))
.subscribe(([selectedClassificationId, classifications]) => {
this.classifications = classifications;
combineLatest([this.selectedClassificationId$, computedTreeNodes$]).pipe(takeUntil(this.destroy$))
.subscribe(([selectedClassificationId, treeNodes]) => {
this.treeNodes = treeNodes;
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);
// wait for angular's two-way binding to convert the treeNodes to the internal tree structure.
// after that conversion the new treeNodes are available
setTimeout(() => this.selectNode(this.selectNodeId), 0);
}
}
});
this.classificationTypeSelected$.pipe(takeUntil(this.destroy$)).subscribe(() => {
if (this.tree.treeModel.getActiveNode()) { this.deselectActiveNode(); }
});
if (this.tree.treeModel.getActiveNode()) {
this.deselectActiveNode();
}
classificationsDeepCopy(classification: TreeNodeModel) {
const ret: TreeNodeModel = { ...classification };
ret.children = ret.children ? [...ret.children] : [];
ret.children = ret.children.map(children => this.classificationsDeepCopy(children));
return ret;
});
}
ngAfterViewChecked(): void {
@ -179,6 +184,11 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
this.switchTaskanaSpinnerEmit.emit(active);
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
private selectNode(nodeId: string) {
if (nodeId) {
const selectedNode = this.getNode(nodeId);
@ -200,16 +210,16 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
}
private filterNodes(text, iconText) {
this.tree.treeModel.filterNodes(node => this.checkNameAndKey(node, text)
&& this.checkIcon(node, iconText));
this.tree.treeModel.filterNodes(node => TaskanaTreeComponent.checkNameAndKey(node, text)
&& TaskanaTreeComponent.checkIcon(node, iconText));
}
private checkNameAndKey(node: any, text: string): boolean {
private static checkNameAndKey(node: any, text: string): boolean {
return (node.data.name.toUpperCase().includes(text.toUpperCase())
|| node.data.key.toUpperCase().includes(text.toUpperCase()));
}
private checkIcon(node: any, iconText: string): boolean {
private static checkIcon(node: any, iconText: string): boolean {
return (node.data.category.toUpperCase() === iconText.toUpperCase()
|| iconText === '');
}
@ -228,17 +238,19 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
|| event.target.localName === 'taskana-tree');
}
private getClassification(classificationId: string): Promise<ClassificationDefinition> {
private getClassification(classificationId: string): Promise<Classification> {
return this.classificationsService.getClassification(classificationId).toPromise();
}
private updateClassification(classification: Classification) {
this.store.dispatch(new UpdateClassification(classification));
this.store.dispatch(new UpdateClassification(classification))
.subscribe(() => {
this.notificationsService.showToast(
NOTIFICATION_TYPES.SUCCESS_ALERT_5,
new Map<string, string>([['classificationKey', classification.key]])
);
this.switchTaskanaSpinner(false);
});
}
private collapseParentNodeIfItIsTheLastChild(node: any) {
@ -247,9 +259,4 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
this.getNode(node.parentId).collapse();
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}

View File

@ -7,13 +7,11 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { Overlay } from '@angular/cdk/overlay';
import { UserInformationComponent } from './user-information.component';
describe('UserInformationComponent', () => {
let component: UserInformationComponent;
let fixture: ComponentFixture<UserInformationComponent>;
let debugElement;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({

View File

@ -15,7 +15,6 @@ export class BusinessAdminGuard implements CanActivate {
return this.navigateToWorkplace();
}
navigateToWorkplace(): boolean {
this.router.navigate(['workplace']);
return false;

View File

@ -1,5 +1,3 @@
import { LinksClassification } from 'app/shared/models/links-classfication';
export class AccessIdDefinition {
constructor(
public accessId?: string,

View File

@ -1,4 +1,3 @@
export enum ACTION {
DEFAULT,
CREATE,

View File

@ -1,33 +0,0 @@
import { LinksClassification } from 'app/shared/models/links-classfication';
export class ClassificationDefinition {
constructor(
public classificationId?: string,
public key?: string,
public parentId?: string,
public parentKey?: string,
public category?: string,
public domain?: string,
public type?: string,
public isValidInDomain?: boolean,
public created?: string,
public modified?: string,
public name?: string,
public description?: string,
public priority?: number,
public serviceLevel?: string,
public applicationEntryPoint?: string,
public custom1?: string,
public custom2?: string,
public custom3?: string,
public custom4?: string,
public custom5?: string,
public custom6?: string,
public custom7?: string,
public custom8?: string,
public _links?: LinksClassification
) {
}
}
export const customFieldCount: number = 8;

View File

@ -0,0 +1,9 @@
import { Links } from './links';
import { Page } from './page';
import { ClassificationSummary } from './classification-summary';
export interface ClassificationPagingList {
classifications: ClassificationSummary[];
_links?: Links;
page?: Page
}

View File

@ -1,10 +0,0 @@
import { Classification } from './classification';
import { Links } from './links';
export class ClassificationResource {
constructor(
public classifications: Classification[] = [],
public _links?: Links
) {
}
}

View File

@ -0,0 +1,23 @@
export interface ClassificationSummary {
classificationId?: string;
key?: string;
applicationEntryPoint?: string;
category?: string;
domain?: string;
name?: string;
parentId?: string;
parentKey?: string;
priority?: number;
serviceLevel?: string;
type?: string;
custom1?: string;
custom2?: string;
custom3?: string;
custom4?: string;
custom5?: string;
custom6?: string;
custom7?: string;
custom8?: string;
}
export const customFieldCount: number = 8;

View File

@ -1,17 +1,11 @@
import { Links } from 'app/shared/models/links';
import { ClassificationSummary } from './classification-summary';
import { Links } from './links';
export class Classification {
constructor(
public classificationId?: string, // newly created classifications don't have an id yet.
public key?: string,
public category?: string,
public type?: string,
public domain?: string,
public name?: string,
public parentId?: string,
public priority?: number,
public serviceLevel?: string,
public _links?: Links
) {
}
export interface Classification extends ClassificationSummary {
isValidInDomain?: boolean;
created?: string; // TODO: make this a Date
modified?: string; // TODO: make this a Date
description?: string;
_links?: Links;
}

View File

@ -2,7 +2,7 @@ import { map } from 'rxjs/operators';
import { OperatorFunction } from 'rxjs';
export interface Customisation {
[language: string]: CustomisationContent
[language: string]: CustomisationContent;
}
export interface CustomisationContent {
@ -13,17 +13,17 @@ export interface CustomisationContent {
export interface TasksCustomisation {
information?: {
owner: LookupField
};
owner: LookupField;
}
}
export interface ClassificationsCustomisation {
information?: CustomFields;
categories?: ClassificationCategoryImages
categories?: ClassificationCategoryImages;
}
export interface ClassificationCategoryImages {
[key: string]: string
[key: string]: string;
}
export interface WorkbasketsCustomisation {
@ -34,16 +34,16 @@ export interface WorkbasketsCustomisation {
export type AccessItemsCustomisation = { accessId?: LookupField } & CustomFields;
export interface CustomFields {
[key: string]: CustomField
[key: string]: CustomField;
}
export interface CustomField {
visible: boolean
field: string
visible: boolean;
field: string;
}
export interface LookupField {
lookupField: boolean
lookupField: boolean;
}
export function getCustomFields(amount: number): OperatorFunction<CustomFields, CustomField[]> {

View File

@ -1,4 +1,3 @@
export enum ICONTYPES {
ALL = 'ALL',
PERSONAL = 'PERSONAL',

View File

@ -1,12 +0,0 @@
import { Links } from './links';
export class LinksClassification extends Links {
constructor(
self?,
distributionTargets?,
accessItems?,
public getAllClassifications?: { 'href': string },
public createClassification?: { 'href': string },
public updateClassification?: { 'href': string },
) { super(self, distributionTargets, accessItems); }
}

View File

@ -1,10 +0,0 @@
import { Links } from './links';
export class LinksWorkbasketSummary extends Links {
constructor(
self?,
distributionTargets?,
accessItems?,
public allWorkbaskets?: { 'href': string }
) { super(self, distributionTargets, accessItems); }
}

View File

@ -1,9 +1,5 @@
export class Links {
constructor(
public self?: { 'href': string },
public distributionTargets?: { 'href': string },
public accessItems?: { 'href': string },
public allWorkbasketUrl?: { 'href': string },
public removeDistributionTargets?: {'href': string}
) { }
export interface Links {
[description: string]: {
href: string
}
}

View File

@ -1,6 +1,5 @@
import { Pair } from './pair';
export enum NOTIFICATION_TYPES {
// ERRORS
@ -58,6 +57,7 @@ export enum NOTIFICATION_TYPES {
SUCCESS_ALERT_14,
WARNING_ALERT,
WARNING_ALERT_2,
WARNING_CANT_COPY
}
export const notifications = new Map<NOTIFICATION_TYPES, Pair>([
@ -310,5 +310,9 @@ export const notifications = new Map<NOTIFICATION_TYPES, Pair>([
[NOTIFICATION_TYPES.INFO_ALERT_2, new Pair(
'',
'The selected Workbasket is empty!'
)],
[NOTIFICATION_TYPES.WARNING_CANT_COPY, new Pair(
'',
'Can\'t copy a not created classification'
)]
]);

View File

@ -1,4 +1,3 @@
export class QueryParameters {
SORTBY: string;
SORTDIRECTION: string;

View File

@ -3,7 +3,6 @@ export enum Direction {
DESC = 'desc'
}
export class Sorting {
sortBy: string;
sortDirection: string;

View File

@ -3,5 +3,5 @@ import { TaskHistoryEventData } from './task-history-event';
export class TaskHistoryEventResourceData {
public taskHistoryEvents: Array<TaskHistoryEventData>;
public _links: Links = new Links();
public _links: Links = {};
}

View File

@ -1,18 +1,5 @@
import { Classification } from 'app/shared/models/classification';
export class TreeNodeModel extends Classification {
constructor(
public id?: string,
public key?: string,
public category?: string,
public type?: string,
public domain?: string,
public name?: string,
public parentId?: string,
public priority?: number,
public serviceLevel?: string,
public children: TreeNodeModel[] = []
) {
super(id, key, category, type, domain, name, parentId, priority, serviceLevel);
}
export interface TreeNodeModel extends Classification {
children: TreeNodeModel[]
}

View File

@ -1,4 +1,3 @@
export class UserInfo {
constructor(
public userId: string = '',

View File

@ -1,4 +1,3 @@
export class Version {
constructor(
public version: string = ''

View File

@ -4,6 +4,6 @@ import { WorkbasketAccessItems } from './workbasket-access-items';
export class WorkbasketAccessItemsResource {
constructor(
public accessItems: Array<WorkbasketAccessItems> = [],
public _links: Links = new Links()
public _links: Links = {}
) { }
}

View File

@ -23,7 +23,7 @@ export class WorkbasketAccessItems {
public permCustom10: boolean = false,
public permCustom11: boolean = false,
public permCustom12: boolean = false,
public _links: Links = new Links()
public _links: Links = {}
) { }
}

View File

@ -4,6 +4,6 @@ import { Workbasket } from './workbasket';
export class WorkbasketResource {
constructor(
public workbaskets: Array<Workbasket> = [],
public _links: Links = new Links()
public _links: Links = {}
) { }
}

View File

@ -1,11 +1,11 @@
import { Page } from 'app/shared/models/page';
import { WorkbasketSummary } from './workbasket-summary';
import { LinksWorkbasketSummary } from './links-workbasket-summary';
import { Links } from './links';
export class WorkbasketSummaryResource {
constructor(
public workbaskets: Array<WorkbasketSummary> = [],
public _links: LinksWorkbasketSummary = new LinksWorkbasketSummary(),
public _links: Links = {},
public page: Page = new Page()
) {
}

View File

@ -2,28 +2,6 @@ import { Links } from './links';
import { ICONTYPES } from './icon-types';
export class Workbasket {
public static equals(org: Workbasket, comp: Workbasket): boolean {
if (org.workbasketId !== comp.workbasketId) { return false; }
if (org.created !== comp.created) { return false; }
if (org.key !== comp.key) { return false; }
if (org.domain !== comp.domain) { return false; }
if (org.type !== comp.type) { return false; }
if (org.modified !== comp.modified) { return false; }
if (org.name !== comp.name) { return false; }
if (org.description !== comp.description) { return false; }
if (org.owner !== comp.owner) { return false; }
if (org.custom1 !== comp.custom1) { return false; }
if (org.custom2 !== comp.custom2) { return false; }
if (org.custom3 !== comp.custom3) { return false; }
if (org.custom4 !== comp.custom4) { return false; }
if (org.orgLevel1 !== comp.orgLevel1) { return false; }
if (org.orgLevel2 !== comp.orgLevel2) { return false; }
if (org.orgLevel3 !== comp.orgLevel3) { return false; }
if (org.orgLevel4 !== comp.orgLevel4) { return false; }
return true;
}
constructor(
public workbasketId?: string,
public created?: string,
@ -42,7 +20,7 @@ export class Workbasket {
public orgLevel2?: string,
public orgLevel3?: string,
public orgLevel4?: string,
public _links: Links = new Links()
public _links: Links = {}
) {
}
}

View File

@ -16,7 +16,6 @@ export class SpreadNumberPipe implements PipeTransform {
let leftDifference = 0;
let rightDifference = 0;
if (minArrayValue < 0) { leftDifference = Math.abs(minArrayValue); minArrayValue = 0; }
if (maxArrayValue > maxPageNumber) {
rightDifference = maxArrayValue - maxPageNumber;

View File

@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { TreeNodeModel } from '../../models/tree-node';
import { Classification } from '../../models/classification';
@Injectable({
providedIn: 'root'
})
export class ClassificationTreeService {
transformToTreeNode(classifications: Classification[]): TreeNodeModel[] {
const classificationsAsTree: TreeNodeModel[] = classifications.map(c => ({
...c,
children: []
})).sort((a: TreeNodeModel, b: TreeNodeModel) => a.key.localeCompare(b.key));
const roots: TreeNodeModel[] = [];
const children: TreeNodeModel[] = [];
classificationsAsTree.forEach(item => {
const parent = item.parentId;
const target = !parent ? roots : (children[parent] || (children[parent] = []));
target.push(item);
});
roots.forEach(parent => this.findChildren(parent, children));
return roots;
}
private findChildren(parent: TreeNodeModel, children: TreeNodeModel[]) {
if (children[parent.classificationId]) {
parent.children = children[parent.classificationId];
parent.children.forEach(child => this.findChildren(child, children));
}
}
}

View File

@ -5,9 +5,8 @@ import { Observable, Subject } from 'rxjs';
import { map, mergeMap, tap } from 'rxjs/operators';
import { Classification } from 'app/shared/models/classification';
import { ClassificationDefinition } from 'app/shared/models/classification-definition';
import { ClassificationResource } from 'app/shared/models/classification-resource';
import { ClassificationPagingList } from 'app/shared/models/classification-paging-list';
import { DomainService } from 'app/shared/services/domain/domain.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { Direction } from 'app/shared/models/sorting';
@ -16,20 +15,21 @@ import { QueryParameters } from 'app/shared/models/query-parameters';
@Injectable()
export class ClassificationsService {
private url = `${environment.taskanaRestUrl}/v1/classifications/`;
private classificationSaved = new Subject<number>();
private classificationResourcePromise: Promise<ClassificationResource>;
private classificationResourcePromise: Promise<ClassificationPagingList>;
private lastDomain: string;
constructor(
private httpClient: HttpClient,
private domainService: DomainService,
) {}
) {
}
private static classificationParameters(domain: string): QueryParameters {
private static classificationParameters(domain: string, type?: string): QueryParameters {
const parameters = new QueryParameters();
parameters.SORTBY = TaskanaQueryParameters.parameters.KEY;
parameters.SORTDIRECTION = Direction.ASC;
parameters.DOMAIN = domain;
parameters.TYPE = type;
delete TaskanaQueryParameters.page;
delete TaskanaQueryParameters.pageSize;
@ -37,22 +37,22 @@ export class ClassificationsService {
}
// GET
getClassifications(classificationType?: string): Observable<Array<Classification>> {
getClassifications(classificationType?: string): Observable<ClassificationPagingList> {
return this.domainService.getSelectedDomain().pipe(
mergeMap(domain => this.getClassificationObservable(this.httpClient.get<ClassificationResource>(
`${this.url}${TaskanaQueryParameters.getQueryParameters(ClassificationsService.classificationParameters(domain))}`
), classificationType)),
tap(() => {
this.domainService.domainChangedComplete();
})
mergeMap(domain => this.httpClient.get<ClassificationPagingList>(
`${this.url}${TaskanaQueryParameters.getQueryParameters(
ClassificationsService.classificationParameters(domain, classificationType)
)}`
)),
tap(() => this.domainService.domainChangedComplete())
);
}
// GET
getClassificationsByDomain(domain: string, forceRefresh = false): Promise<ClassificationResource> {
getClassificationsByDomain(domain: string, forceRefresh = false): Promise<ClassificationPagingList> {
if (this.lastDomain !== domain || !this.classificationResourcePromise || forceRefresh) {
this.lastDomain = domain;
this.classificationResourcePromise = this.httpClient.get<ClassificationResource>(
this.classificationResourcePromise = this.httpClient.get<ClassificationPagingList>(
`${this.url}${TaskanaQueryParameters.getQueryParameters(ClassificationsService.classificationParameters(domain))}`
).toPromise();
}
@ -60,8 +60,8 @@ export class ClassificationsService {
}
// GET
getClassification(id: string): Observable<ClassificationDefinition> {
return this.httpClient.get<ClassificationDefinition>(`${this.url}${id}`);
getClassification(id: string): Observable<Classification> {
return this.httpClient.get<Classification>(`${this.url}${id}`);
}
// POST
@ -78,49 +78,4 @@ export class ClassificationsService {
deleteClassification(id: string): Observable<string> {
return this.httpClient.delete<string>(`${this.url}${id}`);
}
// #region "Service extras"
triggerClassificationSaved() {
this.classificationSaved.next(Date.now());
}
classificationSavedTriggered(): Observable<number> {
return this.classificationSaved.asObservable();
}
// #endregion
private getClassificationObservable(
classificationRef: Observable<ClassificationResource>,
classificationType: string
): Observable<Array<Classification>> {
return classificationRef.pipe(map(
(resource: ClassificationResource) => (
resource.classifications ? this.buildHierarchy(resource.classifications, classificationType) : []
)
));
}
private buildHierarchy(classifications: Array<Classification>, type: string): Array<Classification> {
const roots = [];
const children = [];
classifications.forEach(item => {
if (item.type === type) {
const parent = item.parentId;
const target = !parent ? roots : (children[parent] || (children[parent] = []));
target.push(item);
}
});
roots.forEach(parent => this.findChildren(parent, children));
return roots;
}
private findChildren(parent: any, children: Array<any>) {
if (children[parent.classificationId]) {
parent.children = children[parent.classificationId];
parent.children.forEach(child => this.findChildren(child, children));
}
}
}

View File

@ -6,7 +6,7 @@ export class RequestInProgressService {
public requestInProgressTriggered = new Subject<boolean>();
setRequestInProgress(value: boolean) {
setTimeout(() => this.requestInProgressTriggered.next(value), 0);
this.requestInProgressTriggered.next(value);
}
getRequestInProgress(): Observable<boolean> {

View File

@ -10,7 +10,6 @@ export class SelectedRouteService {
constructor(private router: Router) { }
selectRoute(value) {
this.selectedRouteTriggered.next(this.getRoute(value));
}

View File

@ -6,7 +6,6 @@ import { Version } from 'app/shared/models/version';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TaskanaEngineService {
currentUserInfo: UserInfo;

View File

@ -1,4 +1,3 @@
import { throwError as observableThrowError, Observable, Subject } from 'rxjs';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@ -46,7 +45,6 @@ export class WorkbasketService {
return this.workbasketSummaryRef;
}
return this.domainService.getSelectedDomain()
.pipe(mergeMap(domain => {
this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryResource>(
@ -54,7 +52,6 @@ export class WorkbasketService {
.getQueryParameters(this.workbasketParameters(sortBy, order, name, nameLike, descLike, owner, ownerLike,
type, key, keyLike, requiredPermission, allPages, domain))}`
);
this.workbasketSummaryRef.pipe(tap((workbaskets => workbaskets)));
return this.workbasketSummaryRef;
}),
tap(() => {
@ -124,7 +121,6 @@ export class WorkbasketService {
return this.httpClient.delete<string>(url);
}
// #endregion
// #region "Service extras"
selectWorkBasket(id?: string) {
@ -159,7 +155,6 @@ export class WorkbasketService {
return observableThrowError(errMsg);
}
private workbasketParameters(
sortBy: string = TaskanaQueryParameters.parameters.KEY,
order: string = Direction.ASC,

View File

@ -10,7 +10,6 @@ import { AccordionModule } from 'ngx-bootstrap/accordion';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
/**
* Components
*/
@ -44,7 +43,6 @@ import { MapToIterable } from './pipes/map-to-iterable.pipe';
import { NumberToArray } from './pipes/number-to-array.pipe';
import { DateTimeZonePipe } from './pipes/date-time-zone.pipe';
/**
* Services
*/

View File

@ -1,4 +1,3 @@
import { ClassificationDefinition } from '../../models/classification-definition';
import { ACTION } from '../../models/action';
import { Classification } from '../../models/classification';
@ -20,13 +19,13 @@ export class DeselectClassification {
export class CreateClassification {
static readonly type = '[Classification] Create a new classification';
constructor(public classification: ClassificationDefinition) {
constructor(public classification: Classification) {
}
}
export class SaveClassification {
static readonly type = '[Classification] Save a classification and select it';
constructor(public classification: ClassificationDefinition) {
constructor(public classification: Classification) {
}
}

View File

@ -1,7 +1,8 @@
import { Selector } from '@ngxs/store';
import { ClassificationStateModel, ClassificationState } from './classification.state';
import { ClassificationDefinition } from '../../models/classification-definition';
import { ACTION } from '../../models/action';
import { Classification } from '../../models/classification';
import { CategoriesResponse } from '../../services/classification-categories/classification-categories.service';
export class ClassificationSelectors {
@Selector([ClassificationState])
@ -20,17 +21,17 @@ export class ClassificationSelectors {
}
@Selector([ClassificationState])
static selectClassificationTypesObject(state: ClassificationStateModel): Object {
static selectClassificationTypesObject(state: ClassificationStateModel): CategoriesResponse {
return state.classificationTypes;
}
@Selector([ClassificationState])
static classifications(state: ClassificationStateModel): ClassificationDefinition[] {
static classifications(state: ClassificationStateModel): Classification[] {
return state.classifications;
}
@Selector([ClassificationState])
static selectedClassification(state: ClassificationStateModel): ClassificationDefinition {
static selectedClassification(state: ClassificationStateModel): Classification {
return state.selectedClassification;
}

View File

@ -15,9 +15,10 @@ import { CreateClassification,
SetSelectedClassificationType,
UpdateClassification } from './classification.actions';
import { ClassificationsService } from '../../services/classifications/classifications.service';
import { ClassificationDefinition } from '../../models/classification-definition';
import { ACTION } from '../../models/action';
import { DomainService } from '../../services/domain/domain.service';
import { Classification } from '../../models/classification';
import { ClassificationSummary } from '../../models/classification-summary';
class InitializeStore {
static readonly type = '[ClassificationState] Initializing state';
@ -32,10 +33,23 @@ export class ClassificationState implements NgxsAfterBootstrap {
) {
}
@Action(InitializeStore)
initializeStore(ctx: StateContext<ClassificationStateModel>): Observable<any> {
return this.categoryService.getClassificationCategoriesByType().pipe(
take(1),
tap(classificationTypes => {
ctx.patchState({
classificationTypes,
classifications: undefined,
selectedClassificationType: Object.keys(classificationTypes)[0],
});
}),
);
}
@Action(SetSelectedClassificationType)
setSelectedClassificationType(ctx: StateContext<ClassificationStateModel>, action: SetSelectedClassificationType): Observable<null> {
const state: ClassificationStateModel = ctx.getState();
if (state.classificationTypes[action.selectedType]) {
if (ctx.getState().classificationTypes[action.selectedType]) {
ctx.patchState({
selectedClassificationType: action.selectedType,
selectedClassification: undefined,
@ -46,17 +60,16 @@ export class ClassificationState implements NgxsAfterBootstrap {
}
@Action(SelectClassification)
selectClassification(ctx: StateContext<ClassificationStateModel>, action: SelectClassification): Observable<any|null> {
selectClassification(ctx: StateContext<ClassificationStateModel>, action: SelectClassification): Observable<Classification | null> {
if (typeof action.classificationId !== 'undefined') {
return this.classificationsService.getClassification(action.classificationId).pipe(take(1), tap(
selectedClassification => {
ctx.patchState({
return this.classificationsService.getClassification(action.classificationId).pipe(
take(1),
tap(selectedClassification => ctx.patchState({
selectedClassification,
action: ACTION.DEFAULT,
selectedClassificationType: selectedClassification.type
});
}
));
selectedClassificationType: selectedClassification.type,
action: ACTION.DEFAULT
}))
);
}
return of(null);
}
@ -70,81 +83,44 @@ export class ClassificationState implements NgxsAfterBootstrap {
return of(null);
}
@Action(InitializeStore)
initializeStore(ctx: StateContext<ClassificationStateModel>): Observable<any> {
return this.categoryService.getClassificationCategoriesByType().pipe(
take(1), tap(classificationTypes => {
ctx.setState({
...ctx.getState(),
classificationTypes,
classifications: undefined,
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
});
}),
take(1),
tap(list => ctx.patchState({
classifications: list.classifications
})),
);
}
@Action(CreateClassification)
createClassification(ctx: StateContext<ClassificationStateModel>, action: CreateClassification): Observable<any> {
return this.classificationsService.postClassification(action.classification).pipe(
take(1), tap(classification => {
ctx.patchState(
{
take(1), tap(classification => ctx.patchState({
classifications: [...ctx.getState().classifications, classification],
selectedClassification: classification,
action: ACTION.DEFAULT
}
);
})
}))
);
}
@Action(SaveClassification)
saveClassification(ctx: StateContext<ClassificationStateModel>, action: SaveClassification): Observable<any> {
return this.classificationsService.putClassification(action.classification).pipe(
take(1), tap(savedClassification => {
ctx.patchState({
classifications: ctx.getState().classifications.map(currentClassification => {
if (currentClassification.classificationId === savedClassification.classificationId) {
return savedClassification;
}
return currentClassification;
}),
take(1),
tap(savedClassification => ctx.patchState({
classifications: updateClassificationList(ctx.getState().classifications, savedClassification),
selectedClassification: savedClassification
});
}), tap(() => this.classificationsService.getClassifications(
ctx.getState().selectedClassificationType
).subscribe(
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 });
})
take(1),
tap(selectedClassification => ctx.patchState({ selectedClassification }))
);
}
@ -152,14 +128,15 @@ export class ClassificationState implements NgxsAfterBootstrap {
setActiveAction(ctx: StateContext<ClassificationStateModel>, action: SetActiveAction): Observable<null> {
if (action.action === ACTION.CREATE) {
// Initialization of a new classification
const initialClassification: ClassificationDefinition = new ClassificationDefinition();
const state: ClassificationStateModel = ctx.getState();
initialClassification.type = state.selectedClassificationType;
[initialClassification.category] = state.classificationTypes[initialClassification.type];
const date = TaskanaDate.getDate();
initialClassification.created = date;
initialClassification.modified = date;
initialClassification.domain = this.domainService.getSelectedDomainValue();
const initialClassification: Classification = {
type: state.selectedClassificationType,
category: state.classificationTypes[state.selectedClassificationType][0],
created: date,
modified: date,
domain: this.domainService.getSelectedDomainValue(),
};
if (state.selectedClassification) {
initialClassification.parentId = state.selectedClassification.classificationId;
initialClassification.parentKey = state.selectedClassification.key;
@ -174,17 +151,33 @@ export class ClassificationState implements NgxsAfterBootstrap {
@Action(RemoveSelectedClassification)
removeSelectedClassification(ctx: StateContext<ClassificationStateModel>): Observable<any> {
const sel = ctx.getState().selectedClassification;
return this.classificationsService.deleteClassification(sel.classificationId).pipe(take(1), tap(() => {
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(take(1), tap(
classifications => ctx.patchState({ classifications })
));
return this.classificationsService.putClassification(action.classification).pipe(
take(1),
tap(
classification => {
const state = ctx.getState();
let { selectedClassification } = state;
if (selectedClassification && selectedClassification.classificationId === classification.classificationId) {
selectedClassification = classification;
}
ctx.patchState({
classifications: updateClassificationList(state.classifications, classification),
selectedClassification
});
}
)
);
}
// initialize after Startup service has configured the taskanaRestUrl properly.
@ -194,9 +187,18 @@ export class ClassificationState implements NgxsAfterBootstrap {
}
}
function updateClassificationList(classifications: ClassificationSummary[], classification: Classification) {
return classifications.map(c => {
if (c.classificationId === classification.classificationId) {
return classification;
}
return c;
});
}
export interface ClassificationStateModel {
classifications: ClassificationDefinition[],
selectedClassification: ClassificationDefinition,
classifications: ClassificationSummary[],
selectedClassification: Classification,
selectedClassificationType: string;
classificationTypes: CategoriesResponse,
action: ACTION,

View File

@ -29,7 +29,6 @@ export class EngineConfigurationState implements NgxsOnInit {
}
}
export interface EngineConfigurationStateModel {
customisation: Customisation,
language: string

View File

@ -9,7 +9,6 @@ import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { TaskListComponent } from './task-list.component';
import { DateTimeZonePipe } from '../../../shared/pipes/date-time-zone.pipe';
@Component({
selector: 'taskana-dummy-detail',
template: 'dummydetail'

View File

@ -35,7 +35,6 @@ xdescribe('TaskMasterComponent', () => {
let component: TaskMasterComponent;
let fixture: ComponentFixture<TaskMasterComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule, TypeaheadModule,

View File

@ -97,7 +97,6 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
});
}
performSorting(sort: Sorting) {
this.sort = sort;
this.getTasks();

View File

@ -8,7 +8,6 @@ import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.ser
import { Subscription } from 'rxjs';
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
@Component({
selector: 'taskana-task',
templateUrl: './task.component.html',
@ -25,7 +24,6 @@ export class TaskComponent implements OnInit, OnDestroy {
task: Task = null;
workbaskets: Workbasket[];
constructor(private taskService: TaskService,
private workbasketService: WorkbasketService,
private classificationService: ClassificationsService,

View File

@ -1,4 +1,4 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { ClassificationsService } from 'app/shared/services/classifications/classifications.service';
@ -11,9 +11,7 @@ import { Component } from '@angular/core';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { SelectedRouteService } from 'app/shared/services/selected-route/selected-route';
import { configureTests } from 'app/app.test.configuration';
import { ClassificationResource } from 'app/shared/models/classification-resource';
import { Classification } from 'app/shared/models/classification';
import { Links } from 'app/shared/models/links';
import { ClassificationPagingList } from 'app/shared/models/classification-paging-list';
import { TaskdetailsGeneralFieldsComponent } from './general-fields.component';
@Component({
@ -35,7 +33,7 @@ xdescribe('GeneralComponent', () => {
beforeEach(done => {
const configure = (testBed: TestBed) => {
TestBed.configureTestingModule({
testBed.configureTestingModule({
imports: [FormsModule, HttpClientModule, RouterTestingModule.withRoutes(routes)],
declarations: [TaskdetailsGeneralFieldsComponent, DummyDetailComponent],
providers: [HttpClient, ClassificationCategoriesService,
@ -43,16 +41,31 @@ xdescribe('GeneralComponent', () => {
});
};
configureTests(configure).then(testBed => {
classificationsService = TestBed.get(ClassificationsService);
spyOn(classificationsService, 'getClassificationsByDomain').and.returnValue(new ClassificationResource(
new Array<Classification>(
new Classification('id1', '1', 'category', 'type', 'domain_a', 'classification1', 'parentId',
1, 'service', new Links({ href: 'someurl' })),
new Classification('id2', '2', 'category', 'type', 'domain_a', 'classification2', 'parentId2',
1, 'service', new Links({ href: 'someurl' }))
),
new Links({ href: 'someurl' })
));
classificationsService = testBed.get(ClassificationsService);
const resource: ClassificationPagingList = {
classifications: [
{
classificationId: 'id1',
key: 'key1',
category: 'category',
type: 'type',
domain: 'DOMAIN_A',
name: 'classification1',
parentId: 'parentId',
parentKey: 'parentKey'
}, {
classificationId: 'id2',
key: 'key2',
category: 'category',
type: 'type',
domain: 'DOMAIN_A',
name: 'classification1',
parentId: 'parentId',
parentKey: 'parentKey'
},
]
};
spyOn(classificationsService, 'getClassificationsByDomain').and.returnValue(resource);
done();
});
});

View File

@ -20,7 +20,6 @@ import { TaskComponent } from './components/task/task.component';
import { GeneralFieldsExtensionComponent } from './components/taskdetails-general-fields-extension/general-fields-extension.component';
import { TaskListComponent } from './components/task-list/task-list.component';
import { TaskService } from './services/task.service';
import { TokenInterceptor } from './services/token-interceptor.service';
import { WorkplaceService } from './services/workplace.service';

View File

@ -18,29 +18,24 @@
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** Evergreen browsers require these. */
import 'core-js/es6/reflect';
import 'core-js/es7/reflect';
/** ALL Firefox browsers require the following to support `@angular/animation`. */
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/** *************************************************************************************************
* Zone JS is required by Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/** *************************************************************************************************
* APPLICATION IMPORTS
*/