Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6bf086b
Updated work on routing by id.
mspalti Sep 26, 2019
e41b308
Added new unit tests.
mspalti Sep 30, 2019
8fe37ee
Minor change to comment.
mspalti Oct 1, 2019
24be6c1
Added unit tests.
mspalti Oct 1, 2019
dd817c9
Fixed lint errors.
mspalti Oct 1, 2019
b89e57a
Added unit test for isCachedOrPending lookup by id (FindByIdRequest).
mspalti Oct 1, 2019
efc91a4
Updated work on routing by id.
mspalti Sep 26, 2019
5ec9b7c
Added new unit tests.
mspalti Sep 30, 2019
a1d21cd
Minor change to comment.
mspalti Oct 1, 2019
2aaab83
Added unit tests.
mspalti Oct 1, 2019
5e40d7a
Fixed lint errors.
mspalti Oct 1, 2019
2d49f3e
Added unit test for isCachedOrPending lookup by id (FindByIdRequest).
mspalti Oct 1, 2019
85d179e
Bugfix for request by handle (removed unnecessary encoding)
mspalti Oct 15, 2019
b695da8
Encoding removed.
mspalti Oct 17, 2019
6b26506
Added matcher function to router that supports handle paths (eliminat…
mspalti Oct 17, 2019
dfd1881
Added support for using the dso (uuid) endpoint.
mspalti Oct 18, 2019
03c36ab
Updated unit test.
mspalti Oct 18, 2019
c3095e3
Merge branch 'routing_by_id_2' of https://github.com/mspalti/dspace-a…
mspalti Oct 30, 2019
d3a84c7
Updated matcher in lookup-by-id routing module.
mspalti Oct 30, 2019
9a0a164
Removed the success filter from DSO data response so that all respons…
mspalti Oct 30, 2019
d309b20
Removed the handle index (not needed because the uuid index will alwa…
mspalti Oct 30, 2019
0dd765d
Reverted object cache method names back to the original UUID convention.
mspalti Oct 30, 2019
dc43e23
Added not found for identifier message to i18n.
mspalti Nov 1, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions resources/i18n/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
"error.collection": "Error fetching collection",
"error.collections": "Error fetching collections",
"error.community": "Error fetching community",
"error.identifier": "No item found for the identifier",
"error.default": "Error",
"error.item": "Error fetching item",
"error.items": "Error fetching items",
Expand Down
41 changes: 41 additions & 0 deletions src/app/+lookup-by-id/lookup-by-id-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { LookupGuard } from './lookup-guard';
import { NgModule } from '@angular/core';
import { RouterModule, UrlSegment } from '@angular/router';
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
import { hasValue, isNotEmpty } from '../shared/empty.util';

@NgModule({
imports: [
RouterModule.forChild([
{
matcher: (url) => {
// The expected path is :idType/:id
const idType = url[0].path;
// Allow for handles that are delimited with a forward slash.
const id = url
.slice(1)
.map((us: UrlSegment) => us.path)
.join('/');
if (isNotEmpty(idType) && isNotEmpty(id)) {
return {
consumed: url,
posParams: {
idType: new UrlSegment(idType, {}),
id: new UrlSegment(id, {})
}
};
}
return null;
},
canActivate: [LookupGuard],
component: ObjectNotFoundComponent }
])
],
providers: [
LookupGuard
]
})

export class LookupRoutingModule {

}
23 changes: 23 additions & 0 deletions src/app/+lookup-by-id/lookup-by-id.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../shared/shared.module';
import { LookupRoutingModule } from './lookup-by-id-routing.module';
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';

@NgModule({
imports: [
LookupRoutingModule,
CommonModule,
SharedModule,
],
declarations: [
ObjectNotFoundComponent
],
providers: [
DsoRedirectDataService
]
})
export class LookupIdModule {

}
50 changes: 50 additions & 0 deletions src/app/+lookup-by-id/lookup-guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { LookupGuard } from './lookup-guard';
import { of as observableOf } from 'rxjs';
import { IdentifierType } from '../core/data/request.models';

describe('LookupGuard', () => {
let dsoService: any;
let guard: any;

beforeEach(() => {
dsoService = {
findById: jasmine.createSpy('findById').and.returnValue(observableOf({ hasFailed: false,
hasSucceeded: true }))
};
guard = new LookupGuard(dsoService);
});

it('should call findById with handle params', () => {
const scopedRoute = {
params: {
id: '1234',
idType: '123456789'
}
};
guard.canActivate(scopedRoute as any, undefined);
expect(dsoService.findById).toHaveBeenCalledWith('123456789/1234', IdentifierType.HANDLE)
});

it('should call findById with handle params', () => {
const scopedRoute = {
params: {
id: '123456789%2F1234',
idType: 'handle'
}
};
guard.canActivate(scopedRoute as any, undefined);
expect(dsoService.findById).toHaveBeenCalledWith('123456789%2F1234', IdentifierType.HANDLE)
});

it('should call findById with UUID params', () => {
const scopedRoute = {
params: {
id: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',
idType: 'uuid'
}
};
guard.canActivate(scopedRoute as any, undefined);
expect(dsoService.findById).toHaveBeenCalledWith('34cfed7c-f597-49ef-9cbe-ea351f0023c2', IdentifierType.UUID)
});

});
53 changes: 53 additions & 0 deletions src/app/+lookup-by-id/lookup-guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Injectable } from '@angular/core';
import { IdentifierType } from '../core/data/request.models';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { RemoteData } from '../core/data/remote-data';
import { FindByIDRequest } from '../core/data/request.models';
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';

interface LookupParams {
type: IdentifierType;
id: string;
}

@Injectable()
export class LookupGuard implements CanActivate {

constructor(private dsoService: DsoRedirectDataService) {
}

canActivate(route: ActivatedRouteSnapshot, state:RouterStateSnapshot): Observable<boolean> {
const params = this.getLookupParams(route);
return this.dsoService.findById(params.id, params.type).pipe(
map((response: RemoteData<FindByIDRequest>) => response.hasFailed)
);
}

private getLookupParams(route: ActivatedRouteSnapshot): LookupParams {
let type;
let id;
const idType = route.params.idType;

// If the idType is not recognized, assume a legacy handle request (handle/prefix/id)
if (idType !== IdentifierType.HANDLE && idType !== IdentifierType.UUID) {
type = IdentifierType.HANDLE;
const prefix = route.params.idType;
const handleId = route.params.id;
id = `${prefix}/${handleId}`;

} else if (route.params.idType === IdentifierType.HANDLE) {
type = IdentifierType.HANDLE;
id = route.params.id;

} else {
type = IdentifierType.UUID;
id = route.params.id;
}
return {
type: type,
id: id
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div class="object-not-found container">
<h1>{{"error.identifier" | translate}}</h1>
<h2><small><em>{{missingItem}}</em></small></h2>
<br />
<p class="text-center">
<a routerLink="/home" class="btn btn-primary">{{"404.link.home-page" | translate}}</a>
</p>
</div>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TranslateModule } from '@ngx-translate/core';

import { ObjectNotFoundComponent } from './objectnotfound.component';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
import { of as observableOf } from 'rxjs';
import { ActivatedRoute } from '@angular/router';

describe('ObjectNotFoundComponent', () => {
let comp: ObjectNotFoundComponent;
let fixture: ComponentFixture<ObjectNotFoundComponent>;
const testUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2';
const uuidType = 'uuid';
const handlePrefix = '123456789';
const handleId = '22';
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
params: observableOf({id: testUUID, idType: uuidType})
});
const activatedRouteStubHandle = Object.assign(new ActivatedRouteStub(), {
params: observableOf({id: handleId, idType: handlePrefix})
});
describe('uuid request', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot()
], providers: [
{provide: ActivatedRoute, useValue: activatedRouteStub}
],
declarations: [ObjectNotFoundComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ObjectNotFoundComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});

it('should create instance', () => {
expect(comp).toBeDefined()
});

it('should have id and idType', () => {
expect(comp.id).toEqual(testUUID);
expect(comp.idType).toEqual(uuidType);
expect(comp.missingItem).toEqual('uuid: ' + testUUID);
});
});

describe( 'legacy handle request', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot()
], providers: [
{provide: ActivatedRoute, useValue: activatedRouteStubHandle}
],
declarations: [ObjectNotFoundComponent],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ObjectNotFoundComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
});

it('should have handle prefix and id', () => {
expect(comp.id).toEqual(handleId);
expect(comp.idType).toEqual(handlePrefix);
expect(comp.missingItem).toEqual('handle: ' + handlePrefix + '/' + handleId);
});
});

});
43 changes: 43 additions & 0 deletions src/app/+lookup-by-id/objectnotfound/objectnotfound.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@

import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

/**
* This component representing the `PageNotFound` DSpace page.
*/
@Component({
selector: 'ds-objnotfound',
styleUrls: ['./objectnotfound.component.scss'],
templateUrl: './objectnotfound.component.html',
changeDetection: ChangeDetectionStrategy.Default
})
export class ObjectNotFoundComponent implements OnInit {

idType: string;

id: string;

missingItem: string;

/**
* Initialize instance variables
*
* @param {AuthService} authservice
* @param {ServerResponseService} responseService
*/
constructor(private route: ActivatedRoute) {
route.params.subscribe((params) => {
this.idType = params.idType;
this.id = params.id;
})
}

ngOnInit(): void {
if (this.idType.startsWith('handle') || this.idType.startsWith('uuid')) {
this.missingItem = this.idType + ': ' + this.id;
} else {
this.missingItem = 'handle: ' + this.idType + '/' + this.id;
}
}

}
2 changes: 2 additions & 0 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export function getAdminModulePath() {
RouterModule.forRoot([
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
{ path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
{ path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
{ path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
{ path: COLLECTION_MODULE_PATH, loadChildren: './+collection-page/collection-page.module#CollectionPageModule' },
{ path: ITEM_MODULE_PATH, loadChildren: './+item-page/item-page.module#ItemPageModule' },
Expand Down
2 changes: 1 addition & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ const EXPORTS = [
...PROVIDERS
],
declarations: [
...DECLARATIONS,
...DECLARATIONS
],
exports: [
...EXPORTS
Expand Down
1 change: 1 addition & 0 deletions src/app/core/cache/object-cache.reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export abstract class TypedObject {
*/
export class CacheableObject extends TypedObject {
uuid?: string;
handle?: string;
self: string;
// isNew: boolean;
// dirtyType: DirtyType;
Expand Down
5 changes: 3 additions & 2 deletions src/app/core/cache/object-cache.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { applyPatch, Operation } from 'fast-json-patch';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';

import { distinctUntilChanged, filter, map, mergeMap, take, } from 'rxjs/operators';
import { hasNoValue, hasValue, isNotEmpty } from '../../shared/empty.util';
import { hasNoValue, isNotEmpty } from '../../shared/empty.util';
import { CoreState } from '../core.reducers';
import { coreSelector } from '../core.selectors';
import { RestRequestMethod } from '../data/rest-request-method';
Expand Down Expand Up @@ -80,7 +80,8 @@ export class ObjectCacheService {
* @return Observable<NormalizedObject<T>>
* An observable of the requested object in normalized form
*/
getObjectByUUID<T extends CacheableObject>(uuid: string): Observable<NormalizedObject<T>> {
getObjectByUUID<T extends CacheableObject>(uuid: string):
Observable<NormalizedObject<T>> {
return this.store.pipe(
select(selfLinkFromUuidSelector(uuid)),
mergeMap((selfLink: string) => this.getObjectBySelfLink(selfLink)
Expand Down
3 changes: 2 additions & 1 deletion src/app/core/data/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,9 @@ export abstract class DataService<T extends CacheableObject> {
}

findById(id: string): Observable<RemoteData<T>> {

const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, id)));
map((endpoint: string) => this.getIDHref(endpoint, encodeURIComponent(id))));

hrefObs.pipe(
find((href: string) => hasValue(href)))
Expand Down
Loading