openaire-library/landingPages/landing-utils/metrics/metrics.component.ts

354 lines
12 KiB
TypeScript

import {Component, Input, Output, EventEmitter, ChangeDetectorRef} from '@angular/core';
import {Metrics} from '../../../utils/entities/metrics';
import {MetricsService} from '../../../services/metrics.service';
import {ErrorCodes} from '../../../utils/properties/errorCodes';
import {Subscription, zip} from 'rxjs';
import {EnvProperties} from '../../../utils/properties/env-properties';
import {animate, state, style, transition, trigger} from "@angular/animations";
@Component({
selector: 'metrics',
styleUrls: ['metrics.component.css'],
template: `
<div *ngIf="metrics && (pageViews >0 || metrics.totalViews > 0|| metrics.totalDownloads >0)" class="metrics"
click-outside-or-esc (clickOutside)="close($event)"
id="metrics" targetId="metrics">
<div class="m-badge clickable" (click)="toggle($event)">
<img src="assets/common-assets/logo-small-usage-counts.png" loading="lazy" alt="usage counts">
<div class="number">{{total | number}}</div>
</div>
<div class="widget" [@widget]="state.toString()">
<div class="body">
<div [@body]="state.toString()">
<div *ngIf="entityType == 'projects'" class="uk-margin-bottom">
<i class="uk-text-center">Project metrics
are derived from aggregating individual research results metrics.
</i>
</div>
<div class="uk-child-width-1-3@m uk-grid-small uk-grid-divider uk-grid-match" uk-grid>
<div class="uk-text-center uk-first-column">
<div class="number">{{metrics.totalDownloads | number}}</div>
<div>Downloads</div>
</div>
<div class="uk-text-center">
<div class="number">{{pageViews | number}}</div>
<div>OpenAIRE views</div>
</div>
<div class="uk-text-center">
<div class="number">{{metrics.totalViews | number}}</div>
<div>Total views</div>
</div>
</div>
<div *ngIf="(metrics.totalViews > 0 && viewsFrameUrl) || (metrics.totalDownloads > 0 && downloadsFrameUrl)"
[@charts]="state.toString()"
class="charts uk-visible-toggle" tabindex="-1" uk-slider>
<ul class="uk-slider-items uk-child-width-1-1">
<li *ngIf="metrics && metrics.infos.size > 0" class="uk-overflow-auto" style="height: 200px">
<table
class="uk-table uk-table-small uk-table-striped">
<thead>
<tr>
<th class="uk-text-center uk-text-bold">From</th>
<th class="uk-text-center uk-text-bold">Views</th>
<th class="uk-text-center uk-text-bold">Downloads</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let key of getKeys(metrics.infos)">
<td class="uk-text-center">
<a href="{{metrics.infos.get(key).url}}">
{{metrics.infos.get(key).name}}
</a>
</td>
<td class="uk-text-center">
{{metrics.infos.get(key).numOfViews | number}}
<!-- <span *ngIf="metrics.infos.get(key).numOfViews > 0 && metrics.infos.get(key).openaireViews > 0">
( {{metrics.infos.get(key).openaireViews | number}} from OpenAIRE )
</span>-->
</td>
<td class="uk-text-center">
{{metrics.infos.get(key).numOfDownloads | number}}
<!--<span *ngIf="metrics.infos.get(key).numOfDownloads > 0 && metrics.infos.get(key).openaireDownloads > 0">
( {{metrics.infos.get(key).openaireDownloads | number}} from OpenAIRE )
</span>-->
</td>
</tr>
</tbody>
</table>
</li>
<!-- remove check for datasources when with the new charts -->
<li #element>
<i-frame *ngIf="metricsClicked && metrics.totalViews > 0 && viewsFrameUrl"
[height]="200"
[url]=viewsFrameUrl [addClass]="false">
</i-frame>
</li>
<li #element>
<i-frame *ngIf="metricsClicked && metrics.totalDownloads > 0 && downloadsFrameUrl"
[height]="200"
[url]=downloadsFrameUrl [addClass]="false">
</i-frame>
</li>
</ul>
<div class="uk-text-right uk-margin-small-top">
<a href="#" uk-slidenav-previous uk-slider-item="previous"></a>
<a href="#" class="space" uk-slidenav-next uk-slider-item="next"></a>
</div>
</div>
</div>
</div>
<div class="footer uk-flex uk-flex-middle" [@footer]="state.toString()">
<span>Powered by</span>
<a href="https://usagecounts.openaire.eu" target="_blank">
<img width="120" src="assets/common-assets/logo-large-usage-counts.png" loading="lazy" alt="usage counts">
</a>
</div>
</div>
</div>
`,
animations: [
trigger('widget', [
state('-1', style({
width: '150px',
opacity: 0,
height: '60px',
})),
state('0', style({
width: '150px',
opacity: 0,
height: '60px',
"margin-top": '-25px'
})),
state('1', style({
width: '150px',
opacity: 1,
height: '60px',
})),
state('2', style({
width: '350px',
height: 'auto',
})),
state('3', style({
width: '350px',
height: 'auto',
})),
state('4', style({
width: '350px',
height: 'auto',
})),
transition('0 => 1', [
animate('300ms ease-out')
]),
transition('1 => 2', [
animate('400ms cubic-bezier(.18,.89,.56,1)')
])
]),
trigger('body', [
state('2', style({
opacity: 1,
height: 'auto',
})),
state('3', style({
opacity: 1,
height: 'auto'
})),
state('4', style({
opacity: 1,
height: 'auto'
})),
transition('* => 2', [
animate('400ms cubic-bezier(.18,.89,.56,1)')
])
]),
trigger('charts', [
state('3', style({
opacity: 1
})),
state('4', style({
opacity: 1
})),
transition('* => 3', [
animate('800ms ease-out')
])
]),
trigger('footer', [
state('4', style({
transform: 'translateY(0)'
})),
transition('3 => 4', [
animate('800ms ease-out')
])
])
]
})
export class MetricsComponent {
@Output() metricsResults = new EventEmitter();
@Input() id: string;
@Input() entityType: string;
@Input() entity: string;
//@Input() name: string = "";
@Input() pageViews: number = 0;
@Input() properties: EnvProperties;
@Input() shortView: boolean = false;
@Input() open = false;
@Input() viewsFrameUrl: string;
@Input() downloadsFrameUrl: string;
public metrics: Metrics;
public errorCodes: ErrorCodes;
private sub: Subscription;
private timeouts: any[] = [];
public metricsClicked: boolean = true;
public status: number;
public state: number = -1;
constructor(private metricsService: MetricsService, private cdr: ChangeDetectorRef) {
}
ngOnInit() {
this.errorCodes = new ErrorCodes();
if (typeof document !== 'undefined') {
this.status = this.errorCodes.LOADING;
this.getMetrics();
}
}
ngOnDestroy() {
if(this.sub) {
this.sub.unsubscribe();
}
}
public get total(): number {
return +this.pageViews + +this.metrics.totalViews + +this.metrics.totalDownloads;
}
private getMetrics() {
//queries from old API - replaced with queries to the stats tool
/* this.sub = this.metricsService.getMetrics(this.id, this.entityType, this.properties).subscribe(
data => {
this.metrics = data;
this.cdr.detectChanges();
this.status = this.errorCodes.DONE;
this.metricsResults.emit({
totalViews: this.metrics.totalViews,
totalDownloads: this.metrics.totalDownloads,
pageViews: this.metrics.pageViews
});
},
err => {
if (err.status == '404') {
this.status = this.errorCodes.NOT_FOUND;
} else if (err.status == '500') {
this.status = this.errorCodes.ERROR;
} else {
this.status = this.errorCodes.NOT_AVAILABLE;
}
this.metricsResults.emit({
totalViews: 0,
totalDownloads: 0
});
}
);*/
let obs;
if (this.entityType == "results") {
obs = zip(this.metricsService.getMetricsNumber(this.id, "usagestats.results.views", this.properties),
this.metricsService.getMetricsNumber(this.id, "usagestats.results.downloads", this.properties),
this.metricsService.getMetricsNumber(this.id, "usagestats.results.views.openaire", this.properties),
this.metricsService.getMetricsNumbersByRepository(this.id, "usagestats.results.viewsdownloads.repository", this.properties)
);
} else if (this.entityType == "projects") {
obs = zip(this.metricsService.getMetricsNumber(this.id, "usagestats.projects.views", this.properties),
this.metricsService.getMetricsNumber(this.id, "usagestats.projects.downloads", this.properties),
this.metricsService.getMetricsNumber(this.id, "usagestats.projects.views.openaire", this.properties));
} else if (this.entityType == "datasources") {
obs = zip(this.metricsService.getMetricsNumber(this.id, "usagestats.views.repository.local", this.properties),
this.metricsService.getMetricsNumber(this.id, "usagestats.downloads.repository.local", this.properties),
this.metricsService.getMetricsNumber(this.id, "usagestats.views.openaire", this.properties),
// this.metricsService.getMetricsNumber(this.id, "usagestats.downloads.repository.openaire", this.properties)
);
}
this.sub = obs.subscribe(
data => {
this.metrics = new Metrics();
this.metrics.infos = new Map<string, { "name": string, "url": string, "numOfDownloads": string, "numOfViews": string, "openaireDownloads": string, "openaireViews": string }>();
this.metrics.totalViews = data[0] ? data[0] : 0;
this.metrics.totalDownloads = data[1] ? data[1] : 0;
this.metrics.pageViews = data[2] ? data[2] : 0;
this.metrics.infos = data[3] ? data[3] : 0;
this.cdr.detectChanges();
this.status = this.errorCodes.DONE;
this.metricsResults.emit({
totalViews: this.metrics.totalViews,
totalDownloads: this.metrics.totalDownloads,
pageViews: this.metrics.pageViews
});
},
err => {
if (err.status == '404') {
this.status = this.errorCodes.NOT_FOUND;
} else if (err.status == '500') {
this.status = this.errorCodes.ERROR;
} else {
this.status = this.errorCodes.NOT_AVAILABLE;
}
this.metricsResults.emit({
totalViews: 0,
totalDownloads: 0
});
}
);
}
public close(event) {
if(event.value && this.state !== -1) {
this.timeouts.forEach(timeout => {
clearTimeout(timeout);
});
this.state = -1;
this.timeouts = [];
}
}
public toggle(event) {
this.metricsClicked = true;
event.stopPropagation();
if(this.state !== -1) {
this.timeouts.forEach(timeout => {
clearTimeout(timeout);
});
this.state = -1;
this.timeouts = [];
} else {
this.state++;
this.timeouts.push(setTimeout(() => {
this.state++;
this.timeouts.push(setTimeout(() => {
this.state++;
this.timeouts.push(setTimeout(() => {
this.state++;
this.timeouts.push(setTimeout(() => {
this.state++;
}, 400));
}, 800));
}, 300));
}, 100));
}
this.cdr.detectChanges();
}
public getKeys(map) {
return Array.from(map.keys());
}
}