diff --git a/db/contrib.go b/db/contrib.go index 17a0e1f..f181103 100644 --- a/db/contrib.go +++ b/db/contrib.go @@ -15,7 +15,7 @@ type Contrib struct { } const saveContribQuery = ` -insert into contributions (week, author, owner, repo, commits, additions, deletions) +insert into contribs (week, author, owner, repo, commits, additions, deletions) values (:week, :author, :owner, :repo, :commits, :additions, :deletions) on duplicate key update commits=values(commits), additions=values(additions), deletions=values(deletions)` diff --git a/db/db.go b/db/db.go index 884ae57..2b3623c 100644 --- a/db/db.go +++ b/db/db.go @@ -34,9 +34,13 @@ func mustSelect(dest interface{}, query string, args ...interface{}) { func measure(op string, start time.Time) { duration := time.Since(start).Nanoseconds() outcome := "succeeded" - if err := recover(); err != nil { + err := recover() + if err != nil { outcome = "failed" } log.Printf("Operation %s %s; time: %d (%dms)\n", op, outcome, duration, duration/1000000) + if err != nil { + panic(err) + } } diff --git a/db/stat.go b/db/stat.go new file mode 100644 index 0000000..b859a6c --- /dev/null +++ b/db/stat.go @@ -0,0 +1,163 @@ +package db + +import ( + "net/http" + "strconv" + "time" +) + +type ( + StatRequest struct { + Org string + Team string + User string + From int64 + To int64 + } + StatItem struct { + Item string `json:"item"` + Value int `json:"value"` + } + StatPoint struct { + StatItem + Timestamp uint64 `json:"ts"` + } +) + +const orgReposTopQuery = ` +select + c.repo as item, + sum(c.commits) as value +from contribs c +join members m on + c.author = m.user and + c.owner = m.org +where + m.id is not null and + c.owner = ? and + c.week >= ? and + c.week <= ? +group by item +order by value desc` + +const orgReposActivityQuery = ` +select + c.week as ts, + c.repo as item, + sum(c.commits) as value +from contribs c +join members m on + c.author = m.user and + c.owner = m.org +where + m.id is not null and + c.owner = ? and + c.week >= ? and + c.week <= ? +group by ts, item +order by ts, item` + +const orgTeamsTopQuery = ` +select + t.name as item, + sum(c.commits) value +from contribs c +join members m on + c.author = m.user and + c.owner = m.org +join teams t on + m.team_id = t.id +where + m.id is not null and + c.owner = ? and + c.week >= ? and + c.week <= ? +group by item +order by value desc` + +const orgTeamsActivityQuery = ` +select + c.week as ts, + t.name as item, + sum(c.commits) as value +from contribs c +join members m on + c.author = m.user and + c.owner = m.org +join teams t on + m.team_id = t.id +where + m.id is not null and + c.owner = ? and + c.week >= ? and + c.week <= ? +group by ts, item +order by ts, item` + +const orgUsersTopQuery = ` +select + c.author as item, + sum(c.commits) value +from contribs c +join members m on + c.author = m.user and + c.owner = m.org +where + m.id is not null and + c.owner = ? and + c.week >= ? and + c.week <= ? +group by item +order by value desc` + +func StatOrgReposTop(r *StatRequest) (res []StatItem) { + defer measure("StatOrgReposTop", time.Now()) + mustSelect(&res, orgReposTopQuery, r.Org, r.From, r.To) + return +} + +func StatOrgReposActivity(r *StatRequest) (res []StatPoint) { + defer measure("StatOrgReposActivity", time.Now()) + mustSelect(&res, orgReposActivityQuery, r.Org, r.From, r.To) + return +} + +func StatOrgTeamsTop(r *StatRequest) (res []StatItem) { + defer measure("StatOrgTeamsTop", time.Now()) + mustSelect(&res, orgTeamsTopQuery, r.Org, r.From, r.To) + return +} + +func StatOrgTeamsActivity(r *StatRequest) (res []StatPoint) { + defer measure("StatOrgTeamsActivity", time.Now()) + mustSelect(&res, orgTeamsActivityQuery, r.Org, r.From, r.To) + return +} + +func StatOrgUsersTop(r *StatRequest) (res []StatItem) { + defer measure("StatOrgUsersTop", time.Now()) + mustSelect(&res, orgUsersTopQuery, r.Org, r.From, r.To) + return +} + +func ParseRequest(r *http.Request) *StatRequest { + var err error + var from, to int64 + if len(r.FormValue("from")) > 0 { + if from, err = strconv.ParseInt(r.FormValue("from"), 10, 64); err != nil { + panic(err) + } + } + if len(r.FormValue("to")) > 0 { + if to, err = strconv.ParseInt(r.FormValue("to"), 10, 64); err != nil { + panic(err) + } + } + return &StatRequest{ + Org: r.FormValue("org"), + Team: r.FormValue("team"), + User: r.FormValue("user"), + From: from, + To: to, + } +} diff --git a/db/team.go b/db/team.go index 074dd13..f27824f 100644 --- a/db/team.go +++ b/db/team.go @@ -6,6 +6,7 @@ import ( type Team struct { ID uint64 `json:"id"` + Slug string `json:"slug"` Owner string `json:"owner"` Name string `json:"name"` } diff --git a/server/server.go b/server/server.go index a3336f2..30935a9 100644 --- a/server/server.go +++ b/server/server.go @@ -31,6 +31,11 @@ func init() { http.HandleFunc("/api/orgs", apiOrgsHandler) http.HandleFunc("/api/teams", apiTeamsHandler) http.HandleFunc("/api/repos", apiReposHandler) + http.HandleFunc("/api/stat/repos/top", statOrgReposTop) + http.HandleFunc("/api/stat/repos/activity", statOrgReposActivity) + http.HandleFunc("/api/stat/teams/top", statOrgTeamsTop) + http.HandleFunc("/api/stat/teams/activity", statOrgTeamsActivity) + http.HandleFunc("/api/stat/users/top", statOrgUsersTop) } func Start() { diff --git a/server/stat.go b/server/stat.go new file mode 100644 index 0000000..8f91b9c --- /dev/null +++ b/server/stat.go @@ -0,0 +1,32 @@ +package server + +import ( + "net/http" + + "github.com/localhots/empact/db" +) + +func statOrgReposTop(w http.ResponseWriter, r *http.Request) { + top := db.StatOrgReposTop(db.ParseRequest(r)) + respondWith(w, top) +} + +func statOrgReposActivity(w http.ResponseWriter, r *http.Request) { + activity := db.StatOrgReposActivity(db.ParseRequest(r)) + respondWith(w, activity) +} + +func statOrgTeamsTop(w http.ResponseWriter, r *http.Request) { + top := db.StatOrgTeamsTop(db.ParseRequest(r)) + respondWith(w, top) +} + +func statOrgTeamsActivity(w http.ResponseWriter, r *http.Request) { + activity := db.StatOrgTeamsActivity(db.ParseRequest(r)) + respondWith(w, activity) +} + +func statOrgUsersTop(w http.ResponseWriter, r *http.Request) { + top := db.StatOrgUsersTop(db.ParseRequest(r)) + respondWith(w, top) +}