diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskResource.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskResource.java index 43fe5ea88..3bf9c687a 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskResource.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskResource.java @@ -3,7 +3,6 @@ package pro.taskana.rest.resource; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; import org.springframework.hateoas.ResourceSupport; @@ -37,8 +36,8 @@ public class TaskResource extends ResourceSupport { private boolean isRead; private boolean isTransferred; // All objects have to be serializable - private Map customAttributes = Collections.emptyMap(); - private Map callbackInfo = Collections.emptyMap(); + private List customAttributes = Collections.emptyList(); + private List callbackInfo = Collections.emptyList(); private List attachments = new ArrayList<>(); private String custom1; private String custom2; @@ -225,19 +224,19 @@ public class TaskResource extends ResourceSupport { this.isTransferred = isTransferred; } - public Map getCustomAttributes() { + public List getCustomAttributes() { return customAttributes; } - public void setCustomAttributes(Map customAttributes) { + public void setCustomAttributes(List customAttributes) { this.customAttributes = customAttributes; } - public Map getCallbackInfo() { + public List getCallbackInfo() { return callbackInfo; } - public void setCallbackInfo(Map callbackInfo) { + public void setCallbackInfo(List callbackInfo) { this.callbackInfo = callbackInfo; } @@ -376,4 +375,33 @@ public class TaskResource extends ResourceSupport { public void setCustom16(String custom16) { this.custom16 = custom16; } + + /** + * A CustomAttribute is a user customized attribute which is saved as a Map and can be retreived from + * either {@link pro.taskana.Task#getCustomAttributes()} or {@link pro.taskana.Task#getCallbackInfo()}. + */ + public static class CustomAttribute { + + private final String key; + private final String value; + + @SuppressWarnings("unused") + public CustomAttribute() { + this(null, null); + //necessary for jackson. + } + + public CustomAttribute(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + } } diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/assembler/TaskResourceAssembler.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/assembler/TaskResourceAssembler.java index 7c094a6b5..654744184 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/assembler/TaskResourceAssembler.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/assembler/TaskResourceAssembler.java @@ -1,6 +1,8 @@ package pro.taskana.rest.resource.assembler; import java.time.Instant; +import java.util.Objects; +import java.util.stream.Collectors; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -62,6 +64,12 @@ public class TaskResourceAssembler classificationAssembler.toResource(task.getClassificationSummary())); resource.setWorkbasketSummaryResource(workbasketAssembler.toResource(task.getWorkbasketSummary())); resource.setAttachments(attachmentAssembler.toResources(task.getAttachments())); + resource.setCustomAttributes(task.getCustomAttributes().entrySet().stream() + .map(e -> new TaskResource.CustomAttribute(e.getKey(), e.getValue())) + .collect(Collectors.toList())); + resource.setCallbackInfo(task.getCallbackInfo().entrySet().stream() + .map(e -> new TaskResource.CustomAttribute(e.getKey(), e.getValue())) + .collect(Collectors.toList())); try { if (task.getCustomAttribute("1") != null) { resource.setCustom1(task.getCustomAttribute("1")); @@ -141,6 +149,12 @@ public class TaskResourceAssembler task.setClassificationSummary(classificationAssembler.toModel(resource.getClassificationSummaryResource())); task.setWorkbasketSummary(workbasketAssembler.toModel(resource.getWorkbasketSummaryResource())); task.setAttachments(attachmentAssembler.toModel(resource.getAttachments())); + task.setCustomAttributes(resource.getCustomAttributes().stream() + .filter(e -> Objects.nonNull(e.getKey()) && !e.getKey().isEmpty()) + .collect(Collectors.toMap(TaskResource.CustomAttribute::getKey, TaskResource.CustomAttribute::getValue))); + task.setCallbackInfo(resource.getCallbackInfo().stream() + .filter(e -> Objects.nonNull(e.getKey()) && !e.getKey().isEmpty()) + .collect(Collectors.toMap(TaskResource.CustomAttribute::getKey, TaskResource.CustomAttribute::getValue))); if (resource.getCustom1() != null) { task.setCustom1(resource.getCustom1()); } diff --git a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html index a1d348ad4..4aabd8da3 100644 --- a/web/src/app/administration/workbasket/details/information/workbasket-information.component.html +++ b/web/src/app/administration/workbasket/details/information/workbasket-information.component.html @@ -8,7 +8,7 @@ - -
  • - - - -
  • + - \ No newline at end of file + diff --git a/web/src/app/shared/sort/sort.component.scss b/web/src/app/shared/sort/sort.component.scss index 7fe240830..3b3c0ca5c 100644 --- a/web/src/app/shared/sort/sort.component.scss +++ b/web/src/app/shared/sort/sort.component.scss @@ -1,3 +1,7 @@ +.dropdown-fix { + display: inline-block; + vertical-align: center; +} .sortby-dropdown { min-width: 200px; diff --git a/web/src/app/workplace/models/task.ts b/web/src/app/workplace/models/task.ts index 4710ab405..aca98d9e5 100644 --- a/web/src/app/workplace/models/task.ts +++ b/web/src/app/workplace/models/task.ts @@ -22,8 +22,8 @@ export class Task { public priority: number, public classificationSummaryResource: Classification, public workbasketSummaryResource: Workbasket, - public customAttributes: Object, - public callbackInfo: Object, + public customAttributes: Array = [], + public callbackInfo: Array = [], public custom1: string, public custom2: string, public custom3: string, @@ -47,20 +47,3 @@ export class CustomAttribute { key: string; value: string; } - -export function convertToCustomAttributes(callbackInfo: boolean = false): CustomAttribute[] { - return Object.keys(callbackInfo ? this.callbackInfo : this.customAttributes) - .map(k => ({ key: k, value: (callbackInfo ? this.callbackInfo : this.customAttributes)[k] })); -} - -export function saveCustomAttributes(attributes: CustomAttribute[], callbackInfo: boolean = false): void { - const att: Object = attributes.filter(attr => attr.key).reduce((acc, obj) => { - acc[obj.key] = obj.value; - return acc; - }, {}); - if (callbackInfo) { - this.callbackInfo = att; - } else { - this.customAttributes = att; - } -} diff --git a/web/src/app/workplace/taskdetails/attribute/attribute.component.html b/web/src/app/workplace/taskdetails/attribute/attribute.component.html index 75eaf11e2..4ddcd355d 100644 --- a/web/src/app/workplace/taskdetails/attribute/attribute.component.html +++ b/web/src/app/workplace/taskdetails/attribute/attribute.component.html @@ -1,4 +1,4 @@ - + diff --git a/web/src/app/workplace/taskdetails/attribute/attribute.component.ts b/web/src/app/workplace/taskdetails/attribute/attribute.component.ts index f0380e21d..3dda9e10d 100644 --- a/web/src/app/workplace/taskdetails/attribute/attribute.component.ts +++ b/web/src/app/workplace/taskdetails/attribute/attribute.component.ts @@ -1,5 +1,5 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { convertToCustomAttributes, CustomAttribute, Task } from 'app/workplace/models/task'; +import { CustomAttribute } from 'app/workplace/models/task'; @Component({ selector: 'taskana-task-details-attributes', @@ -8,20 +8,15 @@ import { convertToCustomAttributes, CustomAttribute, Task } from 'app/workplace/ }) export class TaskdetailsAttributeComponent implements OnInit { - @Input() task: Task; @Input() callbackInfo = false; - attributes: CustomAttribute[] = []; + @Input() attributes: CustomAttribute[] = []; + @Output() attributesChange: EventEmitter = new EventEmitter(); - @Output() notify: EventEmitter = new EventEmitter(); constructor() { } ngOnInit() { - if (this.task) { - this.attributes = convertToCustomAttributes.bind(this.task)(this.callbackInfo); - this.notify.emit(this.attributes); - } } addAttribute(): void { diff --git a/web/src/app/workplace/taskdetails/custom/custom-fields.component.html b/web/src/app/workplace/taskdetails/custom/custom-fields.component.html index 08d254ead..52863f452 100644 --- a/web/src/app/workplace/taskdetails/custom/custom-fields.component.html +++ b/web/src/app/workplace/taskdetails/custom/custom-fields.component.html @@ -2,49 +2,49 @@
    -
    -
    -
    -
    -
    -
    -
    -
    @@ -52,49 +52,49 @@
    -
    -
    -
    -
    -
    -
    -
    -
    diff --git a/web/src/app/workplace/taskdetails/custom/custom-fields.component.ts b/web/src/app/workplace/taskdetails/custom/custom-fields.component.ts index 334435be5..021ec3e9a 100644 --- a/web/src/app/workplace/taskdetails/custom/custom-fields.component.ts +++ b/web/src/app/workplace/taskdetails/custom/custom-fields.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Task } from 'app/workplace/models/task'; @Component({ @@ -8,6 +8,7 @@ import { Task } from 'app/workplace/models/task'; export class TaskdetailsCustomFieldsComponent implements OnInit { @Input() task: Task; + @Output() taskChange: EventEmitter = new EventEmitter(); constructor() { } diff --git a/web/src/app/workplace/taskdetails/general/general-fields.component.html b/web/src/app/workplace/taskdetails/general/general-fields.component.html index 5f6440d11..af39a4a4c 100644 --- a/web/src/app/workplace/taskdetails/general/general-fields.component.html +++ b/web/src/app/workplace/taskdetails/general/general-fields.component.html @@ -19,18 +19,18 @@ [(ngModel)]="task.priority" name="task.priority">
    -
    - - -
    +
    + + +
    = new EventEmitter(); constructor() { } diff --git a/web/src/app/workplace/taskdetails/taskdetails.component.html b/web/src/app/workplace/taskdetails/taskdetails.component.html index 490599bfa..f3f2fe1ef 100644 --- a/web/src/app/workplace/taskdetails/taskdetails.component.html +++ b/web/src/app/workplace/taskdetails/taskdetails.component.html @@ -30,15 +30,18 @@
    -
    +
    + - -
    @@ -56,12 +59,10 @@
    - +
    - +
    diff --git a/web/src/app/workplace/taskdetails/taskdetails.component.ts b/web/src/app/workplace/taskdetails/taskdetails.component.ts index 53c41e4b2..558fd7f79 100644 --- a/web/src/app/workplace/taskdetails/taskdetails.component.ts +++ b/web/src/app/workplace/taskdetails/taskdetails.component.ts @@ -5,9 +5,12 @@ import { ActivatedRoute, Router } from '@angular/router'; import { TaskService } from 'app/workplace/services/task.service'; import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service'; -import {convertToCustomAttributes, saveCustomAttributes, CustomAttribute, Task} from 'app/workplace/models/task'; -import {ErrorModel} from '../../models/modal-error'; -import {ErrorModalService} from '../../services/errorModal/error-modal.service'; +import { Task } from 'app/workplace/models/task'; +import { ErrorModel } from '../../models/modal-error'; +import { ErrorModalService } from '../../services/errorModal/error-modal.service'; +import { RequestInProgressService } from '../../services/requestInProgress/request-in-progress.service'; +import { AlertService } from '../../services/alert/alert.service'; +import { AlertModel, AlertType } from '../../models/alert'; @Component({ selector: 'taskana-task-details', @@ -16,8 +19,7 @@ import {ErrorModalService} from '../../services/errorModal/error-modal.service'; }) export class TaskdetailsComponent implements OnInit, OnDestroy { task: Task = null; - customAttributes: CustomAttribute[] = []; - callbackInfo: CustomAttribute[] = []; + taskClone: Task = null; requestInProgress = false; tabSelected = 'general'; @@ -28,6 +30,8 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { private taskService: TaskService, private router: Router, private removeConfirmationService: RemoveConfirmationService, + private requestInProgressService: RequestInProgressService, + private alertService: AlertService, private errorModalService: ErrorModalService) { } @@ -38,11 +42,19 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { }); } + resetTask(): void { + this.task = { ...this.taskClone }; + this.task.customAttributes = this.taskClone.customAttributes.slice(0); + this.task.callbackInfo = this.taskClone.callbackInfo.slice(0); + this.alertService.triggerAlert(new AlertModel(AlertType.INFO, 'Reset edited fields')); + } + getTask(id: string): void { this.requestInProgress = true; this.taskService.getTask(id).subscribe(task => { this.requestInProgress = false; this.task = task; + this.cloneTask(); this.taskService.selectTask(task); }, err => { this.errorModalService.triggerError( @@ -51,14 +63,14 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { } updateTask() { - this.requestInProgress = true; - saveCustomAttributes.bind(this.task)(this.customAttributes); - saveCustomAttributes.bind(this.task)(this.callbackInfo, true); + this.requestInProgressService.setRequestInProgress(true); this.taskService.updateTask(this.task).subscribe(task => { - this.requestInProgress = false; + this.requestInProgressService.setRequestInProgress(false); this.task = task; + this.cloneTask(); this.taskService.publishUpdatedTask(task); - }); + this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, 'Update successful!')) + }, err => {this.alertService.triggerAlert(new AlertModel(AlertType.DANGER, 'Update not successful!'))}); } openTask(taskId: string) { @@ -79,7 +91,6 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { this.taskService.deleteTask(this.task).subscribe(); this.taskService.publishDeletedTask(this.task); this.task = null; - this.customAttributes = []; this.router.navigate([`/workplace/tasks`]); } @@ -93,17 +104,15 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { this.router.navigate(['./'], { relativeTo: this.route.parent }); } - linkAttributes(attr: CustomAttribute[], callbackInfo: boolean = false): void { - if (callbackInfo) { - this.callbackInfo = attr; - } else { - this.customAttributes = attr; - } - } - ngOnDestroy(): void { if (this.routeSubscription) { this.routeSubscription.unsubscribe(); } } + + private cloneTask() { + this.taskClone = { ...this.task }; + this.taskClone.customAttributes = this.task.customAttributes.slice(0); + this.taskClone.callbackInfo = this.task.callbackInfo.slice(0); + } } diff --git a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html index 6834533e6..142cff0b0 100644 --- a/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html +++ b/web/src/app/workplace/tasklist/tasklist-toolbar/tasklist-toolbar.component.html @@ -14,10 +14,10 @@
    -