mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 19:43:04 +00:00
initial metadata service with full coverage
This commit is contained in:
@@ -6,7 +6,7 @@ import { HomePageComponent } from './home-page.component';
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', component: HomePageComponent, pathMatch: 'full' }
|
||||
{ path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'DSpace Angular :: Home' } }
|
||||
])
|
||||
]
|
||||
})
|
||||
|
@@ -6,7 +6,7 @@ import { SearchPageComponent } from './search-page.component';
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{ path: '', component: SearchPageComponent }
|
||||
{ path: '', component: SearchPageComponent, data: { title: 'DSpace Angular :: Search' } }
|
||||
])
|
||||
]
|
||||
})
|
||||
|
@@ -1,13 +1,30 @@
|
||||
import { inheritSerialization } from 'cerialize';
|
||||
import { inheritSerialization, autoserialize } from 'cerialize';
|
||||
|
||||
import { mapsTo } from '../builders/build-decorators';
|
||||
|
||||
import { BitstreamFormat } from '../../shared/bitstream-format.model';
|
||||
import { NormalizedObject } from './normalized-object.model';
|
||||
|
||||
@mapsTo(BitstreamFormat)
|
||||
@inheritSerialization(NormalizedObject)
|
||||
export class NormalizedBitstreamFormat extends NormalizedObject {
|
||||
// TODO: this class was created as a placeholder when we connected to the live rest api
|
||||
|
||||
get uuid(): string {
|
||||
return this.self;
|
||||
}
|
||||
@autoserialize
|
||||
shortDescription: string;
|
||||
|
||||
@autoserialize
|
||||
description: string;
|
||||
|
||||
@autoserialize
|
||||
mimetype: string;
|
||||
|
||||
@autoserialize
|
||||
supportLevel: number;
|
||||
|
||||
@autoserialize
|
||||
internal: boolean;
|
||||
|
||||
@autoserialize
|
||||
extensions: string;
|
||||
|
||||
}
|
||||
|
@@ -21,16 +21,11 @@ export class NormalizedBitstream extends NormalizedDSpaceObject {
|
||||
@autoserialize
|
||||
content: string;
|
||||
|
||||
/**
|
||||
* The mime type of this Bitstream
|
||||
*/
|
||||
@autoserialize
|
||||
mimetype: string;
|
||||
|
||||
/**
|
||||
* The format of this Bitstream
|
||||
*/
|
||||
@autoserialize
|
||||
@relationship(ResourceType.BitstreamFormat, false)
|
||||
format: string;
|
||||
|
||||
/**
|
||||
|
@@ -15,11 +15,9 @@ export class NormalizedObjectFactory {
|
||||
case ResourceType.Bitstream: {
|
||||
return NormalizedBitstream
|
||||
}
|
||||
// commented out for now, bitstreamformats aren't used in the UI yet
|
||||
// and slow things down noticeably
|
||||
// case ResourceType.BitstreamFormat: {
|
||||
// return NormalizedBitstreamFormat
|
||||
// }
|
||||
case ResourceType.BitstreamFormat: {
|
||||
return NormalizedBitstreamFormat
|
||||
}
|
||||
case ResourceType.Bundle: {
|
||||
return NormalizedBundle
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, async, fakeAsync, inject, tick } from '@angu
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
import { Location, CommonModule } from '@angular/common';
|
||||
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { Component, DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { By, Meta, MetaDefinition } from '@angular/platform-browser';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@@ -58,6 +58,10 @@ describe('MetadataService', () => {
|
||||
let router: Router;
|
||||
let fixture: ComponentFixture<TestComponent>;
|
||||
|
||||
let tagStore: Map<string, MetaDefinition[]>;
|
||||
|
||||
let envConfig: GlobalConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
store = new Store<CoreState>(undefined, undefined, undefined);
|
||||
@@ -74,7 +78,7 @@ describe('MetadataService', () => {
|
||||
StoreModule.forRoot({}),
|
||||
RouterTestingModule.withRoutes([
|
||||
{ path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } },
|
||||
{ path: 'other', component: DummyItemComponent, pathMatch: 'full' }
|
||||
{ path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } }
|
||||
])
|
||||
],
|
||||
declarations: [
|
||||
@@ -95,11 +99,14 @@ describe('MetadataService', () => {
|
||||
meta = TestBed.get(Meta);
|
||||
metadataService = TestBed.get(MetadataService);
|
||||
|
||||
envConfig = TestBed.get(GLOBAL_CONFIG);
|
||||
|
||||
router = TestBed.get(Router);
|
||||
location = TestBed.get(Location);
|
||||
|
||||
fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
tagStore = metadataService.getTagStore();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -112,40 +119,45 @@ describe('MetadataService', () => {
|
||||
spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem);
|
||||
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
|
||||
tick();
|
||||
const tagStore: Map<string, MetaDefinition[]> = metadataService.getTagStore();
|
||||
expect(tagStore.get('citation_title').length).toEqual(1);
|
||||
expect(tagStore.get('citation_title')[0].content).toEqual('Test PowerPoint Document');
|
||||
expect(tagStore.get('citation_author')[0].content).toEqual('Doe, Jane');
|
||||
expect(tagStore.get('citation_date')[0].content).toEqual('1650-06-26T19:58:25Z');
|
||||
expect(tagStore.get('citation_issn')[0].content).toEqual('123456789');
|
||||
expect(tagStore.get('citation_language')[0].content).toEqual('en');
|
||||
expect(tagStore.get('citation_keywords')[0].content).toEqual('keyword1; keyword2; keyword3');
|
||||
}));
|
||||
|
||||
it('items page should set meta tags as published Thesis', fakeAsync(() => {
|
||||
spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Thesis')));
|
||||
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
|
||||
tick();
|
||||
const tagStore: Map<string, MetaDefinition[]> = metadataService.getTagStore();
|
||||
expect(tagStore.get('citation_dissertation_name').length).toEqual(1);
|
||||
expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document');
|
||||
expect(tagStore.get('citation_dissertation_institution').length).toEqual(1);
|
||||
expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher');
|
||||
expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([envConfig.ui.baseUrl, router.url].join(''));
|
||||
expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content');
|
||||
}));
|
||||
|
||||
it('items page should set meta tags as published Technical Report', fakeAsync(() => {
|
||||
spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Technical Report')));
|
||||
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
|
||||
tick();
|
||||
const tagStore: Map<string, MetaDefinition[]> = metadataService.getTagStore();
|
||||
expect(tagStore.get('citation_technical_report_institution').length).toEqual(1);
|
||||
expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher');
|
||||
}));
|
||||
|
||||
it('other navigation should clear meta tags', fakeAsync(() => {
|
||||
it('other navigation should title and description', fakeAsync(() => {
|
||||
spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem);
|
||||
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
|
||||
tick();
|
||||
expect(tagStore.size).toBeGreaterThan(0)
|
||||
router.navigate(['/other']);
|
||||
tick();
|
||||
const tagStore: Map<string, MetaDefinition[]> = metadataService.getTagStore();
|
||||
expect(tagStore.size).toEqual(0);
|
||||
expect(tagStore.size).toEqual(2);
|
||||
expect(tagStore.get('title')[0].content).toEqual('Dummy Title');
|
||||
expect(tagStore.get('description')[0].content).toEqual('This is a dummy component for testing!');
|
||||
}));
|
||||
|
||||
const mockType = (mockItem: Item, type: string): Item => {
|
||||
const typedMockItem = Object.assign({}, mockItem) as Item;
|
||||
const typedMockItem = Object.assign(new Item(), mockItem) as Item;
|
||||
for (const metadatum of typedMockItem.metadata) {
|
||||
if (metadatum.key === 'dc.type') {
|
||||
metadatum.value = type;
|
||||
@@ -156,7 +168,7 @@ describe('MetadataService', () => {
|
||||
}
|
||||
|
||||
const mockPublisher = (mockItem: Item): Item => {
|
||||
const publishedMockItem = Object.assign({}, mockItem) as Item;
|
||||
const publishedMockItem = Object.assign(new Item(), mockItem) as Item;
|
||||
publishedMockItem.metadata.push({
|
||||
key: 'dc.publisher',
|
||||
language: 'en_US',
|
||||
|
@@ -40,15 +40,17 @@ export class MetadataService {
|
||||
private meta: Meta,
|
||||
@Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig
|
||||
) {
|
||||
// TODO:
|
||||
this.meta.addTags([
|
||||
{ property: 'og:title', content: 'DSpace Angular Universal' }
|
||||
{ property: 'og:title', content: 'DSpace Angular Universal' },
|
||||
{ property: 'og:description', content: 'The modern front-end for DSpace 7.' }
|
||||
]);
|
||||
this.initialized = false;
|
||||
this.tagStore = new Map<string, MetaDefinition[]>();
|
||||
}
|
||||
|
||||
public listenForRouteChange(): void {
|
||||
const subscription = this.router.events
|
||||
this.router.events
|
||||
.filter((event) => event instanceof NavigationEnd)
|
||||
.map(() => this.router.routerState.root)
|
||||
.map((route: ActivatedRoute) => {
|
||||
@@ -60,6 +62,7 @@ export class MetadataService {
|
||||
}
|
||||
|
||||
private processRouteChange(routeInfo: any): void {
|
||||
this.clearMetaTags();
|
||||
if (routeInfo.params.value.id && routeInfo.data.value.type) {
|
||||
this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type)
|
||||
.first().subscribe((normalizedObject: CacheableObject) => {
|
||||
@@ -70,13 +73,18 @@ export class MetadataService {
|
||||
this.currentObject.next(dspaceObject);
|
||||
});
|
||||
} else {
|
||||
this.clearMetaTags();
|
||||
if (routeInfo.data.value.title) {
|
||||
this.addMetaTag('title', routeInfo.data.value.title);
|
||||
}
|
||||
if (routeInfo.data.value.description) {
|
||||
this.addMetaTag('description', routeInfo.data.value.description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private initialize(dspaceObject: DSpaceObject): void {
|
||||
this.currentObject = new BehaviorSubject<DSpaceObject>(dspaceObject);
|
||||
const subscription = this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => {
|
||||
this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => {
|
||||
this.setMetaTags();
|
||||
});
|
||||
this.initialized = true;
|
||||
@@ -91,7 +99,8 @@ export class MetadataService {
|
||||
|
||||
private setMetaTags(): void {
|
||||
|
||||
this.clearMetaTags();
|
||||
this.setTitleTag();
|
||||
this.setDescriptionTag();
|
||||
|
||||
this.setCitationTitleTag();
|
||||
this.setCitationAuthorTags();
|
||||
@@ -131,6 +140,23 @@ export class MetadataService {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <meta name="title" ... > to the <head>
|
||||
*/
|
||||
private setTitleTag(): void {
|
||||
const value = this.getMetaTagValue('dc.title');
|
||||
this.addMetaTag('title', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <meta name="description" ... > to the <head>
|
||||
*/
|
||||
private setDescriptionTag(): void {
|
||||
// TODO: truncate abstract
|
||||
const value = this.getMetaTagValue('dc.description.abstract');
|
||||
this.addMetaTag('desciption', value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add <meta name="citation_title" ... > to the <head>
|
||||
*/
|
||||
@@ -175,7 +201,7 @@ export class MetadataService {
|
||||
* Add <meta name="citation_language" ... > to the <head>
|
||||
*/
|
||||
private setCitationLanguageTag(): void {
|
||||
const value = this.getMetaTagValue('dc.language.iso');
|
||||
const value = this.getFirstMetaTagValue(['dc.language', 'dc.language.iso']);
|
||||
this.addMetaTag('citation_language', value);
|
||||
}
|
||||
|
||||
@@ -229,12 +255,13 @@ export class MetadataService {
|
||||
const item = this.currentObject.value as Item;
|
||||
// NOTE: Observable resolves many times with same data
|
||||
// taking only two, fist one is empty array
|
||||
const subscription = item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => {
|
||||
item.getFiles().take(2).subscribe((bitstreams: Bitstream[]) => {
|
||||
for (const bitstream of bitstreams) {
|
||||
if (bitstream.mimetype === 'application/pdf') {
|
||||
this.addMetaTag('citation_abstract_html_url', bitstream.content);
|
||||
break;
|
||||
}
|
||||
bitstream.format.payload.take(1).subscribe((format) => {
|
||||
if (format.mimetype === 'application/pdf') {
|
||||
this.addMetaTag('citation_pdf_url', bitstream.content);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
17
src/app/core/shared/bitstream-format.model.ts
Normal file
17
src/app/core/shared/bitstream-format.model.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
|
||||
export class BitstreamFormat extends DSpaceObject {
|
||||
|
||||
shortDescription: string;
|
||||
|
||||
description: string;
|
||||
|
||||
mimetype: string;
|
||||
|
||||
supportLevel: number;
|
||||
|
||||
internal: boolean;
|
||||
|
||||
extensions: string;
|
||||
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import { DSpaceObject } from './dspace-object.model';
|
||||
import { RemoteData } from '../data/remote-data';
|
||||
import { Item } from './item.model';
|
||||
import { BitstreamFormat } from './bitstream-format.model';
|
||||
|
||||
export class Bitstream extends DSpaceObject {
|
||||
|
||||
@@ -9,11 +10,6 @@ export class Bitstream extends DSpaceObject {
|
||||
*/
|
||||
sizeBytes: number;
|
||||
|
||||
/**
|
||||
* The mime type of this Bitstream
|
||||
*/
|
||||
mimetype: string;
|
||||
|
||||
/**
|
||||
* The description of this Bitstream
|
||||
*/
|
||||
@@ -24,6 +20,11 @@ export class Bitstream extends DSpaceObject {
|
||||
*/
|
||||
bundleName: string;
|
||||
|
||||
/**
|
||||
* An array of Bitstream Format of this Bitstream
|
||||
*/
|
||||
format: RemoteData<BitstreamFormat>;
|
||||
|
||||
/**
|
||||
* An array of Items that are direct parents of this Bitstream
|
||||
*/
|
||||
|
@@ -2,6 +2,7 @@ import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
||||
/* tslint:disable:no-shadowed-variable */
|
||||
export const MockItem: Item = Object.assign(new Item(), {
|
||||
handle: '10673/6',
|
||||
lastModified: '2017-04-24T19:44:08.178+0000',
|
||||
@@ -33,7 +34,114 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next([]);
|
||||
observer.next([
|
||||
{
|
||||
sizeBytes: 10201,
|
||||
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713/content',
|
||||
format: {
|
||||
self: {
|
||||
_isScalar: true,
|
||||
value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10',
|
||||
scheduler: null
|
||||
},
|
||||
requestPending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
responsePending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
isSuccessFul: Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
errorMessage: Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
statusCode: Observable.create((observer) => {
|
||||
observer.next(202);
|
||||
}),
|
||||
pageInfo: Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next({
|
||||
shortDescription: 'Microsoft Word XML',
|
||||
description: 'Microsoft Word XML',
|
||||
mimetype: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
supportLevel: 0,
|
||||
internal: false,
|
||||
extensions: null,
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/10'
|
||||
});
|
||||
})
|
||||
},
|
||||
bundleName: 'ORIGINAL',
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/cf9b0c8e-a1eb-4b65-afd0-567366448713',
|
||||
id: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
|
||||
uuid: 'cf9b0c8e-a1eb-4b65-afd0-567366448713',
|
||||
type: 'bitstream',
|
||||
name: 'test_word.docx',
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: null,
|
||||
value: 'test_word.docx'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
sizeBytes: 31302,
|
||||
content: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content',
|
||||
format: {
|
||||
self: {
|
||||
_isScalar: true,
|
||||
value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4',
|
||||
scheduler: null
|
||||
},
|
||||
requestPending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
responsePending: Observable.create((observer) => {
|
||||
observer.next(false);
|
||||
}),
|
||||
isSuccessFul: Observable.create((observer) => {
|
||||
observer.next(true);
|
||||
}),
|
||||
errorMessage: Observable.create((observer) => {
|
||||
observer.next('');
|
||||
}),
|
||||
statusCode: Observable.create((observer) => {
|
||||
observer.next(202);
|
||||
}),
|
||||
pageInfo: Observable.create((observer) => {
|
||||
observer.next({});
|
||||
}),
|
||||
payload: Observable.create((observer) => {
|
||||
observer.next({
|
||||
shortDescription: 'Adobe PDF',
|
||||
description: 'Adobe Portable Document Format',
|
||||
mimetype: 'application/pdf',
|
||||
supportLevel: 0,
|
||||
internal: false,
|
||||
extensions: null,
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreamformats/4'
|
||||
});
|
||||
})
|
||||
},
|
||||
bundleName: 'ORIGINAL',
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28',
|
||||
id: '99b00f3c-1cc6-4689-8158-91965bee6b28',
|
||||
uuid: '99b00f3c-1cc6-4689-8158-91965bee6b28',
|
||||
type: 'bitstream',
|
||||
name: 'test_pdf.pdf',
|
||||
metadata: [
|
||||
{
|
||||
key: 'dc.title',
|
||||
language: null,
|
||||
value: 'test_pdf.pdf'
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
})
|
||||
},
|
||||
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357',
|
||||
@@ -162,3 +270,4 @@ export const MockItem: Item = Object.assign(new Item(), {
|
||||
})
|
||||
}
|
||||
})
|
||||
/* tslint:enable:no-shadowed-variable */
|
||||
|
Reference in New Issue
Block a user