autocompletequeryservice.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package asset
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/opencost/opencost/core/pkg/autocomplete"
  6. coreasset "github.com/opencost/opencost/core/pkg/autocomplete/asset"
  7. "github.com/opencost/opencost/core/pkg/opencost"
  8. )
  9. func QueryAssetAutocompleteFromSet(assetSet *opencost.AssetSet, req autocomplete.Request) (*autocomplete.Response, error) {
  10. field, err := autocomplete.NormalizeRequest(&req, coreasset.ValidateField, autocomplete.NormalizeOptions{
  11. RequireTenantID: true,
  12. WindowValidator: coreasset.ValidateWindow,
  13. })
  14. if err != nil {
  15. return nil, err
  16. }
  17. route, _, err := coreasset.RouteField(field)
  18. if err != nil {
  19. return nil, fmt.Errorf("%w: %w", autocomplete.ErrBadRequest, err)
  20. }
  21. switch route {
  22. case coreasset.RouteStaticType:
  23. return &autocomplete.Response{Data: autocomplete.UniqueSortedLimited(
  24. autocomplete.ToSet(coreasset.FilterStaticValues(coreasset.StaticTypes(), req.Search)),
  25. req.Limit,
  26. )}, nil
  27. case coreasset.RouteStaticCategory:
  28. return &autocomplete.Response{Data: autocomplete.UniqueSortedLimited(
  29. autocomplete.ToSet(coreasset.FilterStaticValues(coreasset.StaticCategories(), req.Search)),
  30. req.Limit,
  31. )}, nil
  32. }
  33. var matcher opencost.AssetMatcher
  34. if autocomplete.HasFilter(req.Filter) {
  35. compiler := opencost.NewAssetMatchCompiler()
  36. matcher, err = compiler.Compile(req.Filter)
  37. if err != nil {
  38. return nil, fmt.Errorf("%w: failed to compile filter: %w", autocomplete.ErrBadRequest, err)
  39. }
  40. }
  41. search := strings.ToLower(req.Search)
  42. results := map[string]struct{}{}
  43. for _, a := range assetSet.Assets {
  44. if a == nil {
  45. continue
  46. }
  47. if matcher != nil && !matcher.Matches(a) {
  48. continue
  49. }
  50. values := assetAutocompleteValues(a, field)
  51. for _, value := range values {
  52. if value == "" {
  53. continue
  54. }
  55. if search != "" && !strings.Contains(strings.ToLower(value), search) {
  56. continue
  57. }
  58. results[value] = struct{}{}
  59. }
  60. }
  61. return &autocomplete.Response{Data: autocomplete.UniqueSortedLimited(results, req.Limit)}, nil
  62. }
  63. func assetAutocompleteValues(asset opencost.Asset, field string) []string {
  64. props := asset.GetProperties()
  65. if props == nil {
  66. return nil
  67. }
  68. switch {
  69. case field == "account":
  70. return []string{props.Account}
  71. case field == "cluster":
  72. return []string{props.Cluster}
  73. case field == "name":
  74. return []string{props.Name}
  75. case field == "provider":
  76. return []string{props.Provider}
  77. case field == "providerid":
  78. return []string{props.ProviderID}
  79. case field == "type":
  80. return []string{asset.Type().String()}
  81. case field == "category":
  82. return []string{props.Category}
  83. case field == "label":
  84. keys := make([]string, 0, len(asset.GetLabels()))
  85. for key := range asset.GetLabels() {
  86. keys = append(keys, key)
  87. }
  88. return keys
  89. case strings.HasPrefix(field, "label:"):
  90. labelName := strings.TrimPrefix(field, "label:")
  91. if value, ok := autocomplete.MapValueFold(asset.GetLabels(), labelName); ok {
  92. return []string{value}
  93. }
  94. }
  95. return nil
  96. }