github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/misc/dashboard/app/build/ui.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // TODO(adg): packages at weekly/release 6 // TODO(adg): some means to register new packages 7 8 package build 9 10 import ( 11 "bytes" 12 "errors" 13 "html/template" 14 "net/http" 15 "regexp" 16 "sort" 17 "strconv" 18 "strings" 19 20 "appengine" 21 "appengine/datastore" 22 "cache" 23 ) 24 25 func init() { 26 http.HandleFunc("/", uiHandler) 27 } 28 29 // uiHandler draws the build status page. 30 func uiHandler(w http.ResponseWriter, r *http.Request) { 31 c := appengine.NewContext(r) 32 now := cache.Now(c) 33 const key = "build-ui" 34 35 page, _ := strconv.Atoi(r.FormValue("page")) 36 if page < 0 { 37 page = 0 38 } 39 40 // Used cached version of front page, if available. 41 if page == 0 { 42 var b []byte 43 if cache.Get(r, now, key, &b) { 44 w.Write(b) 45 return 46 } 47 } 48 49 commits, err := goCommits(c, page) 50 if err != nil { 51 logErr(w, r, err) 52 return 53 } 54 builders := commitBuilders(commits, "") 55 56 var tipState *TagState 57 if page == 0 { 58 // only show sub-repo state on first page 59 tipState, err = TagStateByName(c, "tip") 60 if err != nil { 61 logErr(w, r, err) 62 return 63 } 64 } 65 66 p := &Pagination{} 67 if len(commits) == commitsPerPage { 68 p.Next = page + 1 69 } 70 if page > 0 { 71 p.Prev = page - 1 72 p.HasPrev = true 73 } 74 data := &uiTemplateData{commits, builders, tipState, p} 75 76 var buf bytes.Buffer 77 if err := uiTemplate.Execute(&buf, data); err != nil { 78 logErr(w, r, err) 79 return 80 } 81 82 // Cache the front page. 83 if page == 0 { 84 cache.Set(r, now, key, buf.Bytes()) 85 } 86 87 buf.WriteTo(w) 88 } 89 90 type Pagination struct { 91 Next, Prev int 92 HasPrev bool 93 } 94 95 // goCommits gets a slice of the latest Commits to the Go repository. 96 // If page > 0 it paginates by commitsPerPage. 97 func goCommits(c appengine.Context, page int) ([]*Commit, error) { 98 q := datastore.NewQuery("Commit"). 99 Ancestor((&Package{}).Key(c)). 100 Order("-Num"). 101 Limit(commitsPerPage). 102 Offset(page * commitsPerPage) 103 var commits []*Commit 104 _, err := q.GetAll(c, &commits) 105 return commits, err 106 } 107 108 // commitBuilders returns the names of the builders that provided 109 // Results for the provided commits. 110 func commitBuilders(commits []*Commit, goHash string) []string { 111 builders := make(map[string]bool) 112 for _, commit := range commits { 113 for _, r := range commit.Results(goHash) { 114 builders[r.Builder] = true 115 } 116 } 117 return keys(builders) 118 } 119 120 func keys(m map[string]bool) (s []string) { 121 for k := range m { 122 s = append(s, k) 123 } 124 sort.Strings(s) 125 return 126 } 127 128 // TagState represents the state of all Packages at a Tag. 129 type TagState struct { 130 Tag *Commit 131 Packages []*PackageState 132 } 133 134 // PackageState represents the state of a Package at a Tag. 135 type PackageState struct { 136 Package *Package 137 Commit *Commit 138 } 139 140 // TagStateByName fetches the results for all Go subrepos at the specified Tag. 141 func TagStateByName(c appengine.Context, name string) (*TagState, error) { 142 tag, err := GetTag(c, name) 143 if err != nil { 144 return nil, err 145 } 146 pkgs, err := Packages(c, "subrepo") 147 if err != nil { 148 return nil, err 149 } 150 var st TagState 151 for _, pkg := range pkgs { 152 com, err := pkg.LastCommit(c) 153 if err != nil { 154 c.Warningf("%v: no Commit found: %v", pkg, err) 155 continue 156 } 157 st.Packages = append(st.Packages, &PackageState{pkg, com}) 158 } 159 st.Tag, err = tag.Commit(c) 160 if err != nil { 161 return nil, err 162 } 163 return &st, nil 164 } 165 166 type uiTemplateData struct { 167 Commits []*Commit 168 Builders []string 169 TipState *TagState 170 Pagination *Pagination 171 } 172 173 var uiTemplate = template.Must( 174 template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"), 175 ) 176 177 var tmplFuncs = template.FuncMap{ 178 "builderOS": builderOS, 179 "builderArch": builderArch, 180 "builderArchShort": builderArchShort, 181 "builderArchChar": builderArchChar, 182 "builderTitle": builderTitle, 183 "builderSpans": builderSpans, 184 "repoURL": repoURL, 185 "shortDesc": shortDesc, 186 "shortHash": shortHash, 187 "shortUser": shortUser, 188 "tail": tail, 189 } 190 191 func splitDash(s string) (string, string) { 192 i := strings.Index(s, "-") 193 if i >= 0 { 194 return s[:i], s[i+1:] 195 } 196 return s, "" 197 } 198 199 // builderOS returns the os tag for a builder string 200 func builderOS(s string) string { 201 os, _ := splitDash(s) 202 return os 203 } 204 205 // builderArch returns the arch tag for a builder string 206 func builderArch(s string) string { 207 _, arch := splitDash(s) 208 arch, _ = splitDash(arch) // chop third part 209 return arch 210 } 211 212 // builderArchShort returns a short arch tag for a builder string 213 func builderArchShort(s string) string { 214 if s == "linux-amd64-race" { 215 return "race" 216 } 217 arch := builderArch(s) 218 switch arch { 219 case "amd64": 220 return "x64" 221 } 222 return arch 223 } 224 225 // builderArchChar returns the architecture letter for a builder string 226 func builderArchChar(s string) string { 227 arch := builderArch(s) 228 switch arch { 229 case "386": 230 return "8" 231 case "amd64": 232 return "6" 233 case "arm": 234 return "5" 235 } 236 return arch 237 } 238 239 type builderSpan struct { 240 N int 241 OS string 242 } 243 244 // builderSpans creates a list of tags showing 245 // the builder's operating system names, spanning 246 // the appropriate number of columns. 247 func builderSpans(s []string) []builderSpan { 248 var sp []builderSpan 249 for len(s) > 0 { 250 i := 1 251 os := builderOS(s[0]) 252 for i < len(s) && builderOS(s[i]) == os { 253 i++ 254 } 255 sp = append(sp, builderSpan{i, os}) 256 s = s[i:] 257 } 258 return sp 259 } 260 261 // builderTitle formats "linux-amd64-foo" as "linux amd64 foo". 262 func builderTitle(s string) string { 263 return strings.Replace(s, "-", " ", -1) 264 } 265 266 // shortDesc returns the first line of a description. 267 func shortDesc(desc string) string { 268 if i := strings.Index(desc, "\n"); i != -1 { 269 desc = desc[:i] 270 } 271 return desc 272 } 273 274 // shortHash returns a short version of a hash. 275 func shortHash(hash string) string { 276 if len(hash) > 12 { 277 hash = hash[:12] 278 } 279 return hash 280 } 281 282 // shortUser returns a shortened version of a user string. 283 func shortUser(user string) string { 284 if i, j := strings.Index(user, "<"), strings.Index(user, ">"); 0 <= i && i < j { 285 user = user[i+1 : j] 286 } 287 if i := strings.Index(user, "@"); i >= 0 { 288 return user[:i] 289 } 290 return user 291 } 292 293 // repoRe matches Google Code repositories and subrepositories (without paths). 294 var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+)(\.[a-z0-9\-]+)?$`) 295 296 // repoURL returns the URL of a change at a Google Code repository or subrepo. 297 func repoURL(hash, packagePath string) (string, error) { 298 if packagePath == "" { 299 return "https://code.google.com/p/go/source/detail?r=" + hash, nil 300 } 301 m := repoRe.FindStringSubmatch(packagePath) 302 if m == nil { 303 return "", errors.New("unrecognized package: " + packagePath) 304 } 305 url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash 306 if len(m) > 2 { 307 url += "&repo=" + m[2][1:] 308 } 309 return url, nil 310 } 311 312 // tail returns the trailing n lines of s. 313 func tail(n int, s string) string { 314 lines := strings.Split(s, "\n") 315 if len(lines) < n { 316 return s 317 } 318 return strings.Join(lines[len(lines)-n:], "\n") 319 }