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  }