Fix overall viewers stats with start/end dates
This commit is contained in:
parent
72cd9f303a
commit
624ea01b10
2 changed files with 138 additions and 42 deletions
|
@ -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
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
Loading…
Reference in a new issue