Skip to content

Commit cd1d409

Browse files
authored
Merge pull request DSpace#1792 from atmire/w2p-93914_delete-process
Add delete process functionality
2 parents e523583 + 154d66f commit cd1d409

9 files changed

Lines changed: 691 additions & 42 deletions
Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,99 @@
11
<div class="container" *ngVar="(processRD$ | async)?.payload as process">
22
<div class="d-flex">
3-
<h2 class="flex-grow-1">{{'process.detail.title' | translate:{id: process?.processId, name: process?.scriptName} }}</h2>
4-
<div>
5-
<button class="btn btn-lg btn-success " routerLink="/processes/new" [queryParams]="{id: process?.processId}"><i class="fas fa-plus pr-2"></i>{{'process.detail.create' | translate}}</button>
6-
</div>
3+
<h2 class="flex-grow-1">{{'process.detail.title' | translate:{
4+
id: process?.processId,
5+
name: process?.scriptName
6+
} }}</h2>
77
</div>
88
<ds-process-detail-field id="process-name" [title]="'process.detail.script'">
99
<div>{{ process?.scriptName }}</div>
1010
</ds-process-detail-field>
1111

12-
<ds-process-detail-field *ngIf="process?.parameters && process?.parameters?.length > 0" id="process-arguments" [title]="'process.detail.arguments'">
12+
<ds-process-detail-field *ngIf="process?.parameters && process?.parameters?.length > 0" id="process-arguments"
13+
[title]="'process.detail.arguments'">
1314
<div *ngFor="let argument of process?.parameters">{{ argument?.name }} {{ argument?.value }}</div>
1415
</ds-process-detail-field>
1516

1617
<div *ngVar="(filesRD$ | async)?.payload?.page as files">
17-
<ds-process-detail-field *ngIf="files && files?.length > 0" id="process-files" [title]="'process.detail.output-files'">
18-
<ds-file-download-link *ngFor="let file of files; let last=last;" [bitstream]="file">
19-
<span>{{getFileName(file)}}</span>
20-
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
21-
</ds-file-download-link>
18+
<ds-process-detail-field *ngIf="files && files?.length > 0" id="process-files"
19+
[title]="'process.detail.output-files'">
20+
<ds-file-download-link *ngFor="let file of files; let last=last;" [bitstream]="file">
21+
<span>{{getFileName(file)}}</span>
22+
<span>({{(file?.sizeBytes) | dsFileSize }})</span>
23+
</ds-file-download-link>
2224
</ds-process-detail-field>
2325
</div>
2426

25-
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time" [title]="'process.detail.start-time' | translate">
27+
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time"
28+
[title]="'process.detail.start-time' | translate">
2629
<div>{{ process.startTime | date:dateFormat:'UTC' }}</div>
2730
</ds-process-detail-field>
2831

29-
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time" [title]="'process.detail.end-time' | translate">
32+
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time"
33+
[title]="'process.detail.end-time' | translate">
3034
<div>{{ process.endTime | date:dateFormat:'UTC' }}</div>
3135
</ds-process-detail-field>
3236

33-
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status" [title]="'process.detail.status' | translate">
37+
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status"
38+
[title]="'process.detail.status' | translate">
3439
<div>{{ process.processStatus }}</div>
3540
</ds-process-detail-field>
3641

3742
<ds-process-detail-field *ngIf="isProcessFinished(process)" id="process-output" [title]="'process.detail.output'">
38-
<button *ngIf="!showOutputLogs && process?._links?.output?.href != undefined" id="showOutputButton" class="btn btn-primary" (click)="showProcessOutputLogs()">
39-
{{ 'process.detail.logs.button' | translate }}
40-
</button>
41-
<ds-themed-loading *ngIf="retrievingOutputLogs$ | async" class="ds-themed-loading" message="{{ 'process.detail.logs.loading' | translate }}"></ds-themed-loading>
42-
<pre class="font-weight-bold text-secondary bg-light p-3"
43-
*ngIf="showOutputLogs && (outputLogs$ | async)?.length > 0">{{ (outputLogs$ | async) }}</pre>
44-
<p id="no-output-logs-message" *ngIf="(!(retrievingOutputLogs$ | async) && showOutputLogs)
43+
<button *ngIf="!showOutputLogs && process?._links?.output?.href != undefined" id="showOutputButton"
44+
class="btn btn-primary" (click)="showProcessOutputLogs()">
45+
{{ 'process.detail.logs.button' | translate }}
46+
</button>
47+
<ds-themed-loading *ngIf="retrievingOutputLogs$ | async" class="ds-themed-loading"
48+
message="{{ 'process.detail.logs.loading' | translate }}"></ds-themed-loading>
49+
<pre class="font-weight-bold text-secondary bg-light p-3"
50+
*ngIf="showOutputLogs && (outputLogs$ | async)?.length > 0">{{ (outputLogs$ | async) }}</pre>
51+
<p id="no-output-logs-message" *ngIf="(!(retrievingOutputLogs$ | async) && showOutputLogs)
4552
&& !(outputLogs$ | async) || (outputLogs$ | async)?.length == 0 || !process._links.output">
46-
{{ 'process.detail.logs.none' | translate }}
47-
</p>
53+
{{ 'process.detail.logs.none' | translate }}
54+
</p>
55+
</ds-process-detail-field>
56+
57+
<ds-process-detail-field id="process-actions" [title]="'process.detail.actions'">
58+
<button class="btn btn-success mr-2" routerLink="/processes/new" [queryParams]="{id: process?.processId}"><i
59+
class="fas fa-plus pr-2"></i>{{'process.detail.create' | translate}}</button>
60+
<button *ngIf="isProcessFinished(process)" id="delete" class="btn btn-danger"
61+
(click)="openDeleteModal(deleteModal)">
62+
<i class="fas fa-trash pr-2"></i>{{ 'process.detail.delete.button' | translate }}
63+
</button>
4864
</ds-process-detail-field>
4965

5066
<div style="text-align: right;">
51-
<a class="btn btn-outline-secondary mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
67+
<a class="btn btn-outline-secondary mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
5268
</div>
5369
</div>
70+
71+
<ng-template #deleteModal >
72+
73+
<div *ngVar="(processRD$ | async)?.payload as process">
74+
75+
<div class="modal-header">
76+
<div>
77+
<h4>{{'process.detail.delete.header' | translate }}</h4>
78+
</div>
79+
<button type="button" class="close"
80+
(click)="closeModal()" aria-label="Close">
81+
<span aria-hidden="true">×</span>
82+
</button>
83+
</div>
84+
85+
<div class="modal-body">
86+
<div>{{'process.detail.delete.body' | translate }}</div>
87+
<div class="mt-4">
88+
<button class="btn btn-primary mr-2" (click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button>
89+
<button id="delete-confirm" class="btn btn-danger"
90+
(click)="deleteProcess(process)">{{ 'process.detail.delete.confirm' | translate }}
91+
</button>
92+
</div>
93+
</div>
94+
95+
96+
</div>
97+
98+
</ng-template>
99+

src/app/process-page/detail/process-detail.component.spec.ts

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,23 @@ import { RouterTestingModule } from '@angular/router/testing';
1919
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2020
import { ProcessDetailFieldComponent } from './process-detail-field/process-detail-field.component';
2121
import { Process } from '../processes/process.model';
22-
import { ActivatedRoute } from '@angular/router';
22+
import { ActivatedRoute, Router } from '@angular/router';
2323
import { of as observableOf } from 'rxjs';
2424
import { By } from '@angular/platform-browser';
2525
import { FileSizePipe } from '../../shared/utils/file-size-pipe';
2626
import { Bitstream } from '../../core/shared/bitstream.model';
2727
import { ProcessDataService } from '../../core/data/processes/process-data.service';
2828
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
29-
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
29+
import {
30+
createFailedRemoteDataObject$,
31+
createSuccessfulRemoteDataObject,
32+
createSuccessfulRemoteDataObject$
33+
} from '../../shared/remote-data.utils';
3034
import { createPaginatedList } from '../../shared/testing/utils.test';
35+
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
36+
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
37+
import { NotificationsService } from '../../shared/notifications/notifications.service';
38+
import { getProcessListRoute } from '../process-page-routing.paths';
3139

3240
describe('ProcessDetailComponent', () => {
3341
let component: ProcessDetailComponent;
@@ -44,6 +52,11 @@ describe('ProcessDetailComponent', () => {
4452

4553
let processOutput;
4654

55+
let modalService;
56+
let notificationsService;
57+
58+
let router;
59+
4760
function init() {
4861
processOutput = 'Process Started';
4962
process = Object.assign(new Process(), {
@@ -93,7 +106,8 @@ describe('ProcessDetailComponent', () => {
93106
}
94107
});
95108
processService = jasmine.createSpyObj('processService', {
96-
getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files))
109+
getFiles: createSuccessfulRemoteDataObject$(createPaginatedList(files)),
110+
delete: createSuccessfulRemoteDataObject$(null)
97111
});
98112
bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
99113
findByHref: createSuccessfulRemoteDataObject$(logBitstream)
@@ -104,13 +118,23 @@ describe('ProcessDetailComponent', () => {
104118
httpClient = jasmine.createSpyObj('httpClient', {
105119
get: observableOf(processOutput)
106120
});
121+
122+
modalService = jasmine.createSpyObj('modalService', {
123+
open: {}
124+
});
125+
126+
notificationsService = new NotificationsServiceStub();
127+
128+
router = jasmine.createSpyObj('router', {
129+
navigateByUrl:{}
130+
});
107131
}
108132

109133
beforeEach(waitForAsync(() => {
110134
init();
111135
TestBed.configureTestingModule({
112136
declarations: [ProcessDetailComponent, ProcessDetailFieldComponent, VarDirective, FileSizePipe],
113-
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
137+
imports: [TranslateModule.forRoot()],
114138
providers: [
115139
{
116140
provide: ActivatedRoute,
@@ -121,6 +145,9 @@ describe('ProcessDetailComponent', () => {
121145
{ provide: DSONameService, useValue: nameService },
122146
{ provide: AuthService, useValue: new AuthServiceMock() },
123147
{ provide: HttpClient, useValue: httpClient },
148+
{ provide: NgbModal, useValue: modalService },
149+
{ provide: NotificationsService, useValue: notificationsService },
150+
{ provide: Router, useValue: router },
124151
],
125152
schemas: [CUSTOM_ELEMENTS_SCHEMA]
126153
}).compileComponents();
@@ -207,4 +234,34 @@ describe('ProcessDetailComponent', () => {
207234
});
208235
});
209236

237+
describe('openDeleteModal', () => {
238+
it('should open the modal', () => {
239+
component.openDeleteModal({});
240+
expect(modalService.open).toHaveBeenCalledWith({});
241+
});
242+
});
243+
244+
describe('deleteProcess', () => {
245+
it('should delete the process and navigate back to the overview page on success', () => {
246+
spyOn(component, 'closeModal');
247+
component.deleteProcess(process);
248+
249+
expect(processService.delete).toHaveBeenCalledWith(process.processId);
250+
expect(notificationsService.success).toHaveBeenCalled();
251+
expect(component.closeModal).toHaveBeenCalled();
252+
expect(router.navigateByUrl).toHaveBeenCalledWith(getProcessListRoute());
253+
});
254+
it('should delete the process and not navigate on error', () => {
255+
(processService.delete as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
256+
spyOn(component, 'closeModal');
257+
258+
component.deleteProcess(process);
259+
260+
expect(processService.delete).toHaveBeenCalledWith(process.processId);
261+
expect(notificationsService.error).toHaveBeenCalled();
262+
expect(component.closeModal).not.toHaveBeenCalled();
263+
expect(router.navigateByUrl).not.toHaveBeenCalled();
264+
});
265+
});
266+
210267
});

src/app/process-page/detail/process-detail.component.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,20 @@ import { RemoteData } from '../../core/data/remote-data';
1212
import { Bitstream } from '../../core/shared/bitstream.model';
1313
import { DSpaceObject } from '../../core/shared/dspace-object.model';
1414
import {
15-
getFirstSucceededRemoteDataPayload,
16-
getFirstSucceededRemoteData
15+
getFirstCompletedRemoteData,
16+
getFirstSucceededRemoteData,
17+
getFirstSucceededRemoteDataPayload
1718
} from '../../core/shared/operators';
1819
import { URLCombiner } from '../../core/url-combiner/url-combiner';
1920
import { AlertType } from '../../shared/alert/aletr-type';
2021
import { hasValue } from '../../shared/empty.util';
2122
import { ProcessStatus } from '../processes/process-status.model';
2223
import { Process } from '../processes/process.model';
2324
import { redirectOn4xx } from '../../core/shared/authorized.operators';
25+
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
26+
import { getProcessListRoute } from '../process-page-routing.paths';
27+
import { NotificationsService } from '../../shared/notifications/notifications.service';
28+
import { TranslateService } from '@ngx-translate/core';
2429

2530
@Component({
2631
selector: 'ds-process-detail',
@@ -71,14 +76,23 @@ export class ProcessDetailComponent implements OnInit {
7176
*/
7277
dateFormat = 'yyyy-MM-dd HH:mm:ss ZZZZ';
7378

79+
/**
80+
* Reference to NgbModal
81+
*/
82+
protected modalRef: NgbModalRef;
83+
7484
constructor(protected route: ActivatedRoute,
7585
protected router: Router,
7686
protected processService: ProcessDataService,
7787
protected bitstreamDataService: BitstreamDataService,
7888
protected nameService: DSONameService,
7989
private zone: NgZone,
8090
protected authService: AuthService,
81-
protected http: HttpClient) {
91+
protected http: HttpClient,
92+
protected modalService: NgbModal,
93+
protected notificationsService: NotificationsService,
94+
protected translateService: TranslateService
95+
) {
8296
}
8397

8498
/**
@@ -172,4 +186,36 @@ export class ProcessDetailComponent implements OnInit {
172186
|| process.processStatus.toString() === ProcessStatus[ProcessStatus.FAILED].toString()));
173187
}
174188

189+
/**
190+
* Delete the current process
191+
* @param process
192+
*/
193+
deleteProcess(process: Process) {
194+
this.processService.delete(process.processId).pipe(
195+
getFirstCompletedRemoteData()
196+
).subscribe((rd) => {
197+
if (rd.hasSucceeded) {
198+
this.notificationsService.success(this.translateService.get('process.detail.delete.success'));
199+
this.closeModal();
200+
this.router.navigateByUrl(getProcessListRoute());
201+
} else {
202+
this.notificationsService.error(this.translateService.get('process.detail.delete.error'));
203+
}
204+
});
205+
}
206+
207+
/**
208+
* Open a given modal.
209+
* @param content - the modal content.
210+
*/
211+
openDeleteModal(content) {
212+
this.modalRef = this.modalService.open(content);
213+
}
214+
/**
215+
* Close the modal.
216+
*/
217+
closeModal() {
218+
this.modalRef.close();
219+
}
220+
175221
}

0 commit comments

Comments
 (0)