blob: ea9fe0ddf03a0ee141845f8a3d8b278073df896f [file] [log] [blame]
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package dashboard
// This file handles the front page.
import (
"bytes"
"html/template"
"io"
"net/http"
"strings"
"sync"
"time"
"appengine"
"appengine/datastore"
"appengine/user"
)
func init() {
http.HandleFunc("/", handleFront)
http.HandleFunc("/favicon.ico", http.NotFound)
}
// maximum number of active CLs to show in person-specific tables.
const maxCLs = 100
func handleFront(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
data := &frontPageData{
Reviewers: personList,
User: user.Current(c).Email,
IsAdmin: user.IsAdmin(c),
}
var currentPerson string
u := data.User
you := "you"
if e := r.FormValue("user"); e != "" {
u = e
you = e
}
currentPerson, data.UserIsReviewer = emailToPerson[u]
if !data.UserIsReviewer {
currentPerson = u
}
var wg sync.WaitGroup
errc := make(chan error, 10)
activeCLs := datastore.NewQuery("CL").
Filter("Closed =", false).
Order("-Modified")
tableFetch := func(index int, f func(tbl *clTable) error) {
wg.Add(1)
go func() {
defer wg.Done()
start := time.Now()
if err := f(&data.Tables[index]); err != nil {
errc <- err
}
data.Timing[index] = time.Now().Sub(start)
}()
}
data.Tables[0].Title = "CLs assigned to " + you + " for review"
if data.UserIsReviewer {
tableFetch(0, func(tbl *clTable) error {
q := activeCLs.Filter("Reviewer =", currentPerson).Limit(maxCLs)
tbl.Assignable = true
_, err := q.GetAll(c, &tbl.CLs)
return err
})
}
tableFetch(1, func(tbl *clTable) error {
q := activeCLs
if data.UserIsReviewer {
q = q.Filter("Author =", currentPerson)
} else {
q = q.Filter("Owner =", currentPerson)
}
q = q.Limit(maxCLs)
tbl.Title = "CLs sent by " + you
tbl.Assignable = true
_, err := q.GetAll(c, &tbl.CLs)
return err
})
tableFetch(2, func(tbl *clTable) error {
q := activeCLs.Limit(50)
tbl.Title = "Other active CLs"
tbl.Assignable = true
if _, err := q.GetAll(c, &tbl.CLs); err != nil {
return err
}
// filter
for i := len(tbl.CLs) - 1; i >= 0; i-- {
cl := tbl.CLs[i]
if cl.Owner == currentPerson || cl.Author == currentPerson || cl.Reviewer == currentPerson {
// Preserve order.
copy(tbl.CLs[i:], tbl.CLs[i+1:])
tbl.CLs = tbl.CLs[:len(tbl.CLs)-1]
}
}
return nil
})
tableFetch(3, func(tbl *clTable) error {
q := datastore.NewQuery("CL").
Filter("Closed =", true).
Order("-Modified").
Limit(10)
tbl.Title = "Recently closed CLs"
tbl.Assignable = false
_, err := q.GetAll(c, &tbl.CLs)
return err
})
// Not really a table fetch.
tableFetch(0, func(_ *clTable) error {
var err error
data.LogoutURL, err = user.LogoutURL(c, "/")
return err
})
wg.Wait()
select {
case err := <-errc:
c.Errorf("%v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
default:
}
var b bytes.Buffer
if err := frontPage.ExecuteTemplate(&b, "front", &data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
io.Copy(w, &b)
}
type frontPageData struct {
Tables [4]clTable
Timing [4]time.Duration
Reviewers []string
UserIsReviewer bool
User, LogoutURL string // actual logged in user
IsAdmin bool
}
type clTable struct {
Title string
Assignable bool
CLs []*CL
}
var frontPage = template.Must(template.New("front").Funcs(template.FuncMap{
"selected": func(a, b string) string {
if a == b {
return "selected"
}
return ""
},
"shortemail": func(s string) string {
if i := strings.Index(s, "@"); i >= 0 {
s = s[:i]
}
return s
},
}).Parse(`
<!doctype html>
<html>
<head>
<title>Go code reviews</title>
<link rel="icon" type="image/png" href="/static/icon.png" />
<style type="text/css">
body {
font-family: Helvetica, sans-serif;
}
img#gopherstamp {
float: right;
height: auto;
width: 250px;
}
h1, h2, h3 {
color: #777;
margin-bottom: 0;
}
table {
border-spacing: 0;
}
td {
vertical-align: top;
padding: 2px 5px;
}
tr.unreplied td.email {
border-left: 2px solid blue;
}
tr.pending td {
background: #fc8;
}
tr.failed td {
background: #f88;
}
tr.saved td {
background: #8f8;
}
.cls {
margin-top: 0;
}
a {
color: blue;
text-decoration: none; /* no link underline */
}
address {
font-size: 10px;
text-align: right;
}
.email {
font-family: monospace;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<img id="gopherstamp" src="/static/gopherstamp.jpg" />
<h1>Go code reviews</h1>
<table class="cls">
{{range $i, $tbl := .Tables}}
<tr><td colspan="5"><h3>{{$tbl.Title}}</h3></td></tr>
{{if .CLs}}
{{range $cl := .CLs}}
<tr id="cl-{{$cl.Number}}" class="{{if not $i}}{{if not .Reviewed}}unreplied{{end}}{{end}}">
<td class="email">{{$cl.DisplayOwner}}</td>
<td>
{{if $tbl.Assignable}}
<select id="cl-rev-{{$cl.Number}}" {{if not $.UserIsReviewer}}disabled{{end}}>
<option></option>
{{range $.Reviewers}}
<option {{selected . $cl.Reviewer}}>{{.}}</option>
{{end}}
</select>
<script type="text/javascript">
$(function() {
$('#cl-rev-{{$cl.Number}}').change(function() {
var r = $(this).val();
var row = $('tr#cl-{{$cl.Number}}');
row.addClass('pending');
$.post('/assign', {
'cl': '{{$cl.Number}}',
'r': r
}).success(function() {
row.removeClass('pending');
row.addClass('saved');
}).error(function() {
row.removeClass('pending');
row.addClass('failed');
});
});
});
</script>
{{end}}
</td>
<td>
<a href="http://codereview.appspot.com/{{.Number}}/" title="{{ printf "%s" .Description}}">{{.Number}}: {{.FirstLineHTML}}</a>
{{if and .LGTMs $tbl.Assignable}}<br /><span style="font-size: smaller;">LGTMs: {{.LGTMHTML}}</span>{{end}}
{{if and .NotLGTMs $tbl.Assignable}}<br /><span style="font-size: smaller; color: #f74545;">NOT LGTMs: {{.NotLGTMHTML}}</span>{{end}}
{{if .LastUpdateBy}}<br /><span style="font-size: smaller; color: #777777;">(<span title="{{.LastUpdateBy}}">{{.LastUpdateBy | shortemail}}</span>) {{.LastUpdate}}</span>{{end}}
</td>
<td title="Last modified">{{.ModifiedAgo}}</td>
<td>{{if $.IsAdmin}}<a href="/update-cl?cl={{.Number}}" title="Update this CL">&#x27f3;</a>{{end}}</td>
</tr>
{{end}}
{{else}}
<tr><td colspan="5"><em>none</em></td></tr>
{{end}}
{{end}}
</table>
<hr />
<address>
You are <span class="email">{{.User}}</span> &middot; <a href="{{.LogoutURL}}">logout</a><br />
datastore timing: {{range .Timing}} {{.}}{{end}}
</address>
</body>
</html>
`))