diff options
author | Chocobozzz <me@florianbigard.com> | 2022-05-05 14:12:57 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2022-05-05 14:13:14 +0200 |
commit | 49f0468d44468528c2fb2c8b0efd19cdaeeec43d (patch) | |
tree | 4fd07d5da74506729a1cbbe67548bfd9c6c76849 /server | |
parent | f18a060a83d7053897173b2a24fb7984893131c7 (diff) | |
download | PeerTube-49f0468d44468528c2fb2c8b0efd19cdaeeec43d.tar.gz PeerTube-49f0468d44468528c2fb2c8b0efd19cdaeeec43d.tar.zst PeerTube-49f0468d44468528c2fb2c8b0efd19cdaeeec43d.zip |
Add filter by start/end date overall stats in api
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/videos/stats.ts | 9 | ||||
-rw-r--r-- | server/middlewares/validators/videos/video-stats.ts | 10 | ||||
-rw-r--r-- | server/models/view/local-video-viewer.ts | 36 | ||||
-rw-r--r-- | server/tests/api/check-params/views.ts | 24 | ||||
-rw-r--r-- | server/tests/api/views/video-views-overall-stats.ts | 21 |
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 @@ | |||
1 | import express from 'express' | 1 | import express from 'express' |
2 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' | 2 | import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer' |
3 | import { VideoStatsTimeserieMetric, VideoStatsTimeserieQuery } from '@shared/models' | 3 | import { VideoStatsOverallQuery, VideoStatsTimeserieMetric, VideoStatsTimeserieQuery } from '@shared/models' |
4 | import { | 4 | import { |
5 | asyncMiddleware, | 5 | asyncMiddleware, |
6 | authenticate, | 6 | authenticate, |
@@ -39,8 +39,13 @@ export { | |||
39 | 39 | ||
40 | async function getOverallStats (req: express.Request, res: express.Response) { | 40 | async 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 | |||
10 | const videoOverallStatsValidator = [ | 10 | const 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 | }) |