Skip to content

Commit 2eadda3

Browse files
authored
feat(signposting): add signposting links to content landing pages (#875)
1 parent 01f5a10 commit 2eadda3

File tree

8 files changed

+72
-4
lines changed

8 files changed

+72
-4
lines changed

src/app/features/files/pages/file-detail/file-detail.component.spec.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ describe('FileDetailComponent', () => {
4141
} as unknown as jest.Mocked<DataciteService>;
4242

4343
const mockRoute: Partial<ActivatedRoute> = {
44-
params: of({ providerId: 'osf', preprintId: 'p1' }),
45-
queryParams: of({ providerId: 'osf', preprintId: 'p1' }),
44+
params: of({ providerId: 'osf', fileGuid: 'file-1' }),
45+
queryParams: of({ providerId: 'osf', fileGuid: 'file-1' }),
4646
};
4747
(MOCK_STORE.selectSignal as jest.Mock).mockImplementation((selector) => {
4848
switch (selector) {
@@ -79,6 +79,7 @@ describe('FileDetailComponent', () => {
7979
}).compileComponents();
8080
fixture = TestBed.createComponent(FileDetailComponent);
8181
component = fixture.componentInstance;
82+
document.head.innerHTML = '';
8283
fixture.detectChanges();
8384
});
8485

@@ -95,4 +96,15 @@ describe('FileDetailComponent', () => {
9596
it('should call dataciteService.logIdentifiableView on start ', () => {
9697
expect(dataciteService.logIdentifiableView).toHaveBeenCalledWith(component.fileMetadata$);
9798
});
99+
100+
it('should add signposting tags during SSR', () => {
101+
fixture.detectChanges();
102+
103+
const linkTags = Array.from(document.head.querySelectorAll('link[rel="linkset"]'));
104+
expect(linkTags.length).toBe(2);
105+
expect(linkTags[0].getAttribute('href')).toBe('http://localhost:4200/metadata/file-1/?format=linkset');
106+
expect(linkTags[0].getAttribute('type')).toBe('application/linkset');
107+
expect(linkTags[1].getAttribute('href')).toBe('http://localhost:4200/metadata/file-1/?format=linkset%2Bjson');
108+
expect(linkTags[1].getAttribute('type')).toBe('application/linkset+json');
109+
});
98110
});

src/app/features/files/pages/file-detail/file-detail.component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
effect,
2020
HostBinding,
2121
inject,
22+
OnInit,
2223
signal,
2324
} from '@angular/core';
2425
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
@@ -47,6 +48,7 @@ import { pathJoin } from '@osf/shared/helpers/path-join.helper';
4748
import { CustomConfirmationService } from '@osf/shared/services/custom-confirmation.service';
4849
import { DataciteService } from '@osf/shared/services/datacite/datacite.service';
4950
import { MetaTagsService } from '@osf/shared/services/meta-tags.service';
51+
import { SignpostingService } from '@osf/shared/services/signposting.service';
5052
import { ToastService } from '@osf/shared/services/toast.service';
5153
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
5254
import { FileDetailsModel } from '@shared/models/files/file.model';
@@ -94,7 +96,7 @@ import {
9496
changeDetection: ChangeDetectionStrategy.OnPush,
9597
providers: [DatePipe],
9698
})
97-
export class FileDetailComponent {
99+
export class FileDetailComponent implements OnInit {
98100
@HostBinding('class') classes = 'flex flex-column flex-1 w-full h-full';
99101

100102
readonly store = inject(Store);
@@ -111,6 +113,7 @@ export class FileDetailComponent {
111113
private readonly translateService = inject(TranslateService);
112114
private readonly environment = inject(ENVIRONMENT);
113115
private readonly clipboard = inject(Clipboard);
116+
private readonly signpostingService = inject(SignpostingService);
114117

115118
readonly dataciteService = inject(DataciteService);
116119

@@ -284,6 +287,10 @@ export class FileDetailComponent {
284287
this.dataciteService.logIdentifiableView(this.fileMetadata$).pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
285288
}
286289

290+
ngOnInit(): void {
291+
this.signpostingService.addSignposting(this.fileGuid);
292+
}
293+
287294
getIframeLink(version: string) {
288295
const url = this.getMfrUrlWithVersion(version);
289296
if (url) {

src/app/features/preprints/pages/preprint-details/preprint-details.component.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ describe('PreprintDetailsComponent SSR Tests', () => {
466466
store = TestBed.inject(Store);
467467
fixture = TestBed.createComponent(PreprintDetailsComponent);
468468
component = fixture.componentInstance;
469+
document.head.innerHTML = '';
469470
});
470471

471472
it('should render PreprintDetailsComponent server-side without errors', () => {
@@ -475,6 +476,17 @@ describe('PreprintDetailsComponent SSR Tests', () => {
475476
expect(component).toBeTruthy();
476477
});
477478

479+
it('should add signposting tags during SSR', () => {
480+
fixture.detectChanges();
481+
482+
const linkTags = Array.from(document.head.querySelectorAll('link[rel="linkset"]'));
483+
expect(linkTags.length).toBe(2);
484+
expect(linkTags[0].getAttribute('href')).toBe('http://localhost:4200/metadata/preprint-1/?format=linkset');
485+
expect(linkTags[0].getAttribute('type')).toBe('application/linkset');
486+
expect(linkTags[1].getAttribute('href')).toBe('http://localhost:4200/metadata/preprint-1/?format=linkset%2Bjson');
487+
expect(linkTags[1].getAttribute('type')).toBe('application/linkset+json');
488+
});
489+
478490
it('should not access browser-only APIs during SSR', () => {
479491
const platformId = TestBed.inject(PLATFORM_ID);
480492
expect(platformId).toBe('server');

src/app/features/preprints/pages/preprint-details/preprint-details.component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { FixSpecialCharPipe } from '@osf/shared/pipes/fix-special-char.pipe';
3535
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
3636
import { DataciteService } from '@osf/shared/services/datacite/datacite.service';
3737
import { MetaTagsService } from '@osf/shared/services/meta-tags.service';
38+
import { SignpostingService } from '@osf/shared/services/signposting.service';
3839
import { ToastService } from '@osf/shared/services/toast.service';
3940
import { ContributorsSelectors } from '@osf/shared/stores/contributors';
4041

@@ -104,6 +105,7 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
104105
private readonly prerenderReady = inject(PrerenderReadyService);
105106
private readonly platformId = inject(PLATFORM_ID);
106107
private readonly isBrowser = isPlatformBrowser(this.platformId);
108+
private readonly signpostingService = inject(SignpostingService);
107109

108110
private readonly environment = inject(ENVIRONMENT);
109111

@@ -304,6 +306,8 @@ export class PreprintDetailsComponent implements OnInit, OnDestroy {
304306
this.actions.getPreprintProviderById(this.providerId());
305307
this.fetchPreprint(this.preprintId());
306308

309+
this.signpostingService.addSignposting(this.preprintId());
310+
307311
this.dataciteService.logIdentifiableView(this.preprint$).pipe(takeUntilDestroyed(this.destroyRef)).subscribe();
308312
}
309313

src/app/features/project/overview/project-overview.component.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ describe('ProjectOverviewComponent SSR Tests', () => {
262262
store = TestBed.inject(Store);
263263
fixture = TestBed.createComponent(ProjectOverviewComponent);
264264
component = fixture.componentInstance;
265+
document.head.innerHTML = '';
265266
});
266267

267268
it('should render ProjectOverviewComponent server-side without errors', () => {
@@ -285,6 +286,17 @@ describe('ProjectOverviewComponent SSR Tests', () => {
285286
expect(component).toBeTruthy();
286287
});
287288

289+
it('should add signposting tags during SSR', () => {
290+
fixture.detectChanges();
291+
292+
const linkTags = Array.from(document.head.querySelectorAll('link[rel="linkset"]'));
293+
expect(linkTags.length).toBe(2);
294+
expect(linkTags[0].getAttribute('href')).toBe('http://localhost:4200/metadata/project-123/?format=linkset');
295+
expect(linkTags[0].getAttribute('type')).toBe('application/linkset');
296+
expect(linkTags[1].getAttribute('href')).toBe('http://localhost:4200/metadata/project-123/?format=linkset%2Bjson');
297+
expect(linkTags[1].getAttribute('type')).toBe('application/linkset+json');
298+
});
299+
288300
it('should not call browser-only actions in ngOnDestroy during SSR', () => {
289301
const dispatchSpy = jest.spyOn(store, 'dispatch');
290302

src/app/features/project/overview/project-overview.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ViewOnlyLinkMessageComponent } from '@osf/shared/components/view-only-l
3535
import { Mode } from '@osf/shared/enums/mode.enum';
3636
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
3737
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
38+
import { SignpostingService } from '@osf/shared/services/signposting.service';
3839
import { ToastService } from '@osf/shared/services/toast.service';
3940
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
4041
import {
@@ -104,6 +105,7 @@ export class ProjectOverviewComponent implements OnInit {
104105
private readonly customDialogService = inject(CustomDialogService);
105106
private readonly platformId = inject(PLATFORM_ID);
106107
private readonly isBrowser = isPlatformBrowser(this.platformId);
108+
private readonly signpostingService = inject(SignpostingService);
107109

108110
submissions = select(CollectionsModerationSelectors.getCollectionSubmissions);
109111
collectionProvider = select(CollectionsSelectors.getCollectionProvider);
@@ -193,6 +195,7 @@ export class ProjectOverviewComponent implements OnInit {
193195
this.actions.getBookmarksId();
194196
this.actions.getComponents(projectId);
195197
this.actions.getLinkedProjects(projectId);
198+
this.signpostingService.addSignposting(projectId);
196199
}
197200
}
198201

src/app/features/registry/pages/registry-overview/registry-overview.component.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,7 @@ describe('RegistryOverviewComponent', () => {
629629
}).compileComponents();
630630
fixture = TestBed.createComponent(RegistryOverviewComponent);
631631
component = fixture.componentInstance;
632+
document.head.innerHTML = '';
632633
});
633634

634635
it('should render server-side without errors', () => {
@@ -638,6 +639,16 @@ describe('RegistryOverviewComponent', () => {
638639
expect(component).toBeTruthy();
639640
});
640641

642+
it('should add signposting tags during SSR', () => {
643+
fixture.detectChanges();
644+
const linkTags = Array.from(document.head.querySelectorAll('link[rel="linkset"]'));
645+
expect(linkTags.length).toBe(2);
646+
expect(linkTags[0].getAttribute('href')).toBe('http://localhost:4200/metadata/registry-1/?format=linkset');
647+
expect(linkTags[0].getAttribute('type')).toBe('application/linkset');
648+
expect(linkTags[1].getAttribute('href')).toBe('http://localhost:4200/metadata/registry-1/?format=linkset%2Bjson');
649+
expect(linkTags[1].getAttribute('type')).toBe('application/linkset+json');
650+
});
651+
641652
it('should not access browser-only APIs during SSR', () => {
642653
const platformId = TestBed.inject(PLATFORM_ID);
643654
expect(platformId).toBe('server');

src/app/features/registry/pages/registry-overview/registry-overview.component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
effect,
1616
HostBinding,
1717
inject,
18+
OnInit,
1819
signal,
1920
} from '@angular/core';
2021
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
@@ -32,6 +33,7 @@ import { toCamelCase } from '@osf/shared/helpers/camel-case';
3233
import { SchemaResponse } from '@osf/shared/models/registration/schema-response.model';
3334
import { CustomDialogService } from '@osf/shared/services/custom-dialog.service';
3435
import { LoaderService } from '@osf/shared/services/loader.service';
36+
import { SignpostingService } from '@osf/shared/services/signposting.service';
3537
import { ToastService } from '@osf/shared/services/toast.service';
3638
import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service';
3739
import { GetBookmarksCollectionId } from '@osf/shared/stores/bookmarks';
@@ -75,7 +77,7 @@ import {
7577
styleUrl: './registry-overview.component.scss',
7678
changeDetection: ChangeDetectionStrategy.OnPush,
7779
})
78-
export class RegistryOverviewComponent {
80+
export class RegistryOverviewComponent implements OnInit {
7981
@HostBinding('class') classes = 'flex-1 flex flex-column w-full h-full';
8082
private readonly route = inject(ActivatedRoute);
8183
private readonly router = inject(Router);
@@ -84,6 +86,7 @@ export class RegistryOverviewComponent {
8486
private readonly viewOnlyService = inject(ViewOnlyLinkHelperService);
8587
private readonly customDialogService = inject(CustomDialogService);
8688
private readonly loaderService = inject(LoaderService);
89+
private readonly signpostingService = inject(SignpostingService);
8790

8891
readonly registry = select(RegistrySelectors.getRegistry);
8992
readonly isRegistryLoading = select(RegistrySelectors.isRegistryLoading);
@@ -169,6 +172,10 @@ export class RegistryOverviewComponent {
169172
.subscribe();
170173
}
171174

175+
ngOnInit(): void {
176+
this.signpostingService.addSignposting(this.registryId());
177+
}
178+
172179
openRevision(revisionIndex: number): void {
173180
this.selectedRevisionIndex.set(revisionIndex);
174181
}

0 commit comments

Comments
 (0)