Tsk-589 Create remove confirmation component

- Create new remove confirmation component and use it for Classification, workbasket and tasks deletion.
This commit is contained in:
Martin Rojas Miguel Angel 2018-07-04 08:40:59 +02:00 committed by Holger Hagen
parent f47a3cfdd5
commit a576ed98fb
16 changed files with 224 additions and 84 deletions

View File

@ -6,12 +6,18 @@ import { RouterTestingModule } from '@angular/router/testing';
import { Component } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { AngularSvgIconModule } from 'angular-svg-icon';
import { configureTests } from 'app/app.test.configuration';
import { ClassificationDetailsComponent } from './classification-details.component';
import { SpinnerComponent } from 'app/shared/spinner/spinner.component';
import { ClassificationDefinition } from 'app/models/classification-definition';
import { LinksClassification } from 'app/models/links-classfication';
import { Pair } from 'app/models/pair';
// tslint:disable:max-line-length
import { ClassificationCategoriesService } from 'app/administration/services/classification-categories-service/classification-categories.service';
// tslint:enable:max-line-length
import { MasterAndDetailService } from 'app/services/masterAndDetail/master-and-detail.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { ClassificationsService } from 'app/administration/services/classifications/classifications.service';
@ -20,12 +26,9 @@ import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { AlertService } from 'app/services/alert/alert.service';
import { TreeService } from 'app/services/tree/tree.service';
import { ClassificationTypesService } from 'app/administration/services/classification-types/classification-types.service';
// tslint:disable:max-line-length
import { ClassificationCategoriesService } from 'app/administration/services/classification-categories-service/classification-categories.service';
// tslint:enable:max-line-length
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { configureTests } from 'app/app.test.configuration';
import { Pair } from 'app/models/pair';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
@Component({
selector: 'taskana-dummy-detail',
@ -42,10 +45,9 @@ describe('ClassificationDetailsComponent', () => {
let component: ClassificationDetailsComponent;
let fixture: ComponentFixture<ClassificationDetailsComponent>;
const treeNodes: Array<TreeNodeModel> = new Array(new TreeNodeModel());
const classificationTypes: Array<string> = new Array<string>('type1', 'type2');
let classificationsSpy, classificationsTypesSpy;
let classificationsService, classificationTypesService, classificationCategoriesService;
let treeService;
let classificationsService, classificationTypesService, classificationCategoriesService,
treeService, removeConfirmationService;
beforeEach(done => {
const configure = (testBed: TestBed) => {
@ -63,8 +65,10 @@ describe('ClassificationDetailsComponent', () => {
classificationsService = TestBed.get(ClassificationsService);
classificationTypesService = TestBed.get(ClassificationTypesService);
classificationCategoriesService = TestBed.get(ClassificationCategoriesService);
classificationsSpy = spyOn(classificationsService, 'getClassifications').and.returnValue(Observable.of(treeNodes));
classificationsTypesSpy = spyOn(classificationTypesService, 'getClassificationTypes').and.returnValue(Observable.of([]));
classificationsService = TestBed.get(ClassificationsService);
removeConfirmationService = TestBed.get(RemoveConfirmationService);
spyOn(classificationsService, 'getClassifications').and.returnValue(Observable.of(treeNodes));
spyOn(classificationTypesService, 'getClassificationTypes').and.returnValue(Observable.of([]));
spyOn(classificationCategoriesService, 'getCategories').and.returnValue(Observable.of(['firstCategory', 'secondCategory']));
spyOn(classificationsService, 'deleteClassification').and.returnValue(Observable.of(true));
spyOn(classificationCategoriesService, 'getCategoryIcon').and.returnValue(new Pair('assets/icons/categories/external.svg'));
@ -84,8 +88,8 @@ describe('ClassificationDetailsComponent', () => {
it('should trigger treeService remove node id after removing a node', () => {
const treeServiceSpy = spyOn(treeService, 'setRemovedNodeId');
component.removeClassification();
removeConfirmationService.runCallbackFunction();
expect(treeServiceSpy).toHaveBeenCalledWith('id1');
});

View File

@ -17,6 +17,8 @@ import { RequestInProgressService } from 'app/services/requestInProgress/request
import { AlertService } from 'app/services/alert/alert.service';
import { TreeService } from 'app/services/tree/tree.service';
import { ClassificationTypesService } from 'app/administration/services/classification-types/classification-types.service';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
// tslint:disable:max-line-length
import { ClassificationCategoriesService } from 'app/administration/services/classification-categories-service/classification-categories.service';
// tslint:enable:max-line-length
@ -71,7 +73,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
private classificationTypeService: ClassificationTypesService,
private categoryService: ClassificationCategoriesService,
private domainService: DomainService,
private customFieldsService: CustomFieldsService) { }
private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService) { }
ngOnInit() {
this.classificationTypeService.getClassificationTypes().subscribe((classificationTypes: Array<string>) => {
@ -126,27 +129,8 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
}
removeClassification() {
if (!this.classification || !this.classification.classificationId) {
this.errorModalService.triggerError(
new ErrorModel('There is no classification selected', 'Please check if you are creating a classification'));
return false;
}
this.requestInProgressService.setRequestInProgress(true);
this.treeService.setRemovedNodeId(this.classification.classificationId);
this.classificationRemoveSubscription = this.classificationsService
.deleteClassification(this.classification._links.self.href)
.subscribe(() => {
const key = this.classification.key;
this.classification = undefined;
this.afterRequest();
this.classificationsService.selectClassification(undefined);
this.router.navigate(['administration/classifications']);
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Classification ${key} was removed successfully`))
}, error => {
this.errorModalService.triggerError(new ErrorModel('There was error while removing your classification', error))
this.afterRequest();
})
this.removeConfirmationService.setRemoveConfirmation(this.removeClassificationConfirmation.bind(this),
`You are going to delete classification: ${this.classification.key}. Can you confirm this action?`);
}
onSave() {
@ -242,6 +226,30 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
});
}
private removeClassificationConfirmation() {
if (!this.classification || !this.classification.classificationId) {
this.errorModalService.triggerError(
new ErrorModel('There is no classification selected', 'Please check if you are creating a classification'));
return false;
}
this.requestInProgressService.setRequestInProgress(true);
this.treeService.setRemovedNodeId(this.classification.classificationId);
this.classificationRemoveSubscription = this.classificationsService
.deleteClassification(this.classification._links.self.href)
.subscribe(() => {
const key = this.classification.key;
this.classification = undefined;
this.afterRequest();
this.classificationsService.selectClassification(undefined);
this.router.navigate(['administration/classifications']);
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS, `Classification ${key} was removed successfully`))
}, error => {
this.errorModalService.triggerError(new ErrorModel('There was error while removing your classification', error))
this.afterRequest();
})
}
ngOnDestroy(): void {

View File

@ -1,9 +1,7 @@
import { Component, OnInit, Input, Output, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { ActivatedRoute, Params, Router, NavigationStart } from '@angular/router';
import { Component, OnInit, Input, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs/Subscription';
import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { ICONTYPES } from 'app/models/type';
import { ErrorModel } from 'app/models/modal-error';
import { ACTION } from 'app/models/action';
@ -16,8 +14,8 @@ import { ErrorModalService } from 'app/services/errorModal/error-modal.service';
import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets/saving-workbaskets.service';
import { WorkbasketService } from 'app/services/workbasket/workbasket.service';
import { RequestInProgressService } from 'app/services/requestInProgress/request-in-progress.service';
import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
@Component({
selector: 'taskana-workbasket-information',
@ -53,7 +51,8 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
private errorModalService: ErrorModalService,
private savingWorkbasket: SavingWorkbasketService,
private requestInProgressService: RequestInProgressService,
private customFieldsService: CustomFieldsService) {
private customFieldsService: CustomFieldsService,
private removeConfirmationService: RemoveConfirmationService) {
this.allTypes = new Map([['PERSONAL', 'Personal'], ['GROUP', 'Group'],
['CLEARANCE', 'Clearance'], ['TOPIC', 'Topic']])
@ -103,18 +102,8 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
}
removeWorkbasket() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.deleteWorkbasket(this.workbasket._links.self.href).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS,
`Workbasket ${this.workbasket.workbasketId} was removed successfully`))
this.router.navigate(['administration/workbaskets']);
}, error => {
this.requestInProgressService.setRequestInProgress(false);
this.errorModalService.triggerError(new ErrorModel(
`There was an error deleting workbasket ${this.workbasket.workbasketId}`, error))
});
this.removeConfirmationService.setRemoveConfirmation(this.onRemoveConfirmed.bind(this),
`You are going to delete workbasket: ${this.workbasket.key}. Can you confirm this action?`);
}
copyWorkbasket() {
@ -172,6 +161,21 @@ export class WorkbasketInformationComponent implements OnInit, OnChanges, OnDest
this.workbasket.modified = date;
}
private onRemoveConfirmed() {
this.requestInProgressService.setRequestInProgress(true);
this.workbasketService.deleteWorkbasket(this.workbasket._links.self.href).subscribe(response => {
this.requestInProgressService.setRequestInProgress(false);
this.workbasketService.triggerWorkBasketSaved();
this.alertService.triggerAlert(new AlertModel(AlertType.SUCCESS,
`Workbasket ${this.workbasket.workbasketId} was removed successfully`))
this.router.navigate(['administration/workbaskets']);
}, error => {
this.requestInProgressService.setRequestInProgress(false);
this.errorModalService.triggerError(new ErrorModel(
`There was an error deleting workbasket ${this.workbasket.workbasketId}`, error))
});
}
ngOnDestroy() {
if (this.workbasketSubscription) { this.workbasketSubscription.unsubscribe(); }
if (this.routeSubscription) { this.routeSubscription.unsubscribe(); }

View File

@ -5,5 +5,6 @@
<taskana-general-message-modal *ngIf="modalErrorMessage" [(message)]="modalErrorMessage" [title]="modalTitle" error="true"></taskana-general-message-modal>
<taskana-spinner [isRunning]="requestInProgress" isModal="true"></taskana-spinner>
<taskana-alert></taskana-alert>
<taskana-remove-confirmation></taskana-remove-confirmation>
</div>
</div>

View File

@ -6,12 +6,6 @@ import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientModule } from '@angular/common/http';
import { SharedModule } from 'app/shared/shared.module';
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 { OrientationService } from './services/orientation/orientation.service';
import { SelectedRouteService } from './services/selected-route/selected-route';
import { NavBarComponent } from './components/nav-bar/nav-bar.component';
describe('AppComponent', () => {
@ -32,8 +26,7 @@ describe('AppComponent', () => {
RouterTestingModule.withRoutes(routes),
HttpClientModule,
SharedModule
],
providers: [ErrorModalService, RequestInProgressService, AlertService, OrientationService, SelectedRouteService]
]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);

View File

@ -32,6 +32,7 @@ import { TitlesService } from 'app/services/titles/titles.service';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { WindowRefService } from 'app/services/window/window.service';
import { TaskanaEngineService } from 'app/services/taskana-engine/taskana-engine.service';
import { RemoveConfirmationService } from './services/remove-confirmation/remove-confirmation.service';
/**
* Components
@ -106,7 +107,8 @@ export function startupServiceFactory(startupService: StartupService): () => Pro
TreeService,
TitlesService,
CustomFieldsService,
TaskanaEngineService
TaskanaEngineService,
RemoveConfirmationService
],
bootstrap: [AppComponent]
})

View File

@ -8,11 +8,18 @@ import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import { TaskanaEngineServiceMock } from './services/taskana-engine/taskana-engine.mock.service';
import { TaskanaEngineService } from './services/taskana-engine/taskana-engine.service';
import { DomainService } from './services/domain/domain.service';
import { DomainServiceMock } from './services/domain/domain.service.mock';
import { CustomFieldsService } from 'app/services/custom-fields/custom-fields.service';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
import { AlertService } from './services/alert/alert.service';
import { ErrorModalService } from './services/errorModal/error-modal.service';
import { RequestInProgressService } from './services/requestInProgress/request-in-progress.service';
import { OrientationService } from './services/orientation/orientation.service';
import { SelectedRouteService } from './services/selected-route/selected-route';
export const configureTests = (configure: (testBed: TestBed) => void) => {
@ -27,7 +34,8 @@ export const configureTests = (configure: (testBed: TestBed) => void) => {
configure(testBed);
testBed.configureTestingModule({
providers: [{ provide: TaskanaEngineService, useClass: TaskanaEngineServiceMock },
{ provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService]
{ provide: DomainService, useClass: DomainServiceMock }, CustomFieldsService, RemoveConfirmationService,
AlertService, ErrorModalService, RequestInProgressService, OrientationService, SelectedRouteService]
});
return testBed.compileComponents().then(() => testBed);

View File

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class RemoveConfirmationService {
private removeConfirmationCallbackSubject = new Subject<{ callback: Function, message: string }>();
private removeConfirmationCallback: Function;
constructor() { }
setRemoveConfirmation(callback: Function, message: string) {
this.removeConfirmationCallback = callback;
this.removeConfirmationCallbackSubject.next({ callback: callback, message: message });
}
getRemoveConfirmation(): Observable<{ callback: Function, message: string }> {
return this.removeConfirmationCallbackSubject.asObservable();
}
runCallbackFunction() {
this.removeConfirmationCallback();
}
}

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter, DoCheck } from '@angular/core';
import { Component, Input, ViewChild, OnChanges, SimpleChanges, Output, EventEmitter } from '@angular/core';
declare var $: any;
@Component({

View File

@ -0,0 +1,24 @@
<div #removeConfirmationModal class="modal fade" tabindex="-1" data-backdrop="static" data-keyboard="false" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content word-break">
<div class="modal-header">
<h4 class="modal-title" id="generalModalLabel">Delete confirmation</h4>
</div>
<div class="modal-body">
<div class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
{{message}}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-danger" data-dismiss="modal" data-toggle="tooltip" title="Cancel">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</button>
<button type="button" class="btn btn-default btn-primary" data-dismiss="modal" data-toggle="tooltip" title="Confirm" (click)="confirmAction()">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,4 @@
.word-break{word-break: break-word; }
.modal{
z-index: 1051;
}

View File

@ -0,0 +1,29 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RemoveConfirmationComponent } from './remove-confirmation.component';
import { configureTests } from 'app/app.test.configuration';
import { RemoveConfirmationService } from '../../services/remove-confirmation/remove-confirmation.service';
describe('RemoveConfirmationComponent', () => {
let component: RemoveConfirmationComponent;
let fixture: ComponentFixture<RemoveConfirmationComponent>;
beforeEach(done => {
const configure = (testBed: TestBed) => {
testBed.configureTestingModule({
declarations: [RemoveConfirmationComponent],
providers: []
})
};
configureTests(configure).then(testBed => {
fixture = TestBed.createComponent(RemoveConfirmationComponent);
component = fixture.componentInstance;
TestBed.get(RemoveConfirmationService);
fixture.detectChanges();
done();
});
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,34 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { RemoveConfirmationService } from 'app/services/remove-confirmation/remove-confirmation.service';
declare var $: any;
@Component({
selector: 'taskana-remove-confirmation',
templateUrl: './remove-confirmation.component.html',
styleUrls: ['./remove-confirmation.component.scss']
})
export class RemoveConfirmationComponent implements OnInit {
private confirmationCallback: Function;
message: string;
@ViewChild('removeConfirmationModal')
private modal;
constructor(private removeConfirmationService: RemoveConfirmationService) { }
ngOnInit() {
this.removeConfirmationService.getRemoveConfirmation().subscribe(({ callback, message }) => {
this.confirmationCallback = callback;
this.message = message;
$(this.modal.nativeElement).modal('toggle');
})
}
confirmAction() {
this.confirmationCallback();
}
}

View File

@ -14,6 +14,7 @@ import { AlertComponent } from 'app/shared/alert/alert.component';
import { MasterAndDetailComponent } from 'app/shared/master-and-detail/master-and-detail.component';
import { TaskanaTreeComponent } from 'app/shared/tree/tree.component';
import { TypeAheadComponent } from 'app/shared/type-ahead/type-ahead.component';
import { RemoveConfirmationComponent } from 'app/shared/remove-confirmation/remove-confirmation.component';
/**
* Pipes
@ -30,7 +31,7 @@ import { MapToIterable } from './pipes/mapToIterable/mapToIterable';
*/
import { HttpClientInterceptor } from './services/httpClientInterceptor/http-client-interceptor.service';
import { AccessIdsService } from './services/access-ids/access-ids.service';
import {SortComponent} from './sort/sort.component';
import { SortComponent } from './sort/sort.component';
@ -58,7 +59,8 @@ const DECLARATIONS = [
SpreadNumberPipe,
OrderBy,
MapToIterable,
SortComponent
SortComponent,
RemoveConfirmationComponent
];
@NgModule({

View File

@ -1,9 +1,11 @@
import {Component, 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';
import {Location} from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
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';
@Component({
selector: 'taskana-task-details',
@ -17,9 +19,9 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
private routeSubscription: Subscription;
constructor(private route: ActivatedRoute,
private taskService: TaskService,
private router: Router,
private location: Location) {
private taskService: TaskService,
private router: Router,
private removeConfirmationService: RemoveConfirmationService) {
}
ngOnInit() {
@ -47,7 +49,7 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
}
openTask(taskId: string) {
this.router.navigate([{outlets: {detail: `task/${taskId}`}}], {relativeTo: this.route.parent});
this.router.navigate([{ outlets: { detail: `task/${taskId}` } }], { relativeTo: this.route.parent });
}
workOnTaskDisabled(): boolean {
@ -55,6 +57,11 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
}
deleteTask(): void {
this.removeConfirmationService.setRemoveConfirmation(this.deleteTaskConfirmation.bind(this),
`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;

View File

@ -1,22 +1,17 @@
<div class="row">
<div class="col-md-6">
<div class="input-group">
<input [(ngModel)]="result" [typeahead]="workbasketNames" class="form-control"
(typeaheadOnSelect)="workbasketSelected = true" (typeaheadNoResults)="workbasketSelected = false"
placeholder="Search for Workbasket ..."/>
<input [(ngModel)]="result" [typeahead]="workbasketNames" class="form-control" (typeaheadOnSelect)="workbasketSelected = true"
typeaheadMinLength="0" (typeaheadNoResults)="workbasketSelected = false" placeholder="Search for Workbasket ..." />
<span class="input-group-btn">
<button class="btn btn-primary" type="button" (click)="searchBasket()"
[disabled]="!workbasketSelected">Go!</button>
<button class="btn btn-primary" type="button" (click)="searchBasket()" [disabled]="!workbasketSelected">Go!</button>
</span>
</div>
</div>
<div class="col-md-6">
<div class="pull-right margin-right">
<taskana-sort
[sortingFields]="sortingFields"
(performSorting)="sorting($event)"></taskana-sort>
<taskana-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)"></taskana-sort>
</div>
</div>
</div>