1
0
Fork 0

Fix overall viewers stats with start/end dates

This commit is contained in:
Chocobozzz 2022-11-24 10:07:58 +01:00
parent 72cd9f303a
commit 624ea01b10
No known key found for this signature in database
GPG key ID: 583A612D890159BE
2 changed files with 138 additions and 42 deletions

View file

@ -112,17 +112,14 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
replacements: { videoId: video.id } as any replacements: { videoId: video.id } as any
} }
let dateWhere = '' if (startDate) queryOptions.replacements.startDate = startDate
if (endDate) queryOptions.replacements.endDate = endDate
if (startDate) { const buildWatchTimePromise = () => {
dateWhere += ' AND "localVideoViewer"."startDate" >= :startDate' let watchTimeDateWhere = ''
queryOptions.replacements.startDate = startDate
}
if (endDate) { if (startDate) watchTimeDateWhere += ' AND "localVideoViewer"."startDate" >= :startDate'
dateWhere += ' AND "localVideoViewer"."endDate" <= :endDate' if (endDate) watchTimeDateWhere += ' AND "localVideoViewer"."endDate" <= :endDate'
queryOptions.replacements.endDate = endDate
}
const watchTimeQuery = `SELECT ` + const watchTimeQuery = `SELECT ` +
`COUNT("localVideoViewer"."id") AS "totalViewers", ` + `COUNT("localVideoViewer"."id") AS "totalViewers", ` +
@ -130,40 +127,73 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
`AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` + `AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` +
`FROM "localVideoViewer" ` + `FROM "localVideoViewer" ` +
`INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` + `INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` +
`WHERE "videoId" = :videoId ${dateWhere}` `WHERE "videoId" = :videoId ${watchTimeDateWhere}`
const watchTimePromise = LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, queryOptions) return LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, queryOptions)
}
const watchPeakQuery = `WITH "watchPeakValues" AS ( const buildWatchPeakPromise = () => {
let watchPeakDateWhereStart = ''
let watchPeakDateWhereEnd = ''
if (startDate) {
watchPeakDateWhereStart += ' AND "localVideoViewer"."startDate" >= :startDate'
watchPeakDateWhereEnd += ' AND "localVideoViewer"."endDate" >= :startDate'
}
if (endDate) {
watchPeakDateWhereStart += ' AND "localVideoViewer"."startDate" <= :endDate'
watchPeakDateWhereEnd += ' AND "localVideoViewer"."endDate" <= :endDate'
}
// Add viewers that were already here, before our start date
const beforeWatchersQuery = startDate
// eslint-disable-next-line max-len
? `SELECT COUNT(*) AS "total" FROM "localVideoViewer" WHERE "localVideoViewer"."startDate" < :startDate AND "localVideoViewer"."endDate" >= :startDate`
: `SELECT 0 AS "total"`
const watchPeakQuery = `WITH
"beforeWatchers" AS (${beforeWatchersQuery}),
"watchPeakValues" AS (
SELECT "startDate" AS "dateBreakpoint", 1 AS "inc" SELECT "startDate" AS "dateBreakpoint", 1 AS "inc"
FROM "localVideoViewer" FROM "localVideoViewer"
WHERE "videoId" = :videoId ${dateWhere} WHERE "videoId" = :videoId ${watchPeakDateWhereStart}
UNION ALL UNION ALL
SELECT "endDate" AS "dateBreakpoint", -1 AS "inc" SELECT "endDate" AS "dateBreakpoint", -1 AS "inc"
FROM "localVideoViewer" FROM "localVideoViewer"
WHERE "videoId" = :videoId ${dateWhere} WHERE "videoId" = :videoId ${watchPeakDateWhereEnd}
) )
SELECT "dateBreakpoint", "concurrent" SELECT "dateBreakpoint", "concurrent"
FROM ( FROM (
SELECT "dateBreakpoint", SUM(SUM("inc")) OVER (ORDER BY "dateBreakpoint") AS "concurrent" SELECT "dateBreakpoint", SUM(SUM("inc")) OVER (ORDER BY "dateBreakpoint") + (SELECT "total" FROM "beforeWatchers") AS "concurrent"
FROM "watchPeakValues" FROM "watchPeakValues"
GROUP BY "dateBreakpoint" GROUP BY "dateBreakpoint"
) tmp ) tmp
ORDER BY "concurrent" DESC ORDER BY "concurrent" DESC
FETCH FIRST 1 ROW ONLY` FETCH FIRST 1 ROW ONLY`
const watchPeakPromise = LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, queryOptions)
return LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, queryOptions)
}
const buildCountriesPromise = () => {
let countryDateWhere = ''
if (startDate) countryDateWhere += ' AND "localVideoViewer"."endDate" >= :startDate'
if (endDate) countryDateWhere += ' AND "localVideoViewer"."startDate" <= :endDate'
const countriesQuery = `SELECT country, COUNT(country) as viewers ` + const countriesQuery = `SELECT country, COUNT(country) as viewers ` +
`FROM "localVideoViewer" ` + `FROM "localVideoViewer" ` +
`WHERE "videoId" = :videoId AND country IS NOT NULL ${dateWhere} ` + `WHERE "videoId" = :videoId AND country IS NOT NULL ${countryDateWhere} ` +
`GROUP BY country ` + `GROUP BY country ` +
`ORDER BY viewers DESC` `ORDER BY viewers DESC`
const countriesPromise = LocalVideoViewerModel.sequelize.query<any>(countriesQuery, queryOptions)
return LocalVideoViewerModel.sequelize.query<any>(countriesQuery, queryOptions)
}
const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([ const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([
watchTimePromise, buildWatchTimePromise(),
watchPeakPromise, buildWatchPeakPromise(),
countriesPromise buildCountriesPromise()
]) ])
const viewersPeak = rowsWatchPeak.length !== 0 const viewersPeak = rowsWatchPeak.length !== 0

View file

@ -4,6 +4,56 @@ import { expect } from 'chai'
import { FfmpegCommand } from 'fluent-ffmpeg' import { FfmpegCommand } from 'fluent-ffmpeg'
import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared' import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared'
import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands' import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands'
import { wait } from '@shared/core-utils'
import { VideoStatsOverall } from '@shared/models'
/**
*
* Simulate 5 sections of viewers
* * user0 started and ended before start date
* * user1 started before start date and ended in the interval
* * user2 started started in the interval and ended after end date
* * user3 started and ended in the interval
* * user4 started and ended after end date
*/
async function simulateComplexViewers (servers: PeerTubeServer[], videoUUID: string) {
const user0 = '8.8.8.8,127.0.0.1'
const user1 = '8.8.8.8,127.0.0.1'
const user2 = '8.8.8.9,127.0.0.1'
const user3 = '8.8.8.10,127.0.0.1'
const user4 = '8.8.8.11,127.0.0.1'
await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user0 }) // User 0 starts
await wait(500)
await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user1 }) // User 1 starts
await servers[0].views.view({ id: videoUUID, currentTime: 2, xForwardedFor: user0 }) // User 0 ends
await wait(500)
const startDate = new Date().toISOString()
await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user2 }) // User 2 starts
await wait(500)
await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user3 }) // User 3 starts
await wait(500)
await servers[0].views.view({ id: videoUUID, currentTime: 4, xForwardedFor: user1 }) // User 1 ends
await wait(500)
await servers[0].views.view({ id: videoUUID, currentTime: 3, xForwardedFor: user3 }) // User 3 ends
await wait(500)
const endDate = new Date().toISOString()
await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user4 }) // User 4 starts
await servers[0].views.view({ id: videoUUID, currentTime: 5, xForwardedFor: user2 }) // User 2 ends
await wait(500)
await servers[0].views.view({ id: videoUUID, currentTime: 1, xForwardedFor: user4 }) // User 4 ends
await processViewersStats(servers)
return { startDate, endDate }
}
describe('Test views overall stats', function () { describe('Test views overall stats', function () {
let servers: PeerTubeServer[] let servers: PeerTubeServer[]
@ -237,6 +287,22 @@ describe('Test views overall stats', function () {
expect(new Date(stats.viewersPeakDate)).to.be.below(before2Watchers) expect(new Date(stats.viewersPeakDate)).to.be.below(before2Watchers)
} }
}) })
it('Should complex filter peak viewers by date', async function () {
this.timeout(60000)
const { startDate, endDate } = await simulateComplexViewers(servers, videoUUID)
const expectCorrect = (stats: VideoStatsOverall) => {
expect(stats.viewersPeak).to.equal(3)
expect(new Date(stats.viewersPeakDate)).to.be.above(new Date(startDate)).and.below(new Date(endDate))
}
expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate, endDate }))
expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate }))
expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, endDate }))
expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID }))
})
}) })
describe('Test countries', function () { describe('Test countries', function () {