Merge pull request #891 from atmire/DSO-404-fix

Fix DSO 404 pages
This commit is contained in:
Tim Donohue
2020-10-28 13:04:57 -05:00
committed by GitHub
9 changed files with 54 additions and 30 deletions

View File

@@ -17,7 +17,7 @@ import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { import {
getSucceededRemoteData, getSucceededRemoteData,
redirectToPageNotFoundOn404, redirectOn404Or401,
toDSpaceObjectListRD toDSpaceObjectListRD
} from '../core/shared/operators'; } from '../core/shared/operators';
@@ -63,7 +63,7 @@ export class CollectionPageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.collectionRD$ = this.route.data.pipe( this.collectionRD$ = this.route.data.pipe(
map((data) => data.dso as RemoteData<Collection>), map((data) => data.dso as RemoteData<Collection>),
redirectToPageNotFoundOn404(this.router), redirectOn404Or401(this.router),
take(1) take(1)
); );
this.logoRD$ = this.collectionRD$.pipe( this.logoRD$ = this.collectionRD$.pipe(

View File

@@ -13,7 +13,7 @@ import { MetadataService } from '../core/metadata/metadata.service';
import { fadeInOut } from '../shared/animations/fade'; import { fadeInOut } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { redirectToPageNotFoundOn404 } from '../core/shared/operators'; import { redirectOn404Or401 } from '../core/shared/operators';
@Component({ @Component({
selector: 'ds-community-page', selector: 'ds-community-page',
@@ -47,7 +47,7 @@ export class CommunityPageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.communityRD$ = this.route.data.pipe( this.communityRD$ = this.route.data.pipe(
map((data) => data.dso as RemoteData<Community>), map((data) => data.dso as RemoteData<Community>),
redirectToPageNotFoundOn404(this.router) redirectOn404Or401(this.router)
); );
this.logoRD$ = this.communityRD$.pipe( this.logoRD$ = this.communityRD$.pipe(
map((rd: RemoteData<Community>) => rd.payload), map((rd: RemoteData<Community>) => rd.payload),

View File

@@ -11,7 +11,7 @@ import { Item } from '../../core/shared/item.model';
import { MetadataService } from '../../core/metadata/metadata.service'; import { MetadataService } from '../../core/metadata/metadata.service';
import { fadeInOut } from '../../shared/animations/fade'; import { fadeInOut } from '../../shared/animations/fade';
import { redirectToPageNotFoundOn404 } from '../../core/shared/operators'; import { redirectOn404Or401 } from '../../core/shared/operators';
import { ViewMode } from '../../core/shared/view-mode.model'; import { ViewMode } from '../../core/shared/view-mode.model';
/** /**
@@ -56,7 +56,7 @@ export class ItemPageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.itemRD$ = this.route.data.pipe( this.itemRD$ = this.route.data.pipe(
map((data) => data.item as RemoteData<Item>), map((data) => data.item as RemoteData<Item>),
redirectToPageNotFoundOn404(this.router) redirectOn404Or401(this.router)
); );
this.metadataService.processRemoteData(this.itemRD$); this.metadataService.processRemoteData(this.itemRD$);
} }

View File

@@ -55,12 +55,18 @@ export function getDSORoute(dso: DSpaceObject): string {
} }
} }
export const UNAUTHORIZED_PATH = 'unauthorized'; export const UNAUTHORIZED_PATH = '401';
export function getUnauthorizedRoute() { export function getUnauthorizedRoute() {
return `/${UNAUTHORIZED_PATH}`; return `/${UNAUTHORIZED_PATH}`;
} }
export const PAGE_NOT_FOUND_PATH = '404';
export function getPageNotFoundRoute() {
return `/${PAGE_NOT_FOUND_PATH}`;
}
export const INFO_MODULE_PATH = 'info'; export const INFO_MODULE_PATH = 'info';
export function getInfoModulePath() { export function getInfoModulePath() {
return `/${INFO_MODULE_PATH}`; return `/${INFO_MODULE_PATH}`;

View File

@@ -3,12 +3,13 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { DSOBreadcrumbsService } from './dso-breadcrumbs.service'; import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
import { DataService } from '../data/data.service'; import { DataService } from '../data/data.service';
import { getRemoteDataPayload, getSucceededRemoteData } from '../shared/operators'; import { getRemoteDataPayload } from '../shared/operators';
import { map } from 'rxjs/operators'; import { filter, map, take } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { DSpaceObject } from '../shared/dspace-object.model'; import { DSpaceObject } from '../shared/dspace-object.model';
import { ChildHALResource } from '../shared/child-hal-resource.model'; import { ChildHALResource } from '../shared/child-hal-resource.model';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model'; import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { hasValue } from '../../shared/empty.util';
/** /**
* The class that resolves the BreadcrumbConfig object for a DSpaceObject * The class that resolves the BreadcrumbConfig object for a DSpaceObject
@@ -29,12 +30,17 @@ export abstract class DSOBreadcrumbResolver<T extends ChildHALResource & DSpaceO
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> { resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<BreadcrumbConfig<T>> {
const uuid = route.params.id; const uuid = route.params.id;
return this.dataService.findById(uuid, ...this.followLinks).pipe( return this.dataService.findById(uuid, ...this.followLinks).pipe(
getSucceededRemoteData(), filter((rd) => hasValue(rd.error) || hasValue(rd.payload)),
take(1),
getRemoteDataPayload(), getRemoteDataPayload(),
map((object: T) => { map((object: T) => {
if (hasValue(object)) {
const fullPath = state.url; const fullPath = state.url;
const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid; const url = fullPath.substr(0, fullPath.indexOf(uuid)) + uuid;
return {provider: this.breadcrumbService, key: object, url: url}; return {provider: this.breadcrumbService, key: object, url: url};
} else {
return undefined;
}
}) })
); );
} }

View File

@@ -13,7 +13,8 @@ import {
getRequestFromRequestUUID, getRequestFromRequestUUID,
getResourceLinksFromResponse, getResourceLinksFromResponse,
getResponseFromEntry, getResponseFromEntry,
getSucceededRemoteData, redirectToPageNotFoundOn404 getSucceededRemoteData,
redirectOn404Or401
} from './operators'; } from './operators';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { RemoteDataError } from '../data/remote-data-error'; import { RemoteDataError } from '../data/remote-data-error';
@@ -199,7 +200,7 @@ describe('Core Module - RxJS Operators', () => {
}); });
}); });
describe('redirectToPageNotFoundOn404', () => { describe('redirectOn404Or401', () => {
let router; let router;
beforeEach(() => { beforeEach(() => {
router = jasmine.createSpyObj('router', ['navigateByUrl']); router = jasmine.createSpyObj('router', ['navigateByUrl']);
@@ -208,21 +209,28 @@ describe('Core Module - RxJS Operators', () => {
it('should call navigateByUrl to a 404 page, when the remote data contains a 404 error', () => { it('should call navigateByUrl to a 404 page, when the remote data contains a 404 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(404, 'Not Found', 'Object was not found')); const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(404, 'Not Found', 'Object was not found'));
observableOf(testRD).pipe(redirectToPageNotFoundOn404(router)).subscribe(); observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
expect(router.navigateByUrl).toHaveBeenCalledWith('/404', { skipLocationChange: true }); expect(router.navigateByUrl).toHaveBeenCalledWith('/404', { skipLocationChange: true });
}); });
it('should not call navigateByUrl to a 404 page, when the remote data contains another error than a 404', () => { it('should call navigateByUrl to a 401 page, when the remote data contains a 401 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(401, 'Unauthorized', 'The current user is unauthorized'));
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
expect(router.navigateByUrl).toHaveBeenCalledWith('/401', { skipLocationChange: true });
});
it('should not call navigateByUrl to a 404 or 401 page, when the remote data contains another error than a 404 or 401', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(500, 'Server Error', 'Something went wrong')); const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(500, 'Server Error', 'Something went wrong'));
observableOf(testRD).pipe(redirectToPageNotFoundOn404(router)).subscribe(); observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
expect(router.navigateByUrl).not.toHaveBeenCalled(); expect(router.navigateByUrl).not.toHaveBeenCalled();
}); });
it('should not call navigateByUrl to a 404 page, when the remote data contains no error', () => { it('should not call navigateByUrl to a 404 or 401 page, when the remote data contains no error', () => {
const testRD = createSuccessfulRemoteDataObject(undefined); const testRD = createSuccessfulRemoteDataObject(undefined);
observableOf(testRD).pipe(redirectToPageNotFoundOn404(router)).subscribe(); observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe();
expect(router.navigateByUrl).not.toHaveBeenCalled(); expect(router.navigateByUrl).not.toHaveBeenCalled();
}); });
}); });

View File

@@ -13,7 +13,7 @@ import { MetadataField } from '../metadata/metadata-field.model';
import { MetadataSchema } from '../metadata/metadata-schema.model'; import { MetadataSchema } from '../metadata/metadata-schema.model';
import { BrowseDefinition } from './browse-definition.model'; import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { getUnauthorizedRoute } from '../../app-routing-paths'; import { getPageNotFoundRoute, getUnauthorizedRoute } from '../../app-routing-paths';
import { getEndUserAgreementPath } from '../../info/info-routing-paths'; import { getEndUserAgreementPath } from '../../info/info-routing-paths';
/** /**
@@ -171,16 +171,20 @@ export const getAllSucceededRemoteListPayload = () =>
); );
/** /**
* Operator that checks if a remote data object contains a page not found error * Operator that checks if a remote data object returned a 401 or 404 error
* When it does contain such an error, it will redirect the user to a page not found, without altering the current URL * When it does contain such an error, it will redirect the user to the related error page, without altering the current URL
* @param router The router used to navigate to a new page * @param router The router used to navigate to a new page
*/ */
export const redirectToPageNotFoundOn404 = (router: Router) => export const redirectOn404Or401 = (router: Router) =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => <T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe( source.pipe(
tap((rd: RemoteData<T>) => { tap((rd: RemoteData<T>) => {
if (rd.hasFailed && rd.error.statusCode === 404) { if (rd.hasFailed) {
router.navigateByUrl('/404', { skipLocationChange: true }); if (rd.error.statusCode === 404) {
router.navigateByUrl(getPageNotFoundRoute(), {skipLocationChange: true});
} else if (rd.error.statusCode === 401) {
router.navigateByUrl(getUnauthorizedRoute(), {skipLocationChange: true});
}
} }
})); }));

View File

@@ -4,7 +4,7 @@ import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Process } from '../processes/process.model'; import { Process } from '../processes/process.model';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { getFirstSucceededRemoteDataPayload, redirectToPageNotFoundOn404 } from '../../core/shared/operators'; import { getFirstSucceededRemoteDataPayload, redirectOn404Or401 } from '../../core/shared/operators';
import { AlertType } from '../../shared/alert/aletr-type'; import { AlertType } from '../../shared/alert/aletr-type';
import { ProcessDataService } from '../../core/data/processes/process-data.service'; import { ProcessDataService } from '../../core/data/processes/process-data.service';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list';
@@ -49,7 +49,7 @@ export class ProcessDetailComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.processRD$ = this.route.data.pipe( this.processRD$ = this.route.data.pipe(
map((data) => data.process as RemoteData<Process>), map((data) => data.process as RemoteData<Process>),
redirectToPageNotFoundOn404(this.router) redirectOn404Or401(this.router)
); );
this.filesRD$ = this.processRD$.pipe( this.filesRD$ = this.processRD$.pipe(

View File

@@ -4,7 +4,7 @@ import { UsageReportService } from '../../core/statistics/usage-report-data.serv
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { UsageReport } from '../../core/statistics/models/usage-report.model'; import { UsageReport } from '../../core/statistics/models/usage-report.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { getRemoteDataPayload, getSucceededRemoteData, redirectToPageNotFoundOn404 } from '../../core/shared/operators'; import { getRemoteDataPayload, getSucceededRemoteData, redirectOn404Or401 } from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
@@ -55,7 +55,7 @@ export abstract class StatisticsPageComponent<T extends DSpaceObject> implements
protected getScope$(): Observable<DSpaceObject> { protected getScope$(): Observable<DSpaceObject> {
return this.route.data.pipe( return this.route.data.pipe(
map((data) => data.scope as RemoteData<T>), map((data) => data.scope as RemoteData<T>),
redirectToPageNotFoundOn404(this.router), redirectOn404Or401(this.router),
getSucceededRemoteData(), getSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
); );