aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/videos/stats.ts9
-rw-r--r--server/middlewares/validators/videos/video-stats.ts10
-rw-r--r--server/models/view/local-video-viewer.ts36
-rw-r--r--server/tests/api/check-params/views.ts24
-rw-r--r--server/tests/api/views/video-views-overall-stats.ts21
5 files changed, 88 insertions, 12 deletions
diff --git a/server/controllers/api/videos/stats.ts b/server/controllers/api/videos/stats.ts
index 71452d9f0..30e2bb06c 100644
--- a/server/controllers/api/videos/stats.ts
+++ b/server/controllers/api/videos/stats.ts
@@ -1,6 +1,6 @@
1import express from 'express' 1import express from 'express'
2import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' 2import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer'
3import { VideoStatsTimeserieMetric, VideoStatsTimeserieQuery } from '@shared/models' 3import { VideoStatsOverallQuery, VideoStatsTimeserieMetric, VideoStatsTimeserieQuery } from '@shared/models'
4import { 4import {
5 asyncMiddleware, 5 asyncMiddleware,
6 authenticate, 6 authenticate,
@@ -39,8 +39,13 @@ export {
39 39
40async function getOverallStats (req: express.Request, res: express.Response) { 40async function getOverallStats (req: express.Request, res: express.Response) {
41 const video = res.locals.videoAll 41 const video = res.locals.videoAll
42 const query = req.query as VideoStatsOverallQuery
42 43
43 const stats = await LocalVideoViewerModel.getOverallStats(video) 44 const stats = await LocalVideoViewerModel.getOverallStats({
45 video,
46 startDate: query.startDate,
47 endDate: query.endDate
48 })
44 49
45 return res.json(stats) 50 return res.json(stats)
46} 51}
diff --git a/server/middlewares/validators/videos/video-stats.ts b/server/middlewares/validators/videos/video-stats.ts
index 12509abde..f17fbcc09 100644
--- a/server/middlewares/validators/videos/video-stats.ts
+++ b/server/middlewares/validators/videos/video-stats.ts
@@ -10,6 +10,16 @@ import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVi
10const videoOverallStatsValidator = [ 10const videoOverallStatsValidator = [
11 isValidVideoIdParam('videoId'), 11 isValidVideoIdParam('videoId'),
12 12
13 query('startDate')
14 .optional()
15 .custom(isDateValid)
16 .withMessage('Should have a valid start date'),
17
18 query('endDate')
19 .optional()
20 .custom(isDateValid)
21 .withMessage('Should have a valid end date'),
22
13 async (req: express.Request, res: express.Response, next: express.NextFunction) => { 23 async (req: express.Request, res: express.Response, next: express.NextFunction) => {
14 logger.debug('Checking videoOverallStatsValidator parameters', { parameters: req.body }) 24 logger.debug('Checking videoOverallStatsValidator parameters', { parameters: req.body })
15 25
diff --git a/server/models/view/local-video-viewer.ts b/server/models/view/local-video-viewer.ts
index 5928ba5f6..2862f8b96 100644
--- a/server/models/view/local-video-viewer.ts
+++ b/server/models/view/local-video-viewer.ts
@@ -100,10 +100,28 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
100 }) 100 })
101 } 101 }
102 102
103 static async getOverallStats (video: MVideo): Promise<VideoStatsOverall> { 103 static async getOverallStats (options: {
104 const options = { 104 video: MVideo
105 startDate?: string
106 endDate?: string
107 }): Promise<VideoStatsOverall> {
108 const { video, startDate, endDate } = options
109
110 const queryOptions = {
105 type: QueryTypes.SELECT as QueryTypes.SELECT, 111 type: QueryTypes.SELECT as QueryTypes.SELECT,
106 replacements: { videoId: video.id } 112 replacements: { videoId: video.id } as any
113 }
114
115 let dateWhere = ''
116
117 if (startDate) {
118 dateWhere += ' AND "localVideoViewer"."startDate" >= :startDate'
119 queryOptions.replacements.startDate = startDate
120 }
121
122 if (endDate) {
123 dateWhere += ' AND "localVideoViewer"."endDate" <= :endDate'
124 queryOptions.replacements.endDate = endDate
107 } 125 }
108 126
109 const watchTimeQuery = `SELECT ` + 127 const watchTimeQuery = `SELECT ` +
@@ -111,9 +129,9 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
111 `AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` + 129 `AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` +
112 `FROM "localVideoViewer" ` + 130 `FROM "localVideoViewer" ` +
113 `INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` + 131 `INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` +
114 `WHERE "videoId" = :videoId` 132 `WHERE "videoId" = :videoId ${dateWhere}`
115 133
116 const watchTimePromise = LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, options) 134 const watchTimePromise = LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, queryOptions)
117 135
118 const watchPeakQuery = `WITH "watchPeakValues" AS ( 136 const watchPeakQuery = `WITH "watchPeakValues" AS (
119 SELECT "startDate" AS "dateBreakpoint", 1 AS "inc" 137 SELECT "startDate" AS "dateBreakpoint", 1 AS "inc"
@@ -122,7 +140,7 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
122 UNION ALL 140 UNION ALL
123 SELECT "endDate" AS "dateBreakpoint", -1 AS "inc" 141 SELECT "endDate" AS "dateBreakpoint", -1 AS "inc"
124 FROM "localVideoViewer" 142 FROM "localVideoViewer"
125 WHERE "videoId" = :videoId 143 WHERE "videoId" = :videoId ${dateWhere}
126 ) 144 )
127 SELECT "dateBreakpoint", "concurrent" 145 SELECT "dateBreakpoint", "concurrent"
128 FROM ( 146 FROM (
@@ -132,14 +150,14 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
132 ) tmp 150 ) tmp
133 ORDER BY "concurrent" DESC 151 ORDER BY "concurrent" DESC
134 FETCH FIRST 1 ROW ONLY` 152 FETCH FIRST 1 ROW ONLY`
135 const watchPeakPromise = LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, options) 153 const watchPeakPromise = LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, queryOptions)
136 154
137 const countriesQuery = `SELECT country, COUNT(country) as viewers ` + 155 const countriesQuery = `SELECT country, COUNT(country) as viewers ` +
138 `FROM "localVideoViewer" ` + 156 `FROM "localVideoViewer" ` +
139 `WHERE "videoId" = :videoId AND country IS NOT NULL ` + 157 `WHERE "videoId" = :videoId AND country IS NOT NULL ${dateWhere} ` +
140 `GROUP BY country ` + 158 `GROUP BY country ` +
141 `ORDER BY viewers DESC` 159 `ORDER BY viewers DESC`
142 const countriesPromise = LocalVideoViewerModel.sequelize.query<any>(countriesQuery, options) 160 const countriesPromise = LocalVideoViewerModel.sequelize.query<any>(countriesQuery, queryOptions)
143 161
144 const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([ 162 const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([
145 watchTimePromise, 163 watchTimePromise,
diff --git a/server/tests/api/check-params/views.ts b/server/tests/api/check-params/views.ts
index 3dba2a42e..fe037b145 100644
--- a/server/tests/api/check-params/views.ts
+++ b/server/tests/api/check-params/views.ts
@@ -75,8 +75,30 @@ describe('Test videos views', function () {
75 }) 75 })
76 }) 76 })
77 77
78 it('Should fail with an invalid start date', async function () {
79 await servers[0].videoStats.getOverallStats({
80 videoId,
81 startDate: 'fake' as any,
82 endDate: new Date().toISOString(),
83 expectedStatus: HttpStatusCode.BAD_REQUEST_400
84 })
85 })
86
87 it('Should fail with an invalid end date', async function () {
88 await servers[0].videoStats.getOverallStats({
89 videoId,
90 startDate: new Date().toISOString(),
91 endDate: 'fake' as any,
92 expectedStatus: HttpStatusCode.BAD_REQUEST_400
93 })
94 })
95
78 it('Should succeed with the correct parameters', async function () { 96 it('Should succeed with the correct parameters', async function () {
79 await servers[0].videoStats.getOverallStats({ videoId }) 97 await servers[0].videoStats.getOverallStats({
98 videoId,
99 startDate: new Date().toISOString(),
100 endDate: new Date().toISOString()
101 })
80 }) 102 })
81 }) 103 })
82 104
diff --git a/server/tests/api/views/video-views-overall-stats.ts b/server/tests/api/views/video-views-overall-stats.ts
index 72b072c96..53b8f0d4b 100644
--- a/server/tests/api/views/video-views-overall-stats.ts
+++ b/server/tests/api/views/video-views-overall-stats.ts
@@ -141,6 +141,27 @@ describe('Test views overall stats', function () {
141 } 141 }
142 }) 142 })
143 143
144 it('Should filter overall stats by date', async function () {
145 this.timeout(60000)
146
147 const beforeView = new Date()
148
149 await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 3 ] })
150 await processViewersStats(servers)
151
152 {
153 const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId, startDate: beforeView.toISOString() })
154 expect(stats.averageWatchTime).to.equal(3)
155 expect(stats.totalWatchTime).to.equal(3)
156 }
157
158 {
159 const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId, endDate: beforeView.toISOString() })
160 expect(stats.averageWatchTime).to.equal(22)
161 expect(stats.totalWatchTime).to.equal(88)
162 }
163 })
164
144 after(async function () { 165 after(async function () {
145 await stopFfmpeg(command) 166 await stopFfmpeg(command)
146 }) 167 })