github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/deck/badge.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bytes" 21 "html/template" 22 23 "path/filepath" 24 "sort" 25 "strings" 26 27 prowapi "sigs.k8s.io/prow/pkg/apis/prowjobs/v1" 28 ) 29 30 var svg = `<svg xmlns="http://www.w3.org/2000/svg" width="{{.Width}}" height="20"> 31 <linearGradient id="a" x2="0" y2="100%"> 32 <stop offset="0" stop-color="#bbb" stop-opacity=".1"/> 33 <stop offset="1" stop-opacity=".1"/> 34 </linearGradient> 35 <rect rx="3" width="100%" height="20" fill="#555"/> 36 <g fill="{{.Color}}"> 37 <rect rx="3" x="{{.RightStart}}" width="{{.RightWidth}}" height="20"/> 38 <path d="M{{.RightStart}} 0h4v20h-4z"/> 39 </g> 40 <rect rx="3" width="100%" height="20" fill="url(#a)"/> 41 <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"> 42 <g fill="#010101" opacity=".3"> 43 <text x="{{.XposLeft}}" y="15">{{.Subject}}</text> 44 <text x="{{.XposRight}}" y="15">{{.Status}}</text> 45 </g> 46 <text x="{{.XposLeft}}" y="14">{{.Subject}}</text> 47 <text x="{{.XposRight}}" y="14">{{.Status}}</text> 48 </g> 49 </svg>` 50 51 var svgTemplate = template.Must(template.New("svg").Parse(svg)) 52 53 // Make a small SVG badge that looks like `[subject | status]`, with the status 54 // text in the given color. Provides a local version of the shields.io service. 55 func makeShield(subject, status, color string) []byte { 56 // TODO(rmmh): Use better font-size metrics for prettier badges-- estimating 57 // character widths as 6px isn't very accurate. 58 // See also: https://github.com/badges/shields/blob/master/measure-text.js 59 p := struct { 60 Width, RightStart, RightWidth int 61 XposLeft, XposRight float64 62 Subject, Status string 63 Color string 64 }{ 65 Subject: subject, 66 Status: status, 67 RightStart: 13 + 6*len(subject), 68 RightWidth: 13 + 6*len(status), 69 } 70 p.Width = p.RightStart + p.RightWidth 71 p.XposLeft = float64(p.RightStart) * 0.5 72 p.XposRight = float64(p.RightStart) + float64(p.RightWidth-2)*0.5 73 switch color { 74 case "brightgreen": 75 p.Color = "#4c1" 76 case "red": 77 p.Color = "#e05d44" 78 default: 79 p.Color = color 80 } 81 var buf bytes.Buffer 82 svgTemplate.Execute(&buf, p) 83 return buf.Bytes() 84 } 85 86 // pickLatestJobs returns the most recent run of each job matching the selector, 87 // which is comma-separated list of globs, for example "ci-ti-*,ci-other". 88 // jobs will be sorted by StartTime in reverse order to display recent jobs first 89 func pickLatestJobs(jobs []prowapi.ProwJob, selector string) []prowapi.ProwJob { 90 sort.Slice(jobs, func(i, j int) bool { 91 return jobs[j].Status.StartTime.Before(&jobs[i].Status.StartTime) 92 }) 93 var out []prowapi.ProwJob 94 have := make(map[string]bool) 95 want := strings.Split(selector, ",") 96 for _, job := range jobs { 97 if have[job.Spec.Job] { 98 continue // already have the latest result for this job 99 } 100 for _, pat := range want { 101 if match, _ := filepath.Match(pat, job.Spec.Job); match { 102 have[job.Spec.Job] = true 103 out = append(out, job) 104 break 105 } 106 } 107 } 108 return out 109 } 110 111 func renderBadge(jobs []prowapi.ProwJob) (string, string, []byte) { 112 color := "brightgreen" 113 status := "passing" 114 if len(jobs) == 0 { 115 color = "darkgrey" 116 status = "no results" 117 } else { 118 failedJobs := []string{} 119 for _, job := range jobs { 120 if job.Status.State == "failure" { 121 failedJobs = append(failedJobs, job.Spec.Job) 122 } 123 } 124 sort.Strings(failedJobs) 125 if len(failedJobs) > 3 { 126 failedJobs = append(failedJobs[:3], "...") 127 } 128 if len(failedJobs) > 0 { 129 color = "red" 130 status = "failing " + strings.Join(failedJobs, ", ") 131 } 132 } 133 134 return status, color, makeShield("build", status, color) 135 }