autocompletequeryservice.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. package allocation
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/core/pkg/autocomplete"
  6. coreallocation "github.com/opencost/opencost/core/pkg/autocomplete/allocation"
  7. "github.com/opencost/opencost/core/pkg/opencost"
  8. )
  9. func QueryAllocationAutocompleteFromSetRange(asr *opencost.AllocationSetRange, req autocomplete.Request) (*autocomplete.Response, error) {
  10. field, err := autocomplete.NormalizeRequest(&req, coreallocation.ValidateField, autocomplete.NormalizeOptions{
  11. EnsureLabelConfig: true,
  12. })
  13. if err != nil {
  14. return nil, err
  15. }
  16. var matcher opencost.AllocationMatcher
  17. if autocomplete.HasFilter(req.Filter) {
  18. compiler := opencost.NewAllocationMatchCompiler(req.LabelConfig)
  19. matcher, err = compiler.Compile(req.Filter)
  20. if err != nil {
  21. return nil, fmt.Errorf("%w: failed to compile filter: %w", autocomplete.ErrBadRequest, err)
  22. }
  23. }
  24. search := strings.ToLower(req.Search)
  25. results := map[string]struct{}{}
  26. for _, as := range asr.Allocations {
  27. if as == nil {
  28. continue
  29. }
  30. for _, alloc := range as.Allocations {
  31. if alloc == nil || alloc.Properties == nil {
  32. continue
  33. }
  34. if matcher != nil && !matcher.Matches(alloc) {
  35. continue
  36. }
  37. values := allocationAutocompleteValues(alloc.Properties, field)
  38. for _, value := range values {
  39. if value == "" {
  40. continue
  41. }
  42. if search != "" && !strings.Contains(strings.ToLower(value), search) {
  43. continue
  44. }
  45. results[value] = struct{}{}
  46. }
  47. }
  48. }
  49. return &autocomplete.Response{Data: autocomplete.UniqueSortedLimited(results, req.Limit)}, nil
  50. }
  51. func allocationAutocompleteValues(props *opencost.AllocationProperties, field string) []string {
  52. switch {
  53. case field == "account":
  54. return nil
  55. case field == "cluster":
  56. return []string{props.Cluster}
  57. case field == "namespace":
  58. return []string{props.Namespace}
  59. case field == "node":
  60. return []string{props.Node}
  61. case field == "controllerkind":
  62. return []string{props.ControllerKind}
  63. case field == "controllername":
  64. return []string{props.Controller}
  65. case field == "pod":
  66. return []string{props.Pod}
  67. case field == "container":
  68. return []string{props.Container}
  69. case field == "label":
  70. return mapKeys(props.Labels)
  71. case strings.HasPrefix(field, "label:"):
  72. label := strings.TrimPrefix(field, "label:")
  73. if v, ok := autocomplete.MapValueFold(props.Labels, label); ok {
  74. return []string{v}
  75. }
  76. case field == "namespacelabel":
  77. return mapKeys(props.NamespaceLabels)
  78. case strings.HasPrefix(field, "namespacelabel:"):
  79. label := strings.TrimPrefix(field, "namespacelabel:")
  80. if v, ok := autocomplete.MapValueFold(props.NamespaceLabels, label); ok {
  81. return []string{v}
  82. }
  83. }
  84. return nil
  85. }
  86. func mapKeys(values map[string]string) []string {
  87. result := make([]string, 0, len(values))
  88. for k := range values {
  89. result = append(result, k)
  90. }
  91. return result
  92. }