mirror of https://github.com/Chocobozzz/PeerTube
Add filter by start/end date overall stats in api
parent
f18a060a83
commit
49f0468d44
|
@ -1,6 +1,6 @@
|
|||
import express from 'express'
|
||||
import { LocalVideoViewerModel } from '@server/models/view/local-video-viewer'
|
||||
import { VideoStatsTimeserieMetric, VideoStatsTimeserieQuery } from '@shared/models'
|
||||
import { VideoStatsOverallQuery, VideoStatsTimeserieMetric, VideoStatsTimeserieQuery } from '@shared/models'
|
||||
import {
|
||||
asyncMiddleware,
|
||||
authenticate,
|
||||
|
@ -39,8 +39,13 @@ export {
|
|||
|
||||
async function getOverallStats (req: express.Request, res: express.Response) {
|
||||
const video = res.locals.videoAll
|
||||
const query = req.query as VideoStatsOverallQuery
|
||||
|
||||
const stats = await LocalVideoViewerModel.getOverallStats(video)
|
||||
const stats = await LocalVideoViewerModel.getOverallStats({
|
||||
video,
|
||||
startDate: query.startDate,
|
||||
endDate: query.endDate
|
||||
})
|
||||
|
||||
return res.json(stats)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,16 @@ import { areValidationErrors, checkUserCanManageVideo, doesVideoExist, isValidVi
|
|||
const videoOverallStatsValidator = [
|
||||
isValidVideoIdParam('videoId'),
|
||||
|
||||
query('startDate')
|
||||
.optional()
|
||||
.custom(isDateValid)
|
||||
.withMessage('Should have a valid start date'),
|
||||
|
||||
query('endDate')
|
||||
.optional()
|
||||
.custom(isDateValid)
|
||||
.withMessage('Should have a valid end date'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking videoOverallStatsValidator parameters', { parameters: req.body })
|
||||
|
||||
|
|
|
@ -100,10 +100,28 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
|
|||
})
|
||||
}
|
||||
|
||||
static async getOverallStats (video: MVideo): Promise<VideoStatsOverall> {
|
||||
const options = {
|
||||
static async getOverallStats (options: {
|
||||
video: MVideo
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
}): Promise<VideoStatsOverall> {
|
||||
const { video, startDate, endDate } = options
|
||||
|
||||
const queryOptions = {
|
||||
type: QueryTypes.SELECT as QueryTypes.SELECT,
|
||||
replacements: { videoId: video.id }
|
||||
replacements: { videoId: video.id } as any
|
||||
}
|
||||
|
||||
let dateWhere = ''
|
||||
|
||||
if (startDate) {
|
||||
dateWhere += ' AND "localVideoViewer"."startDate" >= :startDate'
|
||||
queryOptions.replacements.startDate = startDate
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
dateWhere += ' AND "localVideoViewer"."endDate" <= :endDate'
|
||||
queryOptions.replacements.endDate = endDate
|
||||
}
|
||||
|
||||
const watchTimeQuery = `SELECT ` +
|
||||
|
@ -111,9 +129,9 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
|
|||
`AVG("localVideoViewer"."watchTime") AS "averageWatchTime" ` +
|
||||
`FROM "localVideoViewer" ` +
|
||||
`INNER JOIN "video" ON "video"."id" = "localVideoViewer"."videoId" ` +
|
||||
`WHERE "videoId" = :videoId`
|
||||
`WHERE "videoId" = :videoId ${dateWhere}`
|
||||
|
||||
const watchTimePromise = LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, options)
|
||||
const watchTimePromise = LocalVideoViewerModel.sequelize.query<any>(watchTimeQuery, queryOptions)
|
||||
|
||||
const watchPeakQuery = `WITH "watchPeakValues" AS (
|
||||
SELECT "startDate" AS "dateBreakpoint", 1 AS "inc"
|
||||
|
@ -122,7 +140,7 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
|
|||
UNION ALL
|
||||
SELECT "endDate" AS "dateBreakpoint", -1 AS "inc"
|
||||
FROM "localVideoViewer"
|
||||
WHERE "videoId" = :videoId
|
||||
WHERE "videoId" = :videoId ${dateWhere}
|
||||
)
|
||||
SELECT "dateBreakpoint", "concurrent"
|
||||
FROM (
|
||||
|
@ -132,14 +150,14 @@ export class LocalVideoViewerModel extends Model<Partial<AttributesOnly<LocalVid
|
|||
) tmp
|
||||
ORDER BY "concurrent" DESC
|
||||
FETCH FIRST 1 ROW ONLY`
|
||||
const watchPeakPromise = LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, options)
|
||||
const watchPeakPromise = LocalVideoViewerModel.sequelize.query<any>(watchPeakQuery, queryOptions)
|
||||
|
||||
const countriesQuery = `SELECT country, COUNT(country) as viewers ` +
|
||||
`FROM "localVideoViewer" ` +
|
||||
`WHERE "videoId" = :videoId AND country IS NOT NULL ` +
|
||||
`WHERE "videoId" = :videoId AND country IS NOT NULL ${dateWhere} ` +
|
||||
`GROUP BY country ` +
|
||||
`ORDER BY viewers DESC`
|
||||
const countriesPromise = LocalVideoViewerModel.sequelize.query<any>(countriesQuery, options)
|
||||
const countriesPromise = LocalVideoViewerModel.sequelize.query<any>(countriesQuery, queryOptions)
|
||||
|
||||
const [ rowsWatchTime, rowsWatchPeak, rowsCountries ] = await Promise.all([
|
||||
watchTimePromise,
|
||||
|
|
|
@ -75,8 +75,30 @@ describe('Test videos views', function () {
|
|||
})
|
||||
})
|
||||
|
||||
it('Should fail with an invalid start date', async function () {
|
||||
await servers[0].videoStats.getOverallStats({
|
||||
videoId,
|
||||
startDate: 'fake' as any,
|
||||
endDate: new Date().toISOString(),
|
||||
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
||||
})
|
||||
})
|
||||
|
||||
it('Should fail with an invalid end date', async function () {
|
||||
await servers[0].videoStats.getOverallStats({
|
||||
videoId,
|
||||
startDate: new Date().toISOString(),
|
||||
endDate: 'fake' as any,
|
||||
expectedStatus: HttpStatusCode.BAD_REQUEST_400
|
||||
})
|
||||
})
|
||||
|
||||
it('Should succeed with the correct parameters', async function () {
|
||||
await servers[0].videoStats.getOverallStats({ videoId })
|
||||
await servers[0].videoStats.getOverallStats({
|
||||
videoId,
|
||||
startDate: new Date().toISOString(),
|
||||
endDate: new Date().toISOString()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -141,6 +141,27 @@ describe('Test views overall stats', function () {
|
|||
}
|
||||
})
|
||||
|
||||
it('Should filter overall stats by date', async function () {
|
||||
this.timeout(60000)
|
||||
|
||||
const beforeView = new Date()
|
||||
|
||||
await servers[0].views.simulateViewer({ id: vodVideoId, currentTimes: [ 0, 3 ] })
|
||||
await processViewersStats(servers)
|
||||
|
||||
{
|
||||
const stats = await servers[0].videoStats.getOverallStats({ videoId: vodVideoId, startDate: beforeView.toISOString() })
|
||||
expect(stats.averageWatchTime).to.equal(3)
|
||||
expect(stats.totalWatchTime).to.equal(3)
|
||||
}
|
||||
|
||||
{
|
||||
const stats = await servers[0].videoStats.getOverallStats({ videoId: liveVideoId, endDate: beforeView.toISOString() })
|
||||
expect(stats.averageWatchTime).to.equal(22)
|
||||
expect(stats.totalWatchTime).to.equal(88)
|
||||
}
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await stopFfmpeg(command)
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * from './video-stats-overall-query.model'
|
||||
export * from './video-stats-overall.model'
|
||||
export * from './video-stats-retention.model'
|
||||
export * from './video-stats-timeserie-query.model'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
export interface VideoStatsOverallQuery {
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
}
|
|
@ -6,6 +6,8 @@ export class VideoStatsCommand extends AbstractCommand {
|
|||
|
||||
getOverallStats (options: OverrideCommandOptions & {
|
||||
videoId: number | string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
}) {
|
||||
const path = '/api/v1/videos/' + options.videoId + '/stats/overall'
|
||||
|
||||
|
@ -13,6 +15,8 @@ export class VideoStatsCommand extends AbstractCommand {
|
|||
...options,
|
||||
path,
|
||||
|
||||
query: pick(options, [ 'startDate', 'endDate' ]),
|
||||
|
||||
implicitToken: true,
|
||||
defaultExpectedStatus: HttpStatusCode.OK_200
|
||||
})
|
||||
|
|
|
@ -1952,6 +1952,18 @@ paths:
|
|||
- OAuth2: []
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/idOrUUID'
|
||||
- name: startDate
|
||||
in: query
|
||||
description: Filter stats by start date
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: endDate
|
||||
in: query
|
||||
description: Filter stats by end date
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
@ -1996,6 +2008,18 @@ paths:
|
|||
enum:
|
||||
- 'viewers'
|
||||
- 'aggregateWatchTime'
|
||||
- name: startDate
|
||||
in: query
|
||||
description: Filter stats by start date
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
- name: endDate
|
||||
in: query
|
||||
description: Filter stats by end date
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
responses:
|
||||
'200':
|
||||
description: successful operation
|
||||
|
|
Loading…
Reference in New Issue