mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-18 15:33:04 +00:00
Merge pull request #968 from atmire/w2p-74647_Add-meta-generator-page-header
Meta "generator" tag in page header
This commit is contained in:
@@ -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({
|
||||
|
38
src/app/core/data/root-data.service.spec.ts
Normal file
38
src/app/core/data/root-data.service.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
103
src/app/core/data/root-data.service.ts
Normal file
103
src/app/core/data/root-data.service.ts
Normal 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 */
|
@@ -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
|
||||
*/
|
||||
|
@@ -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', () => {
|
||||
|
@@ -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 });
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ export class HALEndpointService {
|
||||
) {
|
||||
}
|
||||
|
||||
protected getRootHref(): string {
|
||||
public getRootHref(): string {
|
||||
return new RESTURLCombiner().toString();
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user