github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/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  // +build appengine
     9  
    10  package build
    11  
    12  import (
    13  	"bytes"
    14  	"errors"
    15  	"fmt"
    16  	"html/template"
    17  	"net/http"
    18  	"regexp"
    19  	"sort"
    20  	"strconv"
    21  	"strings"
    22  
    23  	"cache"
    24  
    25  	"appengine"
    26  	"appengine/datastore"
    27  )
    28  
    29  func init() {
    30  	handleFunc("/", uiHandler)
    31  }
    32  
    33  // uiHandler draws the build status page.
    34  func uiHandler(w http.ResponseWriter, r *http.Request) {
    35  	d := dashboardForRequest(r)
    36  	c := d.Context(appengine.NewContext(r))
    37  	now := cache.Now(c)
    38  	key := "build-ui"
    39  
    40  	page, _ := strconv.Atoi(r.FormValue("page"))
    41  	if page < 0 {
    42  		page = 0
    43  	}
    44  	key += fmt.Sprintf("-page%v", page)
    45  
    46  	branch := r.FormValue("branch")
    47  	if branch != "" {
    48  		key += "-branch-" + branch
    49  	}
    50  
    51  	repo := r.FormValue("repo")
    52  	if repo != "" {
    53  		key += "-repo-" + repo
    54  	}
    55  
    56  	var b []byte
    57  	if cache.Get(r, now, key, &b) {
    58  		w.Write(b)
    59  		return
    60  	}
    61  
    62  	pkg := &Package{} // empty package is the main repository
    63  	if repo != "" {
    64  		var err error
    65  		pkg, err = GetPackage(c, repo)
    66  		if err != nil {
    67  			logErr(w, r, err)
    68  			return
    69  		}
    70  	}
    71  	commits, err := dashCommits(c, pkg, page, branch)
    72  	if err != nil {
    73  		logErr(w, r, err)
    74  		return
    75  	}
    76  	builders := commitBuilders(commits)
    77  
    78  	var tipState *TagState
    79  	if pkg.Kind == "" && page == 0 && (branch == "" || branch == "default") {
    80  		// only show sub-repo state on first page of normal repo view
    81  		tipState, err = TagStateByName(c, "tip")
    82  		if err != nil {
    83  			logErr(w, r, err)
    84  			return
    85  		}
    86  	}
    87  
    88  	p := &Pagination{}
    89  	if len(commits) == commitsPerPage {
    90  		p.Next = page + 1
    91  	}
    92  	if page > 0 {
    93  		p.Prev = page - 1
    94  		p.HasPrev = true
    95  	}
    96  	data := &uiTemplateData{d, pkg, commits, builders, tipState, p, branch}
    97  
    98  	var buf bytes.Buffer
    99  	if err := uiTemplate.Execute(&buf, data); err != nil {
   100  		logErr(w, r, err)
   101  		return
   102  	}
   103  
   104  	cache.Set(r, now, key, buf.Bytes())
   105  
   106  	buf.WriteTo(w)
   107  }
   108  
   109  type Pagination struct {
   110  	Next, Prev int
   111  	HasPrev    bool
   112  }
   113  
   114  // dashCommits gets a slice of the latest Commits to the current dashboard.
   115  // If page > 0 it paginates by commitsPerPage.
   116  func dashCommits(c appengine.Context, pkg *Package, page int, branch string) ([]*Commit, error) {
   117  	offset := page * commitsPerPage
   118  	q := datastore.NewQuery("Commit").
   119  		Ancestor(pkg.Key(c)).
   120  		Order("-Num")
   121  
   122  	var commits []*Commit
   123  	if branch == "" {
   124  		_, err := q.Limit(commitsPerPage).Offset(offset).
   125  			GetAll(c, &commits)
   126  		return commits, err
   127  	}
   128  
   129  	// Look for commits on a specific branch.
   130  	for t, n := q.Run(c), 0; len(commits) < commitsPerPage && n < 1000; {
   131  		var c Commit
   132  		_, err := t.Next(&c)
   133  		if err == datastore.Done {
   134  			break
   135  		}
   136  		if err != nil {
   137  			return nil, err
   138  		}
   139  		if !isBranchCommit(&c, branch) {
   140  			continue
   141  		}
   142  		if n >= offset {
   143  			commits = append(commits, &c)
   144  		}
   145  		n++
   146  	}
   147  	return commits, nil
   148  }
   149  
   150  // isBranchCommit reports whether the given commit is on the specified branch.
   151  // It does so by examining the commit description, so there will be some bad
   152  // matches where the branch commits do not begin with the "[branch]" prefix.
   153  func isBranchCommit(c *Commit, b string) bool {
   154  	d := strings.TrimSpace(c.Desc)
   155  	if b == "default" {
   156  		return !strings.HasPrefix(d, "[")
   157  	}
   158  	return strings.HasPrefix(d, "["+b+"]")
   159  }
   160  
   161  // commitBuilders returns the names of the builders that provided
   162  // Results for the provided commits.
   163  func commitBuilders(commits []*Commit) []string {
   164  	builders := make(map[string]bool)
   165  	for _, commit := range commits {
   166  		for _, r := range commit.Results() {
   167  			builders[r.Builder] = true
   168  		}
   169  	}
   170  	k := keys(builders)
   171  	sort.Sort(builderOrder(k))
   172  	return k
   173  }
   174  
   175  func keys(m map[string]bool) (s []string) {
   176  	for k := range m {
   177  		s = append(s, k)
   178  	}
   179  	sort.Strings(s)
   180  	return
   181  }
   182  
   183  // builderOrder implements sort.Interface, sorting builder names
   184  // ("darwin-amd64", etc) first by builderPriority and then alphabetically.
   185  type builderOrder []string
   186  
   187  func (s builderOrder) Len() int      { return len(s) }
   188  func (s builderOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   189  func (s builderOrder) Less(i, j int) bool {
   190  	pi, pj := builderPriority(s[i]), builderPriority(s[j])
   191  	if pi == pj {
   192  		return s[i] < s[j]
   193  	}
   194  	return pi < pj
   195  }
   196  
   197  func builderPriority(builder string) (p int) {
   198  	// Put -temp builders at the end, always.
   199  	if strings.HasSuffix(builder, "-temp") {
   200  		defer func() { p += 20 }()
   201  	}
   202  	// Group race builders together.
   203  	if isRace(builder) {
   204  		return 1
   205  	}
   206  	// If the OS has a specified priority, use it.
   207  	if p, ok := osPriority[builderOS(builder)]; ok {
   208  		return p
   209  	}
   210  	// The rest.
   211  	return 10
   212  }
   213  
   214  func isRace(s string) bool {
   215  	return strings.Contains(s, "-race-") || strings.HasSuffix(s, "-race")
   216  }
   217  
   218  func unsupported(builder string) bool {
   219  	if strings.HasSuffix(builder, "-temp") {
   220  		return true
   221  	}
   222  	return unsupportedOS(builderOS(builder))
   223  }
   224  
   225  func unsupportedOS(os string) bool {
   226  	if os == "race" {
   227  		return false
   228  	}
   229  	p, ok := osPriority[os]
   230  	return !ok || p > 0
   231  }
   232  
   233  // Priorities for specific operating systems.
   234  var osPriority = map[string]int{
   235  	"darwin":  0,
   236  	"freebsd": 0,
   237  	"linux":   0,
   238  	"windows": 0,
   239  	// race == 1
   240  	"openbsd":   2,
   241  	"netbsd":    3,
   242  	"dragonfly": 4,
   243  }
   244  
   245  // TagState represents the state of all Packages at a Tag.
   246  type TagState struct {
   247  	Tag      *Commit
   248  	Packages []*PackageState
   249  }
   250  
   251  // PackageState represents the state of a Package at a Tag.
   252  type PackageState struct {
   253  	Package *Package
   254  	Commit  *Commit
   255  }
   256  
   257  // TagStateByName fetches the results for all Go subrepos at the specified Tag.
   258  func TagStateByName(c appengine.Context, name string) (*TagState, error) {
   259  	tag, err := GetTag(c, name)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	pkgs, err := Packages(c, "subrepo")
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	var st TagState
   268  	for _, pkg := range pkgs {
   269  		com, err := pkg.LastCommit(c)
   270  		if err != nil {
   271  			c.Warningf("%v: no Commit found: %v", pkg, err)
   272  			continue
   273  		}
   274  		st.Packages = append(st.Packages, &PackageState{pkg, com})
   275  	}
   276  	st.Tag, err = tag.Commit(c)
   277  	if err != nil {
   278  		return nil, err
   279  	}
   280  	return &st, nil
   281  }
   282  
   283  type uiTemplateData struct {
   284  	Dashboard  *Dashboard
   285  	Package    *Package
   286  	Commits    []*Commit
   287  	Builders   []string
   288  	TipState   *TagState
   289  	Pagination *Pagination
   290  	Branch     string
   291  }
   292  
   293  var uiTemplate = template.Must(
   294  	template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"),
   295  )
   296  
   297  var tmplFuncs = template.FuncMap{
   298  	"buildDashboards":   buildDashboards,
   299  	"builderOS":         builderOS,
   300  	"builderSpans":      builderSpans,
   301  	"builderSubheading": builderSubheading,
   302  	"builderTitle":      builderTitle,
   303  	"repoURL":           repoURL,
   304  	"shortDesc":         shortDesc,
   305  	"shortHash":         shortHash,
   306  	"shortUser":         shortUser,
   307  	"tail":              tail,
   308  	"unsupported":       unsupported,
   309  }
   310  
   311  func splitDash(s string) (string, string) {
   312  	i := strings.Index(s, "-")
   313  	if i >= 0 {
   314  		return s[:i], s[i+1:]
   315  	}
   316  	return s, ""
   317  }
   318  
   319  // builderOS returns the os tag for a builder string
   320  func builderOS(s string) string {
   321  	os, _ := splitDash(s)
   322  	return os
   323  }
   324  
   325  // builderOSOrRace returns the builder OS or, if it is a race builder, "race".
   326  func builderOSOrRace(s string) string {
   327  	if isRace(s) {
   328  		return "race"
   329  	}
   330  	return builderOS(s)
   331  }
   332  
   333  // builderArch returns the arch tag for a builder string
   334  func builderArch(s string) string {
   335  	_, arch := splitDash(s)
   336  	arch, _ = splitDash(arch) // chop third part
   337  	return arch
   338  }
   339  
   340  // builderSubheading returns a short arch tag for a builder string
   341  // or, if it is a race builder, the builder OS.
   342  func builderSubheading(s string) string {
   343  	if isRace(s) {
   344  		return builderOS(s)
   345  	}
   346  	arch := builderArch(s)
   347  	switch arch {
   348  	case "amd64":
   349  		return "x64"
   350  	}
   351  	return arch
   352  }
   353  
   354  // builderArchChar returns the architecture letter for a builder string
   355  func builderArchChar(s string) string {
   356  	arch := builderArch(s)
   357  	switch arch {
   358  	case "386":
   359  		return "8"
   360  	case "amd64":
   361  		return "6"
   362  	case "arm":
   363  		return "5"
   364  	}
   365  	return arch
   366  }
   367  
   368  type builderSpan struct {
   369  	N           int
   370  	OS          string
   371  	Unsupported bool
   372  }
   373  
   374  // builderSpans creates a list of tags showing
   375  // the builder's operating system names, spanning
   376  // the appropriate number of columns.
   377  func builderSpans(s []string) []builderSpan {
   378  	var sp []builderSpan
   379  	for len(s) > 0 {
   380  		i := 1
   381  		os := builderOSOrRace(s[0])
   382  		u := unsupportedOS(os) || strings.HasSuffix(s[0], "-temp")
   383  		for i < len(s) && builderOSOrRace(s[i]) == os {
   384  			i++
   385  		}
   386  		sp = append(sp, builderSpan{i, os, u})
   387  		s = s[i:]
   388  	}
   389  	return sp
   390  }
   391  
   392  // builderTitle formats "linux-amd64-foo" as "linux amd64 foo".
   393  func builderTitle(s string) string {
   394  	return strings.Replace(s, "-", " ", -1)
   395  }
   396  
   397  // buildDashboards returns the known public dashboards.
   398  func buildDashboards() []*Dashboard {
   399  	return dashboards
   400  }
   401  
   402  // shortDesc returns the first line of a description.
   403  func shortDesc(desc string) string {
   404  	if i := strings.Index(desc, "\n"); i != -1 {
   405  		desc = desc[:i]
   406  	}
   407  	return limitStringLength(desc, 100)
   408  }
   409  
   410  // shortHash returns a short version of a hash.
   411  func shortHash(hash string) string {
   412  	if len(hash) > 12 {
   413  		hash = hash[:12]
   414  	}
   415  	return hash
   416  }
   417  
   418  // shortUser returns a shortened version of a user string.
   419  func shortUser(user string) string {
   420  	if i, j := strings.Index(user, "<"), strings.Index(user, ">"); 0 <= i && i < j {
   421  		user = user[i+1 : j]
   422  	}
   423  	if i := strings.Index(user, "@"); i >= 0 {
   424  		return user[:i]
   425  	}
   426  	return user
   427  }
   428  
   429  // repoRe matches Google Code repositories and subrepositories (without paths).
   430  var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+)(\.[a-z0-9\-]+)?$`)
   431  
   432  // repoURL returns the URL of a change at a Google Code repository or subrepo.
   433  func repoURL(dashboard, hash, packagePath string) (string, error) {
   434  	if packagePath == "" {
   435  		if dashboard == "Gccgo" {
   436  			return "https://code.google.com/p/gofrontend/source/detail?r=" + hash, nil
   437  		}
   438  		if dashboard == "Mercurial" {
   439  			return "https://golang.org/change/" + hash, nil
   440  		}
   441  		// TODO(adg): use the above once /change/ points to git hashes
   442  		return "https://go.googlesource.com/go/+/" + hash, nil
   443  	}
   444  
   445  	// TODO(adg): remove this old hg stuff, one day.
   446  	if dashboard == "Mercurial" {
   447  		m := repoRe.FindStringSubmatch(packagePath)
   448  		if m == nil {
   449  			return "", errors.New("unrecognized package: " + packagePath)
   450  		}
   451  		url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash
   452  		if len(m) > 2 {
   453  			url += "&repo=" + m[2][1:]
   454  		}
   455  		return url, nil
   456  	}
   457  
   458  	repo := strings.TrimPrefix(packagePath, "golang.org/x/")
   459  	return "https://go.googlesource.com/" + repo + "/+/" + hash, nil
   460  }
   461  
   462  // tail returns the trailing n lines of s.
   463  func tail(n int, s string) string {
   464  	lines := strings.Split(s, "\n")
   465  	if len(lines) < n {
   466  		return s
   467  	}
   468  	return strings.Join(lines[len(lines)-n:], "\n")
   469  }