Merged in coar-CST-12235 (pull request #951)

Coar CST-12235

Approved-by: Andrea Bollini
This commit is contained in:
Alisa Ismailati
2023-10-31 09:27:01 +00:00
committed by Andrea Bollini
6 changed files with 166 additions and 8 deletions

View File

@@ -1,14 +1,20 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { getFirstSucceededRemoteData } from '../../shared/operators'; import { getFirstSucceededRemoteData, getRemoteDataPayload } from '../../shared/operators';
import { ConfigurationDataService } from '../../data/configuration-data.service'; import { ConfigurationDataService } from '../../data/configuration-data.service';
import { map, Observable } from 'rxjs'; import { map, Observable } from 'rxjs';
import { DefaultAppConfig } from '../../../../config/default-app-config'; import { DefaultAppConfig } from '../../../../config/default-app-config';
import { ConfigurationProperty } from '../../shared/configuration-property.model';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class NotifyInfoService { export class NotifyInfoService {
/**
* The relation link for the inbox
*/
private _inboxRelationLink = 'http://www.w3.org/ns/ldp#inbox';
constructor( constructor(
private configService: ConfigurationDataService, private configService: ConfigurationDataService,
) {} ) {}
@@ -24,6 +30,20 @@ export class NotifyInfoService {
); );
} }
/**
* Get the url of the local inbox from the REST configuration
* @returns the url of the local inbox
*/
getCoarLdnLocalInboxUrls(): Observable<string[]> {
return this.configService.findByPropertyName('ldn.notify.inbox').pipe(
getFirstSucceededRemoteData(),
getRemoteDataPayload(),
map((response: ConfigurationProperty) => {
return response.values;
})
);
}
getCoarLdnRestApiUrl(): string { getCoarLdnRestApiUrl(): string {
const appConfig = new DefaultAppConfig(); const appConfig = new DefaultAppConfig();
const restConfig = appConfig.rest; const restConfig = appConfig.rest;
@@ -35,4 +55,12 @@ export class NotifyInfoService {
return `${ssl ? 'https' : 'http'}://${host}:${port}${namespace}`; return `${ssl ? 'https' : 'http'}://${host}:${port}${namespace}`;
} }
/**
* Method to get the relation link for the inbox
* @returns the relation link for the inbox
*/
getInboxRelationLink(): string {
return this._inboxRelationLink;
}
} }

View File

@@ -1,22 +1,50 @@
import { Component, OnInit } from '@angular/core'; import { Component, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { map } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Site } from '../core/shared/site.model'; import { Site } from '../core/shared/site.model';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { isPlatformServer } from '@angular/common';
import { ServerResponseService } from '../core/services/server-response.service';
import { NotifyInfoService } from '../core/coar-notify/notify-info/notify-info.service';
import { LinkDefinition, LinkHeadService } from '../core/services/link-head.service';
import { isNotEmpty } from '../shared/empty.util';
@Component({ @Component({
selector: 'ds-home-page', selector: 'ds-home-page',
styleUrls: ['./home-page.component.scss'], styleUrls: ['./home-page.component.scss'],
templateUrl: './home-page.component.html' templateUrl: './home-page.component.html'
}) })
export class HomePageComponent implements OnInit { export class HomePageComponent implements OnInit, OnDestroy {
site$: Observable<Site>; site$: Observable<Site>;
recentSubmissionspageSize: number; recentSubmissionspageSize: number;
/**
* An array of LinkDefinition objects representing inbox links for the home page.
*/
inboxLinks: LinkDefinition[] = [];
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private responseService: ServerResponseService,
private notifyInfoService: NotifyInfoService,
protected linkHeadService: LinkHeadService,
@Inject(PLATFORM_ID) private platformId: string
) { ) {
this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize; this.recentSubmissionspageSize = environment.homePage.recentSubmissions.pageSize;
// Get COAR REST API URLs from REST configuration
// only if COAR configuration is enabled
this.notifyInfoService.isCoarConfigEnabled().pipe(
switchMap((coarLdnEnabled: boolean) => {
if (coarLdnEnabled) {
return this.notifyInfoService.getCoarLdnLocalInboxUrls();
}
})
).subscribe((coarRestApiUrls: string[]) => {
if (coarRestApiUrls.length > 0) {
this.initPageLinks(coarRestApiUrls);
}
});
} }
ngOnInit(): void { ngOnInit(): void {
@@ -24,4 +52,38 @@ export class HomePageComponent implements OnInit {
map((data) => data.site as Site), map((data) => data.site as Site),
); );
} }
/**
* Initializes page links for COAR REST API URLs.
* @param coarRestApiUrls An array of COAR REST API URLs.
*/
private initPageLinks(coarRestApiUrls: string[]): void {
const rel = this.notifyInfoService.getInboxRelationLink();
let links = '';
coarRestApiUrls.forEach((coarRestApiUrl: string) => {
// Add link to head
let tag: LinkDefinition = {
href: coarRestApiUrl,
rel: rel
};
this.inboxLinks.push(tag);
this.linkHeadService.addTag(tag);
links = links + (isNotEmpty(links) ? ', ' : '') + `<${coarRestApiUrl}> ; rel="${rel}"`;
});
if (isPlatformServer(this.platformId)) {
// Add link to response header
this.responseService.setHeader('Link', links);
}
}
/**
* It removes the inbox links from the head of the html.
*/
ngOnDestroy(): void {
this.inboxLinks.forEach((link: LinkDefinition) => {
this.linkHeadService.removeTag(`href='${link.href}'`);
});
}
} }

View File

@@ -23,6 +23,7 @@ import { RemoteData } from '../../core/data/remote-data';
import { ServerResponseService } from '../../core/services/server-response.service'; import { ServerResponseService } from '../../core/services/server-response.service';
import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service';
import { LinkHeadService } from '../../core/services/link-head.service'; import { LinkHeadService } from '../../core/services/link-head.service';
import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service';
const mockItem: Item = Object.assign(new Item(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -122,6 +123,7 @@ describe('FullItemPageComponent', () => {
{ provide: ServerResponseService, useValue: serverResponseService }, { provide: ServerResponseService, useValue: serverResponseService },
{ provide: SignpostingDataService, useValue: signpostingDataService }, { provide: SignpostingDataService, useValue: signpostingDataService },
{ provide: LinkHeadService, useValue: linkHeadService }, { provide: LinkHeadService, useValue: linkHeadService },
{ provide: NotifyInfoService, useValue: {} },
{ provide: PLATFORM_ID, useValue: 'server' } { provide: PLATFORM_ID, useValue: 'server' }
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -19,6 +19,7 @@ import { AuthorizationDataService } from '../../core/data/feature-authorization/
import { ServerResponseService } from '../../core/services/server-response.service'; import { ServerResponseService } from '../../core/services/server-response.service';
import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service';
import { LinkHeadService } from '../../core/services/link-head.service'; import { LinkHeadService } from '../../core/services/link-head.service';
import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service';
/** /**
* This component renders a full item page. * This component renders a full item page.
@@ -55,9 +56,10 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit,
protected responseService: ServerResponseService, protected responseService: ServerResponseService,
protected signpostingDataService: SignpostingDataService, protected signpostingDataService: SignpostingDataService,
protected linkHeadService: LinkHeadService, protected linkHeadService: LinkHeadService,
protected notifyInfoService: NotifyInfoService,
@Inject(PLATFORM_ID) protected platformId: string, @Inject(PLATFORM_ID) protected platformId: string,
) { ) {
super(route, router, items, authService, authorizationService, responseService, signpostingDataService, linkHeadService, platformId); super(route, router, items, authService, authorizationService, responseService, signpostingDataService, linkHeadService,notifyInfoService, platformId);
} }
/*** AoT inheritance fix, will hopefully be resolved in the near future **/ /*** AoT inheritance fix, will hopefully be resolved in the near future **/

View File

@@ -26,6 +26,7 @@ import { ServerResponseService } from '../../core/services/server-response.servi
import { SignpostingDataService } from '../../core/data/signposting-data.service'; import { SignpostingDataService } from '../../core/data/signposting-data.service';
import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service';
import { SignpostingLink } from '../../core/data/signposting-links.model'; import { SignpostingLink } from '../../core/data/signposting-links.model';
import { NotifyInfoService } from '../../core/coar-notify/notify-info/notify-info.service';
const mockItem: Item = Object.assign(new Item(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])), bundles: createSuccessfulRemoteDataObject$(createPaginatedList([])),
@@ -62,6 +63,7 @@ describe('ItemPageComponent', () => {
let serverResponseService: jasmine.SpyObj<ServerResponseService>; let serverResponseService: jasmine.SpyObj<ServerResponseService>;
let signpostingDataService: jasmine.SpyObj<SignpostingDataService>; let signpostingDataService: jasmine.SpyObj<SignpostingDataService>;
let linkHeadService: jasmine.SpyObj<LinkHeadService>; let linkHeadService: jasmine.SpyObj<LinkHeadService>;
let notifyInfoService: jasmine.SpyObj<NotifyInfoService>;
const mockMetadataService = { const mockMetadataService = {
/* eslint-disable no-empty,@typescript-eslint/no-empty-function */ /* eslint-disable no-empty,@typescript-eslint/no-empty-function */
@@ -94,6 +96,12 @@ describe('ItemPageComponent', () => {
removeTag: jasmine.createSpy('removeTag'), removeTag: jasmine.createSpy('removeTag'),
}); });
notifyInfoService = jasmine.createSpyObj('NotifyInfoService', {
getInboxRelationLink: 'http://www.w3.org/ns/ldp#inbox',
isCoarConfigEnabled: observableOf(true),
getCoarLdnLocalInboxUrls: observableOf(['http://test.org', 'http://test2.org']),
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({ imports: [TranslateModule.forRoot({
loader: { loader: {
@@ -112,6 +120,7 @@ describe('ItemPageComponent', () => {
{ provide: ServerResponseService, useValue: serverResponseService }, { provide: ServerResponseService, useValue: serverResponseService },
{ provide: SignpostingDataService, useValue: signpostingDataService }, { provide: SignpostingDataService, useValue: signpostingDataService },
{ provide: LinkHeadService, useValue: linkHeadService }, { provide: LinkHeadService, useValue: linkHeadService },
{ provide: NotifyInfoService, useValue: notifyInfoService},
{ provide: PLATFORM_ID, useValue: 'server' }, { provide: PLATFORM_ID, useValue: 'server' },
], ],

View File

@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit, PLATFORM
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { isPlatformServer } from '@angular/common'; import { isPlatformServer } from '@angular/common';
import { Observable } from 'rxjs'; import { Observable, combineLatest } from 'rxjs';
import { map, take } from 'rxjs/operators'; import { map, switchMap, take } from 'rxjs/operators';
import { ItemDataService } from '../../core/data/item-data.service'; import { ItemDataService } from '../../core/data/item-data.service';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
@@ -21,6 +21,7 @@ import { SignpostingDataService } from '../../core/data/signposting-data.service
import { SignpostingLink } from '../../core/data/signposting-links.model'; import { SignpostingLink } from '../../core/data/signposting-links.model';
import { isNotEmpty } from '../../shared/empty.util'; import { isNotEmpty } from '../../shared/empty.util';
import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service'; import { LinkDefinition, LinkHeadService } from '../../core/services/link-head.service';
import { NotifyInfoService } from 'src/app/core/coar-notify/notify-info/notify-info.service';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -68,6 +69,13 @@ export class ItemPageComponent implements OnInit, OnDestroy {
*/ */
signpostingLinks: SignpostingLink[] = []; signpostingLinks: SignpostingLink[] = [];
/**
* An array of LinkDefinition objects representing inbox links for the item page.
*/
inboxTags: LinkDefinition[] = [];
coarRestApiUrls: string[] = [];
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected router: Router, protected router: Router,
@@ -77,6 +85,7 @@ export class ItemPageComponent implements OnInit, OnDestroy {
protected responseService: ServerResponseService, protected responseService: ServerResponseService,
protected signpostingDataService: SignpostingDataService, protected signpostingDataService: SignpostingDataService,
protected linkHeadService: LinkHeadService, protected linkHeadService: LinkHeadService,
protected notifyInfoService: NotifyInfoService,
@Inject(PLATFORM_ID) protected platformId: string @Inject(PLATFORM_ID) protected platformId: string
) { ) {
this.initPageLinks(); this.initPageLinks();
@@ -106,7 +115,8 @@ export class ItemPageComponent implements OnInit, OnDestroy {
*/ */
private initPageLinks(): void { private initPageLinks(): void {
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
this.signpostingDataService.getLinks(params.id).pipe(take(1)).subscribe((signpostingLinks: SignpostingLink[]) => { combineLatest([this.signpostingDataService.getLinks(params.id).pipe(take(1)), this.getCoarLdnLocalInboxUrls()])
.subscribe(([signpostingLinks, coarRestApiUrls]) => {
let links = ''; let links = '';
this.signpostingLinks = signpostingLinks; this.signpostingLinks = signpostingLinks;
@@ -124,6 +134,11 @@ export class ItemPageComponent implements OnInit, OnDestroy {
this.linkHeadService.addTag(tag); this.linkHeadService.addTag(tag);
}); });
if (coarRestApiUrls.length > 0) {
let inboxLinks = this.initPageInboxLinks(coarRestApiUrls);
links = links + (isNotEmpty(links) ? ', ' : '') + inboxLinks;
}
if (isPlatformServer(this.platformId)) { if (isPlatformServer(this.platformId)) {
this.responseService.setHeader('Link', links); this.responseService.setHeader('Link', links);
} }
@@ -131,9 +146,49 @@ export class ItemPageComponent implements OnInit, OnDestroy {
}); });
} }
/**
* Sets the COAR LDN local inbox URL if COAR configuration is enabled.
* If the COAR LDN local inbox URL is retrieved successfully, initializes the page inbox links.
*/
private getCoarLdnLocalInboxUrls(): Observable<string[]> {
return this.notifyInfoService.isCoarConfigEnabled().pipe(
switchMap((coarLdnEnabled: boolean) => {
if (coarLdnEnabled) {
return this.notifyInfoService.getCoarLdnLocalInboxUrls();
}
})
);
}
/**
* Initializes the page inbox links.
* @param coarRestApiUrls - An array of COAR REST API URLs.
*/
private initPageInboxLinks(coarRestApiUrls: string[]): string {
const rel = this.notifyInfoService.getInboxRelationLink();
let links = '';
coarRestApiUrls.forEach((coarRestApiUrl: string) => {
// Add link to head
let tag: LinkDefinition = {
href: coarRestApiUrl,
rel: rel
};
this.inboxTags.push(tag);
this.linkHeadService.addTag(tag);
links = links + (isNotEmpty(links) ? ', ' : '') + `<${coarRestApiUrl}> ; rel="${rel}"`;
});
return links;
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.signpostingLinks.forEach((link: SignpostingLink) => { this.signpostingLinks.forEach((link: SignpostingLink) => {
this.linkHeadService.removeTag(`href='${link.href}'`); this.linkHeadService.removeTag(`href='${link.href}'`);
}); });
this.inboxTags.forEach((link: LinkDefinition) => {
this.linkHeadService.removeTag(`href='${link.href}'`);
});
} }
} }