diff options
author | Chocobozzz <me@florianbigard.com> | 2022-04-08 10:22:56 +0200 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2022-04-15 09:49:35 +0200 |
commit | 3eda9b775ae700ac544e8c5588514627796b83cd (patch) | |
tree | 97ec1fdfce274e83d976352f5b4154c315ee33d7 /server | |
parent | 901bcf5c188ea79350fecd499ad76460b866617b (diff) | |
download | PeerTube-3eda9b775ae700ac544e8c5588514627796b83cd.tar.gz PeerTube-3eda9b775ae700ac544e8c5588514627796b83cd.tar.zst PeerTube-3eda9b775ae700ac544e8c5588514627796b83cd.zip |
Support interactive video stats graph
Diffstat (limited to 'server')
-rw-r--r-- | server/lib/timeserie.ts | 24 | ||||
-rw-r--r-- | server/models/view/local-video-viewer.ts | 8 | ||||
-rw-r--r-- | server/tests/api/views/video-views-timeserie-stats.ts | 22 |
3 files changed, 24 insertions, 30 deletions
diff --git a/server/lib/timeserie.ts b/server/lib/timeserie.ts index d8f700a2f..bd3d1c1ca 100644 --- a/server/lib/timeserie.ts +++ b/server/lib/timeserie.ts | |||
@@ -1,24 +1,17 @@ | |||
1 | import { logger } from '@server/helpers/logger' | 1 | import { logger } from '@server/helpers/logger' |
2 | import { VideoStatsTimeserieGroupInterval } from '@shared/models' | ||
3 | 2 | ||
4 | function buildGroupByAndBoundaries (startDateString: string, endDateString: string) { | 3 | function buildGroupByAndBoundaries (startDateString: string, endDateString: string) { |
5 | const startDate = new Date(startDateString) | 4 | const startDate = new Date(startDateString) |
6 | const endDate = new Date(endDateString) | 5 | const endDate = new Date(endDateString) |
7 | 6 | ||
8 | const groupByMatrix: { [ id in VideoStatsTimeserieGroupInterval ]: string } = { | ||
9 | one_day: '1 day', | ||
10 | one_hour: '1 hour', | ||
11 | ten_minutes: '10 minutes', | ||
12 | one_minute: '1 minute' | ||
13 | } | ||
14 | const groupInterval = buildGroupInterval(startDate, endDate) | 7 | const groupInterval = buildGroupInterval(startDate, endDate) |
15 | 8 | ||
16 | logger.debug('Found "%s" group interval.', groupInterval, { startDate, endDate }) | 9 | logger.debug('Found "%s" group interval.', groupInterval, { startDate, endDate }) |
17 | 10 | ||
18 | // Remove parts of the date we don't need | 11 | // Remove parts of the date we don't need |
19 | if (groupInterval === 'one_day') { | 12 | if (groupInterval.endsWith(' day') || groupInterval.endsWith(' days')) { |
20 | startDate.setHours(0, 0, 0, 0) | 13 | startDate.setHours(0, 0, 0, 0) |
21 | } else if (groupInterval === 'one_hour') { | 14 | } else if (groupInterval.endsWith(' hour') || groupInterval.endsWith(' hours')) { |
22 | startDate.setMinutes(0, 0, 0) | 15 | startDate.setMinutes(0, 0, 0) |
23 | } else { | 16 | } else { |
24 | startDate.setSeconds(0, 0) | 17 | startDate.setSeconds(0, 0) |
@@ -26,7 +19,6 @@ function buildGroupByAndBoundaries (startDateString: string, endDateString: stri | |||
26 | 19 | ||
27 | return { | 20 | return { |
28 | groupInterval, | 21 | groupInterval, |
29 | sqlInterval: groupByMatrix[groupInterval], | ||
30 | startDate, | 22 | startDate, |
31 | endDate | 23 | endDate |
32 | } | 24 | } |
@@ -40,16 +32,18 @@ export { | |||
40 | 32 | ||
41 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
42 | 34 | ||
43 | function buildGroupInterval (startDate: Date, endDate: Date): VideoStatsTimeserieGroupInterval { | 35 | function buildGroupInterval (startDate: Date, endDate: Date): string { |
44 | const aDay = 86400 | 36 | const aDay = 86400 |
45 | const anHour = 3600 | 37 | const anHour = 3600 |
46 | const aMinute = 60 | 38 | const aMinute = 60 |
47 | 39 | ||
48 | const diffSeconds = (endDate.getTime() - startDate.getTime()) / 1000 | 40 | const diffSeconds = (endDate.getTime() - startDate.getTime()) / 1000 |
49 | 41 | ||
50 | if (diffSeconds >= 6 * aDay) return 'one_day' | 42 | if (diffSeconds >= 15 * aDay) return '1 day' |
51 | if (diffSeconds >= 6 * anHour) return 'one_hour' | 43 | if (diffSeconds >= 8 * aDay) return '12 hours' |
52 | if (diffSeconds >= 60 * aMinute) return 'ten_minutes' | 44 | if (diffSeconds >= 4 * aDay) return '6 hours' |
45 | if (diffSeconds >= 15 * anHour) return '1 hour' | ||
46 | if (diffSeconds >= 180 * aMinute) return '10 minutes' | ||
53 | 47 | ||
54 | return 'one_minute' | 48 | return '1 minute' |
55 | } | 49 | } |
diff --git a/server/models/view/local-video-viewer.ts b/server/models/view/local-video-viewer.ts index ad2ad35ca..b6ddcbb57 100644 --- a/server/models/view/local-video-viewer.ts +++ b/server/models/view/local-video-viewer.ts | |||
@@ -221,7 +221,7 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid | |||
221 | }): Promise<VideoStatsTimeserie> { | 221 | }): Promise<VideoStatsTimeserie> { |
222 | const { video, metric } = options | 222 | const { video, metric } = options |
223 | 223 | ||
224 | const { groupInterval, sqlInterval, startDate, endDate } = buildGroupByAndBoundaries(options.startDate, options.endDate) | 224 | const { groupInterval, startDate, endDate } = buildGroupByAndBoundaries(options.startDate, options.endDate) |
225 | 225 | ||
226 | const selectMetrics: { [ id in VideoStatsTimeserieMetric ]: string } = { | 226 | const selectMetrics: { [ id in VideoStatsTimeserieMetric ]: string } = { |
227 | viewers: 'COUNT("localVideoViewer"."id")', | 227 | viewers: 'COUNT("localVideoViewer"."id")', |
@@ -230,9 +230,9 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid | |||
230 | 230 | ||
231 | const query = `WITH "intervals" AS ( | 231 | const query = `WITH "intervals" AS ( |
232 | SELECT | 232 | SELECT |
233 | "time" AS "startDate", "time" + :sqlInterval::interval as "endDate" | 233 | "time" AS "startDate", "time" + :groupInterval::interval as "endDate" |
234 | FROM | 234 | FROM |
235 | generate_series(:startDate::timestamptz, :endDate::timestamptz, :sqlInterval::interval) serie("time") | 235 | generate_series(:startDate::timestamptz, :endDate::timestamptz, :groupInterval::interval) serie("time") |
236 | ) | 236 | ) |
237 | SELECT "intervals"."startDate" as "date", COALESCE(${selectMetrics[metric]}, 0) AS value | 237 | SELECT "intervals"."startDate" as "date", COALESCE(${selectMetrics[metric]}, 0) AS value |
238 | FROM | 238 | FROM |
@@ -249,7 +249,7 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid | |||
249 | replacements: { | 249 | replacements: { |
250 | startDate, | 250 | startDate, |
251 | endDate, | 251 | endDate, |
252 | sqlInterval, | 252 | groupInterval, |
253 | videoId: video.id | 253 | videoId: video.id |
254 | } | 254 | } |
255 | } | 255 | } |
diff --git a/server/tests/api/views/video-views-timeserie-stats.ts b/server/tests/api/views/video-views-timeserie-stats.ts index 4db76fe89..fd3aba188 100644 --- a/server/tests/api/views/video-views-timeserie-stats.ts +++ b/server/tests/api/views/video-views-timeserie-stats.ts | |||
@@ -110,21 +110,21 @@ describe('Test views timeserie stats', function () { | |||
110 | 110 | ||
111 | it('Should use a custom start/end date', async function () { | 111 | it('Should use a custom start/end date', async function () { |
112 | const now = new Date() | 112 | const now = new Date() |
113 | const tenDaysAgo = new Date() | 113 | const twentyDaysAgo = new Date() |
114 | tenDaysAgo.setDate(tenDaysAgo.getDate() - 9) | 114 | twentyDaysAgo.setDate(twentyDaysAgo.getDate() - 19) |
115 | 115 | ||
116 | const result = await servers[0].videoStats.getTimeserieStats({ | 116 | const result = await servers[0].videoStats.getTimeserieStats({ |
117 | videoId: vodVideoId, | 117 | videoId: vodVideoId, |
118 | metric: 'aggregateWatchTime', | 118 | metric: 'aggregateWatchTime', |
119 | startDate: tenDaysAgo, | 119 | startDate: twentyDaysAgo, |
120 | endDate: now | 120 | endDate: now |
121 | }) | 121 | }) |
122 | 122 | ||
123 | expect(result.groupInterval).to.equal('one_day') | 123 | expect(result.groupInterval).to.equal('1 day') |
124 | expect(result.data).to.have.lengthOf(10) | 124 | expect(result.data).to.have.lengthOf(20) |
125 | 125 | ||
126 | const first = result.data[0] | 126 | const first = result.data[0] |
127 | expect(new Date(first.date).toLocaleDateString()).to.equal(tenDaysAgo.toLocaleDateString()) | 127 | expect(new Date(first.date).toLocaleDateString()).to.equal(twentyDaysAgo.toLocaleDateString()) |
128 | 128 | ||
129 | expectInterval(result, 24 * 3600 * 1000) | 129 | expectInterval(result, 24 * 3600 * 1000) |
130 | expectTodayLastValue(result, 9) | 130 | expectTodayLastValue(result, 9) |
@@ -142,7 +142,7 @@ describe('Test views timeserie stats', function () { | |||
142 | endDate: now | 142 | endDate: now |
143 | }) | 143 | }) |
144 | 144 | ||
145 | expect(result.groupInterval).to.equal('one_hour') | 145 | expect(result.groupInterval).to.equal('1 hour') |
146 | expect(result.data).to.have.length.above(24).and.below(50) | 146 | expect(result.data).to.have.length.above(24).and.below(50) |
147 | 147 | ||
148 | expectInterval(result, 3600 * 1000) | 148 | expectInterval(result, 3600 * 1000) |
@@ -152,7 +152,7 @@ describe('Test views timeserie stats', function () { | |||
152 | it('Should automatically group by ten minutes', async function () { | 152 | it('Should automatically group by ten minutes', async function () { |
153 | const now = new Date() | 153 | const now = new Date() |
154 | const twoHoursAgo = new Date() | 154 | const twoHoursAgo = new Date() |
155 | twoHoursAgo.setHours(twoHoursAgo.getHours() - 1) | 155 | twoHoursAgo.setHours(twoHoursAgo.getHours() - 4) |
156 | 156 | ||
157 | const result = await servers[0].videoStats.getTimeserieStats({ | 157 | const result = await servers[0].videoStats.getTimeserieStats({ |
158 | videoId: vodVideoId, | 158 | videoId: vodVideoId, |
@@ -161,8 +161,8 @@ describe('Test views timeserie stats', function () { | |||
161 | endDate: now | 161 | endDate: now |
162 | }) | 162 | }) |
163 | 163 | ||
164 | expect(result.groupInterval).to.equal('ten_minutes') | 164 | expect(result.groupInterval).to.equal('10 minutes') |
165 | expect(result.data).to.have.length.above(6).and.below(18) | 165 | expect(result.data).to.have.length.above(20).and.below(30) |
166 | 166 | ||
167 | expectInterval(result, 60 * 10 * 1000) | 167 | expectInterval(result, 60 * 10 * 1000) |
168 | expectTodayLastValue(result, 9) | 168 | expectTodayLastValue(result, 9) |
@@ -180,7 +180,7 @@ describe('Test views timeserie stats', function () { | |||
180 | endDate: now | 180 | endDate: now |
181 | }) | 181 | }) |
182 | 182 | ||
183 | expect(result.groupInterval).to.equal('one_minute') | 183 | expect(result.groupInterval).to.equal('1 minute') |
184 | expect(result.data).to.have.length.above(20).and.below(40) | 184 | expect(result.data).to.have.length.above(20).and.below(40) |
185 | 185 | ||
186 | expectInterval(result, 60 * 1000) | 186 | expectInterval(result, 60 * 1000) |