]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Use inner join and document code for viewr stats for channels
authorRigel Kent <sendmemail@rigelk.eu>
Mon, 30 Mar 2020 10:06:46 +0000 (12:06 +0200)
committerChocobozzz <chocobozzz@cpy.re>
Tue, 31 Mar 2020 08:29:24 +0000 (10:29 +0200)
client/package.json
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
client/src/app/shared/video-channel/video-channel.model.ts
client/yarn.lock
server/models/video/video-channel.ts
server/tests/api/videos/video-channels.ts
shared/models/videos/channel/video-channel.model.ts

index c6a5fa1bb375a423e017666adf071c181bd6f8c0..52647ce1dbe4af68bb505aab3b78bfa76a6f7155 100644 (file)
@@ -56,6 +56,7 @@
     "@ngx-loading-bar/router": "^4.2.0",
     "@ngx-meta/core": "^8.0.2",
     "@ngx-translate/i18n-polyfill": "^1.0.0",
+    "@types/chart.js": "^2.9.16",
     "@types/core-js": "^2.5.2",
     "@types/debug": "^4.1.5",
     "@types/hls.js": "^0.12.4",
index 94e74938b84215cf6b42b97c7568e5fb43009a44..03d45227eca8b341160fd7f3eb113da7822bce62 100644 (file)
@@ -20,7 +20,7 @@
       <div i18n class="video-channel-followers">{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div>
 
       <div *ngIf="!isInSmallView" class="w-100 d-flex justify-content-end">
-        <p-chart *ngIf="videoChannelsData && videoChannelsData[i]" type="line" [data]="videoChannelsData[i]" [options]="chartOptions" width="40vw" height="100px"></p-chart>
+        <p-chart *ngIf="videoChannelsChartData && videoChannelsChartData[i]" type="line" [data]="videoChannelsChartData[i]" [options]="chartOptions" width="40vw" height="100px"></p-chart>
       </div>
     </div>
 
index 27a1576213cb853be9797a877ad6783f19a8355e..153fc01270977f1e1a18a711fb3bbb23521166a3 100644 (file)
@@ -8,7 +8,8 @@ import { ScreenService } from '@app/shared/misc/screen.service'
 import { User } from '@app/shared'
 import { flatMap } from 'rxjs/operators'
 import { I18n } from '@ngx-translate/i18n-polyfill'
-import { minBy, maxBy } from 'lodash-es'
+import { min, minBy, max, maxBy } from 'lodash-es'
+import { ChartData } from 'chart.js'
 
 @Component({
   selector: 'my-account-video-channels',
@@ -17,7 +18,7 @@ import { minBy, maxBy } from 'lodash-es'
 })
 export class MyAccountVideoChannelsComponent implements OnInit {
   videoChannels: VideoChannel[] = []
-  videoChannelsData: any[]
+  videoChannelsChartData: ChartData[]
   videoChannelsMinimumDailyViews = 0
   videoChannelsMaximumDailyViews: number
 
@@ -125,7 +126,9 @@ export class MyAccountVideoChannelsComponent implements OnInit {
         .pipe(flatMap(() => this.videoChannelService.listAccountVideoChannels(this.user.account, null, true)))
         .subscribe(res => {
           this.videoChannels = res.data
-          this.videoChannelsData = this.videoChannels.map(v => ({
+
+          // chart data
+          this.videoChannelsChartData = this.videoChannels.map(v => ({
             labels: v.viewsPerDay.map(day => day.date.toLocaleDateString()),
             datasets: [
               {
@@ -135,9 +138,22 @@ export class MyAccountVideoChannelsComponent implements OnInit {
                   borderColor: "#c6c6c6"
               }
             ]
-          }))
-          this.videoChannelsMinimumDailyViews = minBy(this.videoChannels.map(v => minBy(v.viewsPerDay, day => day.views)), day => day.views).views
-          this.videoChannelsMaximumDailyViews = maxBy(this.videoChannels.map(v => maxBy(v.viewsPerDay, day => day.views)), day => day.views).views
+          } as ChartData))
+
+          // chart options that depend on chart data:
+          // we don't want to skew values and have min at 0, so we define what the floor/ceiling is here
+          this.videoChannelsMinimumDailyViews = min(
+            this.videoChannels.map(v => minBy( // compute local minimum daily views for each channel, by their "views" attribute
+              v.viewsPerDay,
+              day => day.views
+            ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute
+          )
+          this.videoChannelsMaximumDailyViews = max(
+            this.videoChannels.map(v => maxBy( // compute local maximum daily views for each channel, by their "views" attribute
+              v.viewsPerDay,
+              day => day.views
+            ).views) // the object returned is a ViewPerDate, so we still need to get the views attribute
+          )
         })
   }
 }
index c93af0ca50916e91e120d9fd94ba1aa65340e266..617d6d44d93b88b7427e6fa8a041f4e6dd059415 100644 (file)
@@ -1,4 +1,4 @@
-import { VideoChannel as ServerVideoChannel, viewsPerTime } from '../../../../../shared/models/videos'
+import { VideoChannel as ServerVideoChannel, ViewsPerDate } from '../../../../../shared/models/videos'
 import { Actor } from '../actor/actor.model'
 import { Account } from '../../../../../shared/models/actors'
 
@@ -12,7 +12,7 @@ export class VideoChannel extends Actor implements ServerVideoChannel {
   ownerAccount?: Account
   ownerBy?: string
   ownerAvatarUrl?: string
-  viewsPerDay?: viewsPerTime[]
+  viewsPerDay?: ViewsPerDate[]
 
   constructor (hash: ServerVideoChannel) {
     super(hash)
index b3f38a664201b35f748d78b0c1562634b7c8269d..e34da3d6e04b4b25df1b9ebfcfef7b7bd2668d9a 100644 (file)
   dependencies:
     "@types/node" "*"
 
+"@types/chart.js@^2.9.16":
+  version "2.9.16"
+  resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.9.16.tgz#ac9d268fa192c0ec0efd740f802683e3ed97642c"
+  integrity sha512-Mofg7xFIeAWME46YMVKHPCyUz2Z0KsVMNE1f4oF3T74mK3RiPQxOm9qzoeNTyMs6lpl4x0tiHL+Wsz2DHCxQlQ==
+  dependencies:
+    moment "^2.10.2"
+
 "@types/core-js@^2.5.2":
   version "2.5.2"
   resolved "https://registry.yarnpkg.com/@types/core-js/-/core-js-2.5.2.tgz#d4c25420044d4a5b65e00a82fc04b7824b62691f"
index 78fc3d7e4810da3e1e87030258a5913f90b45345..642e129ff7828e036d225c86e92d6aed7ef7454d 100644 (file)
@@ -166,42 +166,43 @@ export type SummaryOptions = {
       VideoModel
     ]
   },
-  [ScopeNames.WITH_STATS]: (options: AvailableWithStatsOptions = { daysPrior: 30 }) => ({
-    attributes: {
-      include: [
-        [
-          literal(
-            '(' +
-            `SELECT string_agg(concat_ws('|', t.day, t.views), ',') ` +
-            'FROM ( ' +
-              'WITH ' +
-                'days AS ( ' +
-                  `SELECT generate_series(date_trunc('day', now()) - '${options.daysPrior} day'::interval, ` +
-                         `date_trunc('day', now()), '1 day'::interval) AS day ` +
-                '), ' +
-                'views AS ( ' +
-                  'SELECT * ' +
-                  'FROM "videoView" ' +
-                  'WHERE "videoView"."videoId" IN ( ' +
-                    'SELECT "video"."id" ' +
-                    'FROM "video" ' +
+  [ScopeNames.WITH_STATS]: (options: AvailableWithStatsOptions = { daysPrior: 30 }) => {
+    const daysPrior = parseInt(options.daysPrior + '', 10)
+
+    return {
+      attributes: {
+        include: [
+          [
+            literal(
+              '(' +
+              `SELECT string_agg(concat_ws('|', t.day, t.views), ',') ` +
+              'FROM ( ' +
+                'WITH ' +
+                  'days AS ( ' +
+                    `SELECT generate_series(date_trunc('day', now()) - '${daysPrior} day'::interval, ` +
+                          `date_trunc('day', now()), '1 day'::interval) AS day ` +
+                  '), ' +
+                  'views AS ( ' +
+                    'SELECT v.* ' +
+                    'FROM "videoView" AS v ' +
+                    'INNER JOIN "video" ON "video"."id" = v."videoId" ' +
                     'WHERE "video"."channelId" = "VideoChannelModel"."id" ' +
                   ') ' +
-                ') ' +
-              'SELECT days.day AS day, ' +
-                     'COALESCE(SUM(views.views), 0) AS views ' +
-              'FROM days ' +
-              `LEFT JOIN views ON date_trunc('day', "views"."startDate") = date_trunc('day', days.day) ` +
-              'GROUP BY 1 ' +
-              'ORDER BY day ' +
-            ') t' +
-            ')'
-          ),
-          'viewsPerDay'
+                'SELECT days.day AS day, ' +
+                      'COALESCE(SUM(views.views), 0) AS views ' +
+                'FROM days ' +
+                `LEFT JOIN views ON date_trunc('day', "views"."startDate") = date_trunc('day', days.day) ` +
+                'GROUP BY day ' +
+                'ORDER BY day ' +
+              ') t' +
+              ')'
+            ),
+            'viewsPerDay'
+          ]
         ]
-      ]
+      }
     }
-  })
+  }
 }))
 @Table({
   tableName: 'videoChannel',
index bde45584d8bc6622fc6e7133310301fae03cfd6c..876a6ab66381e92afc615a28684e0efeb8936d86 100644 (file)
@@ -2,7 +2,7 @@
 
 import * as chai from 'chai'
 import 'mocha'
-import { User, Video, VideoChannel, viewsPerTime, VideoDetails } from '../../../../shared/index'
+import { User, Video, VideoChannel, ViewsPerDate, VideoDetails } from '../../../../shared/index'
 import {
   cleanupTests,
   createUser,
@@ -376,7 +376,7 @@ describe('Test video channels', function () {
       res.body.data.forEach((channel: VideoChannel) => {
         expect(channel).to.haveOwnProperty('viewsPerDay')
         expect(channel.viewsPerDay).to.have.length(30 + 1) // daysPrior + today
-        channel.viewsPerDay.forEach((v: viewsPerTime) => {
+        channel.viewsPerDay.forEach((v: ViewsPerDate) => {
           expect(v.date).to.be.an('string')
           expect(v.views).to.equal(0)
         })
index 5fe6609d907275543c09268d6ca8f66ed66120d7..421004e68957881628d47853a5183c6184b00147 100644 (file)
@@ -2,7 +2,7 @@ import { Actor } from '../../actors/actor.model'
 import { Account } from '../../actors/index'
 import { Avatar } from '../../avatars'
 
-export type viewsPerTime = {
+export type ViewsPerDate = {
   date: Date
   views: number
 }
@@ -13,7 +13,7 @@ export interface VideoChannel extends Actor {
   support: string
   isLocal: boolean
   ownerAccount?: Account
-  viewsPerDay?: viewsPerTime[] // chronologically ordered
+  viewsPerDay?: ViewsPerDate[] // chronologically ordered
 }
 
 export interface VideoChannelSummary {