From f4c0df176ef5590dae0a0fea595e7079c55a63c7 Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 12 Oct 2017 19:08:03 -0500 Subject: [PATCH 1/7] metadata service --- package.json | 49 ++- .../collection-page-routing.module.ts | 7 +- .../community-page-routing.module.ts | 7 +- .../+item-page/item-page-routing.module.ts | 5 +- .../search-page.component.spec.ts | 2 +- src/app/app.component.spec.ts | 8 +- src/app/app.component.ts | 9 +- src/app/core/core.module.ts | 43 ++- src/app/core/data/data.service.ts | 7 +- .../core/metadata/metadata.service.spec.ts | 114 ++++++ src/app/core/metadata/metadata.service.ts | 354 ++++++++++++++++++ src/app/footer/footer.component.spec.ts | 2 +- src/app/shared/error/error.component.spec.ts | 6 +- .../shared/loading/loading.component.spec.ts | 6 +- src/app/shared/mocks/mock-action.ts | 6 + src/app/shared/mocks/mock-active-router.ts | 34 ++ .../shared/mocks/mock-host-window-service.ts | 19 + src/app/shared/mocks/mock-item.ts | 164 ++++++++ src/app/shared/mocks/mock-metadata-service.ts | 9 + src/app/shared/mocks/mock-normalized-item.ts | 114 ++++++ src/app/shared/mocks/mock-router.ts | 4 + src/app/shared/mocks/mock-store.ts | 23 ++ src/app/shared/mocks/mock-translate-loader.ts | 8 + .../pagination/pagination.component.spec.ts | 22 +- yarn.lock | 166 ++++---- 25 files changed, 1018 insertions(+), 170 deletions(-) create mode 100644 src/app/core/metadata/metadata.service.spec.ts create mode 100644 src/app/core/metadata/metadata.service.ts create mode 100644 src/app/shared/mocks/mock-action.ts create mode 100644 src/app/shared/mocks/mock-active-router.ts create mode 100644 src/app/shared/mocks/mock-host-window-service.ts create mode 100644 src/app/shared/mocks/mock-item.ts create mode 100644 src/app/shared/mocks/mock-metadata-service.ts create mode 100644 src/app/shared/mocks/mock-normalized-item.ts create mode 100644 src/app/shared/mocks/mock-router.ts create mode 100644 src/app/shared/mocks/mock-store.ts create mode 100644 src/app/shared/mocks/mock-translate-loader.ts diff --git a/package.json b/package.json index 6f761ef47c..9a5e024941 100644 --- a/package.json +++ b/package.json @@ -12,15 +12,14 @@ }, "scripts": { "global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup", - "clean:coverage": "yarn run rimraf coverage", - "clean:dist": "yarn run rimraf dist", - "clean:doc": "yarn run rimraf doc", - "clean:log": "yarn run rimraf *.log*", - "clean:json": "yarn run rimraf *.records.json", - "clean:node": "yarn run rimraf node_modules", + "clean:coverage": "rimraf coverage", + "clean:dist": "rimraf dist", + "clean:doc": "rimraf doc", + "clean:log": "rimraf *.log*", + "clean:json": "rimraf *.records.json", + "clean:node": "rimraf node_modules", "clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json", "clean": "yarn run clean:prod && yarn run clean:node", - "rimraf": "rimraf", "prebuild": "yarn run clean:dist", "prebuild:aot": "yarn run prebuild", "prebuild:prod": "yarn run prebuild", @@ -69,15 +68,15 @@ "coverage": "http-server -c-1 -o -p 9875 ./coverage" }, "dependencies": { - "@angular/animations": "4.4.4", - "@angular/common": "4.4.4", - "@angular/core": "4.4.4", - "@angular/forms": "4.4.4", - "@angular/http": "4.4.4", - "@angular/platform-browser": "4.4.4", - "@angular/platform-browser-dynamic": "4.4.4", - "@angular/platform-server": "4.4.4", - "@angular/router": "4.4.4", + "@angular/animations": "4.4.5", + "@angular/common": "4.4.5", + "@angular/core": "4.4.5", + "@angular/forms": "4.4.5", + "@angular/http": "4.4.5", + "@angular/platform-browser": "4.4.5", + "@angular/platform-browser-dynamic": "4.4.5", + "@angular/platform-server": "4.4.5", + "@angular/router": "4.4.5", "@angularclass/bootloader": "1.0.1", "@angularclass/idle-preload": "1.0.4", "@ng-bootstrap/ng-bootstrap": "1.0.0-beta.5", @@ -89,11 +88,11 @@ "@ngx-translate/http-loader": "2.0.0", "body-parser": "1.18.2", "bootstrap": "v4.0.0-beta", - "cerialize": "0.1.16", + "cerialize": "0.1.18", "compression": "1.7.1", "cookie-parser": "1.4.3", "core-js": "2.5.1", - "express": "4.16.1", + "express": "4.16.2", "express-session": "1.15.6", "font-awesome": "4.7.0", "http-server": "0.10.0", @@ -103,7 +102,7 @@ "methods": "1.1.2", "morgan": "1.9.0", "ngx-pagination": "3.0.1", - "pem": "1.12.2", + "pem": "1.12.3", "reflect-metadata": "0.1.10", "rxjs": "5.4.3", "ts-md5": "1.2.2", @@ -111,10 +110,10 @@ "zone.js": "0.8.18" }, "devDependencies": { - "@angular/compiler": "4.4.4", - "@angular/compiler-cli": "4.4.4", + "@angular/compiler": "4.4.5", + "@angular/compiler-cli": "4.4.5", "@ngrx/store-devtools": "4.0.0", - "@ngtools/webpack": "1.7.2", + "@ngtools/webpack": "1.7.4", "@types/cookie-parser": "1.4.1", "@types/deep-freeze": "0.1.1", "@types/express": "4.0.37", @@ -168,13 +167,13 @@ "postcss-apply": "0.8.0", "postcss-cli": "4.1.1", "postcss-cssnext": "3.0.2", - "postcss-loader": "2.0.6", + "postcss-loader": "2.0.7", "postcss-responsive-type": "1.0.0", "postcss-smart-import": "0.7.5", "protractor": "5.1.2", "protractor-istanbul-plugin": "2.0.0", "raw-loader": "0.5.1", - "resolve-url-loader": "2.1.0", + "resolve-url-loader": "2.1.1", "rimraf": "2.6.2", "rollup": "0.50.0", "rollup-plugin-commonjs": "8.2.1", @@ -191,7 +190,7 @@ "tslint": "5.7.0", "typedoc": "0.9.0", "typescript": "2.5.3", - "webpack": "3.6.0", + "webpack": "3.7.1", "webpack-bundle-analyzer": "2.9.0", "webpack-dev-middleware": "1.12.0", "webpack-dev-server": "2.9.1", diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index c62643acc2..fc7f58b6fb 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -2,12 +2,15 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; +import { NormalizedCollection } from '../core/cache/models/normalized-collection.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } + { path: ':id', component: CollectionPageComponent, pathMatch: 'full', data: { type: NormalizedCollection } } ]) ] }) -export class CollectionPageRoutingModule { } +export class CollectionPageRoutingModule { + +} diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index e090a93087..e761717808 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -2,12 +2,15 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; +import { NormalizedCommunity } from '../core/cache/models/normalized-community.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } + { path: ':id', component: CommunityPageComponent, pathMatch: 'full', data: { type: NormalizedCommunity } } ]) ] }) -export class CommunityPageRoutingModule { } +export class CommunityPageRoutingModule { + +} diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index 9dca4d0f6e..e315b18a9c 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -3,12 +3,13 @@ import { RouterModule } from '@angular/router'; import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; +import { NormalizedItem } from '../core/cache/models/normalized-item.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: ItemPageComponent, pathMatch: 'full' }, - { path: ':id/full', component: FullItemPageComponent } + { path: ':id', component: ItemPageComponent, pathMatch: 'full', data: { type: NormalizedItem } }, + { path: ':id/full', component: FullItemPageComponent, data: { type: NormalizedItem } } ]) ] }) diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts index a3e314db69..0b00021ed6 100644 --- a/src/app/+search-page/search-page.component.spec.ts +++ b/src/app/+search-page/search-page.component.spec.ts @@ -104,7 +104,7 @@ describe('SearchPageComponent', () => { (comp as any).updateSearchResults({}); expect(comp.results as any).toBe(mockResults); - }); + }); }); }); diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index 99cf1068e4..0854c5756e 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -1,4 +1,3 @@ -// ... test imports import { async, ComponentFixture, @@ -23,14 +22,18 @@ import { AppComponent } from './app.component'; import { HostWindowState } from './shared/host-window.reducer'; import { HostWindowResizeAction } from './shared/host-window.actions'; -import { MockTranslateLoader } from './shared/testing/mock-translate-loader'; import { BrowserTransferStateModule } from '../modules/transfer-state/browser-transfer-state.module'; import { BrowserTransferStoreModule } from '../modules/transfer-store/browser-transfer-store.module'; +import { MetadataService } from './core/metadata/metadata.service'; + import { GLOBAL_CONFIG, ENV_CONFIG } from '../config'; import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { MockTranslateLoader } from './shared/mocks/mock-translate-loader'; +import { MockMetadataService } from './shared/mocks/mock-metadata-service'; + let comp: AppComponent; let fixture: ComponentFixture; let de: DebugElement; @@ -57,6 +60,7 @@ describe('App component', () => { providers: [ { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, { provide: NativeWindowService, useValue: new NativeWindowRef() }, + { provide: MetadataService, useValue: new MockMetadataService() }, AppComponent ], schemas: [CUSTOM_ELEMENTS_SCHEMA] diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 9ea58365df..221c1c37d1 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,12 +12,10 @@ import { TranslateService } from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { TransferState } from '../modules/transfer-state/transfer-state'; - import { HostWindowState } from './shared/host-window.reducer'; - import { HostWindowResizeAction } from './shared/host-window.actions'; - import { NativeWindowRef, NativeWindowService } from './shared/window.service'; +import { MetadataService } from './core/metadata/metadata.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../config'; @@ -35,13 +33,16 @@ export class AppComponent implements OnInit { @Inject(NativeWindowService) private _window: NativeWindowRef, private translate: TranslateService, private cache: TransferState, - private store: Store + private store: Store, + private metadata: MetadataService ) { // this language will be used as a fallback when a translation isn't found in the current language translate.setDefaultLang('en'); // the lang to use, if the lang isn't available, it will use the current loader to get them translate.use('en'); + metadata.listenForRouteChange(); + if (config.debug) { console.info(config); } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 9742a6b500..b782f1d4fc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -1,29 +1,35 @@ -import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core'; +import { + NgModule, + Optional, + SkipSelf, + ModuleWithProviders +} from '@angular/core'; import { CommonModule } from '@angular/common'; -import { isNotEmpty } from '../shared/empty.util'; -import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service'; -import { ObjectCacheService } from './cache/object-cache.service'; -import { ResponseCacheService } from './cache/response-cache.service'; -import { CollectionDataService } from './data/collection-data.service'; -import { ItemDataService } from './data/item-data.service'; -import { RequestService } from './data/request.service'; -import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; -import { CommunityDataService } from './data/community-data.service'; -import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; -import { coreEffects } from './core.effects'; -import { EffectsModule } from '@ngrx/effects'; import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { coreEffects } from './core.effects'; import { coreReducers } from './core.reducers'; -import { DSOResponseParsingService } from './data/dso-response-parsing.service'; -import { RootResponseParsingService } from './data/root-response-parsing.service'; + +import { isNotEmpty } from '../shared/empty.util'; import { ApiService } from '../shared/api.service'; - +import { CollectionDataService } from './data/collection-data.service'; +import { CommunityDataService } from './data/community-data.service'; +import { DSOResponseParsingService } from './data/dso-response-parsing.service'; +import { DSpaceRESTv2Service } from './dspace-rest-v2/dspace-rest-v2.service'; import { HostWindowService } from '../shared/host-window.service'; -import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; - +import { ItemDataService } from './data/item-data.service'; +import { MetadataService } from './metadata/metadata.service'; +import { ObjectCacheService } from './cache/object-cache.service'; +import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; +import { RemoteDataBuildService } from './cache/builders/remote-data-build.service'; +import { RequestService } from './data/request.service'; +import { ResponseCacheService } from './cache/response-cache.service'; +import { RootResponseParsingService } from './data/root-response-parsing.service'; import { ServerResponseService } from '../shared/server-response.service'; +import { NativeWindowFactory, NativeWindowService } from '../shared/window.service'; const IMPORTS = [ CommonModule, @@ -47,6 +53,7 @@ const PROVIDERS = [ DSpaceRESTv2Service, HostWindowService, ItemDataService, + MetadataService, ObjectCacheService, PaginationComponentOptions, RemoteDataBuildService, diff --git a/src/app/core/data/data.service.ts b/src/app/core/data/data.service.ts index e48e7a8bb8..d1054d69ef 100644 --- a/src/app/core/data/data.service.ts +++ b/src/app/core/data/data.service.ts @@ -3,7 +3,10 @@ import { CacheableObject } from '../cache/object-cache.reducer'; import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { RemoteData } from './remote-data'; import { - FindAllOptions, FindAllRequest, FindByIDRequest, RestRequest, + FindAllOptions, + FindAllRequest, + FindByIDRequest, + RestRequest, RootEndpointRequest } from './request.models'; import { Store } from '@ngrx/store'; @@ -51,7 +54,7 @@ export abstract class DataService } public isEnabledOnRestApi(): Observable { - return this.getEndpointMap() + return this.getEndpointMap() .map((map: EndpointMap) => isNotEmpty(map[this.linkName])) .startWith(undefined) .distinctUntilChanged(); diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts new file mode 100644 index 0000000000..6e76874604 --- /dev/null +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -0,0 +1,114 @@ +import { ComponentFixture, TestBed, async, fakeAsync, inject, tick } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { Location, CommonModule } from '@angular/common'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { By, Meta } from '@angular/platform-browser'; +import { Router } from '@angular/router'; + +import { Store, StoreModule } from '@ngrx/store'; + +import { Observable } from 'rxjs/Observable'; + +import { MetadataService } from './metadata.service'; + +import { CoreState } from '../core.reducers'; + +import { GlobalConfig } from '../../../config/global-config.interface'; +import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; + +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RequestService } from '../data/request.service'; +import { ResponseCacheService } from '../cache/response-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +import { NormalizedItem } from '../cache/models/normalized-item.model'; + +import { MockRouter } from '../../shared/mocks/mock-router'; +import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; +import { MockItem } from '../../shared/mocks/mock-item'; + +/* tslint:disable:max-classes-per-file */ +@Component({ + template: `` +}) +class TestComponent { + constructor(private metadata: MetadataService) { + metadata.listenForRouteChange(); + } +} + +@Component({ template: '' }) class DummyItemComponent { } +/* tslint:enable:max-classes-per-file */ + +describe('MetadataService', () => { + let metadataService: MetadataService; + + let meta: Meta; + + let store: Store; + + let objectCacheService: ObjectCacheService; + let responseCacheService: ResponseCacheService; + let requestService: RequestService; + let remoteDataBuildService: RemoteDataBuildService; + + let location: Location; + let router: Router; + let fixture: ComponentFixture; + + beforeEach(() => { + + store = new Store(undefined, undefined, undefined); + spyOn(store, 'dispatch'); + + objectCacheService = new ObjectCacheService(store); + responseCacheService = new ResponseCacheService(store); + requestService = new RequestService(objectCacheService, responseCacheService, store); + remoteDataBuildService = new RemoteDataBuildService(objectCacheService, responseCacheService, requestService); + + TestBed.configureTestingModule({ + imports: [ + CommonModule, + StoreModule.forRoot({}), + RouterTestingModule.withRoutes([ + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } } + ]) + ], + declarations: [ + TestComponent, + DummyItemComponent + ], + providers: [ + { provide: ObjectCacheService, useValue: objectCacheService }, + { provide: ResponseCacheService, useValue: responseCacheService }, + { provide: RequestService, useValue: requestService }, + { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, + { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, + Meta, + MetadataService + ], + schemas: [CUSTOM_ELEMENTS_SCHEMA] + }); + meta = TestBed.get(Meta); + metadataService = TestBed.get(MetadataService); + + router = TestBed.get(Router); + location = TestBed.get(Location); + + fixture = TestBed.createComponent(TestComponent); + fixture.detectChanges(); + }); + + beforeEach(() => { + spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { + observer.next(MockNormalizedItem); + })); + spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + }); + + it('upon navigation should call meta tag setters', () => { + router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); + }); + +}); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts new file mode 100644 index 0000000000..9e8c6747f5 --- /dev/null +++ b/src/app/core/metadata/metadata.service.ts @@ -0,0 +1,354 @@ +import 'rxjs/add/operator/first' +import 'rxjs/add/operator/take' + +import { Inject, Injectable } from '@angular/core'; +import { + ActivatedRoute, + Event, + NavigationEnd, + Params, + Router +} from '@angular/router'; +import { Meta, MetaDefinition } from '@angular/platform-browser'; + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Observable } from 'rxjs/Observable'; + +import { Bitstream } from '../shared/bitstream.model'; +import { CacheableObject } from '../cache/object-cache.reducer'; +import { DSpaceObject } from '../shared/dspace-object.model'; +import { Item } from '../shared/item.model'; +import { Metadatum } from '../shared/metadatum.model'; +import { ObjectCacheService } from '../cache/object-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; + +@Injectable() +export class MetadataService { + + private initialized: boolean; + + private tagStore: Map; + + private currentObject: BehaviorSubject; + + constructor( + private router: Router, + private objectCacheService: ObjectCacheService, + private remoteDataBuildService: RemoteDataBuildService, + private meta: Meta, + @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig + ) { + this.meta.addTags([ + { property: 'og:title', content: 'DSpace Angular Universal' } + ]); + this.initialized = false; + this.tagStore = new Map(); + } + + public listenForRouteChange(): void { + const subscription = this.router.events + .filter((event) => event instanceof NavigationEnd) + .map(() => this.router.routerState.root) + .map((route: ActivatedRoute) => { + route = this.getCurrentRoute(route); + return { params: route.params, data: route.data }; + }).subscribe((routeInfo: any) => { + if (routeInfo.params.value.id && routeInfo.data.value.type) { + this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) + .first().subscribe((normalizedObject: CacheableObject) => { + const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } else { + this.clearMetaTags(); + } + }); + } + + private initialize(dspaceObject: DSpaceObject): void { + this.currentObject = new BehaviorSubject(dspaceObject); + const subscription = this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { + this.setMetaTags(); + }); + this.initialized = true; + } + + private getCurrentRoute(route: ActivatedRoute): ActivatedRoute { + while (route.firstChild) { + route = route.firstChild; + } + return route; + } + + private setMetaTags(): void { + + this.clearMetaTags(); + + this.setCitationTitleTag(); + this.setCitationAuthorTags(); + this.setCitationDateTag(); + this.setCitationISSNTag(); + this.setCitationISBNTag(); + + this.setCitationLanguageTag(); + this.setCitationKeywordsTag(); + + this.setCitationAbstractUrlTag(); + this.setCitationPdfUrlTag(); + + if (this.isDissertation()) { + this.setCitationDissertationNameTag(); + this.setCitationDissertationInstitutionTag(); + } + + if (this.isTechReport()) { + this.setCitationTechReportInstitutionTag(); + } + + // this.setCitationJournalTitleTag(); + // this.setCitationVolumeTag(); + // this.setCitationIssueTag(); + // this.setCitationFirstPageTag(); + // this.setCitationLastPageTag(); + // this.setCitationDOITag(); + // this.setCitationPMIDTag(); + + // this.setCitationFullTextTag(); + + // this.setCitationConferenceTag(); + + // this.setCitationPatentCountryTag(); + // this.setCitationPatentNumberTag(); + + } + + /** + * Add to the + */ + private setCitationTitleTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('citation_title', value); + } + + /** + * Add to the + */ + private setCitationAuthorTags(): void { + const values: string[] = this.getMetaTagValues(['dc.author', 'dc.contributor.author', 'dc.creator']); + this.addMetaTags('citation_author', values); + } + + /** + * Add to the + */ + private setCitationDateTag(): void { + const value = this.getFirstMetaTagValue(['dc.date.copyright', 'dc.date.issued', 'dc.date.available', 'dc.date.accessioned']); + this.addMetaTag('citation_date', value); + } + + /** + * Add to the + */ + private setCitationISSNTag(): void { + const value = this.getMetaTagValue('dc.identifier.issn'); + this.addMetaTag('citation_issn', value); + } + + /** + * Add to the + */ + private setCitationISBNTag(): void { + const value = this.getMetaTagValue('dc.identifier.isbn'); + this.addMetaTag('citation_isbn', value); + } + + /** + * Add to the + */ + private setCitationLanguageTag(): void { + const value = this.getMetaTagValue('dc.language.iso'); + this.addMetaTag('citation_language', value); + } + + /** + * Add to the + */ + private setCitationDissertationNameTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('citation_dissertation_name', value); + } + + /** + * Add to the + */ + private setCitationDissertationInstitutionTag(): void { + const value = this.getMetaTagValue('dc.publisher'); + this.addMetaTag('citation_dissertation_institution', value); + } + + /** + * Add to the + */ + private setCitationTechReportInstitutionTag(): void { + const value = this.getMetaTagValue('dc.publisher'); + this.addMetaTag('citation_technical_report_institution', value); + } + + /** + * Add to the + */ + private setCitationKeywordsTag(): void { + const value = this.getMetaTagValuesAndCombine('dc.subject'); + this.addMetaTag('citation_keywords', value); + } + + /** + * Add to the + */ + private setCitationAbstractUrlTag(): void { + if (this.currentObject.value instanceof Item) { + const value = [this.envConfig.ui.baseUrl, this.router.url].join(''); + this.addMetaTag('citation_abstract_html_url', value); + } + } + + /** + * Add to the + */ + private setCitationPdfUrlTag(): void { + if (this.currentObject.value instanceof Item) { + 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[]) => { + for (const bitstream of bitstreams) { + if (bitstream.mimetype === 'application/pdf') { + this.addMetaTag('citation_abstract_html_url', bitstream.content); + break; + } + } + }); + } + } + + /** + * Returns true if this._item is a dissertation + * + * @returns {boolean} + * true if this._item has a dc.type equal to 'Thesis' + */ + private isDissertation(): boolean { + let isDissertation = false; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === 'dc.type') { + isDissertation = metadatum.value === 'Thesis'; + break; + } + } + return isDissertation; + } + + /** + * Returns true if this._item is a technical report + * + * @returns {boolean} + * true if this._item has a dc.type equal to 'Technical Report' + */ + private isTechReport(): boolean { + let isTechReport = false; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === 'dc.type') { + isTechReport = metadatum.value === 'Technical Report'; + break; + } + } + return isTechReport; + } + + private getMetaTagValue(key: string): string { + let value: string; + for (const metadatum of this.currentObject.value.metadata) { + if (metadatum.key === key) { + value = metadatum.value; + } + } + return value; + } + + private getFirstMetaTagValue(keys: string[]): string { + let value: string; + for (const metadatum of this.currentObject.value.metadata) { + for (const key of keys) { + if (key === metadatum.key) { + value = metadatum.value; + break; + } + } + if (value !== undefined) { + break; + } + } + return value; + } + + private getMetaTagValuesAndCombine(key: string): string { + return this.getMetaTagValues([key]).join('; '); + } + + private getMetaTagValues(keys: string[]): string[] { + const values: string[] = []; + for (const metadatum of this.currentObject.value.metadata) { + for (const key of keys) { + if (key === metadatum.key) { + values.push(metadatum.value); + } + } + } + return values; + } + + private addMetaTag(property: string, content: string): void { + if (content) { + const tag = { property, content } as MetaDefinition; + this.meta.addTag(tag); + this.storeTag(property, tag); + } + } + + private addMetaTags(property: string, content: string[]): void { + for (const value of content) { + this.addMetaTag(property, value); + } + } + + private storeTag(key: string, tag: MetaDefinition): void { + const tags: MetaDefinition[] = this.getTags(key); + tags.push(tag); + this.setTags(key, tags); + } + + private getTags(key: string): MetaDefinition[] { + let tags: MetaDefinition[] = this.tagStore.get(key); + if (tags === undefined) { + tags = []; + } + return tags; + } + + private setTags(key: string, tags: MetaDefinition[]): void { + this.tagStore.set(key, tags); + } + + private clearMetaTags() { + this.tagStore.forEach((tags: MetaDefinition[], property: string) => { + this.meta.removeTag("property='" + property + "'"); + }); + this.tagStore.clear(); + } + +} diff --git a/src/app/footer/footer.component.spec.ts b/src/app/footer/footer.component.spec.ts index 6d56726764..dde432e1ef 100644 --- a/src/app/footer/footer.component.spec.ts +++ b/src/app/footer/footer.component.spec.ts @@ -21,7 +21,7 @@ import { Store, StoreModule } from '@ngrx/store'; // Load the implementations that should be tested import { FooterComponent } from './footer.component'; -import { MockTranslateLoader } from '../shared/testing/mock-translate-loader'; +import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader'; let comp: FooterComponent; let fixture: ComponentFixture; diff --git a/src/app/shared/error/error.component.spec.ts b/src/app/shared/error/error.component.spec.ts index a0226f7f86..7335f93aed 100644 --- a/src/app/shared/error/error.component.spec.ts +++ b/src/app/shared/error/error.component.spec.ts @@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core'; import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; import { ErrorComponent } from './error.component'; @@ -25,8 +25,8 @@ describe('ErrorComponent (inline template)', () => { } }), ], - declarations: [ ErrorComponent ], // declare the test component - providers: [ TranslateService ] + declarations: [ErrorComponent], // declare the test component + providers: [TranslateService] }).compileComponents(); // compile template and css })); diff --git a/src/app/shared/loading/loading.component.spec.ts b/src/app/shared/loading/loading.component.spec.ts index 0b758b8218..aca9673282 100644 --- a/src/app/shared/loading/loading.component.spec.ts +++ b/src/app/shared/loading/loading.component.spec.ts @@ -4,7 +4,7 @@ import { DebugElement } from '@angular/core'; import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; import { LoadingComponent } from './loading.component'; @@ -25,8 +25,8 @@ describe('LoadingComponent (inline template)', () => { } }), ], - declarations: [ LoadingComponent ], // declare the test component - providers: [ TranslateService ] + declarations: [LoadingComponent], // declare the test component + providers: [TranslateService] }).compileComponents(); // compile template and css })); diff --git a/src/app/shared/mocks/mock-action.ts b/src/app/shared/mocks/mock-action.ts new file mode 100644 index 0000000000..0f619c8aff --- /dev/null +++ b/src/app/shared/mocks/mock-action.ts @@ -0,0 +1,6 @@ +import { Action } from '@ngrx/store'; + +export class MockAction implements Action { + type = null; + payload: {}; +} diff --git a/src/app/shared/mocks/mock-active-router.ts b/src/app/shared/mocks/mock-active-router.ts new file mode 100644 index 0000000000..391b9c3426 --- /dev/null +++ b/src/app/shared/mocks/mock-active-router.ts @@ -0,0 +1,34 @@ +import { Params } from '@angular/router'; + +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export class MockActivatedRoute { + + private _testParams?: any; + + // ActivatedRoute.params is Observable + private subject?: BehaviorSubject = new BehaviorSubject(this.testParams); + + params = this.subject.asObservable(); + queryParams = this.subject.asObservable(); + + constructor(params?: Params) { + if (params) { + this.testParams = params; + } else { + this.testParams = {}; + } + } + + // Test parameters + get testParams() { return this._testParams; } + set testParams(params: {}) { + this._testParams = params; + this.subject.next(params); + } + + // ActivatedRoute.snapshot.params + get snapshot() { + return { params: this.testParams }; + } +} diff --git a/src/app/shared/mocks/mock-host-window-service.ts b/src/app/shared/mocks/mock-host-window-service.ts new file mode 100644 index 0000000000..104e712682 --- /dev/null +++ b/src/app/shared/mocks/mock-host-window-service.ts @@ -0,0 +1,19 @@ +import { Observable } from 'rxjs/Observable'; + +// declare a stub service +export class MockHostWindowService { + + private width: number; + + constructor(width) { + this.setWidth(width); + } + + setWidth(width) { + this.width = width; + } + + isXs(): Observable { + return Observable.of(this.width < 576); + } +} diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts new file mode 100644 index 0000000000..43ad178485 --- /dev/null +++ b/src/app/shared/mocks/mock-item.ts @@ -0,0 +1,164 @@ +import { Observable } from 'rxjs/Observable'; + +import { Item } from '../../core/shared/item.model'; + +export const MockItem: Item = Object.assign(new Item(), { + handle: '10673/6', + lastModified: '2017-04-24T19:44:08.178+0000', + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + bitstreams: { + self: { + _isScalar: true, + value: '1507836003548', + 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([]); + }) + }, + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', + id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + type: 'item', + name: 'Test PowerPoint Document', + metadata: [ + { + key: 'dc.creator', + language: 'en_US', + value: 'Doe, Jane' + }, + { + key: 'dc.date.accessioned', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.available', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.issued', + language: null, + value: '1650-06-26' + }, + { + key: 'dc.identifier.issn', + language: 'en_US', + value: '123456789' + }, + { + key: 'dc.identifier.uri', + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + }, + { + key: 'dc.description.abstract', + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + }, + { + key: 'dc.language', + language: 'en_US', + value: 'en' + }, + { + key: 'dc.rights', + language: 'en_US', + value: '© Jane Doe' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword1' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword2' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword3' + }, + { + key: 'dc.title', + language: 'en_US', + value: 'Test PowerPoint Document' + }, + { + key: 'dc.type', + language: 'en_US', + value: 'text' + } + ], + owningCollection: { + self: { + _isScalar: true, + value: 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb', + 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([]); + }) + } +}) diff --git a/src/app/shared/mocks/mock-metadata-service.ts b/src/app/shared/mocks/mock-metadata-service.ts new file mode 100644 index 0000000000..8456e92b92 --- /dev/null +++ b/src/app/shared/mocks/mock-metadata-service.ts @@ -0,0 +1,9 @@ + +export class MockMetadataService { + + // tslint:disable-next-line:no-empty + public listenForRouteChange(): void { + + } + +} diff --git a/src/app/shared/mocks/mock-normalized-item.ts b/src/app/shared/mocks/mock-normalized-item.ts new file mode 100644 index 0000000000..d8716b4bf2 --- /dev/null +++ b/src/app/shared/mocks/mock-normalized-item.ts @@ -0,0 +1,114 @@ +import { NormalizedItem } from '../../core/cache/models/normalized-item.model'; + +export const MockNormalizedItem: NormalizedItem = Object.assign(new NormalizedItem(), { + handle: '10673/6', + lastModified: new Date('2017-04-24T19:44:08.178+0000'), + isArchived: true, + isDiscoverable: true, + isWithdrawn: false, + bitstreams: [ + 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/a7cd7d97-4e40-41db-80a8-fac908b63bb8', + 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/9ff3df0d-1709-472f-8c00-d3e8db2153c8', + 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/d660a4b8-e7cc-45cd-b026-35f98c5bd3ba' + ], + self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', + id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', + type: 'item', + name: 'Test PowerPoint Document', + metadata: [ + { + key: 'dc.creator', + language: 'en_US', + value: 'Doe, Jane L' + }, + { + key: 'dc.date.accessioned', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.available', + language: null, + value: '1650-06-26T19:58:25Z' + }, + { + key: 'dc.date.issued', + language: null, + value: '1650-06-26' + }, + { + key: 'dc.identifier.issn', + language: 'en_US', + value: '123456789' + }, + { + key: 'dc.identifier.uri', + language: null, + value: 'http://dspace7.4science.it/xmlui/handle/10673/6' + }, + { + key: 'dc.description.abstract', + language: 'en_US', + value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' + }, + { + key: 'dc.description.provenance', + language: 'en', + value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' + }, + { + key: 'dc.language', + language: 'en_US', + value: 'en' + }, + { + key: 'dc.rights', + language: 'en_US', + value: '© Jane Doe' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword1' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword2' + }, + { + key: 'dc.subject', + language: 'en_US', + value: 'keyword3' + }, + { + key: 'dc.title', + language: 'en_US', + value: 'Test PowerPoint Document' + }, + { + key: 'dc.type', + language: 'en_US', + value: 'text' + } + ], + owningCollection: [ + 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb' + ] +}) diff --git a/src/app/shared/mocks/mock-router.ts b/src/app/shared/mocks/mock-router.ts new file mode 100644 index 0000000000..054c63d4c0 --- /dev/null +++ b/src/app/shared/mocks/mock-router.ts @@ -0,0 +1,4 @@ +export class MockRouter { + // noinspection TypeScriptUnresolvedFunction + navigate = jasmine.createSpy('navigate'); +} diff --git a/src/app/shared/mocks/mock-store.ts b/src/app/shared/mocks/mock-store.ts new file mode 100644 index 0000000000..c619b5aa77 --- /dev/null +++ b/src/app/shared/mocks/mock-store.ts @@ -0,0 +1,23 @@ +import { Action } from '@ngrx/store'; +import { Observable } from 'rxjs/Observable'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; + +export class MockStore extends BehaviorSubject { + + constructor(private _initialState: T) { + super(_initialState); + } + + dispatch = (action: Action): void => { + console.info(); + } + + select = (pathOrMapFn: any): Observable => { + return Observable.of(this.getValue()); + } + + nextState(_newState: T) { + this.next(_newState); + } + +} diff --git a/src/app/shared/mocks/mock-translate-loader.ts b/src/app/shared/mocks/mock-translate-loader.ts new file mode 100644 index 0000000000..6e22066f8a --- /dev/null +++ b/src/app/shared/mocks/mock-translate-loader.ts @@ -0,0 +1,8 @@ +import { TranslateLoader } from '@ngx-translate/core'; +import { Observable } from 'rxjs/Observable'; + +export class MockTranslateLoader implements TranslateLoader { + getTranslation(lang: string): Observable { + return Observable.of({}); + } +} diff --git a/src/app/shared/pagination/pagination.component.spec.ts b/src/app/shared/pagination/pagination.component.spec.ts index 92d7ba693f..a4b9e5fcea 100644 --- a/src/app/shared/pagination/pagination.component.spec.ts +++ b/src/app/shared/pagination/pagination.component.spec.ts @@ -34,10 +34,10 @@ import Spy = jasmine.Spy; import { PaginationComponent } from './pagination.component'; import { PaginationComponentOptions } from './pagination-component-options.model'; -import { MockTranslateLoader } from '../testing/mock-translate-loader'; -import { HostWindowServiceStub } from '../testing/host-window-service-stub'; -import { ActivatedRouteStub } from '../testing/active-router-stub'; -import { RouterStub } from '../testing/router-stub'; +import { MockTranslateLoader } from '../mocks/mock-translate-loader'; +import { MockHostWindowService } from '../mocks/mock-host-window-service'; +import { MockActivatedRoute } from '../mocks/mock-active-router'; +import { MockRouter } from '../mocks/mock-router'; import { HostWindowService } from '../host-window.service'; import { EnumKeysPipe } from '../utils/enum-keys-pipe'; @@ -45,7 +45,7 @@ import { SortOptions } from '../../core/cache/models/sort-options.model'; import { GLOBAL_CONFIG, ENV_CONFIG } from '../../../config'; -function createTestComponent(html: string, type: { new (...args: any[]): T }): ComponentFixture { +function createTestComponent(html: string, type: { new(...args: any[]): T }): ComponentFixture { TestBed.overrideComponent(type, { set: { template: html } }); @@ -123,19 +123,19 @@ describe('Pagination component', () => { let testFixture: ComponentFixture; let de: DebugElement; let html; - let hostWindowServiceStub: HostWindowServiceStub; + let hostWindowServiceStub: MockHostWindowService; - let activatedRouteStub: ActivatedRouteStub; - let routerStub: RouterStub; + let activatedRouteStub: MockActivatedRoute; + let routerStub: MockRouter; // Define initial state and test state const _initialState = { width: 1600, height: 770 }; // async beforeEach beforeEach(async(() => { - activatedRouteStub = new ActivatedRouteStub(); - routerStub = new RouterStub(); - hostWindowServiceStub = new HostWindowServiceStub(_initialState.width); + activatedRouteStub = new MockActivatedRoute(); + routerStub = new MockRouter(); + hostWindowServiceStub = new MockHostWindowService(_initialState.width); TestBed.configureTestingModule({ imports: [ diff --git a/yarn.lock b/yarn.lock index 109d1eb310..39e4bf575d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,79 +2,79 @@ # yarn lockfile v1 -"@angular/animations@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.4.tgz#a2f9353604347abe15df98292058842f52f08bc2" +"@angular/animations@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.4.5.tgz#5a5a551d757e5a5560098f6f8535c102d93954d7" dependencies: tslib "^1.7.1" -"@angular/common@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.4.tgz#ae0a818aaa0c6a3f0901e7b80bd94e1c22eb9365" +"@angular/common@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.4.5.tgz#bd5179dc922adbf4c3ea6dfb19e73cb849ffdc37" dependencies: tslib "^1.7.1" -"@angular/compiler-cli@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.4.tgz#063080a497d9175396825050222c717da184f6cf" +"@angular/compiler-cli@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.4.5.tgz#61fa0336acd1a208c5f1c5c6d4df679e99953248" dependencies: - "@angular/tsc-wrapped" "4.4.4" + "@angular/tsc-wrapped" "4.4.5" minimist "^1.2.0" reflect-metadata "^0.1.2" -"@angular/compiler@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.4.tgz#326eb0029d9a3541aaca124def9adc51c36f2b41" +"@angular/compiler@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.4.5.tgz#8721a5910f2bb52f09e2d404cad264f35ede5902" dependencies: tslib "^1.7.1" -"@angular/core@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.4.tgz#bd37ecf54158f97489996c9386bd222f80a32f5c" +"@angular/core@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.4.5.tgz#54acbcbda11719f883c786a906974abeb132f1a0" dependencies: tslib "^1.7.1" -"@angular/forms@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.4.tgz#4db3790509b6b10f1db8a7c1b7f52187cf64cfd4" +"@angular/forms@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.4.5.tgz#e9552086232aab2ce1d08ef198b62204ea13c43b" dependencies: tslib "^1.7.1" -"@angular/http@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.4.tgz#667faf616bb624168eafae6ee92e5eba23a9d1f2" +"@angular/http@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.4.5.tgz#2c735ed842401fc2356419268e288dcf2396e84f" dependencies: tslib "^1.7.1" -"@angular/platform-browser-dynamic@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.4.tgz#c3c9eb854a528556a07054127932e527fa932e14" +"@angular/platform-browser-dynamic@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.4.5.tgz#774dbdc1d90f775dbf1e319f6ed42b260623b61f" dependencies: tslib "^1.7.1" -"@angular/platform-browser@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.4.tgz#a3898e2e7ba9d84ffa0d47144c6971179c75aee6" +"@angular/platform-browser@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.4.5.tgz#74eb91c0b758126f26d53ee56c7cf4668bd9cac5" dependencies: tslib "^1.7.1" -"@angular/platform-server@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.4.4.tgz#73ee41fa1cec8628fcc03174727b27cb0031b22a" +"@angular/platform-server@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.4.5.tgz#76f23b2c384ed7395dc1793cf85978883ba2cb50" dependencies: parse5 "^3.0.1" tslib "^1.7.1" xhr2 "^0.1.4" -"@angular/router@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.4.tgz#7be391096e843cb3e04f9f05d1d65a88df9bc7cf" +"@angular/router@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.4.5.tgz#f73130cf487d9a32cc1988afda59665f44a28a89" dependencies: tslib "^1.7.1" -"@angular/tsc-wrapped@4.4.4": - version "4.4.4" - resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.4.tgz#9841821e55616b826ca160250fe85e15fc74ffc3" +"@angular/tsc-wrapped@4.4.5": + version "4.4.5" + resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.4.5.tgz#30a0cbb43a663aa75dca984894be4813778ddc9c" dependencies: tsickle "^0.21.0" @@ -106,9 +106,9 @@ version "4.0.3" resolved "https://registry.yarnpkg.com/@ngrx/store/-/store-4.0.3.tgz#36abacdfa19bfb8506e40de80bae06050a1e15e9" -"@ngtools/webpack@1.7.2": - version "1.7.2" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.2.tgz#3fc4de01786dcc2f50d8cbaaa117311e56799977" +"@ngtools/webpack@1.7.4": + version "1.7.4" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.7.4.tgz#5015c47ebd339045dd89a1bef0497f4524d2c8ed" dependencies: enhanced-resolve "^3.1.0" loader-utils "^1.0.2" @@ -192,7 +192,11 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/node@*", "@types/node@8.0.26": +"@types/node@*": + version "8.0.34" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.34.tgz#55f801fa2ddb2a40dd6dfc15ecfe1dde9c129fe9" + +"@types/node@8.0.26": version "8.0.26" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.26.tgz#4d58be925306fd22b1141085535a0268b8beb189" @@ -568,7 +572,7 @@ atob@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" -autoprefixer@7.1.5: +autoprefixer@7.1.5, autoprefixer@^7.1.1: version "7.1.5" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.5.tgz#d65d14b83c7cd1dd7bc801daa00557addf5a06b2" dependencies: @@ -590,17 +594,6 @@ autoprefixer@^6.3.1: postcss "^5.2.16" postcss-value-parser "^3.2.3" -autoprefixer@^7.1.1: - version "7.1.4" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.4.tgz#960847dbaa4016bc8e8e52ec891cbf8f1257a748" - dependencies: - browserslist "^2.4.0" - caniuse-lite "^1.0.30000726" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^6.0.11" - postcss-value-parser "^3.2.3" - awesome-typescript-loader@3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/awesome-typescript-loader/-/awesome-typescript-loader-3.2.3.tgz#aa2119b7c808a031e2b28945b031450a8975367f" @@ -793,11 +786,7 @@ blocking-proxy@0.0.5: dependencies: minimist "^1.2.0" -bluebird@^3.3.0, bluebird@^3.4.7: - version "3.5.0" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" - -bluebird@^3.5.1: +bluebird@^3.3.0, bluebird@^3.4.7, bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -975,14 +964,7 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -browserslist@^2.0.0, browserslist@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.4.0.tgz#693ee93d01e66468a6348da5498e011f578f87f8" - dependencies: - caniuse-lite "^1.0.30000718" - electron-to-chromium "^1.3.18" - -browserslist@^2.5.0: +browserslist@^2.0.0, browserslist@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.5.1.tgz#68e4bc536bbcc6086d62843a2ffccea8396821c6" dependencies: @@ -1099,11 +1081,7 @@ caniuse-lite@1.0.30000697: version "1.0.30000697" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000697.tgz#125fb00604b63fbb188db96a667ce2922dcd6cdd" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000718, caniuse-lite@^1.0.30000726: - version "1.0.30000740" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000740.tgz#f2c4c04d6564eb812e61006841700ad557f6f973" - -caniuse-lite@^1.0.30000744: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000744: version "1.0.30000745" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000745.tgz#20d6fede1157a4935133502946fc7e0e6b880da5" @@ -1126,11 +1104,11 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -cerialize@0.1.16: - version "0.1.16" - resolved "https://registry.yarnpkg.com/cerialize/-/cerialize-0.1.16.tgz#88678bffbd7817a90aa5b58a8c66d6bdca3035be" +cerialize@0.1.18: + version "0.1.18" + resolved "https://registry.yarnpkg.com/cerialize/-/cerialize-0.1.18.tgz#d0f4f1b61cec7e4ed16a3eda0cac2bc99787414d" dependencies: - typescript "^2.1.6" + typescript "^2.5.0" chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" @@ -2042,7 +2020,7 @@ ejs@^2.5.6: version "2.5.7" resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.5.7.tgz#cc872c168880ae3c7189762fd5ffc00896c9518a" -electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.18, electron-to-chromium@^1.3.24: +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.24: version "1.3.24" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.24.tgz#9b7b88bb05ceb9fa016a177833cc2dde388f21b6" @@ -2414,9 +2392,9 @@ express-session@1.15.6: uid-safe "~2.1.5" utils-merge "1.0.1" -express@4.16.1, express@^4.13.3, express@^4.15.2: - version "4.16.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.1.tgz#6b33b560183c9b253b7b62144df33a4654ac9ed0" +express@4.16.2, express@^4.13.3, express@^4.15.2: + version "4.16.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" dependencies: accepts "~1.3.4" array-flatten "1.1.1" @@ -5129,9 +5107,9 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pem@1.12.2: - version "1.12.2" - resolved "https://registry.yarnpkg.com/pem/-/pem-1.12.2.tgz#d2f1744c9ff8144f795f96d0c54a4b2be6f43b0c" +pem@1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/pem/-/pem-1.12.3.tgz#b1fb5c8b79da8d18146c27fee79b0d4ddf9905b3" dependencies: md5 "^2.2.1" os-tmpdir "^1.0.1" @@ -5484,12 +5462,12 @@ postcss-load-plugins@^2.3.0: cosmiconfig "^2.1.1" object-assign "^4.1.0" -postcss-loader@2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.6.tgz#8c7e0055a3df1889abc6bad52dd45b2f41bbc6fc" +postcss-loader@2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-2.0.7.tgz#4d2da1489cee0a14f72c0d9440c9ee7eded34345" dependencies: loader-utils "^1.1.0" - postcss "^6.0.2" + postcss "^6.0.0" postcss-load-config "^1.2.0" schema-utils "^0.3.0" @@ -5755,7 +5733,7 @@ postcss-zindex@^2.0.1: postcss "^5.0.4" uniqs "^2.0.0" -postcss@6.0.13, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.2, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: +postcss@6.0.13, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.13, postcss@^6.0.3, postcss@^6.0.5, postcss@^6.0.6, postcss@^6.0.8: version "6.0.13" resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.13.tgz#b9ecab4ee00c89db3ec931145bd9590bbf3f125f" dependencies: @@ -6320,9 +6298,9 @@ requires-port@1.0.x, requires-port@1.x.x: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" -resolve-url-loader@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.0.tgz#27c95cc16a4353923fdbdc2dbaf5eef22232c477" +resolve-url-loader@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-2.1.1.tgz#5354e87381aae348371e555172c50816708e6c1c" dependencies: adjust-sourcemap-loader "^1.1.0" camelcase "^4.0.0" @@ -7419,7 +7397,7 @@ typescript@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.4.1.tgz#c3ccb16ddaa0b2314de031e7e6fee89e5ba346bc" -typescript@2.5.3, typescript@^2.1.6: +typescript@2.5.3, typescript@^2.5.0: version "2.5.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.5.3.tgz#df3dcdc38f3beb800d4bc322646b04a3f6ca7f0d" @@ -7827,9 +7805,9 @@ webpack-sources@^1.0.1: source-list-map "^2.0.0" source-map "~0.5.3" -webpack@3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.6.0.tgz#a89a929fbee205d35a4fa2cc487be9cbec8898bc" +webpack@3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.7.1.tgz#6046b5c415ff7df7a0dc54c5b6b86098e8b952da" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" From 2d7bad9e5b6f9bcdad8c525dc3a5feb5bb4173b6 Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 12 Oct 2017 20:34:12 -0500 Subject: [PATCH 2/7] metadata service impoved test coverage --- .../core/metadata/metadata.service.spec.ts | 64 +++++++++++++++++-- src/app/core/metadata/metadata.service.ts | 38 ++++++----- 2 files changed, 82 insertions(+), 20 deletions(-) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 6e76874604..818a398e72 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -3,7 +3,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { By, Meta } from '@angular/platform-browser'; +import { By, Meta, MetaDefinition } from '@angular/platform-browser'; import { Router } from '@angular/router'; import { Store, StoreModule } from '@ngrx/store'; @@ -23,6 +23,7 @@ import { ResponseCacheService } from '../cache/response-cache.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { NormalizedItem } from '../cache/models/normalized-item.model'; +import { Item } from '../../core/shared/item.model'; import { MockRouter } from '../../shared/mocks/mock-router'; import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; @@ -72,7 +73,8 @@ describe('MetadataService', () => { CommonModule, StoreModule.forRoot({}), RouterTestingModule.withRoutes([ - { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } } + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, + { path: 'other', component: DummyItemComponent, pathMatch: 'full' } ]) ], declarations: [ @@ -104,11 +106,63 @@ describe('MetadataService', () => { spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { observer.next(MockNormalizedItem); })); - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); }); - it('upon navigation should call meta tag setters', () => { + it('items page should set meta tags', fakeAsync(() => { + spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); - }); + tick(); + const tagStore: Map = metadataService.getTagStore(); + expect(tagStore.get('citation_title').length).toEqual(1); + expect(tagStore.get('citation_title')[0].content).toEqual('Test PowerPoint Document'); + })); + + 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 = 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'); + })); + + 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 = 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(() => { + router.navigate(['/other']); + tick(); + const tagStore: Map = metadataService.getTagStore(); + expect(tagStore.size).toEqual(0); + })); + + const mockType = (mockItem: Item, type: string): Item => { + const typedMockItem = Object.assign({}, mockItem) as Item; + for (const metadatum of typedMockItem.metadata) { + if (metadatum.key === 'dc.type') { + metadatum.value = type; + break; + } + } + return typedMockItem; + } + + const mockPublisher = (mockItem: Item): Item => { + const publishedMockItem = Object.assign({}, mockItem) as Item; + publishedMockItem.metadata.push({ + key: 'dc.publisher', + language: 'en_US', + value: 'Mock Publisher' + }); + return publishedMockItem; + } }); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 9e8c6747f5..a7674ae7f2 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -55,21 +55,25 @@ export class MetadataService { route = this.getCurrentRoute(route); return { params: route.params, data: route.data }; }).subscribe((routeInfo: any) => { - if (routeInfo.params.value.id && routeInfo.data.value.type) { - this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) - .first().subscribe((normalizedObject: CacheableObject) => { - const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; - if (!this.initialized) { - this.initialize(dspaceObject); - } - this.currentObject.next(dspaceObject); - }); - } else { - this.clearMetaTags(); - } + this.processRouteChange(routeInfo); }); } + private processRouteChange(routeInfo: any): void { + if (routeInfo.params.value.id && routeInfo.data.value.type) { + this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) + .first().subscribe((normalizedObject: CacheableObject) => { + const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } else { + this.clearMetaTags(); + } + } + private initialize(dspaceObject: DSpaceObject): void { this.currentObject = new BehaviorSubject(dspaceObject); const subscription = this.currentObject.asObservable().distinctUntilKeyChanged('uuid').subscribe(() => { @@ -246,7 +250,7 @@ export class MetadataService { let isDissertation = false; for (const metadatum of this.currentObject.value.metadata) { if (metadatum.key === 'dc.type') { - isDissertation = metadatum.value === 'Thesis'; + isDissertation = metadatum.value.toLowerCase() === 'thesis'; break; } } @@ -263,7 +267,7 @@ export class MetadataService { let isTechReport = false; for (const metadatum of this.currentObject.value.metadata) { if (metadatum.key === 'dc.type') { - isTechReport = metadatum.value === 'Technical Report'; + isTechReport = metadatum.value.toLowerCase() === 'technical report'; break; } } @@ -344,11 +348,15 @@ export class MetadataService { this.tagStore.set(key, tags); } - private clearMetaTags() { + public clearMetaTags() { this.tagStore.forEach((tags: MetaDefinition[], property: string) => { this.meta.removeTag("property='" + property + "'"); }); this.tagStore.clear(); } + public getTagStore(): Map { + return this.tagStore; + } + } From c37a30ec2afccb2a6f1fce3ff9891cb5d7b5a840 Mon Sep 17 00:00:00 2001 From: William Welling Date: Thu, 12 Oct 2017 20:35:30 -0500 Subject: [PATCH 3/7] upgraded node types --- package.json | 2 +- yarn.lock | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 9a5e024941..3959872257 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "@types/jasmine": "2.6.0", "@types/memory-cache": "0.0.31", "@types/mime": "2.0.0", - "@types/node": "8.0.26", + "@types/node": "8.0.34", "@types/serve-static": "1.7.32", "@types/source-map": "0.5.1", "@types/webfontloader": "1.6.29", diff --git a/yarn.lock b/yarn.lock index 39e4bf575d..2fcafccd02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,14 +192,10 @@ version "2.0.29" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-2.0.29.tgz#5002e14f75e2d71e564281df0431c8c1b4a2a36a" -"@types/node@*": +"@types/node@*", "@types/node@8.0.34": version "8.0.34" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.34.tgz#55f801fa2ddb2a40dd6dfc15ecfe1dde9c129fe9" -"@types/node@8.0.26": - version "8.0.26" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.26.tgz#4d58be925306fd22b1141085535a0268b8beb189" - "@types/node@^6.0.46": version "6.0.88" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66" From 2f9c8468fd3108e6c0e4b01256f287007c7d1cce Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 00:38:26 -0500 Subject: [PATCH 4/7] initial metadata service with full coverage --- .../+home-page/home-page-routing.module.ts | 2 +- .../search-page-routing.module.ts | 2 +- .../normalized-bitstream-format.model.ts | 27 ++++- .../models/normalized-bitstream.model.ts | 7 +- .../cache/models/normalized-object-factory.ts | 8 +- .../core/metadata/metadata.service.spec.ts | 42 ++++--- src/app/core/metadata/metadata.service.ts | 49 ++++++-- src/app/core/shared/bitstream-format.model.ts | 17 +++ src/app/core/shared/bitstream.model.ts | 11 +- src/app/shared/mocks/mock-item.ts | 111 +++++++++++++++++- 10 files changed, 226 insertions(+), 50 deletions(-) create mode 100644 src/app/core/shared/bitstream-format.model.ts diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index e68b633a6d..f70109e3fe 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -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' } } ]) ] }) diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index de2d64c6c9..a74a88dcfa 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -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' } } ]) ] }) diff --git a/src/app/core/cache/models/normalized-bitstream-format.model.ts b/src/app/core/cache/models/normalized-bitstream-format.model.ts index 2813794011..bb8b049a1c 100644 --- a/src/app/core/cache/models/normalized-bitstream-format.model.ts +++ b/src/app/core/cache/models/normalized-bitstream-format.model.ts @@ -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; } diff --git a/src/app/core/cache/models/normalized-bitstream.model.ts b/src/app/core/cache/models/normalized-bitstream.model.ts index ba5343e252..db8002a874 100644 --- a/src/app/core/cache/models/normalized-bitstream.model.ts +++ b/src/app/core/cache/models/normalized-bitstream.model.ts @@ -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; /** diff --git a/src/app/core/cache/models/normalized-object-factory.ts b/src/app/core/cache/models/normalized-object-factory.ts index e9f3b1c9e6..3c67b18b3e 100644 --- a/src/app/core/cache/models/normalized-object-factory.ts +++ b/src/app/core/cache/models/normalized-object-factory.ts @@ -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 } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 818a398e72..90501c4308 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -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; + let tagStore: Map; + + let envConfig: GlobalConfig; + beforeEach(() => { store = new Store(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 = 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 = 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 = 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 = 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', diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index a7674ae7f2..cfc4744161 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -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(); } 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); - 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 to the + */ + private setTitleTag(): void { + const value = this.getMetaTagValue('dc.title'); + this.addMetaTag('title', value); + } + + /** + * Add to the + */ + private setDescriptionTag(): void { + // TODO: truncate abstract + const value = this.getMetaTagValue('dc.description.abstract'); + this.addMetaTag('desciption', value); + } + /** * Add to the */ @@ -175,7 +201,7 @@ export class MetadataService { * Add to the */ 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); + } + }); } }); } diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts new file mode 100644 index 0000000000..c0f6be29c9 --- /dev/null +++ b/src/app/core/shared/bitstream-format.model.ts @@ -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; + +} diff --git a/src/app/core/shared/bitstream.model.ts b/src/app/core/shared/bitstream.model.ts index 5e4ee929d4..0b77a7b032 100644 --- a/src/app/core/shared/bitstream.model.ts +++ b/src/app/core/shared/bitstream.model.ts @@ -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; + /** * An array of Items that are direct parents of this Bitstream */ diff --git a/src/app/shared/mocks/mock-item.ts b/src/app/shared/mocks/mock-item.ts index 43ad178485..0331491aa0 100644 --- a/src/app/shared/mocks/mock-item.ts +++ b/src/app/shared/mocks/mock-item.ts @@ -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 */ From 366f350970b359194892d4553d1af1d561631304 Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 00:43:14 -0500 Subject: [PATCH 5/7] removed unused import, added todo comment --- src/app/core/metadata/metadata.service.spec.ts | 2 +- src/app/core/metadata/metadata.service.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 90501c4308..803343c12c 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -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, DebugElement, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By, Meta, MetaDefinition } from '@angular/platform-browser'; import { Router } from '@angular/router'; diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index cfc4744161..60c2004071 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -40,7 +40,8 @@ export class MetadataService { private meta: Meta, @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig ) { - // TODO: + // TODO: determine what open graph meta tags are needed and whether + // the differ per route. potentially add image based on DSpaceObject this.meta.addTags([ { property: 'og:title', content: 'DSpace Angular Universal' }, { property: 'og:description', content: 'The modern front-end for DSpace 7.' } From 617f3ad0294a06e88697c302200e6db1c2a585bc Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 08:32:05 -0500 Subject: [PATCH 6/7] updating title element on route change with i18n translation --- e2e/app.e2e-spec.ts | 4 ++-- resources/i18n/en.json | 4 ++++ .../+home-page/home-page-routing.module.ts | 2 +- .../search-page-routing.module.ts | 2 +- .../core/metadata/metadata.service.spec.ts | 23 +++++++++++++++---- src/app/core/metadata/metadata.service.ts | 21 +++++++++++++---- 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts index 79b9f251dd..f5ac9094d0 100644 --- a/e2e/app.e2e-spec.ts +++ b/e2e/app.e2e-spec.ts @@ -7,9 +7,9 @@ describe('protractor App', () => { page = new ProtractorPage(); }); - it('should display title "DSpace"', () => { + it('should display translated title "DSpace Angular :: Home"', () => { page.navigateTo(); - expect(page.getPageTitleText()).toEqual('DSpace'); + expect(page.getPageTitleText()).toEqual('DSpace Angular :: Home'); }); it('should display header "Welcome to DSpace"', () => { diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 3388190295..2239d605cc 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -65,12 +65,16 @@ } }, "home": { + "title": "DSpace Angular :: Home", + "description": "", "top-level-communities": { "head": "Communities in DSpace", "help": "Select a community to browse its collections." } }, "search": { + "title": "DSpace Angular :: Search", + "description": "", "form": { "search": "Search", "search_dspace": "Search DSpace" diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index f70109e3fe..d7dcc18f49 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -6,7 +6,7 @@ import { HomePageComponent } from './home-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'DSpace Angular :: Home' } } + { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'home.title' } } ]) ] }) diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts index a74a88dcfa..65cca99a34 100644 --- a/src/app/+search-page/search-page-routing.module.ts +++ b/src/app/+search-page/search-page-routing.module.ts @@ -6,7 +6,7 @@ import { SearchPageComponent } from './search-page.component'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: SearchPageComponent, data: { title: 'DSpace Angular :: Search' } } + { path: '', component: SearchPageComponent, data: { title: 'search.title' } } ]) ] }) diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 803343c12c..6f740b9f18 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -3,9 +3,11 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { By, Meta, MetaDefinition } from '@angular/platform-browser'; +import { By, Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; +import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; + import { Store, StoreModule } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; @@ -22,12 +24,13 @@ import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; -import { NormalizedItem } from '../cache/models/normalized-item.model'; import { Item } from '../../core/shared/item.model'; +import { NormalizedItem } from '../cache/models/normalized-item.model'; -import { MockRouter } from '../../shared/mocks/mock-router'; -import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; import { MockItem } from '../../shared/mocks/mock-item'; +import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; +import { MockRouter } from '../../shared/mocks/mock-router'; +import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; /* tslint:disable:max-classes-per-file */ @Component({ @@ -47,6 +50,8 @@ describe('MetadataService', () => { let meta: Meta; + let title: Title; + let store: Store; let objectCacheService: ObjectCacheService; @@ -76,6 +81,12 @@ describe('MetadataService', () => { imports: [ CommonModule, StoreModule.forRoot({}), + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useClass: MockTranslateLoader + } + }), RouterTestingModule.withRoutes([ { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } } @@ -92,11 +103,13 @@ describe('MetadataService', () => { { provide: RemoteDataBuildService, useValue: remoteDataBuildService }, { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, Meta, + Title, MetadataService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); meta = TestBed.get(Meta); + title = TestBed.get(Title); metadataService = TestBed.get(MetadataService); envConfig = TestBed.get(GLOBAL_CONFIG); @@ -119,6 +132,7 @@ describe('MetadataService', () => { spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); + expect(title.getTitle()).toEqual('Test PowerPoint Document'); 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'); @@ -152,6 +166,7 @@ describe('MetadataService', () => { router.navigate(['/other']); tick(); expect(tagStore.size).toEqual(2); + 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 component for testing!'); })); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 60c2004071..4090d2b484 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -9,7 +9,10 @@ import { Params, Router } from '@angular/router'; -import { Meta, MetaDefinition } from '@angular/platform-browser'; + +import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; + +import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; @@ -37,7 +40,9 @@ export class MetadataService { private router: Router, private objectCacheService: ObjectCacheService, private remoteDataBuildService: RemoteDataBuildService, + private translate: TranslateService, private meta: Meta, + private title: Title, @Inject(GLOBAL_CONFIG) private envConfig: GlobalConfig ) { // TODO: determine what open graph meta tags are needed and whether @@ -63,7 +68,6 @@ 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) => { @@ -74,11 +78,17 @@ export class MetadataService { this.currentObject.next(dspaceObject); }); } else { + this.clearMetaTags(); if (routeInfo.data.value.title) { - this.addMetaTag('title', routeInfo.data.value.title); + this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.addMetaTag('title', translatedTitle); + this.title.setTitle(translatedTitle); + }); } if (routeInfo.data.value.description) { - this.addMetaTag('description', routeInfo.data.value.description); + this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.addMetaTag('description', translatedDescription); + }); } } } @@ -100,6 +110,8 @@ export class MetadataService { private setMetaTags(): void { + this.clearMetaTags(); + this.setTitleTag(); this.setDescriptionTag(); @@ -147,6 +159,7 @@ export class MetadataService { private setTitleTag(): void { const value = this.getMetaTagValue('dc.title'); this.addMetaTag('title', value); + this.title.setTitle(value); } /** From 6f42fa5af5bc19c96a4e27d4af4b546668c9dcd8 Mon Sep 17 00:00:00 2001 From: William Welling Date: Fri, 13 Oct 2017 13:09:00 -0500 Subject: [PATCH 7/7] moved meta service into applicable page components --- .../collection-page-routing.module.ts | 3 +- .../collection-page.component.ts | 18 ++- .../community-page-routing.module.ts | 3 +- .../community-page.component.ts | 11 +- .../full/full-item-page.component.ts | 10 +- .../+item-page/item-page-routing.module.ts | 5 +- .../+item-page/simple/item-page.component.ts | 9 +- .../core/metadata/metadata.service.spec.ts | 69 ++++++++--- src/app/core/metadata/metadata.service.ts | 47 ++++---- src/app/shared/mocks/mock-normalized-item.ts | 114 ------------------ 10 files changed, 108 insertions(+), 181 deletions(-) delete mode 100644 src/app/shared/mocks/mock-normalized-item.ts diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts index fc7f58b6fb..c886aa655c 100644 --- a/src/app/+collection-page/collection-page-routing.module.ts +++ b/src/app/+collection-page/collection-page-routing.module.ts @@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CollectionPageComponent } from './collection-page.component'; -import { NormalizedCollection } from '../core/cache/models/normalized-collection.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CollectionPageComponent, pathMatch: 'full', data: { type: NormalizedCollection } } + { path: ':id', component: CollectionPageComponent, pathMatch: 'full' } ]) ] }) diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 65bf708727..30d9c17fe3 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -7,6 +7,8 @@ import { } from '@angular/core'; import { ActivatedRoute, Params } from '@angular/router'; +import { PageInfo } from '../core/shared/page-info.model'; +import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; import { Collection } from '../core/shared/collection.model'; @@ -18,8 +20,8 @@ import { Item } from '../core/shared/item.model'; import { SortOptions, SortDirection } from '../core/cache/models/sort-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { hasValue, isNotEmpty, isUndefined } from '../shared/empty.util'; -import { PageInfo } from '../core/shared/page-info.model'; -import { Observable } from 'rxjs/Observable'; + +import { MetadataService } from '../core/metadata/metadata.service'; import { fadeIn, fadeInOut } from '../shared/animations/fade'; @@ -41,9 +43,12 @@ export class CollectionPageComponent implements OnInit, OnDestroy { private subs: Subscription[] = []; private collectionId: string; - constructor(private collectionDataService: CollectionDataService, - private itemDataService: ItemDataService, - private route: ActivatedRoute) { + constructor( + private collectionDataService: CollectionDataService, + private itemDataService: ItemDataService, + private metadata: MetadataService, + private route: ActivatedRoute + ) { this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.pageSizeOptions = [4]; @@ -57,12 +62,13 @@ export class CollectionPageComponent implements OnInit, OnDestroy { Observable.combineLatest( this.route.params, this.route.queryParams, - (params, queryParams,) => { + (params, queryParams, ) => { return Object.assign({}, params, queryParams); }) .subscribe((params) => { this.collectionId = params.id; this.collectionData = this.collectionDataService.findById(this.collectionId); + this.metadata.processRemoteData(this.collectionData); this.subs.push(this.collectionData.payload.subscribe((collection) => this.logoData = collection.logo)); const page = +params.page || this.paginationConfig.currentPage; diff --git a/src/app/+community-page/community-page-routing.module.ts b/src/app/+community-page/community-page-routing.module.ts index e761717808..6fd5cc8cb5 100644 --- a/src/app/+community-page/community-page-routing.module.ts +++ b/src/app/+community-page/community-page-routing.module.ts @@ -2,12 +2,11 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommunityPageComponent } from './community-page.component'; -import { NormalizedCommunity } from '../core/cache/models/normalized-community.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: CommunityPageComponent, pathMatch: 'full', data: { type: NormalizedCommunity } } + { path: ':id', component: CommunityPageComponent, pathMatch: 'full' } ]) ] }) diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index 1295e14521..0cd94658be 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -9,6 +9,8 @@ import { RemoteData } from '../core/data/remote-data'; import { CommunityDataService } from '../core/data/community-data.service'; import { hasValue } from '../shared/empty.util'; +import { MetadataService } from '../core/metadata/metadata.service'; + import { fadeInOut } from '../shared/animations/fade'; @Component({ @@ -24,6 +26,7 @@ export class CommunityPageComponent implements OnInit, OnDestroy { constructor( private communityDataService: CommunityDataService, + private metadata: MetadataService, private route: ActivatedRoute ) { @@ -32,15 +35,13 @@ export class CommunityPageComponent implements OnInit, OnDestroy { ngOnInit(): void { this.route.params.subscribe((params: Params) => { this.communityData = this.communityDataService.findById(params.id); - this.subs.push(this.communityData.payload - .subscribe((community) => this.logoData = community.logo)); + this.metadata.processRemoteData(this.communityData); + this.subs.push(this.communityData.payload.subscribe((community) => this.logoData = community.logo)); }); } ngOnDestroy(): void { - this.subs - .filter((sub) => hasValue(sub)) - .forEach((sub) => sub.unsubscribe()); + this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe()); } } diff --git a/src/app/+item-page/full/full-item-page.component.ts b/src/app/+item-page/full/full-item-page.component.ts index 337c598021..270cf1fcae 100644 --- a/src/app/+item-page/full/full-item-page.component.ts +++ b/src/app/+item-page/full/full-item-page.component.ts @@ -1,15 +1,17 @@ import { Component, OnInit } from '@angular/core'; -import { animate, state, transition, trigger, style, keyframes } from '@angular/animations'; +import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; import { ItemPageComponent } from '../simple/item-page.component'; import { Metadatum } from '../../core/shared/metadatum.model'; import { ItemDataService } from '../../core/data/item-data.service'; -import { ActivatedRoute } from '@angular/router'; + import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -30,8 +32,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit { metadata: Observable; - constructor(route: ActivatedRoute, items: ItemDataService) { - super(route, items); + constructor(route: ActivatedRoute, items: ItemDataService, metadataService: MetadataService) { + super(route, items, metadataService); } /*** AoT inheritance fix, will hopefully be resolved in the near future **/ diff --git a/src/app/+item-page/item-page-routing.module.ts b/src/app/+item-page/item-page-routing.module.ts index e315b18a9c..9dca4d0f6e 100644 --- a/src/app/+item-page/item-page-routing.module.ts +++ b/src/app/+item-page/item-page-routing.module.ts @@ -3,13 +3,12 @@ import { RouterModule } from '@angular/router'; import { ItemPageComponent } from './simple/item-page.component'; import { FullItemPageComponent } from './full/full-item-page.component'; -import { NormalizedItem } from '../core/cache/models/normalized-item.model'; @NgModule({ imports: [ RouterModule.forChild([ - { path: ':id', component: ItemPageComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: ':id/full', component: FullItemPageComponent, data: { type: NormalizedItem } } + { path: ':id', component: ItemPageComponent, pathMatch: 'full' }, + { path: ':id/full', component: FullItemPageComponent } ]) ] }) diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts index 8f1bd18b4e..ce9283e144 100644 --- a/src/app/+item-page/simple/item-page.component.ts +++ b/src/app/+item-page/simple/item-page.component.ts @@ -8,6 +8,8 @@ import { ItemDataService } from '../../core/data/item-data.service'; import { RemoteData } from '../../core/data/remote-data'; import { Bitstream } from '../../core/shared/bitstream.model'; +import { MetadataService } from '../../core/metadata/metadata.service'; + import { fadeInOut } from '../../shared/animations/fade'; /** @@ -31,7 +33,11 @@ export class ItemPageComponent implements OnInit { thumbnail: Observable; - constructor(private route: ActivatedRoute, private items: ItemDataService) { + constructor( + private route: ActivatedRoute, + private items: ItemDataService, + private metadataService: MetadataService + ) { } @@ -44,6 +50,7 @@ export class ItemPageComponent implements OnInit { initialize(params) { this.id = +params.id; this.item = this.items.findById(params.id); + this.metadataService.processRemoteData(this.item); this.thumbnail = this.item.payload.flatMap((i) => i.getThumbnail()); } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 6f740b9f18..1258751f58 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -4,7 +4,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { Location, CommonModule } from '@angular/common'; import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { By, Meta, MetaDefinition, Title } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { TranslateModule, TranslateLoader } from '@ngx-translate/core'; @@ -19,17 +19,16 @@ import { CoreState } from '../core.reducers'; import { GlobalConfig } from '../../../config/global-config.interface'; import { ENV_CONFIG, GLOBAL_CONFIG } from '../../../config'; +import { ItemDataService } from '../data/item-data.service'; import { ObjectCacheService } from '../cache/object-cache.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { RequestService } from '../data/request.service'; import { ResponseCacheService } from '../cache/response-cache.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; -import { NormalizedItem } from '../cache/models/normalized-item.model'; import { MockItem } from '../../shared/mocks/mock-item'; -import { MockNormalizedItem } from '../../shared/mocks/mock-normalized-item'; -import { MockRouter } from '../../shared/mocks/mock-router'; import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader'; /* tslint:disable:max-classes-per-file */ @@ -42,7 +41,13 @@ class TestComponent { } } -@Component({ template: '' }) class DummyItemComponent { } +@Component({ template: '' }) class DummyItemComponent { + constructor(private route: ActivatedRoute, private items: ItemDataService, private metadata: MetadataService) { + this.route.params.subscribe((params) => { + this.metadata.processRemoteData(this.items.findById(params.id)); + }); + } +} /* tslint:enable:max-classes-per-file */ describe('MetadataService', () => { @@ -58,6 +63,7 @@ describe('MetadataService', () => { let responseCacheService: ResponseCacheService; let requestService: RequestService; let remoteDataBuildService: RemoteDataBuildService; + let itemDataService: ItemDataService; let location: Location; let router: Router; @@ -88,8 +94,8 @@ describe('MetadataService', () => { } }), RouterTestingModule.withRoutes([ - { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full', data: { type: NormalizedItem } }, - { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy component for testing!' } } + { path: 'items/:id', component: DummyItemComponent, pathMatch: 'full' }, + { path: 'other', component: DummyItemComponent, pathMatch: 'full', data: { title: 'Dummy Title', description: 'This is a dummy item component for testing!' } } ]) ], declarations: [ @@ -104,12 +110,14 @@ describe('MetadataService', () => { { provide: GLOBAL_CONFIG, useValue: ENV_CONFIG }, Meta, Title, + ItemDataService, MetadataService ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }); meta = TestBed.get(Meta); title = TestBed.get(Title); + itemDataService = TestBed.get(ItemDataService); metadataService = TestBed.get(MetadataService); envConfig = TestBed.get(GLOBAL_CONFIG); @@ -122,14 +130,8 @@ describe('MetadataService', () => { tagStore = metadataService.getTagStore(); }); - beforeEach(() => { - spyOn(objectCacheService, 'getByUUID').and.returnValue(Observable.create((observer) => { - observer.next(MockNormalizedItem); - })); - }); - it('items page should set meta tags', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(title.getTitle()).toEqual('Test PowerPoint Document'); @@ -142,7 +144,7 @@ describe('MetadataService', () => { })); it('items page should set meta tags as published Thesis', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Thesis'))); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Thesis')))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); @@ -152,14 +154,14 @@ describe('MetadataService', () => { })); it('items page should set meta tags as published Technical Report', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(mockPublisher(mockType(MockItem, 'Technical Report'))); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(mockPublisher(mockType(MockItem, 'Technical Report')))); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.get('citation_technical_report_institution')[0].content).toEqual('Mock Publisher'); })); it('other navigation should title and description', fakeAsync(() => { - spyOn(remoteDataBuildService, 'build').and.returnValue(MockItem); + spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(MockItem)); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); tick(); expect(tagStore.size).toBeGreaterThan(0) @@ -168,9 +170,38 @@ describe('MetadataService', () => { expect(tagStore.size).toEqual(2); 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 component for testing!'); + expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!'); })); + const mockRemoteData = (mockItem: Item): RemoteData => { + return new RemoteData( + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(false); + }), + Observable.create((observer) => { + observer.next(true); + }), + Observable.create((observer) => { + observer.next(''); + }), + Observable.create((observer) => { + observer.next(200); + }), + Observable.create((observer) => { + observer.next({}); + }), + Observable.create((observer) => { + observer.next(MockItem); + }) + ); + } + const mockType = (mockItem: Item, type: string): Item => { const typedMockItem = Object.assign(new Item(), mockItem) as Item; for (const metadatum of typedMockItem.metadata) { diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 4090d2b484..32b002e721 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -17,13 +17,12 @@ import { TranslateService } from '@ngx-translate/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Observable } from 'rxjs/Observable'; +import { RemoteData } from '../data/remote-data'; import { Bitstream } from '../shared/bitstream.model'; import { CacheableObject } from '../cache/object-cache.reducer'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; import { Metadatum } from '../shared/metadatum.model'; -import { ObjectCacheService } from '../cache/object-cache.service'; -import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; @@ -38,8 +37,6 @@ export class MetadataService { constructor( private router: Router, - private objectCacheService: ObjectCacheService, - private remoteDataBuildService: RemoteDataBuildService, private translate: TranslateService, private meta: Meta, private title: Title, @@ -67,29 +64,29 @@ export class MetadataService { }); } + public processRemoteData(remoteData: RemoteData): void { + remoteData.payload.take(1).subscribe((dspaceObject: DSpaceObject) => { + if (!this.initialized) { + this.initialize(dspaceObject); + } + this.currentObject.next(dspaceObject); + }); + } + private processRouteChange(routeInfo: any): void { - if (routeInfo.params.value.id && routeInfo.data.value.type) { - this.objectCacheService.getByUUID(routeInfo.params.value.id, routeInfo.data.value.type) - .first().subscribe((normalizedObject: CacheableObject) => { - const dspaceObject = this.remoteDataBuildService.build(normalizedObject) as DSpaceObject; - if (!this.initialized) { - this.initialize(dspaceObject); - } - this.currentObject.next(dspaceObject); - }); - } else { + if (routeInfo.params.value.id === undefined) { this.clearMetaTags(); - if (routeInfo.data.value.title) { - this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { - this.addMetaTag('title', translatedTitle); - this.title.setTitle(translatedTitle); - }); - } - if (routeInfo.data.value.description) { - this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { - this.addMetaTag('description', translatedDescription); - }); - } + } + if (routeInfo.data.value.title) { + this.translate.get(routeInfo.data.value.title).take(1).subscribe((translatedTitle: string) => { + this.addMetaTag('title', translatedTitle); + this.title.setTitle(translatedTitle); + }); + } + if (routeInfo.data.value.description) { + this.translate.get(routeInfo.data.value.description).take(1).subscribe((translatedDescription: string) => { + this.addMetaTag('description', translatedDescription); + }); } } diff --git a/src/app/shared/mocks/mock-normalized-item.ts b/src/app/shared/mocks/mock-normalized-item.ts deleted file mode 100644 index d8716b4bf2..0000000000 --- a/src/app/shared/mocks/mock-normalized-item.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { NormalizedItem } from '../../core/cache/models/normalized-item.model'; - -export const MockNormalizedItem: NormalizedItem = Object.assign(new NormalizedItem(), { - handle: '10673/6', - lastModified: new Date('2017-04-24T19:44:08.178+0000'), - isArchived: true, - isDiscoverable: true, - isWithdrawn: false, - bitstreams: [ - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/a7cd7d97-4e40-41db-80a8-fac908b63bb8', - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/9ff3df0d-1709-472f-8c00-d3e8db2153c8', - 'https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/d660a4b8-e7cc-45cd-b026-35f98c5bd3ba' - ], - self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357', - id: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', - uuid: '0ec7ff22-f211-40ab-a69e-c819b0b1f357', - type: 'item', - name: 'Test PowerPoint Document', - metadata: [ - { - key: 'dc.creator', - language: 'en_US', - value: 'Doe, Jane L' - }, - { - key: 'dc.date.accessioned', - language: null, - value: '1650-06-26T19:58:25Z' - }, - { - key: 'dc.date.available', - language: null, - value: '1650-06-26T19:58:25Z' - }, - { - key: 'dc.date.issued', - language: null, - value: '1650-06-26' - }, - { - key: 'dc.identifier.issn', - language: 'en_US', - value: '123456789' - }, - { - key: 'dc.identifier.uri', - language: null, - value: 'http://dspace7.4science.it/xmlui/handle/10673/6' - }, - { - key: 'dc.description.abstract', - language: 'en_US', - value: 'This is really just a sample abstract. If it was a real abstract it would contain useful information about this test document. Sorry though, nothing useful in this paragraph. You probably shouldn\'t have even bothered to read it!' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Made available in DSpace on 2012-06-26T19:58:25Z (GMT). No. of bitstreams: 2\r\ntest_ppt.ppt: 12707328 bytes, checksum: a353fc7d29b3c558c986f7463a41efd3 (MD5)\r\ntest_ppt.pptx: 12468572 bytes, checksum: 599305edb4ebee329667f2c35b14d1d6 (MD5)' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2013-06-13T09:17:34Z (GMT).' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2013-06-13T11:04:16Z (GMT).' - }, - { - key: 'dc.description.provenance', - language: 'en', - value: 'Restored into DSpace on 2017-04-24T19:44:08Z (GMT).' - }, - { - key: 'dc.language', - language: 'en_US', - value: 'en' - }, - { - key: 'dc.rights', - language: 'en_US', - value: '© Jane Doe' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword1' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword2' - }, - { - key: 'dc.subject', - language: 'en_US', - value: 'keyword3' - }, - { - key: 'dc.title', - language: 'en_US', - value: 'Test PowerPoint Document' - }, - { - key: 'dc.type', - language: 'en_US', - value: 'text' - } - ], - owningCollection: [ - 'https://dspace7.4science.it/dspace-spring-rest/api/core/collections/1c11f3f1-ba1f-4f36-908a-3f1ea9a557eb' - ] -})