88248: #346: Withdrawn item tombstone page

This commit is contained in:
Yana De Pauw
2022-03-14 16:33:10 +01:00
parent 9fc7b57157
commit b21f76456d
7 changed files with 178 additions and 30 deletions

View File

@@ -4,19 +4,21 @@
<ds-item-alerts [item]="item"></ds-item-alerts>
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
<ds-view-tracker [object]="item"></ds-view-tracker>
<div class="d-flex flex-row">
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute$ | async" [dso]="item" [tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
<div *ngIf="!item.isWithdrawn || (isAdmin$|async)" class="full-item-info">
<div class="d-flex flex-row">
<ds-item-page-title-field class="mr-auto" [item]="item"></ds-item-page-title-field>
<div class="pl-2">
<ds-dso-page-edit-button [pageRoute]="itemPageRoute$ | async" [dso]="item"
[tooltipMsg]="'item.page.edit'"></ds-dso-page-edit-button>
</div>
</div>
</div>
<div class="simple-view-link my-3" *ngIf="!fromWfi">
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
{{"item.page.link.simple" | translate}}
</a>
</div>
<table class="table table-responsive table-striped">
<tbody>
<div class="simple-view-link my-3" *ngIf="!fromWfi">
<a class="btn btn-outline-primary" [routerLink]="[(itemPageRoute$ | async)]">
{{"item.page.link.simple" | translate}}
</a>
</div>
<table class="table table-responsive table-striped">
<tbody>
<ng-container *ngFor="let mdEntry of (metadata$ | async) | keyvalue">
<tr *ngFor="let mdValue of mdEntry.value">
<td>{{mdEntry.key}}</td>
@@ -24,14 +26,16 @@
<td>{{mdValue.language}}</td>
</tr>
</ng-container>
</tbody>
</table>
<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 class="button-row bottom" *ngIf="fromWfi">
<div class="text-right">
<button class="btn btn-outline-secondary mr-1" (click)="back()"><i class="fas fa-arrow-left"></i> {{'item.page.return' | translate}}</button>
</tbody>
</table>
<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 class="button-row bottom" *ngIf="fromWfi">
<div class="text-right">
<button class="btn btn-outline-secondary mr-1" (click)="back()"><i
class="fas fa-arrow-left"></i> {{'item.page.return' | translate}}</button>
</div>
</div>
</div>
</div>

View File

@@ -11,12 +11,15 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
import { VarDirective } from '../../shared/utils/var.directive';
import { RouterTestingModule } from '@angular/router/testing';
import { Item } from '../../core/shared/item.model';
import { of as observableOf } from 'rxjs';
import { BehaviorSubject, of as observableOf } from 'rxjs';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { AuthService } from '../../core/auth/auth.service';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { createRelationshipsObservable } from '../simple/item-types/shared/item.component.spec';
import { RemoteData } from '../../core/data/remote-data';
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -30,6 +33,13 @@ const mockItem: Item = Object.assign(new Item(), {
}
});
const mockWithdrawnItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
metadata: [],
relationships: createRelationshipsObservable(),
isWithdrawn: true
});
const metadataServiceStub = {
/* tslint:disable:no-empty */
processRemoteData: () => {
@@ -44,6 +54,7 @@ describe('FullItemPageComponent', () => {
let authService: AuthService;
let routeStub: ActivatedRouteStub;
let routeData;
let authorizationDataService: AuthorizationDataService;
@@ -61,6 +72,10 @@ describe('FullItemPageComponent', () => {
data: observableOf(routeData)
});
authorizationDataService = jasmine.createSpyObj('authorizationDataService', {
isAuthorized: observableOf(false),
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
@@ -74,6 +89,7 @@ describe('FullItemPageComponent', () => {
{ provide: ItemDataService, useValue: {} },
{ provide: MetadataService, useValue: metadataServiceStub },
{ provide: AuthService, useValue: authService },
{ provide: AuthorizationDataService, useValue: authorizationDataService },
],
schemas: [NO_ERRORS_SCHEMA]
@@ -111,4 +127,53 @@ describe('FullItemPageComponent', () => {
expect(simpleViewBtn).toBeFalsy();
});
}));
describe('when the item is withdrawn and the user is an admin', () => {
beforeEach(() => {
comp.isAdmin$ = observableOf(true);
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockWithdrawnItem));
fixture.detectChanges();
});
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader.nativeElement).toBeDefined();
});
});
describe('when the item is withdrawn and the user is not an admin', () => {
beforeEach(() => {
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockWithdrawnItem));
fixture.detectChanges();
});
it('should not display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader).toBeNull();
});
});
describe('when the item is not withdrawn and the user is an admin', () => {
beforeEach(() => {
comp.isAdmin$ = observableOf(true);
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockItem));
fixture.detectChanges();
});
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader.nativeElement).toBeDefined();
});
});
describe('when the item is not withdrawn and the user is not an admin', () => {
beforeEach(() => {
comp.itemRD$ = new BehaviorSubject<RemoteData<Item>>(createSuccessfulRemoteDataObject(mockItem));
fixture.detectChanges();
});
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('.full-item-info'));
expect(objectLoader.nativeElement).toBeDefined();
});
});
});

View File

@@ -15,6 +15,7 @@ import { fadeInOut } from '../../shared/animations/fade';
import { hasValue } from '../../shared/empty.util';
import { AuthService } from '../../core/auth/auth.service';
import { Location } from '@angular/common';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
/**
@@ -46,8 +47,9 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
router: Router,
items: ItemDataService,
authService: AuthService,
authorizationService: AuthorizationDataService,
private _location: Location) {
super(route, router, items, authService);
super(route, router, items, authService, authorizationService);
}
/*** AoT inheritance fix, will hopefully be resolved in the near future **/

View File

@@ -4,7 +4,7 @@
<ds-item-alerts [item]="item"></ds-item-alerts>
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
<ds-view-tracker [object]="item"></ds-view-tracker>
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
<ds-item-versions class="mt-2" [item]="item"></ds-item-versions>
</div>
</div>

View File

@@ -21,6 +21,7 @@ import {
} from '../../shared/remote-data.utils';
import { AuthService } from '../../core/auth/auth.service';
import { createPaginatedList } from '../../shared/testing/utils.test';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -28,10 +29,18 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
const mockWithdrawnItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
metadata: [],
relationships: createRelationshipsObservable(),
isWithdrawn: true
});
describe('ItemPageComponent', () => {
let comp: ItemPageComponent;
let fixture: ComponentFixture<ItemPageComponent>;
let authService: AuthService;
let authorizationDataService: AuthorizationDataService;
const mockMetadataService = {
/* tslint:disable:no-empty */
@@ -48,6 +57,9 @@ describe('ItemPageComponent', () => {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
authorizationDataService = jasmine.createSpyObj('authorizationDataService', {
isAuthorized: observableOf(false),
});
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
@@ -63,6 +75,7 @@ describe('ItemPageComponent', () => {
{ provide: MetadataService, useValue: mockMetadataService },
{ provide: Router, useValue: {} },
{ provide: AuthService, useValue: authService },
{ provide: AuthorizationDataService, useValue: authorizationDataService },
],
schemas: [NO_ERRORS_SCHEMA]
@@ -102,4 +115,53 @@ describe('ItemPageComponent', () => {
});
});
describe('when the item is withdrawn and the user is an admin', () => {
beforeEach(() => {
comp.isAdmin$ = observableOf(true);
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockWithdrawnItem);
fixture.detectChanges();
});
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
expect(objectLoader.nativeElement).toBeDefined();
});
});
describe('when the item is withdrawn and the user is not an admin', () => {
beforeEach(() => {
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockWithdrawnItem);
fixture.detectChanges();
});
it('should not display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
expect(objectLoader).toBeNull();
});
});
describe('when the item is not withdrawn and the user is an admin', () => {
beforeEach(() => {
comp.isAdmin$ = observableOf(true);
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockItem);
fixture.detectChanges();
});
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
expect(objectLoader.nativeElement).toBeDefined();
});
});
describe('when the item is not withdrawn and the user is not an admin', () => {
beforeEach(() => {
comp.itemRD$ = createSuccessfulRemoteDataObject$(mockItem);
fixture.detectChanges();
});
it('should display the item', () => {
const objectLoader = fixture.debugElement.query(By.css('ds-listable-object-component-loader'));
expect(objectLoader.nativeElement).toBeDefined();
});
});
});

View File

@@ -1,4 +1,4 @@
import { map } from 'rxjs/operators';
import { map, switchMap } from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@@ -13,6 +13,8 @@ import { getAllSucceededRemoteDataPayload, redirectOn4xx } from '../../core/shar
import { ViewMode } from '../../core/shared/view-mode.model';
import { AuthService } from '../../core/auth/auth.service';
import { getItemPageRoute } from '../item-page-routing-paths';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
/**
* This component renders a simple item page.
@@ -48,11 +50,17 @@ export class ItemPageComponent implements OnInit {
*/
itemPageRoute$: Observable<string>;
/**
* Whether the current user is an admin or not
*/
isAdmin$: Observable<boolean>;
constructor(
protected route: ActivatedRoute,
private router: Router,
private items: ItemDataService,
private authService: AuthService,
private authorizationService: AuthorizationDataService
) { }
/**
@@ -67,5 +75,7 @@ export class ItemPageComponent implements OnInit {
getAllSucceededRemoteDataPayload(),
map((item) => getItemPageRoute(item))
);
this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf);
}
}

View File

@@ -1,8 +1,13 @@
<div>
<div *ngIf="item && !item.isDiscoverable" class="private-warning">
<ds-alert [type]="AlertTypeEnum.Warning" [content]="'item.alerts.private' | translate"></ds-alert>
</div>
<div *ngIf="item && item.isWithdrawn" class="withdrawn-warning">
<ds-alert [type]="AlertTypeEnum.Warning" [content]="'item.alerts.withdrawn' | translate"></ds-alert>
</div>
<div *ngIf="item && !item.isDiscoverable" class="private-warning">
<ds-alert [type]="AlertTypeEnum.Warning" [content]="'item.alerts.private' | translate"></ds-alert>
</div>
<div *ngIf="item && item.isWithdrawn" class="withdrawn-warning">
<ds-alert [type]="AlertTypeEnum.Warning">
<div class="d-flex justify-content-between flex-wrap">
<span>{{'item.alerts.withdrawn' | translate}}</span>
<a routerLink="/home" class="btn btn-primary btn-sm">{{"404.link.home-page" | translate}}</a>
</div>
</ds-alert>
</div>
</div>