diff --git a/client/package.json b/client/package.json
index c6a5fa1bb..52647ce1d 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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",
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
index 94e74938b..03d45227e 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html
@@ -20,7 +20,7 @@
{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
index 27a157621..153fc0127 100644
--- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
+++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts
@@ -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
+ )
})
}
}
diff --git a/client/src/app/shared/video-channel/video-channel.model.ts b/client/src/app/shared/video-channel/video-channel.model.ts
index c93af0ca5..617d6d44d 100644
--- a/client/src/app/shared/video-channel/video-channel.model.ts
+++ b/client/src/app/shared/video-channel/video-channel.model.ts
@@ -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)
diff --git a/client/yarn.lock b/client/yarn.lock
index b3f38a664..e34da3d6e 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -1149,6 +1149,13 @@
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"
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts
index 78fc3d7e4..642e129ff 100644
--- a/server/models/video/video-channel.ts
+++ b/server/models/video/video-channel.ts
@@ -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',
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts
index bde45584d..876a6ab66 100644
--- a/server/tests/api/videos/video-channels.ts
+++ b/server/tests/api/videos/video-channels.ts
@@ -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)
})
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts
index 5fe6609d9..421004e68 100644
--- a/shared/models/videos/channel/video-channel.model.ts
+++ b/shared/models/videos/channel/video-channel.model.ts
@@ -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 {