mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
@@ -20,6 +20,8 @@ import {
|
|||||||
COLLECTION_CREATE_PATH
|
COLLECTION_CREATE_PATH
|
||||||
} from './collection-page-routing-paths';
|
} from './collection-page-routing-paths';
|
||||||
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard';
|
||||||
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -69,7 +71,21 @@ import { CollectionPageAdministratorGuard } from './collection-page-administrato
|
|||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
canActivate: [AuthenticatedGuard]
|
canActivate: [AuthenticatedGuard]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
data: {
|
||||||
|
menu: {
|
||||||
|
public: [{
|
||||||
|
id: 'statistics_collection_:id',
|
||||||
|
active: true,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.statistics',
|
||||||
|
link: 'statistics/collections/:id/',
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -12,6 +12,8 @@ import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.servi
|
|||||||
import { LinkService } from '../core/cache/builders/link.service';
|
import { LinkService } from '../core/cache/builders/link.service';
|
||||||
import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-routing-paths';
|
import { COMMUNITY_EDIT_PATH, COMMUNITY_CREATE_PATH } from './community-page-routing-paths';
|
||||||
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
|
import { CommunityPageAdministratorGuard } from './community-page-administrator.guard';
|
||||||
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -45,7 +47,21 @@ import { CommunityPageAdministratorGuard } from './community-page-administrator.
|
|||||||
component: CommunityPageComponent,
|
component: CommunityPageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
data: {
|
||||||
|
menu: {
|
||||||
|
public: [{
|
||||||
|
id: 'statistics_community_:id',
|
||||||
|
active: true,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.statistics',
|
||||||
|
link: 'statistics/communities/:id/',
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -3,6 +3,8 @@ import { RouterModule } from '@angular/router';
|
|||||||
|
|
||||||
import { HomePageComponent } from './home-page.component';
|
import { HomePageComponent } from './home-page.component';
|
||||||
import { HomePageResolver } from './home-page.resolver';
|
import { HomePageResolver } from './home-page.resolver';
|
||||||
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -11,7 +13,21 @@ import { HomePageResolver } from './home-page.resolver';
|
|||||||
path: '',
|
path: '',
|
||||||
component: HomePageComponent,
|
component: HomePageComponent,
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
data: {title: 'home.title'},
|
data: {
|
||||||
|
title: 'home.title',
|
||||||
|
menu: {
|
||||||
|
public: [{
|
||||||
|
id: 'statistics_site',
|
||||||
|
active: true,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.statistics',
|
||||||
|
link: 'statistics',
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
site: HomePageResolver
|
site: HomePageResolver
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,8 @@ import { LinkService } from '../core/cache/builders/link.service';
|
|||||||
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
|
||||||
import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths';
|
import { UPLOAD_BITSTREAM_PATH, ITEM_EDIT_PATH } from './item-page-routing-paths';
|
||||||
import { ItemPageAdministratorGuard } from './item-page-administrator.guard';
|
import { ItemPageAdministratorGuard } from './item-page-administrator.guard';
|
||||||
|
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||||
|
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -43,6 +45,20 @@ import { ItemPageAdministratorGuard } from './item-page-administrator.guard';
|
|||||||
canActivate: [AuthenticatedGuard]
|
canActivate: [AuthenticatedGuard]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
data: {
|
||||||
|
menu: {
|
||||||
|
public: [{
|
||||||
|
id: 'statistics_item_:id',
|
||||||
|
active: true,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.statistics',
|
||||||
|
link: 'statistics/items/:id/',
|
||||||
|
} as LinkMenuItemModel,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
|
@@ -69,6 +69,10 @@ import { SiteRegisterGuard } from './core/data/feature-authorization/feature-aut
|
|||||||
{ path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] },
|
{ path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] },
|
||||||
{ path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
|
{ path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
|
||||||
{ path: UNAUTHORIZED_PATH, component: UnauthorizedComponent },
|
{ path: UNAUTHORIZED_PATH, component: UnauthorizedComponent },
|
||||||
|
{
|
||||||
|
path: 'statistics',
|
||||||
|
loadChildren: './statistics-page/statistics-page-routing.module#StatisticsPageRoutingModule',
|
||||||
|
},
|
||||||
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
|
||||||
]}
|
]}
|
||||||
],
|
],
|
||||||
|
@@ -171,6 +171,7 @@ import { EndUserAgreementCurrentUserGuard } from './end-user-agreement/end-user-
|
|||||||
import { EndUserAgreementCookieGuard } from './end-user-agreement/end-user-agreement-cookie.guard';
|
import { EndUserAgreementCookieGuard } from './end-user-agreement/end-user-agreement-cookie.guard';
|
||||||
import { EndUserAgreementService } from './end-user-agreement/end-user-agreement.service';
|
import { EndUserAgreementService } from './end-user-agreement/end-user-agreement.service';
|
||||||
import { SiteRegisterGuard } from './data/feature-authorization/feature-authorization-guard/site-register.guard';
|
import { SiteRegisterGuard } from './data/feature-authorization/feature-authorization-guard/site-register.guard';
|
||||||
|
import { UsageReport } from './statistics/models/usage-report.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -371,7 +372,8 @@ export const models =
|
|||||||
Vocabulary,
|
Vocabulary,
|
||||||
VocabularyEntry,
|
VocabularyEntry,
|
||||||
VocabularyEntryDetail,
|
VocabularyEntryDetail,
|
||||||
ConfigurationProperty
|
ConfigurationProperty,
|
||||||
|
UsageReport,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
51
src/app/core/statistics/models/usage-report.model.ts
Normal file
51
src/app/core/statistics/models/usage-report.model.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { autoserialize, inheritSerialization } from 'cerialize';
|
||||||
|
import { typedObject } from '../../cache/builders/build-decorators';
|
||||||
|
import { excludeFromEquals } from '../../utilities/equals.decorators';
|
||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
import { HALResource } from '../../shared/hal-resource.model';
|
||||||
|
import { USAGE_REPORT } from './usage-report.resource-type';
|
||||||
|
import { HALLink } from '../../shared/hal-link.model';
|
||||||
|
import { deserialize, autoserializeAs } from 'cerialize';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A usage report.
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
@inheritSerialization(HALResource)
|
||||||
|
export class UsageReport extends HALResource {
|
||||||
|
|
||||||
|
static type = USAGE_REPORT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object type
|
||||||
|
*/
|
||||||
|
@excludeFromEquals
|
||||||
|
@autoserialize
|
||||||
|
type: ResourceType;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@autoserializeAs('report-type')
|
||||||
|
reportType: string;
|
||||||
|
|
||||||
|
@autoserialize
|
||||||
|
points: Point[];
|
||||||
|
|
||||||
|
@deserialize
|
||||||
|
_links: {
|
||||||
|
self: HALLink;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A statistics data point.
|
||||||
|
*/
|
||||||
|
export interface Point {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
type: string;
|
||||||
|
values: Array<{
|
||||||
|
views: number;
|
||||||
|
}>;
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for License
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const USAGE_REPORT = new ResourceType('usagereport');
|
62
src/app/core/statistics/usage-report-data.service.ts
Normal file
62
src/app/core/statistics/usage-report-data.service.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
|
import { CoreState } from '../core.reducers';
|
||||||
|
import { HALEndpointService } from '../shared/hal-endpoint.service';
|
||||||
|
import { DataService } from '../data/data.service';
|
||||||
|
import { RequestService } from '../data/request.service';
|
||||||
|
import { DefaultChangeAnalyzer } from '../data/default-change-analyzer.service';
|
||||||
|
import { USAGE_REPORT } from './models/usage-report.resource-type';
|
||||||
|
import { UsageReport } from './models/usage-report.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../shared/operators';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service to retrieve {@link UsageReport}s from the REST API
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
@dataService(USAGE_REPORT)
|
||||||
|
export class UsageReportService extends DataService<UsageReport> {
|
||||||
|
|
||||||
|
protected linkPath = 'statistics/usagereports';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected comparator: DefaultChangeAnalyzer<UsageReport>,
|
||||||
|
protected halService: HALEndpointService,
|
||||||
|
protected http: HttpClient,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
protected objectCache: ObjectCacheService,
|
||||||
|
protected rdbService: RemoteDataBuildService,
|
||||||
|
protected requestService: RequestService,
|
||||||
|
protected store: Store<CoreState>,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getStatistic(scope: string, type: string): Observable<UsageReport> {
|
||||||
|
return this.findById(`${scope}_${type}`).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
searchStatistics(uri: string, page: number, size: number): Observable<UsageReport[]> {
|
||||||
|
return this.searchBy('object', {
|
||||||
|
searchParams: [{
|
||||||
|
fieldName: `uri`,
|
||||||
|
fieldValue: uri,
|
||||||
|
}],
|
||||||
|
currentPage: page,
|
||||||
|
elementsPerPage: size,
|
||||||
|
}).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((list) => list.page),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -64,19 +64,6 @@ export class NavbarComponent extends MenuComponent {
|
|||||||
link: `/community-list`
|
link: `/community-list`
|
||||||
} as LinkMenuItemModel
|
} as LinkMenuItemModel
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Statistics */
|
|
||||||
{
|
|
||||||
id: 'statistics',
|
|
||||||
active: false,
|
|
||||||
visible: true,
|
|
||||||
model: {
|
|
||||||
type: MenuItemType.LINK,
|
|
||||||
text: 'menu.section.statistics',
|
|
||||||
link: ''
|
|
||||||
} as LinkMenuItemModel,
|
|
||||||
index: 2
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
// Read the different Browse-By types from config and add them to the browse menu
|
// Read the different Browse-By types from config and add them to the browse menu
|
||||||
const types = environment.browseBy.types;
|
const types = environment.browseBy.types;
|
||||||
|
@@ -14,6 +14,7 @@ import { MenuEffects } from './menu.effects';
|
|||||||
describe('MenuEffects', () => {
|
describe('MenuEffects', () => {
|
||||||
let menuEffects: MenuEffects;
|
let menuEffects: MenuEffects;
|
||||||
let routeDataMenuSection: MenuSection;
|
let routeDataMenuSection: MenuSection;
|
||||||
|
let routeDataMenuSectionResolved: MenuSection;
|
||||||
let routeDataMenuChildSection: MenuSection;
|
let routeDataMenuChildSection: MenuSection;
|
||||||
let toBeRemovedMenuSection: MenuSection;
|
let toBeRemovedMenuSection: MenuSection;
|
||||||
let alreadyPresentMenuSection: MenuSection;
|
let alreadyPresentMenuSection: MenuSection;
|
||||||
@@ -23,13 +24,23 @@ describe('MenuEffects', () => {
|
|||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
routeDataMenuSection = {
|
routeDataMenuSection = {
|
||||||
id: 'mockSection',
|
id: 'mockSection_:idparam',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.mockSection',
|
text: 'menu.section.mockSection',
|
||||||
link: ''
|
link: 'path/:linkparam'
|
||||||
|
} as LinkMenuItemModel
|
||||||
|
};
|
||||||
|
routeDataMenuSectionResolved = {
|
||||||
|
id: 'mockSection_id_param_resolved',
|
||||||
|
active: false,
|
||||||
|
visible: true,
|
||||||
|
model: {
|
||||||
|
type: MenuItemType.LINK,
|
||||||
|
text: 'menu.section.mockSection',
|
||||||
|
link: 'path/link_param_resolved'
|
||||||
} as LinkMenuItemModel
|
} as LinkMenuItemModel
|
||||||
};
|
};
|
||||||
routeDataMenuChildSection = {
|
routeDataMenuChildSection = {
|
||||||
@@ -70,6 +81,10 @@ describe('MenuEffects', () => {
|
|||||||
menu: {
|
menu: {
|
||||||
[MenuID.PUBLIC]: [routeDataMenuSection, alreadyPresentMenuSection]
|
[MenuID.PUBLIC]: [routeDataMenuSection, alreadyPresentMenuSection]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
idparam: 'id_param_resolved',
|
||||||
|
linkparam: 'link_param_resolved',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
firstChild: {
|
firstChild: {
|
||||||
@@ -120,7 +135,7 @@ describe('MenuEffects', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(menuEffects.buildRouteMenuSections$).toBeObservable(expected);
|
expect(menuEffects.buildRouteMenuSections$).toBeObservable(expected);
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, routeDataMenuSection);
|
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, routeDataMenuSectionResolved);
|
||||||
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, routeDataMenuChildSection);
|
expect(menuService.addSection).toHaveBeenCalledWith(MenuID.PUBLIC, routeDataMenuChildSection);
|
||||||
expect(menuService.addSection).not.toHaveBeenCalledWith(MenuID.PUBLIC, alreadyPresentMenuSection);
|
expect(menuService.addSection).not.toHaveBeenCalledWith(MenuID.PUBLIC, alreadyPresentMenuSection);
|
||||||
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, toBeRemovedMenuSection.id);
|
expect(menuService.removeSection).toHaveBeenCalledWith(MenuID.PUBLIC, toBeRemovedMenuSection.id);
|
||||||
|
@@ -19,7 +19,7 @@ export class MenuEffects {
|
|||||||
/**
|
/**
|
||||||
* On route change, build menu sections for every menu type depending on the current route data
|
* On route change, build menu sections for every menu type depending on the current route data
|
||||||
*/
|
*/
|
||||||
@Effect({ dispatch: false })
|
@Effect({dispatch: false})
|
||||||
public buildRouteMenuSections$: Observable<Action> = this.actions$
|
public buildRouteMenuSections$: Observable<Action> = this.actions$
|
||||||
.pipe(
|
.pipe(
|
||||||
ofType(ROUTER_NAVIGATED),
|
ofType(ROUTER_NAVIGATED),
|
||||||
@@ -68,17 +68,52 @@ export class MenuEffects {
|
|||||||
*/
|
*/
|
||||||
resolveRouteMenuSections(route: ActivatedRoute, menuID: MenuID): MenuSection[] {
|
resolveRouteMenuSections(route: ActivatedRoute, menuID: MenuID): MenuSection[] {
|
||||||
const data = route.snapshot.data;
|
const data = route.snapshot.data;
|
||||||
|
const params = route.snapshot.params;
|
||||||
const last: boolean = hasNoValue(route.firstChild);
|
const last: boolean = hasNoValue(route.firstChild);
|
||||||
|
|
||||||
if (hasValue(data) && hasValue(data.menu) && hasValue(data.menu[menuID])) {
|
if (hasValue(data) && hasValue(data.menu) && hasValue(data.menu[menuID])) {
|
||||||
|
|
||||||
|
let menuSections: MenuSection[] | MenuSection = data.menu[menuID];
|
||||||
|
menuSections = this.resolveSubstitutions(menuSections, params);
|
||||||
|
|
||||||
if (!last) {
|
if (!last) {
|
||||||
return [...data.menu[menuID], ...this.resolveRouteMenuSections(route.firstChild, menuID)]
|
return [...menuSections, ...this.resolveRouteMenuSections(route.firstChild, menuID)]
|
||||||
} else {
|
} else {
|
||||||
return [...data.menu[menuID]];
|
return [...menuSections];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return !last ? this.resolveRouteMenuSections(route.firstChild, menuID) : [];
|
return !last ? this.resolveRouteMenuSections(route.firstChild, menuID) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private resolveSubstitutions(object, params) {
|
||||||
|
|
||||||
|
let resolved;
|
||||||
|
if (typeof object === 'string') {
|
||||||
|
resolved = object;
|
||||||
|
let match: RegExpMatchArray;
|
||||||
|
do {
|
||||||
|
match = resolved.match(/:(\w+)/);
|
||||||
|
if (match) {
|
||||||
|
const substitute = params[match[1]];
|
||||||
|
if (hasValue(substitute)) {
|
||||||
|
resolved = resolved.replace(match[0], `${substitute}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (match);
|
||||||
|
} else if (Array.isArray(object)) {
|
||||||
|
resolved = [];
|
||||||
|
object.forEach((entry, index) => {
|
||||||
|
resolved[index] = this.resolveSubstitutions(object[index], params);
|
||||||
|
});
|
||||||
|
} else if (typeof object === 'object') {
|
||||||
|
resolved = {};
|
||||||
|
Object.keys(object).forEach((key) => {
|
||||||
|
resolved[key] = this.resolveSubstitutions(object[key], params);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolved = object;
|
||||||
|
}
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,109 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { CollectionStatisticsPageComponent } from './collection-statistics-page.component';
|
||||||
|
import { StatisticsTableComponent } from '../statistics-table/statistics-table.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
|
||||||
|
describe('CollectionStatisticsPageComponent', () => {
|
||||||
|
|
||||||
|
let component: CollectionStatisticsPageComponent;
|
||||||
|
let de: DebugElement;
|
||||||
|
let fixture: ComponentFixture<CollectionStatisticsPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
const activatedRoute = {
|
||||||
|
data: observableOf({
|
||||||
|
scope: new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
Object.assign(new Collection(), {
|
||||||
|
id: 'collection_id',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
};
|
||||||
|
|
||||||
|
const usageReportService = {
|
||||||
|
getStatistic: (scope, type) => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(usageReportService, 'getStatistic').and.callFake(
|
||||||
|
(scope, type) => observableOf(
|
||||||
|
Object.assign(
|
||||||
|
new UsageReport(), {
|
||||||
|
id: `${scope}-${type}-report`,
|
||||||
|
points: [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const nameService = {
|
||||||
|
getName: () => observableOf('test dso name'),
|
||||||
|
};
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CollectionStatisticsPageComponent,
|
||||||
|
StatisticsTableComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: UsageReportService, useValue: usageReportService },
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useValue: nameService },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CollectionStatisticsPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
de = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve to the correct collection', () => {
|
||||||
|
expect(de.query(By.css('.header')).nativeElement.id)
|
||||||
|
.toEqual('collection_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a statistics table for each usage report', () => {
|
||||||
|
expect(de.query(By.css('ds-statistics-table.collection_id-TotalVisits-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.collection_id-TotalVisitsPerMonth-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.collection_id-TopCountries-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.collection_id-TopCities-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { StatisticsPageComponent } from '../statistics-page/statistics-page.component';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { ActivatedRoute , Router} from '@angular/router';
|
||||||
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing the statistics page for a collection.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-statistics-page',
|
||||||
|
templateUrl: '../statistics-page/statistics-page.component.html',
|
||||||
|
styleUrls: ['./collection-statistics-page.component.scss']
|
||||||
|
})
|
||||||
|
export class CollectionStatisticsPageComponent extends StatisticsPageComponent<Collection> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The report types to show on this statistics page.
|
||||||
|
*/
|
||||||
|
types: string[] = [
|
||||||
|
'TotalVisits',
|
||||||
|
'TotalVisitsPerMonth',
|
||||||
|
'TopCountries',
|
||||||
|
'TopCities',
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected usageReportService: UsageReportService,
|
||||||
|
protected nameService: DSONameService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
route,
|
||||||
|
router,
|
||||||
|
usageReportService,
|
||||||
|
nameService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,109 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { CommunityStatisticsPageComponent } from './community-statistics-page.component';
|
||||||
|
import { StatisticsTableComponent } from '../statistics-table/statistics-table.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
|
||||||
|
describe('CommunityStatisticsPageComponent', () => {
|
||||||
|
|
||||||
|
let component: CommunityStatisticsPageComponent;
|
||||||
|
let de: DebugElement;
|
||||||
|
let fixture: ComponentFixture<CommunityStatisticsPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
const activatedRoute = {
|
||||||
|
data: observableOf({
|
||||||
|
scope: new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
Object.assign(new Community(), {
|
||||||
|
id: 'community_id',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
};
|
||||||
|
|
||||||
|
const usageReportService = {
|
||||||
|
getStatistic: (scope, type) => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(usageReportService, 'getStatistic').and.callFake(
|
||||||
|
(scope, type) => observableOf(
|
||||||
|
Object.assign(
|
||||||
|
new UsageReport(), {
|
||||||
|
id: `${scope}-${type}-report`,
|
||||||
|
points: [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const nameService = {
|
||||||
|
getName: () => observableOf('test dso name'),
|
||||||
|
};
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CommunityStatisticsPageComponent,
|
||||||
|
StatisticsTableComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: UsageReportService, useValue: usageReportService },
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useValue: nameService },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(CommunityStatisticsPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
de = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve to the correct community', () => {
|
||||||
|
expect(de.query(By.css('.header')).nativeElement.id)
|
||||||
|
.toEqual('community_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a statistics table for each usage report', () => {
|
||||||
|
expect(de.query(By.css('ds-statistics-table.community_id-TotalVisits-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.community_id-TotalVisitsPerMonth-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.community_id-TopCountries-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.community_id-TopCities-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { StatisticsPageComponent } from '../statistics-page/statistics-page.component';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Community } from '../../core/shared/community.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing the statistics page for a community.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-community-statistics-page',
|
||||||
|
templateUrl: '../statistics-page/statistics-page.component.html',
|
||||||
|
styleUrls: ['./community-statistics-page.component.scss']
|
||||||
|
})
|
||||||
|
export class CommunityStatisticsPageComponent extends StatisticsPageComponent<Community> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The report types to show on this statistics page.
|
||||||
|
*/
|
||||||
|
types: string[] = [
|
||||||
|
'TotalVisits',
|
||||||
|
'TotalVisitsPerMonth',
|
||||||
|
'TopCountries',
|
||||||
|
'TopCities',
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected usageReportService: UsageReportService,
|
||||||
|
protected nameService: DSONameService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
route,
|
||||||
|
router,
|
||||||
|
usageReportService,
|
||||||
|
nameService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,111 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ItemStatisticsPageComponent } from './item-statistics-page.component';
|
||||||
|
import { StatisticsTableComponent } from '../statistics-table/statistics-table.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
|
||||||
|
describe('ItemStatisticsPageComponent', () => {
|
||||||
|
|
||||||
|
let component: ItemStatisticsPageComponent;
|
||||||
|
let de: DebugElement;
|
||||||
|
let fixture: ComponentFixture<ItemStatisticsPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
const activatedRoute = {
|
||||||
|
data: observableOf({
|
||||||
|
scope: new RemoteData(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
Object.assign(new Item(), {
|
||||||
|
id: 'item_id',
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
};
|
||||||
|
|
||||||
|
const usageReportService = {
|
||||||
|
getStatistic: (scope, type) => undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
spyOn(usageReportService, 'getStatistic').and.callFake(
|
||||||
|
(scope, type) => observableOf(
|
||||||
|
Object.assign(
|
||||||
|
new UsageReport(), {
|
||||||
|
id: `${scope}-${type}-report`,
|
||||||
|
points: [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const nameService = {
|
||||||
|
getName: () => observableOf('test dso name'),
|
||||||
|
};
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ItemStatisticsPageComponent,
|
||||||
|
StatisticsTableComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: UsageReportService, useValue: usageReportService },
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useValue: nameService },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemStatisticsPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
de = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve to the correct item', () => {
|
||||||
|
expect(de.query(By.css('.header')).nativeElement.id)
|
||||||
|
.toEqual('item_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a statistics table for each usage report', () => {
|
||||||
|
expect(de.query(By.css('ds-statistics-table.item_id-TotalVisits-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.item_id-TotalVisitsPerMonth-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.item_id-TotalDownloads-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.item_id-TopCountries-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
expect(de.query(By.css('ds-statistics-table.item_id-TopCities-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,42 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { StatisticsPageComponent } from '../statistics-page/statistics-page.component';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing the statistics page for an item.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item-statistics-page',
|
||||||
|
templateUrl: '../statistics-page/statistics-page.component.html',
|
||||||
|
styleUrls: ['./item-statistics-page.component.scss']
|
||||||
|
})
|
||||||
|
export class ItemStatisticsPageComponent extends StatisticsPageComponent<Item> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The report types to show on this statistics page.
|
||||||
|
*/
|
||||||
|
types: string[] = [
|
||||||
|
'TotalVisits',
|
||||||
|
'TotalVisitsPerMonth',
|
||||||
|
'TotalDownloads',
|
||||||
|
'TopCountries',
|
||||||
|
'TopCities',
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected usageReportService: UsageReportService,
|
||||||
|
protected nameService: DSONameService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
route,
|
||||||
|
router,
|
||||||
|
usageReportService,
|
||||||
|
nameService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,100 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { SiteStatisticsPageComponent } from './site-statistics-page.component';
|
||||||
|
import { StatisticsTableComponent } from '../statistics-table/statistics-table.component';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { Site } from '../../core/shared/site.model';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
import { SiteDataService } from '../../core/data/site-data.service';
|
||||||
|
|
||||||
|
describe('SiteStatisticsPageComponent', () => {
|
||||||
|
|
||||||
|
let component: SiteStatisticsPageComponent;
|
||||||
|
let de: DebugElement;
|
||||||
|
let fixture: ComponentFixture<SiteStatisticsPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
|
||||||
|
const activatedRoute = {
|
||||||
|
};
|
||||||
|
|
||||||
|
const router = {
|
||||||
|
};
|
||||||
|
|
||||||
|
const usageReportService = {
|
||||||
|
searchStatistics: () => observableOf([
|
||||||
|
Object.assign(
|
||||||
|
new UsageReport(), {
|
||||||
|
id: `site_id-TotalVisits-report`,
|
||||||
|
points: [],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const nameService = {
|
||||||
|
getName: () => observableOf('test dso name'),
|
||||||
|
};
|
||||||
|
|
||||||
|
const siteService = {
|
||||||
|
find: () => observableOf(Object.assign(new Site(), {
|
||||||
|
id: 'site_id',
|
||||||
|
_links: {
|
||||||
|
self: {
|
||||||
|
href: 'test_site_link',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
SiteStatisticsPageComponent,
|
||||||
|
StatisticsTableComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: UsageReportService, useValue: usageReportService },
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useValue: nameService },
|
||||||
|
{ provide: SiteDataService, useValue: siteService },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(SiteStatisticsPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
de = fixture.debugElement;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should resolve to the correct site', () => {
|
||||||
|
expect(de.query(By.css('.header')).nativeElement.id)
|
||||||
|
.toEqual('site_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show a statistics table for each usage report', () => {
|
||||||
|
expect(de.query(By.css('ds-statistics-table.site_id-TotalVisits-report')).nativeElement)
|
||||||
|
.toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { StatisticsPageComponent } from '../statistics-page/statistics-page.component';
|
||||||
|
import { SiteDataService } from '../../core/data/site-data.service';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Site } from '../../core/shared/site.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { switchMap } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing the site-wide statistics page.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-site-statistics-page',
|
||||||
|
templateUrl: '../statistics-page/statistics-page.component.html',
|
||||||
|
styleUrls: ['./site-statistics-page.component.scss']
|
||||||
|
})
|
||||||
|
export class SiteStatisticsPageComponent extends StatisticsPageComponent<Site> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The report types to show on this statistics page.
|
||||||
|
*/
|
||||||
|
types: string[] = [
|
||||||
|
'TotalVisits',
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected usageReportService: UsageReportService,
|
||||||
|
protected nameService: DSONameService,
|
||||||
|
protected siteService: SiteDataService,
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
route,
|
||||||
|
router,
|
||||||
|
usageReportService,
|
||||||
|
nameService,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getScope$() {
|
||||||
|
return this.siteService.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getReports$() {
|
||||||
|
return this.scope$.pipe(
|
||||||
|
switchMap((scope) =>
|
||||||
|
this.usageReportService.searchStatistics(scope._links.self.href, 0, 10),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
81
src/app/statistics-page/statistics-page-routing.module.ts
Normal file
81
src/app/statistics-page/statistics-page-routing.module.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
|
||||||
|
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
|
import { StatisticsPageModule } from './statistics-page.module';
|
||||||
|
import { SiteStatisticsPageComponent } from './site-statistics-page/site-statistics-page.component';
|
||||||
|
import { ItemPageResolver } from '../+item-page/item-page.resolver';
|
||||||
|
import { ItemStatisticsPageComponent } from './item-statistics-page/item-statistics-page.component';
|
||||||
|
import { CollectionPageResolver } from '../+collection-page/collection-page.resolver';
|
||||||
|
import { CollectionStatisticsPageComponent } from './collection-statistics-page/collection-statistics-page.component';
|
||||||
|
import { CommunityPageResolver } from '../+community-page/community-page.resolver';
|
||||||
|
import { CommunityStatisticsPageComponent } from './community-statistics-page/community-statistics-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
StatisticsPageModule,
|
||||||
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: I18nBreadcrumbResolver
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'statistics.title',
|
||||||
|
breadcrumbKey: 'statistics'
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: SiteStatisticsPageComponent,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `items/:id`,
|
||||||
|
resolve: {
|
||||||
|
scope: ItemPageResolver,
|
||||||
|
breadcrumb: I18nBreadcrumbResolver
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'statistics.title',
|
||||||
|
breadcrumbKey: 'statistics'
|
||||||
|
},
|
||||||
|
component: ItemStatisticsPageComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `collections/:id`,
|
||||||
|
resolve: {
|
||||||
|
scope: CollectionPageResolver,
|
||||||
|
breadcrumb: I18nBreadcrumbResolver
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'statistics.title',
|
||||||
|
breadcrumbKey: 'statistics'
|
||||||
|
},
|
||||||
|
component: CollectionStatisticsPageComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: `communities/:id`,
|
||||||
|
resolve: {
|
||||||
|
scope: CommunityPageResolver,
|
||||||
|
breadcrumb: I18nBreadcrumbResolver
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title: 'statistics.title',
|
||||||
|
breadcrumbKey: 'statistics'
|
||||||
|
},
|
||||||
|
component: CommunityStatisticsPageComponent,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
I18nBreadcrumbResolver,
|
||||||
|
I18nBreadcrumbsService,
|
||||||
|
CollectionPageResolver,
|
||||||
|
CommunityPageResolver,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class StatisticsPageRoutingModule {
|
||||||
|
}
|
39
src/app/statistics-page/statistics-page.module.ts
Normal file
39
src/app/statistics-page/statistics-page.module.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CoreModule } from '../core/core.module';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
|
import { UsageReportService } from '../core/statistics/usage-report-data.service';
|
||||||
|
import { SiteStatisticsPageComponent } from './site-statistics-page/site-statistics-page.component';
|
||||||
|
import { StatisticsTableComponent } from './statistics-table/statistics-table.component';
|
||||||
|
import { ItemStatisticsPageComponent } from './item-statistics-page/item-statistics-page.component';
|
||||||
|
import { CollectionStatisticsPageComponent } from './collection-statistics-page/collection-statistics-page.component';
|
||||||
|
import { CommunityStatisticsPageComponent } from './community-statistics-page/community-statistics-page.component';
|
||||||
|
|
||||||
|
const components = [
|
||||||
|
StatisticsTableComponent,
|
||||||
|
SiteStatisticsPageComponent,
|
||||||
|
ItemStatisticsPageComponent,
|
||||||
|
CollectionStatisticsPageComponent,
|
||||||
|
CommunityStatisticsPageComponent,
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
CoreModule.forRoot(),
|
||||||
|
StatisticsModule.forRoot()
|
||||||
|
],
|
||||||
|
declarations: components,
|
||||||
|
providers: [
|
||||||
|
UsageReportService,
|
||||||
|
],
|
||||||
|
exports: components
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module handles all components and pipes that are necessary for the search page
|
||||||
|
*/
|
||||||
|
export class StatisticsPageModule {
|
||||||
|
}
|
@@ -0,0 +1,29 @@
|
|||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<ng-container *ngVar="(scope$ | async) as scope">
|
||||||
|
<h2 *ngIf="scope"
|
||||||
|
class="header"
|
||||||
|
id="{{ scope.id }}">
|
||||||
|
{{ 'statistics.header' | translate: { scope: getName(scope) } }}
|
||||||
|
</h2>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngVar="reports$ | async as reports">
|
||||||
|
|
||||||
|
<ng-container *ngIf="!reports">
|
||||||
|
<ds-loading></ds-loading>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="reports">
|
||||||
|
<ds-statistics-table *ngFor="let report of reports"
|
||||||
|
[report]="report"
|
||||||
|
class="m-2 {{ report.id }}">
|
||||||
|
</ds-statistics-table>
|
||||||
|
<div *ngIf="!(hasData$ | async)">
|
||||||
|
{{ 'statistics.page.no-data' | translate }}
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1,84 @@
|
|||||||
|
import { OnInit } from '@angular/core';
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { UsageReportService } from '../../core/statistics/usage-report-data.service';
|
||||||
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
|
import { UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData, redirectToPageNotFoundOn404 } from '../../core/shared/operators';
|
||||||
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class representing an abstract statistics page component.
|
||||||
|
*/
|
||||||
|
export abstract class StatisticsPageComponent<T extends DSpaceObject> implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The scope dso for this statistics page, as an Observable.
|
||||||
|
*/
|
||||||
|
scope$: Observable<DSpaceObject>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The report types to show on this statistics page.
|
||||||
|
*/
|
||||||
|
types: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The usage report types to show on this statistics page, as an Observable list.
|
||||||
|
*/
|
||||||
|
reports$: Observable<UsageReport[]>;
|
||||||
|
|
||||||
|
hasData$: Observable<boolean>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected router: Router,
|
||||||
|
protected usageReportService: UsageReportService,
|
||||||
|
protected nameService: DSONameService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.scope$ = this.getScope$();
|
||||||
|
this.reports$ = this.getReports$();
|
||||||
|
this.hasData$ = this.reports$.pipe(
|
||||||
|
map((reports) => reports.some(
|
||||||
|
(report) => report.points.length > 0
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the scope dso for this statistics page, as an Observable.
|
||||||
|
*/
|
||||||
|
protected getScope$(): Observable<DSpaceObject> {
|
||||||
|
return this.route.data.pipe(
|
||||||
|
map((data) => data.scope as RemoteData<T>),
|
||||||
|
redirectToPageNotFoundOn404(this.router),
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the usage reports for this statistics page, as an Observable list
|
||||||
|
*/
|
||||||
|
protected getReports$(): Observable<UsageReport[]> {
|
||||||
|
return this.scope$.pipe(
|
||||||
|
switchMap((scope) =>
|
||||||
|
combineLatest(
|
||||||
|
this.types.map((type) => this.usageReportService.getStatistic(scope.id, type))
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the scope dso.
|
||||||
|
* @param scope the scope dso to get the name for
|
||||||
|
*/
|
||||||
|
getName(scope: DSpaceObject): string {
|
||||||
|
return this.nameService.getName(scope);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
<div *ngIf="hasData"
|
||||||
|
class="m-1">
|
||||||
|
|
||||||
|
<h3 class="m-1">
|
||||||
|
{{ 'statistics.table.title.' + report.reportType | translate }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<th scope="col"></th>
|
||||||
|
<th scope="col"
|
||||||
|
*ngFor="let header of headers"
|
||||||
|
class="{{header}}-header">
|
||||||
|
{{ header }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr *ngFor="let point of report.points"
|
||||||
|
class="{{point.id}}-data">
|
||||||
|
<th scope="row">
|
||||||
|
{{ getLabel(point) | async }}
|
||||||
|
</th>
|
||||||
|
<td *ngFor="let header of headers"
|
||||||
|
class="{{point.id}}-{{header}}-data">
|
||||||
|
{{ point.values[header] }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1,8 @@
|
|||||||
|
th, td {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
width: 50px;
|
||||||
|
max-width: 50px;
|
||||||
|
}
|
@@ -0,0 +1,98 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { StatisticsTableComponent } from './statistics-table.component';
|
||||||
|
import { UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { DebugElement } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
describe('StatisticsTableComponent', () => {
|
||||||
|
|
||||||
|
let component: StatisticsTableComponent;
|
||||||
|
let de: DebugElement;
|
||||||
|
let fixture: ComponentFixture<StatisticsTableComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
StatisticsTableComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: DSpaceObjectDataService, useValue: {} },
|
||||||
|
{ provide: DSONameService, useValue: {} },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(StatisticsTableComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
de = fixture.debugElement;
|
||||||
|
component.report = Object.assign(new UsageReport(), {
|
||||||
|
points: [],
|
||||||
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the storage report is empty', () => {
|
||||||
|
|
||||||
|
it ('should not display a table', () => {
|
||||||
|
expect(de.query(By.css('table'))).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the storage report has data', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
component.report = Object.assign(new UsageReport(), {
|
||||||
|
points: [
|
||||||
|
{
|
||||||
|
id: 'item_1',
|
||||||
|
values: {
|
||||||
|
views: 7,
|
||||||
|
downloads: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'item_2',
|
||||||
|
values: {
|
||||||
|
views: 8,
|
||||||
|
downloads: 8,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
component.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it ('should display a table with the correct data', () => {
|
||||||
|
|
||||||
|
expect(de.query(By.css('table'))).toBeTruthy();
|
||||||
|
|
||||||
|
expect(de.query(By.css('th.views-header')).nativeElement.innerText)
|
||||||
|
.toEqual('views');
|
||||||
|
expect(de.query(By.css('th.downloads-header')).nativeElement.innerText)
|
||||||
|
.toEqual('downloads');
|
||||||
|
|
||||||
|
expect(de.query(By.css('td.item_1-views-data')).nativeElement.innerText)
|
||||||
|
.toEqual('7');
|
||||||
|
expect(de.query(By.css('td.item_1-downloads-data')).nativeElement.innerText)
|
||||||
|
.toEqual('4');
|
||||||
|
expect(de.query(By.css('td.item_2-views-data')).nativeElement.innerText)
|
||||||
|
.toEqual('8');
|
||||||
|
expect(de.query(By.css('td.item_2-downloads-data')).nativeElement.innerText)
|
||||||
|
.toEqual('8');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,67 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Point, UsageReport } from '../../core/statistics/models/usage-report.model';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../core/shared/operators';
|
||||||
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component representing a statistics table for a given usage report.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-statistics-table',
|
||||||
|
templateUrl: './statistics-table.component.html',
|
||||||
|
styleUrls: ['./statistics-table.component.scss']
|
||||||
|
})
|
||||||
|
export class StatisticsTableComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The usage report to display a statistics table for
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
report: UsageReport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boolean indicating whether the usage report has data
|
||||||
|
*/
|
||||||
|
hasData: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The table headers
|
||||||
|
*/
|
||||||
|
headers: string[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected dsoService: DSpaceObjectDataService,
|
||||||
|
protected nameService: DSONameService,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.hasData = this.report.points.length > 0;
|
||||||
|
if (this.hasData) {
|
||||||
|
this.headers = Object.keys(this.report.points[0].values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the row label to display for a statistics point.
|
||||||
|
* @param point the statistics point to get the label for
|
||||||
|
*/
|
||||||
|
getLabel(point: Point): Observable<string> {
|
||||||
|
switch (this.report.reportType) {
|
||||||
|
case 'TotalVisits':
|
||||||
|
return this.dsoService.findById(point.id).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((item) => this.nameService.getName(item)),
|
||||||
|
);
|
||||||
|
case 'TopCities':
|
||||||
|
case 'topCountries':
|
||||||
|
default:
|
||||||
|
return of(point.label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2852,6 +2852,30 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
"statistics.title": "Statistics",
|
||||||
|
|
||||||
|
"statistics.header": "Statistics for {{ scope }}",
|
||||||
|
|
||||||
|
"statistics.breadcrumbs": "Statistics",
|
||||||
|
|
||||||
|
"statistics.page.no-data": "No data available",
|
||||||
|
|
||||||
|
"statistics.table.no-data": "No data available",
|
||||||
|
|
||||||
|
"statistics.table.title.TotalVisits": "Total visits",
|
||||||
|
|
||||||
|
"statistics.table.title.TotalVisitsPerMonth": "Total visits per month",
|
||||||
|
|
||||||
|
"statistics.table.title.TotalDownloads": "File Visits",
|
||||||
|
|
||||||
|
"statistics.table.title.TopCountries": "Top country views",
|
||||||
|
|
||||||
|
"statistics.table.title.TopCities": "Top city views",
|
||||||
|
|
||||||
|
"statistics.table.header.views": "Views",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"submission.edit.title": "Edit Submission",
|
"submission.edit.title": "Edit Submission",
|
||||||
|
|
||||||
"submission.general.cannot_submit": "You have not the privilege to make a new submission.",
|
"submission.general.cannot_submit": "You have not the privilege to make a new submission.",
|
||||||
|
Reference in New Issue
Block a user