Merge pull request #1631 from 4Science/CST-5253-change-approach-workflow-approval

Add view item button for mydspace objects
This commit is contained in:
Tim Donohue
2022-05-11 17:14:56 -05:00
committed by GitHub
26 changed files with 365 additions and 132 deletions

View File

@@ -70,6 +70,12 @@ export function getWorkflowItemModuleRoute() {
return `/${WORKFLOW_ITEM_MODULE_PATH}`; return `/${WORKFLOW_ITEM_MODULE_PATH}`;
} }
export const WORKSPACE_ITEM_MODULE_PATH = 'workspaceitems';
export function getWorkspaceItemModuleRoute() {
return `/${WORKSPACE_ITEM_MODULE_PATH}`;
}
export function getDSORoute(dso: DSpaceObject): string { export function getDSORoute(dso: DSpaceObject): string {
if (hasValue(dso)) { if (hasValue(dso)) {
switch ((dso as any).type) { switch ((dso as any).type) {

View File

@@ -0,0 +1,43 @@
import { DSpaceObject } from './../../shared/dspace-object.model';
import { followLink } from './../../../shared/utils/follow-link-config.model';
import { ChildHALResource } from './../../shared/child-hal-resource.model';
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { switchMap } from 'rxjs/operators';
import { DataService } from '../../data/data.service';
import { RemoteData } from '../../data/remote-data';
import { getFirstCompletedRemoteData } from '../../shared/operators';
/**
* This class represents a resolver that requests a specific item before the route is activated
*/
@Injectable()
export class SubmissionObjectResolver<T> implements Resolve<RemoteData<T>> {
constructor(
protected dataService: DataService<any>,
protected store: Store<any>
) {
}
/**
* Method for resolving an item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<T>> {
const itemRD$ = this.dataService.findById(route.params.id,
true,
false,
followLink('item'),
).pipe(
getFirstCompletedRemoteData(),
switchMap((wfiRD: RemoteData<any>) => wfiRD.payload.item as Observable<RemoteData<T>>),
getFirstCompletedRemoteData()
);
return itemRD$;
}
}

View File

@@ -12,26 +12,28 @@
[tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button> [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
</div> </div>
</div> </div>
<div class="simple-view-link my-3" *ngIf="!fromWfi"> <div class="simple-view-link my-3" *ngIf="!fromSubmissionObject">
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]"> <a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
{{"item.page.link.simple" | translate}} {{"item.page.link.simple" | translate}}
</a> </a>
</div> </div>
<table class="table table-responsive table-striped"> <div class="table-responsive">
<tbody> <table class="table table-striped">
<ng-container *ngFor="let mdEntry of (metadata$ | async) | keyvalue"> <tbody>
<tr *ngFor="let mdValue of mdEntry.value"> <ng-container *ngFor="let mdEntry of (metadata$ | async) | keyvalue">
<td>{{mdEntry.key}}</td> <tr *ngFor="let mdValue of mdEntry.value">
<td>{{mdValue.value}}</td> <td>{{mdEntry.key}}</td>
<td>{{mdValue.language}}</td> <td>{{mdValue.value}}</td>
</tr> <td>{{mdValue.language}}</td>
</ng-container> </tr>
</tbody> </ng-container>
</table> </tbody>
</table>
</div>
<ds-item-page-full-file-section [item]="item"></ds-item-page-full-file-section> <ds-item-page-full-file-section [item]="item"></ds-item-page-full-file-section>
<ds-item-page-collections [item]="item"></ds-item-page-collections> <ds-item-page-collections [item]="item"></ds-item-page-collections>
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions> <ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
<div class="button-row bottom" *ngIf="fromWfi"> <div class="button-row bottom" *ngIf="fromSubmissionObject">
<div class="text-right"> <div class="text-right">
<button class="btn btn-outline-secondary mr-1" (click)="back()"><i <button class="btn btn-outline-secondary mr-1" (click)="back()"><i
class="fas fa-arrow-left"></i> {{'item.page.return' | translate}}</button> class="fas fa-arrow-left"></i> {{'item.page.return' | translate}}</button>

View File

@@ -112,7 +112,7 @@ describe('FullItemPageComponent', () => {
}); });
it('should show simple view button when not originated from workflow item', () => { it('should show simple view button when not originated from workflow item', () => {
expect(comp.fromWfi).toBe(false); expect(comp.fromSubmissionObject).toBe(false);
const simpleViewBtn = fixture.debugElement.query(By.css('.simple-view-link')); const simpleViewBtn = fixture.debugElement.query(By.css('.simple-view-link'));
expect(simpleViewBtn).toBeTruthy(); expect(simpleViewBtn).toBeTruthy();
}); });
@@ -122,7 +122,7 @@ describe('FullItemPageComponent', () => {
comp.ngOnInit(); comp.ngOnInit();
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(comp.fromWfi).toBe(true); expect(comp.fromSubmissionObject).toBe(true);
const simpleViewBtn = fixture.debugElement.query(By.css('.simple-view-link')); const simpleViewBtn = fixture.debugElement.query(By.css('.simple-view-link'));
expect(simpleViewBtn).toBeFalsy(); expect(simpleViewBtn).toBeFalsy();
}); });

View File

@@ -2,7 +2,7 @@ import { filter, map } from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Data, Router } from '@angular/router'; import { ActivatedRoute, Data, Router } from '@angular/router';
import { Observable , BehaviorSubject } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { ItemPageComponent } from '../simple/item-page.component'; import { ItemPageComponent } from '../simple/item-page.component';
import { MetadataMap } from '../../core/shared/metadata.models'; import { MetadataMap } from '../../core/shared/metadata.models';
@@ -37,9 +37,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
metadata$: Observable<MetadataMap>; metadata$: Observable<MetadataMap>;
/** /**
* True when the itemRD has been originated from its workflowitem, false otherwise. * True when the itemRD has been originated from its workspaceite/workflowitem, false otherwise.
*/ */
fromWfi = false; fromSubmissionObject = false;
subs = []; subs = [];
@@ -61,7 +61,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
map((item: Item) => item.metadata),); map((item: Item) => item.metadata),);
this.subs.push(this.route.data.subscribe((data: Data) => { this.subs.push(this.route.data.subscribe((data: Data) => {
this.fromWfi = hasValue(data.wfi); this.fromSubmissionObject = hasValue(data.wfi) || hasValue(data.wsi);
}) })
); );
} }

View File

@@ -1,22 +1,16 @@
<ng-container *ngVar="(actionRD$ | async)?.payload as workflowAction"> <ng-container *ngVar="(actionRD$ | async)?.payload as workflowAction">
<div class="mt-1 mb-3 space-children-mr"> <div class="mt-1 mb-3 space-children-mr">
<ds-claimed-task-actions-loader *ngFor="let option of workflowAction?.options" <ds-claimed-task-actions-loader *ngFor="let option of workflowAction?.options" [option]="option" [object]="object"
[option]="option" (processCompleted)="this.processCompleted.emit($event)">
[object]="object"
(processCompleted)="this.processCompleted.emit($event)">
</ds-claimed-task-actions-loader> </ds-claimed-task-actions-loader>
<ng-container *ngIf="hasViewAction(workflowAction)"> <button class="btn btn-primary workflow-view" ngbTooltip="{{'submission.workflow.generic.view-help' | translate}}"
<button class="btn btn-primary workflow-view" [routerLink]="[getWorkflowItemViewRoute((workflowitem$ | async))]">
ngbTooltip="{{'submission.workflow.generic.view-help' | translate}}" <i class="fa fa-info-circle"></i> {{"submission.workflow.generic.view" | translate}}
[routerLink]="[getWorkflowItemViewRoute((workflowitem$ | async))]"> </button>
<i class="fa fa-info-circle"></i> {{"submission.workflow.generic.view" | translate}}
</button>
</ng-container>
<ds-claimed-task-actions-loader [option]="returnToPoolOption" <ds-claimed-task-actions-loader [option]="returnToPoolOption" [object]="object"
[object]="object" (processCompleted)="this.processCompleted.emit($event)">
(processCompleted)="this.processCompleted.emit($event)">
</ds-claimed-task-actions-loader> </ds-claimed-task-actions-loader>
</div> </div>
</ng-container> </ng-container>

View File

@@ -161,25 +161,22 @@ describe('ClaimedTaskActionsComponent', () => {
}); });
})); }));
describe('when edit options is not available', () => { it('should display a view button', waitForAsync(() => {
it('should display a view button', waitForAsync(() => { component.object = null;
component.object = null; component.initObjects(mockObject);
component.initObjects(mockObject); fixture.detectChanges();
fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
const debugElement = fixture.debugElement.query(By.css('.workflow-view')); const debugElement = fixture.debugElement.query(By.css('.workflow-view'));
expect(debugElement).toBeTruthy(); expect(debugElement).toBeTruthy();
expect(debugElement.nativeElement.innerText.trim()).toBe('submission.workflow.generic.view'); expect(debugElement.nativeElement.innerText.trim()).toBe('submission.workflow.generic.view');
}); });
})); }));
it('getWorkflowItemViewRoute should return the combined uri to show a workspaceitem', waitForAsync(() => {
const href = component.getWorkflowItemViewRoute(workflowitem);
expect(href).toEqual('/workflowitems/333/view');
}));
});
it('getWorkflowItemViewRoute should return the combined uri to show a workspaceitem', waitForAsync(() => {
const href = component.getWorkflowItemViewRoute(workflowitem);
expect(href).toEqual('/workflowitems/333/view');
}));
}); });

View File

@@ -102,14 +102,6 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent<Claime
this.actionRD$ = object.action; this.actionRD$ = object.action;
} }
/**
* Check if claimed task actions should display a view item button.
* @param workflowAction
*/
hasViewAction(workflowAction: WorkflowAction) {
return !workflowAction?.options.includes('submit_edit_metadata');
}
/** /**
* Get the workflowitem view route. * Get the workflowitem view route.
*/ */

View File

@@ -1,8 +1,13 @@
<button type="button" <button type="button" class="btn btn-info mt-1 mb-3"
class="btn btn-info mt-1 mb-3" ngbTooltip="{{'submission.workflow.tasks.pool.claim_help' | translate}}" [disabled]="(processing$ | async)"
ngbTooltip="{{'submission.workflow.tasks.pool.claim_help' | translate}}" (click)="claim()">
[disabled]="(processing$ | async)" <span *ngIf="(processing$ | async)"><i class='fas fa-circle-notch fa-spin'></i>
(click)="claim()"> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="(processing$ | async)"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span> <span *ngIf="!(processing$ | async)"><i class="fas fa-hand-paper"></i> {{'submission.workflow.tasks.pool.claim' |
<span *ngIf="!(processing$ | async)"><i class="fas fa-hand-paper"></i> {{'submission.workflow.tasks.pool.claim' | translate}}</span> translate}}</span>
</button>
<button class="btn btn-primary workflow-view ml-1 mt-1 mb-3" data-test="view-btn"
ngbTooltip="{{'submission.workflow.generic.view-help' | translate}}"
[routerLink]="[getWorkflowItemViewRoute((workflowitem$ | async))]">
<i class="fa fa-info-circle"></i> {{"submission.workflow.generic.view" | translate}}
</button> </button>

View File

@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core'; import { ChangeDetectionStrategy, Injector, NO_ERRORS_SCHEMA } from '@angular/core';
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
@@ -133,6 +133,12 @@ describe('PoolTaskActionsComponent', () => {
expect(btn).toBeDefined(); expect(btn).toBeDefined();
}); });
it('should display view button', () => {
const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]'));
expect(btn).toBeDefined();
});
it('should call claim task with href of getPoolTaskEndpointById', ((done) => { it('should call claim task with href of getPoolTaskEndpointById', ((done) => {
const poolTaskHref = 'poolTaskHref'; const poolTaskHref = 'poolTaskHref';

View File

@@ -2,7 +2,7 @@ import { Component, Injector, Input, OnDestroy } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import {filter, map, switchMap, take} from 'rxjs/operators'; import { filter, map, switchMap, take } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { WorkflowItem } from '../../../core/submission/models/workflowitem.model'; import { WorkflowItem } from '../../../core/submission/models/workflowitem.model';
@@ -19,6 +19,7 @@ import { Item } from '../../../core/shared/item.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { MyDSpaceReloadableActionsComponent } from '../mydspace-reloadable-actions'; import { MyDSpaceReloadableActionsComponent } from '../mydspace-reloadable-actions';
import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response'; import { ProcessTaskResponse } from '../../../core/tasks/models/process-task-response';
import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths';
/** /**
* This component represents mydspace actions related to PoolTask object. * This component represents mydspace actions related to PoolTask object.
@@ -58,12 +59,12 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent
* @param {RequestService} requestService * @param {RequestService} requestService
*/ */
constructor(protected injector: Injector, constructor(protected injector: Injector,
protected router: Router, protected router: Router,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected claimedTaskService: ClaimedTaskDataService, protected claimedTaskService: ClaimedTaskDataService,
protected translate: TranslateService, protected translate: TranslateService,
protected searchService: SearchService, protected searchService: SearchService,
protected requestService: RequestService) { protected requestService: RequestService) {
super(PoolTask.type, injector, router, notificationsService, translate, searchService, requestService); super(PoolTask.type, injector, router, notificationsService, translate, searchService, requestService);
} }
@@ -91,7 +92,7 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent
return this.objectDataService.getPoolTaskEndpointById(this.object.id) return this.objectDataService.getPoolTaskEndpointById(this.object.id)
.pipe(switchMap((poolTaskHref) => { .pipe(switchMap((poolTaskHref) => {
return this.claimedTaskService.claimTask(this.object.id, poolTaskHref); return this.claimedTaskService.claimTask(this.object.id, poolTaskHref);
})); }));
} }
reloadObjectExecution(): Observable<RemoteData<DSpaceObject> | DSpaceObject> { reloadObjectExecution(): Observable<RemoteData<DSpaceObject> | DSpaceObject> {
@@ -107,12 +108,19 @@ export class PoolTaskActionsComponent extends MyDSpaceReloadableActionsComponent
switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload()) switchMap((workflowItem: WorkflowItem) => workflowItem.item.pipe(getFirstSucceededRemoteDataPayload())
)) ))
.subscribe((item: Item) => { .subscribe((item: Item) => {
this.itemUuid = item.uuid; this.itemUuid = item.uuid;
}); });
} }
ngOnDestroy() { ngOnDestroy() {
this.subs.forEach((sub) => sub.unsubscribe()); this.subs.forEach((sub) => sub.unsubscribe());
} }
/**
* Get the workflowitem view route.
*/
getWorkflowItemViewRoute(workflowitem: WorkflowItem): string {
return getWorkflowItemViewRoute(workflowitem?.id);
}
} }

View File

@@ -0,0 +1,5 @@
<button class="btn btn-primary workflow-view mt-1 mb-3" data-test="view-btn"
ngbTooltip="{{'submission.workspace.generic.view-help' | translate}}"
[routerLink]="[getWorkflowItemViewRoute(object)]">
<i class="fa fa-info-circle"></i> {{"submission.workspace.generic.view" | translate}}
</button>

View File

@@ -18,6 +18,7 @@ import { getMockRequestService } from '../../mocks/request.service.mock';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { getMockSearchService } from '../../mocks/search-service.mock'; import { getMockSearchService } from '../../mocks/search-service.mock';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
import { By } from '@angular/platform-browser';
let component: WorkflowitemActionsComponent; let component: WorkflowitemActionsComponent;
let fixture: ComponentFixture<WorkflowitemActionsComponent>; let fixture: ComponentFixture<WorkflowitemActionsComponent>;
@@ -105,4 +106,10 @@ describe('WorkflowitemActionsComponent', () => {
expect(component.object).toEqual(mockObject); expect(component.object).toEqual(mockObject);
}); });
it('should display view button', () => {
const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]'));
expect(btn).toBeDefined();
});
}); });

View File

@@ -9,6 +9,7 @@ import { WorkflowItemDataService } from '../../../core/submission/workflowitem-d
import { NotificationsService } from '../../notifications/notifications.service'; import { NotificationsService } from '../../notifications/notifications.service';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { SearchService } from '../../../core/shared/search/search.service'; import { SearchService } from '../../../core/shared/search/search.service';
import { getWorkflowItemViewRoute } from '../../../workflowitems-edit-page/workflowitems-edit-page-routing-paths';
/** /**
* This component represents actions related to WorkflowItem object. * This component represents actions related to WorkflowItem object.
@@ -44,6 +45,13 @@ export class WorkflowitemActionsComponent extends MyDSpaceActionsComponent<Workf
super(WorkflowItem.type, injector, router, notificationsService, translate, searchService, requestService); super(WorkflowItem.type, injector, router, notificationsService, translate, searchService, requestService);
} }
/**
* Get the workflowitem view route.
*/
getWorkflowItemViewRoute(workflowitem: WorkflowItem): string {
return getWorkflowItemViewRoute(workflowitem?.id);
}
/** /**
* Init the target object * Init the target object
* *

View File

@@ -1,22 +1,28 @@
<div class="space-children-mr"> <div class="space-children-mr">
<a class="btn btn-primary mt-1 mb-3"
id="{{'edit_' + object.id}}" <button class="btn btn-primary workflow-view mt-1 mb-3" data-test="view-btn"
ngbTooltip="{{'submission.workflow.generic.edit-help' | translate}}" ngbTooltip="{{'submission.workspace.generic.view-help' | translate}}"
[routerLink]="['/workspaceitems/' + object.id + '/edit']" [routerLink]="[getWorkspaceItemViewRoute(object)]">
role="button"> <i class="fa fa-info-circle"></i> {{"submission.workspace.generic.view" | translate}}
</button>
<a class="btn btn-primary mt-1 mb-3" id="{{'edit_' + object.id}}"
ngbTooltip="{{'submission.workflow.generic.edit-help' | translate}}"
[routerLink]="['/workspaceitems/' + object.id + '/edit']" role="button">
<i class="fa fa-edit"></i> {{'submission.workflow.generic.edit' | translate}} <i class="fa fa-edit"></i> {{'submission.workflow.generic.edit' | translate}}
</a> </a>
<button type="button" <button type="button" id="{{'delete_' + object.id}}" class="btn btn-danger mt-1 mb-3"
id="{{'delete_' + object.id}}" ngbTooltip="{{'submission.workflow.generic.delete-help' | translate}}"
class="btn btn-danger mt-1 mb-3" (click)="$event.preventDefault();confirmDiscard(content)">
ngbTooltip="{{'submission.workflow.generic.delete-help' | translate}}" <span *ngIf="(processingDelete$ | async)"><i class='fas fa-circle-notch fa-spin'></i>
(click)="$event.preventDefault();confirmDiscard(content)"> {{'submission.workflow.tasks.generic.processing' | translate}}</span>
<span *ngIf="(processingDelete$ | async)"><i class='fas fa-circle-notch fa-spin'></i> {{'submission.workflow.tasks.generic.processing' | translate}}</span> <span *ngIf="!(processingDelete$ | async)"><i class="fa fa-trash"></i> {{'submission.workflow.generic.delete' |
<span *ngIf="!(processingDelete$ | async)"><i class="fa fa-trash"></i> {{'submission.workflow.generic.delete' | translate}}</span> translate}}</span>
</button> </button>
</div> </div>
<ng-template #content let-c="close" let-d="dismiss"> <ng-template #content let-c="close" let-d="dismiss">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title text-danger">{{'submission.general.discard.confirm.title' | translate}}</h4> <h4 class="modal-title text-danger">{{'submission.general.discard.confirm.title' | translate}}</h4>
@@ -28,7 +34,9 @@
<p>{{'submission.general.discard.confirm.info' | translate}}</p> <p>{{'submission.general.discard.confirm.info' | translate}}</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" id="delete_cancel" class="btn btn-secondary" (click)="c('cancel')">{{'submission.general.discard.confirm.cancel' | translate}}</button> <button type="button" id="delete_cancel" class="btn btn-secondary"
<button type="button" id="delete_confirm"class="btn btn-danger" (click)="c('ok')">{{'submission.general.discard.confirm.submit' | translate}}</button> (click)="c('cancel')">{{'submission.general.discard.confirm.cancel' | translate}}</button>
<button type="button" id="delete_confirm" class="btn btn-danger"
(click)="c('ok')">{{'submission.general.discard.confirm.submit' | translate}}</button>
</div> </div>
</ng-template> </ng-template>

View File

@@ -132,6 +132,12 @@ describe('WorkspaceitemActionsComponent', () => {
expect(btn).toBeDefined(); expect(btn).toBeDefined();
}); });
it('should display view button', () => {
const btn = fixture.debugElement.query(By.css('button [data-test="view-btn"]'));
expect(btn).toBeDefined();
});
describe('on discard confirmation', () => { describe('on discard confirmation', () => {
beforeEach((done) => { beforeEach((done) => {
mockDataService.delete.and.returnValue(observableOf(true)); mockDataService.delete.and.returnValue(observableOf(true));

View File

@@ -14,6 +14,7 @@ import { SearchService } from '../../../core/shared/search/search.service';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
import { RemoteData } from '../../../core/data/remote-data'; import { RemoteData } from '../../../core/data/remote-data';
import { NoContent } from '../../../core/shared/NoContent.model'; import { NoContent } from '../../../core/shared/NoContent.model';
import { getWorkspaceItemViewRoute } from '../../../workspaceitems-edit-page/workspaceitems-edit-page-routing-paths';
/** /**
* This component represents actions related to WorkspaceItem object. * This component represents actions related to WorkspaceItem object.
@@ -48,12 +49,12 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<Work
* @param {RequestService} requestService * @param {RequestService} requestService
*/ */
constructor(protected injector: Injector, constructor(protected injector: Injector,
protected router: Router, protected router: Router,
protected modalService: NgbModal, protected modalService: NgbModal,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected translate: TranslateService, protected translate: TranslateService,
protected searchService: SearchService, protected searchService: SearchService,
protected requestService: RequestService) { protected requestService: RequestService) {
super(WorkspaceItem.type, injector, router, notificationsService, translate, searchService, requestService); super(WorkspaceItem.type, injector, router, notificationsService, translate, searchService, requestService);
} }
@@ -85,4 +86,11 @@ export class WorkspaceitemActionsComponent extends MyDSpaceActionsComponent<Work
this.object = object; this.object = object;
} }
/**
* Get the workflowitem view route.
*/
getWorkspaceItemViewRoute(workspaceItem: WorkspaceItem): string {
return getWorkspaceItemViewRoute(workspaceItem?.id);
}
} }

View File

@@ -3,9 +3,12 @@ import { Component } from '@angular/core';
import { ViewMode } from '../../../../core/shared/view-mode.model'; import { ViewMode } from '../../../../core/shared/view-mode.model';
import { Item } from '../../../../core/shared/item.model'; import { Item } from '../../../../core/shared/item.model';
import { SearchResultDetailElementComponent } from '../search-result-detail-element.component'; import { SearchResultDetailElementComponent } from '../search-result-detail-element.component';
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type'; import {
MyDspaceItemStatusType
} from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator'; import { listableObjectComponent } from '../../../object-collection/shared/listable-object/listable-object.decorator';
import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model'; import { ItemSearchResult } from '../../../object-collection/shared/item-search-result.model';
import { Context } from '../../../../core/shared/context.model';
/** /**
* This component renders item object for the search result in the detail view. * This component renders item object for the search result in the detail view.
@@ -16,7 +19,8 @@ import { ItemSearchResult } from '../../../object-collection/shared/item-search-
templateUrl: './item-search-result-detail-element.component.html' templateUrl: './item-search-result-detail-element.component.html'
}) })
@listableObjectComponent(Item, ViewMode.DetailedListElement) @listableObjectComponent(ItemSearchResult, ViewMode.DetailedListElement, Context.Workspace)
@listableObjectComponent(ItemSearchResult, ViewMode.DetailedListElement, Context.Workflow)
export class ItemSearchResultDetailElementComponent extends SearchResultDetailElementComponent<ItemSearchResult, Item> { export class ItemSearchResultDetailElementComponent extends SearchResultDetailElementComponent<ItemSearchResult, Item> {
/** /**

View File

@@ -1,43 +1,21 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { Resolve } from '@angular/router';
import { Observable } from 'rxjs';
import { RemoteData } from '../core/data/remote-data'; import { RemoteData } from '../core/data/remote-data';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { followLink } from '../shared/utils/follow-link-config.model';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service'; import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
import { WorkflowItem } from '../core/submission/models/workflowitem.model'; import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
import { switchMap } from 'rxjs/operators';
/** /**
* This class represents a resolver that requests a specific item before the route is activated * This class represents a resolver that requests a specific item before the route is activated
*/ */
@Injectable() @Injectable()
export class ItemFromWorkflowResolver implements Resolve<RemoteData<Item>> { export class ItemFromWorkflowResolver extends SubmissionObjectResolver<Item> implements Resolve<RemoteData<Item>> {
constructor( constructor(
private workflowItemService: WorkflowItemDataService, private workflowItemService: WorkflowItemDataService,
protected store: Store<any> protected store: Store<any>
) { ) {
super(workflowItemService, store);
} }
/**
* Method for resolving an item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
const itemRD$ = this.workflowItemService.findById(route.params.id,
true,
false,
followLink('item'),
).pipe(
getFirstCompletedRemoteData(),
switchMap((wfiRD: RemoteData<WorkflowItem>) => wfiRD.payload.item as Observable<RemoteData<Item>>),
getFirstCompletedRemoteData()
);
return itemRD$;
}
} }

View File

@@ -0,0 +1,36 @@
import { first } from 'rxjs/operators';
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
describe('ItemFromWorkspaceResolver', () => {
describe('resolve', () => {
let resolver: ItemFromWorkspaceResolver;
let wfiService: WorkspaceitemDataService;
const uuid = '1234-65487-12354-1235';
const itemUuid = '8888-8888-8888-8888';
const wfi = {
id: uuid,
item: createSuccessfulRemoteDataObject$({ id: itemUuid })
};
beforeEach(() => {
wfiService = {
findById: (id: string) => createSuccessfulRemoteDataObject$(wfi)
} as any;
resolver = new ItemFromWorkspaceResolver(wfiService, null);
});
it('should resolve a an item from from the workflow item with the correct id', (done) => {
resolver.resolve({ params: { id: uuid } } as any, undefined)
.pipe(first())
.subscribe(
(resolved) => {
expect(resolved.payload.id).toEqual(itemUuid);
done();
}
);
});
});
});

View File

@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { RemoteData } from '../core/data/remote-data';
import { Item } from '../core/shared/item.model';
import { Store } from '@ngrx/store';
import { SubmissionObjectResolver } from '../core/submission/resolver/submission-object.resolver';
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
/**
* This class represents a resolver that requests a specific item before the route is activated
*/
@Injectable()
export class ItemFromWorkspaceResolver extends SubmissionObjectResolver<Item> implements Resolve<RemoteData<Item>> {
constructor(
private workspaceItemService: WorkspaceitemDataService,
protected store: Store<any>
) {
super(workspaceItemService, store);
}
}

View File

@@ -0,0 +1,30 @@
import { first } from 'rxjs/operators';
import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
describe('WorkflowItemPageResolver', () => {
describe('resolve', () => {
let resolver: WorkspaceItemPageResolver;
let wsiService: WorkspaceitemDataService;
const uuid = '1234-65487-12354-1235';
beforeEach(() => {
wsiService = {
findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
} as any;
resolver = new WorkspaceItemPageResolver(wsiService);
});
it('should resolve a workspace item with the correct id', (done) => {
resolver.resolve({ params: { id: uuid } } as any, undefined)
.pipe(first())
.subscribe(
(resolved) => {
expect(resolved.payload.id).toEqual(uuid);
done();
}
);
});
});
});

View File

@@ -0,0 +1,34 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { RemoteData } from '../core/data/remote-data';
import { followLink } from '../shared/utils/follow-link-config.model';
import { WorkspaceitemDataService } from '../core/submission/workspaceitem-data.service';
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
import { getFirstCompletedRemoteData } from '../core/shared/operators';
/**
* This class represents a resolver that requests a specific workflow item before the route is activated
*/
@Injectable()
export class WorkspaceItemPageResolver implements Resolve<RemoteData<WorkflowItem>> {
constructor(private workspaceItemService: WorkspaceitemDataService) {
}
/**
* Method for resolving a workflow item based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found workflow item based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<WorkflowItem>> {
return this.workspaceItemService.findById(route.params.id,
true,
false,
followLink('item'),
).pipe(
getFirstCompletedRemoteData(),
);
}
}

View File

@@ -0,0 +1,8 @@
import { getWorkspaceItemModuleRoute } from '../app-routing-paths';
import { URLCombiner } from '../core/url-combiner/url-combiner';
export function getWorkspaceItemViewRoute(wfiId: string) {
return new URLCombiner(getWorkspaceItemModuleRoute(), wfiId, WORKSPACE_ITEM_VIEW_PATH).toString();
}
export const WORKSPACE_ITEM_VIEW_PATH = 'view';

View File

@@ -4,22 +4,42 @@ import { RouterModule } from '@angular/router';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component'; import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
import { ThemedFullItemPageComponent } from '../item-page/full/themed-full-item-page.component';
import { ItemFromWorkspaceResolver } from './item-from-workspace.resolver';
import { WorkspaceItemPageResolver } from './workspace-item-page.resolver';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{ path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '', redirectTo: '/home', pathMatch: 'full' },
{ {
canActivate: [AuthenticatedGuard], path: ':id',
path: ':id/edit', resolve: { wsi: WorkspaceItemPageResolver },
component: ThemedSubmissionEditComponent, children: [
resolve: { {
breadcrumb: I18nBreadcrumbResolver canActivate: [AuthenticatedGuard],
}, path: 'edit',
data: { title: 'submission.edit.title', breadcrumbKey: 'submission.edit' } component: ThemedSubmissionEditComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'submission.edit.title', breadcrumbKey: 'submission.edit' }
},
{
canActivate: [AuthenticatedGuard],
path: 'view',
component: ThemedFullItemPageComponent,
resolve: {
dso: ItemFromWorkspaceResolver,
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'workspace-item.view.title', breadcrumbKey: 'workspace-item.view' }
}
]
} }
]) ])
] ],
providers: [WorkspaceItemPageResolver, ItemFromWorkspaceResolver]
}) })
/** /**
* This module defines the default component to load when navigating to the workspaceitems edit page path * This module defines the default component to load when navigating to the workspaceitems edit page path

View File

@@ -4072,6 +4072,10 @@
"submission.workflow.tasks.pool.show-detail": "Show detail", "submission.workflow.tasks.pool.show-detail": "Show detail",
"submission.workspace.generic.view": "View",
"submission.workspace.generic.view-help": "Select this option to view the item's metadata.",
"thumbnail.default.alt": "Thumbnail Image", "thumbnail.default.alt": "Thumbnail Image",
@@ -4178,6 +4182,9 @@
"workflow-item.view.breadcrumbs": "Workflow View", "workflow-item.view.breadcrumbs": "Workflow View",
"workspace-item.view.breadcrumbs": "Workspace View",
"workspace-item.view.title": "Workspace View",
"idle-modal.header": "Session will expire soon", "idle-modal.header": "Session will expire soon",