diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.html b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.html
index 8923055af..f81bd6022 100644
--- a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.html
+++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.html
@@ -102,6 +102,20 @@
+
+ 0">
+ {{'ANNOTATION-DIALOG.ANNOTATION-STATUS.TITLE' | translate}}
+
+ {{status.label}}
+
+ {{getAnnotationStatusFormControl(annotationStatusFormGroup.get('annotationsStatusArray'), getParentAnnotation(thread).id).get('statusId').getError('backendError').message}}
+ {{'GENERAL.VALIDATION.REQUIRED' | translate}}
+
+
+
diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts
index 80fb51011..967e691d7 100644
--- a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts
+++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.component.ts
@@ -1,9 +1,12 @@
import { Component, Inject } from '@angular/core';
-import { FormBuilder, FormControl, UntypedFormGroup, Validators } from '@angular/forms';
+import { FormBuilder, FormControl, UntypedFormArray, UntypedFormGroup, Validators } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';
+import { AnnotationStatus, AnnotationStatusPersist } from '@annotation-service/core/model/annotation-status.model';
import { Annotation, AnnotationPersist } from '@annotation-service/core/model/annotation.model';
+import { Status } from '@annotation-service/core/model/status.model';
import { AnnotationLookup } from '@annotation-service/core/query/annotation.lookup';
+import { StatusLookup } from '@annotation-service/core/query/status.lookup';
import { AnnotationService } from '@annotation-service/services/http/annotation.service';
import { AnnotationProtectionType } from '@app/core/common/enum/annotation-protection-type.enum';
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
@@ -14,8 +17,12 @@ import { BaseComponent } from '@common/base/base.component';
import { FormService } from '@common/forms/form-service';
import { Guid } from '@common/types/guid';
import { TranslateService } from '@ngx-translate/core';
+import { IsActive } from '@notification-service/core/enum/is-active.enum';
import { takeUntil } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
+import { AnnotationStatusArrayEditorModel } from './annotation-status-editor.model';
+import { StatusService } from '@annotation-service/services/http/status.service';
+import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum';
@Component({
selector: 'app-annotation-dialog',
@@ -43,6 +50,9 @@ export class AnnotationDialogComponent extends BaseComponent {
threadReplyTextsFG: Array;
threadFormGroup: UntypedFormGroup;
private formBuilder: FormBuilder = new FormBuilder();
+ public annotationStatusFormGroup: UntypedFormGroup;
+ public listingStatuses: Status[] = [];
+ public dmpUsersMentionNames: string[] = [];
constructor(
@@ -55,12 +65,14 @@ export class AnnotationDialogComponent extends BaseComponent {
private annotationService: AnnotationService,
private formService: FormService,
public enumUtils: EnumUtils,
+ private statusService: StatusService,
protected routerUtils: RouterUtilsService,
) {
super();
this.entityId = data.entityId;
this.anchor = data.anchor;
this.entityType = data.entityType;
+ this.dmpUsersMentionNames = data.dmpUsers.map(x => x.user.name);
dialogRef.backdropClick().pipe(takeUntil(this._destroyed)).subscribe(() => dialogRef.close(this.changesMade));
}
@@ -72,6 +84,7 @@ export class AnnotationDialogComponent extends BaseComponent {
if (this.entityId != null) {
this.loadThreads();
}
+ this.getStatuses();
}
createThread() {
@@ -139,6 +152,13 @@ export class AnnotationDialogComponent extends BaseComponent {
nameof(x => x.author.name),
nameof(x => x.payload),
nameof(x => x.protectionType),
+ [nameof(x => x.annotationStatuses), nameof(x => x.id)].join('.'),
+ [nameof(x => x.annotationStatuses), nameof(x => x.annotation), nameof(x => x.id)].join('.'),
+ [nameof(x => x.annotationStatuses), nameof(x => x.status), nameof(x => x.id)].join('.'),
+ [nameof(x => x.annotationStatuses), nameof(x => x.createdAt)].join('.'),
+ [nameof(x => x.annotationStatuses), nameof(x => x.updatedAt)].join('.'),
+ [nameof(x => x.annotationStatuses), nameof(x => x.isActive)].join('.'),
+ [nameof(x => x.annotationStatuses), nameof(x => x.hash)].join('.'),
]
};
@@ -160,6 +180,8 @@ export class AnnotationDialogComponent extends BaseComponent {
this.parentAnnotationsPerThread[element.threadId.toString()] = data.items.filter(x => x.threadId === element.threadId && x.id === element.id)[0];
this.threads.add(element.threadId);
});
+ // create annotation status array to handle each annotation
+ this.annotationStatusFormGroup = new AnnotationStatusArrayEditorModel().fromModel(this.comments, this.listingStatuses).buildForm();
},
error => this.onCallbackError(error),
);
@@ -236,4 +258,76 @@ export class AnnotationDialogComponent extends BaseComponent {
SnackBarNotificationLevel.Success
);
}
+
+ // status
+
+ private getStatuses(){
+ const lookup: StatusLookup = new StatusLookup();
+ lookup.metadata = { countAll: true };
+ lookup.page = { size: 100, offset: 0 };
+ lookup.isActive = [IsActive.Active];
+ lookup.project = {
+ fields: [
+ nameof(x => x.id),
+ nameof(x => x.label),
+ nameof(x => x.internalStatus)
+ ]
+ };
+
+ lookup.order = { items: [nameof(x => x.label)] };
+
+ return this.statusService.query(lookup).pipe(takeUntil(this._destroyed))
+ .subscribe(result => {
+ if (result && result.items?.length > 0) this.listingStatuses.push(...result.items);
+ });
+ }
+
+ private getAnnotationStatusControl(formArray: UntypedFormArray, annotationId: Guid){
+ const index = formArray.controls.findIndex(x => x.get('annotationId')?.value == annotationId);
+ if (index < 0) return null;
+ return formArray.at(index);
+ }
+
+ getAnnotationStatusFormControl(formArray: UntypedFormArray, annotationId: Guid) {
+ const control = this.getAnnotationStatusControl(formArray, annotationId);
+ if (control == null) return;
+ return control;
+ }
+
+ persistAnnotationStatus(formArray: UntypedFormArray, annotationId: Guid){
+ const control = this.getAnnotationStatusControl(formArray, annotationId);
+ if (control && control.valid){
+ const formData = this.formService.getValue(control.value) as AnnotationStatusPersist;
+ this.annotationService.persistStatus(formData)
+ .pipe(takeUntil(this._destroyed)).subscribe(
+ complete => this.onCallbackAnnotationStatusSuccess(),
+ error => this.onCallbackError(error)
+ );
+ }
+ }
+
+ changeIcon(formArray: UntypedFormArray, annotationId: Guid){
+ const control = this.getAnnotationStatusControl(formArray, annotationId);
+ if (control == null) return;
+ const status = this.listingStatuses.find(x => x.id === control.get('statusId').value) || null;
+ if (status && status.internalStatus != null && status.internalStatus == InternalStatus.Resolved){
+ control.get('isCheckIcon').patchValue(true);
+ } else {
+ control.get('isCheckIcon').patchValue(false);
+ }
+ }
+
+ canSaveAnnotationStatus(formArray: UntypedFormArray, annotationId: Guid): boolean{
+ const control = this.getAnnotationStatusControl(formArray, annotationId);
+ if (control == null) return false;
+ return control.valid;
+ }
+
+ private onCallbackAnnotationStatusSuccess() {
+ this.uiNotificationService.snackBarNotification(this.language.instant('ANNOTATION-DIALOG.ANNOTATION-STATUS.SUCCESS'), SnackBarNotificationLevel.Success);
+ this.refreshAnnotations();
+ }
+
+
+
}
diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts
index 92b80ffb6..b2e256cde 100644
--- a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts
+++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-dialog.module.ts
@@ -3,12 +3,14 @@ import { FormattingModule } from '@app/core/formatting.module';
import { CommonFormsModule } from '@common/forms/common-forms.module';
import { CommonUiModule } from '@common/ui/common-ui.module';
import { AnnotationDialogComponent } from './annotation-dialog.component';
+import { AutoCompleteModule } from '@app/library/auto-complete/auto-complete.module';
@NgModule({
imports: [
CommonUiModule,
CommonFormsModule,
FormattingModule,
+ AutoCompleteModule,
],
declarations: [
AnnotationDialogComponent,
diff --git a/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-status-editor.model.ts b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-status-editor.model.ts
new file mode 100644
index 000000000..86ba12190
--- /dev/null
+++ b/dmp-frontend/src/app/ui/annotations/annotation-dialog-component/annotation-status-editor.model.ts
@@ -0,0 +1,109 @@
+import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
+import { InternalStatus } from '@annotation-service/core/enum/internal-status.enum';
+import { AnnotationStatus, AnnotationStatusPersist } from '@annotation-service/core/model/annotation-status.model';
+import { Annotation } from '@annotation-service/core/model/annotation.model';
+import { Status } from '@annotation-service/core/model/status.model';
+import { IsActive } from '@app/core/common/enum/is-active.enum';
+import { BaseEditorModel } from '@common/base/base-form-editor-model';
+import { BackendErrorValidator } from '@common/forms/validation/custom-validator';
+import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
+import { Validation, ValidationContext } from '@common/forms/validation/validation-context';
+import { Guid } from '@common/types/guid';
+
+export class AnnotationStatusArrayEditorModel {
+ annotationsStatusArray: AnnotationStatusEditorModel [] = [];
+
+ public validationErrorModel: ValidationErrorModel = new ValidationErrorModel();
+ protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
+
+ constructor() { }
+
+ public fromModel(item: Annotation[], listingStatuses: Status[]): AnnotationStatusArrayEditorModel {
+ if (item) {
+ item.forEach(annotation => {
+ if (annotation.annotationStatuses){
+ annotation.annotationStatuses = annotation.annotationStatuses.filter(x => x.isActive === IsActive.Active);
+ annotation.annotationStatuses.forEach( x => {
+ this.annotationsStatusArray.push(new AnnotationStatusEditorModel(this.validationErrorModel).fromModel(x, listingStatuses))
+ })
+ } else {
+ const tempAnnotationStatus = {
+ annotation: annotation,
+ status: null
+ } as AnnotationStatus;
+ this.annotationsStatusArray.push(new AnnotationStatusEditorModel(this.validationErrorModel).fromModel(tempAnnotationStatus, listingStatuses))
+ }
+
+ })
+ }
+ return this;
+ }
+
+ buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup {
+ if (context == null) { context = this.createValidationContext(); }
+
+ return this.formBuilder.group({
+ annotationsStatusArray: this.formBuilder.array(
+ (this.annotationsStatusArray ?? []).map(
+ (item, index) => item.buildForm()
+ ), context.getValidation('annotationsStatusArray').validators
+ ),
+ });
+ }
+
+ createValidationContext(): ValidationContext {
+ const baseContext: ValidationContext = new ValidationContext();
+ const baseValidationArray: Validation[] = new Array();
+ baseValidationArray.push({ key: 'annotationsStatusArray', validators: [BackendErrorValidator(this.validationErrorModel, 'annotationsStatusArray')] });
+
+ baseContext.validation = baseValidationArray;
+ return baseContext;
+ }
+}
+
+export class AnnotationStatusEditorModel implements AnnotationStatusPersist {
+ annotationId: Guid;
+ statusId: Guid;
+ isCheckIcon: boolean = false;
+
+ protected formBuilder: UntypedFormBuilder = new UntypedFormBuilder();
+
+ constructor(public validationErrorModel: ValidationErrorModel = new ValidationErrorModel()) {}
+
+ public fromModel(item: AnnotationStatus, listingStatuses: Status[]): AnnotationStatusEditorModel {
+ if (item) {
+ this.annotationId = item.annotation?.id;
+ this.statusId = item.status?.id;
+ if (this.statusId) {
+ const status = listingStatuses.find(x => x.id === this.statusId) || null;
+ if (status && status.internalStatus != null && status.internalStatus == InternalStatus.Resolved){
+ this.isCheckIcon = true;
+ } else {
+ this.isCheckIcon = false;
+ }
+ }
+ }
+ return this;
+ }
+
+ buildForm(context: ValidationContext = null, disabled: boolean = false): UntypedFormGroup {
+ if (context == null) { context = this.createValidationContext(); }
+
+ return this.formBuilder.group({
+ annotationId: [{ value: this.annotationId, disabled: disabled }, context.getValidation('annotationId').validators],
+ statusId: [{ value: this.statusId, disabled: disabled }, context.getValidation('statusId').validators],
+ isCheckIcon: [{ value: this.isCheckIcon, disabled: disabled }, context.getValidation('isCheckIcon').validators],
+ });
+ }
+
+ createValidationContext(): ValidationContext {
+ const baseContext: ValidationContext = new ValidationContext();
+ const baseValidationArray: Validation[] = new Array();
+ baseValidationArray.push({ key: 'annotationId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'label')] });
+ baseValidationArray.push({ key: 'statusId', validators: [Validators.required, BackendErrorValidator(this.validationErrorModel, 'internalStatus')] });
+ baseValidationArray.push({ key: 'isCheckIcon', validators: [] });
+
+ baseContext.validation = baseValidationArray;
+ return baseContext;
+ }
+}
diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.component.html b/dmp-frontend/src/app/ui/description/editor/description-editor.component.html
index a072f3dbd..4f05f9a68 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-editor.component.html
+++ b/dmp-frontend/src/app/ui/description/editor/description-editor.component.html
@@ -178,6 +178,7 @@
[validationErrorModel]="editorModel.validationErrorModel"
[isNew]="isNew || isCopy"
[canReview]="canReview"
+ [dmpUsers]="item?.dmp?.dmpUsers ?? []"
>
diff --git a/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts b/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts
index 4fc6c4da9..f0acdfcb8 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts
+++ b/dmp-frontend/src/app/ui/description/editor/description-editor.component.ts
@@ -210,6 +210,7 @@ export class DescriptionEditorComponent extends BaseEditor x.isActive === IsActive.Active);
this.item = data;
this.initialTemplateId = data?.descriptionTemplate?.id?.toString();
if (data && data.dmpDescriptionTemplate?.sectionId && data.dmp?.blueprint?.definition?.sections?.length > 0 && data.dmp?.descriptions?.length > 0) {
diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts
index a4822ec3b..c48cb95a2 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts
+++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-field-set/form-field-set.component.ts
@@ -20,6 +20,7 @@ import { DescriptionTemplateFieldType } from '@app/core/common/enum/description-
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
import { RouterUtilsService } from '@app/core/services/router/router-utils.service';
import { TranslateService } from '@ngx-translate/core';
+import { DmpUser } from '@app/core/model/dmp/dmp';
@Component({
selector: 'app-description-form-field-set',
@@ -34,6 +35,7 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
@Input() hideAnnotations: boolean = false;
@Input() canReview: boolean = false;
@Input() numbering: string;
+ @Input() dmpUsers: DmpUser[] = [];
get isMultiplicityEnabled() {
return this.fieldSet.hasMultiplicity && this.fieldSet.multiplicity != null;
@@ -183,7 +185,8 @@ export class DescriptionFormFieldSetComponent extends BaseComponent {
data: {
entityId: this.descriptionId,
anchor: fieldSetId,
- entityType: AnnotationEntityType.Description
+ entityType: AnnotationEntityType.Description,
+ dmpUsers: this.dmpUsers
}
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(changesMade => {
diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html
index 2360e1ad2..a22461f6c 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html
+++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.html
@@ -25,6 +25,7 @@
[isChild]="false"
[hideAnnotations]="isNew"
[canReview]="canReview"
+ [dmpUsers]="dmpUsers"
>
diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts
index a88fb4631..e0814b3a1 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts
+++ b/dmp-frontend/src/app/ui/description/editor/description-form/components/form-section/form-section.component.ts
@@ -8,6 +8,7 @@ import { ToCEntryType } from '../../../table-of-contents/models/toc-entry-type.e
import { LinkToScroll } from '../../../table-of-contents/table-of-contents.component';
import { VisibilityRulesService } from '../../visibility-rules/visibility-rules.service';
import { Guid } from '@common/types/guid';
+import { DmpUser } from '@app/core/model/dmp/dmp';
@Component({
@@ -25,6 +26,7 @@ export class DescriptionFormSectionComponent extends BaseComponent implements On
@Input() visibilityRulesService: VisibilityRulesService;
@Input() path: string;
@Input() descriptionId: Guid;
+ @Input() dmpUsers: DmpUser[] = [];
// @Input() descriptionTemplateId: String;
diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html
index 6dc8589bc..f29f4c18e 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html
+++ b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.html
@@ -15,7 +15,7 @@
diff --git a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts
index cb4399975..828647825 100644
--- a/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts
+++ b/dmp-frontend/src/app/ui/description/editor/description-form/description-form.component.ts
@@ -8,6 +8,7 @@ import { Guid } from '@common/types/guid';
import { LinkToScroll } from '../table-of-contents/table-of-contents.component';
import { DescriptionFormAnnotationService } from './description-form-annotation.service';
import { VisibilityRulesService } from './visibility-rules/visibility-rules.service';
+import { DmpUser } from '@app/core/model/dmp/dmp';
@Component({
selector: 'app-description-form',
@@ -26,6 +27,7 @@ export class DescriptionFormComponent extends BaseComponent implements OnInit, O
@Input() datasetDescription: String;
@Input() linkToScroll: LinkToScroll;
@Input() validationErrorModel: ValidationErrorModel;
+ @Input() dmpUsers: DmpUser[] = [];
@Output() formChanged: EventEmitter = new EventEmitter();
diff --git a/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts b/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts
index 95096a3be..e50bb0028 100644
--- a/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts
+++ b/dmp-frontend/src/app/ui/description/editor/resolvers/description-editor-entity.resolver.ts
@@ -5,7 +5,7 @@ import { AppPermission } from '@app/core/common/enum/permission.enum';
import { DescriptionTemplate } from '@app/core/model/description-template/description-template';
import { Description, DescriptionExternalIdentifier, DescriptionField, DescriptionPropertyDefinition, DescriptionPropertyDefinitionFieldSet, DescriptionPropertyDefinitionFieldSetItem, DescriptionReference, DescriptionReferenceData, DescriptionTag } from '@app/core/model/description/description';
import { DescriptionTemplatesInSection, DmpBlueprint, DmpBlueprintDefinition, DmpBlueprintDefinitionSection } from '@app/core/model/dmp-blueprint/dmp-blueprint';
-import { Dmp, DmpDescriptionTemplate } from '@app/core/model/dmp/dmp';
+import { Dmp, DmpDescriptionTemplate, DmpUser } from '@app/core/model/dmp/dmp';
import { PrefillingSource } from '@app/core/model/prefilling-source/prefilling-source';
import { ReferenceType } from '@app/core/model/reference-type/reference-type';
import { Reference } from '@app/core/model/reference/reference';
@@ -149,6 +149,12 @@ export class DescriptionEditorEntityResolver extends BaseEditorResolver {
(prefix ? prefix + '.' : '') + [nameof(x => x.descriptions), nameof(x => x.dmpDescriptionTemplate), nameof(x => x.descriptionTemplateGroupId)].join('.'),
(prefix ? prefix + '.' : '') + [nameof(x => x.descriptions), nameof(x => x.dmpDescriptionTemplate), nameof(x => x.sectionId)].join('.'),
(prefix ? prefix + '.' : '') + [nameof(x => x.descriptions), nameof(x => x.dmpDescriptionTemplate), nameof(x => x.isActive)].join('.'),
+ (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.id)].join('.'),
+ (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.sectionId)].join('.'),
+ (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.user.id)].join('.'),
+ (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.user.name)].join('.'),
+ (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.role)].join('.'),
+ (prefix ? prefix + '.' : '') + [nameof(x => x.dmpUsers), nameof(x => x.isActive)].join('.'),
]
}