Commit f855653b authored by FORESTIER Fabien's avatar FORESTIER Fabien
Browse files

[WIP] Add datsetUsageStatistics service and link the graph with the service

parent 6dc2201b
{
"name": "webapp",
"version": "2.5.1",
"version": "2.5.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
......
......@@ -16,6 +16,7 @@ export class AppConfig {
geocoder: string;
proxyMap: string;
seo: string;
datasetUsageStatistics: string;
};
theFunctionalitiesInterruptor: {
credits: boolean;
......
......@@ -8,12 +8,12 @@
</div>
<app-info-summary class="mobile-section"></app-info-summary>
<div class="info-section" >
<div class="info-section" *ngIf="statistics">
<span class="info-title" i18n="@@dataset.info.dashboard">
Dashboard
</span>
<div class="dashboard">
<app-info-chart></app-info-chart>
<app-info-chart [statistics]="statistics"></app-info-chart>
</div>
</div>
......@@ -152,10 +152,10 @@
<span class="keywords" *ngFor="let keyword of keywords">{{ keyword }}</span>
</div>
</div>
<div class="rightside">
<app-info-summary></app-info-summary>
<app-info-partners></app-info-partners>
<app-info-contact></app-info-contact>
</div>
</div>
\ No newline at end of file
<div class="rightside">
<app-info-summary></app-info-summary>
<app-info-partners></app-info-partners>
<app-info-contact></app-info-contact>
</div>
</div>
\ No newline at end of file
......@@ -4,6 +4,7 @@ import { datatsetDataRepresentationType } from '../../../../i18n/traductions';
import { AppRoutes } from '../../../routes';
import { Metadata, typesMetadata } from '../../../shared/models';
import { DatasetDetailService } from '../../services';
import { UsageStatisticsService } from '../../services/usage-statistics.service';
@Component({
selector: 'app-dataset-info',
......@@ -15,10 +16,12 @@ export class DatasetInfoComponent implements OnInit, OnDestroy {
metadata: Metadata;
descriptions: Object[];
statistics;
sub: Subscription;
constructor(
private _datasetDetailService: DatasetDetailService,
private _usageStatisticsService: UsageStatisticsService,
) { }
ngOnInit() {
......@@ -38,6 +41,14 @@ export class DatasetInfoComponent implements OnInit, OnDestroy {
initializeInfo() {
this.metadata = this._datasetDetailService.datasetMetadata;
this._usageStatisticsService.getDatasetLastYearStats(this._datasetDetailService.dataset.uuid).subscribe(
(data) => {
this.statistics = data;
},
(err) => {
// Do something on error ...
});
}
setBackupImage() {
......
<div id="divChart">
<canvas id="myChart"></canvas>
</div>
\ No newline at end of file
<p class="title" i18n="@@dataset.detail.info.graph.title">Number of requests made to this dataset</p>
<canvas id="myChart"></canvas>
</div>
\ No newline at end of file
#divChart{
canvas{
margin:auto;
max-width: 650px;
width:100%;
max-height: 350px;
}
}
\ No newline at end of file
#divChart {
canvas {
margin: auto;
width: 100%;
max-height: 350px;
}
}
.title {
font-size: 14px;
font-weight: 600;
margin-bottom: 1.5rem;
}
import { Component, OnInit } from '@angular/core';
import { Chart, ChartOptions, ChartType, ChartDataSets, ChartPoint } from 'chart.js';
import { Component, Input, OnInit } from '@angular/core';
import { Chart } from 'chart.js';
import { datasetStatistics } from '../../../../../i18n/traductions';
@Component({
selector: 'app-info-chart',
......@@ -9,59 +9,67 @@ import { Chart, ChartOptions, ChartType, ChartDataSets, ChartPoint } from 'chart
})
export class InfoChartComponent implements OnInit {
@Input('statistics') statistics;
chart;
constructor() { }
barWidth = 20;
ngOnInit(): void {
const myChart = new Chart('myChart', {
this.chart = new Chart('myChart', {
type: 'bar',
data: {
labels: ['Jan', 'Fev', 'Mars', 'April', 'May', 'June', 'July', 'Aug', 'Sept', 'Oct', 'Nov'],
labels: this.statistics.dates.map(date => new Date(date).toLocaleString('default', { month: 'short' })),
datasets: [
{ data: [45, 37, 60, 70, 46, 33, 59, 26, 40, 50, 55],
{
data: this.statistics.data.wms || [],
label: 'WMS',
backgroundColor: '#7BC0F5',
hoverBackgroundColor: '#7BC0F5',
hoverBorderColor: '#DA322F',
hoverBorderWidth: 2,
barThickness : this.barWidth,
barThickness: this.barWidth,
stack: 'a',
},
{ data: [12, 37, 55, 70, 78, 33, 55, 70, 78, 33, 65],
{
data: this.statistics.data.wfs || [],
label: 'WFS',
hoverBorderColor: '#DA322F',
hoverBorderWidth: 2,
backgroundColor: '#E5A1F1',
hoverBackgroundColor: '#E5A1F1',
barThickness : this.barWidth,
barThickness: this.barWidth,
stack: 'a',
},
{ data: [58, 37, 60, 65, 46, 89, 55, 70, 78, 33, 55],
{
data: this.statistics.data.ws || [],
label: 'WS',
hoverBorderColor: '#DA322F',
hoverBorderWidth: 2,
backgroundColor: '#78D7D7',
hoverBackgroundColor: '#78D7D7',
barThickness : this.barWidth,
barThickness: this.barWidth,
stack: 'a',
},
{ data: [36, 87, 54, 25, 10, 33, 46, 33, 59, 26, 40],
label: 'Autres',
{
data: this.aggregateOtherServicesStats(this.statistics.data),
label: datasetStatistics.otherServices,
hoverBorderColor: '#DA322F',
hoverBorderWidth: 2,
backgroundColor: '#F2C774',
hoverBackgroundColor: '#F2C774',
barThickness : this.barWidth,
barThickness: this.barWidth,
stack: 'a',
},
],
},
options: {
responsive: true,
maintainAspectRatio: false,
title: {
display: true,
text: 'Services utilisés',
text: '',
position: 'bottom',
},
tooltips: {
......@@ -78,17 +86,17 @@ export class InfoChartComponent implements OnInit {
tooltipItem.forEach((tooltip) => {
total += tooltip.yLabel;
});
return total.toString() + ' requêtes';
return `${total.toString()} ${datasetStatistics.requests}`;
},
labelTextColor: (tooltipItem, data) => {
// for each tooltiplabel get the bar color
return data.data.datasets[tooltipItem.datasetIndex].backgroundColor.toString() ;
// for each tooltiplabel get the bar color
return data.data.datasets[tooltipItem.datasetIndex].backgroundColor.toString();
},
},
},
legend: {
position: 'bottom',
fullWidth: false,
fullWidth: true,
labels: {
boxWidth: 13,
padding: 30,
......@@ -113,4 +121,17 @@ export class InfoChartComponent implements OnInit {
},
});
}
aggregateOtherServicesStats(data) {
let array: number[] = [];
for (const service in data) {
if (service !== 'wms' && service !== 'wfs' && service !== 'ws') {
array = data[service].map((a, i) => array[i] ? a + array[i] : a);
}
}
return array;
}
}
......@@ -10,10 +10,9 @@ import { DatasetDetailComponents } from './components';
import { DatasetDetailRoutingModule } from './dataset-detail-routing.module';
import { DatasetDetailResolvers } from './resolvers';
import { DatasetDetailServices } from './services';
import { InfoChartComponent } from './components/dataset-info/info-chart/info-chart.component';
@NgModule({
declarations: [...DatasetDetailComponents, InfoChartComponent],
declarations: [...DatasetDetailComponents],
imports: [
CommonModule,
DatasetDetailRoutingModule,
......
export interface DatasetUsageStatistics {
dates: string[]; // This contains an array of date string formatted like this 'YYYY-MM-DD'
data: any; // data is an object with many properties possible. The key of each property correspond to service name
// and the value is an array of numbers corresponding of the number of request for this service. The order of the numbers
// in the array correspond to the order of the dates
}
\ No newline at end of file
export { DatasetChild, IDatasetRawChild } from './dataset-child.model';
export { DatasetUsageStatistics } from './dataset-usage-statistics.model';
export { IProjection, Projection } from './projection.model';
export { Format, IFormat, IResource, IResourceFormat, Resource } from './resource.model';
import { DatasetDetailService } from './dataset-detail.service';
import { ResourcesService } from './resources.service';
import { UsageStatisticsService } from './usage-statistics.service';
export { DatasetDetailService, ResourcesService };
......@@ -7,4 +8,5 @@ export { DatasetDetailService, ResourcesService };
export const DatasetDetailServices = [
DatasetDetailService,
ResourcesService,
UsageStatisticsService,
];
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { APP_CONFIG } from '../../core/services/app-config.service';
import { DatasetUsageStatistics } from '../models';
@Injectable()
export class UsageStatisticsService {
constructor(
private _httpClient: HttpClient,
) { }
getDatasetLastYearStats(uuid: string): Observable<DatasetUsageStatistics> {
// The statistics graph displays monthly usage of each service available for the dataset
// It doesn't display the current month information but stops at the previous month
// However the months in javascript dates goes from 0 to 11 so when passing start and end dates, we don't need to subsctract a month
// The backend service expect a 2 digits month while javascript date does not give 'O1' but only '1'. This is while we use a small trick
// to make sure there always two digits for the month in the date
const today = new Date();
const aMonthAgo = `${today.getFullYear()}-${`0${today.getMonth()}`.slice(-2)}-${today.getDate()}`;
today.setFullYear(today.getFullYear() - 1);
const aMonthAndAYearAgo = `${today.getFullYear()}-${`0${today.getMonth()}`.slice(-2)}-${today.getDate()}`;
const url = `${APP_CONFIG.backendUrls.datasetUsageStatistics}?start=${aMonthAndAYearAgo}&uuid=${uuid}&granularity=month&end=${aMonthAgo}`;
return this._httpClient.get(url).pipe(
map((data: any[]) => {
// As the service doesn't return anything for a month if no requests to the service has been made,
// we can directly use the date from the services answers, instead we use this functions that generates
// the dates ('YYYY-MM-01') just as the service would do but making sure that there is no missing months
const dates = this.generateMonthlyDate();
// Collecting all the possible values of the service property for the dataset and removing duplicates
const services = [...new Set(data.map(e => e.service))];
const sortedData = {};
// For each service of the dataset fill an array of value, each item of the array corresponding to the count
// of requests to the services for the month at the same position in the dates array
// ex: sortedData['wms'][0] = 100, dates[0] = "2019-04-01" means that during April 2019 the wms service of this dataset
// has been requested 100 times
services.forEach((service) => {
// Looking in the data array if an item match the service and date of the currect iteration, if not set value to 0
sortedData[service] = dates.map((date) => {
const value = data.find(d => d.service === service && d.date === date);
return value ? value.count : 0;
});
});
return {
dates,
data: sortedData,
};
}),
);
}
// Generates an array of dates string formatted as follow 'YYYY-MM-01', starting a year and a month ago until a month ago
// and using monthly intervals
private generateMonthlyDate = (): string[] => {
const today = new Date();
today.setMonth(today.getMonth() - 1); // Starting a month before the current date
const dates = [];
for (let i = 0; i < 12; i += 1) {
dates.push(`${today.getFullYear()}-${`0${today.getMonth() + 1}`.slice(-2)}-01`);
today.setMonth(today.getMonth() - 1);
console.log(today.getMonth());
}
return dates;
}
}
......@@ -13,7 +13,8 @@
"reuses": "",
"proxyMap": "",
"geocoder": "",
"seo": ""
"seo": "",
"datasetUsageStatistics": ""
},
"theFunctionalitiesInterruptor": {
"credits": true,
......
......@@ -459,6 +459,10 @@
<source>News</source>
<target>News</target>
</trans-unit>
<trans-unit id="dataset.detail.info.graph.title" datatype="html">
<source>Number of requests made to this dataset</source>
<target>Number of requests made to this dataset</target>
</trans-unit>
<trans-unit id="dataset.detail.info" datatype="html">
<source>This dataset doesn't contain geographical data.</source>
<target>This dataset doesn't contain geographical data.</target>
......
......@@ -459,6 +459,10 @@
<source>News</source>
<target>Actualités</target>
</trans-unit>
<trans-unit id="dataset.detail.info.graph.title" datatype="html">
<source>Number of requests made to this dataset</source>
<target>Nombre de requêtes effectuées sur ce jeu de données</target>
</trans-unit>
<trans-unit id="dataset.detail.info" datatype="html">
<source>This dataset doesn't contain geographical data.</source>
<target>Ce jeu de données ne contient pas de données géographiques.</target>
......
......@@ -324,3 +324,8 @@ export const buttonCopyLinkToCliboardMessages = {
hover: 'Copier le lien',
copied: 'Copié !',
};
export const datasetStatistics = {
requests: 'requêtes',
otherServices: 'Autres',
};
......@@ -326,3 +326,8 @@ export const buttonCopyLinkToCliboardMessages = {
hover: 'Copy the link',
copied: 'Copied!',
};
export const datasetStatistics = {
requests: 'requests',
otherServices: 'Other',
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment