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  }