Org and team charts
This commit is contained in:
parent
311bd021a8
commit
e6f934c5dc
|
@ -67,11 +67,11 @@ var Dashboard = React.createClass({
|
||||||
var OrgStats = React.createClass({
|
var OrgStats = React.createClass({
|
||||||
mixins: [Router.Navigation, Router.State],
|
mixins: [Router.Navigation, Router.State],
|
||||||
render: function(){
|
render: function(){
|
||||||
var topTeams = "/api/stat/teams/top?org="+ this.getParams().org,
|
var topRepos = "/api/stat/orgs/top?org="+ this.getParams().org +"&item=repo",
|
||||||
teamURL = "/app/"+ this.getParams().org +"/teams/";
|
repoURL = "/app/"+ this.getParams().org +"/repos/";
|
||||||
return (
|
return (
|
||||||
<section className="content">
|
<section className="content">
|
||||||
<BarChart api={topTeams} link={teamURL}/>
|
<BarChart api={topRepos} link={repoURL}/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -80,8 +80,12 @@ var OrgStats = React.createClass({
|
||||||
var TeamStats = React.createClass({
|
var TeamStats = React.createClass({
|
||||||
mixins: [Router.Navigation, Router.State],
|
mixins: [Router.Navigation, Router.State],
|
||||||
render: function(){
|
render: function(){
|
||||||
|
var topRepos = "/api/stat/teams/top?org="+ this.getParams().org +"&team="+ this.getParams().team +"&item=repo",
|
||||||
|
repoURL = "/app/"+ this.getParams().org +"/repos/";
|
||||||
return (
|
return (
|
||||||
<section className="content">Team stats!</section>
|
<section className="content">
|
||||||
|
<BarChart api={topRepos} link={repoURL}/>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
var Router = ReactRouter;
|
|
||||||
|
|
||||||
var BarChart = React.createClass({displayName: "BarChart",
|
|
||||||
barHeight: 40,
|
|
||||||
barMargin: 5,
|
|
||||||
|
|
||||||
getInitialState: function() {
|
|
||||||
return {points: [], max: 1};
|
|
||||||
},
|
|
||||||
|
|
||||||
componentDidMount: function() {
|
|
||||||
$.get(this.props.api, function(res){
|
|
||||||
var max = 1;
|
|
||||||
res.map(function(el) {
|
|
||||||
if (el.value > max) {
|
|
||||||
max = el.value
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.setState({points: res, max: max});
|
|
||||||
}.bind(this))
|
|
||||||
},
|
|
||||||
|
|
||||||
height: function() {
|
|
||||||
if (this.state.points.length === 0) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return this.y(this.state.points.length) - this.barMargin;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
y: function(i) {
|
|
||||||
return i*(this.barHeight + this.barMargin);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
return (
|
|
||||||
React.createElement("svg", {className: "barchart", width: "100%", height: this.height()},
|
|
||||||
this.state.points.map(this.renderBar)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
renderBar: function(point, i) {
|
|
||||||
return (
|
|
||||||
React.createElement(Bar, {key: point.item, point: point, i: i, link: this.props.link,
|
|
||||||
y: this.y(i),
|
|
||||||
width: point.value/this.state.max,
|
|
||||||
height: this.barHeight})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var Bar = React.createClass({displayName: "Bar",
|
|
||||||
mixins: [Router.Navigation],
|
|
||||||
handleClick: function(e) {
|
|
||||||
this.transitionTo(this.props.link + this.props.point.item);
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
|
||||||
var p = this.props.point
|
|
||||||
w = this.props.width*500,
|
|
||||||
label = p.item + ": " + p.value,
|
|
||||||
tx = 10;
|
|
||||||
if (label.length*15 > w) {
|
|
||||||
tx = w + tx;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
React.createElement("g", {onClick: this.handleClick},
|
|
||||||
React.createElement("rect", {className: "bar", fill: Colors2[this.props.i],
|
|
||||||
width: this.props.width*500,
|
|
||||||
height: this.props.height,
|
|
||||||
x: "0", y: this.props.y, rx: "2", ry: "2"}),
|
|
||||||
React.createElement("rect", {className: "label_underlay", x: tx-6, y: this.props.y+10, height: 20, width: label.length*10+5, rx: "3", ry: "3"}),
|
|
||||||
React.createElement("text", {className: "label", x: tx, y: this.props.y + 26}, label)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -10,10 +10,11 @@ var BarChart = React.createClass({
|
||||||
|
|
||||||
componentDidMount: function() {
|
componentDidMount: function() {
|
||||||
$.get(this.props.api, function(res){
|
$.get(this.props.api, function(res){
|
||||||
|
res = res.slice(0, 15);
|
||||||
var max = 1;
|
var max = 1;
|
||||||
res.map(function(el) {
|
res.map(function(el) {
|
||||||
if (el.value > max) {
|
if (el.commits > max) {
|
||||||
max = el.value
|
max = el.commits
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.setState({points: res, max: max});
|
this.setState({points: res, max: max});
|
||||||
|
@ -44,7 +45,7 @@ var BarChart = React.createClass({
|
||||||
return (
|
return (
|
||||||
<Bar key={point.item} point={point} i={i} link={this.props.link}
|
<Bar key={point.item} point={point} i={i} link={this.props.link}
|
||||||
y={this.y(i)}
|
y={this.y(i)}
|
||||||
width={point.value/this.state.max}
|
width={point.commits/this.state.max}
|
||||||
height={this.barHeight} />
|
height={this.barHeight} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -59,9 +60,10 @@ var Bar = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var p = this.props.point
|
var p = this.props.point
|
||||||
w = this.props.width*500,
|
w = this.props.width*500,
|
||||||
label = p.item + ": " + p.value,
|
label = p.item + ": " + p.commits,
|
||||||
|
lw = label.length*10 + 5,
|
||||||
tx = 10;
|
tx = 10;
|
||||||
if (label.length*15 > w) {
|
if (lw > w) {
|
||||||
tx = w + tx;
|
tx = w + tx;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -70,7 +72,7 @@ var Bar = React.createClass({
|
||||||
width={this.props.width*500}
|
width={this.props.width*500}
|
||||||
height={this.props.height}
|
height={this.props.height}
|
||||||
x="0" y={this.props.y} rx="2" ry="2" />
|
x="0" y={this.props.y} rx="2" ry="2" />
|
||||||
<rect className="label_underlay" x={tx-6} y={this.props.y+10} height={20} width={label.length*10+5} rx="3" ry="3" />
|
<rect className="label_underlay" x={tx-6} y={this.props.y+10} height={20} width={lw} rx="3" ry="3" />
|
||||||
<text className="label" x={tx} y={this.props.y + 26}>{label}</text>
|
<text className="label" x={tx} y={this.props.y + 26}>{label}</text>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|
11
db/db.go
11
db/db.go
|
@ -31,6 +31,17 @@ func mustSelect(dest interface{}, query string, args ...interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustSelectN(dest interface{}, query string, params interface{}) {
|
||||||
|
var stmt *sqlx.NamedStmt
|
||||||
|
var err error
|
||||||
|
if stmt, err = db.PrepareNamed(query); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err = stmt.Select(dest, params); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func measure(op string, start time.Time) {
|
func measure(op string, start time.Time) {
|
||||||
duration := time.Since(start).Nanoseconds()
|
duration := time.Since(start).Nanoseconds()
|
||||||
outcome := "succeeded"
|
outcome := "succeeded"
|
||||||
|
|
142
db/stat.go
142
db/stat.go
|
@ -1,57 +1,27 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
StatItem struct {
|
StatItem struct {
|
||||||
Item string `json:"item"`
|
Item string `json:"item"`
|
||||||
Value int `json:"value"`
|
Commits int `json:"commits"`
|
||||||
|
Delta int `json:"delta"`
|
||||||
}
|
}
|
||||||
StatPoint struct {
|
StatPoint struct {
|
||||||
StatItem
|
StatItem
|
||||||
Timestamp uint64 `json:"ts"`
|
Week uint64 `json:"week"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const orgReposTopQuery = `
|
const orgTopQuery = `
|
||||||
select
|
select
|
||||||
c.repo as item,
|
%s as item,
|
||||||
sum(c.commits) as value
|
sum(c.commits) as commits,
|
||||||
from contribs c
|
sum(c.additions) - sum(c.deletions) as delta
|
||||||
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
|
from contribs c
|
||||||
join members m on
|
join members m on
|
||||||
c.author = m.user and
|
c.author = m.user and
|
||||||
|
@ -60,17 +30,18 @@ join teams t on
|
||||||
m.team_id = t.id
|
m.team_id = t.id
|
||||||
where
|
where
|
||||||
m.id is not null and
|
m.id is not null and
|
||||||
c.owner = ? and
|
c.owner = :org and
|
||||||
c.week >= ? and
|
c.week >= :from and
|
||||||
c.week <= ?
|
c.week <= :to
|
||||||
group by item
|
group by item
|
||||||
order by value desc`
|
order by %s desc`
|
||||||
|
|
||||||
const orgTeamsActivityQuery = `
|
const orgActivityQuery = `
|
||||||
select
|
select
|
||||||
c.week as ts,
|
%s as item,
|
||||||
t.name as item,
|
sum(c.commits) as commits,
|
||||||
sum(c.commits) as value
|
sum(c.additions) - sum(c.deletions) as delta,
|
||||||
|
c.week as week
|
||||||
from contribs c
|
from contribs c
|
||||||
join members m on
|
join members m on
|
||||||
c.author = m.user and
|
c.author = m.user and
|
||||||
|
@ -79,54 +50,73 @@ join teams t on
|
||||||
m.team_id = t.id
|
m.team_id = t.id
|
||||||
where
|
where
|
||||||
m.id is not null and
|
m.id is not null and
|
||||||
c.owner = ? and
|
c.owner = :org and
|
||||||
c.week >= ? and
|
c.week >= :from and
|
||||||
c.week <= ?
|
c.week <= :to
|
||||||
group by ts, item
|
group by item, week
|
||||||
order by ts, item`
|
order by week, %s desc`
|
||||||
|
|
||||||
const orgUsersTopQuery = `
|
const teamTopQuery = `
|
||||||
select
|
select
|
||||||
c.author as item,
|
%s as item,
|
||||||
sum(c.commits) value
|
sum(c.commits) as commits,
|
||||||
|
sum(c.additions) - sum(c.deletions) as delta
|
||||||
from contribs c
|
from contribs c
|
||||||
join members m on
|
join members m on
|
||||||
c.author = m.user and
|
c.author = m.user and
|
||||||
c.owner = m.org
|
c.owner = m.org
|
||||||
|
join teams t on
|
||||||
|
m.team_id = t.id and
|
||||||
|
t.name = :team
|
||||||
where
|
where
|
||||||
m.id is not null and
|
m.id is not null and
|
||||||
c.owner = ? and
|
c.owner = :org and
|
||||||
c.week >= ? and
|
c.week >= :from and
|
||||||
c.week <= ?
|
c.week <= :to
|
||||||
group by item
|
group by item
|
||||||
order by value desc`
|
order by %s desc`
|
||||||
|
|
||||||
func StatOrgReposTop(org string, from, to int64) (res []StatItem) {
|
const teamActivityQuery = `
|
||||||
defer measure("StatOrgReposTop", time.Now())
|
select
|
||||||
mustSelect(&res, orgReposTopQuery, org, from, to)
|
%s as item,
|
||||||
|
sum(c.commits) as commits,
|
||||||
|
sum(c.additions) - sum(c.deletions) as delta,
|
||||||
|
c.week as week
|
||||||
|
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 and
|
||||||
|
t.name = :team
|
||||||
|
where
|
||||||
|
m.id is not null and
|
||||||
|
c.owner = :org and
|
||||||
|
c.week >= :from and
|
||||||
|
c.week <= :to
|
||||||
|
group by item, week
|
||||||
|
order by week, %s desc`
|
||||||
|
|
||||||
|
func StatOrgTop(p map[string]interface{}) (res []StatItem) {
|
||||||
|
defer measure("StatOrgTop", time.Now())
|
||||||
|
mustSelectN(&res, fmt.Sprintf(orgTopQuery, p["item"], p["sort"]), p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatOrgReposActivity(org string, from, to int64) (res []StatPoint) {
|
func StatOrgActivity(p map[string]interface{}) (res []StatPoint) {
|
||||||
defer measure("StatOrgReposActivity", time.Now())
|
defer measure("StatOrgActivity", time.Now())
|
||||||
mustSelect(&res, orgReposActivityQuery, org, from, to)
|
mustSelectN(&res, fmt.Sprintf(orgActivityQuery, p["item"], p["sort"]), p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatOrgTeamsTop(org string, from, to int64) (res []StatItem) {
|
func StatTeamTop(p map[string]interface{}) (res []StatItem) {
|
||||||
defer measure("StatOrgTeamsTop", time.Now())
|
defer measure("StatTeamTop", time.Now())
|
||||||
mustSelect(&res, orgTeamsTopQuery, org, from, to)
|
mustSelectN(&res, fmt.Sprintf(teamTopQuery, p["item"], p["sort"]), p)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatOrgTeamsActivity(org string, from, to int64) (res []StatPoint) {
|
func StatTeamActivity(p map[string]interface{}) (res []StatPoint) {
|
||||||
defer measure("StatOrgTeamsActivity", time.Now())
|
defer measure("StatTeamActivity", time.Now())
|
||||||
mustSelect(&res, orgTeamsActivityQuery, org, from, to)
|
mustSelectN(&res, fmt.Sprintf(teamActivityQuery, p["item"], p["sort"]), p)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func StatOrgUsersTop(org string, from, to int64) (res []StatItem) {
|
|
||||||
defer measure("StatOrgUsersTop", time.Now())
|
|
||||||
mustSelect(&res, orgUsersTopQuery, org, from, to)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,12 @@ func apiOrgsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func apiTeamsHandler(w http.ResponseWriter, r *http.Request) {
|
func apiTeamsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
req, stat := parseRequest(w, r)
|
req, stat := parseRequest(w, r)
|
||||||
teams := db.OrgTeams(stat.org)
|
teams := db.OrgTeams(stat.Org)
|
||||||
req.respondWith(teams)
|
req.respondWith(teams)
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiReposHandler(w http.ResponseWriter, r *http.Request) {
|
func apiReposHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
req, stat := parseRequest(w, r)
|
req, stat := parseRequest(w, r)
|
||||||
repos := db.OrgRepos(stat.org)
|
repos := db.OrgRepos(stat.Org)
|
||||||
req.respondWith(repos)
|
req.respondWith(repos)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,13 @@ type (
|
||||||
login string
|
login string
|
||||||
}
|
}
|
||||||
statRequest struct {
|
statRequest struct {
|
||||||
org string
|
Org string `structs:"org"`
|
||||||
team string
|
Team string `structs:"team"`
|
||||||
user string
|
User string `structs:"user"`
|
||||||
from int64
|
From int64 `structs:"from"`
|
||||||
to int64
|
To int64 `structs:"to"`
|
||||||
|
Item string `structs:"item"`
|
||||||
|
Sort string `structs:"sort"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,12 +73,33 @@ func parseStatRequest(r *http.Request) *statRequest {
|
||||||
} else {
|
} else {
|
||||||
to = time.Now().Unix()
|
to = time.Now().Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var item string
|
||||||
|
switch val := r.FormValue("item"); val {
|
||||||
|
case "author":
|
||||||
|
item = "c.author"
|
||||||
|
case "team":
|
||||||
|
item = "t.name"
|
||||||
|
default:
|
||||||
|
item = "c.repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
var sort string
|
||||||
|
switch val := r.FormValue("sort"); val {
|
||||||
|
case "commits", "delta":
|
||||||
|
sort = val
|
||||||
|
default:
|
||||||
|
sort = "commits"
|
||||||
|
}
|
||||||
|
|
||||||
return &statRequest{
|
return &statRequest{
|
||||||
org: r.FormValue("org"),
|
Org: r.FormValue("org"),
|
||||||
team: r.FormValue("team"),
|
Team: r.FormValue("team"),
|
||||||
user: r.FormValue("user"),
|
User: r.FormValue("user"),
|
||||||
from: from,
|
From: from,
|
||||||
to: to,
|
To: to,
|
||||||
|
Item: item,
|
||||||
|
Sort: sort,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,15 +18,16 @@ var (
|
||||||
func init() {
|
func init() {
|
||||||
http.HandleFunc("/auth/signin", authSigninHandler)
|
http.HandleFunc("/auth/signin", authSigninHandler)
|
||||||
http.HandleFunc("/auth/callback", authCallbackHandler)
|
http.HandleFunc("/auth/callback", authCallbackHandler)
|
||||||
|
|
||||||
http.HandleFunc("/api/", authHandler)
|
http.HandleFunc("/api/", authHandler)
|
||||||
http.HandleFunc("/api/orgs", apiOrgsHandler)
|
http.HandleFunc("/api/orgs", apiOrgsHandler)
|
||||||
http.HandleFunc("/api/teams", apiTeamsHandler)
|
http.HandleFunc("/api/teams", apiTeamsHandler)
|
||||||
http.HandleFunc("/api/repos", apiReposHandler)
|
http.HandleFunc("/api/repos", apiReposHandler)
|
||||||
http.HandleFunc("/api/stat/repos/top", statOrgReposTop)
|
|
||||||
http.HandleFunc("/api/stat/repos/activity", statOrgReposActivity)
|
http.HandleFunc("/api/stat/orgs/top", statOrgTopHandler)
|
||||||
http.HandleFunc("/api/stat/teams/top", statOrgTeamsTop)
|
http.HandleFunc("/api/stat/orgs/activity", statOrgActivityHandler)
|
||||||
http.HandleFunc("/api/stat/teams/activity", statOrgTeamsActivity)
|
http.HandleFunc("/api/stat/teams/top", statTeamTopHandler)
|
||||||
http.HandleFunc("/api/stat/users/top", statOrgUsersTop)
|
http.HandleFunc("/api/stat/teams/activity", statTeamActivityHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start() {
|
func Start() {
|
||||||
|
|
|
@ -3,35 +3,30 @@ package server
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/fatih/structs"
|
||||||
"github.com/localhots/empact/db"
|
"github.com/localhots/empact/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
func statOrgReposTop(w http.ResponseWriter, r *http.Request) {
|
func statOrgTopHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
req, stat := parseRequest(w, r)
|
req, stat := parseRequest(w, r)
|
||||||
top := db.StatOrgReposTop(stat.org, stat.from, stat.to)
|
top := db.StatOrgTop(structs.Map(stat))
|
||||||
req.respondWith(top)
|
req.respondWith(top)
|
||||||
}
|
}
|
||||||
|
|
||||||
func statOrgReposActivity(w http.ResponseWriter, r *http.Request) {
|
func statOrgActivityHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
req, stat := parseRequest(w, r)
|
req, stat := parseRequest(w, r)
|
||||||
activity := db.StatOrgReposActivity(stat.org, stat.from, stat.to)
|
activity := db.StatOrgActivity(structs.Map(stat))
|
||||||
req.respondWith(activity)
|
req.respondWith(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func statOrgTeamsTop(w http.ResponseWriter, r *http.Request) {
|
func statTeamTopHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
req, stat := parseRequest(w, r)
|
req, stat := parseRequest(w, r)
|
||||||
top := db.StatOrgTeamsTop(stat.org, stat.from, stat.to)
|
top := db.StatTeamTop(structs.Map(stat))
|
||||||
req.respondWith(top)
|
req.respondWith(top)
|
||||||
}
|
}
|
||||||
|
|
||||||
func statOrgTeamsActivity(w http.ResponseWriter, r *http.Request) {
|
func statTeamActivityHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
req, stat := parseRequest(w, r)
|
req, stat := parseRequest(w, r)
|
||||||
activity := db.StatOrgTeamsActivity(stat.org, stat.from, stat.to)
|
activity := db.StatTeamActivity(structs.Map(stat))
|
||||||
req.respondWith(activity)
|
req.respondWith(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func statOrgUsersTop(w http.ResponseWriter, r *http.Request) {
|
|
||||||
req, stat := parseRequest(w, r)
|
|
||||||
top := db.StatOrgUsersTop(stat.org, stat.from, stat.to)
|
|
||||||
req.respondWith(top)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue