From a572b0acea461050f913445b6dffa28a050ff1eb Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Thu, 24 Jun 2021 15:28:26 +0200 Subject: [PATCH] [CST-4320] No way to view an Item when in simple "Approve/Reject" workflow stage --- .../themed-workflow-item-view.component.ts | 26 ++++ .../workflow-item-view.component.html | 9 ++ .../workflow-item-view.component.spec.ts | 114 ++++++++++++++++++ .../workflow-item-view.component.ts | 40 ++++++ .../workflowitems-edit-page-routing-paths.ts | 4 + .../workflowitems-edit-page-routing.module.ts | 13 +- .../workflowitems-edit-page.module.ts | 15 ++- .../claimed-task-actions.component.html | 9 ++ .../claimed-task-actions.component.spec.ts | 27 ++++- .../claimed-task-actions.component.ts | 17 +++ src/assets/i18n/en.json5 | 2 + .../workflow-item-view.component.html | 0 .../workflow-item-view.component.scss | 0 .../workflow-item-view.component.ts | 14 +++ src/themes/custom/theme.module.ts | 2 + 15 files changed, 288 insertions(+), 4 deletions(-) create mode 100644 src/app/+workflowitems-edit-page/workflow-item-view/themed-workflow-item-view.component.ts create mode 100644 src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.html create mode 100644 src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.spec.ts create mode 100644 src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.ts create mode 100644 src/themes/custom/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.html create mode 100644 src/themes/custom/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.scss create mode 100644 src/themes/custom/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.ts diff --git a/src/app/+workflowitems-edit-page/workflow-item-view/themed-workflow-item-view.component.ts b/src/app/+workflowitems-edit-page/workflow-item-view/themed-workflow-item-view.component.ts new file mode 100644 index 0000000000..f0630fa561 --- /dev/null +++ b/src/app/+workflowitems-edit-page/workflow-item-view/themed-workflow-item-view.component.ts @@ -0,0 +1,26 @@ +import { ThemedComponent } from '../../shared/theme-support/themed.component'; +import { Component } from '@angular/core'; +import { WorkflowItemViewComponent } from './workflow-item-view.component'; + +/** + * Themed wrapper for WorkflowItemViewComponent + */ + +@Component({ + selector: 'ds-themed-workflow-item-view', + styleUrls: [], + templateUrl: './../../shared/theme-support/themed.component.html' +}) +export class ThemedWorkflowItemViewComponent extends ThemedComponent { + protected getComponentName(): string { + return 'WorkflowItemViewComponent'; + } + + protected importThemedComponent(themeName: string): Promise { + return import(`../../../themes/${themeName}/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component`); + } + + protected importUnthemedComponent(): Promise { + return import(`./workflow-item-view.component`); + } +} diff --git a/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.html b/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.html new file mode 100644 index 0000000000..00401f34ce --- /dev/null +++ b/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.html @@ -0,0 +1,9 @@ +
+
+
+ +
+
+ + +
diff --git a/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.spec.ts b/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.spec.ts new file mode 100644 index 0000000000..6804c7a816 --- /dev/null +++ b/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.spec.ts @@ -0,0 +1,114 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { RouteService } from '../../core/services/route.service'; +import { NotificationsService } from '../../shared/notifications/notifications.service'; +import { WorkflowItem } from '../../core/submission/models/workflowitem.model'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { VarDirective } from '../../shared/utils/var.directive'; +import { of as observableOf } from 'rxjs'; +import { RequestService } from '../../core/data/request.service'; +import { + createFailedRemoteDataObject$, + createPendingRemoteDataObject$, + createSuccessfulRemoteDataObject, + createSuccessfulRemoteDataObject$ +} from '../../shared/remote-data.utils'; +import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock'; +import { ActivatedRouteStub } from '../../shared/testing/active-router.stub'; +import { RouterStub } from '../../shared/testing/router.stub'; +import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub'; +import { getMockRequestService } from '../../shared/mocks/request.service.mock'; +import { Item } from '../../core/shared/item.model'; +import { createPaginatedList } from '../../shared/testing/utils.test'; +import { createRelationshipsObservable } from '../../+item-page/simple/item-types/shared/item.component.spec'; +import { WorkflowItemViewComponent } from './workflow-item-view.component'; +import { By } from '@angular/platform-browser'; + +describe('WorkflowItemViewComponent', () => { + + const mockItem: Item = Object.assign(new Item(), { + bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), + metadata: [], + relationships: createRelationshipsObservable() + }); + + const mockWfi = new WorkflowItem(); + mockWfi.item = createSuccessfulRemoteDataObject$(mockItem); + + const mockRoute = Object.assign(new ActivatedRouteStub(), { + data: observableOf({ wfi: createSuccessfulRemoteDataObject(mockWfi) }) + }); + + let comp: WorkflowItemViewComponent; + let fixture: ComponentFixture; + let wfi; + let itemRD$; + let id; + + function init() { + itemRD$ = createSuccessfulRemoteDataObject$(itemRD$); + wfi = new WorkflowItem(); + wfi.item = itemRD$; + id = 'de11b5e5-064a-4e98-a7ac-a1a6a65ddf80'; + } + + beforeEach(waitForAsync(() => { + init(); + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: TranslateLoaderMock + } + })], + declarations: [WorkflowItemViewComponent, VarDirective], + providers: [ + { provide: ActivatedRoute, useValue: mockRoute }, + { provide: Router, useClass: RouterStub }, + { provide: RouteService, useValue: {} }, + { provide: NotificationsService, useClass: NotificationsServiceStub }, + { provide: RequestService, useValue: getMockRequestService() }, + ], + schemas: [NO_ERRORS_SCHEMA] + }) + .compileComponents(); + })); + + beforeEach(waitForAsync(() => { + fixture = TestBed.createComponent(WorkflowItemViewComponent); + comp = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(comp).toBeTruthy(); + }); + + describe('when the item is loading', () => { + beforeEach(() => { + comp.itemRD$ = createPendingRemoteDataObject$(); + // comp.itemRD$ = observableOf(new RemoteData(true, true, true, null, undefined)); + fixture.detectChanges(); + }); + + it('should display a loading component', () => { + const loading = fixture.debugElement.query(By.css('ds-loading')); + expect(loading.nativeElement).toBeDefined(); + }); + }); + + describe('when the item failed loading', () => { + beforeEach(() => { + comp.itemRD$ = createFailedRemoteDataObject$('server error', 500); + fixture.detectChanges(); + }); + + it('should display an error component', () => { + const error = fixture.debugElement.query(By.css('ds-error')); + expect(error.nativeElement).toBeDefined(); + }); + }); + +}); diff --git a/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.ts b/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.ts new file mode 100644 index 0000000000..e9a51074fc --- /dev/null +++ b/src/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { ActivatedRoute, Data } from '@angular/router'; +import { map, switchMap } from 'rxjs/operators';import { RemoteData } from '../../core/data/remote-data'; + +import { + getAllSucceededRemoteData, + getRemoteDataPayload +} from '../../core/shared/operators'; +import { WorkflowItem } from '../../core/submission/models/workflowitem.model'; +import { Item } from '../../core/shared/item.model'; +import { ViewMode } from '../../core/shared/view-mode.model'; + +@Component({ + selector: 'ds-workflow-item-view', + templateUrl: './workflow-item-view.component.html' +}) +/** + * Component representing a page to delete a workflow item + */ +export class WorkflowItemViewComponent implements OnInit { + + public wfi$: Observable; + public itemRD$: Observable>; + + /** + * The view-mode we're currently on + */ + viewMode = ViewMode.StandalonePage; + + + constructor(protected route: ActivatedRoute) { + } + + ngOnInit() { + this.wfi$ = this.route.data.pipe(map((data: Data) => data.wfi as RemoteData), getRemoteDataPayload()); + this.itemRD$ = this.wfi$.pipe(switchMap((wfi: WorkflowItem) => (wfi.item as Observable>).pipe(getAllSucceededRemoteData()))); + } + +} diff --git a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts index a8802e7a2f..e2d969a872 100644 --- a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts +++ b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing-paths.ts @@ -8,6 +8,9 @@ export function getWorkflowItemPageRoute(wfiId: string) { export function getWorkflowItemEditRoute(wfiId: string) { return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, WORKFLOW_ITEM_EDIT_PATH).toString(); } +export function getWorkflowItemViewRoute(wfiId: string) { + return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, WORKFLOW_ITEM_VIEW_PATH).toString(); +} export function getWorkflowItemDeleteRoute(wfiId: string) { return new URLCombiner(getWorkflowItemModuleRoute(), wfiId, WORKFLOW_ITEM_DELETE_PATH).toString(); @@ -19,4 +22,5 @@ export function getWorkflowItemSendBackRoute(wfiId: string) { export const WORKFLOW_ITEM_EDIT_PATH = 'edit'; export const WORKFLOW_ITEM_DELETE_PATH = 'delete'; +export const WORKFLOW_ITEM_VIEW_PATH = 'view'; export const WORKFLOW_ITEM_SEND_BACK_PATH = 'sendback'; diff --git a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts index 733eebecc2..eee235e12f 100644 --- a/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts +++ b/src/app/+workflowitems-edit-page/workflowitems-edit-page-routing.module.ts @@ -6,12 +6,14 @@ import { WorkflowItemPageResolver } from './workflow-item-page.resolver'; import { WORKFLOW_ITEM_DELETE_PATH, WORKFLOW_ITEM_EDIT_PATH, - WORKFLOW_ITEM_SEND_BACK_PATH + WORKFLOW_ITEM_SEND_BACK_PATH, + WORKFLOW_ITEM_VIEW_PATH } from './workflowitems-edit-page-routing-paths'; import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component'; import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component'; import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component'; import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; +import { ThemedWorkflowItemViewComponent } from './workflow-item-view/themed-workflow-item-view.component'; @NgModule({ imports: [ @@ -29,6 +31,15 @@ import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.reso }, data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit' } }, + { + canActivate: [AuthenticatedGuard], + path: WORKFLOW_ITEM_VIEW_PATH, + component: ThemedWorkflowItemViewComponent, + resolve: { + breadcrumb: I18nBreadcrumbResolver + }, + data: { title: 'workflow-item.view.title', breadcrumbKey: 'workflow-item.view' } + }, { canActivate: [AuthenticatedGuard], path: WORKFLOW_ITEM_DELETE_PATH, diff --git a/src/app/+workflowitems-edit-page/workflowitems-edit-page.module.ts b/src/app/+workflowitems-edit-page/workflowitems-edit-page.module.ts index 6e4b3212e8..221c0328a0 100644 --- a/src/app/+workflowitems-edit-page/workflowitems-edit-page.module.ts +++ b/src/app/+workflowitems-edit-page/workflowitems-edit-page.module.ts @@ -7,6 +7,10 @@ import { WorkflowItemDeleteComponent } from './workflow-item-delete/workflow-ite import { WorkflowItemSendBackComponent } from './workflow-item-send-back/workflow-item-send-back.component'; import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component'; import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component'; +import { WorkflowItemViewComponent } from './workflow-item-view/workflow-item-view.component'; +import { ThemedWorkflowItemViewComponent } from './workflow-item-view/themed-workflow-item-view.component'; +import { StatisticsModule } from '../statistics/statistics.module'; +import { ItemPageModule } from '../+item-page/item-page.module'; @NgModule({ imports: [ @@ -14,8 +18,17 @@ import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/t CommonModule, SharedModule, SubmissionModule, + StatisticsModule, + ItemPageModule ], - declarations: [WorkflowItemDeleteComponent, ThemedWorkflowItemDeleteComponent, WorkflowItemSendBackComponent, ThemedWorkflowItemSendBackComponent] + declarations: [ + WorkflowItemDeleteComponent, + ThemedWorkflowItemDeleteComponent, + WorkflowItemSendBackComponent, + ThemedWorkflowItemSendBackComponent, + WorkflowItemViewComponent, + ThemedWorkflowItemViewComponent + ] }) /** * This module handles all modules that need to access the workflowitems edit page. diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html index 6a39fd44ca..7454a80873 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.html @@ -5,6 +5,15 @@ [object]="object" (processCompleted)="this.processCompleted.emit($event)"> + + + + + diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts index 8319a9a1db..fa896c7dfd 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.spec.ts @@ -23,6 +23,7 @@ import { WorkflowActionDataService } from '../../../core/data/workflow-action-da import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { VarDirective } from '../../utils/var.directive'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../remote-data.utils'; +import { By } from '@angular/platform-browser'; let component: ClaimedTaskActionsComponent; let fixture: ComponentFixture; @@ -81,7 +82,7 @@ function init() { } }); rdItem = createSuccessfulRemoteDataObject(item); - workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem) }); + workflowitem = Object.assign(new WorkflowItem(), { item: observableOf(rdItem), id: '333' }); rdWorkflowitem = createSuccessfulRemoteDataObject(workflowitem); mockObject = Object.assign(new ClaimedTask(), { workflowitem: observableOf(rdWorkflowitem), id: '1234' }); workflowAction = Object.assign(new WorkflowAction(), { id: 'action-1', options: ['option-1', 'option-2'] }); @@ -91,7 +92,7 @@ function init() { }); } -describe('ClaimedTaskActionsComponent', () => { +fdescribe('ClaimedTaskActionsComponent', () => { beforeEach(waitForAsync(() => { init(); TestBed.configureTestingModule({ @@ -159,4 +160,26 @@ describe('ClaimedTaskActionsComponent', () => { expect(notificationsServiceStub.error).toHaveBeenCalled(); }); })); + + describe('when edit options is not available', () => { + it('should display a view button', waitForAsync(() => { + component.object = null; + component.initObjects(mockObject); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + const debugElement = fixture.debugElement.query(By.css('.workflow-view')); + expect(debugElement).toBeTruthy(); + expect(debugElement.nativeElement.innerText).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'); + })); + }); + + }); diff --git a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts index b610232279..5b142a64ec 100644 --- a/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts +++ b/src/app/shared/mydspace-actions/claimed-task/claimed-task-actions.component.ts @@ -17,6 +17,7 @@ import { SearchService } from '../../../core/shared/search/search.service'; import { WorkflowAction } from '../../../core/tasks/models/workflow-action-object.model'; import { WorkflowActionDataService } from '../../../core/data/workflow-action-data.service'; import { WORKFLOW_TASK_OPTION_RETURN_TO_POOL } from './return-to-pool/claimed-task-actions-return-to-pool.component'; +import { getWorkflowItemViewRoute } from '../../../+workflowitems-edit-page/workflowitems-edit-page-routing-paths'; /** * This component represents actions related to ClaimedTask object. @@ -85,6 +86,7 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent>).pipe( filter((rd: RemoteData) => ((!rd.isRequestPending) && isNotUndefined(rd.payload))), map((rd: RemoteData) => rd.payload), @@ -100,4 +102,19 @@ export class ClaimedTaskActionsComponent extends MyDSpaceActionsComponent