:bugfixes for workplace
This commit is contained in:
Lars Leo Grätz 2018-05-28 17:55:30 +02:00 committed by Martin Rojas Miguel Angel
parent ace632d814
commit 2aeb815d8e
15 changed files with 574 additions and 484 deletions

View File

@ -178,12 +178,12 @@ public class TaskController extends AbstractPagingController {
return result;
}
@RequestMapping(path = "/{taskId}/transfer/{workbasketKey}")
@RequestMapping(path = "/{taskId}/transfer/{workbasketId}")
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<TaskResource> transferTask(@PathVariable String taskId, @PathVariable String workbasketKey)
public ResponseEntity<TaskResource> transferTask(@PathVariable String taskId, @PathVariable String workbasketId)
throws TaskNotFoundException, WorkbasketNotFoundException, NotAuthorizedException, InvalidWorkbasketException,
InvalidStateException {
Task updatedTask = taskService.transfer(taskId, workbasketKey);
Task updatedTask = taskService.transfer(taskId, workbasketId);
ResponseEntity<TaskResource> result = new ResponseEntity<>(taskResourceAssembler.toResource(updatedTask),
HttpStatus.OK);
return result;

View File

@ -17,9 +17,6 @@ import {WorkbasketResource} from '../../models/workbasket-resource';
@Injectable()
export class WorkbasketService {
workbasketKey: string;
workbasketName: string;
public workBasketSelected = new Subject<string>();
public workBasketSaved = new Subject<number>();

View File

@ -17,7 +17,7 @@
</div>
<div *ngIf="currentRoute === 'tasks'" class="center-block no-detail">
<h3 class="grey">Select a Task</h3>
<!--TODO: ICON for task?-->
<span class="glyphicon glyphicon-object-align-bottom"></span>
</div>
</div>
</div>

View File

@ -4,16 +4,31 @@ import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {environment} from 'app/../environments/environment';
import {TaskResource} from 'app/workplace/models/task-resource';
import {Subject} from 'rxjs/Subject';
@Injectable()
export class TaskService {
url = `${environment.taskanaRestUrl}/v1/tasks`;
taskChangedSource = new Subject<Task>();
taskChangedStream = this.taskChangedSource.asObservable();
taskDeletedSource = new Subject<Task>();
taskDeletedStream = this.taskDeletedSource.asObservable();
publishUpdatedTask(task: Task) {
this.taskChangedSource.next(task);
}
publishDeletedTask(task: Task) {
this.taskDeletedSource.next(task);
}
constructor(private httpClient: HttpClient) {
}
findTasksWithWorkbasket(basketKey: string): Observable<TaskResource> {
return this.httpClient.get<TaskResource>(`${this.url}?workbasket-id=${basketKey}`);
findTasksWithWorkbasket(basketId: string): Observable<TaskResource> {
return this.httpClient.get<TaskResource>(`${this.url}?workbasket-id=${basketId}`);
}
getTask(id: string): Observable<Task> {
@ -28,11 +43,15 @@ export class TaskService {
return this.httpClient.post<Task>(`${this.url}/${id}/claim`, 'test');
}
transferTask(taskId: string, workbasketKey: string): Observable<Task> {
return this.httpClient.post<Task>(`${this.url}/${taskId}/transfer/${workbasketKey}`, '');
transferTask(taskId: string, workbasketId: string): Observable<Task> {
return this.httpClient.post<Task>(`${this.url}/${taskId}/transfer/${workbasketId}`, '');
}
updateTask(task: Task): Observable<Task> {
return this.httpClient.put<Task>(`${this.url}/${task.taskId}`, task);
}
deleteTask(task: Task): Observable<Task> {
return this.httpClient.delete<Task>(`${this.url}/${task.taskId}`);
}
}

View File

@ -2,21 +2,31 @@
<div class="panel panel-default">
<div class="panel-heading">
<div class="pull-right">
<div class="col-md-6">
<input class="form-control" auto-complete [(ngModel)]="workbasket" [source]="autoCompleteData"
placeholder="Transfer ..."/>
<button type="button" (click)="navigateBack()" class="btn btn-default"><span title="Cancel and return to task detail"
class="glyphicon glyphicon-arrow-left text-muted"></span>
</button>
<div class="dropdown" style="display: inline">
<button type="button" data-toggle="dropdown" aria-expanded="true" class="btn btn-default dropdown-toggle">
<span
title="Transfer task to another workbasket"
class="glyphicon glyphicon-transfer text-muted"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li *ngFor="let workbasket of workbaskets">
<a class="dropdown-item" (click)="transferTask(workbasket)">
<label>{{workbasket.name}}</label>
</a>
</li>
</ul>
</div>
<button (click)="transferTask()" class="btn-link"><span title="Transfer task to another workbasket"
class="glyphicon glyphicon-new-window text-muted"></span>
</button>
<button (click)="cancelTask()" class="btn-link"><span title="Cancel task and return to task list"
class="glyphicon glyphicon-remove-circle text-muted"></span>
</button>
<button (click)="completeTask()" class="btn-link"><span title="Complete task and return to task list"
class="glyphicon glyphicon-ok-circle text-success"></span>
<button type="button" (click)="completeTask()" class="btn btn-default"><span
title="Complete task and return to task list"
class="glyphicon glyphicon-ok blue text-success"></span>
</button>
</div>
<h4 class="panel-header"><b>{{task?.name}}</b></h4>
<div class="panel-header"><h4><b>{{task?.name}}</b></h4></div>
</div>
<div class="panel-body" *ngIf="task">

View File

@ -1,10 +1,11 @@
import {Component, OnInit} from '@angular/core';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router';
import {Task} from 'app/workplace/models/task';
import {Workbasket} from 'app/models/workbasket';
import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
import {TaskService} from 'app/workplace/services/task.service';
import {WorkbasketService} from 'app/services/workbasket/workbasket.service';
import {Subscription} from 'rxjs/Subscription';
@Component({
@ -12,15 +13,17 @@ import {WorkbasketService} from 'app/services/workbasket/workbasket.service';
templateUrl: './task.component.html',
styleUrls: ['./task.component.scss']
})
export class TaskComponent implements OnInit {
task: Task = null;
export class TaskComponent implements OnInit, OnDestroy {
routeSubscription: Subscription;
requestInProgress = false;
address = 'https://bing.com';
link: SafeResourceUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.address);
autoCompleteData: string[] = [];
workbasket: string = null;
workbasketKey: string;
task: Task = null;
workbaskets: Workbasket[];
requestInProgress = false;
constructor(private taskService: TaskService,
private workbasketService: WorkbasketService,
@ -30,8 +33,10 @@ export class TaskComponent implements OnInit {
}
ngOnInit() {
const id = this.route.snapshot.params['id'];
this.routeSubscription = this.route.params.subscribe(params => {
const id = params['id'];
this.getTask(id);
});
}
getTask(id: string) {
@ -41,50 +46,56 @@ export class TaskComponent implements OnInit {
this.requestInProgress = false;
this.task = task;
this.link = this.sanitizer.bypassSecurityTrustResourceUrl(`${this.address}/?q=${this.task.name}`);
this.workbasketService.getAllWorkBaskets().subscribe(workbaskets => {
this.workbaskets = workbaskets._embedded ? workbaskets._embedded.workbaskets : [];
this.workbaskets.forEach(workbasket => {
if (workbasket.key !== this.task.workbasketSummaryResource.key) {
this.autoCompleteData.push(workbasket.name);
}
});
});
this.getWorkbaskets();
});
}
transferTask() {
if (this.workbasket) {
this.workbaskets.forEach(workbasket => {
if (workbasket.name === this.workbasket) {
this.workbasketKey = workbasket.key;
}
});
getWorkbaskets() {
this.requestInProgress = true;
this.taskService.transferTask(this.task.taskId, this.workbasketKey).subscribe(
this.workbasketService.getAllWorkBaskets().subscribe(workbaskets => {
this.requestInProgress = false;
this.workbaskets = workbaskets._embedded ? workbaskets._embedded.workbaskets : [];
let index = -1;
for (let i = 0; i < this.workbaskets.length; i++) {
if (this.workbaskets[i].name === this.task.workbasketSummaryResource.name) {
index = i;
}
}
if (index !== -1) {
this.workbaskets.splice(index, 1);
}
});
}
transferTask(workbasket: Workbasket) {
this.requestInProgress = true;
this.taskService.transferTask(this.task.taskId, workbasket.workbasketId).subscribe(
task => {
this.requestInProgress = false;
this.task = task
});
this.navigateBack();
}
}
cancelTask() {
this.navigateBack();
}
completeTask() {
this.requestInProgress = true;
this.taskService.completeTask(this.task.taskId).subscribe(
task => {
this.requestInProgress = false;
this.task = task
});
this.task = task;
this.taskService.publishUpdatedTask(task);
this.navigateBack();
});
}
private navigateBack() {
navigateBack() {
this.router.navigate([{outlets: {detail: `taskdetail/${this.task.taskId}`}}], {relativeTo: this.route.parent});
}
ngOnDestroy(): void {
if (this.routeSubscription) {
this.routeSubscription.unsubscribe();
}
}
}

View File

@ -1,14 +1,17 @@
<taskana-spinner [isRunning]="requestInProgress"></taskana-spinner>
<div class="panel panel-default" *ngIf="task">
<div class="panel panel-default" *ngIf="task && !requestInProgress">
<div class="panel-heading">
<div class="pull-right">
<button type="button" title="Open task to work on it" class="btn btn-default" aria-label="Left Align"
(click)="openTask(task.taskId)">
[disabled]="workOnTaskDisabled()" (click)="openTask(task.taskId)">
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
</button>
<button type="button" title="Update Task" class="btn btn-default" (click)="updateTask()">
<button type="button" title="Update Task" class="btn btn-default btn-primary" (click)="updateTask()">
<span class="glyphicon glyphicon-saved" aria-hidden="true"></span>
</button>
<button type="button" title="Delete Task" class="btn btn-default btn-danger" (click)="deleteTask()">
<span class="glyphicon glyphicon-remove"></span>
</button>
</div>
<h4 class="panel-header"><b>{{task?.name}}</b></h4>
</div>
@ -18,9 +21,9 @@
<div class="col-md-6">
<div class="form-group">
<label for="task-description" class="control-label">Description</label>
<input type="text" class="form-control" id="task-description" placeholder="Description"
<textarea type="text" class="form-control" id="task-description" placeholder="Description"
[(ngModel)]="task.description"
name="task.description">
name="task.description"></textarea>
</div>
<div class="form-group">
<label for="task-owner" class="control-label">Owner</label>
@ -63,7 +66,7 @@
name="task.due">
</div>
<div class="form-group">
<label for="task-priority" class="control-label">Priority</label>
<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">

View File

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

View File

@ -1,15 +1,16 @@
import {Component, OnInit} from '@angular/core';
import {Task} from '../models/task';
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Task} from 'app/workplace/models/task';
import {ActivatedRoute, Router} from '@angular/router';
import {TaskService} from '../services/task.service';
import {TaskService} from 'app/workplace/services/task.service';
import {Subscription} from 'rxjs/Subscription';
import {Location} from '@angular/common';
@Component({
selector: 'taskana-task-details',
templateUrl: './taskdetails.component.html',
styleUrls: ['./taskdetails.component.scss']
})
export class TaskdetailsComponent implements OnInit {
export class TaskdetailsComponent implements OnInit, OnDestroy {
task: Task = null;
requestInProgress = false;
@ -17,7 +18,8 @@ export class TaskdetailsComponent implements OnInit {
constructor(private route: ActivatedRoute,
private taskService: TaskService,
private router: Router) {
private router: Router,
private location: Location) {
}
ngOnInit() {
@ -40,10 +42,28 @@ export class TaskdetailsComponent implements OnInit {
this.taskService.updateTask(this.task).subscribe(task => {
this.requestInProgress = false;
this.task = task;
this.taskService.publishUpdatedTask(task);
});
}
openTask(taskId: string) {
this.router.navigate([{outlets: {detail: `task/${taskId}`}}], {relativeTo: this.route.parent});
}
workOnTaskDisabled(): boolean {
return this.task ? this.task.state === 'COMPLETED' : false;
}
deleteTask(): void {
this.taskService.deleteTask(this.task).subscribe();
this.taskService.publishDeletedTask(this.task);
this.task = null;
this.router.navigate([`/workplace/tasks`]);
}
ngOnDestroy(): void {
if (this.routeSubscription) {
this.routeSubscription.unsubscribe();
}
}
}

View File

@ -3,7 +3,9 @@
<!--TODO: add toolbar for sorting, also add pagination-->
<div>
<ul #taskList id="task-list-container" class="list-group">
<li class="list-group-item" *ngIf="tasks === undefined || tasks.length === 0" type="text"> There are no Tasks for this workbasket </li>
<li class="list-group-item" *ngIf="tasks === undefined || tasks.length === 0" type="text">
<b>This Workbasket has no Tasks</b>
</li>
<li class="list-group-item" *ngFor="let task of tasks" [class.active]="task.taskId == selectedId"
type="text" (click)="selectTask(task.taskId)">
<div class="row">

View File

@ -18,14 +18,15 @@
ul {
max-height: 90vh;
margin-bottom: 10px;
overflow:scroll;
-webkit-overflow-scrolling: touch;
overflow: hidden;
overflow-y: scroll;
}
a > label {
height: 2em;
width: 100%;
}
dd, dt {
text-overflow: ellipsis;
white-space: nowrap;
@ -42,6 +43,7 @@ dt > i {
li > div.row > dl {
margin-bottom: 0px;
}
li > div.row > dl:first-child {
margin-left: 10px;
}

View File

@ -1,22 +1,41 @@
import {Component, Input, OnInit} from '@angular/core';
import {Task} from '../models/task';
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {Task} from 'app/workplace/models/task';
import {ActivatedRoute, Router} from '@angular/router';
import {TaskService} from 'app/workplace/services/task.service';
import {Subscription} from 'rxjs/Subscription';
@Component({
selector: 'taskana-task-list',
templateUrl: './tasklist.component.html',
styleUrls: ['./tasklist.component.scss']
})
export class TasklistComponent implements OnInit {
export class TasklistComponent implements OnInit, OnDestroy {
private columnForOrdering: string;
private taskChangeSubscription: Subscription;
private taskDeletedSubscription: Subscription;
selectedId = '';
@Input() tasks: Task[];
constructor(private router: Router,
private route: ActivatedRoute) {
private route: ActivatedRoute,
private taskService: TaskService) {
this.columnForOrdering = 'id'; // default: order tasks by id
this.taskChangeSubscription = this.taskService.taskChangedStream.subscribe(task => {
for (let i = 0; i < this.tasks.length; i++) {
if (this.tasks[i].taskId === task.taskId) {
this.tasks[i] = task;
}
}
});
this.taskDeletedSubscription = this.taskService.taskDeletedStream.subscribe(task => {
for (let i = 0; i < this.tasks.length; i++) {
if (this.tasks[i].taskId === task.taskId) {
this.tasks.splice(i, 1);
}
}
})
}
ngOnInit() {
@ -34,4 +53,9 @@ export class TasklistComponent implements OnInit {
this.selectedId = taskId;
this.router.navigate([{outlets: {detail: `taskdetail/${this.selectedId}`}}], {relativeTo: this.route});
}
ngOnDestroy(): void {
this.taskChangeSubscription.unsubscribe();
this.taskDeletedSubscription.unsubscribe();
}
}

View File

@ -1,16 +1,14 @@
<div class="row">
<div class="col-md-12">
<div class="input-group">
<input class="form-control dropdown-toggle" auto-complete [(ngModel)]="result" [source]="autoCompleteData"
<input [(ngModel)]="result" [typeahead]="workbasketNames" class="form-control"
(typeaheadOnSelect)="workbasketSelected = true" (typeaheadNoResults)="workbasketSelected = false"
placeholder="Search for Workbasket ..."/>
<span class="input-group-btn">
<button class="btn btn-primary" type="button" (click)="searchBasket()"
[disabled]="result.length==0">Go!</button>
[disabled]="!workbasketSelected">Go!</button>
</span>
</div>
<!-- /input-group -->
</div>
<!-- /.col-lg-18 -->
</div>
<!-- /.row -->

View File

@ -14,11 +14,13 @@ export class SelectorComponent implements OnInit {
tasksChanged = new EventEmitter<Task[]>();
tasks: Task[] = [];
autoCompleteData: string[] = [];
workbasketNames: string[] = [];
result = '';
resultKey: string;
resultId = '';
workbaskets: Workbasket[];
currentBasket: Workbasket;
workbasketSelected = false;
constructor(private taskService: TaskService,
private workbasketService: WorkbasketService) {
@ -28,34 +30,37 @@ export class SelectorComponent implements OnInit {
this.workbasketService.getAllWorkBaskets().subscribe(workbaskets => {
this.workbaskets = workbaskets._embedded ? workbaskets._embedded.workbaskets : [];
this.workbaskets.forEach(workbasket => {
this.autoCompleteData.push(workbasket.name);
this.workbasketNames.push(workbasket.name);
});
});
if (this.workbasketService.workbasketKey) {
this.getTasks(this.workbasketService.workbasketKey);
this.result = this.workbasketService.workbasketName;
}
}
searchBasket() {
if (this.workbaskets) {
this.workbaskets.forEach(workbasket => {
if (workbasket.name === this.result) {
this.resultKey = workbasket.workbasketId;
this.resultId = workbasket.workbasketId;
this.currentBasket = workbasket;
}
});
this.getTasks(this.resultKey);
this.workbasketService.workbasketKey = this.resultKey;
this.workbasketService.workbasketName = this.result;
if (this.resultId.length > 0) {
this.getTasks(this.resultId);
this.tasksChanged.emit(this.tasks);
} else {
this.tasks = [];
this.tasksChanged.emit(this.tasks);
}
}
this.resultId = '';
}
getTasks(workbasketKey: string) {
this.taskService.findTasksWithWorkbasket(workbasketKey).subscribe(
getTasks(workbasketId: string) {
this.taskService.findTasksWithWorkbasket(workbasketId).subscribe(
tasks => {
if (!tasks || tasks._embedded === undefined) {
this.tasks.length = 0;
if (!tasks || tasks._embedded === undefined) {
return;
}
tasks._embedded.tasks.forEach(e => this.tasks.push(e));

View File

@ -1,11 +1,10 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {Ng2AutoCompleteModule} from 'ng2-auto-complete';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {AngularSvgIconModule} from 'angular-svg-icon';
import {WorkplaceRoutingModule} from './workplace-routing.module';
import {AlertModule} from 'ngx-bootstrap';
import {AlertModule, TypeaheadModule} from 'ngx-bootstrap';
import {SelectorComponent} from './workbasket-selector/workbasket-selector.component';
import {TasklistComponent} from './tasklist/tasklist.component';
@ -23,9 +22,9 @@ import {CustomHttpClientInterceptor} from './services/custom-http-interceptor/cu
const MODULES = [
TypeaheadModule.forRoot(),
CommonModule,
FormsModule,
Ng2AutoCompleteModule,
HttpClientModule,
AngularSvgIconModule,
WorkplaceRoutingModule,