TSK-682: redesigned task details and added custom Attributes

This commit is contained in:
Mustapha Zorgati 2018-09-08 10:56:39 +02:00 committed by Martin Rojas Miguel Angel
parent ef703b4a40
commit 66e25b4bd5
33 changed files with 590 additions and 288 deletions

View File

@ -94,6 +94,7 @@ public class TaskControllerRestDocumentation {
taskFieldDescriptionsMap.put("primaryObjRef.type", "The type of the reference (contract, claim, policy, customer, ...)");
taskFieldDescriptionsMap.put("primaryObjRef.value", "The value of the primary object reference");
taskFieldDescriptionsMap.put("customAttributes", "A container for all additional information on the task in JSON representation");
taskFieldDescriptionsMap.put("callbackInfo", "A container for all callback information of the task in JSON representation");
taskFieldDescriptionsMap.put("attachments", "");
taskFieldDescriptionsMap.put("custom1", "A custom property with name \"1\"");
taskFieldDescriptionsMap.put("custom2", "A custom property with name \"2\"");
@ -152,6 +153,7 @@ public class TaskControllerRestDocumentation {
fieldWithPath("read").description(taskFieldDescriptionsMap.get("read")),
fieldWithPath("transferred").description(taskFieldDescriptionsMap.get("transferred")),
fieldWithPath("customAttributes").description(taskFieldDescriptionsMap.get("customAttributes")),
fieldWithPath("callbackInfo").description(taskFieldDescriptionsMap.get("callbackInfo")),
fieldWithPath("attachments").description(taskFieldDescriptionsMap.get("attachments")),
fieldWithPath("custom1").description(taskFieldDescriptionsMap.get("custom1")).type("String"),
fieldWithPath("custom2").description(taskFieldDescriptionsMap.get("custom2")).type("String"),
@ -201,6 +203,7 @@ public class TaskControllerRestDocumentation {
fieldWithPath("read").description(taskFieldDescriptionsMap.get("read")),
fieldWithPath("transferred").description(taskFieldDescriptionsMap.get("transferred")),
fieldWithPath("customAttributes").ignored(),
fieldWithPath("callbackInfo").ignored(),
fieldWithPath("attachments").description(taskFieldDescriptionsMap.get("attachments")),
fieldWithPath("custom1").description(taskFieldDescriptionsMap.get("custom1")),
fieldWithPath("custom2").description(taskFieldDescriptionsMap.get("custom2")),
@ -247,6 +250,7 @@ public class TaskControllerRestDocumentation {
fieldWithPath("owner").description(taskFieldDescriptionsMap.get("owner")).type("String").optional(),
fieldWithPath("primaryObjRef.id").description(taskFieldDescriptionsMap.get("primaryObjRef.id")).type("String").optional(),
fieldWithPath("customAttributes").description(taskFieldDescriptionsMap.get("customAttributes")).type("Object").optional(),
fieldWithPath("callbackInfo").description(taskFieldDescriptionsMap.get("callbackInfo")).type("Object").optional(),
fieldWithPath("attachments").description(taskFieldDescriptionsMap.get("attachments")).type("Array").optional(),
fieldWithPath("custom1").description(taskFieldDescriptionsMap.get("custom1")).type("String").optional(),
fieldWithPath("custom2").description(taskFieldDescriptionsMap.get("custom2")).type("String").optional(),
@ -313,20 +317,19 @@ public class TaskControllerRestDocumentation {
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
StringBuilder content = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
con.disconnect();
String originalTask = content.toString();
String modifiedTask = new String(originalTask.toString());
this.mockMvc.perform(RestDocumentationRequestBuilders
.put("http://127.0.0.1:" + port + "/v1/tasks/TKI:100000000000000000000000000000000000")
.header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")
.contentType("application/json")
.content(modifiedTask))
.content(originalTask))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcRestDocumentation.document("UpdateTaskDocTest",
requestFields(taskFieldDescriptors),

View File

@ -46,7 +46,7 @@ import pro.taskana.rest.resource.WorkbasketSummaryResource;
import pro.taskana.rest.resource.assembler.DistributionTargetListAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketAccessItemAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketAccessItemListAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketResourceAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketSummaryResourcesAssembler;
/**
@ -80,7 +80,7 @@ public class WorkbasketController extends AbstractPagingController {
private WorkbasketService workbasketService;
@Autowired
private WorkbasketAssembler workbasketAssembler;
private WorkbasketResourceAssembler workbasketResourceAssembler;
@Autowired
private DistributionTargetListAssembler distributionTargetListAssembler;
@ -133,7 +133,7 @@ public class WorkbasketController extends AbstractPagingController {
throws WorkbasketNotFoundException, NotAuthorizedException {
ResponseEntity<WorkbasketResource> result;
Workbasket workbasket = workbasketService.getWorkbasket(workbasketId);
result = new ResponseEntity<>(workbasketAssembler.toResource(workbasket), HttpStatus.OK);
result = new ResponseEntity<>(workbasketResourceAssembler.toResource(workbasket), HttpStatus.OK);
return result;
}
@ -151,9 +151,9 @@ public class WorkbasketController extends AbstractPagingController {
public ResponseEntity<WorkbasketResource> createWorkbasket(@RequestBody WorkbasketResource workbasketResource)
throws InvalidWorkbasketException, NotAuthorizedException, WorkbasketAlreadyExistException,
WorkbasketNotFoundException, DomainNotFoundException {
Workbasket workbasket = workbasketAssembler.toModel(workbasketResource);
Workbasket workbasket = workbasketResourceAssembler.toModel(workbasketResource);
workbasket = workbasketService.createWorkbasket(workbasket);
return new ResponseEntity<>(workbasketAssembler.toResource(workbasket), HttpStatus.CREATED);
return new ResponseEntity<>(workbasketResourceAssembler.toResource(workbasket), HttpStatus.CREATED);
}
@PutMapping(path = "/{workbasketId}")
@ -164,9 +164,9 @@ public class WorkbasketController extends AbstractPagingController {
throws InvalidWorkbasketException, WorkbasketNotFoundException, NotAuthorizedException {
ResponseEntity<WorkbasketResource> result;
if (workbasketId.equals(workbasketResource.workbasketId)) {
Workbasket workbasket = workbasketAssembler.toModel(workbasketResource);
Workbasket workbasket = workbasketResourceAssembler.toModel(workbasketResource);
workbasket = workbasketService.updateWorkbasket(workbasket);
result = ResponseEntity.ok(workbasketAssembler.toResource(workbasket));
result = ResponseEntity.ok(workbasketResourceAssembler.toResource(workbasket));
} else {
throw new InvalidWorkbasketException(
"Target-WB-ID('" + workbasketId

View File

@ -28,7 +28,7 @@ import pro.taskana.rest.resource.WorkbasketDefinition;
import pro.taskana.rest.resource.WorkbasketResource;
import pro.taskana.rest.resource.assembler.WorkbasketAccessItemAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketDefinitionAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketAssembler;
import pro.taskana.rest.resource.assembler.WorkbasketResourceAssembler;
import java.util.ArrayList;
import java.util.HashMap;
@ -50,7 +50,7 @@ public class WorkbasketDefinitionController {
private WorkbasketDefinitionAssembler workbasketDefinitionAssembler;
@Autowired
private WorkbasketAssembler workbasketAssembler;
private WorkbasketResourceAssembler workbasketResourceAssembler;
@Autowired
private WorkbasketAccessItemAssembler workbasketAccessItemAssembler;
@ -110,11 +110,11 @@ public class WorkbasketDefinitionController {
if (systemIds.containsKey(logicalId(res))) {
res.workbasketId = systemIds.get(logicalId(res));
workbasket = workbasketService.updateWorkbasket(
workbasketAssembler.toModel(res));
workbasketResourceAssembler.toModel(res));
} else {
res.workbasketId = null;
workbasket = workbasketService.createWorkbasket(
workbasketAssembler.toModel(res));
workbasketResourceAssembler.toModel(res));
}
res.workbasketId = oldId;

View File

@ -38,6 +38,7 @@ public class TaskResource extends ResourceSupport {
private boolean isTransferred;
// All objects have to be serializable
private Map<String, String> customAttributes = Collections.emptyMap();
private Map<String, String> callbackInfo = Collections.emptyMap();
private List<AttachmentResource> attachments = new ArrayList<>();
private String custom1;
private String custom2;
@ -232,6 +233,14 @@ public class TaskResource extends ResourceSupport {
this.customAttributes = customAttributes;
}
public Map<String, String> getCallbackInfo() {
return callbackInfo;
}
public void setCallbackInfo(Map<String, String> callbackInfo) {
this.callbackInfo = callbackInfo;
}
public List<AttachmentResource> getAttachments() {
return attachments;
}

View File

@ -24,13 +24,13 @@ import pro.taskana.rest.resource.DistributionTargetResource;
public class DistributionTargetListAssembler {
@Autowired
private DistributionTargetAssembler distributionTargetAssembler;
private DistributionTargetResourceAssembler distributionTargetResourceAssembler;
public Resources<DistributionTargetResource> toResource(String workbasketId,
Collection<WorkbasketSummary> distributionTargets) throws WorkbasketNotFoundException, NotAuthorizedException {
List<DistributionTargetResource> resourceList = new ArrayList<>();
for (WorkbasketSummary wb : distributionTargets) {
resourceList.add(distributionTargetAssembler.toResource(wb));
resourceList.add(distributionTargetResourceAssembler.toResource(wb));
}
Resources<DistributionTargetResource> distributionTargetListResource = new Resources<>(resourceList);

View File

@ -16,7 +16,7 @@ import pro.taskana.rest.resource.DistributionTargetResource;
* Transforms WorkbasketSummary to its resource counterpart DistributionTargerResource and vice versa.
*/
@Component
public class DistributionTargetAssembler {
public class DistributionTargetResourceAssembler {
public DistributionTargetResource toResource(WorkbasketSummary summary)
throws WorkbasketNotFoundException, NotAuthorizedException {

View File

@ -33,7 +33,7 @@ public class WorkbasketDefinitionAssembler {
private WorkbasketService workbasketService;
@Autowired
private WorkbasketAssembler workbasketAssembler;
private WorkbasketResourceAssembler workbasketResourceAssembler;
@Autowired
private WorkbasketAccessItemAssembler workbasketAccessItemAssembler;
@ -60,7 +60,7 @@ public class WorkbasketDefinitionAssembler {
.stream()
.map(WorkbasketSummary::getId)
.collect(Collectors.toSet());
WorkbasketDefinition resource = new WorkbasketDefinition(workbasketAssembler.toResource(basket), distroTargets,
WorkbasketDefinition resource = new WorkbasketDefinition(workbasketResourceAssembler.toResource(basket), distroTargets,
authorizations);
return addLinks(resource, basket);
}

View File

@ -21,7 +21,7 @@ import pro.taskana.rest.resource.WorkbasketResource;
* Transforms {@link Workbasket} to its resource counterpart {@link WorkbasketResource} and vice versa.
*/
@Component
public class WorkbasketAssembler {
public class WorkbasketResourceAssembler {
@Autowired
private WorkbasketService workbasketService;

View File

@ -1,36 +0,0 @@
package pro.taskana.rest.resource.assembler;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import pro.taskana.WorkbasketSummary;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.WorkbasketNotFoundException;
import pro.taskana.rest.WorkbasketController;
import pro.taskana.rest.resource.WorkbasketSummaryResource;
/**
* Transforms {@link WorkbasketSummary} to its resource counterpart {@link WorkbasketSummaryResource} and vice versa.
*/
@Component
public class WorkbasketSummaryAssembler {
public WorkbasketSummaryResource toResource(WorkbasketSummary summary)
throws WorkbasketNotFoundException, NotAuthorizedException {
WorkbasketSummaryResource resource = new WorkbasketSummaryResource();
BeanUtils.copyProperties(summary, resource);
// named different so needs to be set by hand
resource.setWorkbasketId(summary.getId());
return addLinks(resource, summary);
}
private WorkbasketSummaryResource addLinks(WorkbasketSummaryResource resource, WorkbasketSummary summary)
throws WorkbasketNotFoundException, NotAuthorizedException {
resource.add(linkTo(methodOn(WorkbasketController.class).getWorkbasket(summary.getId())).withSelfRel());
return resource;
}
}

View File

@ -20,17 +20,17 @@ import pro.taskana.rest.TestConfiguration;
import pro.taskana.rest.resource.WorkbasketResource;
/**
* Test for {@link WorkbasketAssembler}.
* Test for {@link WorkbasketResourceAssembler}.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfiguration.class})
@WebAppConfiguration
public class WorkbasketAssemblerTest {
public class WorkbasketResourceAssemblerTest {
@Autowired
WorkbasketService workbasketService;
@Autowired
WorkbasketAssembler workbasketAssembler;
WorkbasketResourceAssembler workbasketResourceAssembler;
@Test
public void workbasketToResource() throws NotAuthorizedException, WorkbasketNotFoundException {
@ -52,7 +52,7 @@ public class WorkbasketAssemblerTest {
((WorkbasketImpl) workbasket).setCreated(Instant.parse("2010-01-01T12:00:00Z"));
((WorkbasketImpl) workbasket).setModified(Instant.parse("2010-01-01T12:00:00Z"));
// when
WorkbasketResource workbasketResource = workbasketAssembler.toResource(workbasket);
WorkbasketResource workbasketResource = workbasketResourceAssembler.toResource(workbasket);
// then
testEquality(workbasket, workbasketResource);
}
@ -79,7 +79,7 @@ public class WorkbasketAssemblerTest {
workbasketResource.setOwner("Lars");
workbasketResource.setType(WorkbasketType.PERSONAL);
// when
Workbasket workbasket = workbasketAssembler.toModel(workbasketResource);
Workbasket workbasket = workbasketResourceAssembler.toModel(workbasketResource);
// then
testEquality(workbasket, workbasketResource);
}

View File

@ -10,14 +10,12 @@ import org.springframework.test.context.web.WebAppConfiguration;
import pro.taskana.WorkbasketService;
import pro.taskana.WorkbasketType;
import pro.taskana.exceptions.NotAuthorizedException;
import pro.taskana.exceptions.WorkbasketNotFoundException;
import pro.taskana.impl.WorkbasketSummaryImpl;
import pro.taskana.rest.TestConfiguration;
import pro.taskana.rest.resource.WorkbasketSummaryResource;
/**
* Test for {@link WorkbasketSummaryAssembler}.
* Test for {@link WorkbasketSummaryResourceAssembler}.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestConfiguration.class})
@ -25,12 +23,12 @@ import pro.taskana.rest.resource.WorkbasketSummaryResource;
public class WorkbasketSummaryAssemblerTest {
@Autowired
WorkbasketSummaryAssembler workbasketSummaryAssembler;
WorkbasketSummaryResourceAssembler workbasketSummaryAssembler;
@Autowired
WorkbasketService workbasketService;
@Test
public void workbasketSummaryToResource() throws WorkbasketNotFoundException, NotAuthorizedException {
public void workbasketSummaryToResource() {
// given
WorkbasketSummaryImpl workbasketSummary = (WorkbasketSummaryImpl) workbasketService.newWorkbasket("1",
"DOMAIN_A").asSummary();

View File

@ -6,7 +6,7 @@
<a (click)="backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li>
<li role="presentation" class="active" (click)="selectTab('information')" [ngClass]="{'active':tabSelected === 'information'}">
<li role="presentation" (click)="selectTab('information')" [ngClass]="{'active':tabSelected === 'information'}">
<a aria-controls="work baskets" role="tab" aria-expanded="true">
Information</a>
</li>
@ -35,4 +35,4 @@
</div>
</div>
</div>
</div>
</div>

View File

@ -14,8 +14,7 @@ import { ErrorModalService } from '../../../services/errorModal/error-modal.serv
@Component({
selector: 'taskana-workbasket-details',
templateUrl: './workbasket-details.component.html',
styleUrls: ['./workbasket-details.component.scss']
templateUrl: './workbasket-details.component.html'
})
export class WorkbasketDetailsComponent implements OnInit, OnDestroy {

View File

@ -11,7 +11,7 @@ import { ReportData } from 'app/monitor/models/report-data';
export class TasksComponent implements OnInit {
pieChartLabels: string[] = ['Ready', 'Claimed', 'Completed'];
pieChartLabels: string[];
pieChartData: number[] = [];
pieChartType = 'pie';
reportData: ReportData
@ -23,6 +23,7 @@ export class TasksComponent implements OnInit {
ngOnInit() {
this.restConnectorService.getTaskStatusReport().subscribe((data: ReportData) => {
this.reportData = data;
this.pieChartLabels = Object.keys(data.sumRow.cells);
Object.keys(data.sumRow.cells).forEach(key => {
this.pieChartData.push(data.sumRow.cells[key]);
})

View File

@ -22,6 +22,8 @@ export class Task {
public priority: number,
public classificationSummaryResource: Classification,
public workbasketSummaryResource: Workbasket,
public customAttributes: Object,
public callbackInfo: Object,
public custom1: string,
public custom2: string,
public custom3: string,
@ -40,3 +42,25 @@ export class Task {
public custom16: string) {
}
}
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;
}
}

View File

@ -17,14 +17,14 @@ export class TaskService {
STATE = 'state';
url = `${environment.taskanaRestUrl}/v1/tasks`;
private taskSelected = new Subject<Task>();
taskChangedSource = new Subject<Task>();
taskChangedStream = this.taskChangedSource.asObservable();
taskDeletedSource = new Subject<Task>();
taskDeletedStream = this.taskDeletedSource.asObservable();
private taskSelected = new Subject<Task>();
constructor(private httpClient: HttpClient) {
}
publishUpdatedTask(task: Task) {
this.taskChangedSource.next(task);
@ -42,9 +42,6 @@ export class TaskService {
return this.taskSelected.asObservable();
}
constructor(private httpClient: HttpClient) {
}
/**
* @param {string} basketId
* @param {string} sortBy name of field, that the tasks should be sorted by, default is priority

View File

@ -0,0 +1,35 @@
<ng-container *ngIf="task">
<table class="table table-striped table-center">
<thead>
<tr>
<th class="table__remove-col"></th>
<th>Attribute</th>
<th>Property</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let entry of attributes; let i = index;">
<td>
<button type="button" (click)="removeAttribute(i)" data-toggle="tooltip" title="Remove"
class="btn btn-default remove">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
</td>
<td>
<input type="text" class="form-control" [(ngModel)]="entry.key"
name="{{callbackInfo ? 'callback-info' : 'custom-attribute'}}-{{i}}"/>
</td>
<td>
<input type="text" class="form-control" [(ngModel)]="entry.value"
name="{{callbackInfo ? 'callback-info' : 'custom-attribute'}}-{{i}}-val"/>
</td>
</tr>
</tbody>
</table>
<button type="button" (click)="addAttribute()" class="btn btn-default"
data-toggle="tooltip"
title="Add new access">
<span class="glyphicon glyphicon-plus green-blue" aria-hidden="true"></span>
Add new {{ callbackInfo ? 'callback information' : 'custom attribute'}}
</button>
</ng-container>

View File

@ -0,0 +1,3 @@
.table__remove-col {
width: 57px;
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TaskdetailsAttributeComponent } from './attribute.component';
describe('AttributeComponent', () => {
let component: TaskdetailsAttributeComponent;
let fixture: ComponentFixture<TaskdetailsAttributeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TaskdetailsAttributeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaskdetailsAttributeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,35 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { convertToCustomAttributes, CustomAttribute, Task } from 'app/workplace/models/task';
@Component({
selector: 'taskana-task-details-attributes',
templateUrl: './attribute.component.html',
styleUrls: ['./attribute.component.scss']
})
export class TaskdetailsAttributeComponent implements OnInit {
@Input() task: Task;
@Input() callbackInfo = false;
attributes: CustomAttribute[] = [];
@Output() notify: EventEmitter<CustomAttribute[]> = new EventEmitter<CustomAttribute[]>();
constructor() {
}
ngOnInit() {
if (this.task) {
this.attributes = convertToCustomAttributes.bind(this.task)(this.callbackInfo);
this.notify.emit(this.attributes);
}
}
addAttribute(): void {
this.attributes.push({ key: '', value: '' });
}
removeAttribute(idx: number): void {
this.attributes.splice(idx, 1);
}
}

View File

@ -0,0 +1,102 @@
<ng-container *ngIf="task">
<div class="col-md-6">
<div class="form-group">
<label for="task-custom1" class="control-label">Custom1</label>
<input type="text" class="form-control" id="task-custom1" placeholder="No custom1 set"
[(ngModel)]="task.custom1"
name="task.custom1">
</div>
<div class="form-group">
<label for="task-custom2" class="control-label">Custom2</label>
<input type="text" class="form-control" id="task-custom2" placeholder="No custom2 set"
[(ngModel)]="task.custom2"
name="task.custom2">
</div>
<div class="form-group">
<label for="task-custom3" class="control-label">Custom3</label>
<input type="text" class="form-control" id="task-custom3" placeholder="No custom3 set"
[(ngModel)]="task.custom3"
name="task.custom3">
</div>
<div class="form-group">
<label for="task-custom4" class="control-label">Custom4</label>
<input type="text" class="form-control" id="task-custom4" placeholder="No custom4 set"
[(ngModel)]="task.custom4"
name="task.custom4">
</div>
<div class="form-group">
<label for="task-custom5" class="control-label">Custom5</label>
<input type="text" class="form-control" id="task-custom5" placeholder="No custom5 set"
[(ngModel)]="task.custom5"
name="task.custom5">
</div>
<div class="form-group">
<label for="task-custom6" class="control-label">Custom6</label>
<input type="text" class="form-control" id="task-custom6" placeholder="No custom6 set"
[(ngModel)]="task.custom6"
name="task.custom6">
</div>
<div class="form-group">
<label for="task-custom7" class="control-label">Custom7</label>
<input type="text" class="form-control" id="task-custom7" placeholder="No custom7 set"
[(ngModel)]="task.custom7"
name="task.custom7">
</div>
<div class="form-group">
<label for="task-custom8" class="control-label">Custom8</label>
<input type="text" class="form-control" id="task-custom8" placeholder="No custom8 set"
[(ngModel)]="task.custom8"
name="task.custom8">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="task-custom9" class="control-label">Custom9</label>
<input type="text" class="form-control" id="task-custom9" placeholder="No custom9 set"
[(ngModel)]="task.custom9"
name="task.custom9">
</div>
<div class="form-group">
<label for="task-custom10" class="control-label">Custom10</label>
<input type="text" class="form-control" id="task-custom10" placeholder="No custom10 set"
[(ngModel)]="task.custom10"
name="task.custom10">
</div>
<div class="form-group">
<label for="task-custom11" class="control-label">Custom11</label>
<input type="text" class="form-control" id="task-custom11" placeholder="No custom11 set"
[(ngModel)]="task.custom11"
name="task.custom11">
</div>
<div class="form-group">
<label for="task-custom12" class="control-label">Custom12</label>
<input type="text" class="form-control" id="task-custom12" placeholder="No custom12 set"
[(ngModel)]="task.custom12"
name="task.custom12">
</div>
<div class="form-group">
<label for="task-custom13" class="control-label">Custom13</label>
<input type="text" class="form-control" id="task-custom13" placeholder="No custom13 set"
[(ngModel)]="task.custom13"
name="task.custom13">
</div>
<div class="form-group">
<label for="task-custom14" class="control-label">Custom14</label>
<input type="text" class="form-control" id="task-custom14" placeholder="No custom14 set"
[(ngModel)]="task.custom14"
name="task.custom14">
</div>
<div class="form-group">
<label for="task-custom15" class="control-label">Custom15</label>
<input type="text" class="form-control" id="task-custom15" placeholder="No custom15 set"
[(ngModel)]="task.custom15"
name="task.custom15">
</div>
<div class="form-group">
<label for="task-custom16" class="control-label">Custom16</label>
<input type="text" class="form-control" id="task-custom16" placeholder="No custom16 set"
[(ngModel)]="task.custom16"
name="task.custom16">
</div>
</div>
</ng-container>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TaskdetailsCustomFieldsComponent } from './custom-fields.component';
describe('CustomComponent', () => {
let component: TaskdetailsCustomFieldsComponent;
let fixture: ComponentFixture<TaskdetailsCustomFieldsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TaskdetailsCustomFieldsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaskdetailsCustomFieldsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
import { Task } from 'app/workplace/models/task';
@Component({
selector: 'taskana-task-details-custom-fields',
templateUrl: './custom-fields.component.html'
})
export class TaskdetailsCustomFieldsComponent implements OnInit {
@Input() task: Task;
constructor() {
}
ngOnInit() {
}
}

View File

@ -0,0 +1,113 @@
<ng-container *ngIf="task">
<div class="col-md-12">
<div class="form-group">
<label for="task-description" class="control-label">Description</label>
<textarea class="form-control" id="task-description" placeholder="Description"
[(ngModel)]="task.description"
name="task.description"></textarea>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="task-owner" class="control-label">Owner</label>
<input type="text" class="form-control" id="task-owner" placeholder="Owner" [(ngModel)]="task.owner"
name="task.owner">
</div>
<div class="form-group">
<label for="task-priority" disabled class="control-label">Priority</label>
<input type="text" class="form-control" id="task-priority" placeholder="no priotity set"
[(ngModel)]="task.priority"
name="task.priority">
</div>
<div class="form-group">
<label for="task-state" class="control-label">State</label>
<input type="text" disabled class="form-control" id="task-state" placeholder="Task has no State"
[(ngModel)]="task.state"
name="task.state">
</div>
<div class="form-group">
<label for="task-note" class="control-label">Note</label>
<input type="text" class="form-control" id="task-note" placeholder="Task has no Note"
[(ngModel)]="task.note"
name="task.note">
</div>
<div class="form-group">
<label for="task-created" class="control-label">Creation Date</label>
<input type="text" disabled class="form-control" id="task-created" [(ngModel)]="task.created"
name="task.created">
</div>
<div class="form-group">
<label for="task-claimed" class="control-label">Claim Date</label>
<input type="text" disabled class="form-control" id="task-claimed" placeholder="Not claimed yet"
[(ngModel)]="task.claimed"
name="task.claimed">
</div>
<div class="form-group">
<label for="task-completed" class="control-label">Completion Date</label>
<input type="text" disabled class="form-control" id="task-completed" placeholder="Not completed yet"
[(ngModel)]="task.completed"
name="task.completed">
</div>
<div class="form-group">
<label for="task-modified" class="control-label">Modification Date</label>
<input type="text" disabled class="form-control" id="task-modified" placeholder="Has not been modified"
[(ngModel)]="task.modified"
name="task.modified">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="task-planned" class="control-label">Planned Date</label>
<input type="text" disabled class="form-control" id="task-planned" placeholder="No plan set"
[(ngModel)]="task.planned"
name="task.planned">
</div>
<div class="form-group">
<label for="task-due" class="control-label">Due Date</label>
<input type="text" disabled class="form-control" id="task-due" placeholder="No deadline set"
[(ngModel)]="task.due"
name="task.due">
</div>
<div class="form-group">
<label for="task-workbasket" class="control-label">Workbasket ID</label>
<input type="text" disabled class="form-control" id="task-workbasket"
placeholder="Task does not belong to a Workbasket"
[(ngModel)]="task.workbasketSummaryResource.workbasketId"
name="task.workbasket">
</div>
<div class="form-group">
<label for="task-classification" class="control-label">Classification Key</label>
<input type="text" disabled class="form-control" id="task-classification"
placeholder="Task does not belong to a Classification"
[(ngModel)]="task.classificationSummaryResource.key"
name="task.classification">
</div>
<div class="form-group">
<label for="task-businessProcessId" class="control-label">Business Process ID</label>
<input type="text" disabled class="form-control" id="task-businessProcessId" placeholder="Task has no BPI"
[(ngModel)]="task.businessProcessId"
name="task.businessProcessId">
</div>
<div class="form-group">
<label for="task-parentBusinessProcessId" class="control-label">Parent Business Process ID</label>
<input type="text" disabled class="form-control" id="task-parentBusinessProcessId"
placeholder="Task has no PBPI"
[(ngModel)]="task.businessProcessId"
name="task.parentBusinessProcessId">
</div>
<div class="form-group">
<label for="task-read" class="control-label">Task Read?</label>
<input type="text" disabled class="form-control" id="task-read" placeholder="Task not been read"
[(ngModel)]="task.read"
name="task.read">
</div>
<div class="form-group">
<label for="task-transferred" class="control-label">Task Transferred?</label>
<input type="text" disabled class="form-control" id="task-transferred"
placeholder="Task not been transferred"
[(ngModel)]="task.transferred"
name="task.transferred">
</div>
</div>
</ng-container>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TaskdetailsGeneralFieldsComponent } from './general-fields.component';
describe('GeneralComponent', () => {
let component: TaskdetailsGeneralFieldsComponent;
let fixture: ComponentFixture<TaskdetailsGeneralFieldsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TaskdetailsGeneralFieldsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TaskdetailsGeneralFieldsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,18 @@
import { Component, Input, OnInit } from '@angular/core';
import { Task } from 'app/workplace/models/task';
@Component({
selector: 'taskana-task-details-general-fields',
templateUrl: './general-fields.component.html'
})
export class TaskdetailsGeneralFieldsComponent implements OnInit {
@Input() task: Task;
constructor() {
}
ngOnInit() {
}
}

View File

@ -1,4 +1,33 @@
<taskana-spinner [isRunning]="requestInProgress"></taskana-spinner>
<ul class="nav nav-tabs" role="tablist" *ngIf="task && !requestInProgress">
<li class="visible-xs visible-sm hidden">
<a (click)="backClicked()">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</li>
<li role="presentation" (click)="selectTab('general')" [ngClass]="{'active':tabSelected === 'general'}">
<a role="tab" aria-expanded="true">
General Fields
</a>
</li>
<li role="presentation" (click)="selectTab('custom')" [ngClass]="{'active':tabSelected === 'custom'}">
<a role="tab" aria-expanded="true">
Custom Fields
</a>
</li>
<li role="presentation" (click)="selectTab('custom-attributes')"
[ngClass]="{'active':tabSelected === 'custom-attributes'}">
<a role="tab" aria-expanded="true">
Custom Attributes
</a>
</li>
<li role="presentation" (click)="selectTab('callback-info')"
[ngClass]="{'active':tabSelected === 'callback-info'}">
<a role="tab" aria-expanded="true">
Callback Information
</a>
</li>
</ul>
<div class="panel panel-default" *ngIf="task && !requestInProgress">
<div class="panel-heading">
<div class="pull-right">
@ -16,208 +45,23 @@
<h4 class="panel-header"><b>{{task?.name}}</b></h4>
</div>
<div class="panel-body list-group">
<div class="panel-body">
<form #TaskForm="ngForm">
<div class="col-md-6">
<div class="form-group">
<label for="task-description" class="control-label">Description</label>
<textarea type="text" class="form-control" id="task-description" placeholder="Description"
[(ngModel)]="task.description"
name="task.description"></textarea>
<div class="tab-content">
<div role="tabpanel" class="tab-pane" [ngClass]="{'active':tabSelected === 'general'}">
<taskana-task-details-general-fields [task]="task"></taskana-task-details-general-fields>
</div>
<div class="form-group">
<label for="task-owner" class="control-label">Owner</label>
<input type="text" class="form-control" id="task-owner" placeholder="Owner" [(ngModel)]="task.owner"
name="task.owner">
<div role="tabpanel" class="tab-pane" [ngClass]="{'active':tabSelected === 'custom'}">
<taskana-task-details-custom-fields [task]="task"></taskana-task-details-custom-fields>
</div>
<div class="form-group">
<label for="task-priority" disabled class="control-label">Priority</label>
<input type="text" class="form-control" id="task-priority" placeholder="no priotity set"
[(ngModel)]="task.priority"
name="task.priority">
<div role="tabpanel" class="tab-pane" [ngClass]="{'active':tabSelected === 'custom-attributes'}">
<taskana-task-details-attributes [task]="task"
(notify)="linkAttributes($event)"></taskana-task-details-attributes>
</div>
<div class="form-group">
<label for="task-state" class="control-label">State</label>
<input type="text" disabled class="form-control" id="task-state" placeholder="Task has no State"
[(ngModel)]="task.state"
name="task.state">
</div>
<div class="form-group">
<label for="task-note" class="control-label">Note</label>
<input type="text" class="form-control" id="task-note" placeholder="Task has no Note" [(ngModel)]="task.note"
name="task.note">
</div>
<div class="form-group">
<label for="task-created" class="control-label">Creation Date</label>
<input type="text" disabled class="form-control" id="task-created" [(ngModel)]="task.created"
name="task.created">
</div>
<div class="form-group">
<label for="task-claimed" class="control-label">Claim Date</label>
<input type="text" disabled class="form-control" id="task-claimed" placeholder="Not claimed yet"
[(ngModel)]="task.claimed"
name="task.claimed">
</div>
<div class="form-group">
<label for="task-completed" class="control-label">Completion Date</label>
<input type="text" disabled class="form-control" id="task-completed" placeholder="Not completed yet"
[(ngModel)]="task.completed"
name="task.completed">
</div>
<div class="form-group">
<label for="task-modified" class="control-label">Modification Date</label>
<input type="text" disabled class="form-control" id="task-modified" placeholder="Has not been modified"
[(ngModel)]="task.modified"
name="task.modified">
</div>
<div class="form-group">
<label for="task-planned" class="control-label">Planned Date</label>
<input type="text" disabled class="form-control" id="task-planned" placeholder="No plan set"
[(ngModel)]="task.planned"
name="task.planned">
</div>
<div class="form-group">
<label for="task-due" class="control-label">Due Date</label>
<input type="text" disabled class="form-control" id="task-due" placeholder="No deadline set"
[(ngModel)]="task.due"
name="task.due">
</div>
<div class="form-group">
<label for="task-workbasket" class="control-label">Workbasket ID</label>
<input type="text" disabled class="form-control" id="task-workbasket"
placeholder="Task does not belong to a Workbasket"
[(ngModel)]="task.workbasketSummaryResource.workbasketId"
name="task.workbasket">
</div>
<div class="form-group">
<label for="task-classification" class="control-label">Classification Key</label>
<input type="text" disabled class="form-control" id="task-classification"
placeholder="Task does not belong to a Classification"
[(ngModel)]="task.classificationSummaryResource.key"
name="task.classification">
</div>
<div class="form-group">
<label for="task-businessProcessId" class="control-label">Business Process ID</label>
<input type="text" disabled class="form-control" id="task-businessProcessId" placeholder="Task has no BPI"
[(ngModel)]="task.businessProcessId"
name="task.businessProcessId">
</div>
<div class="form-group">
<label for="task-parentBusinessProcessId" class="control-label">Parent Business Process ID</label>
<input type="text" disabled class="form-control" id="task-parentBusinessProcessId"
placeholder="Task has no PBPI"
[(ngModel)]="task.businessProcessId"
name="task.parentBusinessProcessId">
</div>
<div class="form-group">
<label for="task-read" class="control-label">Task Read?</label>
<input type="text" disabled class="form-control" id="task-read" placeholder="Task not been read"
[(ngModel)]="task.read"
name="task.read">
</div>
<div class="form-group">
<label for="task-transferred" class="control-label">Task Transferred?</label>
<input type="text" disabled class="form-control" id="task-transferred" placeholder="Task not been transferred"
[(ngModel)]="task.transferred"
name="task.transferred">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="task-custom1" class="control-label">Custom1</label>
<input type="text" class="form-control" id="task-custom1" placeholder="No custom1 set"
[(ngModel)]="task.custom1"
name="task.custom1">
</div>
<div class="form-group">
<label for="task-custom2" class="control-label">Custom2</label>
<input type="text" class="form-control" id="task-custom2" placeholder="No custom2 set"
[(ngModel)]="task.custom2"
name="task.custom2">
</div>
<div class="form-group">
<label for="task-custom3" class="control-label">Custom3</label>
<input type="text" class="form-control" id="task-custom3" placeholder="No custom3 set"
[(ngModel)]="task.custom3"
name="task.custom3">
</div>
<div class="form-group">
<label for="task-custom4" class="control-label">Custom4</label>
<input type="text" class="form-control" id="task-custom4" placeholder="No custom4 set"
[(ngModel)]="task.custom4"
name="task.custom4">
</div>
<div class="form-group">
<label for="task-custom5" class="control-label">Custom5</label>
<input type="text" class="form-control" id="task-custom5" placeholder="No custom5 set"
[(ngModel)]="task.custom5"
name="task.custom5">
</div>
<div class="form-group">
<label for="task-custom6" class="control-label">Custom6</label>
<input type="text" class="form-control" id="task-custom6" placeholder="No custom6 set"
[(ngModel)]="task.custom6"
name="task.custom6">
</div>
<div class="form-group">
<label for="task-custom7" class="control-label">Custom7</label>
<input type="text" class="form-control" id="task-custom7" placeholder="No custom7 set"
[(ngModel)]="task.custom7"
name="task.custom7">
</div>
<div class="form-group">
<label for="task-custom8" class="control-label">Custom8</label>
<input type="text" class="form-control" id="task-custom8" placeholder="No custom8 set"
[(ngModel)]="task.custom8"
name="task.custom8">
</div>
<div class="form-group">
<label for="task-custom9" class="control-label">Custom9</label>
<input type="text" class="form-control" id="task-custom9" placeholder="No custom9 set"
[(ngModel)]="task.custom9"
name="task.custom9">
</div>
<div class="form-group">
<label for="task-custom10" class="control-label">Custom10</label>
<input type="text" class="form-control" id="task-custom10" placeholder="No custom10 set"
[(ngModel)]="task.custom10"
name="task.custom10">
</div>
<div class="form-group">
<label for="task-custom11" class="control-label">Custom11</label>
<input type="text" class="form-control" id="task-custom11" placeholder="No custom11 set"
[(ngModel)]="task.custom11"
name="task.custom11">
</div>
<div class="form-group">
<label for="task-custom12" class="control-label">Custom12</label>
<input type="text" class="form-control" id="task-custom12" placeholder="No custom12 set"
[(ngModel)]="task.custom12"
name="task.custom12">
</div>
<div class="form-group">
<label for="task-custom13" class="control-label">Custom13</label>
<input type="text" class="form-control" id="task-custom13" placeholder="No custom13 set"
[(ngModel)]="task.custom13"
name="task.custom13">
</div>
<div class="form-group">
<label for="task-custom14" class="control-label">Custom14</label>
<input type="text" class="form-control" id="task-custom14" placeholder="No custom14 set"
[(ngModel)]="task.custom14"
name="task.custom14">
</div>
<div class="form-group">
<label for="task-custom15" class="control-label">Custom15</label>
<input type="text" class="form-control" id="task-custom15" placeholder="No custom15 set"
[(ngModel)]="task.custom15"
name="task.custom15">
</div>
<div class="form-group">
<label for="task-custom16" class="control-label">Custom16</label>
<input type="text" class="form-control" id="task-custom16" placeholder="No custom16 set"
[(ngModel)]="task.custom16"
name="task.custom16">
<div role="tabpanel" class="tab-pane" [ngClass]="{'active':tabSelected === 'callback-info'}">
<taskana-task-details-attributes [task]="task" [callbackInfo]="true"
(notify)="linkAttributes($event, true)"></taskana-task-details-attributes>
</div>
</div>
</form>

View File

@ -1,6 +0,0 @@
.list-group {
max-height: 85vh;
margin-bottom: 10px;
overflow: hidden;
overflow-y: scroll;
}

View File

@ -5,7 +5,7 @@ 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 { Task } from 'app/workplace/models/task';
import {convertToCustomAttributes, saveCustomAttributes, CustomAttribute, Task} from 'app/workplace/models/task';
import {ErrorModel} from '../../models/modal-error';
import {ErrorModalService} from '../../services/errorModal/error-modal.service';
@ -16,7 +16,10 @@ import {ErrorModalService} from '../../services/errorModal/error-modal.service';
})
export class TaskdetailsComponent implements OnInit, OnDestroy {
task: Task = null;
customAttributes: CustomAttribute[] = [];
callbackInfo: CustomAttribute[] = [];
requestInProgress = false;
tabSelected = 'general';
private routeSubscription: Subscription;
@ -49,6 +52,8 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
updateTask() {
this.requestInProgress = true;
saveCustomAttributes.bind(this.task)(this.customAttributes);
saveCustomAttributes.bind(this.task)(this.callbackInfo, true);
this.taskService.updateTask(this.task).subscribe(task => {
this.requestInProgress = false;
this.task = task;
@ -69,13 +74,33 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
`You are going to delete Task: ${this.task.taskId}. Can you confirm this action?`);
}
deleteTaskConfirmation(): void {
this.taskService.deleteTask(this.task).subscribe();
this.taskService.publishDeletedTask(this.task);
this.task = null;
this.customAttributes = [];
this.router.navigate([`/workplace/tasks`]);
}
selectTab(tab: string): void {
this.tabSelected = tab;
}
backClicked(): void {
this.task = undefined;
this.taskService.selectTask(this.task);
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();

View File

@ -60,6 +60,9 @@ export class TasklistComponent implements OnInit, OnDestroy {
this.currentBasket = task.workbasketSummaryResource;
this.getTasks();
}
if (!task) {
this.selectedId = undefined;
}
});
}

View File

@ -9,6 +9,9 @@ import {AlertModule, TypeaheadModule} from 'ngx-bootstrap';
import {TaskListToolbarComponent} from './tasklist/tasklist-toolbar/tasklist-toolbar.component';
import {TasklistComponent} from './tasklist/tasklist.component';
import {TaskdetailsComponent} from './taskdetails/taskdetails.component';
import {TaskdetailsGeneralFieldsComponent} from './taskdetails/general/general-fields.component';
import {TaskdetailsCustomFieldsComponent} from './taskdetails/custom/custom-fields.component';
import {TaskdetailsAttributeComponent} from './taskdetails/attribute/attribute.component';
import {TaskComponent} from './task/task.component';
import {CodeComponent} from './components/code/code.component';
@ -36,6 +39,9 @@ const DECLARATIONS = [
TaskListToolbarComponent,
TasklistComponent,
TaskdetailsComponent,
TaskdetailsGeneralFieldsComponent,
TaskdetailsCustomFieldsComponent,
TaskdetailsAttributeComponent,
TaskComponent,
CodeComponent,
OrderTasksByPipe

View File

@ -279,7 +279,7 @@ body{
}
}
taskana-workbasket-information, taskana-workbasket-access-items, taskana-workbaskets-distribution-targets, taskana-monitor-tasks,
taskana-workbasket-information,taskana-task-details, taskana-workbasket-access-items, taskana-workbaskets-distribution-targets, taskana-monitor-tasks,
taskana-monitor-workbaskets, taskana-monitor-classification-tasks, taskana-access-items-management, taskana-classification-details,taskana-workbasket-details {
& .panel{
border: none;
@ -302,7 +302,7 @@ taskana-monitor-tasks, taskana-monitor-workbaskets, taskana-monitor-classificati
}
}
li.list-group-item.active:hover, {
li.list-group-item.active:hover {
color: #fff;
background-color: $green;
border-color: $green;
@ -311,7 +311,7 @@ li.list-group-item.active:hover, {
.list-group-item.active{
background-color: $green;
}
li.list-group-item:hover, {
li.list-group-item:hover {
color: #555;
text-decoration: none;
background-color: #f5f5f5;
@ -342,7 +342,43 @@ li.list-group-item:hover, {
}
.align-center {
text-align: center;
text-align: center;
}
.nav.nav-tabs {
& > li {
& > a {
min-height: 52px;
padding-top: 15px;
border-radius: 0;
margin-right: 0;
&.has-changes {
border-bottom: solid #f0ad4e;
}
cursor: pointer;
}
&:first-child > a {
border-left: none;
}
}
& > li.active > a {
border-top: 3px solid #479ea9;
padding-top: 13px;
background-color: #f5f5f5;
}
& > p {
margin: 0px;
}
& > li.disabled {
cursor: not-allowed;
}
& > li.disabled > a {
pointer-events: none;
}
}
.btn.rounded {