diff --git a/e2e/app.po.ts b/e2e/app.po.ts index 54b5b55af3..c76bef118f 100644 --- a/e2e/app.po.ts +++ b/e2e/app.po.ts @@ -2,7 +2,8 @@ import { browser, element, by } from 'protractor'; export class ProtractorPage { navigateTo() { - return browser.get('/'); + return browser.get('/') + .then(() => browser.waitForAngular()); } getPageTitleText() { diff --git a/package.json b/package.json index 2a247f13ad..aaabc0271a 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ }, "dependencies": { "@angular/animations": "^6.1.4", + "@angular/cdk": "^6.4.7", "@angular/cli": "^6.1.5", "@angular/common": "^6.1.4", "@angular/core": "^6.1.4", @@ -229,7 +230,7 @@ "rollup-plugin-node-globals": "1.2.1", "rollup-plugin-node-resolve": "^3.0.3", "rollup-plugin-terser": "^2.0.2", - "sass-loader": "7.1.0", + "sass-loader": "^7.1.0", "script-ext-html-webpack-plugin": "2.0.1", "source-map": "0.7.3", "source-map-loader": "0.2.4", diff --git a/resources/fonts/README.md b/resources/fonts/README.md new file mode 100644 index 0000000000..e4817b8572 --- /dev/null +++ b/resources/fonts/README.md @@ -0,0 +1,3 @@ +# Supported font formats + +DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts. diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index 7fa9c96010..76ffe955a7 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -340,6 +340,14 @@ + "communityList.tabTitle": "DSpace - Community List", + + "communityList.title": "List of Communities", + + "communityList.showMore": "Show More", + + + "community.create.head": "Create a Community", "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}", @@ -825,6 +833,14 @@ "item.page.related-items.view-less": "View less", + "item.page.relationships.isAuthorOfPublication": "Publications", + + "item.page.relationships.isJournalOfPublication": "Publications", + + "item.page.relationships.isOrgUnitOfPerson": "Authors", + + "item.page.relationships.isOrgUnitOfProject": "Research Projects", + "item.page.subject": "Keywords", "item.page.uri": "URI", @@ -1275,6 +1291,8 @@ "project.page.titleprefix": "Research Project: ", + "project.search.results.head": "Project Search Results", + "publication.listelement.badge": "Publication", diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts index cb7aa1ef91..ec4003c108 100644 --- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts +++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts @@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list'; import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model'; import { BitstreamFormat } from '../../../core/shared/bitstream-format.model'; import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; import { map, switchMap, take } from 'rxjs/operators'; import { hasValue } from '../../../shared/empty.util'; import { NotificationsService } from '../../../shared/notifications/notifications.service'; @@ -35,7 +35,7 @@ export class BitstreamFormatsComponent implements OnInit { * The current pagination configuration for the page used by the FindAll method * Currently simply renders all bitstream formats */ - config: FindAllOptions = Object.assign(new FindAllOptions(), { + config: FindListOptions = Object.assign(new FindListOptions(), { elementsPerPage: 20 }); @@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit { * @param event The page change event */ onPageChange(event) { - this.config = Object.assign(new FindAllOptions(), this.config, { + this.config = Object.assign(new FindListOptions(), this.config, { currentPage: event, }); this.pageConfig.currentPage = event; diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index 436cd351a0..12d5c200fd 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -3,6 +3,7 @@ *ngVar="(collectionRD$ | async) as collectionRD">
+
+
diff --git a/src/app/+community-page/community-page.module.ts b/src/app/+community-page/community-page.module.ts index 6d63cadcc8..8b02471fc2 100644 --- a/src/app/+community-page/community-page.module.ts +++ b/src/app/+community-page/community-page.module.ts @@ -6,17 +6,19 @@ import { SharedModule } from '../shared/shared.module'; import { CommunityPageComponent } from './community-page.component'; import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component'; import { CommunityPageRoutingModule } from './community-page-routing.module'; -import {CommunityPageSubCommunityListComponent} from './sub-community-list/community-page-sub-community-list.component'; +import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component'; import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component'; import { CommunityFormComponent } from './community-form/community-form.component'; import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component'; import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component'; +import { StatisticsModule } from '../statistics/statistics.module'; @NgModule({ imports: [ CommonModule, SharedModule, - CommunityPageRoutingModule + CommunityPageRoutingModule, + StatisticsModule.forRoot() ], declarations: [ CommunityPageComponent, diff --git a/src/app/+home-page/home-page-routing.module.ts b/src/app/+home-page/home-page-routing.module.ts index d7dcc18f49..78da529906 100644 --- a/src/app/+home-page/home-page-routing.module.ts +++ b/src/app/+home-page/home-page-routing.module.ts @@ -2,12 +2,25 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { HomePageComponent } from './home-page.component'; +import { HomePageResolver } from './home-page.resolver'; @NgModule({ imports: [ RouterModule.forChild([ - { path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'home.title' } } + { + path: '', + component: HomePageComponent, + pathMatch: 'full', + data: {title: 'home.title'}, + resolve: { + site: HomePageResolver + } + } ]) + ], + providers: [ + HomePageResolver ] }) -export class HomePageRoutingModule { } +export class HomePageRoutingModule { +} diff --git a/src/app/+home-page/home-page.component.html b/src/app/+home-page/home-page.component.html index 39ba479033..5515df595b 100644 --- a/src/app/+home-page/home-page.component.html +++ b/src/app/+home-page/home-page.component.html @@ -1,5 +1,8 @@
+ + +
diff --git a/src/app/+home-page/home-page.component.ts b/src/app/+home-page/home-page.component.ts index 902a0e820d..1b915ae683 100644 --- a/src/app/+home-page/home-page.component.ts +++ b/src/app/+home-page/home-page.component.ts @@ -1,9 +1,26 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { ActivatedRoute } from '@angular/router'; +import { Observable } from 'rxjs'; +import { Site } from '../core/shared/site.model'; @Component({ selector: 'ds-home-page', styleUrls: ['./home-page.component.scss'], templateUrl: './home-page.component.html' }) -export class HomePageComponent { +export class HomePageComponent implements OnInit { + + site$:Observable; + + constructor( + private route:ActivatedRoute, + ) { + } + + ngOnInit():void { + this.site$ = this.route.data.pipe( + map((data) => data.site as Site), + ); + } } diff --git a/src/app/+home-page/home-page.module.ts b/src/app/+home-page/home-page.module.ts index c0c082b36c..51e978bbfe 100644 --- a/src/app/+home-page/home-page.module.ts +++ b/src/app/+home-page/home-page.module.ts @@ -6,12 +6,14 @@ import { HomePageRoutingModule } from './home-page-routing.module'; import { HomePageComponent } from './home-page.component'; import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component'; +import { StatisticsModule } from '../statistics/statistics.module'; @NgModule({ imports: [ CommonModule, SharedModule, - HomePageRoutingModule + HomePageRoutingModule, + StatisticsModule.forRoot() ], declarations: [ HomePageComponent, diff --git a/src/app/+home-page/home-page.resolver.ts b/src/app/+home-page/home-page.resolver.ts new file mode 100644 index 0000000000..1145d1d013 --- /dev/null +++ b/src/app/+home-page/home-page.resolver.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router'; +import { SiteDataService } from '../core/data/site-data.service'; +import { Site } from '../core/shared/site.model'; +import { Observable } from 'rxjs'; +import { take } from 'rxjs/operators'; + +/** + * The class that resolve the Site object for a route + */ +@Injectable() +export class HomePageResolver implements Resolve { + constructor(private siteService:SiteDataService) { + } + + /** + * Method for resolving a site object + * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot + * @param {RouterStateSnapshot} state The current RouterStateSnapshot + * @returns Observable Emits the found Site object, or an error if something went wrong + */ + resolve(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable | Promise | Site { + return this.siteService.find().pipe(take(1)); + } +} diff --git a/src/app/+item-page/full/full-item-page.component.html b/src/app/+item-page/full/full-item-page.component.html index 7aec57da0c..c453df6bff 100644 --- a/src/app/+item-page/full/full-item-page.component.html +++ b/src/app/+item-page/full/full-item-page.component.html @@ -1,6 +1,7 @@
+
- - + +
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 4d97868b58..1b23d567f5 100644 --- a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -24,16 +24,6 @@
- - - -
+
+ + +
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html index ff675ab057..97a3cf416e 100644 --- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -53,8 +53,11 @@
- - + +
diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts index 4c7c3cd030..b2ba10fb98 100644 --- a/src/app/navbar/navbar.component.ts +++ b/src/app/navbar/navbar.component.ts @@ -53,17 +53,18 @@ export class NavbarComponent extends MenuComponent implements OnInit { } as TextMenuItemModel, index: 0 }, - // { - // id: 'browse_global_communities_and_collections', - // parentID: 'browse_global', - // active: false, - // visible: true, - // model: { - // type: MenuItemType.LINK, - // text: 'menu.section.browse_global_communities_and_collections', - // link: '#' - // } as LinkMenuItemModel, - // }, + /* Communities & Collections tree */ + { + id: `browse_global_communities_and_collections`, + parentID: 'browse_global', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: `menu.section.browse_global_communities_and_collections`, + link: `/community-list` + } as LinkMenuItemModel + }, /* Statistics */ { diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts index 54cd4e2f86..65645e0739 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/external-source-tab/dynamic-lookup-relation-external-source-tab.component.ts @@ -8,7 +8,7 @@ import { RemoteData } from '../../../../../../core/data/remote-data'; import { PaginatedList } from '../../../../../../core/data/paginated-list'; import { ExternalSourceEntry } from '../../../../../../core/shared/external-source-entry.model'; import { ExternalSource } from '../../../../../../core/shared/external-source.model'; -import { switchMap } from 'rxjs/operators'; +import { startWith, switchMap } from 'rxjs/operators'; import { PaginatedSearchOptions } from '../../../../../search/paginated-search-options.model'; import { Context } from '../../../../../../core/shared/context.model'; import { ListableObject } from '../../../../../object-collection/shared/listable-object.model'; @@ -130,7 +130,7 @@ export class DsDynamicLookupRelationExternalSourceTabComponent implements OnInit ngOnInit(): void { this.entriesRD$ = this.searchConfigService.paginatedSearchOptions.pipe( switchMap((searchOptions: PaginatedSearchOptions) => - this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions)) + this.externalSourceService.getExternalSourceEntries(this.externalSource.id, searchOptions).pipe(startWith(undefined))) ) } diff --git a/src/app/shared/mocks/mock-angulartics.service.ts b/src/app/shared/mocks/mock-angulartics.service.ts index 99a8b96b22..5581e183d1 100644 --- a/src/app/shared/mocks/mock-angulartics.service.ts +++ b/src/app/shared/mocks/mock-angulartics.service.ts @@ -1,4 +1,5 @@ /* tslint:disable:no-empty */ export class AngularticsMock { public eventTrack(action, properties) { } + public startTracking():void {} } diff --git a/src/app/shared/mocks/mock-request.service.ts b/src/app/shared/mocks/mock-request.service.ts index ce4b167249..103ab14d88 100644 --- a/src/app/shared/mocks/mock-request.service.ts +++ b/src/app/shared/mocks/mock-request.service.ts @@ -1,8 +1,9 @@ import {of as observableOf, Observable } from 'rxjs'; import { RequestService } from '../../core/data/request.service'; import { RequestEntry } from '../../core/data/request.reducer'; +import SpyObj = jasmine.SpyObj; -export function getMockRequestService(requestEntry$: Observable = observableOf(new RequestEntry())): RequestService { +export function getMockRequestService(requestEntry$: Observable = observableOf(new RequestEntry())): SpyObj { return jasmine.createSpyObj('requestService', { configure: false, generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78', diff --git a/src/app/statistics/angulartics/dspace-provider.spec.ts b/src/app/statistics/angulartics/dspace-provider.spec.ts new file mode 100644 index 0000000000..d89d2d9fc6 --- /dev/null +++ b/src/app/statistics/angulartics/dspace-provider.spec.ts @@ -0,0 +1,26 @@ +import { Angulartics2DSpace } from './dspace-provider'; +import { Angulartics2 } from 'angulartics2'; +import { StatisticsService } from '../statistics.service'; +import { filter } from 'rxjs/operators'; +import { of as observableOf } from 'rxjs'; + +describe('Angulartics2DSpace', () => { + let provider:Angulartics2DSpace; + let angulartics2:Angulartics2; + let statisticsService:jasmine.SpyObj; + + beforeEach(() => { + angulartics2 = { + eventTrack: observableOf({action: 'pageView', properties: {object: 'mock-object'}}), + filterDeveloperMode: () => filter(() => true) + } as any; + statisticsService = jasmine.createSpyObj('statisticsService', {trackViewEvent: null}); + provider = new Angulartics2DSpace(angulartics2, statisticsService); + }); + + it('should use the statisticsService', () => { + provider.startTracking(); + expect(statisticsService.trackViewEvent).toHaveBeenCalledWith('mock-object'); + }); + +}); diff --git a/src/app/statistics/angulartics/dspace-provider.ts b/src/app/statistics/angulartics/dspace-provider.ts new file mode 100644 index 0000000000..9ab01f6023 --- /dev/null +++ b/src/app/statistics/angulartics/dspace-provider.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { Angulartics2 } from 'angulartics2'; +import { StatisticsService } from '../statistics.service'; + +/** + * Angulartics2DSpace is a angulartics2 plugin that provides DSpace with the events. + */ +@Injectable({providedIn: 'root'}) +export class Angulartics2DSpace { + + constructor( + private angulartics2:Angulartics2, + private statisticsService:StatisticsService, + ) { + } + + /** + * Activates this plugin + */ + startTracking():void { + this.angulartics2.eventTrack + .pipe(this.angulartics2.filterDeveloperMode()) + .subscribe((event) => this.eventTrack(event)); + } + + private eventTrack(event) { + if (event.action === 'pageView') { + this.statisticsService.trackViewEvent(event.properties.object); + } else if (event.action === 'search') { + this.statisticsService.trackSearchEvent( + event.properties.searchOptions, + event.properties.page, + event.properties.sort, + event.properties.filters + ); + } + } +} diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.html b/src/app/statistics/angulartics/dspace/view-tracker.component.html new file mode 100644 index 0000000000..c0c0ffe181 --- /dev/null +++ b/src/app/statistics/angulartics/dspace/view-tracker.component.html @@ -0,0 +1 @@ +  diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.scss b/src/app/statistics/angulartics/dspace/view-tracker.component.scss new file mode 100644 index 0000000000..c76cafbe44 --- /dev/null +++ b/src/app/statistics/angulartics/dspace/view-tracker.component.scss @@ -0,0 +1,3 @@ +:host { + display: none +} diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.ts b/src/app/statistics/angulartics/dspace/view-tracker.component.ts new file mode 100644 index 0000000000..1151287ea8 --- /dev/null +++ b/src/app/statistics/angulartics/dspace/view-tracker.component.ts @@ -0,0 +1,27 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Angulartics2 } from 'angulartics2'; +import { DSpaceObject } from '../../../core/shared/dspace-object.model'; + +/** + * This component triggers a page view statistic + */ +@Component({ + selector: 'ds-view-tracker', + styleUrls: ['./view-tracker.component.scss'], + templateUrl: './view-tracker.component.html', +}) +export class ViewTrackerComponent implements OnInit { + @Input() object:DSpaceObject; + + constructor( + public angulartics2:Angulartics2 + ) { + } + + ngOnInit():void { + this.angulartics2.eventTrack.next({ + action: 'pageView', + properties: {object: this.object}, + }); + } +} diff --git a/src/app/statistics/statistics.module.ts b/src/app/statistics/statistics.module.ts new file mode 100644 index 0000000000..a67ff7613c --- /dev/null +++ b/src/app/statistics/statistics.module.ts @@ -0,0 +1,36 @@ +import { ModuleWithProviders, NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { CoreModule } from '../core/core.module'; +import { SharedModule } from '../shared/shared.module'; +import { ViewTrackerComponent } from './angulartics/dspace/view-tracker.component'; +import { StatisticsService } from './statistics.service'; + +@NgModule({ + imports: [ + CommonModule, + CoreModule.forRoot(), + SharedModule, + ], + declarations: [ + ViewTrackerComponent, + ], + exports: [ + ViewTrackerComponent, + ], + providers: [ + StatisticsService + ] +}) +/** + * This module handles the statistics + */ +export class StatisticsModule { + static forRoot():ModuleWithProviders { + return { + ngModule: StatisticsModule, + providers: [ + StatisticsService + ] + }; + } +} diff --git a/src/app/statistics/statistics.service.spec.ts b/src/app/statistics/statistics.service.spec.ts new file mode 100644 index 0000000000..c6cc4c10b5 --- /dev/null +++ b/src/app/statistics/statistics.service.spec.ts @@ -0,0 +1,144 @@ +import { StatisticsService } from './statistics.service'; +import { RequestService } from '../core/data/request.service'; +import { HALEndpointServiceStub } from '../shared/testing/hal-endpoint-service-stub'; +import { getMockRequestService } from '../shared/mocks/mock-request.service'; +import { TrackRequest } from './track-request.model'; +import { isEqual } from 'lodash'; +import { DSpaceObjectType } from '../core/shared/dspace-object-type.model'; +import { SearchOptions } from '../shared/search/search-options.model'; + +describe('StatisticsService', () => { + let service:StatisticsService; + let requestService:jasmine.SpyObj; + const restURL = 'https://rest.api'; + const halService:any = new HALEndpointServiceStub(restURL); + + function initTestService() { + return new StatisticsService( + requestService, + halService, + ); + } + + describe('trackViewEvent', () => { + requestService = getMockRequestService(); + service = initTestService(); + + it('should send a request to track an item view ', () => { + const mockItem:any = {uuid: 'mock-item-uuid', type: 'item'}; + service.trackViewEvent(mockItem); + const request:TrackRequest = requestService.configure.calls.mostRecent().args[0]; + expect(request.body).toBeDefined('request.body'); + const body = JSON.parse(request.body); + expect(body.targetId).toBe('mock-item-uuid'); + expect(body.targetType).toBe('item'); + }); + }); + + describe('trackSearchEvent', () => { + requestService = getMockRequestService(); + service = initTestService(); + + const mockSearch:any = new SearchOptions({ + query: 'mock-query', + }); + + const page = { + size: 10, + totalElements: 248, + totalPages: 25, + number: 4 + }; + const sort = {by: 'search-field', order: 'ASC'}; + service.trackSearchEvent(mockSearch, page, sort); + const request:TrackRequest = requestService.configure.calls.mostRecent().args[0]; + const body = JSON.parse(request.body); + + it('should specify the right query', () => { + expect(body.query).toBe('mock-query'); + }); + + it('should specify the pagination info', () => { + expect(body.page).toEqual({ + size: 10, + totalElements: 248, + totalPages: 25, + number: 4 + }); + }); + + it('should specify the sort options', () => { + expect(body.sort).toEqual({ + by: 'search-field', + order: 'asc' + }); + }); + }); + + describe('trackSearchEvent with optional parameters', () => { + requestService = getMockRequestService(); + service = initTestService(); + + const mockSearch:any = new SearchOptions({ + query: 'mock-query', + configuration: 'mock-configuration', + dsoType: DSpaceObjectType.ITEM, + scope: 'mock-scope' + }); + + const page = { + size: 10, + totalElements: 248, + totalPages: 25, + number: 4 + }; + const sort = {by: 'search-field', order: 'ASC'}; + const filters = [ + { + filter: 'title', + operator: 'notcontains', + value: 'dolor sit', + label: 'dolor sit' + }, + { + filter: 'author', + operator: 'authority', + value: '9zvxzdm4qru17or5a83wfgac', + label: 'Amet, Consectetur' + } + ]; + service.trackSearchEvent(mockSearch, page, sort, filters); + const request:TrackRequest = requestService.configure.calls.mostRecent().args[0]; + const body = JSON.parse(request.body); + + it('should specify the dsoType', () => { + expect(body.dsoType).toBe('item'); + }); + + it('should specify the scope', () => { + expect(body.scope).toBe('mock-scope'); + }); + + it('should specify the configuration', () => { + expect(body.configuration).toBe('mock-configuration'); + }); + + it('should specify the filters', () => { + expect(isEqual(body.appliedFilters, [ + { + filter: 'title', + operator: 'notcontains', + value: 'dolor sit', + label: 'dolor sit' + }, + { + filter: 'author', + operator: 'authority', + value: '9zvxzdm4qru17or5a83wfgac', + label: 'Amet, Consectetur' + } + ])).toBe(true); + }); + }); + +}); diff --git a/src/app/statistics/statistics.service.ts b/src/app/statistics/statistics.service.ts new file mode 100644 index 0000000000..ae9118e1b7 --- /dev/null +++ b/src/app/statistics/statistics.service.ts @@ -0,0 +1,93 @@ +import { RequestService } from '../core/data/request.service'; +import { Injectable } from '@angular/core'; +import { DSpaceObject } from '../core/shared/dspace-object.model'; +import { map, take } from 'rxjs/operators'; +import { TrackRequest } from './track-request.model'; +import { hasValue, isNotEmpty } from '../shared/empty.util'; +import { HALEndpointService } from '../core/shared/hal-endpoint.service'; +import { RestRequest } from '../core/data/request.models'; +import { SearchOptions } from '../shared/search/search-options.model'; + +/** + * The statistics service + */ +@Injectable() +export class StatisticsService { + + constructor( + protected requestService:RequestService, + protected halService:HALEndpointService, + ) { + } + + private sendEvent(linkPath:string, body:any) { + const requestId = this.requestService.generateRequestId(); + this.halService.getEndpoint(linkPath).pipe( + map((endpoint:string) => new TrackRequest(requestId, endpoint, JSON.stringify(body))), + take(1) // otherwise the previous events will fire again + ).subscribe((request:RestRequest) => this.requestService.configure(request)); + } + + /** + * To track a page view + * @param dso: The dso which was viewed + */ + trackViewEvent(dso:DSpaceObject) { + this.sendEvent('/statistics/viewevents', { + targetId: dso.uuid, + targetType: (dso as any).type + }); + } + + /** + * To track a search + * @param searchOptions: The query, scope, dsoType and configuration of the search. Filters from this object are ignored in favor of the filters parameter of this method. + * @param page: An object that describes the pagination status + * @param sort: An object that describes the sort status + * @param filters: An array of search filters used to filter the result set + */ + trackSearchEvent( + searchOptions:SearchOptions, + page:{ size:number, totalElements:number, totalPages:number, number:number }, + sort:{ by:string, order:string }, + filters?:Array<{ filter:string, operator:string, value:string, label:string }> + ) { + const body = { + query: searchOptions.query, + page: { + size: page.size, + totalElements: page.totalElements, + totalPages: page.totalPages, + number: page.number + }, + sort: { + by: sort.by, + order: sort.order.toLowerCase() + }, + }; + if (hasValue(searchOptions.configuration)) { + Object.assign(body, {configuration: searchOptions.configuration}) + } + if (hasValue(searchOptions.dsoType)) { + Object.assign(body, {dsoType: searchOptions.dsoType.toLowerCase()}) + } + if (hasValue(searchOptions.scope)) { + Object.assign(body, {scope: searchOptions.scope}) + } + if (isNotEmpty(filters)) { + const bodyFilters = []; + for (let i = 0, arrayLength = filters.length; i < arrayLength; i++) { + const filter = filters[i]; + bodyFilters.push({ + filter: filter.filter, + operator: filter.operator, + value: filter.value, + label: filter.label + }) + } + Object.assign(body, {appliedFilters: bodyFilters}) + } + this.sendEvent('/statistics/searchevents', body); + } + +} diff --git a/src/app/statistics/track-request.model.ts b/src/app/statistics/track-request.model.ts new file mode 100644 index 0000000000..df3e51c070 --- /dev/null +++ b/src/app/statistics/track-request.model.ts @@ -0,0 +1,4 @@ +import { PostRequest } from '../core/data/request.models'; + +export class TrackRequest extends PostRequest { +} diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts index e40ea82163..d318bfe687 100644 --- a/src/app/submission/form/collection/submission-form-collection.component.ts +++ b/src/app/submission/form/collection/submission-form-collection.component.ts @@ -16,7 +16,7 @@ import { SubmissionService } from '../../submission.service'; import { SubmissionObject } from '../../../core/submission/models/submission-object.model'; import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service'; import { CollectionDataService } from '../../../core/data/collection-data.service'; -import { FindAllOptions } from '../../../core/data/request.models'; +import { FindListOptions } from '../../../core/data/request.models'; /** * An interface to represent a collection entry @@ -185,7 +185,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit { map((collectionRD: RemoteData) => collectionRD.payload.name) ); - const findOptions: FindAllOptions = { + const findOptions: FindListOptions = { elementsPerPage: 1000 }; diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index ede2b53e74..87b830ee7d 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -21,6 +21,8 @@ import { AuthService } from '../../app/core/auth/auth.service'; import { Angulartics2Module } from 'angulartics2'; import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { SubmissionService } from '../../app/submission/submission.service'; +import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; +import { StatisticsModule } from '../../app/statistics/statistics.module'; export const REQ_KEY = makeStateKey('req'); @@ -47,7 +49,8 @@ export function getRequest(transferState: TransferState): any { preloadingStrategy: IdlePreload }), - Angulartics2Module.forRoot([Angulartics2GoogleAnalytics]), + StatisticsModule.forRoot(), + Angulartics2Module.forRoot([Angulartics2GoogleAnalytics, Angulartics2DSpace]), BrowserAnimationsModule, DSpaceBrowserTransferStateModule, TranslateModule.forRoot({ diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 02abf6449b..44b21859bd 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -22,6 +22,8 @@ import { Angulartics2GoogleAnalytics } from 'angulartics2/ga'; import { AngularticsMock } from '../../app/shared/mocks/mock-angulartics.service'; import { SubmissionService } from '../../app/submission/submission.service'; import { ServerSubmissionService } from '../../app/submission/server-submission.service'; +import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider'; +import { Angulartics2Module } from 'angulartics2'; export function createTranslateLoader() { return new TranslateJson5UniversalLoader('dist/assets/i18n/', '.json5'); @@ -45,6 +47,7 @@ export function createTranslateLoader() { deps: [] } }), + Angulartics2Module.forRoot([Angulartics2GoogleAnalytics, Angulartics2DSpace]), ServerModule, AppModule ], @@ -53,6 +56,10 @@ export function createTranslateLoader() { provide: Angulartics2GoogleAnalytics, useClass: AngularticsMock }, + { + provide: Angulartics2DSpace, + useClass: AngularticsMock + }, { provide: AuthService, useClass: ServerAuthService diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html index 99d92d2af8..907f70b941 100644 --- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html @@ -4,7 +4,7 @@ diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html similarity index 79% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html index 15529a1bd5..ee78d9c653 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html @@ -53,18 +53,6 @@
- - - -
+
+
+ + +
+
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss similarity index 86% rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss index 54651aede0..4a1d2516da 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss @@ -1,4 +1,4 @@ -@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss'; +@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss'; :host { > * { diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html index bb5cb1b787..1679f9354d 100644 --- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html @@ -79,7 +79,10 @@

{{"item.page.person.search.title" | translate}}

- - + +
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 028815d958..e63ae024ed 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -15,6 +15,9 @@ module.exports = (env) => { let copyWebpackOptions = [{ from: path.join(__dirname, '..', 'node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'), to: path.join('assets', 'fonts') + }, { + from: path.join(__dirname, '..', 'resources', 'fonts'), + to: path.join('assets', 'fonts') }, { from: path.join(__dirname, '..', 'resources', 'images'), to: path.join('assets', 'images') @@ -24,6 +27,15 @@ module.exports = (env) => { } ]; + const themeFonts = path.join(themePath, 'resources', 'fonts'); + if(theme && fs.existsSync(themeFonts)) { + copyWebpackOptions.push({ + from: themeFonts, + to: path.join('assets', 'fonts') , + force: true, + }); + } + const themeImages = path.join(themePath, 'resources', 'images'); if(theme && fs.existsSync(themeImages)) { copyWebpackOptions.push({ @@ -107,12 +119,6 @@ module.exports = (env) => { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { @@ -120,6 +126,12 @@ module.exports = (env) => { includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, { loader: 'sass-resources-loader', options: { @@ -145,23 +157,23 @@ module.exports = (env) => { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { sourceMap: true, includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } - } + }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, ] }, { - test: /\.html$/, + test: /\.(html|eot|ttf|otf|svg|woff|woff2)$/, loader: 'raw-loader' } ] diff --git a/webpack/webpack.test.js b/webpack/webpack.test.js index 83e6e44e79..de53de31c4 100644 --- a/webpack/webpack.test.js +++ b/webpack/webpack.test.js @@ -160,12 +160,6 @@ module.exports = function (env) { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { @@ -173,6 +167,12 @@ module.exports = function (env) { includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, { loader: 'sass-resources-loader', options: { @@ -198,19 +198,19 @@ module.exports = function (env) { sourceMap: true } }, - { - loader: 'resolve-url-loader', - options: { - sourceMap: true - } - }, { loader: 'sass-loader', options: { sourceMap: true, includePaths: [projectRoot('./'), path.join(themePath, 'styles')] } - } + }, + { + loader: 'resolve-url-loader', + options: { + sourceMap: true + } + }, ] }, diff --git a/yarn.lock b/yarn.lock index 26480cccca..b4ec416395 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,13 @@ dependencies: tslib "^1.9.0" +"@angular/cdk@^6.4.7": + version "6.4.7" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-6.4.7.tgz#1549b304dd412e82bd854cc55a7d5c6772ee0411" + integrity sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg== + dependencies: + tslib "^1.7.1" + "@angular/cli@^6.1.5": version "6.1.5" resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.1.5.tgz#312c062631285ff06fd07ecde8afe22cdef5a0e1" @@ -2093,15 +2100,14 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" -clone-deep@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713" - integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ== +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== dependencies: - for-own "^1.0.0" is-plain-object "^2.0.4" - kind-of "^6.0.0" - shallow-clone "^1.0.0" + kind-of "^6.0.2" + shallow-clone "^3.0.0" clone-stats@^0.0.1: version "0.0.1" @@ -4121,11 +4127,6 @@ font-awesome@4.7.0: resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM= -for-in@^0.1.3: - version "0.1.8" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" - integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE= - for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -4138,13 +4139,6 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs= - dependencies: - for-in "^1.0.1" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -6148,7 +6142,7 @@ loader-utils@^0.2.12, loader-utils@^0.2.15, loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" -loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0= @@ -6157,7 +6151,7 @@ loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1 emojis-list "^2.0.0" json5 "^0.5.0" -loader-utils@^1.0.4: +loader-utils@^1.0.1, loader-utils@^1.0.4: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" dependencies: @@ -6371,11 +6365,6 @@ lodash.startswith@^4.2.1: resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c" integrity sha1-xZjErc4YiiflMUVzHNxsDnF3YAw= -lodash.tail@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" - integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ= - lodash.template@^3.0.0: version "3.6.2" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" @@ -6849,14 +6838,6 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mixin-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e" - integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4= - dependencies: - for-in "^0.1.3" - is-extendable "^0.1.1" - mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -9659,17 +9640,16 @@ sass-graph@^2.2.4: scss-tokenizer "^0.2.3" yargs "^7.0.0" -sass-loader@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d" - integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w== +sass-loader@^7.1.0: + version "7.3.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f" + integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA== dependencies: - clone-deep "^2.0.1" + clone-deep "^4.0.1" loader-utils "^1.0.1" - lodash.tail "^4.1.1" neo-async "^2.5.0" - pify "^3.0.0" - semver "^5.5.0" + pify "^4.0.1" + semver "^6.3.0" sass-resources-loader@^2.0.0: version "2.0.0" @@ -9774,7 +9754,7 @@ semver-intersect@^1.1.2: dependencies: semver "^5.0.0" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: +"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== @@ -9784,7 +9764,12 @@ semver@^5.0.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== -semver@^6.1.1: +semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^6.1.1, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -9915,14 +9900,12 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallow-clone@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571" - integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA== +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== dependencies: - is-extendable "^0.1.1" - kind-of "^5.0.0" - mixin-object "^2.0.1" + kind-of "^6.0.2" shebang-command@^1.2.0: version "1.2.0" @@ -10838,6 +10821,11 @@ tsickle@^0.32.1: source-map "^0.6.0" source-map-support "^0.5.0" +tslib@^1.7.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" + integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== + tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"