From f18a8dc9324ab41226bb2853d9098bfd060ed7cb Mon Sep 17 00:00:00 2001
From: Sofie Hofmann <29145005+sofie29@users.noreply.github.com>
Date: Wed, 1 Sep 2021 10:24:05 +0200
Subject: [PATCH] TSK-1630,TSK-1670: Enabled owner validiation and editing
AccessItem name
---
web/angular.json | 5 +-
.../access-items-management.component.html | 5 +-
.../access-items-management.component.spec.ts | 2 -
.../access-items-management.component.ts | 11 +-
.../workbasket-access-items.component.html | 11 +-
.../workbasket-access-items.component.scss | 2 +-
.../workbasket-access-items.component.ts | 4 +-
.../workbasket-information.component.html | 14 +-
.../workbasket-information.component.ts | 15 +-
.../type-ahead/type-ahead.component.html | 47 +++--
.../type-ahead/type-ahead.component.scss | 42 ++--
.../type-ahead/type-ahead.component.spec.ts | 116 ++++++-----
.../type-ahead/type-ahead.component.ts | 196 ++++++++----------
.../type-ahead/type-ahead.mock.component.ts | 29 ---
web/src/app/shared/models/access-id.ts | 5 +-
.../services/access-ids/access-ids.service.ts | 10 +-
.../obtain-message/message-by-error-code.ts | 1 +
web/src/app/shared/shared.module.ts | 2 +-
.../access-items-management.actions.ts | 4 +-
.../access-items-management.selector.ts | 4 +-
.../access-items-management.state.ts | 8 +-
.../workbasket-store/workbasket.state.ts | 1 +
.../task-information.component.html | 13 +-
.../task-information.component.ts | 11 +-
24 files changed, 276 insertions(+), 282 deletions(-)
delete mode 100644 web/src/app/shared/components/type-ahead/type-ahead.mock.component.ts
diff --git a/web/angular.json b/web/angular.json
index 6307b64e1..770e219f2 100644
--- a/web/angular.json
+++ b/web/angular.json
@@ -57,7 +57,10 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
- "browserTarget": "taskana-web:build"
+ "browserTarget": "taskana-web:build",
+ "sourceMap": {
+ "scripts": true
+ }
},
"configurations": {
"production": {
diff --git a/web/src/app/administration/components/access-items-management/access-items-management.component.html b/web/src/app/administration/components/access-items-management/access-items-management.component.html
index 6460eac8a..bc0917319 100644
--- a/web/src/app/administration/components/access-items-management/access-items-management.component.html
+++ b/web/src/app/administration/components/access-items-management/access-items-management.component.html
@@ -2,9 +2,8 @@
-
+
diff --git a/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts b/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts
index f43b0d10a..18adf98e3 100644
--- a/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts
+++ b/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts
@@ -120,7 +120,6 @@ describe('AccessItemsManagementComponent', () => {
...store.snapshot(),
engineConfiguration: engineConfigurationMock
});
- app.accessIdSelected = '1';
fixture.detectChanges();
}));
@@ -181,7 +180,6 @@ describe('AccessItemsManagementComponent', () => {
}));
it('should display a dialog when access is revoked', async(() => {
- app.accessIdSelected = 'xyz';
app.accessId = { accessId: 'xyz', name: 'xyz' };
const notificationService = TestBed.inject(NotificationService);
const showDialogSpy = jest.spyOn(notificationService, 'showDialog').mockImplementation();
diff --git a/web/src/app/administration/components/access-items-management/access-items-management.component.ts b/web/src/app/administration/components/access-items-management/access-items-management.component.ts
index f33bd647e..1c920fa5c 100644
--- a/web/src/app/administration/components/access-items-management/access-items-management.component.ts
+++ b/web/src/app/administration/components/access-items-management/access-items-management.component.ts
@@ -12,7 +12,7 @@ import {
} from 'app/shared/models/sorting';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { takeUntil } from 'rxjs/operators';
-import { AccessIdDefinition } from '../../../shared/models/access-id';
+import { AccessId } from '../../../shared/models/access-id';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { AccessItemsCustomisation, CustomField, getCustomFields } from '../../../shared/models/customisation';
import { customFieldCount } from '../../../shared/models/workbasket-access-items';
@@ -31,14 +31,13 @@ import { WorkbasketAccessItemQueryFilterParameter } from '../../../shared/models
styleUrls: ['./access-items-management.component.scss']
})
export class AccessItemsManagementComponent implements OnInit {
- accessIdSelected: string;
accessIdPrevious: string;
isRequired: boolean = false;
accessIdName: string;
panelState: boolean = false;
accessItemsForm: FormGroup;
- accessId: AccessIdDefinition;
- groups: AccessIdDefinition[];
+ accessId: AccessId;
+ groups: AccessId[];
defaultSortBy: WorkbasketAccessItemQuerySortParameter = WorkbasketAccessItemQuerySortParameter.ACCESS_ID;
sortingFields: Map
= WORKBASKET_ACCESS_ITEM_SORT_PARAMETER_NAMING;
sortModel: Sorting = {
@@ -50,7 +49,7 @@ export class AccessItemsManagementComponent implements OnInit {
@Select(EngineConfigurationSelectors.accessItemsCustomisation)
accessItemsCustomization$: Observable;
- @Select(AccessItemsManagementSelector.groups) groups$: Observable;
+ @Select(AccessItemsManagementSelector.groups) groups$: Observable;
customFields$: Observable;
destroy$ = new Subject();
@@ -68,7 +67,7 @@ export class AccessItemsManagementComponent implements OnInit {
});
}
- onSelectAccessId(selected: AccessIdDefinition) {
+ onSelectAccessId(selected: AccessId) {
if (selected) {
this.accessId = selected;
if (this.accessIdPrevious !== selected.accessId) {
diff --git a/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.html b/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.html
index 5ea84af6f..23283550d 100644
--- a/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.html
+++ b/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.html
@@ -48,11 +48,12 @@
class="workbasket-access-items__typeahead" [ngClass]="{ 'has-warning': (accessItemsClone[index].accessId !== accessItem.value.accessId),
'has-error': !accessItem.value.accessId }">
-
+
diff --git a/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.scss b/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.scss
index 85c4b45c6..a1b02ccb2 100644
--- a/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.scss
+++ b/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.scss
@@ -104,7 +104,7 @@
height: 58px;
}
-::ng-deep .workbasket-access-items__typeahead .typeahead__form .mat-form-field-infix {
+::ng-deep .workbasket-access-items__typeahead .type-ahead__form-field .mat-form-field-infix {
padding: 0 0 0 0;
position: initial;
font-size: medium;
diff --git a/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.ts b/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.ts
index b12372c3d..2b24d1ecf 100644
--- a/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.ts
+++ b/web/src/app/administration/components/workbasket-access-items/workbasket-access-items.component.ts
@@ -19,7 +19,7 @@ import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbaske
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { highlight } from 'app/shared/animations/validation.animation';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
-import { AccessIdDefinition } from 'app/shared/models/access-id';
+import { AccessId } from 'app/shared/models/access-id';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { filter, take, takeUntil, tap } from 'rxjs/operators';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
@@ -303,7 +303,7 @@ export class WorkbasketAccessItemsComponent implements OnInit, OnChanges, OnDest
});
}
- accessItemSelected(accessItem: AccessIdDefinition, row: number) {
+ accessItemSelected(accessItem: AccessId, row: number) {
this.accessItemsGroups.controls[row].get('accessId').setValue(accessItem?.accessId);
this.accessItemsGroups.controls[row].get('accessName').setValue(accessItem?.name);
}
diff --git a/web/src/app/administration/components/workbasket-information/workbasket-information.component.html b/web/src/app/administration/components/workbasket-information/workbasket-information.component.html
index 4cf5101b0..09dcd5860 100644
--- a/web/src/app/administration/components/workbasket-information/workbasket-information.component.html
+++ b/web/src/app/administration/components/workbasket-information/workbasket-information.component.html
@@ -40,13 +40,13 @@
-
- {{lengthError}}
+
diff --git a/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts b/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts
index 1966a3975..42cb6070e 100644
--- a/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts
+++ b/web/src/app/administration/components/workbasket-information/workbasket-information.component.ts
@@ -21,7 +21,7 @@ import {
import { WorkbasketComponent } from '../../models/workbasket-component';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { ButtonAction } from '../../models/button-action';
-import { AccessIdDefinition } from '../../../shared/models/access-id';
+import { AccessId } from '../../../shared/models/access-id';
@Component({
selector: 'taskana-administration-workbasket-information',
@@ -42,6 +42,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
allTypes: Map;
toggleValidationMap = new Map();
lookupField = false;
+ isOwnerValid: boolean = true;
readonly lengthError = 'You have reached the maximum length for this field';
inputOverflowMap = new Map();
@@ -98,7 +99,7 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
.subscribe((button) => {
switch (button) {
case ButtonAction.SAVE:
- this.onSave();
+ this.onSubmit();
break;
case ButtonAction.UNDO:
this.onUndo();
@@ -122,8 +123,10 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
onSubmit() {
this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService.validateFormInformation(this.workbasketForm, this.toggleValidationMap).then((value) => {
- if (value) {
+ if (value && this.isOwnerValid) {
this.onSave();
+ } else {
+ this.notificationService.showError('WORKBASKET_SAVE');
}
});
}
@@ -191,10 +194,8 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
});
}
- onSelectedOwner(owner: AccessIdDefinition) {
- if (owner?.accessId) {
- this.workbasket.owner = owner.accessId;
- }
+ onSelectedOwner(owner: AccessId) {
+ this.workbasket.owner = owner.accessId;
}
getWorkbasketCustomProperty(custom: number) {
diff --git a/web/src/app/shared/components/type-ahead/type-ahead.component.html b/web/src/app/shared/components/type-ahead/type-ahead.component.html
index 9373fdf24..69fc5aa51 100644
--- a/web/src/app/shared/components/type-ahead/type-ahead.component.html
+++ b/web/src/app/shared/components/type-ahead/type-ahead.component.html
@@ -1,18 +1,29 @@
-
-
-
+
+
diff --git a/web/src/app/shared/components/type-ahead/type-ahead.component.scss b/web/src/app/shared/components/type-ahead/type-ahead.component.scss
index 8855b7576..702b9d82f 100644
--- a/web/src/app/shared/components/type-ahead/type-ahead.component.scss
+++ b/web/src/app/shared/components/type-ahead/type-ahead.component.scss
@@ -1,26 +1,34 @@
@import '../../../../theme/colors';
-::placeholder {
- /* Chrome, Firefox, Opera, Safari 10.1+ */
- opacity: 1; /* Firefox */
+.type-ahead {
+ min-height: 0;
+
+ &__form-field {
+ width: 100% !important;
+ }
+
+ &__form-options {
+ white-space: pre;
+ }
+
+ &__input-field {
+ left: 50%;
+ }
+
+ &__error--accessId {
+ white-space: nowrap;
+ padding-top: 8px;
+ }
+
+ ::ng-deep &--small > .mat-form-field-appearance-outline div.mat-form-field-infix {
+ padding: 0.25em;
+ }
}
-.disable {
- cursor: not-allowed;
+::ng-deep .ng-invalid.ng-touched:not(form) {
+ box-shadow: unset;
}
.invalid {
color: $invalid;
}
-
-.typeahead__form {
- width: 100% !important;
-}
-
-.typeahead__form-options {
- white-space: pre;
-}
-
-.align {
- left: 50%;
-}
diff --git a/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts b/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts
index 9961cc11b..3cadca741 100644
--- a/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts
+++ b/web/src/app/shared/components/type-ahead/type-ahead.component.spec.ts
@@ -1,74 +1,82 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
-import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
import { TypeAheadComponent } from './type-ahead.component';
-import { BrowserModule, By } from '@angular/platform-browser';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { RouterModule } from '@angular/router';
-import { RouterTestingModule } from '@angular/router/testing';
-import { MatSelectModule } from '@angular/material/select';
-import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { AccessIdsService } from '../../services/access-ids/access-ids.service';
+import { of } from 'rxjs';
+import { NgxsModule } from '@ngxs/store';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
-import { FormsModule } from '@angular/forms';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatTooltipModule } from '@angular/material/tooltip';
-import { EMPTY } from 'rxjs';
-const AccessIdsServiceSpy: Partial = {
- getAccessItems: jest.fn().mockReturnValue(EMPTY),
- searchForAccessId: jest.fn().mockReturnValue(EMPTY)
+const accessIdService: Partial = {
+ searchForAccessId: jest.fn().mockReturnValue(of([{ accessId: 'user-g-1', name: 'Gerda' }]))
};
-describe('TypeAheadComponent', () => {
- let component: TypeAheadComponent;
+describe('TypeAheadComponent with AccessId input', () => {
let fixture: ComponentFixture;
let debugElement: DebugElement;
+ let component: TypeAheadComponent;
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [TypeAheadComponent],
- imports: [
- BrowserModule,
- RouterModule,
- RouterTestingModule,
- HttpClientTestingModule,
- MatSelectModule,
- MatAutocompleteModule,
- MatFormFieldModule,
- MatInputModule,
- MatTooltipModule,
- FormsModule,
- BrowserAnimationsModule
- ],
- providers: [{ provide: AccessIdsService, useValue: AccessIdsServiceSpy }]
- }).compileComponents();
- }));
+ beforeEach(
+ waitForAsync(() => {
+ TestBed.configureTestingModule({
+ imports: [
+ NgxsModule.forRoot([]),
+ MatFormFieldModule,
+ MatInputModule,
+ MatAutocompleteModule,
+ MatTooltipModule,
+ BrowserAnimationsModule,
+ FormsModule,
+ ReactiveFormsModule
+ ],
+ declarations: [TypeAheadComponent],
+ providers: [{ provide: AccessIdsService, useValue: accessIdService }]
+ }).compileComponents();
- beforeEach(() => {
- fixture = TestBed.createComponent(TypeAheadComponent);
- debugElement = fixture.debugElement;
- component = fixture.debugElement.componentInstance;
- fixture.detectChanges();
- });
+ fixture = TestBed.createComponent(TypeAheadComponent);
+ debugElement = fixture.debugElement;
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ })
+ );
it('should create component', () => {
expect(component).toBeTruthy();
});
- it('should change value via the input field', async(() => {
- component.value = 'val_1';
- component.initializeDataSource();
+ it('should fetch name when typing in an access id', fakeAsync(() => {
+ const input = debugElement.nativeElement.querySelector('.type-ahead__input-field');
+ expect(input).toBeTruthy();
+ input.value = 'user-g-1';
+ input.dispatchEvent(new Event('input'));
+ component.accessIdForm.get('accessId').updateValueAndValidity({ emitEvent: true });
+
+ tick();
+ expect(component.name).toBe('Gerda');
+ }));
+
+ it('should emit false when an invalid access id is set', fakeAsync(() => {
+ const emitSpy = jest.spyOn(component.isFormValid, 'emit');
+ component.displayError = true;
+ component.accessIdForm.get('accessId').setValue('invalid-user');
+ component.accessIdForm.get('accessId').updateValueAndValidity({ emitEvent: true });
+
+ tick();
fixture.detectChanges();
- fixture.whenStable().then(() => {
- let input = debugElement.query(By.css('.typeahead__form-input'));
- let el = input.nativeElement;
- expect(el.value).toBe('val_1');
- el.value = 'val_2';
- el.dispatchEvent(new Event('input'));
- expect(component.value).toBe('val_2');
- component.initializeDataSource();
- expect(component.items.length).toBeNull;
- });
+ expect(emitSpy).toHaveBeenCalledWith(false);
+ }));
+
+ it('should emit true when a valid access id is set', fakeAsync(() => {
+ const emitSpy = jest.spyOn(component.isFormValid, 'emit');
+ component.accessIdForm.get('accessId').setValue('user-g-1');
+ component.accessIdForm.get('accessId').updateValueAndValidity({ emitEvent: true });
+
+ tick();
+ fixture.detectChanges();
+ expect(emitSpy).toHaveBeenCalledWith(true);
}));
});
diff --git a/web/src/app/shared/components/type-ahead/type-ahead.component.ts b/web/src/app/shared/components/type-ahead/type-ahead.component.ts
index 0adbb97be..7062e797e 100644
--- a/web/src/app/shared/components/type-ahead/type-ahead.component.ts
+++ b/web/src/app/shared/components/type-ahead/type-ahead.component.ts
@@ -1,127 +1,115 @@
-import { Component, Input, ViewChild, forwardRef, Output, EventEmitter } from '@angular/core';
-import { Observable } from 'rxjs';
-
-import { AccessIdsService } from 'app/shared/services/access-ids/access-ids.service';
-import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
-import { highlight } from 'app/shared/animations/validation.animation';
-import { mergeMap } from 'rxjs/operators';
-import { AccessIdDefinition } from 'app/shared/models/access-id';
+import { Component, EventEmitter, Input, OnDestroy, OnInit, Output, SimpleChanges } from '@angular/core';
+import { AccessIdsService } from '../../services/access-ids/access-ids.service';
+import { Observable, Subject } from 'rxjs';
+import { FormControl, FormGroup } from '@angular/forms';
+import { AccessId } from '../../models/access-id';
+import { take, takeUntil } from 'rxjs/operators';
+import { Select } from '@ngxs/store';
+import { WorkbasketSelectors } from '../../store/workbasket-store/workbasket.selectors';
+import { ButtonAction } from '../../../administration/models/button-action';
@Component({
selector: 'taskana-shared-type-ahead',
templateUrl: './type-ahead.component.html',
- styleUrls: ['./type-ahead.component.scss'],
- animations: [highlight],
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => TypeAheadComponent),
- multi: true
- }
- ]
+ styleUrls: ['./type-ahead.component.scss']
})
-export class TypeAheadComponent implements ControlValueAccessor {
- dataSource: any;
- typing = false;
- isFirst = false;
- items = [];
+export class TypeAheadComponent implements OnInit, OnDestroy {
+ @Input() savedAccessId;
+ @Input() placeHolderMessage;
+ @Input() entityId;
+ @Input() isRequired = false;
+ @Input() isDisabled = false;
+ @Input() displayError = false;
- @Input()
- placeHolderMessage;
+ @Output() accessIdEventEmitter = new EventEmitter();
+ @Output() isFormValid = new EventEmitter();
- @Input()
- validationValue;
+ @Select(WorkbasketSelectors.buttonAction)
+ buttonAction$: Observable;
- @Input()
- displayError;
+ name: string = '';
+ lastSavedAccessId: string = '';
+ filteredAccessIds: AccessId[] = [];
+ destroy$ = new Subject();
+ accessIdForm = new FormGroup({
+ accessId: new FormControl('')
+ });
+ emptyAccessId: AccessId = { accessId: '', name: '' };
- @Input()
- width;
+ constructor(private accessIdService: AccessIdsService) {}
- @Input()
- disable;
-
- @Input()
- isRequired;
-
- @Output()
- selectedItem = new EventEmitter();
-
- @ViewChild('inputTypeAhead')
- typeaheadLoading = false;
- typeaheadMinLength = 3;
- typeaheadWaitMs = 500;
- typeaheadOptionsInScrollableView = 6;
-
- // The internal data model
- private innerValue: any;
-
- // Placeholders for the callbacks which are later provided
- // by the Control Value Accessor
- private onTouchedCallback: () => {};
- private onChangeCallback: (_: any) => {};
-
- // get accessor
- get value(): any {
- return this.innerValue;
- }
-
- // set accessor including call the onchange callback
- set value(v: any) {
- if (v !== this.innerValue) {
- this.innerValue = v;
+ ngOnChanges(changes: SimpleChanges) {
+ // currently needed because when saving, workbasket-details components sends old workbasket which reverts changes in this component
+ if (changes.entityId) {
+ this.setAccessIdFromInput();
}
}
- // From ControlValueAccessor interface
- writeValue(value: any) {
- if (value !== this.innerValue) {
- this.innerValue = value;
- if (this.value) {
- this.isFirst = true;
- }
- this.initializeDataSource();
+ ngOnInit() {
+ if (this.isDisabled) {
+ this.accessIdForm.controls['accessId'].disable();
}
- }
- // From ControlValueAccessor interface
- registerOnChange(fn: any) {
- this.onChangeCallback = fn;
- }
-
- // From ControlValueAccessor interface
- registerOnTouched(fn: any) {
- this.onTouchedCallback = fn;
- }
-
- constructor(private accessIdsService: AccessIdsService) {}
-
- initializeDataSource() {
- this.dataSource = new Observable((observer: any) => {
- observer.next(this.value);
- }).pipe(mergeMap((token: string) => this.getUsersAsObservable(token)));
- this.accessIdsService.searchForAccessId(this.value).subscribe((items) => {
- this.items = items;
- if (this.isFirst) {
- this.dataSource.selected = this.items.find((item) => item.accessId.toLowerCase() === this.value.toLowerCase());
- this.selectedItem.emit(this.dataSource.selected);
+ // currently needed because this component cannot obtain changes of the current workbasket from workbasket-information component
+ this.buttonAction$.pipe(takeUntil(this.destroy$)).subscribe((button) => {
+ if (button == ButtonAction.UNDO) {
+ this.accessIdForm.controls['accessId'].setValue(this.lastSavedAccessId);
}
});
- }
- getUsersAsObservable(accessId: string): Observable {
- return this.accessIdsService.searchForAccessId(accessId);
- }
-
- typeaheadOnSelect(event): void {
- if (event) {
- if (this.items.length > 0) {
- this.dataSource.selected = this.items.find((item) => item.accessId.toLowerCase() === this.value.toLowerCase());
+ this.accessIdForm.controls['accessId'].valueChanges.pipe(takeUntil(this.destroy$)).subscribe(() => {
+ const value = this.accessIdForm.controls['accessId'].value;
+ if (value === '') {
+ this.handleEmptyAccessId();
+ return;
}
- this.selectedItem.emit(this.dataSource.selected);
+ this.searchForAccessId(value);
+ });
+
+ this.setAccessIdFromInput();
+ }
+
+ handleEmptyAccessId() {
+ this.name = '';
+ this.isFormValid.emit(!this.isRequired);
+ if (this.placeHolderMessage !== 'Search for AccessId') {
+ this.accessIdEventEmitter.emit(this.emptyAccessId);
}
- if (document.activeElement instanceof HTMLElement) {
- document.activeElement.blur();
+ if (this.isRequired) {
+ this.accessIdForm.controls['accessId'].setErrors({ incorrect: true });
}
}
+
+ searchForAccessId(value: string) {
+ this.accessIdService
+ .searchForAccessId(value)
+ .pipe(take(1))
+ .subscribe((accessIds) => {
+ this.filteredAccessIds = accessIds;
+ const accessId = accessIds.find((accessId) => accessId.accessId === value);
+
+ if (typeof accessId !== 'undefined') {
+ this.name = accessId?.name;
+ this.isFormValid.emit(true);
+ this.accessIdEventEmitter.emit(accessId);
+ } else if (this.displayError) {
+ this.isFormValid.emit(false);
+ this.accessIdEventEmitter.emit(this.emptyAccessId);
+ this.accessIdForm.controls['accessId'].setErrors({ incorrect: true });
+ }
+ });
+ }
+
+ setAccessIdFromInput() {
+ const accessId = this.savedAccessId?.value;
+ const access = accessId?.accessId || accessId?.accessId == '' ? accessId.accessId : this.savedAccessId || '';
+ this.accessIdForm.controls['accessId'].setValue(access);
+ this.lastSavedAccessId = access;
+ this.name = accessId?.accessName || '';
+ }
+
+ ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
}
diff --git a/web/src/app/shared/components/type-ahead/type-ahead.mock.component.ts b/web/src/app/shared/components/type-ahead/type-ahead.mock.component.ts
deleted file mode 100644
index ad5386a8b..000000000
--- a/web/src/app/shared/components/type-ahead/type-ahead.mock.component.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Component, forwardRef, Input } from '@angular/core';
-import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
-
-@Component({
- selector: 'taskana-shared-type-ahead',
- template: 'dummydetail',
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- multi: true,
- useExisting: forwardRef(() => TaskanaTypeAheadMockComponent)
- }
- ]
-})
-export class TaskanaTypeAheadMockComponent implements ControlValueAccessor {
- @Input()
- placeHolderMessage;
-
- @Input()
- validationValue;
-
- writeValue(obj: any): void {}
-
- registerOnChange(fn: any): void {}
-
- registerOnTouched(fn: any): void {}
-
- setDisabledState?(isDisabled: boolean): void {}
-}
diff --git a/web/src/app/shared/models/access-id.ts b/web/src/app/shared/models/access-id.ts
index 75c026fd0..1ead44f11 100644
--- a/web/src/app/shared/models/access-id.ts
+++ b/web/src/app/shared/models/access-id.ts
@@ -1,3 +1,4 @@
-export class AccessIdDefinition {
- constructor(public accessId?: string, public name?: string) {}
+export interface AccessId {
+ accessId?: string;
+ name?: string;
}
diff --git a/web/src/app/shared/services/access-ids/access-ids.service.ts b/web/src/app/shared/services/access-ids/access-ids.service.ts
index 9b7582a6a..c5e97c204 100644
--- a/web/src/app/shared/services/access-ids/access-ids.service.ts
+++ b/web/src/app/shared/services/access-ids/access-ids.service.ts
@@ -1,7 +1,7 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from 'environments/environment';
-import { AccessIdDefinition } from 'app/shared/models/access-id';
+import { AccessId } from 'app/shared/models/access-id';
import { Observable, of } from 'rxjs';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { Sorting, WorkbasketAccessItemQuerySortParameter } from 'app/shared/models/sorting';
@@ -20,18 +20,18 @@ export class AccessIdsService {
return this.startupService.getTaskanaRestUrl() + '/v1/access-ids';
}
- searchForAccessId(accessId: string): Observable {
+ searchForAccessId(accessId: string): Observable {
if (!accessId || accessId.length < 3) {
return of([]);
}
- return this.httpClient.get(`${this.url}?search-for=${accessId}`);
+ return this.httpClient.get(`${this.url}?search-for=${accessId}`);
}
- getGroupsByAccessId(accessId: string): Observable {
+ getGroupsByAccessId(accessId: string): Observable {
if (!accessId || accessId.length < 3) {
return of([]);
}
- return this.httpClient.get(`${this.url}/groups?access-id=${accessId}`);
+ return this.httpClient.get(`${this.url}/groups?access-id=${accessId}`);
}
getAccessItems(
diff --git a/web/src/app/shared/services/obtain-message/message-by-error-code.ts b/web/src/app/shared/services/obtain-message/message-by-error-code.ts
index 028403a25..3237e875d 100644
--- a/web/src/app/shared/services/obtain-message/message-by-error-code.ts
+++ b/web/src/app/shared/services/obtain-message/message-by-error-code.ts
@@ -29,6 +29,7 @@ export const messageByErrorCode = {
CLASSIFICATION_WITH_ID_NOT_FOUND: 'Classification with id {classificationId} cannot be found',
CLASSIFICATION_COPY_NOT_CREATED: 'Cannot copy a not created Classification',
+ WORKBASKET_SAVE: 'The Workbasket cannot be saved since the Workbasket Information contains invalid values',
WORKBASKET_WITH_ID_NOT_FOUND: 'Workbasket with id {workbasketId} cannot be found',
WORKBASKET_WITH_KEY_NOT_FOUND: 'Workbasket with key {workbasketKey} cannot be found in domain {domain}',
WORKBASKET_ALREADY_EXISTS:
diff --git a/web/src/app/shared/shared.module.ts b/web/src/app/shared/shared.module.ts
index c5a2283fb..c52cb7d70 100644
--- a/web/src/app/shared/shared.module.ts
+++ b/web/src/app/shared/shared.module.ts
@@ -17,7 +17,6 @@ import { AccordionModule } from 'ngx-bootstrap/accordion';
import { SpinnerComponent } from 'app/shared/components/spinner/spinner.component';
import { MasterAndDetailComponent } from 'app/shared/components/master-and-detail/master-and-detail.component';
import { TaskanaTreeComponent } from 'app/administration/components/tree/tree.component';
-import { TypeAheadComponent } from 'app/shared/components/type-ahead/type-ahead.component';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { FieldErrorDisplayComponent } from 'app/shared/components/field-error-display/field-error-display.component';
import { MatDialogModule } from '@angular/material/dialog';
@@ -26,6 +25,7 @@ import { MatRadioModule } from '@angular/material/radio';
import { SortComponent } from './components/sort/sort.component';
import { PaginationComponent } from './components/pagination/pagination.component';
import { ProgressSpinnerComponent } from './components/progress-spinner/progress-spinner.component';
+import { TypeAheadComponent } from './components/type-ahead/type-ahead.component';
/**
* Pipes
diff --git a/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts b/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts
index 46eb19d66..ff367ae92 100644
--- a/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts
+++ b/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts
@@ -1,11 +1,11 @@
-import { AccessIdDefinition } from '../../models/access-id';
+import { AccessId } from '../../models/access-id';
import { Sorting, WorkbasketAccessItemQuerySortParameter } from '../../models/sorting';
import { WorkbasketAccessItemQueryFilterParameter } from '../../models/workbasket-access-item-query-filter-parameter';
import { QueryPagingParameter } from '../../models/query-paging-parameter';
export class SelectAccessId {
static readonly type = '[Access Items Management] Select access ID';
- constructor(public accessIdDefinition: AccessIdDefinition) {}
+ constructor(public accessIdDefinition: AccessId) {}
}
export class GetGroupsByAccessId {
diff --git a/web/src/app/shared/store/access-items-management-store/access-items-management.selector.ts b/web/src/app/shared/store/access-items-management-store/access-items-management.selector.ts
index 3cb3a742f..6d87d727c 100644
--- a/web/src/app/shared/store/access-items-management-store/access-items-management.selector.ts
+++ b/web/src/app/shared/store/access-items-management-store/access-items-management.selector.ts
@@ -1,10 +1,10 @@
import { Selector } from '@ngxs/store';
import { AccessItemsManagementState, AccessItemsManagementStateModel } from './access-items-management.state';
-import { AccessIdDefinition } from '../../models/access-id';
+import { AccessId } from '../../models/access-id';
export class AccessItemsManagementSelector {
@Selector([AccessItemsManagementState])
- static groups(state: AccessItemsManagementStateModel): AccessIdDefinition[] {
+ static groups(state: AccessItemsManagementStateModel): AccessId[] {
return state.groups;
}
}
diff --git a/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts b/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts
index 64674ee4a..300b12d1a 100644
--- a/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts
+++ b/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts
@@ -8,7 +8,7 @@ import {
import { Observable, of } from 'rxjs';
import { AccessIdsService } from '../../services/access-ids/access-ids.service';
import { take, tap } from 'rxjs/operators';
-import { AccessIdDefinition } from '../../models/access-id';
+import { AccessId } from '../../models/access-id';
import { NotificationService } from '../../services/notifications/notification.service';
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
import { RequestInProgressService } from '../../services/request-in-progress/request-in-progress.service';
@@ -44,7 +44,7 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap {
return this.accessIdsService.getGroupsByAccessId(action.accessId).pipe(
take(1),
tap(
- (groups: AccessIdDefinition[]) => {
+ (groups: AccessId[]) => {
ctx.patchState({
groups
});
@@ -106,6 +106,6 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap {
export interface AccessItemsManagementStateModel {
accessItemsResource: WorkbasketAccessItemsRepresentation;
- selectedAccessId: AccessIdDefinition;
- groups: AccessIdDefinition[];
+ selectedAccessId: AccessId;
+ groups: AccessId[];
}
diff --git a/web/src/app/shared/store/workbasket-store/workbasket.state.ts b/web/src/app/shared/store/workbasket-store/workbasket.state.ts
index 19eec12ed..59a118e9f 100644
--- a/web/src/app/shared/store/workbasket-store/workbasket.state.ts
+++ b/web/src/app/shared/store/workbasket-store/workbasket.state.ts
@@ -241,6 +241,7 @@ export class WorkbasketState implements NgxsAfterBootstrap {
const date = TaskanaDate.getDate();
emptyWorkbasket.created = date;
emptyWorkbasket.modified = date;
+ emptyWorkbasket.owner = '';
const accessItems = { accessItems: [], _links: {} };
const distributionTargets = { distributionTargets: [], _links: {} };
diff --git a/web/src/app/workplace/components/task-information/task-information.component.html b/web/src/app/workplace/components/task-information/task-information.component.html
index 3f549057c..195d677ad 100644
--- a/web/src/app/workplace/components/task-information/task-information.component.html
+++ b/web/src/app/workplace/components/task-information/task-information.component.html
@@ -77,10 +77,15 @@
-
+
diff --git a/web/src/app/workplace/components/task-information/task-information.component.ts b/web/src/app/workplace/components/task-information/task-information.component.ts
index 31cf7a99e..2d47a27bc 100644
--- a/web/src/app/workplace/components/task-information/task-information.component.ts
+++ b/web/src/app/workplace/components/task-information/task-information.component.ts
@@ -12,7 +12,6 @@ import {
import { Task } from 'app/workplace/models/task';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { NgForm } from '@angular/forms';
-import { DomainService } from 'app/shared/services/domain/domain.service';
import { Select } from '@ngxs/store';
import { Observable, Subject } from 'rxjs';
import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
@@ -20,7 +19,7 @@ import { ClassificationsService } from '../../../shared/services/classifications
import { Classification } from '../../../shared/models/classification';
import { TasksCustomisation } from '../../../shared/models/customisation';
import { takeUntil } from 'rxjs/operators';
-import { AccessIdDefinition } from '../../../shared/models/access-id';
+import { AccessId } from '../../../shared/models/access-id';
@Component({
selector: 'taskana-task-information',
@@ -45,6 +44,7 @@ export class TaskInformationComponent implements OnInit, OnChanges, OnDestroy {
requestInProgress = false;
classifications: Classification[];
isClassificationEmpty: boolean;
+ isOwnerValid: boolean = true;
readonly lengthError = 'You have reached the maximum length';
inputOverflowMap = new Map();
@@ -55,8 +55,7 @@ export class TaskInformationComponent implements OnInit, OnChanges, OnDestroy {
constructor(
private classificationService: ClassificationsService,
- private formsValidatorService: FormsValidatorService,
- private domainService: DomainService
+ private formsValidatorService: FormsValidatorService
) {}
ngOnInit() {
@@ -102,7 +101,7 @@ export class TaskInformationComponent implements OnInit, OnChanges, OnDestroy {
this.isClassificationEmpty = typeof this.task.classificationSummary === 'undefined';
this.formsValidatorService.formSubmitAttempt = true;
this.formsValidatorService.validateFormInformation(this.taskForm, this.toggleValidationMap).then((value) => {
- if (value && !this.isClassificationEmpty) {
+ if (value && !this.isClassificationEmpty && this.isOwnerValid) {
this.formValid.emit(true);
}
});
@@ -120,7 +119,7 @@ export class TaskInformationComponent implements OnInit, OnChanges, OnDestroy {
});
}
- onSelectedOwner(owner: AccessIdDefinition) {
+ onSelectedOwner(owner: AccessId) {
if (owner?.accessId) {
this.task.owner = owner.accessId;
}