Skip to content

Commit 4c99d15

Browse files
FrancescoMolinaroatarix83
authored andcommitted
Merged in task/dspace-cris-2023_02_x/DSC-2176 (pull request DSpace#2801)
[DSC-2176] prevent redirect issue while fetching signposting links Approved-by: Giuseppe Digilio
2 parents fbd8606 + 144f574 commit 4c99d15

8 files changed

Lines changed: 112 additions & 29 deletions

src/app/core/services/server-response.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class ServerResponseService {
7373
* @param content
7474
*/
7575
setHeader(header: string, content: string) {
76-
if (this.response) {
76+
if (this.response && !this.response.writableEnded) {
7777
this.response.setHeader(header, content);
7878
}
7979
}

src/app/item-page/full/full-item-page.component.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ describe('FullItemPageComponent', () => {
153153
beforeEach(waitForAsync(() => {
154154
routeData = {
155155
dso: createSuccessfulRemoteDataObject(mockItem),
156+
links: [mocklink, mocklink2]
156157
};
157158

158159
routeStub = Object.assign(new ActivatedRouteStub(), {

src/app/item-page/full/full-item-page.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { hasValue } from '../../shared/empty.util';
1616
import { Location } from '@angular/common';
1717
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
1818
import { ServerResponseService } from '../../core/services/server-response.service';
19-
import { SignpostingDataService } from '../../core/data/signposting-data.service';
2019
import { LinkHeadService } from '../../core/services/link-head.service';
2120
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
2221

@@ -56,12 +55,11 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
5655
protected authorizationService: AuthorizationDataService,
5756
protected _location: Location,
5857
protected responseService: ServerResponseService,
59-
protected signpostingDataService: SignpostingDataService,
6058
protected linkHeadService: LinkHeadService,
6159
@Inject(PLATFORM_ID) protected platformId: string,
6260
@Inject(APP_CONFIG) private appConfig: AppConfig,
6361
) {
64-
super(route, router, items, authorizationService, responseService, signpostingDataService, linkHeadService, platformId);
62+
super(route, router, items, authorizationService, responseService, linkHeadService, platformId);
6563
}
6664

6765
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
@@ -89,6 +87,7 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
8987

9088
ngOnDestroy() {
9189
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
90+
super.ngOnDestroy();
9291
}
9392

9493
protected increaseLimit(metadataKey: string) {

src/app/item-page/item-page-routing.module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths';
1919
import { CrisItemPageTabResolver } from './cris-item-page-tab.resolver';
2020
import { OrcidPageComponent } from './orcid-page/orcid-page.component';
2121
import { OrcidPageGuard } from './orcid-page/orcid-page.guard';
22+
import { signpostingLinksResolver } from './simple/link-resolver/signposting-links.resolver';
23+
2224

2325
@NgModule({
2426
imports: [
@@ -28,6 +30,7 @@ import { OrcidPageGuard } from './orcid-page/orcid-page.guard';
2830
resolve: {
2931
dso: ItemPageResolver,
3032
breadcrumb: ItemBreadcrumbResolver,
33+
links: signpostingLinksResolver,
3134
},
3235
runGuardsAndResolvers: 'always',
3336
children: [

src/app/item-page/simple/item-page.component.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ describe('ItemPageComponent', () => {
6868
/* eslint-enable no-empty, @typescript-eslint/no-empty-function */
6969
};
7070
const mockRoute = Object.assign(new ActivatedRouteStub(), {
71-
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) })
71+
data: observableOf({ dso: createSuccessfulRemoteDataObject(mockItem) , links: [mocklink, mocklink2] })
7272
});
7373

7474
beforeEach(waitForAsync(() => {

src/app/item-page/simple/item-page.component.ts

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
33
import { isPlatformServer } from '@angular/common';
44

55
import { Observable } from 'rxjs';
6-
import { map, take } from 'rxjs/operators';
6+
import { map } from 'rxjs/operators';
77

88
import { ItemDataService } from '../../core/data/item-data.service';
99
import { RemoteData } from '../../core/data/remote-data';
@@ -15,7 +15,6 @@ import { getItemPageRoute } from '../item-page-routing-paths';
1515
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
1616
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
1717
import { ServerResponseService } from '../../core/services/server-response.service';
18-
import { SignpostingDataService } from '../../core/data/signposting-data.service';
1918
import { SignpostingLink } from '../../core/data/signposting-links.model';
2019
import { isNotEmpty } from '../../shared/empty.util';
2120
import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service';
@@ -79,7 +78,6 @@ export class ItemPageComponent implements OnInit, OnDestroy {
7978
protected items: ItemDataService,
8079
protected authorizationService: AuthorizationDataService,
8180
protected responseService: ServerResponseService,
82-
protected signpostingDataService: SignpostingDataService,
8381
protected linkHeadService: LinkHeadService,
8482
@Inject(PLATFORM_ID) protected platformId: string
8583
) {
@@ -111,29 +109,25 @@ export class ItemPageComponent implements OnInit, OnDestroy {
111109
* @private
112110
*/
113111
private initPageLinks(): void {
114-
this.route.params.subscribe(params => {
115-
this.signpostingDataService.getLinks(params.id).pipe(take(1)).subscribe((signpostingLinks: SignpostingLink[]) => {
116-
let links = '';
117-
this.signpostingLinks = signpostingLinks;
118-
119-
signpostingLinks.forEach((link: SignpostingLink) => {
120-
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ');
121-
let tag: LinkDefinition = {
122-
href: link.href,
123-
rel: link.rel
124-
};
125-
if (isNotEmpty(link.type)) {
126-
tag = Object.assign(tag, {
127-
type: link.type
128-
});
129-
}
130-
this.linkHeadService.addTag(tag);
131-
});
132-
133-
if (isPlatformServer(this.platformId)) {
134-
this.responseService.setHeader('Link', links);
112+
this.route.data.subscribe(data => {
113+
this.signpostingLinks = data.links ?? [];
114+
let links = '';
115+
this.signpostingLinks.forEach((link: SignpostingLink) => {
116+
links = links + (isNotEmpty(links) ? ', ' : '') + `<${link.href}> ; rel="${link.rel}"` + (isNotEmpty(link.type) ? ` ; type="${link.type}" ` : ' ');
117+
let tag: LinkDefinition = {
118+
href: link.href,
119+
rel: link.rel
120+
};
121+
if (isNotEmpty(link.type)) {
122+
tag = Object.assign(tag, {
123+
type: link.type
124+
});
135125
}
126+
this.linkHeadService.addTag(tag);
136127
});
128+
if (isPlatformServer(this.platformId)) {
129+
this.responseService.setHeader('Link', links);
130+
}
137131
});
138132
}
139133

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
2+
import { SignpostingDataService } from '../../../core/data/signposting-data.service';
3+
import { of } from 'rxjs';
4+
import { TestBed } from '@angular/core/testing';
5+
import { signpostingLinksResolver } from './signposting-links.resolver';
6+
describe('signpostingLinksResolver', () => {
7+
let resolver: any;
8+
let route: ActivatedRouteSnapshot;
9+
let state = {};
10+
let signpostingDataService: SignpostingDataService;
11+
const testUuid = '1234567890';
12+
const mocklink = {
13+
href: 'http://test.org',
14+
rel: 'rel1',
15+
type: 'type1'
16+
};
17+
const mocklink2 = {
18+
href: 'http://test2.org',
19+
rel: 'rel2',
20+
type: undefined
21+
};
22+
const resolvedLinks = `<${mocklink.href}> ; rel="${mocklink.rel}" ; type="${mocklink.type}" , <${mocklink2.href}> ; rel="${mocklink2.rel}" `;
23+
const mockTag2 = {
24+
href: 'http://test2.org',
25+
rel: 'rel2',
26+
};
27+
function init() {
28+
route = Object.assign(new ActivatedRouteSnapshot(), {
29+
params: {
30+
id: testUuid,
31+
},
32+
});
33+
signpostingDataService = jasmine.createSpyObj('signpostingDataService', {
34+
getLinks: of([mocklink, mocklink2]),
35+
setLinks: () => null,
36+
});
37+
resolver = signpostingLinksResolver;
38+
}
39+
function initTestbed() {
40+
TestBed.configureTestingModule({
41+
providers: [
42+
{provide: RouterStateSnapshot, useValue: state},
43+
{provide: ActivatedRouteSnapshot, useValue: route},
44+
{provide: SignpostingDataService, useValue: signpostingDataService},
45+
]
46+
});
47+
}
48+
describe('when an item page is loaded', () => {
49+
beforeEach(() => {
50+
init();
51+
initTestbed();
52+
});
53+
it('should retrieve links and set header and head tags', () => {
54+
TestBed.runInInjectionContext(() => {
55+
resolver(route, state).subscribe(() => {
56+
expect(signpostingDataService.getLinks).toHaveBeenCalledWith(testUuid);
57+
});
58+
});
59+
});
60+
});
61+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from '@angular/router';
2+
import { SignpostingLink } from '../../../core/data/signposting-links.model';
3+
import { Observable, of } from 'rxjs';
4+
import { inject } from '@angular/core';
5+
import { hasValue } from '../../../shared/empty.util';
6+
import { SignpostingDataService } from '../../../core/data/signposting-data.service';
7+
8+
/**
9+
* Resolver to retrieve signposting links before an eventual redirect of any route guard
10+
*
11+
* @param route
12+
* @param state
13+
* @param signpostingDataService
14+
*/
15+
export const signpostingLinksResolver: ResolveFn<Observable<SignpostingLink[]>> = (
16+
route: ActivatedRouteSnapshot,
17+
state: RouterStateSnapshot,
18+
signpostingDataService: SignpostingDataService = inject(SignpostingDataService),
19+
): Observable<SignpostingLink[]> => {
20+
const uuid = route.params.id;
21+
if (!hasValue(uuid)) {
22+
return of([]);
23+
}
24+
return signpostingDataService.getLinks(uuid);
25+
};

0 commit comments

Comments
 (0)