Skip to content

Commit 4f4dbff

Browse files
authored
Merge pull request DSpace#2227 from 4Science/DURACOM-134
Administer workflow actions
2 parents e2c5171 + 13662e3 commit 4f4dbff

16 files changed

Lines changed: 341 additions & 11 deletions

src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { URLCombiner } from '../../../../../core/url-combiner/url-combiner';
1111
import { WorkspaceItemAdminWorkflowActionsComponent } from './workspace-item-admin-workflow-actions.component';
1212
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
1313
import {
14-
getWorkflowItemDeleteRoute,
14+
getWorkspaceItemDeleteRoute,
1515
} from '../../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths';
1616
import { Item } from '../../../../../core/shared/item.model';
1717
import { RemoteData } from '../../../../../core/data/remote-data';
@@ -83,7 +83,7 @@ describe('WorkspaceItemAdminWorkflowActionsComponent', () => {
8383
it('should render a delete button with the correct link', () => {
8484
const button = fixture.debugElement.query(By.css('a.delete-link'));
8585
const link = button.nativeElement.href;
86-
expect(link).toContain(new URLCombiner(getWorkflowItemDeleteRoute(wsi.id)).toString());
86+
expect(link).toContain(new URLCombiner(getWorkspaceItemDeleteRoute(wsi.id)).toString());
8787
});
8888

8989
it('should render a policies button with the correct link', () => {

src/app/admin/admin-workflow-page/admin-workflow-search-results/actions/workspace-item/workspace-item-admin-workflow-actions.component.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
SupervisionOrderGroupSelectorComponent
1212
} from './supervision-order-group-selector/supervision-order-group-selector.component';
1313
import {
14-
getWorkflowItemDeleteRoute
14+
getWorkspaceItemDeleteRoute
1515
} from '../../../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths';
1616
import { ITEM_EDIT_AUTHORIZATIONS_PATH } from '../../../../../item-page/edit-item-page/edit-item-page.routing-paths';
1717
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
@@ -105,10 +105,10 @@ export class WorkspaceItemAdminWorkflowActionsComponent implements OnInit {
105105
}
106106

107107
/**
108-
* Returns the path to the delete page of this workflow item
108+
* Returns the path to the delete page of this workspace item
109109
*/
110110
getDeleteRoute(): string {
111-
return getWorkflowItemDeleteRoute(this.wsi.id);
111+
return getWorkspaceItemDeleteRoute(this.wsi.id);
112112
}
113113

114114
/**

src/app/workflowitems-edit-page/workflow-item-action-page.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Component, OnInit } from '@angular/core';
22
import { Location } from '@angular/common';
3-
import { Observable, forkJoin } from 'rxjs';
3+
import { Observable, combineLatest } from 'rxjs';
44
import { map, switchMap, take } from 'rxjs/operators';
55
import { TranslateService } from '@ngx-translate/core';
66
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
@@ -52,7 +52,7 @@ export abstract class WorkflowItemActionPageComponent implements OnInit {
5252
* Performs the action and shows a notification based on the outcome of the action
5353
*/
5454
performAction() {
55-
forkJoin([this.wfi$, this.requestService.removeByHrefSubstring('/discover')]).pipe(
55+
combineLatest([this.wfi$, this.requestService.removeByHrefSubstring('/discover')]).pipe(
5656
take(1),
5757
switchMap(([wfi]) => this.sendRequest(wfi.id))
5858
).subscribe((successful: boolean) => {

src/app/workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { URLCombiner } from '../core/url-combiner/url-combiner';
2-
import { getWorkflowItemModuleRoute } from '../app-routing-paths';
2+
import { getWorkflowItemModuleRoute, getWorkspaceItemModuleRoute } from '../app-routing-paths';
33

44
export function getWorkflowItemPageRoute(wfiId: string) {
55
return new URLCombiner(getWorkflowItemModuleRoute(), wfiId).toString();
@@ -24,8 +24,13 @@ export function getAdvancedWorkflowRoute(wfiId: string) {
2424
return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, ADVANCED_WORKFLOW_PATH).toString();
2525
}
2626

27+
export function getWorkspaceItemDeleteRoute(wsiId: string) {
28+
return new URLCombiner(getWorkspaceItemModuleRoute(), wsiId, WORKSPACE_ITEM_DELETE_PATH).toString();
29+
}
30+
2731
export const WORKFLOW_ITEM_EDIT_PATH = 'edit';
2832
export const WORKFLOW_ITEM_DELETE_PATH = 'delete';
2933
export const WORKFLOW_ITEM_VIEW_PATH = 'view';
3034
export const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback';
3135
export const ADVANCED_WORKFLOW_PATH = 'advanced';
36+
export const WORKSPACE_ITEM_DELETE_PATH = 'delete';
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { ThemedComponent } from '../../shared/theme-support/themed.component';
2+
import { Component } from '@angular/core';
3+
import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page.component';
4+
5+
/**
6+
* Themed wrapper for WorkspaceItemsDeletePageComponent
7+
*/
8+
9+
@Component({
10+
selector: 'ds-themed-workspace-items-delete',
11+
styleUrls: [],
12+
templateUrl: './../../shared/theme-support/themed.component.html'
13+
})
14+
export class ThemedWorkspaceItemsDeletePageComponent extends ThemedComponent<WorkspaceItemsDeletePageComponent> {
15+
protected getComponentName(): string {
16+
return 'WorkspaceItemsDeletePageComponent';
17+
}
18+
19+
protected importThemedComponent(themeName: string): Promise<any> {
20+
return import(`../../../themes/${themeName}/app/workflowitems-edit-page/workflow-item-delete/workflow-item-delete.component`);
21+
}
22+
23+
protected importUnthemedComponent(): Promise<any> {
24+
return import(`./workspaceitems-delete-page.component`);
25+
}
26+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<div class="container">
2+
<h2>{{ 'workspace-item.delete.header' | translate }}</h2>
3+
<ds-modify-item-overview *ngIf="(dso$ | async)" [item]="(dso$ | async)"></ds-modify-item-overview>
4+
<button class="btn btn-default" (click)="previousPage()">{{ 'workspace-item.delete.button.cancel' | translate }}</button>
5+
<button class="btn btn-danger" (click)="$event.preventDefault();confirmDelete(content)">{{ 'workspace-item.delete.button.confirm' | translate }}</button>
6+
</div>
7+
8+
<ng-template #content let-c="close" let-d="dismiss" id="delete-modal">
9+
<div class="modal-header">
10+
<h4 class="modal-title text-danger">{{ 'workspace-item.delete.header' | translate }}</h4>
11+
<button type="button" id="delete_close" class="close" aria-label="Close" (click)="d('cancel')">
12+
<span aria-hidden="true">&times;</span>
13+
</button>
14+
</div>
15+
<div class="modal-body">
16+
<p>{{ 'submission.general.discard.confirm.info' | translate }}</p>
17+
</div>
18+
<div class="modal-footer">
19+
<button type="button" id="delete_cancel" class="btn btn-secondary"
20+
(click)="c('cancel')">{{ 'submission.general.discard.confirm.cancel' | translate }}</button>
21+
<button type="button" id="delete_confirm" class="btn btn-danger"
22+
(click)="c('ok')">{{ 'submission.general.discard.confirm.submit' | translate }}</button>
23+
</div>
24+
</ng-template>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
:host ::ng-deep ds-modify-item-overview table {
3+
display: inline-table !important;
4+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { RouteService } from '../../core/services/route.service';
2+
import { NotificationsService } from '../../shared/notifications/notifications.service';
3+
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
4+
import { RouterMock } from '../../shared/mocks/router.mock';
5+
import { ComponentFixture, TestBed } from '@angular/core/testing';
6+
7+
import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page.component';
8+
import { ActivatedRoute, Router } from '@angular/router';
9+
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
10+
import { TranslateModule, TranslateService } from '@ngx-translate/core';
11+
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
12+
import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
13+
import { Location } from '@angular/common';
14+
import { of as observableOf } from 'rxjs';
15+
import { routeServiceStub } from '../../shared/testing/route-service.stub';
16+
import { LocationStub } from '../../shared/testing/location.stub';
17+
import { By } from '@angular/platform-browser';
18+
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
19+
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
20+
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
21+
import { DSpaceObject } from '../../core/shared/dspace-object.model';
22+
23+
describe('WorkspaceitemsDeletePageComponent', () => {
24+
let component: WorkspaceItemsDeletePageComponent;
25+
let fixture: ComponentFixture<WorkspaceItemsDeletePageComponent>;
26+
27+
const workspaceitemDataServiceSpy = jasmine.createSpyObj('WorkspaceitemDataService', {
28+
delete: observableOf(createSuccessfulRemoteDataObject({}))
29+
});
30+
31+
const wsi = new WorkspaceItem();
32+
wsi.id = '1234';
33+
const dso = new DSpaceObject();
34+
dso.uuid = '1234';
35+
36+
const translateServiceStub = {
37+
get: () => observableOf('test-message'),
38+
onLangChange: new EventEmitter(),
39+
onTranslationChange: new EventEmitter(),
40+
onDefaultLangChange: new EventEmitter()
41+
};
42+
43+
const modalService = {
44+
open: () => {/** empty */},
45+
};
46+
47+
beforeEach(async () => {
48+
await TestBed.configureTestingModule({
49+
imports: [TranslateModule.forRoot()],
50+
declarations: [WorkspaceItemsDeletePageComponent],
51+
providers: [
52+
{
53+
provide: ActivatedRoute,
54+
useValue: new ActivatedRouteStub(
55+
{},
56+
{
57+
wsi: createSuccessfulRemoteDataObject(wsi),
58+
dso: createSuccessfulRemoteDataObject(dso),
59+
}
60+
),
61+
},
62+
{ provide: Router, useValue: new RouterMock() },
63+
{
64+
provide: WorkspaceitemDataService,
65+
useValue: workspaceitemDataServiceSpy,
66+
},
67+
{ provide: Location, useValue: new LocationStub() },
68+
{ provide: NgbModal, useValue: modalService },
69+
{
70+
provide: NotificationsService,
71+
useValue: new NotificationsServiceStub(),
72+
},
73+
{ provide: TranslateService, useValue: translateServiceStub },
74+
{ provide: RouteService, useValue: routeServiceStub },
75+
],
76+
schemas: [NO_ERRORS_SCHEMA],
77+
}).compileComponents();
78+
});
79+
80+
beforeEach(() => {
81+
fixture = TestBed.createComponent(WorkspaceItemsDeletePageComponent);
82+
component = fixture.componentInstance;
83+
fixture.detectChanges();
84+
});
85+
86+
it('should create', () => {
87+
expect(component).toBeTruthy();
88+
});
89+
90+
it('should have the current WorkspaceItem', () => {
91+
(component as any).activatedRoute.data.subscribe((data) => {
92+
expect(data.wsi.payload.id).toEqual('1234');
93+
});
94+
});
95+
96+
it('should delete the target workspace item', () => {
97+
spyOn((component as any).modalService, 'open').and.returnValue({});
98+
component.confirmDelete(By.css('#delete-modal'));
99+
fixture.detectChanges();
100+
expect((component as any).modalService.open).toHaveBeenCalled();
101+
});
102+
103+
it('should call workspaceItemService.delete', () => {
104+
component.sendDeleteRequest();
105+
expect((component as any).workspaceItemService.delete).toHaveBeenCalledWith('1234');
106+
});
107+
});
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { NotificationsService } from '../../shared/notifications/notifications.service';
2+
import { NoContent } from '../../core/shared/NoContent.model';
3+
import { RouteService } from '../../core/services/route.service';
4+
import { getFirstCompletedRemoteData, getRemoteDataPayload } from '../../core/shared/operators';
5+
import { RemoteData } from '../../core/data/remote-data';
6+
import { Component, OnInit } from '@angular/core';
7+
import { WorkspaceItem } from '../../core/submission/models/workspaceitem.model';
8+
import { map, Observable, switchMap, take } from 'rxjs';
9+
import { ActivatedRoute, Data, Params, Router } from '@angular/router';
10+
import { Location } from '@angular/common';
11+
import { WorkspaceitemDataService } from '../../core/submission/workspaceitem-data.service';
12+
import { TranslateService } from '@ngx-translate/core';
13+
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
14+
import { DSpaceObject } from '../../core/shared/dspace-object.model';
15+
16+
@Component({
17+
selector: 'ds-workspaceitems-delete-page',
18+
templateUrl: './workspaceitems-delete-page.component.html',
19+
styleUrls: ['./workspaceitems-delete-page.component.scss']
20+
})
21+
export class WorkspaceItemsDeletePageComponent implements OnInit {
22+
23+
/**
24+
* The workspaceitem to delete
25+
*/
26+
public wsi$: Observable<WorkspaceItem>;
27+
28+
/**
29+
* The dspace object
30+
*/
31+
public dso$: Observable<DSpaceObject>;
32+
33+
/**
34+
* The previous query parameters
35+
*/
36+
private previousQueryParameters?: Params;
37+
38+
constructor(
39+
private activatedRoute: ActivatedRoute,
40+
private router: Router,
41+
private routeService: RouteService,
42+
private workspaceItemService: WorkspaceitemDataService,
43+
private notificationsService: NotificationsService,
44+
private translationService: TranslateService,
45+
private location: Location,
46+
private modalService: NgbModal,
47+
) { }
48+
49+
ngOnInit(): void {
50+
this.wsi$ = this.activatedRoute.data.pipe(map((data: Data) => data.wsi as RemoteData<WorkspaceItem>), getRemoteDataPayload());
51+
this.dso$ = this.activatedRoute.data.pipe(map((data: Data) => data.dso as RemoteData<WorkspaceItem>), getRemoteDataPayload());
52+
this.previousQueryParameters = (this.location.getState() as { [key: string]: any }).previousQueryParams;
53+
}
54+
55+
/**
56+
* Navigates to the previous url
57+
* If there's not previous url, it continues to the mydspace page instead
58+
*/
59+
previousPage() {
60+
this.routeService.getPreviousUrl().pipe(take(1))
61+
.subscribe((url: string) => {
62+
let params: Params = {};
63+
if (!url) {
64+
url = '/mydspace';
65+
params = this.previousQueryParameters;
66+
}
67+
if (url.split('?').length > 1) {
68+
for (const param of url.split('?')[1].split('&')) {
69+
params[param.split('=')[0]] = decodeURIComponent(param.split('=')[1]);
70+
}
71+
}
72+
void this.router.navigate([url.split('?')[0]], { queryParams: params });
73+
}
74+
);
75+
}
76+
77+
/**
78+
* Open the modal to confirm the deletion of the workspaceitem
79+
*/
80+
public async confirmDelete(content) {
81+
await this.modalService.open(content).result.then(
82+
(result) => {
83+
if (result === 'ok') {
84+
this.sendDeleteRequest();
85+
}
86+
}
87+
);
88+
}
89+
90+
/**
91+
* Delete the target workspaceitem object
92+
*/
93+
sendDeleteRequest() {
94+
this.wsi$.pipe(
95+
switchMap((wsi: WorkspaceItem) => this.workspaceItemService.delete(wsi.id).pipe(
96+
getFirstCompletedRemoteData(),
97+
))
98+
).subscribe((response: RemoteData<NoContent>) => {
99+
if (response.hasSucceeded) {
100+
const title = this.translationService.get('workspace-item.delete.notification.success.title');
101+
const content = this.translationService.get('workspace-item.delete.title');
102+
this.notificationsService.success(title, content);
103+
this.previousPage();
104+
} else {
105+
const title = this.translationService.get('workspace-item.delete.notification.error.title');
106+
const content = this.translationService.get('workspace-item.delete.notification.error.content');
107+
this.notificationsService.error(title, content);
108+
}
109+
});
110+
}
111+
}

src/app/workspaceitems-edit-page/workspaceitems-edit-page-routing.module.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso
77
import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item-page.component';
88
import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
99
import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
10+
import { WorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page/workspaceitems-delete-page.component';
11+
import { ThemedWorkspaceItemsDeletePageComponent } from './workspaceitems-delete-page/themed-workspaceitems-delete-page.component';
1012

1113
@NgModule({
1214
imports: [
@@ -34,7 +36,27 @@ import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
3436
breadcrumb: I18nBreadcrumbResolver
3537
},
3638
data: { title: 'workspace-item.view.title', breadcrumbKey: 'workspace-item.view' }
37-
}
39+
},
40+
{
41+
canActivate: [AuthenticatedGuard],
42+
path: 'delete',
43+
component: WorkspaceItemsDeletePageComponent,
44+
resolve: {
45+
dso: ItemFromWorkspaceResolver,
46+
breadcrumb: I18nBreadcrumbResolver
47+
},
48+
data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' }
49+
},
50+
{
51+
canActivate: [AuthenticatedGuard],
52+
path: 'delete',
53+
component: ThemedWorkspaceItemsDeletePageComponent,
54+
resolve: {
55+
dso: ItemFromWorkspaceResolver,
56+
breadcrumb: I18nBreadcrumbResolver
57+
},
58+
data: { title: 'workspace-item.delete', breadcrumbKey: 'workspace-item.delete' }
59+
},
3860
]
3961
}
4062
])

0 commit comments

Comments
 (0)