dashboard/app: support for showing links to build-in-progress status
Update golang/go#9494 Change-Id: I849d9f8ed423d29daede167193704dbda26785b1 Reviewed-on: https://go-review.googlesource.com/2590 Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
parent
968c2a1d60
commit
5f779d76c1
|
@ -13,7 +13,7 @@ handlers:
|
||||||
static_dir: static
|
static_dir: static
|
||||||
- url: /(|gccgo/|hg/)log/.+
|
- url: /(|gccgo/|hg/)log/.+
|
||||||
script: _go_app
|
script: _go_app
|
||||||
- url: /(|gccgo/|hg/)(|clear-results|commit|packages|result|perf-result|tag|todo|perf|perfdetail|perfgraph|updatebenchmark)
|
- url: /(|gccgo/|hg/)(|building|clear-results|commit|packages|result|perf-result|tag|todo|perf|perfdetail|perfgraph|updatebenchmark)
|
||||||
script: _go_app
|
script: _go_app
|
||||||
- url: /(|gccgo/|hg/)(init|buildtest|key|perflearn|_ah/queue/go/delay)
|
- url: /(|gccgo/|hg/)(init|buildtest|key|perflearn|_ah/queue/go/delay)
|
||||||
script: _go_app
|
script: _go_app
|
||||||
|
|
|
@ -78,6 +78,10 @@ func GetPackage(c appengine.Context, path string) (*Package, error) {
|
||||||
return p, err
|
return p, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type builderAndGoHash struct {
|
||||||
|
builder, goHash string
|
||||||
|
}
|
||||||
|
|
||||||
// A Commit describes an individual commit in a package.
|
// A Commit describes an individual commit in a package.
|
||||||
//
|
//
|
||||||
// Each Commit entity is a descendant of its associated Package entity.
|
// Each Commit entity is a descendant of its associated Package entity.
|
||||||
|
@ -107,6 +111,8 @@ type Commit struct {
|
||||||
PerfResults []string `datastore:",noindex"`
|
PerfResults []string `datastore:",noindex"`
|
||||||
|
|
||||||
FailNotificationSent bool
|
FailNotificationSent bool
|
||||||
|
|
||||||
|
buildingURLs map[builderAndGoHash]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (com *Commit) Key(c appengine.Context) *datastore.Key {
|
func (com *Commit) Key(c appengine.Context) *datastore.Key {
|
||||||
|
@ -216,12 +222,24 @@ func min(a, b int) int {
|
||||||
// Result returns the build Result for this Commit for the given builder/goHash.
|
// Result returns the build Result for this Commit for the given builder/goHash.
|
||||||
func (c *Commit) Result(builder, goHash string) *Result {
|
func (c *Commit) Result(builder, goHash string) *Result {
|
||||||
for _, r := range c.ResultData {
|
for _, r := range c.ResultData {
|
||||||
|
if !strings.HasPrefix(r, builder) {
|
||||||
|
// Avoid strings.SplitN alloc in the common case.
|
||||||
|
continue
|
||||||
|
}
|
||||||
p := strings.SplitN(r, "|", 4)
|
p := strings.SplitN(r, "|", 4)
|
||||||
if len(p) != 4 || p[0] != builder || p[3] != goHash {
|
if len(p) != 4 || p[0] != builder || p[3] != goHash {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return partsToResult(c, p)
|
return partsToResult(c, p)
|
||||||
}
|
}
|
||||||
|
if u, ok := c.buildingURLs[builderAndGoHash{builder, goHash}]; ok {
|
||||||
|
return &Result{
|
||||||
|
Builder: builder,
|
||||||
|
BuildingURL: u,
|
||||||
|
Hash: c.Hash,
|
||||||
|
GoHash: goHash,
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -409,9 +427,10 @@ type Result struct {
|
||||||
// The Go Commit this was built against (empty for Go commits).
|
// The Go Commit this was built against (empty for Go commits).
|
||||||
GoHash string
|
GoHash string
|
||||||
|
|
||||||
OK bool
|
BuildingURL string `datastore:"-"` // non-empty if currently building
|
||||||
Log string `datastore:"-"` // for JSON unmarshaling only
|
OK bool
|
||||||
LogHash string `datastore:",noindex"` // Key to the Log record.
|
Log string `datastore:"-"` // for JSON unmarshaling only
|
||||||
|
LogHash string `datastore:",noindex"` // Key to the Log record.
|
||||||
|
|
||||||
RunTime int64 // time to build+test in nanoseconds
|
RunTime int64 // time to build+test in nanoseconds
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,12 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"appengine"
|
"appengine"
|
||||||
"appengine/datastore"
|
"appengine/datastore"
|
||||||
|
"appengine/memcache"
|
||||||
|
|
||||||
"cache"
|
"cache"
|
||||||
"key"
|
"key"
|
||||||
|
@ -516,6 +518,29 @@ func packagesHandler(r *http.Request) (interface{}, error) {
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// buildingHandler records that a build is in progress.
|
||||||
|
// The data is only stored in memcache and with a timeout. It's assumed
|
||||||
|
// that the build system will periodically refresh this if the build
|
||||||
|
// is slow.
|
||||||
|
func buildingHandler(r *http.Request) (interface{}, error) {
|
||||||
|
if r.Method != "POST" {
|
||||||
|
return nil, errBadMethod(r.Method)
|
||||||
|
}
|
||||||
|
c := contextForRequest(r)
|
||||||
|
key := fmt.Sprintf("building|%s|%s|%s", r.FormValue("hash"), r.FormValue("gohash"), r.FormValue("builder"))
|
||||||
|
err := memcache.Set(c, &memcache.Item{
|
||||||
|
Key: key,
|
||||||
|
Value: []byte(r.FormValue("url")),
|
||||||
|
Expiration: 15 * time.Minute,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
"key": key,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// resultHandler records a build result.
|
// resultHandler records a build result.
|
||||||
// It reads a JSON-encoded Result value from the request body,
|
// It reads a JSON-encoded Result value from the request body,
|
||||||
// creates a new Result entity, and updates the relevant Commit entity.
|
// creates a new Result entity, and updates the relevant Commit entity.
|
||||||
|
@ -909,6 +934,7 @@ func init() {
|
||||||
handleFunc("/key", keyHandler)
|
handleFunc("/key", keyHandler)
|
||||||
|
|
||||||
// authenticated handlers
|
// authenticated handlers
|
||||||
|
handleFunc("/building", AuthHandler(buildingHandler))
|
||||||
handleFunc("/clear-results", AuthHandler(clearResultsHandler))
|
handleFunc("/clear-results", AuthHandler(clearResultsHandler))
|
||||||
handleFunc("/commit", AuthHandler(commitHandler))
|
handleFunc("/commit", AuthHandler(commitHandler))
|
||||||
handleFunc("/packages", AuthHandler(packagesHandler))
|
handleFunc("/packages", AuthHandler(packagesHandler))
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
|
|
||||||
"appengine"
|
"appengine"
|
||||||
"appengine/datastore"
|
"appengine/datastore"
|
||||||
|
"appengine/memcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -97,6 +98,7 @@ func uiHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
p.HasPrev = true
|
p.HasPrev = true
|
||||||
}
|
}
|
||||||
data := &uiTemplateData{d, pkg, commits, builders, tipState, p, branch}
|
data := &uiTemplateData{d, pkg, commits, builders, tipState, p, branch}
|
||||||
|
data.populateBuildingURLs(c)
|
||||||
|
|
||||||
switch r.FormValue("mode") {
|
switch r.FormValue("mode") {
|
||||||
case "failures":
|
case "failures":
|
||||||
|
@ -376,6 +378,54 @@ type uiTemplateData struct {
|
||||||
Branch string
|
Branch string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// populateBuildingURLs populates each commit in Commits' buildingURLs map with the
|
||||||
|
// URLs of builds which are currently in progress.
|
||||||
|
func (td *uiTemplateData) populateBuildingURLs(ctx appengine.Context) {
|
||||||
|
// need are memcache keys: "building|<hash>|<gohash>|<builder>"
|
||||||
|
// The hash is of the main "go" repo, or the subrepo commit hash.
|
||||||
|
// The gohash is empty for the main repo, else it's the Go hash.
|
||||||
|
var need []string
|
||||||
|
|
||||||
|
commit := map[string]*Commit{} // commit hash -> Commit
|
||||||
|
|
||||||
|
// TODO(bradfitz): this only populates the main repo, not subpackages currently.
|
||||||
|
for _, b := range td.Builders {
|
||||||
|
for _, c := range td.Commits {
|
||||||
|
if c.Result(b, "") == nil {
|
||||||
|
commit[c.Hash] = c
|
||||||
|
need = append(need, "building|"+c.Hash+"||"+b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(need) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m, err := memcache.GetMulti(ctx, need)
|
||||||
|
if err != nil {
|
||||||
|
// oh well. this is a cute non-critical feature anyway.
|
||||||
|
ctx.Debugf("GetMulti of building keys: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, it := range m {
|
||||||
|
f := strings.SplitN(k, "|", 4)
|
||||||
|
if len(f) != 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
hash, goHash, builder := f[1], f[2], f[3]
|
||||||
|
c, ok := commit[hash]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m := c.buildingURLs
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[builderAndGoHash]string)
|
||||||
|
c.buildingURLs = m
|
||||||
|
}
|
||||||
|
m[builderAndGoHash{builder, goHash}] = string(it.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var uiTemplate = template.Must(
|
var uiTemplate = template.Must(
|
||||||
template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"),
|
template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"),
|
||||||
)
|
)
|
||||||
|
|
|
@ -96,7 +96,9 @@
|
||||||
{{range $.Builders}}
|
{{range $.Builders}}
|
||||||
<td class="result{{if (unsupported .)}} unsupported{{end}}">
|
<td class="result{{if (unsupported .)}} unsupported{{end}}">
|
||||||
{{with $c.Result . $h}}
|
{{with $c.Result . $h}}
|
||||||
{{if .OK}}
|
{{if .BuildingURL}}
|
||||||
|
<a href="{{.BuildingURL}}"><img src="https://golang.org/favicon.ico" border=0></a>
|
||||||
|
{{else if .OK}}
|
||||||
<span class="ok">ok</span>
|
<span class="ok">ok</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<a href="{{$.Dashboard.Prefix}}/log/{{.LogHash}}" class="fail">fail</a>
|
<a href="{{$.Dashboard.Prefix}}/log/{{.LogHash}}" class="fail">fail</a>
|
||||||
|
|
Loading…
Reference in New Issue