diff options
8 files changed, 63 insertions, 18 deletions
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html index 2499b6ed5..bf4fa9396 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html | |||
@@ -20,6 +20,8 @@ | |||
20 | 20 | ||
21 | <div i18n class="video-channel-followers">{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> | 21 | <div i18n class="video-channel-followers">{videoChannel.followersCount, plural, =1 {1 subscriber} other {{{ videoChannel.followersCount }} subscribers}}</div> |
22 | 22 | ||
23 | <div i18n class="video-channel-videos">{videoChannel.videosCount, plural, =0 {No videos} =1 {1 video} other {{{ videoChannel.videosCount }} videos}}</div> | ||
24 | |||
23 | <div class="video-channel-buttons"> | 25 | <div class="video-channel-buttons"> |
24 | <my-edit-button [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button> | 26 | <my-edit-button [routerLink]="[ 'update', videoChannel.nameWithHost ]"></my-edit-button> |
25 | <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button> | 27 | <my-delete-button (click)="deleteVideoChannel(videoChannel)"></my-delete-button> |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts index 75d6d8acd..9caefe5b1 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.ts | |||
@@ -96,8 +96,8 @@ export class MyAccountVideoChannelsComponent implements OnInit { | |||
96 | const res = await this.confirmService.confirmWithInput( | 96 | const res = await this.confirmService.confirmWithInput( |
97 | this.i18n( | 97 | this.i18n( |
98 | // tslint:disable | 98 | // tslint:disable |
99 | 'Do you really want to delete {{channelDisplayName}}? It will delete all videos uploaded in this channel, and you will not be able to create another channel with the same name ({{channelName}})!', | 99 | 'Do you really want to delete {{channelDisplayName}}? It will delete {{videosCount}} videos uploaded in this channel, and you will not be able to create another channel with the same name ({{channelName}})!', |
100 | { channelDisplayName: videoChannel.displayName, channelName: videoChannel.name } | 100 | { channelDisplayName: videoChannel.displayName, videosCount: videoChannel.videosCount, channelName: videoChannel.name } |
101 | ), | 101 | ), |
102 | this.i18n( | 102 | this.i18n( |
103 | 'Please type the display name of the video channel ({{displayName}}) to confirm', | 103 | 'Please type the display name of the video channel ({{displayName}}) to confirm', |
diff --git a/client/src/app/shared/confirm/confirm.component.scss b/client/src/app/shared/confirm/confirm.component.scss index 93dd7926b..ed226bc09 100644 --- a/client/src/app/shared/confirm/confirm.component.scss +++ b/client/src/app/shared/confirm/confirm.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
4 | .button { | 8 | .button { |
5 | padding: 0 13px; | 9 | padding: 0 13px; |
6 | } | 10 | } |
diff --git a/client/src/app/shared/video-channel/video-channel.model.ts b/client/src/app/shared/video-channel/video-channel.model.ts index 617d6d44d..2f4597343 100644 --- a/client/src/app/shared/video-channel/video-channel.model.ts +++ b/client/src/app/shared/video-channel/video-channel.model.ts | |||
@@ -9,9 +9,13 @@ export class VideoChannel extends Actor implements ServerVideoChannel { | |||
9 | isLocal: boolean | 9 | isLocal: boolean |
10 | nameWithHost: string | 10 | nameWithHost: string |
11 | nameWithHostForced: string | 11 | nameWithHostForced: string |
12 | |||
12 | ownerAccount?: Account | 13 | ownerAccount?: Account |
13 | ownerBy?: string | 14 | ownerBy?: string |
14 | ownerAvatarUrl?: string | 15 | ownerAvatarUrl?: string |
16 | |||
17 | videosCount?: number | ||
18 | |||
15 | viewsPerDay?: ViewsPerDate[] | 19 | viewsPerDay?: ViewsPerDate[] |
16 | 20 | ||
17 | constructor (hash: ServerVideoChannel) { | 21 | constructor (hash: ServerVideoChannel) { |
@@ -24,6 +28,8 @@ export class VideoChannel extends Actor implements ServerVideoChannel { | |||
24 | this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) | 28 | this.nameWithHost = Actor.CREATE_BY_STRING(this.name, this.host) |
25 | this.nameWithHostForced = Actor.CREATE_BY_STRING(this.name, this.host, true) | 29 | this.nameWithHostForced = Actor.CREATE_BY_STRING(this.name, this.host, true) |
26 | 30 | ||
31 | this.videosCount = hash.videosCount | ||
32 | |||
27 | if (hash.viewsPerDay) { | 33 | if (hash.viewsPerDay) { |
28 | this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date) })) | 34 | this.viewsPerDay = hash.viewsPerDay.map(v => ({ ...v, date: new Date(v.date) })) |
29 | } | 35 | } |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index b5bcbdc65..a4231b6b3 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -173,6 +173,10 @@ export type SummaryOptions = { | |||
173 | attributes: { | 173 | attributes: { |
174 | include: [ | 174 | include: [ |
175 | [ | 175 | [ |
176 | literal('(SELECT COUNT(*) FROM "video" WHERE "channelId" = "VideoChannelModel"."id")'), | ||
177 | 'videosCount' | ||
178 | ], | ||
179 | [ | ||
176 | literal( | 180 | literal( |
177 | '(' + | 181 | '(' + |
178 | `SELECT string_agg(concat_ws('|', t.day, t.views), ',') ` + | 182 | `SELECT string_agg(concat_ws('|', t.day, t.views), ',') ` + |
@@ -544,7 +548,22 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
544 | } | 548 | } |
545 | 549 | ||
546 | toFormattedJSON (this: MChannelFormattable): VideoChannel { | 550 | toFormattedJSON (this: MChannelFormattable): VideoChannel { |
547 | const viewsPerDay = this.get('viewsPerDay') as string | 551 | const viewsPerDayString = this.get('viewsPerDay') as string |
552 | const videosCount = this.get('videosCount') as number | ||
553 | |||
554 | let viewsPerDay: { date: Date, views: number }[] | ||
555 | |||
556 | if (viewsPerDayString) { | ||
557 | viewsPerDay = viewsPerDayString.split(',') | ||
558 | .map(v => { | ||
559 | const [ dateString, amount ] = v.split('|') | ||
560 | |||
561 | return { | ||
562 | date: new Date(dateString), | ||
563 | views: +amount | ||
564 | } | ||
565 | }) | ||
566 | } | ||
548 | 567 | ||
549 | const actor = this.Actor.toFormattedJSON() | 568 | const actor = this.Actor.toFormattedJSON() |
550 | const videoChannel = { | 569 | const videoChannel = { |
@@ -556,15 +575,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
556 | createdAt: this.createdAt, | 575 | createdAt: this.createdAt, |
557 | updatedAt: this.updatedAt, | 576 | updatedAt: this.updatedAt, |
558 | ownerAccount: undefined, | 577 | ownerAccount: undefined, |
559 | viewsPerDay: viewsPerDay | 578 | videosCount, |
560 | ? viewsPerDay.split(',').map(v => { | 579 | viewsPerDay |
561 | const o = v.split('|') | ||
562 | return { | ||
563 | date: new Date(o[0]), | ||
564 | views: +o[1] | ||
565 | } | ||
566 | }) | ||
567 | : undefined | ||
568 | } | 580 | } |
569 | 581 | ||
570 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() | 582 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index ae2483b2f..1f590c02d 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -500,7 +500,7 @@ export class VideoModel extends Model<VideoModel> { | |||
500 | @AllowNull(false) | 500 | @AllowNull(false) |
501 | @Is('VideoPrivacy', value => throwIfNotValid(value, isVideoPrivacyValid, 'privacy')) | 501 | @Is('VideoPrivacy', value => throwIfNotValid(value, isVideoPrivacyValid, 'privacy')) |
502 | @Column | 502 | @Column |
503 | privacy: number | 503 | privacy: VideoPrivacy |
504 | 504 | ||
505 | @AllowNull(false) | 505 | @AllowNull(false) |
506 | @Is('VideoNSFW', value => throwIfNotValid(value, isBooleanValid, 'NSFW boolean')) | 506 | @Is('VideoNSFW', value => throwIfNotValid(value, isBooleanValid, 'NSFW boolean')) |
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts index 89026aba7..e7d9238dd 100644 --- a/server/tests/api/videos/video-channels.ts +++ b/server/tests/api/videos/video-channels.ts | |||
@@ -365,7 +365,7 @@ describe('Test video channels', function () { | |||
365 | } | 365 | } |
366 | }) | 366 | }) |
367 | 367 | ||
368 | it('Should report correct channel statistics', async function () { | 368 | it('Should report correct channel views per days', async function () { |
369 | this.timeout(10000) | 369 | this.timeout(10000) |
370 | 370 | ||
371 | { | 371 | { |
@@ -374,14 +374,18 @@ describe('Test video channels', function () { | |||
374 | accountName: userInfo.account.name + '@' + userInfo.account.host, | 374 | accountName: userInfo.account.name + '@' + userInfo.account.host, |
375 | withStats: true | 375 | withStats: true |
376 | }) | 376 | }) |
377 | res.body.data.forEach((channel: VideoChannel) => { | 377 | |
378 | const channels: VideoChannel[] = res.body.data | ||
379 | |||
380 | for (const channel of channels) { | ||
378 | expect(channel).to.haveOwnProperty('viewsPerDay') | 381 | expect(channel).to.haveOwnProperty('viewsPerDay') |
379 | expect(channel.viewsPerDay).to.have.length(30 + 1) // daysPrior + today | 382 | expect(channel.viewsPerDay).to.have.length(30 + 1) // daysPrior + today |
380 | channel.viewsPerDay.forEach((v: ViewsPerDate) => { | 383 | |
384 | for (const v of channel.viewsPerDay) { | ||
381 | expect(v.date).to.be.an('string') | 385 | expect(v.date).to.be.an('string') |
382 | expect(v.views).to.equal(0) | 386 | expect(v.views).to.equal(0) |
383 | }) | 387 | } |
384 | }) | 388 | } |
385 | } | 389 | } |
386 | 390 | ||
387 | { | 391 | { |
@@ -402,6 +406,21 @@ describe('Test video channels', function () { | |||
402 | } | 406 | } |
403 | }) | 407 | }) |
404 | 408 | ||
409 | it('Should report correct videos count', async function () { | ||
410 | const res = await getAccountVideoChannelsList({ | ||
411 | url: servers[0].url, | ||
412 | accountName: userInfo.account.name + '@' + userInfo.account.host, | ||
413 | withStats: true | ||
414 | }) | ||
415 | const channels: VideoChannel[] = res.body.data | ||
416 | |||
417 | const totoChannel = channels.find(c => c.name === 'toto_channel') | ||
418 | const rootChannel = channels.find(c => c.name === 'root_channel') | ||
419 | |||
420 | expect(rootChannel.videosCount).to.equal(1) | ||
421 | expect(totoChannel.videosCount).to.equal(0) | ||
422 | }) | ||
423 | |||
405 | after(async function () { | 424 | after(async function () { |
406 | await cleanupTests(servers) | 425 | await cleanupTests(servers) |
407 | }) | 426 | }) |
diff --git a/shared/models/videos/channel/video-channel.model.ts b/shared/models/videos/channel/video-channel.model.ts index 421004e68..32829e92a 100644 --- a/shared/models/videos/channel/video-channel.model.ts +++ b/shared/models/videos/channel/video-channel.model.ts | |||
@@ -13,6 +13,8 @@ export interface VideoChannel extends Actor { | |||
13 | support: string | 13 | support: string |
14 | isLocal: boolean | 14 | isLocal: boolean |
15 | ownerAccount?: Account | 15 | ownerAccount?: Account |
16 | |||
17 | videosCount?: number | ||
16 | viewsPerDay?: ViewsPerDate[] // chronologically ordered | 18 | viewsPerDay?: ViewsPerDate[] // chronologically ordered |
17 | } | 19 | } |
18 | 20 | ||