github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/jobserver/badge.go (about) 1 package jobserver 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 "net/url" 8 "text/template" 9 10 "github.com/pf-qiu/concourse/v6/atc/db" 11 ) 12 13 var ( 14 badgePassing = Badge{Width: 88, FillColor: `#44cc11`, Status: `passing`, Title: `build`} 15 badgeFailing = Badge{Width: 80, FillColor: `#e05d44`, Status: `failing`, Title: `build`} 16 badgeUnknown = Badge{Width: 98, FillColor: `#9f9f9f`, Status: `unknown`, Title: `build`} 17 badgeAborted = Badge{Width: 90, FillColor: `#8f4b2d`, Status: `aborted`, Title: `build`} 18 badgeErrored = Badge{Width: 88, FillColor: `#fe7d37`, Status: `errored`, Title: `build`} 19 ) 20 21 type Badge struct { 22 Width int 23 FillColor string 24 Status string 25 Title string 26 } 27 28 func (b *Badge) StatusWidth() int { 29 return b.Width - 37 30 } 31 32 func (b *Badge) StatusTextWidth() string { 33 return fmt.Sprintf("%.1f", float64(b.Width)/2+17.5) 34 } 35 36 func (b *Badge) String() string { 37 tmpl, err := template.New("Badge").Parse(badgeTemplate) 38 if err != nil { 39 panic(err) 40 } 41 42 buffer := &bytes.Buffer{} 43 44 _ = tmpl.Execute(buffer, &b) 45 46 return buffer.String() 47 } 48 49 func (b *Badge) EnrichFromQuery(params url.Values) { 50 if title := params.Get("title"); title != "" { 51 b.Title = title 52 } 53 } 54 55 func BadgeForBuild(build db.Build) Badge { 56 switch { 57 case build == nil: 58 return badgeUnknown 59 case build.Status() == db.BuildStatusSucceeded: 60 return badgePassing 61 case build.Status() == db.BuildStatusFailed: 62 return badgeFailing 63 case build.Status() == db.BuildStatusAborted: 64 return badgeAborted 65 case build.Status() == db.BuildStatusErrored: 66 return badgeErrored 67 default: 68 return badgeUnknown 69 } 70 } 71 72 const badgeTemplate = `<?xml version="1.0" encoding="UTF-8"?> 73 <svg xmlns="http://www.w3.org/2000/svg" width="{{ .Width }}" height="20"> 74 <linearGradient id="b" x2="0" y2="100%"> 75 <stop offset="0" stop-color="#bbb" stop-opacity=".1" /> 76 <stop offset="1" stop-opacity=".1" /> 77 </linearGradient> 78 <mask id="a"> 79 <rect width="{{ .Width }}" height="20" rx="3" fill="#fff" /> 80 </mask> 81 <g mask="url(#a)"> 82 <path fill="#555" d="M0 0h37v20H0z" /> 83 <path fill="{{ .FillColor }}" d="M37 0h{{ .StatusWidth }}v20H37z" /> 84 <path fill="url(#b)" d="M0 0h{{ .Width }}v20H0z" /> 85 </g> 86 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 87 <text x="18.5" y="15" fill="#010101" fill-opacity=".3">{{ .Title }}</text> 88 <text x="18.5" y="14">{{ .Title }}</text> 89 <text x="{{ .StatusTextWidth }}" y="15" fill="#010101" fill-opacity=".3">{{ .Status }}</text> 90 <text x="{{ .StatusTextWidth }}" y="14">{{ .Status }}</text> 91 </g> 92 </svg>` 93 94 func (s *Server) JobBadge(pipeline db.Pipeline) http.Handler { 95 logger := s.logger.Session("job-badge") 96 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 97 jobName := r.FormValue(":job_name") 98 99 job, found, err := pipeline.Job(jobName) 100 if err != nil { 101 logger.Error("error-finding-job", err) 102 w.WriteHeader(http.StatusInternalServerError) 103 return 104 } 105 106 if !found { 107 w.WriteHeader(http.StatusNotFound) 108 return 109 } 110 111 build, _, err := job.FinishedAndNextBuild() 112 if err != nil { 113 logger.Error("could-not-get-job-finished-and-next-build", err) 114 w.WriteHeader(http.StatusInternalServerError) 115 return 116 } 117 118 w.Header().Set("Content-type", "image/svg+xml") 119 120 w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") 121 w.Header().Set("Expires", "0") 122 123 w.WriteHeader(http.StatusOK) 124 125 badge := BadgeForBuild(build) 126 badge.EnrichFromQuery(r.URL.Query()) 127 fmt.Fprint(w, &badge) 128 }) 129 }