Merge pull request #968 from atmire/w2p-74647_Add-meta-generator-page-header

Meta "generator" tag in page header
This commit is contained in:
Tim Donohue
2021-02-04 10:25:10 -06:00
committed by GitHub
7 changed files with 177 additions and 4 deletions

View File

@@ -160,6 +160,7 @@ import { EndUserAgreementService } from './end-user-agreement/end-user-agreement
import { SiteRegisterGuard } from './data/feature-authorization/feature-authorization-guard/site-register.guard';
import { ShortLivedToken } from './auth/models/short-lived-token.model';
import { UsageReport } from './statistics/models/usage-report.model';
import { RootDataService } from './data/root-data.service';
import { Root } from './data/root.model';
/**
@@ -279,6 +280,7 @@ const PROVIDERS = [
EndUserAgreementCurrentUserGuard,
EndUserAgreementCookieGuard,
EndUserAgreementService,
RootDataService,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,
@@ -353,6 +355,7 @@ export const models =
ShortLivedToken,
Registration,
UsageReport,
Root,
];
@NgModule({

View File

@@ -0,0 +1,38 @@
import { RootDataService } from './root-data.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { Observable } from 'rxjs';
import { RemoteData } from './remote-data';
import { Root } from './root.model';
describe('RootDataService', () => {
let service: RootDataService;
let halService: HALEndpointService;
let rootEndpoint;
beforeEach(() => {
rootEndpoint = 'root-endpoint';
halService = jasmine.createSpyObj('halService', {
getRootHref: rootEndpoint
});
service = new RootDataService(null, null, null, null, halService, null, null, null);
(service as any).dataService = jasmine.createSpyObj('dataService', {
findByHref: createSuccessfulRemoteDataObject$({})
});
});
describe('findRoot', () => {
let result$: Observable<RemoteData<Root>>;
beforeEach(() => {
result$ = service.findRoot();
});
it('should call findByHref using the root endpoint', (done) => {
result$.subscribe(() => {
expect((service as any).dataService.findByHref).toHaveBeenCalledWith(rootEndpoint, true);
done();
});
});
});
});

View File

@@ -0,0 +1,103 @@
import { DataService } from './data.service';
import { Root } from './root.model';
import { Injectable } from '@angular/core';
import { ROOT } from './root.resource-type';
import { dataService } from '../cache/builders/build-decorators';
import { RequestService } from './request.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { Store } from '@ngrx/store';
import { CoreState } from '../core.reducers';
import { ObjectCacheService } from '../cache/object-cache.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { HttpClient } from '@angular/common/http';
import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
import { Observable } from 'rxjs';
import { RemoteData } from './remote-data';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { FindListOptions } from './request.models';
import { PaginatedList } from './paginated-list.model';
/* tslint:disable:max-classes-per-file */
/**
* A private DataService implementation to delegate specific methods to.
*/
class DataServiceImpl extends DataService<Root> {
protected linkPath = '';
protected responseMsToLive = 6 * 60 * 60 * 1000;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<Root>) {
super();
}
}
/**
* A service to retrieve the {@link Root} object from the REST API.
*/
@Injectable()
@dataService(ROOT)
export class RootDataService {
/**
* A private DataService instance to delegate specific methods to.
*/
private dataService: DataServiceImpl;
constructor(
protected requestService: RequestService,
protected rdbService: RemoteDataBuildService,
protected store: Store<CoreState>,
protected objectCache: ObjectCacheService,
protected halService: HALEndpointService,
protected notificationsService: NotificationsService,
protected http: HttpClient,
protected comparator: DefaultChangeAnalyzer<Root>) {
this.dataService = new DataServiceImpl(requestService, rdbService, null, objectCache, halService, notificationsService, http, comparator);
}
/**
* Find the {@link Root} object of the REST API
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
findRoot(reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Root>[]): Observable<RemoteData<Root>> {
return this.dataService.findByHref(this.halService.getRootHref(), reRequestOnStale, ...linksToFollow);
}
/**
* Returns an observable of {@link RemoteData} of an object, based on an href, with a list of
* {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
*/
findByHref(href: string, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Root>[]): Observable<RemoteData<Root>> {
return this.dataService.findByHref(href, reRequestOnStale, ...linksToFollow);
}
/**
* Returns a list of observables of {@link RemoteData} of objects, based on an href, with a list
* of {@link FollowLinkConfig}, to automatically resolve {@link HALLink}s of the object
* @param href The url of object we want to retrieve
* @param findListOptions Find list options object
* @param reRequestOnStale Whether or not the request should automatically be re-requested after
* the response becomes stale
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s
* should be automatically resolved
*/
findAllByHref(href: string, findListOptions: FindListOptions = {}, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Root>[]): Observable<RemoteData<PaginatedList<Root>>> {
return this.dataService.findAllByHref(href, findListOptions, reRequestOnStale, ...linksToFollow);
}
}
/* tslint:enable:max-classes-per-file */

View File

@@ -38,6 +38,12 @@ export class Root implements CacheableObject {
@autoserialize
dspaceServer: string;
/**
* The current DSpace version
*/
@autoserialize
dspaceVersion: string;
/**
* The {@link HALLink}s for the root object
*/

View File

@@ -53,6 +53,8 @@ import { environment } from '../../../environments/environment';
import { storeModuleConfig } from '../../app.reducer';
import { HardRedirectService } from '../services/hard-redirect.service';
import { URLCombiner } from '../url-combiner/url-combiner';
import { RootDataService } from '../data/root-data.service';
import { Root } from '../data/root.model';
/* tslint:disable:max-classes-per-file */
@Component({
@@ -91,6 +93,7 @@ describe('MetadataService', () => {
let remoteDataBuildService: RemoteDataBuildService;
let itemDataService: ItemDataService;
let authService: AuthService;
let rootService: RootDataService;
let location: Location;
let router: Router;
@@ -130,6 +133,11 @@ describe('MetadataService', () => {
}
}
};
rootService = jasmine.createSpyObj('rootService', {
findRoot: createSuccessfulRemoteDataObject$(Object.assign(new Root(), {
dspaceVersion: 'mock-dspace-version'
}))
});
TestBed.configureTestingModule({
imports: [
@@ -168,6 +176,7 @@ describe('MetadataService', () => {
{ provide: DefaultChangeAnalyzer, useValue: {} },
{ provide: BitstreamFormatDataService, useValue: mockBitstreamFormatDataService },
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
{ provide: RootDataService, useValue: rootService },
Meta,
Title,
// tslint:disable-next-line:no-empty
@@ -225,17 +234,18 @@ describe('MetadataService', () => {
expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher');
}));
it('other navigation should title and description', fakeAsync(() => {
it('other navigation should add title, description and Generator', fakeAsync(() => {
spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(ItemMock));
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
tick();
expect(tagStore.size).toBeGreaterThan(0);
router.navigate(['/other']);
tick();
expect(tagStore.size).toEqual(2);
expect(tagStore.size).toEqual(3);
expect(title.getTitle()).toEqual('Dummy Title');
expect(tagStore.get('title')[0].content).toEqual('Dummy Title');
expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!');
expect(tagStore.get('Generator')[0].content).toEqual('mock-dspace-version');
}));
describe('when the item has no bitstreams', () => {

View File

@@ -22,6 +22,7 @@ import { Item } from '../shared/item.model';
import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators';
import { HardRedirectService } from '../services/hard-redirect.service';
import { URLCombiner } from '../url-combiner/url-combiner';
import { RootDataService } from '../data/root-data.service';
@Injectable()
export class MetadataService {
@@ -40,7 +41,8 @@ export class MetadataService {
private dsoNameService: DSONameService,
private bitstreamDataService: BitstreamDataService,
private bitstreamFormatDataService: BitstreamFormatDataService,
private redirectService: HardRedirectService
private redirectService: HardRedirectService,
private rootService: RootDataService
) {
// TODO: determine what open graph meta tags are needed and whether
// the differ per route. potentially add image based on DSpaceObject
@@ -91,6 +93,8 @@ export class MetadataService {
this.addMetaTag('description', translatedDescription);
});
}
this.setGenerator();
}
private initialize(dspaceObject: DSpaceObject): void {
@@ -290,6 +294,15 @@ export class MetadataService {
}
}
/**
* Add <meta name="Generator" ... > to the <head> containing the current DSpace version
*/
private setGenerator(): void {
this.rootService.findRoot().pipe(getFirstSucceededRemoteDataPayload()).subscribe((root) => {
this.addMetaTag('Generator', root.dspaceVersion);
});
}
private hasType(value: string): boolean {
return this.currentObject.value.hasMetadata('dc.type', { value: value, ignoreCase: true });
}

View File

@@ -20,7 +20,7 @@ export class HALEndpointService {
) {
}
protected getRootHref(): string {
public getRootHref(): string {
return new RESTURLCombiner().toString();
}