diff options
-rw-r--r-- | client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html | 5 | ||||
-rw-r--r-- | client/src/sass/class-helpers.scss | 21 | ||||
-rw-r--r-- | client/src/sass/include/_fonts.scss | 4 | ||||
-rw-r--r-- | client/src/sass/include/_icons.scss | 24 | ||||
-rw-r--r-- | client/src/sass/primeng-custom.scss | 12 | ||||
-rw-r--r-- | server/models/view/local-video-viewer.ts | 120 | ||||
-rw-r--r-- | server/tests/api/views/video-views-overall-stats.ts | 66 |
7 files changed, 186 insertions, 66 deletions
diff --git a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html index 779d42e0c..7b6bd993c 100644 --- a/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/+videos/+video-edit/video-add-components/video-upload.component.html | |||
@@ -66,7 +66,10 @@ | |||
66 | <span *ngIf="videoUploadPercents !== 100 || videoUploaded">{{ videoUploadPercents }}%</span> | 66 | <span *ngIf="videoUploadPercents !== 100 || videoUploaded">{{ videoUploadPercents }}%</span> |
67 | </div> | 67 | </div> |
68 | </div> | 68 | </div> |
69 | <input *ngIf="videoUploaded === false" type="button" i18n-value="Cancel ongoing upload of a video" value="Cancel" (click)="cancelUpload()" /> | 69 | <input |
70 | *ngIf="videoUploaded === false" | ||
71 | type="button" class="peertube-button grey-button ms-1" i18n-value="Cancel ongoing upload of a video" value="Cancel" (click)="cancelUpload()" | ||
72 | /> | ||
70 | </div> | 73 | </div> |
71 | 74 | ||
72 | <div *ngIf="error && enableRetryAfterError" class="upload-progress-retry"> | 75 | <div *ngIf="error && enableRetryAfterError" class="upload-progress-retry"> |
diff --git a/client/src/sass/class-helpers.scss b/client/src/sass/class-helpers.scss index 72381d1a8..bc965331a 100644 --- a/client/src/sass/class-helpers.scss +++ b/client/src/sass/class-helpers.scss | |||
@@ -2,6 +2,7 @@ | |||
2 | @use '_mixins' as *; | 2 | @use '_mixins' as *; |
3 | @use '_badges' as *; | 3 | @use '_badges' as *; |
4 | @use '_icons' as *; | 4 | @use '_icons' as *; |
5 | @use '_fonts' as *; | ||
5 | 6 | ||
6 | .link-orange { | 7 | .link-orange { |
7 | color: pvar(--mainForegroundColor); | 8 | color: pvar(--mainForegroundColor); |
@@ -62,7 +63,7 @@ | |||
62 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
63 | 64 | ||
64 | .muted { | 65 | .muted { |
65 | color: pvar(--greyForegroundColor) !important; | 66 | @include muted; |
66 | } | 67 | } |
67 | 68 | ||
68 | // --------------------------------------------------------------------------- | 69 | // --------------------------------------------------------------------------- |
@@ -111,7 +112,7 @@ | |||
111 | } | 112 | } |
112 | 113 | ||
113 | .form-group-description { | 114 | .form-group-description { |
114 | @extend .muted !optional; | 115 | @include muted; |
115 | 116 | ||
116 | font-size: 14px; | 117 | font-size: 14px; |
117 | margin-top: 10px; | 118 | margin-top: 10px; |
@@ -219,27 +220,19 @@ label + .form-group-description { | |||
219 | // --------------------------------------------------------------------------- | 220 | // --------------------------------------------------------------------------- |
220 | 221 | ||
221 | .chevron-down { | 222 | .chevron-down { |
222 | @include chevron-down(0.55rem, 0.15rem); | 223 | @include chevron-down-default; |
223 | |||
224 | margin: 0 8px; | ||
225 | } | 224 | } |
226 | 225 | ||
227 | .chevron-up { | 226 | .chevron-up { |
228 | @include chevron-up(0.55rem, 0.15rem); | 227 | @include chevron-up-default; |
229 | |||
230 | margin: 0 8px; | ||
231 | } | 228 | } |
232 | 229 | ||
233 | .chevron-right { | 230 | .chevron-right { |
234 | @include chevron-right(0.55rem, 0.15rem); | 231 | @include chevron-right-default; |
235 | |||
236 | margin: 0 8px; | ||
237 | } | 232 | } |
238 | 233 | ||
239 | .chevron-left { | 234 | .chevron-left { |
240 | @include chevron-left(0.55rem, 0.15rem); | 235 | @include chevron-left-default; |
241 | |||
242 | margin: 0 8px; | ||
243 | } | 236 | } |
244 | 237 | ||
245 | // --------------------------------------------------------------------------- | 238 | // --------------------------------------------------------------------------- |
diff --git a/client/src/sass/include/_fonts.scss b/client/src/sass/include/_fonts.scss index 514261d01..e5a40af34 100644 --- a/client/src/sass/include/_fonts.scss +++ b/client/src/sass/include/_fonts.scss | |||
@@ -15,3 +15,7 @@ | |||
15 | font-display: swap; | 15 | font-display: swap; |
16 | src: url('../fonts/source-sans/WOFF2/VAR/SourceSans3VF-Italic.ttf.woff2') format('woff2'); | 16 | src: url('../fonts/source-sans/WOFF2/VAR/SourceSans3VF-Italic.ttf.woff2') format('woff2'); |
17 | } | 17 | } |
18 | |||
19 | @mixin muted { | ||
20 | color: pvar(--greyForegroundColor) !important; | ||
21 | } | ||
diff --git a/client/src/sass/include/_icons.scss b/client/src/sass/include/_icons.scss index 5d8a312db..08a0c02e3 100644 --- a/client/src/sass/include/_icons.scss +++ b/client/src/sass/include/_icons.scss | |||
@@ -18,6 +18,12 @@ | |||
18 | transform: rotate(45deg); | 18 | transform: rotate(45deg); |
19 | } | 19 | } |
20 | 20 | ||
21 | @mixin chevron-right-default { | ||
22 | @include chevron-right(0.55rem, 0.15rem); | ||
23 | |||
24 | margin: 0 8px; | ||
25 | } | ||
26 | |||
21 | @mixin chevron-down ($size, $border-width) { | 27 | @mixin chevron-down ($size, $border-width) { |
22 | @include chevron($size, $border-width); | 28 | @include chevron($size, $border-width); |
23 | 29 | ||
@@ -25,6 +31,12 @@ | |||
25 | transform: rotate(135deg); | 31 | transform: rotate(135deg); |
26 | } | 32 | } |
27 | 33 | ||
34 | @mixin chevron-down-default { | ||
35 | @include chevron-down(0.55rem, 0.15rem); | ||
36 | |||
37 | margin: 0 8px; | ||
38 | } | ||
39 | |||
28 | @mixin chevron-up ($size, $border-width) { | 40 | @mixin chevron-up ($size, $border-width) { |
29 | @include chevron($size, $border-width); | 41 | @include chevron($size, $border-width); |
30 | 42 | ||
@@ -32,6 +44,12 @@ | |||
32 | transform: rotate(-45deg); | 44 | transform: rotate(-45deg); |
33 | } | 45 | } |
34 | 46 | ||
47 | @mixin chevron-up-default { | ||
48 | @include chevron-up(0.55rem, 0.15rem); | ||
49 | |||
50 | margin: 0 8px; | ||
51 | } | ||
52 | |||
35 | @mixin chevron-left ($size, $border-width) { | 53 | @mixin chevron-left ($size, $border-width) { |
36 | @include chevron($size, $border-width); | 54 | @include chevron($size, $border-width); |
37 | 55 | ||
@@ -39,6 +57,12 @@ | |||
39 | transform: rotate(-135deg); | 57 | transform: rotate(-135deg); |
40 | } | 58 | } |
41 | 59 | ||
60 | @mixin chevron-left-default { | ||
61 | @include chevron-left(0.55rem, 0.15rem); | ||
62 | |||
63 | margin: 0 8px; | ||
64 | } | ||
65 | |||
42 | // --------------------------------------------------------------------------- | 66 | // --------------------------------------------------------------------------- |
43 | 67 | ||
44 | @mixin arrow-up ($size) { | 68 | @mixin arrow-up ($size) { |
diff --git a/client/src/sass/primeng-custom.scss b/client/src/sass/primeng-custom.scss index a82cdbbb9..fb1d3f7bd 100644 --- a/client/src/sass/primeng-custom.scss +++ b/client/src/sass/primeng-custom.scss | |||
@@ -667,7 +667,7 @@ p-table { | |||
667 | @include margin-right(10px); | 667 | @include margin-right(10px); |
668 | 668 | ||
669 | .p-paginator-icon { | 669 | .p-paginator-icon { |
670 | @extend .chevron-left !optional; | 670 | @include chevron-left-default; |
671 | } | 671 | } |
672 | } | 672 | } |
673 | 673 | ||
@@ -675,7 +675,7 @@ p-table { | |||
675 | @include margin-left(10px); | 675 | @include margin-left(10px); |
676 | 676 | ||
677 | .p-paginator-icon { | 677 | .p-paginator-icon { |
678 | @extend .chevron-right !optional; | 678 | @include chevron-right-default; |
679 | } | 679 | } |
680 | } | 680 | } |
681 | 681 | ||
@@ -769,7 +769,7 @@ p-calendar .p-datepicker { | |||
769 | } | 769 | } |
770 | 770 | ||
771 | .p-datepicker-next { | 771 | .p-datepicker-next { |
772 | @extend .chevron-right !optional; | 772 | @include chevron-right-default; |
773 | 773 | ||
774 | color: #000 !important; | 774 | color: #000 !important; |
775 | text-align: end; | 775 | text-align: end; |
@@ -780,7 +780,7 @@ p-calendar .p-datepicker { | |||
780 | } | 780 | } |
781 | 781 | ||
782 | .p-datepicker-prev { | 782 | .p-datepicker-prev { |
783 | @extend .chevron-left !optional; | 783 | @include chevron-left-default; |
784 | 784 | ||
785 | color: #000 !important; | 785 | color: #000 !important; |
786 | text-align: start; | 786 | text-align: start; |
@@ -794,13 +794,13 @@ p-calendar .p-datepicker { | |||
794 | .p-timepicker { | 794 | .p-timepicker { |
795 | 795 | ||
796 | .pi.pi-chevron-up { | 796 | .pi.pi-chevron-up { |
797 | @extend .chevron-up !optional; | 797 | @include chevron-up-default; |
798 | 798 | ||
799 | color: #000 !important; | 799 | color: #000 !important; |
800 | } | 800 | } |
801 | 801 | ||
802 | .pi.pi-chevron-down { | 802 | .pi.pi-chevron-down { |
803 | @extend .chevron-down !optional; | 803 | @include chevron-down-default; |
804 | 804 | ||
805 | color: #000 !important; | 805 | color: #000 !important; |
806 | } | 806 | } |
diff --git a/server/models/view/local-video-viewer.ts b/server/models/view/local-video-viewer.ts index 12350861b..9d0d89a59 100644 --- a/server/models/view/local-video-viewer.ts +++ b/server/models/view/local-video-viewer.ts | |||
@@ -112,58 +112,88 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid | |||
112 | replacements: { videoId: video.id } as any | 112 | replacements: { videoId: video.id } as any |
113 | } | 113 | } |
114 | 114 | ||
115 | let dateWhere = '' | 115 | if (startDate) queryOptions.replacements.startDate = startDate |
116 | if (endDate) queryOptions.replacements.endDate = endDate | ||
116 | 117 | ||
117 | if (startDate) { | 118 | const buildWatchTimePromise = () => { |
118 | dateWhere += ' AND "localVideoViewer"."startDate" >= :startDate' | 119 | let watchTimeDateWhere = '' |
119 | queryOptions.replacements.startDate = startDate | 120 | |
121 | if (startDate) watchTimeDateWhere += ' AND "localVideoViewer"."startDate" >= :startDate' | ||
122 | if (endDate) watchTimeDateWhere += ' AND "localVideoViewer"."endDate" <= :endDate' | ||
123 | |||
124 | const watchTimeQuery = `SELECT ` + | ||
125 | `COUNT("localVideoViewer"."id") AS "totalViewers", ` + | ||
126 | `SUM("localVideoViewer"."watchTime") AS "totalWatchTime", ` + | ||
127 | `AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` + | ||
128 | `FROM "localVideoViewer" ` + | ||
129 | `INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` + | ||
130 | `WHERE "videoId" = :videoId ${watchTimeDateWhere}` | ||
131 | |||
132 | return LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, queryOptions) | ||
120 | } | 133 | } |
121 | 134 | ||
122 | if (endDate) { | 135 | const buildWatchPeakPromise = () => { |
123 | dateWhere += ' AND "localVideoViewer"."endDate" <= :endDate' | 136 | let watchPeakDateWhereStart = '' |
124 | queryOptions.replacements.endDate = endDate | 137 | let watchPeakDateWhereEnd = '' |
138 | |||
139 | if (startDate) { | ||
140 | watchPeakDateWhereStart += ' AND "localVideoViewer"."startDate" >= :startDate' | ||
141 | watchPeakDateWhereEnd += ' AND "localVideoViewer"."endDate" >= :startDate' | ||
142 | } | ||
143 | |||
144 | if (endDate) { | ||
145 | watchPeakDateWhereStart += ' AND "localVideoViewer"."startDate" <= :endDate' | ||
146 | watchPeakDateWhereEnd += ' AND "localVideoViewer"."endDate" <= :endDate' | ||
147 | } | ||
148 | |||
149 | // Add viewers that were already here, before our start date | ||
150 | const beforeWatchersQuery = startDate | ||
151 | // eslint-disable-next-line max-len | ||
152 | ? `SELECT COUNT(*) AS "total" FROM "localVideoViewer" WHERE "localVideoViewer"."startDate" < :startDate AND "localVideoViewer"."endDate" >= :startDate` | ||
153 | : `SELECT 0 AS "total"` | ||
154 | |||
155 | const watchPeakQuery = `WITH | ||
156 | "beforeWatchers" AS (${beforeWatchersQuery}), | ||
157 | "watchPeakValues" AS ( | ||
158 | SELECT "startDate" AS "dateBreakpoint", 1 AS "inc" | ||
159 | FROM "localVideoViewer" | ||
160 | WHERE "videoId" = :videoId ${watchPeakDateWhereStart} | ||
161 | UNION ALL | ||
162 | SELECT "endDate" AS "dateBreakpoint", -1 AS "inc" | ||
163 | FROM "localVideoViewer" | ||
164 | WHERE "videoId" = :videoId ${watchPeakDateWhereEnd} | ||
165 | ) | ||
166 | SELECT "dateBreakpoint", "concurrent" | ||
167 | FROM ( | ||
168 | SELECT "dateBreakpoint", SUM(SUM("inc")) OVER (ORDER BY "dateBreakpoint") + (SELECT "total" FROM "beforeWatchers") AS "concurrent" | ||
169 | FROM "watchPeakValues" | ||
170 | GROUP BY "dateBreakpoint" | ||
171 | ) tmp | ||
172 | ORDER BY "concurrent" DESC | ||
173 | FETCH FIRST 1 ROW ONLY` | ||
174 | |||
175 | return LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, queryOptions) | ||
125 | } | 176 | } |
126 | 177 | ||
127 | const watchTimeQuery = `SELECT ` + | 178 | const buildCountriesPromise = () => { |
128 | `COUNT("localVideoViewer"."id") AS "totalViewers", ` + | 179 | let countryDateWhere = '' |
129 | `SUM("localVideoViewer"."watchTime") AS "totalWatchTime", ` + | 180 | |
130 | `AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` + | 181 | if (startDate) countryDateWhere += ' AND "localVideoViewer"."endDate" >= :startDate' |
131 | `FROM "localVideoViewer" ` + | 182 | if (endDate) countryDateWhere += ' AND "localVideoViewer"."startDate" <= :endDate' |
132 | `INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` + | 183 | |
133 | `WHERE "videoId" = :videoId ${dateWhere}` | 184 | const countriesQuery = `SELECT country, COUNT(country) as viewers ` + |
134 | 185 | `FROM "localVideoViewer" ` + | |
135 | const watchTimePromise = LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, queryOptions) | 186 | `WHERE "videoId" = :videoId AND country IS NOT NULL ${countryDateWhere} ` + |
136 | 187 | `GROUP BY country ` + | |
137 | const watchPeakQuery = `WITH "watchPeakValues" AS ( | 188 | `ORDER BY viewers DESC` |
138 | SELECT "startDate" AS "dateBreakpoint", 1 AS "inc" | 189 | |
139 | FROM "localVideoViewer" | 190 | return LocalVideoViewerModel.sequelize.query<any>(countriesQuery, queryOptions) |
140 | WHERE "videoId" = :videoId ${dateWhere} | 191 | } |
141 | UNION ALL | ||
142 | SELECT "endDate" AS "dateBreakpoint", -1 AS "inc" | ||
143 | FROM "localVideoViewer" | ||
144 | WHERE "videoId" = :videoId ${dateWhere} | ||
145 | ) | ||
146 | SELECT "dateBreakpoint", "concurrent" | ||
147 | FROM ( | ||
148 | SELECT "dateBreakpoint", SUM(SUM("inc")) OVER (ORDER BY "dateBreakpoint") AS "concurrent" | ||
149 | FROM "watchPeakValues" | ||
150 | GROUP BY "dateBreakpoint" | ||
151 | ) tmp | ||
152 | ORDER BY "concurrent" DESC | ||
153 | FETCH FIRST 1 ROW ONLY` | ||
154 | const watchPeakPromise = LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, queryOptions) | ||
155 | |||
156 | const countriesQuery = `SELECT country, COUNT(country) as viewers ` + | ||
157 | `FROM "localVideoViewer" ` + | ||
158 | `WHERE "videoId" = :videoId AND country IS NOT NULL ${dateWhere} ` + | ||
159 | `GROUP BY country ` + | ||
160 | `ORDER BY viewers DESC` | ||
161 | const countriesPromise = LocalVideoViewerModel.sequelize.query<any>(countriesQuery, queryOptions) | ||
162 | 192 | ||
163 | const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([ | 193 | const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([ |
164 | watchTimePromise, | 194 | buildWatchTimePromise(), |
165 | watchPeakPromise, | 195 | buildWatchPeakPromise(), |
166 | countriesPromise | 196 | buildCountriesPromise() |
167 | ]) | 197 | ]) |
168 | 198 | ||
169 | const viewersPeak = rowsWatchPeak.length !== 0 | 199 | const viewersPeak = rowsWatchPeak.length !== 0 |
diff --git a/server/tests/api/views/video-views-overall-stats.ts b/server/tests/api/views/video-views-overall-stats.ts index 3aadc9689..ac636961e 100644 --- a/server/tests/api/views/video-views-overall-stats.ts +++ b/server/tests/api/views/video-views-overall-stats.ts | |||
@@ -4,6 +4,56 @@ import { expect } from 'chai' | |||
4 | import { FfmpegCommand } from 'fluent-ffmpeg' | 4 | import { FfmpegCommand } from 'fluent-ffmpeg' |
5 | import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared' | 5 | import { prepareViewsServers, prepareViewsVideos, processViewersStats } from '@server/tests/shared' |
6 | import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands' | 6 | import { cleanupTests, PeerTubeServer, stopFfmpeg, waitJobs } from '@shared/server-commands' |
7 | import { wait } from '@shared/core-utils' | ||
8 | import { VideoStatsOverall } from '@shared/models' | ||
9 | |||
10 | /** | ||
11 | * | ||
12 | * Simulate 5 sections of viewers | ||
13 | * * user0 started and ended before start date | ||
14 | * * user1 started before start date and ended in the interval | ||
15 | * * user2 started started in the interval and ended after end date | ||
16 | * * user3 started and ended in the interval | ||
17 | * * user4 started and ended after end date | ||
18 | */ | ||
19 | async function simulateComplexViewers (servers: PeerTubeServer[], videoUUID: string) { | ||
20 | const user0 = '8.8.8.8,127.0.0.1' | ||
21 | const user1 = '8.8.8.8,127.0.0.1' | ||
22 | const user2 = '8.8.8.9,127.0.0.1' | ||
23 | const user3 = '8.8.8.10,127.0.0.1' | ||
24 | const user4 = '8.8.8.11,127.0.0.1' | ||
25 | |||
26 | await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user0 }) // User 0 starts | ||
27 | await wait(500) | ||
28 | |||
29 | await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user1 }) // User 1 starts | ||
30 | await servers[0].views.view({ id: videoUUID, currentTime: 2, xForwardedFor: user0 }) // User 0 ends | ||
31 | await wait(500) | ||
32 | |||
33 | const startDate = new Date().toISOString() | ||
34 | await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user2 }) // User 2 starts | ||
35 | await wait(500) | ||
36 | |||
37 | await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user3 }) // User 3 starts | ||
38 | await wait(500) | ||
39 | |||
40 | await servers[0].views.view({ id: videoUUID, currentTime: 4, xForwardedFor: user1 }) // User 1 ends | ||
41 | await wait(500) | ||
42 | |||
43 | await servers[0].views.view({ id: videoUUID, currentTime: 3, xForwardedFor: user3 }) // User 3 ends | ||
44 | await wait(500) | ||
45 | |||
46 | const endDate = new Date().toISOString() | ||
47 | await servers[0].views.view({ id: videoUUID, currentTime: 0, xForwardedFor: user4 }) // User 4 starts | ||
48 | await servers[0].views.view({ id: videoUUID, currentTime: 5, xForwardedFor: user2 }) // User 2 ends | ||
49 | await wait(500) | ||
50 | |||
51 | await servers[0].views.view({ id: videoUUID, currentTime: 1, xForwardedFor: user4 }) // User 4 ends | ||
52 | |||
53 | await processViewersStats(servers) | ||
54 | |||
55 | return { startDate, endDate } | ||
56 | } | ||
7 | 57 | ||
8 | describe('Test views overall stats', function () { | 58 | describe('Test views overall stats', function () { |
9 | let servers: PeerTubeServer[] | 59 | let servers: PeerTubeServer[] |
@@ -237,6 +287,22 @@ describe('Test views overall stats', function () { | |||
237 | expect(new Date(stats.viewersPeakDate)).to.be.below(before2Watchers) | 287 | expect(new Date(stats.viewersPeakDate)).to.be.below(before2Watchers) |
238 | } | 288 | } |
239 | }) | 289 | }) |
290 | |||
291 | it('Should complex filter peak viewers by date', async function () { | ||
292 | this.timeout(60000) | ||
293 | |||
294 | const { startDate, endDate } = await simulateComplexViewers(servers, videoUUID) | ||
295 | |||
296 | const expectCorrect = (stats: VideoStatsOverall) => { | ||
297 | expect(stats.viewersPeak).to.equal(3) | ||
298 | expect(new Date(stats.viewersPeakDate)).to.be.above(new Date(startDate)).and.below(new Date(endDate)) | ||
299 | } | ||
300 | |||
301 | expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate, endDate })) | ||
302 | expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, startDate })) | ||
303 | expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID, endDate })) | ||
304 | expectCorrect(await servers[0].videoStats.getOverallStats({ videoId: videoUUID })) | ||
305 | }) | ||
240 | }) | 306 | }) |
241 | 307 | ||
242 | describe('Test countries', function () { | 308 | describe('Test countries', function () { |