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

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

Issue 2991243002: Milo: Console improvements (Closed)
Patch Set: Review comments 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 » » short += string(tokens[i][0])
76 » }
77 » return strings.ToLower(short)
78 }
79
80 func console(c context.Context, project, name string, limit int) (*resp.Console, error) {
53 tStart := clock.Now(c) 81 tStart := clock.Now(c)
54 def, err := getConsoleDef(c, project, name) 82 def, err := getConsoleDef(c, project, name)
55 if err != nil { 83 if err != nil {
56 return nil, err 84 return nil, err
57 } 85 }
58 » commitInfo, err := git.GetHistory(c, def.RepoURL, def.Ref, 25) 86 » commitInfo, err := git.GetHistory(c, def.RepoURL, def.Ref, limit)
59 if err != nil { 87 if err != nil {
60 return nil, err 88 return nil, err
61 } 89 }
62 tGitiles := clock.Now(c) 90 tGitiles := clock.Now(c)
63 logging.Debugf(c, "Loading commits took %s.", tGitiles.Sub(tStart)) 91 logging.Debugf(c, "Loading commits took %s.", tGitiles.Sub(tStart))
64 92
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)) 93 commitNames := make([]string, len(commitInfo.Commits))
75 for i, commit := range commitInfo.Commits { 94 for i, commit := range commitInfo.Commits {
76 commitNames[i] = hex.EncodeToString(commit.Hash) 95 commitNames[i] = hex.EncodeToString(commit.Hash)
77 } 96 }
78 » rows, err := buildsource.GetConsoleRows(c, project, def, commitNames, bu ilderNames) 97
98 » rows, err := buildsource.GetConsoleRows(c, project, def, commitNames, de f.Builders)
79 tConsole := clock.Now(c) 99 tConsole := clock.Now(c)
80 logging.Debugf(c, "Loading the console took a total of %s.", tConsole.Su b(tGitiles)) 100 logging.Debugf(c, "Loading the console took a total of %s.", tConsole.Su b(tGitiles))
81 if err != nil { 101 if err != nil {
82 return nil, err 102 return nil, err
83 } 103 }
84 104
85 ccb := make([]resp.CommitBuild, len(commitInfo.Commits)) 105 ccb := make([]resp.CommitBuild, len(commitInfo.Commits))
106 builderRefs := make([]resp.BuilderRef, len(def.Builders))
86 for row, commit := range commitInfo.Commits { 107 for row, commit := range commitInfo.Commits {
87 » » ccb[row].Build = make([]*model.BuildSummary, len(builders)) 108 » » ccb[row].Build = make([]*model.BuildSummary, len(def.Builders))
88 ccb[row].Commit = resp.Commit{ 109 ccb[row].Commit = resp.Commit{
89 AuthorName: commit.AuthorName, 110 AuthorName: commit.AuthorName,
90 AuthorEmail: commit.AuthorEmail, 111 AuthorEmail: commit.AuthorEmail,
91 CommitTime: google.TimeFromProto(commit.CommitTime), 112 CommitTime: google.TimeFromProto(commit.CommitTime),
92 Repo: def.RepoURL, 113 Repo: def.RepoURL,
93 Branch: def.Ref, // TODO(hinoka): Actually this doe sn't match, change branch to ref. 114 Branch: def.Ref, // TODO(hinoka): Actually this doe sn't match, change branch to ref.
94 Description: commit.Msg, 115 Description: commit.Msg,
95 Revision: resp.NewLink(commitNames[row], def.RepoURL+ "/+/"+commitNames[row]), 116 Revision: resp.NewLink(commitNames[row], def.RepoURL+ "/+/"+commitNames[row]),
96 } 117 }
97 118
98 » » for col, b := range builders { 119 » » for col, b := range def.Builders {
99 » » » name := buildsource.BuilderID(b.Name) 120 » » » meta := def.BuilderMetas[col]
121 » » » short := meta.ShortName
122 » » » if short == "" {
123 » » » » short = shortname(b)
124 » » » }
125 » » » builderRefs[col] = resp.BuilderRef{
126 » » » » Name: b,
127 » » » » Category: strings.Split(meta.Category, "|"),
128 » » » » ShortName: short,
129 » » » }
130 » » » name := buildsource.BuilderID(b)
100 if summaries := rows[row].Builds[name]; len(summaries) > 0 { 131 if summaries := rows[row].Builds[name]; len(summaries) > 0 {
101 ccb[row].Build[col] = summaries[0] 132 ccb[row].Build[col] = summaries[0]
102 } 133 }
103 } 134 }
104 } 135 }
105 136
106 return &resp.Console{ 137 return &resp.Console{
107 Name: def.ID, 138 Name: def.ID,
108 Commit: ccb, 139 Commit: ccb,
109 » » BuilderRef: builders, 140 » » BuilderRef: builderRefs,
110 }, nil 141 }, nil
111 } 142 }
112 143
113 // consoleRenderer is a wrapper around Console to provide additional methods. 144 // consoleRenderer is a wrapper around Console to provide additional methods.
114 type consoleRenderer struct { 145 type consoleRenderer struct {
115 *resp.Console 146 *resp.Console
116 } 147 }
117 148
118 // Header generates the console header html. 149 // Header generates the console header html.
119 func (c consoleRenderer) Header() template.HTML { 150 func (c consoleRenderer) Header() template.HTML {
120 // First, split things into nice rows and find the max depth. 151 // First, split things into nice rows and find the max depth.
121 cat := make([][]string, len(c.BuilderRef)) 152 cat := make([][]string, len(c.BuilderRef))
122 depth := 0 153 depth := 0
123 for i, b := range c.BuilderRef { 154 for i, b := range c.BuilderRef {
124 cat[i] = b.Category 155 cat[i] = b.Category
125 if len(cat[i]) > depth { 156 if len(cat[i]) > depth {
126 depth = len(cat[i]) 157 depth = len(cat[i])
127 } 158 }
128 } 159 }
129 160
130 » result := "" 161 » var buf bytes.Buffer
162 » must := func(_ int, err error) {
163 » » if err != nil {
164 » » » panic(err)
165 » » }
166 » }
131 for row := 0; row < depth; row++ { 167 for row := 0; row < depth; row++ {
132 » » result += "<tr><th></th><th></th>" 168 » » must(buf.WriteString("<tr><th></th><th></th>"))
133 // "" is the first two nodes, " " is an empty node. 169 // "" is the first two nodes, " " is an empty node.
134 current := "" 170 current := ""
135 colspan := 0 171 colspan := 0
136 for _, br := range cat { 172 for _, br := range cat {
137 colspan++ 173 colspan++
138 var s string 174 var s string
139 if row >= len(br) { 175 if row >= len(br) {
140 s = " " 176 s = " "
141 } else { 177 } else {
142 s = br[row] 178 s = br[row]
143 } 179 }
144 if s != current || current == " " { 180 if s != current || current == " " {
145 if current != "" || current == " " { 181 if current != "" || current == " " {
146 » » » » » result += fmt.Sprintf(`<th colspan="%d"> %s</th>`, colspan, current) 182 » » » » » must(fmt.Fprintf(&buf, `<th colspan="%d" >%s</th>`, colspan, current))
147 colspan = 0 183 colspan = 0
148 } 184 }
149 current = s 185 current = s
150 } 186 }
151 } 187 }
152 if colspan != 0 { 188 if colspan != 0 {
153 » » » result += fmt.Sprintf(`<th colspan="%d">%s</th>`, colspa n, current) 189 » » » must(fmt.Fprintf(&buf, `<th colspan="%d">%s</th>`, colsp an, current))
154 } 190 }
155 » » result += "</tr>" 191 » » must(buf.WriteString("</tr>"))
156 } 192 }
157 193
158 // Last row: The actual builder shortnames. 194 // Last row: The actual builder shortnames.
159 » result += "<tr><th></th><th></th>" 195 » must(buf.WriteString("<tr><th></th><th></th>"))
160 for _, br := range c.BuilderRef { 196 for _, br := range c.BuilderRef {
161 » » result += fmt.Sprintf("<th>%s</th>", br.ShortName) 197 » » _, _, builderName, err := buildsource.BuilderID(br.Name).Split()
198 » » if err != nil {
199 » » » must(fmt.Fprintf(&buf, "<th><a title=\"%s\">ERR</a></th> ", template.HTMLEscapeString(err.Error())))
200 » » } else {
201 » » » must(fmt.Fprintf(
202 » » » » &buf, "<th><a href=\"/%s\" title=\"%s\">%s</a></ th>",
203 » » » » template.HTMLEscapeString(br.Name),
204 » » » » template.HTMLEscapeString(builderName),
205 » » » » template.HTMLEscapeString(br.ShortName)))
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 const defaultLimit = 25
229 const maxLimit = 1000
230 limit := defaultLimit
231 if tLimit := GetLimit(c.Request, -1); tLimit >= 0 {
232 limit = tLimit
233 }
234 if limit > maxLimit {
235 limit = maxLimit
236 }
183 237
184 » result, err := console(c.Context, project, name) 238 » result, err := console(c.Context, project, name, limit)
185 if err != nil { 239 if err != nil {
186 ErrorHandler(c, err) 240 ErrorHandler(c, err)
187 return 241 return
188 } 242 }
189 243
190 templates.MustRender(c.Context, c.Writer, "pages/console.html", template s.Args{ 244 templates.MustRender(c.Context, c.Writer, "pages/console.html", template s.Args{
191 "Console": consoleRenderer{result}, 245 "Console": consoleRenderer{result},
192 }) 246 })
193 } 247 }
194 248
195 // ConsoleMainHandler is a redirect handler that redirects the user to the main 249 // ConsoleMainHandler is a redirect handler that redirects the user to the main
196 // console for a particular project. 250 // console for a particular project.
197 func ConsoleMainHandler(ctx *router.Context) { 251 func ConsoleMainHandler(ctx *router.Context) {
198 w, r, p := ctx.Writer, ctx.Request, ctx.Params 252 w, r, p := ctx.Writer, ctx.Request, ctx.Params
199 proj := p.ByName("project") 253 proj := p.ByName("project")
200 http.Redirect(w, r, fmt.Sprintf("/console/%s/main", proj), http.StatusMo vedPermanently) 254 http.Redirect(w, r, fmt.Sprintf("/console/%s/main", proj), http.StatusMo vedPermanently)
201 return 255 return
202 } 256 }
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