]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Support interactive video stats graph
authorChocobozzz <me@florianbigard.com>
Fri, 8 Apr 2022 08:22:56 +0000 (10:22 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Fri, 15 Apr 2022 07:49:35 +0000 (09:49 +0200)
12 files changed:
client/package.json
client/src/app/+stats/video/video-stats.component.html
client/src/app/+stats/video/video-stats.component.scss
client/src/app/+stats/video/video-stats.component.ts
client/src/app/+stats/video/video-stats.service.ts
client/yarn.lock
server/lib/timeserie.ts
server/models/view/local-video-viewer.ts
server/tests/api/views/video-views-timeserie-stats.ts
shared/models/videos/stats/index.ts
shared/models/videos/stats/video-stats-timeserie-group-interval.type.ts [deleted file]
shared/models/videos/stats/video-stats-timeserie.model.ts

index 7c0732b446aa1340687803f1f5e80cd0b88134cf..7da61df66ab3a6923e9d0be742b629b6365ba183 100644 (file)
@@ -83,6 +83,7 @@
     "buffer": "^6.0.3",
     "cache-chunk-store": "^3.0.0",
     "chart.js": "^3.5.1",
+    "chartjs-plugin-zoom": "^1.2.1",
     "chromedriver": "^99.0.0",
     "core-js": "^3.1.4",
     "css-loader": "^6.2.0",
index ef43c9fba0a4f4da9aa6e21ca569fafb2603c634..400c049ebe9c00948e241cbd5666659c1362e83f 100644 (file)
         </a>
 
         <ng-template ngbNavContent>
-          <div [ngStyle]="{ 'min-height': chartHeight }">
+          <div class="chart-container" [ngStyle]="{ 'min-height': chartHeight }">
             <p-chart
               *ngIf="chartOptions[availableChart.id]"
               [height]="chartHeight" [width]="chartWidth"
               [type]="chartOptions[availableChart.id].type" [options]="chartOptions[availableChart.id].options" [data]="chartOptions[availableChart.id].data"
+              [plugins]="chartPlugins"
             ></p-chart>
           </div>
+
+          <div class="zoom-container">
+            <span *ngIf="!hasZoom() && availableChart.zoomEnabled" i18n class="description">You can select a part of the graph to zoom in</span>
+
+            <my-button i18n *ngIf="hasZoom()" (click)="resetZoom()">Reset zoom</my-button>
+          </div>
         </ng-template>
       </ng-container>
     </div>
index 190499b5c7855c33fb569f29a23491270ef6c821..e2a74152f00dafdde37cfa62fdcc658fce5f000d 100644 (file)
@@ -21,6 +21,7 @@
   min-width: 200px;
   margin-right: 15px;
   background-color: pvar(--submenuBackgroundColor);
+  margin-bottom: 15px;
 
   .label,
   .more-info {
     font-size: 24px;
     font-weight: $font-semibold;
   }
+
+  @media screen and (max-width: $mobile-view) {
+    min-height: fit-content;
+    min-width: fit-content;
+    padding: 15px;
+  }
 }
 
 my-embed {
@@ -45,6 +52,12 @@ my-embed {
   width: 100%;
 }
 
+@include on-small-main-col {
+  my-embed {
+    display: none;
+  }
+}
+
 .tab-content {
   margin-top: 15px;
 }
@@ -52,3 +65,16 @@ my-embed {
 .nav-tabs {
   @include peertube-nav-tabs($border-width: 2px);
 }
+
+.chart-container {
+  margin-bottom: 10px;
+}
+
+.zoom-container {
+  display: flex;
+  justify-content: center;
+
+  .description {
+    font-style: italic;
+  }
+}
index 05319539bcf066c7ab50e504b54d8893facea63f..14db31ecfdca0ee2dc3884638f8752869d3c3b4b 100644 (file)
@@ -1,8 +1,9 @@
-import { ChartConfiguration, ChartData } from 'chart.js'
+import { ChartConfiguration, ChartData, PluginOptionsByType, Scale, TooltipItem } from 'chart.js'
+import zoomPlugin from 'chartjs-plugin-zoom'
 import { Observable, of } from 'rxjs'
 import { Component, OnInit } from '@angular/core'
 import { ActivatedRoute } from '@angular/router'
-import { Notifier } from '@app/core'
+import { Notifier, PeerTubeRouterService } from '@app/core'
 import { NumberFormatterPipe, VideoDetails } from '@app/shared/shared-main'
 import { secondsToTime } from '@shared/core-utils'
 import { VideoStatsOverall, VideoStatsRetention, VideoStatsTimeserie, VideoStatsTimeserieMetric } from '@shared/models/videos'
@@ -15,6 +16,7 @@ type CountryData = { name: string, viewers: number }[]
 type ChartIngestData = VideoStatsTimeserie | VideoStatsRetention | CountryData
 type ChartBuilderResult = {
   type: 'line' | 'bar'
+  plugins: Partial<PluginOptionsByType<'line' | 'bar'>>
   data: ChartData<'line' | 'bar'>
   displayLegend: boolean
 }
@@ -34,19 +36,23 @@ export class VideoStatsComponent implements OnInit {
   availableCharts = [
     {
       id: 'viewers',
-      label: $localize`Viewers`
+      label: $localize`Viewers`,
+      zoomEnabled: true
     },
     {
       id: 'aggregateWatchTime',
-      label: $localize`Watch time`
+      label: $localize`Watch time`,
+      zoomEnabled: true
     },
     {
       id: 'retention',
-      label: $localize`Retention`
+      label: $localize`Retention`,
+      zoomEnabled: false
     },
     {
       id: 'countries',
-      label: $localize`Countries`
+      label: $localize`Countries`,
+      zoomEnabled: false
     }
   ]
 
@@ -56,18 +62,37 @@ export class VideoStatsComponent implements OnInit {
 
   countries: CountryData = []
 
+  chartPlugins = [ zoomPlugin ]
+
+  private timeseriesStartDate: Date
+  private timeseriesEndDate: Date
+
+  private chartIngestData: { [ id in ActiveGraphId ]?: ChartIngestData } = {}
+
   constructor (
     private route: ActivatedRoute,
     private notifier: Notifier,
     private statsService: VideoStatsService,
+    private peertubeRouter: PeerTubeRouterService,
     private numberFormatter: NumberFormatterPipe
   ) {}
 
   ngOnInit () {
     this.video = this.route.snapshot.data.video
 
+    this.route.queryParams.subscribe(params => {
+      this.timeseriesStartDate = params.startDate
+        ? new Date(params.startDate)
+        : undefined
+
+      this.timeseriesEndDate = params.endDate
+        ? new Date(params.endDate)
+        : undefined
+
+      this.loadChart()
+    })
+
     this.loadOverallStats()
-    this.loadChart()
   }
 
   hasCountries () {
@@ -80,6 +105,18 @@ export class VideoStatsComponent implements OnInit {
     this.loadChart()
   }
 
+  resetZoom () {
+    this.peertubeRouter.silentNavigate([], {})
+  }
+
+  hasZoom () {
+    return !!this.timeseriesStartDate && this.isTimeserieGraph(this.activeGraphId)
+  }
+
+  private isTimeserieGraph (graphId: ActiveGraphId) {
+    return graphId === 'aggregateWatchTime' || graphId === 'viewers'
+  }
+
   private loadOverallStats () {
     this.statsService.getOverallStats(this.video.uuid)
       .subscribe({
@@ -125,24 +162,35 @@ export class VideoStatsComponent implements OnInit {
   private loadChart () {
     const obsBuilders: { [ id in ActiveGraphId ]: Observable<ChartIngestData> } = {
       retention: this.statsService.getRetentionStats(this.video.uuid),
-      aggregateWatchTime: this.statsService.getTimeserieStats(this.video.uuid, 'aggregateWatchTime'),
-      viewers: this.statsService.getTimeserieStats(this.video.uuid, 'viewers'),
+
+      aggregateWatchTime: this.statsService.getTimeserieStats({
+        videoId: this.video.uuid,
+        startDate: this.timeseriesStartDate,
+        endDate: this.timeseriesEndDate,
+        metric: 'aggregateWatchTime'
+      }),
+      viewers: this.statsService.getTimeserieStats({
+        videoId: this.video.uuid,
+        startDate: this.timeseriesStartDate,
+        endDate: this.timeseriesEndDate,
+        metric: 'viewers'
+      }),
+
       countries: of(this.countries)
     }
 
     obsBuilders[this.activeGraphId].subscribe({
       next: res => {
-        this.chartOptions[this.activeGraphId] = this.buildChartOptions(this.activeGraphId, res)
+        this.chartIngestData[this.activeGraphId] = res
+
+        this.chartOptions[this.activeGraphId] = this.buildChartOptions(this.activeGraphId)
       },
 
       error: err => this.notifier.error(err.message)
     })
   }
 
-  private buildChartOptions (
-    graphId: ActiveGraphId,
-    rawData: ChartIngestData
-  ): ChartConfiguration<'line' | 'bar'> {
+  private buildChartOptions (graphId: ActiveGraphId): ChartConfiguration<'line' | 'bar'> {
     const dataBuilders: {
       [ id in ActiveGraphId ]: (rawData: ChartIngestData) => ChartBuilderResult
     } = {
@@ -152,7 +200,9 @@ export class VideoStatsComponent implements OnInit {
       countries: (rawData: CountryData) => this.buildCountryChartOptions(rawData)
     }
 
-    const { type, data, displayLegend } = dataBuilders[graphId](rawData)
+    const { type, data, displayLegend, plugins } = dataBuilders[graphId](this.chartIngestData[graphId])
+
+    const self = this
 
     return {
       type,
@@ -162,6 +212,19 @@ export class VideoStatsComponent implements OnInit {
         responsive: true,
 
         scales: {
+          x: {
+            ticks: {
+              callback: function (value) {
+                return self.formatXTick({
+                  graphId,
+                  value,
+                  data: self.chartIngestData[graphId] as VideoStatsTimeserie,
+                  scale: this
+                })
+              }
+            }
+          },
+
           y: {
             beginAtZero: true,
 
@@ -170,7 +233,7 @@ export class VideoStatsComponent implements OnInit {
               : undefined,
 
             ticks: {
-              callback: value => this.formatTick(graphId, value)
+              callback: value => this.formatYTick({ graphId, value })
             }
           }
         },
@@ -181,15 +244,18 @@ export class VideoStatsComponent implements OnInit {
           },
           tooltip: {
             callbacks: {
-              label: value => this.formatTick(graphId, value.raw as number | string)
+              title: items => this.formatTooltipTitle({ graphId, items }),
+              label: value => this.formatYTick({ graphId, value: value.raw as number | string })
             }
-          }
+          },
+
+          ...plugins
         }
       }
     }
   }
 
-  private buildRetentionChartOptions (rawData: VideoStatsRetention) {
+  private buildRetentionChartOptions (rawData: VideoStatsRetention): ChartBuilderResult {
     const labels: string[] = []
     const data: number[] = []
 
@@ -203,6 +269,10 @@ export class VideoStatsComponent implements OnInit {
 
       displayLegend: false,
 
+      plugins: {
+        ...this.buildDisabledZoomPlugin()
+      },
+
       data: {
         labels,
         datasets: [
@@ -215,12 +285,12 @@ export class VideoStatsComponent implements OnInit {
     }
   }
 
-  private buildTimeserieChartOptions (rawData: VideoStatsTimeserie) {
+  private buildTimeserieChartOptions (rawData: VideoStatsTimeserie): ChartBuilderResult {
     const labels: string[] = []
     const data: number[] = []
 
     for (const d of rawData.data) {
-      labels.push(new Date(d.date).toLocaleDateString())
+      labels.push(d.date)
       data.push(d.value)
     }
 
@@ -229,6 +299,31 @@ export class VideoStatsComponent implements OnInit {
 
       displayLegend: false,
 
+      plugins: {
+        zoom: {
+          zoom: {
+            wheel: {
+              enabled: false
+            },
+            drag: {
+              enabled: true
+            },
+            pinch: {
+              enabled: true
+            },
+            mode: 'x',
+            onZoomComplete: ({ chart }) => {
+              const { min, max } = chart.scales.x
+
+              const startDate = rawData.data[min].date
+              const endDate = rawData.data[max].date
+
+              this.peertubeRouter.silentNavigate([], { startDate, endDate })
+            }
+          }
+        }
+      },
+
       data: {
         labels,
         datasets: [
@@ -241,7 +336,7 @@ export class VideoStatsComponent implements OnInit {
     }
   }
 
-  private buildCountryChartOptions (rawData: CountryData) {
+  private buildCountryChartOptions (rawData: CountryData): ChartBuilderResult {
     const labels: string[] = []
     const data: number[] = []
 
@@ -255,8 +350,8 @@ export class VideoStatsComponent implements OnInit {
 
       displayLegend: true,
 
-      options: {
-        indexAxis: 'y'
+      plugins: {
+        ...this.buildDisabledZoomPlugin()
       },
 
       data: {
@@ -277,13 +372,57 @@ export class VideoStatsComponent implements OnInit {
     return getComputedStyle(document.body).getPropertyValue('--mainColorLighter')
   }
 
-  private formatTick (graphId: ActiveGraphId, value: number | string) {
+  private formatXTick (options: {
+    graphId: ActiveGraphId
+    value: number | string
+    data: VideoStatsTimeserie
+    scale: Scale
+  }) {
+    const { graphId, value, data, scale } = options
+
+    const label = scale.getLabelForValue(value as number)
+
+    if (!this.isTimeserieGraph(graphId)) {
+      return label
+    }
+
+    const date = new Date(label)
+
+    if (data.groupInterval.match(/ days?$/)) {
+      return date.toLocaleDateString([], { month: 'numeric', day: 'numeric' })
+    }
+
+    if (data.groupInterval.match(/ hours?$/)) {
+      return date.toLocaleTimeString([], { month: 'numeric', day: 'numeric', hour: 'numeric', minute: 'numeric' })
+    }
+
+    return date.toLocaleTimeString([], { hour: 'numeric', minute: 'numeric' })
+  }
+
+  private formatYTick (options: {
+    graphId: ActiveGraphId
+    value: number | string
+  }) {
+    const { graphId, value } = options
+
     if (graphId === 'retention') return value + ' %'
     if (graphId === 'aggregateWatchTime') return secondsToTime(+value)
 
     return value.toLocaleString()
   }
 
+  private formatTooltipTitle (options: {
+    graphId: ActiveGraphId
+    items: TooltipItem<any>[]
+  }) {
+    const { graphId, items } = options
+    const item = items[0]
+
+    if (this.isTimeserieGraph(graphId)) return new Date(item.label).toLocaleString()
+
+    return item.label
+  }
+
   private countryCodeToName (code: string) {
     const intl: any = Intl
     if (!intl.DisplayNames) return code
@@ -292,4 +431,22 @@ export class VideoStatsComponent implements OnInit {
 
     return regionNames.of(code)
   }
+
+  private buildDisabledZoomPlugin () {
+    return {
+      zoom: {
+        zoom: {
+          wheel: {
+            enabled: false
+          },
+          drag: {
+            enabled: false
+          },
+          pinch: {
+            enabled: false
+          }
+        }
+      }
+    }
+  }
 }
index 8f9d48f6083d67f29226119c02bea3167d14c379..712d03971c35ba212173efd517ec9242cd0d92bd 100644 (file)
@@ -1,6 +1,6 @@
 import { catchError } from 'rxjs'
 import { environment } from 'src/environments/environment'
-import { HttpClient } from '@angular/common/http'
+import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { RestExtractor } from '@app/core'
 import { VideoService } from '@app/shared/shared-main'
@@ -22,8 +22,19 @@ export class VideoStatsService {
       .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 
-  getTimeserieStats (videoId: string, metric: VideoStatsTimeserieMetric) {
-    return this.authHttp.get<VideoStatsTimeserie>(VideoService.BASE_VIDEO_URL + '/' + videoId + '/stats/timeseries/' + metric)
+  getTimeserieStats (options: {
+    videoId: string
+    metric: VideoStatsTimeserieMetric
+    startDate?: Date
+    endDate?: Date
+  }) {
+    const { videoId, metric, startDate, endDate } = options
+
+    let params = new HttpParams()
+    if (startDate) params = params.append('startDate', startDate.toISOString())
+    if (endDate) params = params.append('endDate', endDate.toISOString())
+
+    return this.authHttp.get<VideoStatsTimeserie>(VideoService.BASE_VIDEO_URL + '/' + videoId + '/stats/timeseries/' + metric, { params })
       .pipe(catchError(err => this.restExtractor.handleError(err)))
   }
 
index 5c6e9f8b96c6be4f65c27e60517c1fd2ce2894c3..800c226c2174c9cc03b861e7eda7841571b56acb 100644 (file)
@@ -3792,6 +3792,13 @@ chart.js@^3.5.1:
   resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada"
   integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==
 
+chartjs-plugin-zoom@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/chartjs-plugin-zoom/-/chartjs-plugin-zoom-1.2.1.tgz#7e350ba20d907f397d0c055239dcc67d326df705"
+  integrity sha512-2zbWvw2pljrtMLMXkKw1uxYzAne5PtjJiOZftcut4Lo3Ee8qUt95RpMKDWrZ+pBZxZKQKOD/etdU4pN2jxZUmg==
+  dependencies:
+    hammerjs "^2.0.8"
+
 chokidar@3.5.3, "chokidar@>=3.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.5.2:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@@ -5961,6 +5968,11 @@ gzip-size@^6.0.0:
   dependencies:
     duplexer "^0.1.2"
 
+hammerjs@^2.0.8:
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
+  integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=
+
 handle-thing@^2.0.0:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e"
index d8f700a2fab4f2417f644aebba7fe8d359822764..bd3d1c1caed105a1f4a2270df5a4dbb4ecd078c6 100644 (file)
@@ -1,24 +1,17 @@
 import { logger } from '@server/helpers/logger'
-import { VideoStatsTimeserieGroupInterval } from '@shared/models'
 
 function buildGroupByAndBoundaries (startDateString: string, endDateString: string) {
   const startDate = new Date(startDateString)
   const endDate = new Date(endDateString)
 
-  const groupByMatrix: { [ id in VideoStatsTimeserieGroupInterval ]: string } = {
-    one_day: '1 day',
-    one_hour: '1 hour',
-    ten_minutes: '10 minutes',
-    one_minute: '1 minute'
-  }
   const groupInterval = buildGroupInterval(startDate, endDate)
 
   logger.debug('Found "%s" group interval.', groupInterval, { startDate, endDate })
 
   // Remove parts of the date we don't need
-  if (groupInterval === 'one_day') {
+  if (groupInterval.endsWith(' day') || groupInterval.endsWith(' days')) {
     startDate.setHours(0, 0, 0, 0)
-  } else if (groupInterval === 'one_hour') {
+  } else if (groupInterval.endsWith(' hour') || groupInterval.endsWith(' hours')) {
     startDate.setMinutes(0, 0, 0)
   } else {
     startDate.setSeconds(0, 0)
@@ -26,7 +19,6 @@ function buildGroupByAndBoundaries (startDateString: string, endDateString: stri
 
   return {
     groupInterval,
-    sqlInterval: groupByMatrix[groupInterval],
     startDate,
     endDate
   }
@@ -40,16 +32,18 @@ export {
 
 // ---------------------------------------------------------------------------
 
-function buildGroupInterval (startDate: Date, endDate: Date): VideoStatsTimeserieGroupInterval {
+function buildGroupInterval (startDate: Date, endDate: Date): string {
   const aDay = 86400
   const anHour = 3600
   const aMinute = 60
 
   const diffSeconds = (endDate.getTime() - startDate.getTime()) / 1000
 
-  if (diffSeconds >= 6 * aDay) return 'one_day'
-  if (diffSeconds >= 6 * anHour) return 'one_hour'
-  if (diffSeconds >= 60 * aMinute) return 'ten_minutes'
+  if (diffSeconds >= 15 * aDay) return '1 day'
+  if (diffSeconds >= 8 * aDay) return '12 hours'
+  if (diffSeconds >= 4 * aDay) return '6 hours'
+  if (diffSeconds >= 15 * anHour) return '1 hour'
+  if (diffSeconds >= 180 * aMinute) return '10 minutes'
 
-  return 'one_minute'
+  return 'minute'
 }
index ad2ad35ca61c2363c2fb904e92ebc47263c752ff..b6ddcbb57ffeef47e2f33950eff34389d8a5285b 100644 (file)
@@ -221,7 +221,7 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
   }): Promise<VideoStatsTimeserie> {
     const { video, metric } = options
 
-    const { groupInterval, sqlInterval, startDate, endDate } = buildGroupByAndBoundaries(options.startDate, options.endDate)
+    const { groupInterval, startDate, endDate } = buildGroupByAndBoundaries(options.startDate, options.endDate)
 
     const selectMetrics: { [ id in VideoStatsTimeserieMetric ]: string } = {
       viewers: 'COUNT("localVideoViewer"."id")',
@@ -230,9 +230,9 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
 
     const query = `WITH "intervals" AS (
       SELECT
-        "time" AS "startDate", "time" + :sqlInterval::interval as "endDate"
+        "time" AS "startDate", "time" + :groupInterval::interval as "endDate"
       FROM
-        generate_series(:startDate::timestamptz, :endDate::timestamptz, :sqlInterval::interval) serie("time")
+        generate_series(:startDate::timestamptz, :endDate::timestamptz, :groupInterval::interval) serie("time")
     )
     SELECT "intervals"."startDate" as "date", COALESCE(${selectMetrics[metric]}, 0) AS value
     FROM
@@ -249,7 +249,7 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
       replacements: {
         startDate,
         endDate,
-        sqlInterval,
+        groupInterval,
         videoId: video.id
       }
     }
index 4db76fe8903c33ab1072a98a5a67e15860431fd0..fd3aba188a33e8f74da18e760056be451c377898 100644 (file)
@@ -110,21 +110,21 @@ describe('Test views timeserie stats', function () {
 
     it('Should use a custom start/end date', async function () {
       const now = new Date()
-      const tenDaysAgo = new Date()
-      tenDaysAgo.setDate(tenDaysAgo.getDate() - 9)
+      const twentyDaysAgo = new Date()
+      twentyDaysAgo.setDate(twentyDaysAgo.getDate() - 19)
 
       const result = await servers[0].videoStats.getTimeserieStats({
         videoId: vodVideoId,
         metric: 'aggregateWatchTime',
-        startDate: tenDaysAgo,
+        startDate: twentyDaysAgo,
         endDate: now
       })
 
-      expect(result.groupInterval).to.equal('one_day')
-      expect(result.data).to.have.lengthOf(10)
+      expect(result.groupInterval).to.equal('day')
+      expect(result.data).to.have.lengthOf(20)
 
       const first = result.data[0]
-      expect(new Date(first.date).toLocaleDateString()).to.equal(tenDaysAgo.toLocaleDateString())
+      expect(new Date(first.date).toLocaleDateString()).to.equal(twentyDaysAgo.toLocaleDateString())
 
       expectInterval(result, 24 * 3600 * 1000)
       expectTodayLastValue(result, 9)
@@ -142,7 +142,7 @@ describe('Test views timeserie stats', function () {
         endDate: now
       })
 
-      expect(result.groupInterval).to.equal('one_hour')
+      expect(result.groupInterval).to.equal('hour')
       expect(result.data).to.have.length.above(24).and.below(50)
 
       expectInterval(result, 3600 * 1000)
@@ -152,7 +152,7 @@ describe('Test views timeserie stats', function () {
     it('Should automatically group by ten minutes', async function () {
       const now = new Date()
       const twoHoursAgo = new Date()
-      twoHoursAgo.setHours(twoHoursAgo.getHours() - 1)
+      twoHoursAgo.setHours(twoHoursAgo.getHours() - 4)
 
       const result = await servers[0].videoStats.getTimeserieStats({
         videoId: vodVideoId,
@@ -161,8 +161,8 @@ describe('Test views timeserie stats', function () {
         endDate: now
       })
 
-      expect(result.groupInterval).to.equal('ten_minutes')
-      expect(result.data).to.have.length.above(6).and.below(18)
+      expect(result.groupInterval).to.equal('10 minutes')
+      expect(result.data).to.have.length.above(20).and.below(30)
 
       expectInterval(result, 60 * 10 * 1000)
       expectTodayLastValue(result, 9)
@@ -180,7 +180,7 @@ describe('Test views timeserie stats', function () {
         endDate: now
       })
 
-      expect(result.groupInterval).to.equal('one_minute')
+      expect(result.groupInterval).to.equal('minute')
       expect(result.data).to.have.length.above(20).and.below(40)
 
       expectInterval(result, 60 * 1000)
index 5c4c9df2ab10f702a32a961cefaa4d3ca38aa287..4a6fdaa717e0e62a8a387853b224a31eee247459 100644 (file)
@@ -1,6 +1,5 @@
 export * from './video-stats-overall.model'
 export * from './video-stats-retention.model'
-export * from './video-stats-timeserie-group-interval.type'
 export * from './video-stats-timeserie-query.model'
 export * from './video-stats-timeserie-metric.type'
 export * from './video-stats-timeserie.model'
diff --git a/shared/models/videos/stats/video-stats-timeserie-group-interval.type.ts b/shared/models/videos/stats/video-stats-timeserie-group-interval.type.ts
deleted file mode 100644 (file)
index 9609ecb..0000000
+++ /dev/null
@@ -1 +0,0 @@
-export type VideoStatsTimeserieGroupInterval = 'one_day' | 'one_hour' | 'ten_minutes' | 'one_minute'
index 99bbbe2e32daaf2667f64bc41ece0c98eb3174c2..4a0e208dffea1c6f7730f8b4db20d4113824fdde 100644 (file)
@@ -1,7 +1,5 @@
-import { VideoStatsTimeserieGroupInterval } from './video-stats-timeserie-group-interval.type'
-
 export interface VideoStatsTimeserie {
-  groupInterval: VideoStatsTimeserieGroupInterval
+  groupInterval: string
 
   data: {
     date: string