app/appengine: fix the json view for subrepos

The UI's json view makes some assumptions about the main commit data
coming from the Go Gerrit repo, resulting in misreporting the commit
repo and missing result data. Update this logic to handle other repos.

Since jsonView was untested, a minimal test is added that validates its
handling of the go repo, and reproduces the bug reported in
golang/go#35515 for a subrepo.

Fixes golang/go#35515

Change-Id: I10f01b92bbdabe866e776217e02d64cb5c040389
Reviewed-on: https://go-review.googlesource.com/c/build/+/232897
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Alexander Rakoczy <alex@golang.org>
Reviewed-by: Carlos Amedee <carlos@golang.org>
diff --git a/app/appengine/ui.go b/app/appengine/ui.go
index d730d0c..0083a3c 100644
--- a/app/appengine/ui.go
+++ b/app/appengine/ui.go
@@ -331,6 +331,11 @@
 		}
 	}
 
+	gerritProject := "go"
+	if repo := repos.ByImportPath[tb.req.Repo]; repo != nil {
+		gerritProject = repo.GoGerritProject
+	}
+
 	data := &uiTemplateData{
 		Dashboard:  goDash,
 		Package:    goDash.packageWithPath(tb.req.Repo),
@@ -339,6 +344,7 @@
 		Pagination: &Pagination{},
 		Branches:   tb.res.Branches,
 		Branch:     tb.req.Branch,
+		Repo:       gerritProject,
 	}
 
 	builders := buildersOfCommits(commits)
@@ -458,6 +464,13 @@
 
 func (jsonView) ShowsActiveBuilds() bool { return false }
 func (jsonView) ServeDashboard(w http.ResponseWriter, r *http.Request, data *uiTemplateData) {
+	res := toBuildStatus(r.Host, data)
+	v, _ := json.MarshalIndent(res, "", "\t")
+	w.Header().Set("Content-Type", "text/json; charset=utf-8")
+	w.Write(v)
+}
+
+func toBuildStatus(host string, data *uiTemplateData) types.BuildStatus {
 	// cell returns one of "" (no data), "ok", or a failure URL.
 	cell := func(res *Result) string {
 		switch {
@@ -466,7 +479,7 @@
 		case res.OK:
 			return "ok"
 		}
-		return fmt.Sprintf("https://%v/log/%v", r.Host, res.LogHash)
+		return fmt.Sprintf("https://%v/log/%v", host, res.LogHash)
 	}
 
 	builders := data.allBuilders()
@@ -474,17 +487,23 @@
 	var res types.BuildStatus
 	res.Builders = builders
 
-	// First the commits from the main section (the "go" repo)
+	// First the commits from the main section (the requested repo)
 	for _, c := range data.Commits {
-		rev := types.BuildRevision{
-			Repo:    "go",
-			Results: make([]string, len(res.Builders)),
+		// The logic below works for both the go repo and other subrepos: if c is
+		// in the main go repo, ResultGoHashes returns a slice of length 1
+		// containing the empty string.
+		for _, h := range c.ResultGoHashes() {
+			rev := types.BuildRevision{
+				Repo:       data.Repo,
+				Results:    make([]string, len(res.Builders)),
+				GoRevision: h,
+			}
+			commitToBuildRevision(c, &rev)
+			for i, b := range res.Builders {
+				rev.Results[i] = cell(c.Result(b, h))
+			}
+			res.Revisions = append(res.Revisions, rev)
 		}
-		commitToBuildRevision(c, &rev)
-		for i, b := range res.Builders {
-			rev.Results[i] = cell(c.Result(b, ""))
-		}
-		res.Revisions = append(res.Revisions, rev)
 	}
 
 	// Then the one commit each for the subrepos for each of the tracked tags.
@@ -511,10 +530,7 @@
 			res.Revisions = append(res.Revisions, rev)
 		}
 	}
-
-	v, _ := json.MarshalIndent(res, "", "\t")
-	w.Header().Set("Content-Type", "text/json; charset=utf-8")
-	w.Write(v)
+	return res
 }
 
 // commitToBuildRevision fills in the fields of BuildRevision rev that
@@ -718,6 +734,7 @@
 	Pagination *Pagination
 	Branches   []string
 	Branch     string
+	Repo       string // the repo gerrit project name. "go" if unspecified in the request.
 }
 
 // getActiveBuilds returns the builds that coordinator is currently doing.
diff --git a/app/appengine/ui_test.go b/app/appengine/ui_test.go
index 70f18a9..16f946f 100644
--- a/app/appengine/ui_test.go
+++ b/app/appengine/ui_test.go
@@ -45,6 +45,7 @@
 			},
 			want: &uiTemplateData{
 				Dashboard:  goDash,
+				Repo:       "go",
 				Package:    &Package{Name: "Go", Path: ""},
 				Branches:   []string{"release.foo", "release.bar", "dev.blah"},
 				Builders:   []string{"linux-386", "linux-amd64"},
@@ -59,7 +60,7 @@
 			req:  &apipb.DashboardRequest{},
 			// Have only one commit load from the datastore:
 			testCommitData: map[string]*Commit{
-				"26957168c4c0cdcc7ca4f0b19d0eb19474d224ac": &Commit{
+				"26957168c4c0cdcc7ca4f0b19d0eb19474d224ac": {
 					PackagePath: "",
 					Hash:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
 					ResultData: []string{
@@ -96,6 +97,7 @@
 			},
 			want: &uiTemplateData{
 				Dashboard: goDash,
+				Repo:      "go",
 				Package:   &Package{Name: "Go", Path: ""},
 				Branches:  []string{"release.foo", "release.bar", "dev.blah"},
 				Builders:  []string{"linux-386", "linux-amd64", "openbsd-amd64"},
@@ -192,6 +194,7 @@
 			},
 			want: &uiTemplateData{
 				Dashboard:  goDash,
+				Repo:       "go",
 				Package:    &Package{Name: "Go", Path: ""},
 				Branches:   []string{"release.foo", "release.bar", "dev.blah"},
 				Builders:   []string{"linux-386", "linux-amd64"},
@@ -291,6 +294,7 @@
 			},
 			want: &uiTemplateData{
 				Dashboard:  goDash,
+				Repo:       "net",
 				Package:    &Package{Name: "net", Path: "golang.org/x/net"},
 				Branches:   []string{"master", "dev.blah"},
 				Builders:   []string{"linux-386", "linux-amd64"},
@@ -332,3 +336,206 @@
 		})
 	}
 }
+
+func TestToBuildStatus(t *testing.T) {
+	tests := []struct {
+		name string
+		data *uiTemplateData
+		want types.BuildStatus
+	}{
+		{
+			name: "go repo",
+			data: &uiTemplateData{
+				Dashboard:  goDash,
+				Repo:       "go",
+				Package:    &Package{Name: "Go", Path: ""},
+				Branches:   []string{"release.foo", "release.bar", "dev.blah"},
+				Builders:   []string{"linux-386", "linux-amd64"},
+				Pagination: &Pagination{},
+				Commits: []*CommitInfo{
+					{
+						Hash:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						User:   "Foo Bar <foo@example.com>",
+						Desc:   "runtime: fix all the bugs",
+						Time:   time.Unix(1257894001, 0).UTC(),
+						Branch: "master",
+					},
+				},
+				TagState: []*TagState{
+					{
+						Name:     "master",
+						Tag:      &CommitInfo{Hash: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac"},
+						Builders: []string{"linux-386", "linux-amd64"},
+						Packages: []*PackageState{
+							{
+								Package: &Package{Name: "net", Path: "golang.org/x/net"},
+								Commit: &CommitInfo{
+									Hash:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+									PackagePath: "golang.org/x/net",
+									User:        "Ee Yore <e@e.net>",
+									Desc:        "all: fix networking",
+									Time:        time.Unix(1257894001, 0).UTC(),
+									Branch:      "master",
+								},
+							},
+							{
+								Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
+								Commit: &CommitInfo{
+									Hash:        "dddddddddddddddddddddddddddddddddddddddd",
+									PackagePath: "golang.org/x/sys",
+									User:        "Sys Tem <sys@s.net>",
+									Desc:        "sys: support more systems",
+									Time:        time.Unix(1257894001, 0).UTC(),
+									Branch:      "master",
+								},
+							},
+						},
+					},
+					{
+						Name:     "release-branch.go1.99",
+						Tag:      &CommitInfo{Hash: "ffffffffffffffffffffffffffffffffffffffff"},
+						Builders: []string{"linux-386", "linux-amd64"},
+						Packages: []*PackageState{
+							{
+								Package: &Package{Name: "net", Path: "golang.org/x/net"},
+								Commit: &CommitInfo{
+									Hash:        "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+									PackagePath: "golang.org/x/net",
+									User:        "Ee Yore <e@e.net>",
+									Desc:        "all: fix networking",
+									Time:        time.Unix(1257894001, 0).UTC(),
+									Branch:      "master",
+								},
+							},
+							{
+								Package: &Package{Name: "sys", Path: "golang.org/x/sys"},
+								Commit: &CommitInfo{
+									Hash:        "dddddddddddddddddddddddddddddddddddddddd",
+									PackagePath: "golang.org/x/sys",
+									User:        "Sys Tem <sys@s.net>",
+									Desc:        "sys: support more systems",
+									Time:        time.Unix(1257894001, 0).UTC(),
+									Branch:      "master",
+								},
+							},
+						},
+					},
+				},
+			},
+			want: types.BuildStatus{
+				Builders: []string{"linux-386", "linux-amd64"},
+				Revisions: []types.BuildRevision{
+					{
+						Repo:     "go",
+						Revision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						Date:     "2009-11-10T23:00:01Z",
+						Branch:   "master",
+						Author:   "Foo Bar <foo@example.com>",
+						Desc:     "runtime: fix all the bugs",
+						Results:  []string{"", ""},
+					},
+					{
+						Repo:       "net",
+						Revision:   "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+						GoRevision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						Date:       "2009-11-10T23:00:01Z",
+						Branch:     "master",
+						GoBranch:   "master",
+						Author:     "Ee Yore <e@e.net>",
+						Desc:       "all: fix networking",
+						Results:    []string{"", ""},
+					},
+					{
+						Repo:       "sys",
+						Revision:   "dddddddddddddddddddddddddddddddddddddddd",
+						GoRevision: "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						Date:       "2009-11-10T23:00:01Z",
+						Branch:     "master",
+						GoBranch:   "master",
+						Author:     "Sys Tem <sys@s.net>",
+						Desc:       "sys: support more systems",
+						Results:    []string{"", ""},
+					},
+					{
+						Repo:       "net",
+						Revision:   "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+						GoRevision: "ffffffffffffffffffffffffffffffffffffffff",
+						Date:       "2009-11-10T23:00:01Z",
+						Branch:     "master",
+						GoBranch:   "release-branch.go1.99",
+						Author:     "Ee Yore <e@e.net>",
+						Desc:       "all: fix networking",
+						Results:    []string{"", ""},
+					},
+					{
+						Repo:       "sys",
+						Revision:   "dddddddddddddddddddddddddddddddddddddddd",
+						GoRevision: "ffffffffffffffffffffffffffffffffffffffff",
+						Date:       "2009-11-10T23:00:01Z",
+						Branch:     "master",
+						GoBranch:   "release-branch.go1.99",
+						Author:     "Sys Tem <sys@s.net>",
+						Desc:       "sys: support more systems",
+						Results:    []string{"", ""},
+					},
+				},
+			},
+		},
+		{
+			name: "other repo",
+			data: &uiTemplateData{
+				Dashboard: goDash,
+				Repo:      "tools",
+				Builders:  []string{"linux", "windows"},
+				Commits: []*CommitInfo{
+					{
+						PackagePath: "golang.org/x/tools",
+						Hash:        "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						User:        "Foo Bar <foo@example.com>",
+						Desc:        "tools: fix all the bugs",
+						Time:        time.Unix(1257894001, 0).UTC(),
+						Branch:      "master",
+						ResultData: []string{
+							"linux|false|123|aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+							"windows|false|456|bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+						},
+					},
+				},
+			},
+			want: types.BuildStatus{
+				Builders: []string{"linux", "windows"},
+				Revisions: []types.BuildRevision{
+					{
+						Repo:       "tools",
+						Revision:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						GoRevision: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+						Date:       "2009-11-10T23:00:01Z",
+						Branch:     "master",
+						Author:     "Foo Bar <foo@example.com>",
+						Desc:       "tools: fix all the bugs",
+						Results:    []string{"", "https://build.golang.org/log/456"},
+					},
+					{
+						Repo:       "tools",
+						Revision:   "26957168c4c0cdcc7ca4f0b19d0eb19474d224ac",
+						GoRevision: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+						Date:       "2009-11-10T23:00:01Z",
+						Branch:     "master",
+						Author:     "Foo Bar <foo@example.com>",
+						Desc:       "tools: fix all the bugs",
+						Results:    []string{"https://build.golang.org/log/123", ""},
+					},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := toBuildStatus("build.golang.org", tt.data)
+			if diff := cmp.Diff(tt.want, got); diff != "" {
+				t.Errorf("buildStatus(...) mismatch (-want +got):\n%s", diff)
+			}
+		})
+	}
+}