[CST-4320] workflow item view resolved as full item page

This commit is contained in:
Alessandro Martelli
2021-07-05 15:47:58 +02:00
parent 4b11a2f1c1
commit 111170e7d4
16 changed files with 151 additions and 223 deletions

View File

@@ -10,7 +10,7 @@
<ds-dso-page-edit-button [pageRoute]="itemPageRoute$ | async" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
</div>
</div>
<div class="simple-view-link my-3">
<div class="simple-view-link my-3" *ngIf="!fromWfi">
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
{{"item.page.link.simple" | translate}}
</a>
@@ -29,6 +29,9 @@
<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-versions class="mt-2" [item]="item"></ds-item-versions>
<div *ngIf="fromWfi">
<a class="btn btn-light mt-3" (click)="back()">{{'process.detail.back' | translate}}</a>
</div>
</div>
</div>
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>

View File

@@ -1,4 +1,4 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing';
import { ItemDataService } from '../../core/data/item-data.service';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
@@ -29,9 +29,7 @@ const mockItem: Item = Object.assign(new Item(), {
]
}
});
const routeStub = Object.assign(new ActivatedRouteStub(), {
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
});
const metadataServiceStub = {
/* tslint:disable:no-empty */
processRemoteData: () => {
@@ -44,6 +42,10 @@ describe('FullItemPageComponent', () => {
let fixture: ComponentFixture<FullItemPageComponent>;
let authService: AuthService;
let routeStub: ActivatedRouteStub;
let routeData;
beforeEach(waitForAsync(() => {
authService = jasmine.createSpyObj('authService', {
@@ -51,6 +53,14 @@ describe('FullItemPageComponent', () => {
setRedirectUrl: {}
});
routeData = {
dso: createSuccessfulRemoteDataObject(mockItem),
};
routeStub = Object.assign(new ActivatedRouteStub(), {
data: observableOf(routeData)
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
@@ -84,4 +94,21 @@ describe('FullItemPageComponent', () => {
expect(table.nativeElement.innerHTML).toContain(metadatum.value);
}
});
it('should show simple view button when not originated from workflow item', () => {
expect(comp.fromWfi).toBe(false);
const simpleViewBtn = fixture.debugElement.query(By.css('.simple-view-link'));
expect(simpleViewBtn).toBeTruthy();
});
it('should not show simple view button when originated from workflow', fakeAsync(() => {
routeData.wfi = createSuccessfulRemoteDataObject$({ id: 'wfiId'});
comp.ngOnInit();
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(comp.fromWfi).toBe(true);
const simpleViewBtn = fixture.debugElement.query(By.css('.simple-view-link'));
expect(simpleViewBtn).toBeFalsy();
});
}));
});

View File

@@ -1,6 +1,6 @@
import {filter, map} from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Data, Router } from '@angular/router';
import { Observable , BehaviorSubject } from 'rxjs';
@@ -16,6 +16,8 @@ import { MetadataService } from '../../core/metadata/metadata.service';
import { fadeInOut } from '../../shared/animations/fade';
import { hasValue } from '../../shared/empty.util';
import { AuthService } from '../../core/auth/auth.service';
import { Location } from '@angular/common';
/**
* This component renders a full item page.
@@ -29,13 +31,21 @@ import { AuthService } from '../../core/auth/auth.service';
changeDetection: ChangeDetectionStrategy.OnPush,
animations: [fadeInOut]
})
export class FullItemPageComponent extends ItemPageComponent implements OnInit {
export class FullItemPageComponent extends ItemPageComponent implements OnInit, OnDestroy {
itemRD$: BehaviorSubject<RemoteData<Item>>;
metadata$: Observable<MetadataMap>;
constructor(route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService, authService: AuthService) {
/**
* True when the itemRD has been originated from its workflowitem, false otherwise.
*/
fromWfi = false;
subs = [];
constructor(protected route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService, authService: AuthService,
private _location: Location) {
super(route, router, items, metadataService, authService);
}
@@ -46,5 +56,21 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
map((rd: RemoteData<Item>) => rd.payload),
filter((item: Item) => hasValue(item)),
map((item: Item) => item.metadata),);
this.subs.push(this.route.data.subscribe((data: Data) => {
this.fromWfi = hasValue(data.wfi);
})
);
}
/**
* Navigate back in browser history.
*/
back() {
this._location.back();
}
ngOnDestroy() {
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
}
}

View File

@@ -51,7 +51,7 @@ export class ItemPageComponent implements OnInit {
itemPageRoute$: Observable<string>;
constructor(
private route: ActivatedRoute,
protected route: ActivatedRoute,
private router: Router,
private items: ItemDataService,
private metadataService: MetadataService,

View File

@@ -0,0 +1,36 @@
import { first } from 'rxjs/operators';
import { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
import { ItemFromWorkflowResolver } from './item-from-workflow.resolver';
describe('ItemFromWorkflowResolver', () => {
describe('resolve', () => {
let resolver: ItemFromWorkflowResolver;
let wfiService: WorkflowItemDataService;
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 ItemFromWorkflowResolver(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,43 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { RemoteData } from '../core/data/remote-data';
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 { WorkflowItemDataService } from '../core/submission/workflowitem-data.service';
import { WorkflowItem } from '../core/submission/models/workflowitem.model';
import { switchMap } from 'rxjs/operators';
/**
* This class represents a resolver that requests a specific item before the route is activated
*/
@Injectable()
export class ItemFromWorkflowResolver implements Resolve<RemoteData<Item>> {
constructor(
private workflowItemService: WorkflowItemDataService,
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<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

@@ -1,26 +0,0 @@
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<WorkflowItemViewComponent> {
protected getComponentName(): string {
return 'WorkflowItemViewComponent';
}
protected importThemedComponent(themeName: string): Promise<any> {
return import(`../../../themes/${themeName}/app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component`);
}
protected importUnthemedComponent(): Promise<any> {
return import(`./workflow-item-view.component`);
}
}

View File

@@ -1,9 +0,0 @@
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
<div class="item-page" *ngIf="itemRD?.hasSucceeded">
<div *ngIf="itemRD?.payload as item">
<ds-untyped-item [object]="item"></ds-untyped-item>
</div>
</div>
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
<ds-loading *ngIf="itemRD?.isLoading" message="{{'loading.item' | translate}}"></ds-loading>
</div>

View File

@@ -1,114 +0,0 @@
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<WorkflowItemViewComponent>;
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();
});
});
});

View File

@@ -1,40 +0,0 @@
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<WorkflowItem>;
public itemRD$: Observable<RemoteData<Item>>;
/**
* 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<WorkflowItem>), getRemoteDataPayload());
this.itemRD$ = this.wfi$.pipe(switchMap((wfi: WorkflowItem) => (wfi.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData())));
}
}

View File

@@ -13,7 +13,8 @@ import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submiss
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';
import { ItemFromWorkflowResolver } from './item-from-workflow.resolver';
import { ThemedFullItemPageComponent } from '../+item-page/full/themed-full-item-page.component';
@NgModule({
imports: [
@@ -34,8 +35,9 @@ import { ThemedWorkflowItemViewComponent } from './workflow-item-view/themed-wor
{
canActivate: [AuthenticatedGuard],
path: WORKFLOW_ITEM_VIEW_PATH,
component: ThemedWorkflowItemViewComponent,
component: ThemedFullItemPageComponent,
resolve: {
dso: ItemFromWorkflowResolver,
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'workflow-item.view.title', breadcrumbKey: 'workflow-item.view' }
@@ -62,7 +64,7 @@ import { ThemedWorkflowItemViewComponent } from './workflow-item-view/themed-wor
}]
)
],
providers: [WorkflowItemPageResolver]
providers: [WorkflowItemPageResolver, ItemFromWorkflowResolver]
})
/**
* This module defines the default component to load when navigating to the workflowitems edit page path.

View File

@@ -7,8 +7,6 @@ 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';
@@ -25,9 +23,7 @@ import { ItemPageModule } from '../+item-page/item-page.module';
WorkflowItemDeleteComponent,
ThemedWorkflowItemDeleteComponent,
WorkflowItemSendBackComponent,
ThemedWorkflowItemSendBackComponent,
WorkflowItemViewComponent,
ThemedWorkflowItemViewComponent
ThemedWorkflowItemSendBackComponent
]
})
/**

View File

@@ -1,14 +0,0 @@
import { Component } from '@angular/core';
import { WorkflowItemViewComponent as BaseComponent } from '../../../../../app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component';
@Component({
selector: 'ds-workflow-item-view',
// styleUrls: ['workflow-item-view.component.scss'],
// templateUrl: './workflow-item-view.component.html'
templateUrl: '../../../../../app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component.html'
})
/**
* Component representing a page to view a workflow item
*/
export class WorkflowItemViewComponent extends BaseComponent {
}

View File

@@ -79,7 +79,6 @@ import { HeaderComponent } from './app/header/header.component';
import { FooterComponent } from './app/footer/footer.component';
import { BreadcrumbsComponent } from './app/breadcrumbs/breadcrumbs.component';
import { HeaderNavbarWrapperComponent } from './app/header-nav-wrapper/header-navbar-wrapper.component';
import { WorkflowItemViewComponent } from './app/+workflowitems-edit-page/workflow-item-view/workflow-item-view.component';
const DECLARATIONS = [
HomePageComponent,
@@ -116,7 +115,6 @@ const DECLARATIONS = [
SubmissionSubmitComponent,
WorkflowItemDeleteComponent,
WorkflowItemSendBackComponent,
WorkflowItemViewComponent,
FooterComponent,
HeaderComponent,
NavbarComponent,