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 { SiteRegisterGuard } from './data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||||
import { ShortLivedToken } from './auth/models/short-lived-token.model';
|
import { ShortLivedToken } from './auth/models/short-lived-token.model';
|
||||||
import { UsageReport } from './statistics/models/usage-report.model';
|
import { UsageReport } from './statistics/models/usage-report.model';
|
||||||
|
import { RootDataService } from './data/root-data.service';
|
||||||
import { Root } from './data/root.model';
|
import { Root } from './data/root.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -279,6 +280,7 @@ const PROVIDERS = [
|
|||||||
EndUserAgreementCurrentUserGuard,
|
EndUserAgreementCurrentUserGuard,
|
||||||
EndUserAgreementCookieGuard,
|
EndUserAgreementCookieGuard,
|
||||||
EndUserAgreementService,
|
EndUserAgreementService,
|
||||||
|
RootDataService,
|
||||||
// register AuthInterceptor as HttpInterceptor
|
// register AuthInterceptor as HttpInterceptor
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
@@ -353,6 +355,7 @@ export const models =
|
|||||||
ShortLivedToken,
|
ShortLivedToken,
|
||||||
Registration,
|
Registration,
|
||||||
UsageReport,
|
UsageReport,
|
||||||
|
Root,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@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
|
@autoserialize
|
||||||
dspaceServer: string;
|
dspaceServer: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current DSpace version
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
dspaceVersion: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link HALLink}s for the root object
|
* The {@link HALLink}s for the root object
|
||||||
*/
|
*/
|
||||||
|
@@ -53,6 +53,8 @@ import { environment } from '../../../environments/environment';
|
|||||||
import { storeModuleConfig } from '../../app.reducer';
|
import { storeModuleConfig } from '../../app.reducer';
|
||||||
import { HardRedirectService } from '../services/hard-redirect.service';
|
import { HardRedirectService } from '../services/hard-redirect.service';
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
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 */
|
/* tslint:disable:max-classes-per-file */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -91,6 +93,7 @@ describe('MetadataService', () => {
|
|||||||
let remoteDataBuildService: RemoteDataBuildService;
|
let remoteDataBuildService: RemoteDataBuildService;
|
||||||
let itemDataService: ItemDataService;
|
let itemDataService: ItemDataService;
|
||||||
let authService: AuthService;
|
let authService: AuthService;
|
||||||
|
let rootService: RootDataService;
|
||||||
|
|
||||||
let location: Location;
|
let location: Location;
|
||||||
let router: Router;
|
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({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -168,6 +176,7 @@ describe('MetadataService', () => {
|
|||||||
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
{ provide: DefaultChangeAnalyzer, useValue: {} },
|
||||||
{ provide: BitstreamFormatDataService, useValue: mockBitstreamFormatDataService },
|
{ provide: BitstreamFormatDataService, useValue: mockBitstreamFormatDataService },
|
||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
|
{ provide: RootDataService, useValue: rootService },
|
||||||
Meta,
|
Meta,
|
||||||
Title,
|
Title,
|
||||||
// tslint:disable-next-line:no-empty
|
// tslint:disable-next-line:no-empty
|
||||||
@@ -225,17 +234,18 @@ describe('MetadataService', () => {
|
|||||||
expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher');
|
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));
|
spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(ItemMock));
|
||||||
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
|
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
|
||||||
tick();
|
tick();
|
||||||
expect(tagStore.size).toBeGreaterThan(0);
|
expect(tagStore.size).toBeGreaterThan(0);
|
||||||
router.navigate(['/other']);
|
router.navigate(['/other']);
|
||||||
tick();
|
tick();
|
||||||
expect(tagStore.size).toEqual(2);
|
expect(tagStore.size).toEqual(3);
|
||||||
expect(title.getTitle()).toEqual('Dummy Title');
|
expect(title.getTitle()).toEqual('Dummy Title');
|
||||||
expect(tagStore.get('title')[0].content).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('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', () => {
|
describe('when the item has no bitstreams', () => {
|
||||||
|
@@ -22,6 +22,7 @@ import { Item } from '../shared/item.model';
|
|||||||
import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators';
|
import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators';
|
||||||
import { HardRedirectService } from '../services/hard-redirect.service';
|
import { HardRedirectService } from '../services/hard-redirect.service';
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
|
import { RootDataService } from '../data/root-data.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataService {
|
export class MetadataService {
|
||||||
@@ -40,7 +41,8 @@ export class MetadataService {
|
|||||||
private dsoNameService: DSONameService,
|
private dsoNameService: DSONameService,
|
||||||
private bitstreamDataService: BitstreamDataService,
|
private bitstreamDataService: BitstreamDataService,
|
||||||
private bitstreamFormatDataService: BitstreamFormatDataService,
|
private bitstreamFormatDataService: BitstreamFormatDataService,
|
||||||
private redirectService: HardRedirectService
|
private redirectService: HardRedirectService,
|
||||||
|
private rootService: RootDataService
|
||||||
) {
|
) {
|
||||||
// TODO: determine what open graph meta tags are needed and whether
|
// TODO: determine what open graph meta tags are needed and whether
|
||||||
// the differ per route. potentially add image based on DSpaceObject
|
// the differ per route. potentially add image based on DSpaceObject
|
||||||
@@ -91,6 +93,8 @@ export class MetadataService {
|
|||||||
this.addMetaTag('description', translatedDescription);
|
this.addMetaTag('description', translatedDescription);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initialize(dspaceObject: DSpaceObject): void {
|
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 {
|
private hasType(value: string): boolean {
|
||||||
return this.currentObject.value.hasMetadata('dc.type', { value: value, ignoreCase: true });
|
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();
|
return new RESTURLCombiner().toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user