gosora/routes/panel/analytics.go
Azareal 6bed36b8c6 Use dyntmpl for the analytics panes.
Localised a few more things on the dashboard.
Use questions marks instead of numbers for placeholders on the dashboard.

Added the panel_dashboard_cpu phrase.
Added the panel_dashboard_ram phrase.
2019-04-29 16:11:26 +10:00

927 lines
30 KiB
Go

package panel
import (
"database/sql"
"errors"
"log"
"net/http"
"strconv"
"time"
c "github.com/Azareal/Gosora/common"
"github.com/Azareal/Gosora/common/phrases"
"github.com/Azareal/Gosora/query_gen"
)
// TODO: Move this to another file, probably common/pages.go
type AnalyticsTimeRange struct {
Quantity int
Unit string
Slices int
SliceWidth int
Range string
}
func analyticsTimeRange(rawTimeRange string) (*AnalyticsTimeRange, error) {
timeRange := &AnalyticsTimeRange{}
timeRange.Quantity = 6
timeRange.Unit = "hour"
timeRange.Slices = 12
timeRange.SliceWidth = 60 * 30
timeRange.Range = "six-hours"
switch rawTimeRange {
// This might be pushing it, we might want to come up with a more efficient scheme for dealing with large timeframes like this
case "one-year":
timeRange.Quantity = 12
timeRange.Unit = "month"
timeRange.Slices = 12
timeRange.SliceWidth = 60 * 60 * 24 * 30
timeRange.Range = "one-year"
case "three-months":
timeRange.Quantity = 90
timeRange.Unit = "day"
timeRange.Slices = 30
timeRange.SliceWidth = 60 * 60 * 24 * 3
timeRange.Range = "three-months"
case "one-month":
timeRange.Quantity = 30
timeRange.Unit = "day"
timeRange.Slices = 30
timeRange.SliceWidth = 60 * 60 * 24
timeRange.Range = "one-month"
case "one-week":
timeRange.Quantity = 7
timeRange.Unit = "day"
timeRange.Slices = 14
timeRange.SliceWidth = 60 * 60 * 12
timeRange.Range = "one-week"
case "two-days": // Two days is experimental
timeRange.Quantity = 2
timeRange.Unit = "day"
timeRange.Slices = 24
timeRange.SliceWidth = 60 * 60 * 2
timeRange.Range = "two-days"
case "one-day":
timeRange.Quantity = 1
timeRange.Unit = "day"
timeRange.Slices = 24
timeRange.SliceWidth = 60 * 60
timeRange.Range = "one-day"
case "twelve-hours":
timeRange.Quantity = 12
timeRange.Slices = 24
timeRange.Range = "twelve-hours"
case "six-hours", "":
default:
return timeRange, errors.New("Unknown time range")
}
return timeRange, nil
}
func analyticsTimeRangeToLabelList(timeRange *AnalyticsTimeRange) (revLabelList []int64, labelList []int64, viewMap map[int64]int64) {
viewMap = make(map[int64]int64)
var currentTime = time.Now().Unix()
for i := 1; i <= timeRange.Slices; i++ {
var label = currentTime - int64(i*timeRange.SliceWidth)
revLabelList = append(revLabelList, label)
viewMap[label] = 0
}
for _, value := range revLabelList {
labelList = append(labelList, value)
}
return revLabelList, labelList, viewMap
}
func analyticsRowsToViewMap(rows *sql.Rows, labelList []int64, viewMap map[int64]int64) (map[int64]int64, error) {
defer rows.Close()
for rows.Next() {
var count int64
var createdAt time.Time
err := rows.Scan(&count, &createdAt)
if err != nil {
return viewMap, err
}
var unixCreatedAt = createdAt.Unix()
// TODO: Bulk log this
if c.Dev.SuperDebug {
log.Print("count: ", count)
log.Print("createdAt: ", createdAt)
log.Print("unixCreatedAt: ", unixCreatedAt)
}
for _, value := range labelList {
if unixCreatedAt > value {
viewMap[value] += count
break
}
}
}
return viewMap, rows.Err()
}
func PreAnalyticsDetail(w http.ResponseWriter, r *http.Request, user *c.User) (*c.BasePanelPage, c.RouteError) {
basePage, ferr := buildBasePage(w, r, user, "analytics", "analytics")
if ferr != nil {
return nil, ferr
}
basePage.AddSheet("chartist/chartist.min.css")
basePage.AddScript("chartist/chartist.min.js")
basePage.AddScriptAsync("analytics.js")
return basePage, nil
}
func AnalyticsViews(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsViews")
// TODO: Add some sort of analytics store / iterator?
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
var viewItems []c.PanelAnalyticsItem
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
viewItems = append(viewItems, c.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
var ttime string
if timeRange.Range == "six-hours" || timeRange.Range == "twelve-hours" || timeRange.Range == "one-day" {
ttime = "time"
}
pi := c.PanelAnalyticsStd{graph, viewItems, timeRange.Range, timeRange.Unit, ttime}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_views", pi})
}
func AnalyticsRouteViews(w http.ResponseWriter, r *http.Request, user c.User, route string) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsRouteViews")
// TODO: Validate the route is valid
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, createdAt").Where("route = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(route)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
var viewItems []c.PanelAnalyticsItem
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
viewItems = append(viewItems, c.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
pi := c.PanelAnalyticsRoutePage{basePage, c.SanitiseSingleLine(route), graph, viewItems, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_route_views", pi})
}
func AnalyticsAgentViews(w http.ResponseWriter, r *http.Request, user c.User, agent string) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
// ? Only allow valid agents? The problem with this is that agents wind up getting renamed and it would take a migration to get them all up to snuff
agent = c.SanitiseSingleLine(agent)
c.DebugLog("in panel.AnalyticsAgentViews")
// TODO: Verify the agent is valid
rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, createdAt").Where("browser = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(agent)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
friendlyAgent, ok := phrases.GetUserAgentPhrase(agent)
if !ok {
friendlyAgent = agent
}
pi := c.PanelAnalyticsAgentPage{basePage, agent, friendlyAgent, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_agent_views", pi})
}
func AnalyticsForumViews(w http.ResponseWriter, r *http.Request, user c.User, sfid string) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
fid, err := strconv.Atoi(sfid)
if err != nil {
return c.LocalError("Invalid integer", w, r, user)
}
c.DebugLog("in panel.AnalyticsForumViews")
// TODO: Verify the agent is valid
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, createdAt").Where("forum = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(fid)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
forum, err := c.Forums.Get(fid)
if err != nil {
return c.InternalError(err, w, r)
}
pi := c.PanelAnalyticsAgentPage{basePage, sfid, forum.Name, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_forum_views", pi})
}
func AnalyticsSystemViews(w http.ResponseWriter, r *http.Request, user c.User, system string) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
system = c.SanitiseSingleLine(system)
c.DebugLog("in panel.AnalyticsSystemViews")
// TODO: Verify the OS name is valid
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, createdAt").Where("system = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(system)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
friendlySystem, ok := phrases.GetOSPhrase(system)
if !ok {
friendlySystem = system
}
pi := c.PanelAnalyticsAgentPage{basePage, system, friendlySystem, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_system_views", pi})
}
func AnalyticsLanguageViews(w http.ResponseWriter, r *http.Request, user c.User, lang string) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
lang = c.SanitiseSingleLine(lang)
c.DebugLog("in panel.AnalyticsLanguageViews")
// TODO: Verify the language code is valid
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, createdAt").Where("lang = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(lang)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
friendlyLang, ok := phrases.GetHumanLangPhrase(lang)
if !ok {
friendlyLang = lang
}
pi := c.PanelAnalyticsAgentPage{basePage, lang, friendlyLang, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_lang_views", pi})
}
func AnalyticsReferrerViews(w http.ResponseWriter, r *http.Request, user c.User, domain string) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsReferrerViews")
// TODO: Verify the agent is valid
rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, createdAt").Where("domain = ?").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query(domain)
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
pi := c.PanelAnalyticsAgentPage{basePage, c.SanitiseSingleLine(domain), "", graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_referrer_views", pi})
}
func AnalyticsTopics(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsTopics")
rows, err := qgen.NewAcc().Select("topicchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
var viewItems []c.PanelAnalyticsItem
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
viewItems = append(viewItems, c.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
pi := c.PanelAnalyticsStd{graph, viewItems, timeRange.Range, timeRange.Unit, "time"}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_topics", pi})
}
func AnalyticsPosts(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
c.DebugLog("in panel.AnalyticsPosts")
rows, err := qgen.NewAcc().Select("postchunks").Columns("count, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
viewMap, err = analyticsRowsToViewMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
var viewList []int64
var viewItems []c.PanelAnalyticsItem
for _, value := range revLabelList {
viewList = append(viewList, viewMap[value])
viewItems = append(viewItems, c.PanelAnalyticsItem{Time: value, Count: viewMap[value]})
}
graph := c.PanelTimeGraph{Series: [][]int64{viewList}, Labels: labelList}
c.DebugLogf("graph: %+v\n", graph)
pi := c.PanelAnalyticsStd{graph, viewItems, timeRange.Range, timeRange.Unit, "time"}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_posts", pi})
}
func analyticsRowsToNameMap(rows *sql.Rows) (map[string]int, error) {
nameMap := make(map[string]int)
defer rows.Close()
for rows.Next() {
var count int
var name string
err := rows.Scan(&count, &name)
if err != nil {
return nameMap, err
}
// TODO: Bulk log this
if c.Dev.SuperDebug {
log.Print("count: ", count)
log.Print("name: ", name)
}
nameMap[name] += count
}
return nameMap, rows.Err()
}
func analyticsRowsToDuoMap(rows *sql.Rows, labelList []int64, viewMap map[int64]int64) (map[string]map[int64]int64, map[string]int, error) {
vMap := make(map[string]map[int64]int64)
nameMap := make(map[string]int)
defer rows.Close()
for rows.Next() {
var count int64
var name string
var createdAt time.Time
err := rows.Scan(&count, &name, &createdAt)
if err != nil {
return vMap, nameMap, err
}
// TODO: Bulk log this
var unixCreatedAt = createdAt.Unix()
if c.Dev.SuperDebug {
log.Print("count: ", count)
log.Print("name: ", name)
log.Print("createdAt: ", createdAt)
log.Print("unixCreatedAt: ", unixCreatedAt)
}
vvMap, ok := vMap[name]
if !ok {
vvMap = make(map[int64]int64)
for key, val := range viewMap {
vvMap[key] = val
}
vMap[name] = vvMap
}
for _, value := range labelList {
if unixCreatedAt > value {
vvMap[value] += count
break
}
}
nameMap[name] += int(count)
}
return vMap, nameMap, rows.Err()
}
type OVItem struct {
name string
count int
viewMap map[int64]int64
}
func analyticsVMapToOVList(vMap map[string]map[int64]int64) (ovList []OVItem) {
// Order the map
for name, viewMap := range vMap {
var totcount int
for _, count := range viewMap {
totcount += int(count)
}
ovList = append(ovList, OVItem{name, totcount, viewMap})
}
// Use bubble sort for now as there shouldn't be too many items
for i := 0; i < len(ovList)-1; i++ {
for j := 0; j < len(ovList)-1; j++ {
if ovList[j].count > ovList[j+1].count {
temp := ovList[j]
ovList[j] = ovList[j+1]
ovList[j+1] = temp
}
}
}
// Invert the direction
var tOVList []OVItem
for i := len(ovList) - 1; i >= 0; i-- {
tOVList = append(tOVList, ovList[i])
}
return tOVList
}
func AnalyticsForums(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
basePage.AddSheet("chartist/chartist-plugin-legend.css")
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_forums").Columns("count, forum, createdAt").Where("forum != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
vMap, forumMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
ovList := analyticsVMapToOVList(vMap)
var vList [][]int64
var legendList []string
var i int
for _, ovitem := range ovList {
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, ovitem.viewMap[value])
}
vList = append(vList, viewList)
fid, err := strconv.Atoi(ovitem.name)
if err != nil {
return c.InternalError(err, w, r)
}
var lName string
forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows {
// TODO: Localise this
lName = "Deleted Forum"
} else if err != nil {
return c.InternalError(err, w, r)
} else {
lName = forum.Name
}
legendList = append(legendList, lName)
if i >= 6 {
break
}
i++
}
graph := c.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
c.DebugLogf("graph: %+v\n", graph)
// TODO: Sort this slice
var forumItems []c.PanelAnalyticsAgentsItem
for sfid, count := range forumMap {
fid, err := strconv.Atoi(sfid)
if err != nil {
return c.InternalError(err, w, r)
}
var lName string
forum, err := c.Forums.Get(fid)
if err == sql.ErrNoRows {
// TODO: Localise this
lName = "Deleted Forum"
} else if err != nil {
return c.InternalError(err, w, r)
} else {
lName = forum.Name
}
forumItems = append(forumItems, c.PanelAnalyticsAgentsItem{
Agent: sfid,
FriendlyAgent: lName,
Count: count,
})
}
pi := c.PanelAnalyticsDuoPage{basePage, forumItems, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_forums", pi})
}
func AnalyticsRoutes(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
basePage.AddSheet("chartist/chartist-plugin-legend.css")
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks").Columns("count, route, createdAt").Where("route != ''").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
vMap, routeMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
ovList := analyticsVMapToOVList(vMap)
var vList [][]int64
var legendList []string
var i int
for _, ovitem := range ovList {
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, ovitem.viewMap[value])
}
vList = append(vList, viewList)
legendList = append(legendList, ovitem.name)
if i >= 6 {
break
}
i++
}
graph := c.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
c.DebugLogf("graph: %+v\n", graph)
// TODO: Sort this slice
var routeItems []c.PanelAnalyticsRoutesItem
for route, count := range routeMap {
routeItems = append(routeItems, c.PanelAnalyticsRoutesItem{
Route: route,
Count: count,
})
}
pi := c.PanelAnalyticsRoutesPage{basePage, routeItems, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_routes", pi})
}
// Trialling multi-series charts
func AnalyticsAgents(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
basePage.AddSheet("chartist/chartist-plugin-legend.css")
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_agents").Columns("count, browser, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
vMap, agentMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
ovList := analyticsVMapToOVList(vMap)
var vList [][]int64
var legendList []string
var i int
for _, ovitem := range ovList {
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, ovitem.viewMap[value])
}
vList = append(vList, viewList)
lName, ok := phrases.GetUserAgentPhrase(ovitem.name)
if !ok {
lName = ovitem.name
}
legendList = append(legendList, lName)
if i >= 6 {
break
}
i++
}
graph := c.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
c.DebugLogf("graph: %+v\n", graph)
// TODO: Sort this slice
var agentItems []c.PanelAnalyticsAgentsItem
for agent, count := range agentMap {
aAgent, ok := phrases.GetUserAgentPhrase(agent)
if !ok {
aAgent = agent
}
agentItems = append(agentItems, c.PanelAnalyticsAgentsItem{
Agent: agent,
FriendlyAgent: aAgent,
Count: count,
})
}
pi := c.PanelAnalyticsDuoPage{basePage, agentItems, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_agents", pi})
}
func AnalyticsSystems(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
basePage.AddSheet("chartist/chartist-plugin-legend.css")
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_systems").Columns("count, system, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
vMap, osMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
ovList := analyticsVMapToOVList(vMap)
var vList [][]int64
var legendList []string
var i int
for _, ovitem := range ovList {
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, ovitem.viewMap[value])
}
vList = append(vList, viewList)
lName, ok := phrases.GetOSPhrase(ovitem.name)
if !ok {
lName = ovitem.name
}
legendList = append(legendList, lName)
if i >= 6 {
break
}
i++
}
graph := c.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
c.DebugLogf("graph: %+v\n", graph)
// TODO: Sort this slice
var systemItems []c.PanelAnalyticsAgentsItem
for system, count := range osMap {
sSystem, ok := phrases.GetOSPhrase(system)
if !ok {
sSystem = system
}
systemItems = append(systemItems, c.PanelAnalyticsAgentsItem{
Agent: system,
FriendlyAgent: sSystem,
Count: count,
})
}
pi := c.PanelAnalyticsDuoPage{basePage, systemItems, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_systems", pi})
}
func AnalyticsLanguages(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := PreAnalyticsDetail(w, r, &user)
if ferr != nil {
return ferr
}
basePage.AddScript("chartist/chartist-plugin-legend.min.js")
basePage.AddSheet("chartist/chartist-plugin-legend.css")
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
revLabelList, labelList, viewMap := analyticsTimeRangeToLabelList(timeRange)
rows, err := qgen.NewAcc().Select("viewchunks_langs").Columns("count, lang, createdAt").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
vMap, langMap, err := analyticsRowsToDuoMap(rows, labelList, viewMap)
if err != nil {
return c.InternalError(err, w, r)
}
ovList := analyticsVMapToOVList(vMap)
var vList [][]int64
var legendList []string
var i int
for _, ovitem := range ovList {
var viewList []int64
for _, value := range revLabelList {
viewList = append(viewList, ovitem.viewMap[value])
}
vList = append(vList, viewList)
lName, ok := phrases.GetHumanLangPhrase(ovitem.name)
if !ok {
lName = ovitem.name
}
legendList = append(legendList, lName)
if i >= 6 {
break
}
i++
}
graph := c.PanelTimeGraph{Series: vList, Labels: labelList, Legends: legendList}
c.DebugLogf("graph: %+v\n", graph)
// TODO: Can we de-duplicate these analytics functions further?
// TODO: Sort this slice
var langItems []c.PanelAnalyticsAgentsItem
for lang, count := range langMap {
lLang, ok := phrases.GetHumanLangPhrase(lang)
if !ok {
lLang = lang
}
langItems = append(langItems, c.PanelAnalyticsAgentsItem{
Agent: lang,
FriendlyAgent: lLang,
Count: count,
})
}
pi := c.PanelAnalyticsDuoPage{basePage, langItems, graph, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_langs", pi})
}
func AnalyticsReferrers(w http.ResponseWriter, r *http.Request, user c.User) c.RouteError {
basePage, ferr := buildBasePage(w, r, &user, "analytics", "analytics")
if ferr != nil {
return ferr
}
timeRange, err := analyticsTimeRange(r.FormValue("timeRange"))
if err != nil {
return c.LocalError(err.Error(), w, r, user)
}
rows, err := qgen.NewAcc().Select("viewchunks_referrers").Columns("count, domain").DateCutoff("createdAt", timeRange.Quantity, timeRange.Unit).Query()
if err != nil && err != sql.ErrNoRows {
return c.InternalError(err, w, r)
}
refMap, err := analyticsRowsToNameMap(rows)
if err != nil {
return c.InternalError(err, w, r)
}
// TODO: Sort this slice
var refItems []c.PanelAnalyticsAgentsItem
for domain, count := range refMap {
refItems = append(refItems, c.PanelAnalyticsAgentsItem{
Agent: c.SanitiseSingleLine(domain),
Count: count,
})
}
pi := c.PanelAnalyticsAgentsPage{basePage, refItems, timeRange.Range}
return renderTemplate("panel", w, r, basePage.Header, c.Panel{basePage, "panel_analytics_right","analytics","panel_analytics_referrers", pi})
}