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

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

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

Powered by Google App Engine
This is Rietveld 408576698