github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/dashboard/app/label.go (about) 1 // Copyright 2023 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import ( 7 "context" 8 "fmt" 9 "sort" 10 "strings" 11 ) 12 13 const ( 14 EmptyLabel BugLabelType = "" 15 SubsystemLabel BugLabelType = "subsystems" 16 PriorityLabel BugLabelType = "prio" 17 NoRemindersLabel BugLabelType = "no-reminders" 18 OriginLabel BugLabelType = "origin" 19 MissingBackportLabel BugLabelType = "missing-backport" 20 ) 21 22 type BugPrio string 23 24 const ( 25 LowPrioBug BugPrio = "low" 26 NormalPrioBug BugPrio = "normal" 27 HighPrioBug BugPrio = "high" 28 ) 29 30 type oneOf []string 31 type subsetOf []string 32 type trueFalse struct{} 33 34 func makeLabelSet(c context.Context, ns string) *labelSet { 35 ret := map[BugLabelType]interface{}{ 36 PriorityLabel: oneOf([]string{ 37 string(LowPrioBug), 38 string(NormalPrioBug), 39 string(HighPrioBug), 40 }), 41 NoRemindersLabel: trueFalse{}, 42 MissingBackportLabel: trueFalse{}, 43 } 44 service := getNsConfig(c, ns).Subsystems.Service 45 if service != nil { 46 names := []string{} 47 for _, item := range service.List() { 48 names = append(names, item.Name) 49 } 50 ret[SubsystemLabel] = subsetOf(names) 51 } 52 53 originLabels := []string{} 54 for _, repo := range getNsConfig(c, ns).Repos { 55 if repo.LabelIntroduced != "" { 56 originLabels = append(originLabels, repo.LabelIntroduced) 57 } 58 if repo.LabelReached != "" { 59 originLabels = append(originLabels, repo.LabelReached) 60 } 61 } 62 63 if len(originLabels) > 0 { 64 ret[OriginLabel] = subsetOf(originLabels) 65 } 66 67 return &labelSet{ 68 c: c, 69 ns: ns, 70 labels: ret, 71 } 72 } 73 74 type labelSet struct { 75 c context.Context 76 ns string 77 labels map[BugLabelType]interface{} 78 } 79 80 func (s *labelSet) FindLabel(label BugLabelType) bool { 81 _, ok := s.labels[label] 82 return ok 83 } 84 85 func (s *labelSet) ValidateValues(label BugLabelType, values []BugLabel) string { 86 rules := s.labels[label] 87 if rules == nil { 88 return "" 89 } 90 switch v := rules.(type) { 91 case oneOf: 92 if len(values) != 1 { 93 return "You must specify only one of the allowed values" 94 } 95 if !stringInList([]string(v), values[0].Value) { 96 return fmt.Sprintf("%q is not among the allowed values", values[0].Value) 97 } 98 case subsetOf: 99 for _, item := range values { 100 if !stringInList([]string(v), item.Value) { 101 return fmt.Sprintf("%q is not among the allowed values", item.Value) 102 } 103 } 104 case trueFalse: 105 if len(values) != 1 || values[0].Value != "" { 106 return "This label does not support any values" 107 } 108 } 109 return "" 110 } 111 112 func (s *labelSet) Help() string { 113 var sortedKeys []BugLabelType 114 for key := range s.labels { 115 sortedKeys = append(sortedKeys, key) 116 } 117 sort.Slice(sortedKeys, func(i, j int) bool { 118 return string(sortedKeys[i]) < string(sortedKeys[j]) 119 }) 120 121 var help strings.Builder 122 for _, key := range sortedKeys { 123 if help.Len() > 0 { 124 help.WriteString(", ") 125 } 126 if key == SubsystemLabel { 127 help.WriteString(fmt.Sprintf("%s: {.. see below ..}", key)) 128 continue 129 } 130 switch v := s.labels[key].(type) { 131 case oneOf: 132 help.WriteString(string(key)) 133 help.WriteString(": {") 134 list := []string(v) 135 for i := range list { 136 if i > 0 { 137 help.WriteString(", ") 138 } 139 help.WriteString(list[i]) 140 } 141 help.WriteByte('}') 142 case trueFalse: 143 help.WriteString(string(key)) 144 } 145 } 146 147 var sb strings.Builder 148 writeWrapped(&sb, help.String()) 149 if _, ok := s.labels[SubsystemLabel]; ok { 150 url := subsystemListURL(s.c, s.ns) 151 if url != "" { 152 sb.WriteString(fmt.Sprintf("\nThe list of subsystems: %s", url)) 153 } 154 } 155 return sb.String() 156 } 157 158 func writeWrapped(sb *strings.Builder, str string) { 159 const limit = 80 160 lineLen := 0 161 for _, token := range strings.Fields(str) { 162 if lineLen >= limit || 163 lineLen > 0 && lineLen+len(token) >= limit { 164 sb.WriteByte('\n') 165 lineLen = 0 166 } 167 if lineLen > 0 { 168 sb.WriteString(" ") 169 } 170 sb.WriteString(token) 171 lineLen += len(token) 172 } 173 } 174 175 func (bug *Bug) HasLabel(label BugLabelType, value string) bool { 176 for _, item := range bug.Labels { 177 if item.Label == label && item.Value == value { 178 return true 179 } 180 } 181 return false 182 } 183 184 func (bug *Bug) LabelValues(label BugLabelType) []BugLabel { 185 var ret []BugLabel 186 for _, item := range bug.Labels { 187 if item.Label == label { 188 ret = append(ret, item) 189 } 190 } 191 return ret 192 } 193 194 func (bug *Bug) SetLabels(set *labelSet, values []BugLabel) error { 195 var label BugLabelType 196 for _, v := range values { 197 if label != EmptyLabel && label != v.Label { 198 return fmt.Errorf("values of the same label type are expected") 199 } 200 label = v.Label 201 } 202 if errStr := set.ValidateValues(label, values); errStr != "" { 203 return fmt.Errorf("%s", errStr) 204 } 205 bug.UnsetLabels(label) 206 bug.Labels = append(bug.Labels, values...) 207 return nil 208 } 209 210 func (bug *Bug) UnsetLabels(labels ...BugLabelType) map[BugLabelType]struct{} { 211 toDelete := map[BugLabelType]struct{}{} 212 notFound := map[BugLabelType]struct{}{} 213 for _, name := range labels { 214 toDelete[name] = struct{}{} 215 notFound[name] = struct{}{} 216 } 217 var newList []BugLabel 218 for _, item := range bug.Labels { 219 if _, ok := toDelete[item.Label]; ok { 220 delete(notFound, item.Label) 221 continue 222 } 223 newList = append(newList, item) 224 } 225 bug.Labels = newList 226 return notFound 227 } 228 229 func (bug *Bug) HasUserLabel(label BugLabelType) bool { 230 for _, item := range bug.Labels { 231 if item.Label == label && item.SetBy != "" { 232 return true 233 } 234 } 235 return false 236 } 237 238 func (bug *Bug) prio() BugPrio { 239 for _, label := range bug.LabelValues(PriorityLabel) { 240 return BugPrio(label.Value) 241 } 242 return NormalPrioBug 243 } 244 245 var bugPrioOrder = map[BugPrio]int{ 246 LowPrioBug: 1, 247 NormalPrioBug: 2, 248 HighPrioBug: 3, 249 } 250 251 func (bp BugPrio) LessThan(other BugPrio) bool { 252 return bugPrioOrder[bp] < bugPrioOrder[other] 253 }