Use inner join and document code for viewr stats for channels

pull/2594/head
Rigel Kent 2020-03-30 12:06:46 +02:00 committed by Chocobozzz
parent 714bfcc556
commit 3d527ba173
8 changed files with 70 additions and 45 deletions

View 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",

View 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>

View 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
)
})
}
}

View 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)

View File

@ -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"

View 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',

View 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)
})

View 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 {