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

Side by Side Diff: milo/common/funcs.go

Issue 2977863002: [milo] Refactor all html knowledge out of backends. (Closed)
Patch Set: seems to work :) Created 3 years, 5 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
OLDNEW
(Empty)
1 // Copyright 2016 The LUCI Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (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
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package common
16
17 import (
18 "bytes"
19 "fmt"
20 "html/template"
21 "net/http"
22 "strings"
23 "time"
24
25 "github.com/luci/luci-go/milo/api/resp"
26 )
27
28 // A collection of useful templating functions
29
30 // funcMap is what gets fed into the template bundle.
31 var funcMap = template.FuncMap{
32 "humanDuration": humanDuration,
33 "parseRFC3339": parseRFC3339,
34 "linkify": linkify,
35 "linkifySet": linkifySet,
36 "obfuscateEmail": obfuscateEmail,
37 "localTime": localTime,
38 "shortHash": shortHash,
39 "startswith": strings.HasPrefix,
40 "sub": sub,
41 "consoleHeader": consoleHeader,
42 "pagedURL": pagedURL,
43 "formatTime": formatTime,
44 "percent": percent,
45 }
46
47 // localTime returns a <span> element with t in human format
48 // that will be converted to local timezone in the browser.
49 // Recommended usage: {{ .Date | localTime "N/A" }}
50 func localTime(ifZero string, t time.Time) template.HTML {
51 if t.IsZero() {
52 return template.HTML(template.HTMLEscapeString(ifZero))
53 }
54 milliseconds := t.UnixNano() / 1e6
55 return template.HTML(fmt.Sprintf(
56 `<span class="local-time" data-timestamp="%d">%s</span>`,
57 milliseconds,
58 t.Format(time.RFC850)))
59 }
60
61 func consoleHeader(brs []resp.BuilderRef) template.HTML {
62 // First, split things into nice rows and find the max depth.
63 cat := make([][]string, len(brs))
64 depth := 0
65 for i, b := range brs {
66 cat[i] = b.Category
67 if len(cat[i]) > depth {
68 depth = len(cat[i])
69 }
70 }
71
72 result := ""
73 for row := 0; row < depth; row++ {
74 result += "<tr><th></th>"
75 // "" is the first node, " " is an empty node.
76 current := ""
77 colspan := 0
78 for _, br := range cat {
79 colspan++
80 var s string
81 if row >= len(br) {
82 s = " "
83 } else {
84 s = br[row]
85 }
86 if s != current || current == " " {
87 if current != "" || current == " " {
88 result += fmt.Sprintf(`<th colspan="%d"> %s</th>`, colspan, current)
89 colspan = 0
90 }
91 current = s
92 }
93 }
94 if colspan != 0 {
95 result += fmt.Sprintf(`<th colspan="%d">%s</th>`, colspa n, current)
96 }
97 result += "</tr>"
98 }
99
100 // Last row: The actual builder shortnames.
101 result += "<tr><th></th>"
102 for _, br := range brs {
103 result += fmt.Sprintf("<th>%s</th>", br.ShortName)
104 }
105 result += "</tr>"
106 return template.HTML(result)
107 }
108
109 // humanDuration translates d into a human readable string of x units y units,
110 // where x and y could be in days, hours, minutes, or seconds, whichever is the
111 // largest.
112 func humanDuration(d time.Duration) string {
113 t := int64(d.Seconds())
114 day := t / 86400
115 hr := (t % 86400) / 3600
116
117 if day > 0 {
118 if hr != 0 {
119 return fmt.Sprintf("%d days %d hrs", day, hr)
120 }
121 return fmt.Sprintf("%d days", day)
122 }
123
124 min := (t % 3600) / 60
125 if hr > 0 {
126 if min != 0 {
127 return fmt.Sprintf("%d hrs %d mins", hr, min)
128 }
129 return fmt.Sprintf("%d hrs", hr)
130 }
131
132 sec := t % 60
133 if min > 0 {
134 if sec != 0 {
135 return fmt.Sprintf("%d mins %d secs", min, sec)
136 }
137 return fmt.Sprintf("%d mins", min)
138 }
139
140 if sec != 0 {
141 return fmt.Sprintf("%d secs", sec)
142 }
143
144 if d > time.Millisecond {
145 return fmt.Sprintf("%d ms", d/time.Millisecond)
146 }
147
148 return "0"
149 }
150
151 // obfuscateEmail converts a string containing email adddress email@address.com
152 // into email<junk>@address.com.
153 func obfuscateEmail(email string) template.HTML {
154 email = template.HTMLEscapeString(email)
155 return template.HTML(strings.Replace(
156 email, "@", "<span style=\"display:none\">ohnoyoudont</span>@", -1))
157 }
158
159 // parseRFC3339 parses time represented as a RFC3339 or RFC3339Nano string.
160 // If cannot parse, returns zero time.
161 func parseRFC3339(s string) time.Time {
162 t, err := time.Parse(time.RFC3339, s)
163 if err == nil {
164 return t
165 }
166 t, err = time.Parse(time.RFC3339Nano, s)
167 if err == nil {
168 return t
169 }
170 return time.Time{}
171 }
172
173 // formatTime takes a time object and returns a formatted RFC3339 string.
174 func formatTime(t time.Time) string {
175 return t.Format(time.RFC3339)
176 }
177
178 // linkifyTemplate is the template used in "linkify". Because the template,
179 // itself recursively invokes "linkify", we will initialize it in explicitly
180 // in "init()".
181 //
182 // linkifySetTemplate is the template used in "linkifySet".
183 var linkifyTemplate, linkifySetTemplate *template.Template
184
185 // linkify turns a resp.LinkSet struct into a canonical link.
186 func linkify(link *resp.Link) template.HTML {
187 if link == nil {
188 return ""
189 }
190 buf := bytes.Buffer{}
191 if err := linkifyTemplate.Execute(&buf, link); err != nil {
192 panic(err)
193 }
194 return template.HTML(buf.Bytes())
195 }
196
197 // linkifySet turns a resp.Link struct into a canonical link.
198 func linkifySet(linkSet resp.LinkSet) template.HTML {
199 if len(linkSet) == 0 {
200 return ""
201 }
202 buf := bytes.Buffer{}
203 if err := linkifySetTemplate.Execute(&buf, linkSet); err != nil {
204 panic(err)
205 }
206 return template.HTML(buf.Bytes())
207 }
208
209 // sub subtracts one number from another, because apparently go templates aren't
210 // smart enough to do that.
211 func sub(a, b int) int {
212 return a - b
213 }
214
215 // shortHash abbriviates a git hash into 6 characters.
216 func shortHash(s string) string {
217 if len(s) > 6 {
218 return s[0:6]
219 }
220 return s
221 }
222
223 // pagedURL returns a self URL with the given cursor and limit paging options.
224 // if limit is set to 0, then inherit whatever limit is set in request. If
225 // both are unspecified, then limit is omitted.
226 func pagedURL(r *http.Request, limit int, cursor string) string {
227 if limit == 0 {
228 var err error
229 limit, err = GetLimit(r)
230 if err != nil {
231 // This should not happen because the handler should've already validated the
232 // limit earlier in the process.
233 panic(err)
234 }
235 if limit < 0 {
236 limit = 0
237 }
238 }
239 values := r.URL.Query()
240 switch cursor {
241 case "EMPTY":
242 values.Del("cursor")
243 case "":
244 // Do nothing, just leave the cursor in.
245 default:
246 values.Set("cursor", cursor)
247 }
248 switch {
249 case limit < 0:
250 values.Del("limit")
251 case limit > 0:
252 values.Set("limit", fmt.Sprintf("%d", limit))
253 }
254 result := *r.URL
255 result.RawQuery = values.Encode()
256 return result.String()
257 }
258
259 // percent divides one number by a divisor and returns the percentage in string form.
260 func percent(numerator, divisor int) string {
261 p := float64(numerator) * 100.0 / float64(divisor)
262 return fmt.Sprintf("%.1f", p)
263 }
264
265 func init() {
266 linkifySetTemplate = template.Must(
267 template.New("linkifySet").
268 Funcs(template.FuncMap{
269 "linkify": linkify,
270 }).Parse(
271 `{{ range $i, $link := . }}` +
272 `{{ if gt $i 0 }} {{ end }}` +
273 `{{ $link | linkify}}` +
274 `{{ end }}`))
275
276 linkifyTemplate = template.Must(
277 template.New("linkify").
278 Parse(
279 `<a href="{{.URL}}">` +
280 `{{if .Img}}<img src="{{.Img}}"{{if .Alt }} alt="{{.Alt}}"{{end}}>` +
281 `{{else if .Alias}}[{{.Label}}]` +
282 `{{else}}{{.Label}}{{end}}` +
283 `</a>`))
284 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698