Skip to content

Commit

Permalink
1365 daten zum live view count (#1366)
Browse files Browse the repository at this point in the history
* Added custom lecture stats to show live views

* Added some more insights and fixed an error where only course is filtered but not stream

* Fixed buttons on watch page

* Lint fix

* Fixed prettier issue

* Fixed another issue with eslint

* Fixed missing mock methods

* Removed manually added methods from mock

* Called gomock

* MOved from go.uber to gomock

* Fixed golangci-lint

* Fixed missing import

* Gofumpted
  • Loading branch information
SebiWrn authored Sep 11, 2024
1 parent 7252e64 commit 0e9dc3e
Show file tree
Hide file tree
Showing 8 changed files with 507 additions and 18 deletions.
78 changes: 73 additions & 5 deletions api/statistics.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (

type statReq struct {
Interval string `form:"interval" json:"interval" xml:"interval" binding:"required"`
Lecture string `form:"lecture" json:"lecture" xml:"lecture"`
}

type statExportReq struct {
Expand Down Expand Up @@ -46,11 +47,35 @@ func (r coursesRoutes) getStats(c *gin.Context) {
} else { // use course from context
cid = ctx.(tools.TUMLiveContext).Course.ID
}

var sid uint
if req.Lecture != "" {
sidTemp, err := strconv.ParseUint(req.Lecture, 10, 32)
if err != nil {
logger.Warn("strconv.Atoi failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Status: http.StatusBadRequest,
CustomMessage: "strconv.Atoi failed",
Err: err,
})
return
}
sid = uint(sidTemp)
} else {
sid = ^uint(0)
}

switch req.Interval {
case "week":
fallthrough
case "day":
res, err := r.StatisticsDao.GetCourseStatsWeekdays(cid)
var res []dao.Stat
var err error
if sid != ^uint(0) {
res, err = r.StatisticsDao.GetLectureStatsWeekdays(cid, sid)
} else {
res, err = r.StatisticsDao.GetCourseStatsWeekdays(cid)
}
if err != nil {
logger.Warn("GetCourseStatsWeekdays failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Expand All @@ -69,7 +94,13 @@ func (r coursesRoutes) getStats(c *gin.Context) {
resp.Data.Datasets[0].Data = res
c.JSON(http.StatusOK, resp)
case "hour":
res, err := r.StatisticsDao.GetCourseStatsHourly(cid)
var res []dao.Stat
var err error
if sid != ^uint(0) {
res, err = r.StatisticsDao.GetLectureStatsHourly(cid, sid)
} else {
res, err = r.StatisticsDao.GetCourseStatsHourly(cid)
}
if err != nil {
logger.Warn("GetCourseStatsHourly failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Expand All @@ -87,6 +118,25 @@ func (r coursesRoutes) getStats(c *gin.Context) {
resp.Data.Datasets[0].Label = "Sum(viewers)"
resp.Data.Datasets[0].Data = res
c.JSON(http.StatusOK, resp)
case "lecture":
res, err := r.StatisticsDao.GetLectureStats(cid, sid)
if err != nil {
logger.Warn("GetLectureStats failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Status: http.StatusInternalServerError,
CustomMessage: "can not get course stats hourly",
Err: err,
})
return
}
resp := chartJs{
ChartType: "bar",
Data: chartJsData{Datasets: []chartJsDataset{newChartJsDataset()}},
Options: newChartJsOptions(),
}
resp.Data.Datasets[0].Label = "View Count"
resp.Data.Datasets[0].Data = res
c.JSON(http.StatusOK, resp)
case "activity-live":
resLive, err := r.StatisticsDao.GetStudentActivityCourseStats(cid, true)
if err != nil {
Expand Down Expand Up @@ -144,7 +194,13 @@ func (r coursesRoutes) getStats(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"res": res})
}
case "vodViews":
res, err := r.StatisticsDao.GetCourseNumVodViews(cid)
var res int
var err error
if sid != ^uint(0) {
res, err = r.StatisticsDao.GetLectureNumVodViews(sid)
} else {
res, err = r.StatisticsDao.GetCourseNumVodViews(cid)
}
if err != nil {
logger.Warn("GetCourseNumVodViews failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Expand All @@ -157,7 +213,13 @@ func (r coursesRoutes) getStats(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"res": res})
}
case "liveViews":
res, err := r.StatisticsDao.GetCourseNumLiveViews(cid)
var res int
var err error
if sid != ^uint(0) {
res, err = r.StatisticsDao.GetLectureNumLiveViews(sid)
} else {
res, err = r.StatisticsDao.GetCourseNumLiveViews(cid)
}
if err != nil {
logger.Warn("GetCourseNumLiveViews failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Expand All @@ -171,7 +233,13 @@ func (r coursesRoutes) getStats(c *gin.Context) {
}
case "allDays":
{
res, err := r.StatisticsDao.GetCourseNumVodViewsPerDay(cid)
var res []dao.Stat
var err error
if sid != ^uint(0) {
res, err = r.StatisticsDao.GetLectureNumVodViewsPerDay(sid)
} else {
res, err = r.StatisticsDao.GetCourseNumVodViewsPerDay(cid)
}
if err != nil {
logger.Warn("GetCourseNumLiveViews failed", "err", err, "courseId", cid)
_ = c.Error(tools.RequestError{
Expand Down
74 changes: 74 additions & 0 deletions dao/statistics.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ type StatisticsDao interface {
GetCourseNumStudents(courseID uint) (int64, error)
GetCourseNumVodViews(courseID uint) (int, error)
GetCourseNumLiveViews(courseID uint) (int, error)
GetLectureNumVodViews(streamID uint) (int, error)
GetLectureNumLiveViews(streamID uint) (int, error)
GetCourseNumVodViewsPerDay(courseID uint) ([]Stat, error)
GetLectureNumVodViewsPerDay(streamID uint) ([]Stat, error)
GetCourseStatsWeekdays(courseID uint) ([]Stat, error)
GetCourseStatsHourly(courseID uint) ([]Stat, error)
GetLectureStatsWeekdays(courseID uint, streamID uint) ([]Stat, error)
GetLectureStatsHourly(courseID uint, streamID uint) ([]Stat, error)
GetLectureStats(courseID uint, lectureID uint) ([]Stat, error)
GetStudentActivityCourseStats(courseID uint, live bool) ([]Stat, error)
GetStreamNumLiveViews(streamID uint) (int, error)
}
Expand Down Expand Up @@ -66,6 +72,21 @@ func (d statisticsDao) GetCourseNumLiveViews(courseID uint) (int, error) {
return res, err
}

// GetLectureNumVodViews returns the sum of vod views of a lecture
func (d statisticsDao) GetLectureNumVodViews(streamID uint) (int, error) {
var res int
err := DB.Raw(`SELECT IFNULL(SUM(viewers), 0) FROM stats
WHERE live = 0 AND stream_id = ?`, streamID).Scan(&res).Error
return res, err
}

// GetLectureNumLiveViews returns the sum of live views of a lecture
func (d statisticsDao) GetLectureNumLiveViews(streamID uint) (int, error) {
var res int
err := DB.Raw(`SELECT MAX(viewers) from stats where stream_id = ?`, streamID).Scan(&res).Error
return res, err
}

// GetCourseNumVodViewsPerDay returns the daily amount of vod views for each day
func (d statisticsDao) GetCourseNumVodViewsPerDay(courseID uint) ([]Stat, error) {
var res []Stat
Expand All @@ -78,6 +99,17 @@ func (d statisticsDao) GetCourseNumVodViewsPerDay(courseID uint) ([]Stat, error)
return res, err
}

// GetLectureNumVodViewsPerDay returns the daily amount of vod views for each day
func (d statisticsDao) GetLectureNumVodViewsPerDay(streamID uint) ([]Stat, error) {
var res []Stat
err := DB.Raw(`SELECT DATE_FORMAT(stats.time, GET_FORMAT(DATE, 'EUR')) AS x, sum(viewers) AS y
FROM stats
WHERE stream_id = ? AND live = 0
GROUP BY DATE(stats.time);`,
streamID).Scan(&res).Error
return res, err
}

// GetCourseStatsWeekdays returns the days and their sum of vod views of a course
func (d statisticsDao) GetCourseStatsWeekdays(courseID uint) ([]Stat, error) {
var res []Stat
Expand All @@ -102,6 +134,41 @@ func (d statisticsDao) GetCourseStatsHourly(courseID uint) ([]Stat, error) {
return res, err
}

// GetLectureStatsWeekdays returns the days and their sum of vod views of a lecture
func (d statisticsDao) GetLectureStatsWeekdays(courseID uint, streamID uint) ([]Stat, error) {
var res []Stat
err := DB.Raw(`SELECT DAYNAME(stats.time) AS x, SUM(stats.viewers) as y
FROM stats
JOIN streams s ON s.id = stats.stream_id
WHERE (s.course_id = ? OR ? = 0) AND stats.live = 0 AND stats.stream_id = ?
GROUP BY DAYOFWEEK(stats.time);`,
courseID, courseID, streamID).Scan(&res).Error
return res, err
}

// GetLectureStatsHourly returns the hours with most vod viewing activity of a lecture
func (d statisticsDao) GetLectureStatsHourly(courseID uint, streamID uint) ([]Stat, error) {
var res []Stat
err := DB.Raw(`SELECT HOUR(stats.time) AS x, SUM(stats.viewers) as y
FROM stats
JOIN streams s ON s.id = stats.stream_id
WHERE (s.course_id = ? or ? = 0) AND stats.live = 0 AND stats.stream_id = ?
GROUP BY HOUR(stats.time);`,
courseID, courseID, streamID).Scan(&res).Error
return res, err
}

// GetLectureStats returns the number of viewers during a lecture
func (d statisticsDao) GetLectureStats(courseID uint, streamID uint) ([]Stat, error) {
var res []Stat
err := DB.Raw(`SELECT Date_FORMAT(stats.time, "%H:%i") AS x, stats.viewers AS y
FROM stats
JOIN streams s ON s.id = stats.stream_id
WHERE s.course_id = ? AND s.id = ? AND stats.live = 1
ORDER BY x;`, courseID, streamID).Scan(&res).Error
return res, err
}

// GetStreamNumLiveViews returns the number of viewers currently watching a live stream.
func (d statisticsDao) GetStreamNumLiveViews(streamID uint) (int, error) {
var res int
Expand Down Expand Up @@ -155,6 +222,13 @@ func (d statisticsDao) GetStudentActivityCourseStats(courseID uint, live bool) (
return retVal, err
}

func (d statisticsDao) GetLectureLiveStats(streamID uint) ([]model.Stat, error) {
var res []model.Stat
err := DB.Raw("SELECT * FROM stats WHERE stream_id = ? AND live = 1", streamID).Scan(&res).Error

return res, err
}

// Stat key value struct that is parsable by Chart.js without further modifications.
// See https://www.chartjs.org/docs/master/general/data-structures.html
type Stat struct {
Expand Down
Loading

0 comments on commit 0e9dc3e

Please sign in to comment.