Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(286)

Side by Side Diff: milo/frontend/view_console.go

Issue 2991243002: Milo: Console improvements (Closed)
Patch Set: Review Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « milo/frontend/appengine/templates/pages/console.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2017 The LUCI Authors. 1 // Copyright 2017 The LUCI Authors.
2 // 2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); 3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License. 4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at 5 // You may obtain a copy of the License at
6 // 6 //
7 // http://www.apache.org/licenses/LICENSE-2.0 7 // http://www.apache.org/licenses/LICENSE-2.0
8 // 8 //
9 // Unless required by applicable law or agreed to in writing, software 9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, 10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and 12 // See the License for the specific language governing permissions and
13 // limitations under the License. 13 // limitations under the License.
14 14
15 package frontend 15 package frontend
16 16
17 import ( 17 import (
18 "bytes"
18 "encoding/hex" 19 "encoding/hex"
19 "fmt" 20 "fmt"
20 "html/template" 21 "html/template"
21 "net/http" 22 "net/http"
23 "strings"
22 24
23 "golang.org/x/net/context" 25 "golang.org/x/net/context"
24 26
25 "github.com/luci/luci-go/common/clock" 27 "github.com/luci/luci-go/common/clock"
26 "github.com/luci/luci-go/common/errors" 28 "github.com/luci/luci-go/common/errors"
27 "github.com/luci/luci-go/common/logging" 29 "github.com/luci/luci-go/common/logging"
28 "github.com/luci/luci-go/common/proto/google" 30 "github.com/luci/luci-go/common/proto/google"
29 "github.com/luci/luci-go/server/router" 31 "github.com/luci/luci-go/server/router"
30 "github.com/luci/luci-go/server/templates" 32 "github.com/luci/luci-go/server/templates"
31 33
(...skipping 10 matching lines...) Expand all
42 // that builder will be removed from list of results. 44 // that builder will be removed from list of results.
43 func getConsoleDef(c context.Context, project, name string) (*common.Console, er ror) { 45 func getConsoleDef(c context.Context, project, name string) (*common.Console, er ror) {
44 cs, err := common.GetConsole(c, project, name) 46 cs, err := common.GetConsole(c, project, name)
45 if err != nil { 47 if err != nil {
46 return nil, err 48 return nil, err
47 } 49 }
48 // TODO(hinoka): Remove builders that the user does not have access to. 50 // TODO(hinoka): Remove builders that the user does not have access to.
49 return cs, nil 51 return cs, nil
50 } 52 }
51 53
52 func console(c context.Context, project, name string) (*resp.Console, error) { 54 // shortname calculates a short name (3 char max long name) out of a full name
55 // by splitting on delimiters and taking the first letter of each "word".
56 // name is expected to be a builderID, which is module/<bucket or master>/builde rname
57 func shortname(name string) string {
58 » builderNameComp := strings.SplitN(name, "/", 3)
59 » if len(builderNameComp) == 3 {
60 » » name = builderNameComp[2]
61 » }
62 » tokens := strings.FieldsFunc(name, func(r rune) bool {
63 » » switch r {
64 » » case '_', '-', ' ':
65 » » » return true
66 » » }
67 » » return false
68 » })
69 » numLetters := len(tokens)
70 » if numLetters > 3 {
71 » » numLetters = 3
72 » }
73 » short := ""
74 » for i := 0; i < numLetters; i++ {
75 » » if len(tokens[i]) > 0 {
dnj 2017/08/03 01:02:59 Note that if you have five tokens, "1--2-3-4", thi
Ryan Tseng 2017/08/03 14:06:16 Actually, playing around with it for a bit, it see
76 » » » short += string(tokens[i][0])
77 » » }
78 » }
79 » return strings.ToLower(short)
80 }
81
82 func console(c context.Context, project, name string, limit int) (*resp.Console, error) {
53 tStart := clock.Now(c) 83 tStart := clock.Now(c)
54 def, err := getConsoleDef(c, project, name) 84 def, err := getConsoleDef(c, project, name)
55 if err != nil { 85 if err != nil {
56 return nil, err 86 return nil, err
57 } 87 }
58 » commitInfo, err := git.GetHistory(c, def.RepoURL, def.Ref, 25) 88 » commitInfo, err := git.GetHistory(c, def.RepoURL, def.Ref, limit)
59 if err != nil { 89 if err != nil {
60 return nil, err 90 return nil, err
61 } 91 }
62 tGitiles := clock.Now(c) 92 tGitiles := clock.Now(c)
63 logging.Debugf(c, "Loading commits took %s.", tGitiles.Sub(tStart)) 93 logging.Debugf(c, "Loading commits took %s.", tGitiles.Sub(tStart))
64 94
65 builderNames := make([]string, len(def.Builders))
66 builders := make([]resp.BuilderRef, len(def.Builders))
67 for i, b := range def.Builders {
68 builderNames[i] = b
69 builders[i].Name = b
70 _, _, builders[i].ShortName, _ = buildsource.BuilderID(b).Split( )
71 // TODO(hinoka): Add Categories back in.
72 }
73
74 commitNames := make([]string, len(commitInfo.Commits)) 95 commitNames := make([]string, len(commitInfo.Commits))
75 for i, commit := range commitInfo.Commits { 96 for i, commit := range commitInfo.Commits {
76 commitNames[i] = hex.EncodeToString(commit.Hash) 97 commitNames[i] = hex.EncodeToString(commit.Hash)
77 } 98 }
78 » rows, err := buildsource.GetConsoleRows(c, project, def, commitNames, bu ilderNames) 99
100 » rows, err := buildsource.GetConsoleRows(c, project, def, commitNames, de f.Builders)
79 tConsole := clock.Now(c) 101 tConsole := clock.Now(c)
80 logging.Debugf(c, "Loading the console took a total of %s.", tConsole.Su b(tGitiles)) 102 logging.Debugf(c, "Loading the console took a total of %s.", tConsole.Su b(tGitiles))
81 if err != nil { 103 if err != nil {
82 return nil, err 104 return nil, err
83 } 105 }
84 106
85 ccb := make([]resp.CommitBuild, len(commitInfo.Commits)) 107 ccb := make([]resp.CommitBuild, len(commitInfo.Commits))
108 builderRefs := make([]resp.BuilderRef, len(def.Builders))
86 for row, commit := range commitInfo.Commits { 109 for row, commit := range commitInfo.Commits {
87 » » ccb[row].Build = make([]*model.BuildSummary, len(builders)) 110 » » ccb[row].Build = make([]*model.BuildSummary, len(def.Builders))
88 ccb[row].Commit = resp.Commit{ 111 ccb[row].Commit = resp.Commit{
89 AuthorName: commit.AuthorName, 112 AuthorName: commit.AuthorName,
90 AuthorEmail: commit.AuthorEmail, 113 AuthorEmail: commit.AuthorEmail,
91 CommitTime: google.TimeFromProto(commit.CommitTime), 114 CommitTime: google.TimeFromProto(commit.CommitTime),
92 Repo: def.RepoURL, 115 Repo: def.RepoURL,
93 Branch: def.Ref, // TODO(hinoka): Actually this doe sn't match, change branch to ref. 116 Branch: def.Ref, // TODO(hinoka): Actually this doe sn't match, change branch to ref.
94 Description: commit.Msg, 117 Description: commit.Msg,
95 Revision: resp.NewLink(commitNames[row], def.RepoURL+ "/+/"+commitNames[row]), 118 Revision: resp.NewLink(commitNames[row], def.RepoURL+ "/+/"+commitNames[row]),
96 } 119 }
97 120
98 » » for col, b := range builders { 121 » » for col, b := range def.Builders {
99 » » » name := buildsource.BuilderID(b.Name) 122 » » » meta := def.BuilderMetas[col]
123 » » » short := meta.ShortName
124 » » » if short == "" {
125 » » » » short = shortname(b)
126 » » » }
127 » » » builderRefs[col] = resp.BuilderRef{
128 » » » » Name: b,
129 » » » » Category: strings.Split(meta.Category, "|"),
130 » » » » ShortName: short,
131 » » » }
132 » » » name := buildsource.BuilderID(b)
100 if summaries := rows[row].Builds[name]; len(summaries) > 0 { 133 if summaries := rows[row].Builds[name]; len(summaries) > 0 {
101 ccb[row].Build[col] = summaries[0] 134 ccb[row].Build[col] = summaries[0]
102 } 135 }
103 } 136 }
104 } 137 }
105 138
106 return &resp.Console{ 139 return &resp.Console{
107 Name: def.ID, 140 Name: def.ID,
108 Commit: ccb, 141 Commit: ccb,
109 » » BuilderRef: builders, 142 » » BuilderRef: builderRefs,
110 }, nil 143 }, nil
111 } 144 }
112 145
113 // consoleRenderer is a wrapper around Console to provide additional methods. 146 // consoleRenderer is a wrapper around Console to provide additional methods.
114 type consoleRenderer struct { 147 type consoleRenderer struct {
115 *resp.Console 148 *resp.Console
116 } 149 }
117 150
118 // Header generates the console header html. 151 // Header generates the console header html.
119 func (c consoleRenderer) Header() template.HTML { 152 func (c consoleRenderer) Header() template.HTML {
120 // First, split things into nice rows and find the max depth. 153 // First, split things into nice rows and find the max depth.
121 cat := make([][]string, len(c.BuilderRef)) 154 cat := make([][]string, len(c.BuilderRef))
122 depth := 0 155 depth := 0
123 for i, b := range c.BuilderRef { 156 for i, b := range c.BuilderRef {
124 cat[i] = b.Category 157 cat[i] = b.Category
125 if len(cat[i]) > depth { 158 if len(cat[i]) > depth {
126 depth = len(cat[i]) 159 depth = len(cat[i])
127 } 160 }
128 } 161 }
129 162
130 » result := "" 163 » var buf bytes.Buffer
164 » must := func(_ int, err error) {
165 » » if err != nil {
166 » » » panic(err)
167 » » }
168 » }
131 for row := 0; row < depth; row++ { 169 for row := 0; row < depth; row++ {
132 » » result += "<tr><th></th><th></th>" 170 » » must(buf.WriteString("<tr><th></th><th></th>"))
133 // "" is the first two nodes, " " is an empty node. 171 // "" is the first two nodes, " " is an empty node.
134 current := "" 172 current := ""
135 colspan := 0 173 colspan := 0
136 for _, br := range cat { 174 for _, br := range cat {
137 colspan++ 175 colspan++
138 var s string 176 var s string
139 if row >= len(br) { 177 if row >= len(br) {
140 s = " " 178 s = " "
141 } else { 179 } else {
142 s = br[row] 180 s = br[row]
143 } 181 }
144 if s != current || current == " " { 182 if s != current || current == " " {
145 if current != "" || current == " " { 183 if current != "" || current == " " {
146 » » » » » result += fmt.Sprintf(`<th colspan="%d"> %s</th>`, colspan, current) 184 » » » » » must(fmt.Fprintf(&buf, `<th colspan="%d" >%s</th>`, colspan, current))
147 colspan = 0 185 colspan = 0
148 } 186 }
149 current = s 187 current = s
150 } 188 }
151 } 189 }
152 if colspan != 0 { 190 if colspan != 0 {
153 » » » result += fmt.Sprintf(`<th colspan="%d">%s</th>`, colspa n, current) 191 » » » must(fmt.Fprintf(&buf, `<th colspan="%d">%s</th>`, colsp an, current))
154 } 192 }
155 » » result += "</tr>" 193 » » must(buf.WriteString("</tr>"))
156 } 194 }
157 195
158 // Last row: The actual builder shortnames. 196 // Last row: The actual builder shortnames.
159 » result += "<tr><th></th><th></th>" 197 » must(buf.WriteString("<tr><th></th><th></th>"))
160 for _, br := range c.BuilderRef { 198 for _, br := range c.BuilderRef {
161 » » result += fmt.Sprintf("<th>%s</th>", br.ShortName) 199 » » _, _, builderName, err := buildsource.BuilderID(br.Name).Split()
200 » » if err != nil {
201 » » » must(fmt.Fprintf(&buf, "<th><a title=\"%s\">ERR</a></th> ", err.Error()))
dnj 2017/08/03 01:02:59 Nit: should probably escape the error string in ca
Ryan Tseng 2017/08/03 14:06:16 Done.
202 » » } else {
203 » » » must(fmt.Fprintf(
204 » » » » &buf, "<th><a href=\"/%s\" title=\"%s\">%s</a></ th>",
205 » » » » br.Name, builderName, br.ShortName))
dnj 2017/08/03 01:02:59 (*probably* safe not to escape these, although a m
Ryan Tseng 2017/08/03 14:06:16 Better safe than sorry.
206 » » }
162 } 207 }
163 » result += "</tr>" 208 » must(buf.WriteString("</tr>"))
164 » return template.HTML(result) 209 » return template.HTML(buf.String())
165 } 210 }
166 211
167 func (c consoleRenderer) BuilderLink(bs *model.BuildSummary) (*resp.Link, error) { 212 func (c consoleRenderer) BuilderLink(bs *model.BuildSummary) (*resp.Link, error) {
168 _, _, builderName, err := buildsource.BuilderID(bs.BuilderID).Split() 213 _, _, builderName, err := buildsource.BuilderID(bs.BuilderID).Split()
169 if err != nil { 214 if err != nil {
170 return nil, err 215 return nil, err
171 } 216 }
172 return resp.NewLink(builderName, "/"+bs.BuilderID), nil 217 return resp.NewLink(builderName, "/"+bs.BuilderID), nil
173 } 218 }
174 219
175 // ConsoleHandler renders the console page. 220 // ConsoleHandler renders the console page.
176 func ConsoleHandler(c *router.Context) { 221 func ConsoleHandler(c *router.Context) {
177 project := c.Params.ByName("project") 222 project := c.Params.ByName("project")
178 if project == "" { 223 if project == "" {
179 ErrorHandler(c, errors.New("Missing Project", common.CodeParamet erError)) 224 ErrorHandler(c, errors.New("Missing Project", common.CodeParamet erError))
180 return 225 return
181 } 226 }
182 name := c.Params.ByName("name") 227 name := c.Params.ByName("name")
228 limit := 25
229 if tLimit := GetLimit(c.Request, -1); tLimit >= 0 {
230 limit = tLimit
231 }
232 if limit > 1000 {
233 limit = 1000
234 }
183 235
184 » result, err := console(c.Context, project, name) 236 » result, err := console(c.Context, project, name, limit)
185 if err != nil { 237 if err != nil {
186 ErrorHandler(c, err) 238 ErrorHandler(c, err)
187 return 239 return
188 } 240 }
189 241
190 templates.MustRender(c.Context, c.Writer, "pages/console.html", template s.Args{ 242 templates.MustRender(c.Context, c.Writer, "pages/console.html", template s.Args{
191 "Console": consoleRenderer{result}, 243 "Console": consoleRenderer{result},
192 }) 244 })
193 } 245 }
194 246
195 // ConsoleMainHandler is a redirect handler that redirects the user to the main 247 // ConsoleMainHandler is a redirect handler that redirects the user to the main
196 // console for a particular project. 248 // console for a particular project.
197 func ConsoleMainHandler(ctx *router.Context) { 249 func ConsoleMainHandler(ctx *router.Context) {
198 w, r, p := ctx.Writer, ctx.Request, ctx.Params 250 w, r, p := ctx.Writer, ctx.Request, ctx.Params
199 proj := p.ByName("project") 251 proj := p.ByName("project")
200 http.Redirect(w, r, fmt.Sprintf("/console/%s/main", proj), http.StatusMo vedPermanently) 252 http.Redirect(w, r, fmt.Sprintf("/console/%s/main", proj), http.StatusMo vedPermanently)
201 return 253 return
202 } 254 }
OLDNEW
« no previous file with comments | « milo/frontend/appengine/templates/pages/console.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698