mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
fix(backend): refactor filters
This commit is contained in:
committed by
Łukasz Mierzwa
parent
723d2a4ded
commit
7f9be48800
+6
-6
@@ -18,8 +18,8 @@ import (
|
||||
"github.com/prymitive/karma/internal/uri"
|
||||
)
|
||||
|
||||
func getFiltersFromQuery(filterStrings []string) []filters.FilterT {
|
||||
matchFilters := make([]filters.FilterT, 0, len(filterStrings))
|
||||
func getFiltersFromQuery(filterStrings []string) []filters.Filter {
|
||||
matchFilters := make([]filters.Filter, 0, len(filterStrings))
|
||||
for _, filterExpression := range filterStrings {
|
||||
f := filters.NewFilter(filterExpression)
|
||||
matchFilters = append(matchFilters, f)
|
||||
@@ -324,11 +324,11 @@ func autoGridLabel(dedupedAlerts []models.AlertGroup) string {
|
||||
return lastLabel
|
||||
}
|
||||
|
||||
func filterAlerts(dedupedAlerts []models.AlertGroup, fl []filters.FilterT) (filteredAlerts []models.AlertGroup) {
|
||||
func filterAlerts(dedupedAlerts []models.AlertGroup, fl []filters.Filter) (filteredAlerts []models.AlertGroup) {
|
||||
var matches int
|
||||
var hasAMFilters bool
|
||||
for _, filter := range fl {
|
||||
if filter.GetIsValid() && filter.GetIsAlertmanagerFilter() {
|
||||
if filter.Valid() && filter.IsAlertmanagerFilter() {
|
||||
hasAMFilters = true
|
||||
break
|
||||
}
|
||||
@@ -350,7 +350,7 @@ func filterAlerts(dedupedAlerts []models.AlertGroup, fl []filters.FilterT) (filt
|
||||
for _, alert := range ag.Alerts {
|
||||
var hadMismatch bool
|
||||
for _, filter := range fl {
|
||||
if filter.GetIsValid() {
|
||||
if filter.Valid() {
|
||||
if !filter.Match(&alert, matches) {
|
||||
hadMismatch = true
|
||||
}
|
||||
@@ -364,7 +364,7 @@ func filterAlerts(dedupedAlerts []models.AlertGroup, fl []filters.FilterT) (filt
|
||||
clear(blockedAMs)
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, filter := range fl {
|
||||
if filter.GetIsValid() && filter.GetIsAlertmanagerFilter() && !filter.MatchAlertmanager(&am) {
|
||||
if filter.Valid() && filter.IsAlertmanagerFilter() && !filter.MatchAlertmanager(&am) {
|
||||
blockedAMs[am.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -91,16 +91,16 @@ func index(w http.ResponseWriter, _ *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func populateAPIFilters(matchFilters []filters.FilterT) []models.Filter {
|
||||
func populateAPIFilters(matchFilters []filters.Filter) []models.Filter {
|
||||
apiFilters := []models.Filter{}
|
||||
for _, filter := range matchFilters {
|
||||
af := models.Filter{
|
||||
Text: filter.GetRawText(),
|
||||
Name: filter.GetName(),
|
||||
Matcher: filter.GetMatcher(),
|
||||
Value: filter.GetValue(),
|
||||
Hits: filter.GetHits(),
|
||||
IsValid: filter.GetIsValid(),
|
||||
Text: filter.RawText(),
|
||||
Name: filter.Name(),
|
||||
Matcher: filter.MatcherOperation(),
|
||||
Value: filter.Value(),
|
||||
Hits: filter.Hits(),
|
||||
IsValid: filter.Valid(),
|
||||
}
|
||||
if af.Text != "" {
|
||||
apiFilters = append(apiFilters, af)
|
||||
@@ -426,9 +426,9 @@ func alerts(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
for _, filter := range matchFilters {
|
||||
if filter.GetValue() != "" && filter.GetMatcher() == "=" {
|
||||
transform.ColorLabel(colors, filter.GetName(), filter.GetValue())
|
||||
labelSettings(filter.GetName(), resp.Settings.Labels)
|
||||
if filter.Value() != "" && filter.MatcherOperation() == "=" {
|
||||
transform.ColorLabel(colors, filter.Name(), filter.Value())
|
||||
labelSettings(filter.Name(), resp.Settings.Labels)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,35 +193,34 @@ func DedupColors() models.LabelsColorMap {
|
||||
// DedupAutocomplete returns a list of autocomplete hints merged from all
|
||||
// Alertmanager upstreams
|
||||
func DedupAutocomplete() []models.Autocomplete {
|
||||
uniqueAutocomplete := map[string]*models.Autocomplete{}
|
||||
|
||||
upstreams := GetAlertmanagers()
|
||||
|
||||
var result []models.Autocomplete
|
||||
index := map[string]int{}
|
||||
|
||||
for _, am := range upstreams {
|
||||
ac := am.Autocomplete()
|
||||
for _, hint := range ac {
|
||||
h, found := uniqueAutocomplete[hint.Value]
|
||||
if found {
|
||||
for _, token := range hint.Tokens {
|
||||
if !slices.Contains(h.Tokens, token) {
|
||||
h.Tokens = append(h.Tokens, token)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uniqueAutocomplete[hint.Value] = &models.Autocomplete{
|
||||
Value: hint.Value,
|
||||
Tokens: hint.Tokens,
|
||||
}
|
||||
am.ForEachAutocomplete(func(hint models.Autocomplete) {
|
||||
mergeAutocompleteHint(&result, index, hint)
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func mergeAutocompleteHint(result *[]models.Autocomplete, index map[string]int, hint models.Autocomplete) {
|
||||
if idx, found := index[hint.Value]; found {
|
||||
for _, token := range hint.Tokens {
|
||||
if !slices.Contains((*result)[idx].Tokens, token) {
|
||||
(*result)[idx].Tokens = append((*result)[idx].Tokens, token)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
index[hint.Value] = len(*result)
|
||||
*result = append(*result, models.Autocomplete{
|
||||
Value: hint.Value,
|
||||
Tokens: hint.Tokens,
|
||||
})
|
||||
}
|
||||
|
||||
dedupedAutocomplete := make([]models.Autocomplete, 0, len(uniqueAutocomplete))
|
||||
for _, hint := range uniqueAutocomplete {
|
||||
dedupedAutocomplete = append(dedupedAutocomplete, *hint)
|
||||
}
|
||||
|
||||
return dedupedAutocomplete
|
||||
}
|
||||
|
||||
// DedupKnownLabels returns a deduplicated slice of all known label names
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package alertmanager
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
func TestMergeAutocompleteHintNewEntry(t *testing.T) {
|
||||
// verifies that a new hint is appended to the result slice
|
||||
var result []models.Autocomplete
|
||||
index := map[string]int{}
|
||||
|
||||
mergeAutocompleteHint(&result, index, models.Autocomplete{
|
||||
Value: "foo=bar",
|
||||
Tokens: []string{"foo", "foo=bar"},
|
||||
})
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 result, got %d", len(result))
|
||||
}
|
||||
if result[0].Value != "foo=bar" {
|
||||
t.Errorf("expected Value %q, got %q", "foo=bar", result[0].Value)
|
||||
}
|
||||
if len(result[0].Tokens) != 2 {
|
||||
t.Errorf("expected 2 tokens, got %d", len(result[0].Tokens))
|
||||
}
|
||||
if idx, ok := index["foo=bar"]; !ok || idx != 0 {
|
||||
t.Errorf("expected index[\"foo=bar\"] == 0, got %d (ok=%v)", idx, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeAutocompleteHintDuplicateTokens(t *testing.T) {
|
||||
// verifies that duplicate tokens from a second upstream are not appended
|
||||
var result []models.Autocomplete
|
||||
index := map[string]int{}
|
||||
|
||||
mergeAutocompleteHint(&result, index, models.Autocomplete{
|
||||
Value: "foo=bar",
|
||||
Tokens: []string{"foo", "foo=bar"},
|
||||
})
|
||||
mergeAutocompleteHint(&result, index, models.Autocomplete{
|
||||
Value: "foo=bar",
|
||||
Tokens: []string{"foo", "foo=bar"},
|
||||
})
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 result after merge, got %d", len(result))
|
||||
}
|
||||
if len(result[0].Tokens) != 2 {
|
||||
t.Errorf("expected 2 tokens after merge with identical tokens, got %d", len(result[0].Tokens))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeAutocompleteHintNewTokens(t *testing.T) {
|
||||
// verifies that new tokens from a second upstream are appended
|
||||
var result []models.Autocomplete
|
||||
index := map[string]int{}
|
||||
|
||||
mergeAutocompleteHint(&result, index, models.Autocomplete{
|
||||
Value: "foo=bar",
|
||||
Tokens: []string{"foo", "foo=bar"},
|
||||
})
|
||||
mergeAutocompleteHint(&result, index, models.Autocomplete{
|
||||
Value: "foo=bar",
|
||||
Tokens: []string{"extra_token"},
|
||||
})
|
||||
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("expected 1 result after merge, got %d", len(result))
|
||||
}
|
||||
expected := []string{"foo", "foo=bar", "extra_token"}
|
||||
if len(result[0].Tokens) != len(expected) {
|
||||
t.Fatalf("expected %d tokens, got %d: %v", len(expected), len(result[0].Tokens), result[0].Tokens)
|
||||
}
|
||||
for i, tok := range expected {
|
||||
if result[0].Tokens[i] != tok {
|
||||
t.Errorf("token[%d] = %q, want %q", i, result[0].Tokens[i], tok)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ type alertmanagerMetrics struct {
|
||||
}
|
||||
|
||||
type HealthCheck struct {
|
||||
filters []filters.FilterT
|
||||
filters []filters.Filter
|
||||
wasFound bool
|
||||
}
|
||||
|
||||
@@ -459,14 +459,14 @@ func (am *Alertmanager) Colors() models.LabelsColorMap {
|
||||
return colors
|
||||
}
|
||||
|
||||
// Autocomplete returns a copy of all autocomplete data
|
||||
func (am *Alertmanager) Autocomplete() []models.Autocomplete {
|
||||
// ForEachAutocomplete calls fn for each autocomplete hint while holding a read lock.
|
||||
func (am *Alertmanager) ForEachAutocomplete(fn func(models.Autocomplete)) {
|
||||
am.lock.RLock()
|
||||
defer am.lock.RUnlock()
|
||||
|
||||
autocomplete := make([]models.Autocomplete, len(am.autocomplete))
|
||||
copy(autocomplete, am.autocomplete)
|
||||
return autocomplete
|
||||
for _, hint := range am.autocomplete {
|
||||
fn(hint)
|
||||
}
|
||||
}
|
||||
|
||||
// KnownLabels returns a copy of a map with known labels
|
||||
|
||||
@@ -180,11 +180,11 @@ func WithHealthchecks(val map[string][]string) Option {
|
||||
healthchecks := map[string]HealthCheck{}
|
||||
for name, filterExpressions := range val {
|
||||
hc := HealthCheck{
|
||||
filters: []filters.FilterT{},
|
||||
filters: []filters.Filter{},
|
||||
}
|
||||
for _, filterExpression := range filterExpressions {
|
||||
f := filters.NewFilter(filterExpression)
|
||||
if f == nil || !f.GetIsValid() {
|
||||
if f == nil || !f.Valid() {
|
||||
return fmt.Errorf("%q is not a valid filter", filterExpression)
|
||||
}
|
||||
hc.filters = append(hc.filters, f)
|
||||
|
||||
+55
-66
@@ -9,70 +9,70 @@ import (
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
// FilterT provides methods for interacting with alert filters
|
||||
type FilterT interface {
|
||||
init(name string, matcher *matcherT, rawText string, isValid bool, value string)
|
||||
// Filter provides methods for interacting with alert filters.
|
||||
type Filter interface {
|
||||
Match(alert *models.Alert, matches int) bool
|
||||
MatchAlertmanager(am *models.AlertmanagerInstance) bool
|
||||
GetRawText() string
|
||||
GetHits() int
|
||||
GetIsValid() bool
|
||||
GetName() string
|
||||
GetMatcher() string
|
||||
GetValue() string
|
||||
GetIsAlertmanagerFilter() bool
|
||||
RawText() string
|
||||
Hits() int
|
||||
Valid() bool
|
||||
Name() string
|
||||
MatcherOperation() string
|
||||
Value() string
|
||||
IsAlertmanagerFilter() bool
|
||||
}
|
||||
|
||||
type alertFilter struct {
|
||||
FilterT
|
||||
Matcher matcherT
|
||||
Matched string
|
||||
RawText string
|
||||
Hits int
|
||||
IsValid bool
|
||||
IsAlertmanagerFilter bool
|
||||
// filterBase holds common state shared by all filter implementations.
|
||||
// Concrete filters embed this struct and override Match and optionally
|
||||
// MatchAlertmanager and Value.
|
||||
type filterBase struct {
|
||||
matcher Matcher
|
||||
name string
|
||||
rawText string
|
||||
value string
|
||||
hits int
|
||||
isValid bool
|
||||
isAlertmanagerFilter bool
|
||||
}
|
||||
|
||||
func (filter *alertFilter) GetRawText() string {
|
||||
return filter.RawText
|
||||
}
|
||||
func (f *filterBase) RawText() string { return f.rawText }
|
||||
func (f *filterBase) Hits() int { return f.hits }
|
||||
func (f *filterBase) Valid() bool { return f.isValid }
|
||||
func (f *filterBase) Name() string { return f.name }
|
||||
func (f *filterBase) Value() string { return f.value }
|
||||
func (f *filterBase) IsAlertmanagerFilter() bool { return f.isAlertmanagerFilter }
|
||||
func (f *filterBase) MatcherOperation() string { return f.matcher.Operator }
|
||||
|
||||
func (filter *alertFilter) GetHits() int {
|
||||
return filter.Hits
|
||||
}
|
||||
func (f *filterBase) Match(*models.Alert, int) bool { return false }
|
||||
func (f *filterBase) MatchAlertmanager(*models.AlertmanagerInstance) bool { return false }
|
||||
|
||||
func (filter *alertFilter) GetIsValid() bool {
|
||||
return filter.IsValid
|
||||
}
|
||||
|
||||
func (filter *alertFilter) GetName() string {
|
||||
return filter.Matched
|
||||
}
|
||||
|
||||
func (filter *alertFilter) GetMatcher() string {
|
||||
if filter.Matcher == nil {
|
||||
return ""
|
||||
// buildMatcher creates a Matcher for the given operator and value.
|
||||
// For regex operators it compiles the pattern; for others it delegates to newMatcher.
|
||||
func buildMatcher(operator, value string) (Matcher, bool) {
|
||||
switch operator {
|
||||
case regexpOperator, negativeRegexOperator:
|
||||
m, err := newRegexpMatcher(operator, value)
|
||||
if err != nil {
|
||||
return Matcher{}, false
|
||||
}
|
||||
return m, true
|
||||
default:
|
||||
m, err := newMatcher(operator)
|
||||
if err != nil {
|
||||
return Matcher{}, false
|
||||
}
|
||||
return m, true
|
||||
}
|
||||
return filter.Matcher.GetOperator()
|
||||
}
|
||||
|
||||
func (filter *alertFilter) GetIsAlertmanagerFilter() bool {
|
||||
return filter.IsAlertmanagerFilter
|
||||
}
|
||||
|
||||
type newFilterFactory func() FilterT
|
||||
|
||||
// NewFilter creates new filter object from filter expression like "key=value"
|
||||
// expression will be parsed and best filter implementation and value matcher
|
||||
// will be selected
|
||||
func NewFilter(expression string) FilterT {
|
||||
// NewFilter creates a new filter from a filter expression like "key=value".
|
||||
// The expression is parsed and the best filter implementation and value
|
||||
// matcher are selected.
|
||||
func NewFilter(expression string) Filter {
|
||||
trimmed := strings.Trim(expression, " \t")
|
||||
|
||||
invalid := alwaysInvalidFilter{}
|
||||
invalid.init("", nil, trimmed, false, trimmed)
|
||||
|
||||
if trimmed == "" {
|
||||
return &invalid
|
||||
return &filterBase{rawText: trimmed}
|
||||
}
|
||||
|
||||
reExp := fmt.Sprintf("^(?P<matched>(%s))(?P<operator>(%s))(?P<value>(.*))", filterRegex, matcherRegex)
|
||||
@@ -90,33 +90,22 @@ func NewFilter(expression string) FilterT {
|
||||
value := result["value"]
|
||||
|
||||
if matched == "" && operator == "" && value == "" {
|
||||
// no "filter=" part, just the value, use fuzzy filter
|
||||
f := newFuzzyFilter()
|
||||
matcher, _ := newMatcher(regexpOperator)
|
||||
f.init("", &matcher, trimmed, true, trimmed)
|
||||
return f
|
||||
return newFuzzyFilter(trimmed)
|
||||
}
|
||||
|
||||
if value == "" {
|
||||
// there's no value, so it's always invalid
|
||||
return &invalid
|
||||
return &filterBase{rawText: trimmed}
|
||||
}
|
||||
|
||||
// we have "filter=" part, lookup filter that matches
|
||||
for _, fc := range AllFilters {
|
||||
f := fc.Factory()
|
||||
if !fc.LabelRe.MatchString(matched) {
|
||||
// filter name doesn't match, keep searching
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(fc.SupportedOperators, operator) {
|
||||
return &invalid
|
||||
return &filterBase{rawText: trimmed}
|
||||
}
|
||||
// we validate operator above, no need to re-check
|
||||
matcher, _ := newMatcher(operator)
|
||||
f.init(matched, &matcher, trimmed, true, value)
|
||||
return f
|
||||
return fc.Factory(matched, operator, trimmed, value)
|
||||
}
|
||||
|
||||
return &invalid
|
||||
return &filterBase{rawText: trimmed}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -9,55 +10,51 @@ import (
|
||||
)
|
||||
|
||||
type ageFilter struct {
|
||||
alertFilter
|
||||
value time.Duration
|
||||
filterBase
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func (filter *ageFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
|
||||
dur, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
filter.IsValid = false
|
||||
}
|
||||
if dur > 0 {
|
||||
filter.value = -dur
|
||||
} else {
|
||||
filter.value = dur
|
||||
}
|
||||
}
|
||||
|
||||
func (filter *ageFilter) GetValue() string {
|
||||
return fmt.Sprintf("%v", filter.value)
|
||||
func (filter *ageFilter) Value() string {
|
||||
return fmt.Sprintf("%v", filter.duration)
|
||||
}
|
||||
|
||||
func (filter *ageFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
ts := time.Now().Add(filter.value)
|
||||
isMatch := filter.Matcher.Compare(int(ts.Unix()), int(alert.StartsAt.Unix()))
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
ts := time.Now().Add(filter.duration)
|
||||
isMatch := filter.matcher.Compare(strconv.Itoa(int(ts.Unix())), strconv.Itoa(int(alert.StartsAt.Unix())))
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func (filter *ageFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
ts := time.Now().Add(filter.value)
|
||||
return filter.Matcher.Compare(int(ts.Unix()), int(am.StartsAt.Unix()))
|
||||
ts := time.Now().Add(filter.duration)
|
||||
return filter.matcher.Compare(strconv.Itoa(int(ts.Unix())), strconv.Itoa(int(am.StartsAt.Unix())))
|
||||
}
|
||||
|
||||
func newAgeFilter() FilterT {
|
||||
f := ageFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func newAgeFilter(name, operator, rawText, value string) Filter {
|
||||
dur, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
if dur > 0 {
|
||||
dur = -dur
|
||||
}
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &ageFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
duration: dur,
|
||||
}
|
||||
}
|
||||
|
||||
func ageAutocomplete(name string, operators []string, _ []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,56 +1,47 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type alertmanagerInstanceFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *alertmanagerInstanceFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *alertmanagerInstanceFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *alertmanagerInstanceFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.Matcher.Compare(am.Name, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.matcher.Compare(am.Name, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func (filter *alertmanagerInstanceFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
return filter.Matcher.Compare(am.Name, filter.value)
|
||||
return filter.matcher.Compare(am.Name, filter.value)
|
||||
}
|
||||
|
||||
func newAlertmanagerInstanceFilter() FilterT {
|
||||
f := alertmanagerInstanceFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func newAlertmanagerInstanceFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &alertmanagerInstanceFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func alertmanagerInstanceAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,56 +1,47 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type alertmanagerClusterFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *alertmanagerClusterFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *alertmanagerClusterFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *alertmanagerClusterFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.Matcher.Compare(am.Cluster, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.matcher.Compare(am.Cluster, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func (filter *alertmanagerClusterFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
return filter.Matcher.Compare(am.Cluster, filter.value)
|
||||
return filter.matcher.Compare(am.Cluster, filter.value)
|
||||
}
|
||||
|
||||
func newAlertmanagerClusterFilter() FilterT {
|
||||
f := alertmanagerClusterFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func newAlertmanagerClusterFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &alertmanagerClusterFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func alertmanagerClusterAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,54 +1,43 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type fingerprintFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *fingerprintFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *fingerprintFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *fingerprintFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
m := filter.Matcher.Compare(am.Fingerprint, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.matcher.Compare(am.Fingerprint, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func (filter *fingerprintFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
return filter.Matcher.Compare(am.Fingerprint, filter.value)
|
||||
return filter.matcher.Compare(am.Fingerprint, filter.value)
|
||||
}
|
||||
|
||||
func newFingerprintFilter() FilterT {
|
||||
f := fingerprintFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func newFingerprintFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &fingerprintFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,67 +10,59 @@ import (
|
||||
)
|
||||
|
||||
type fuzzyFilter struct {
|
||||
value *regexp.Regexp
|
||||
alertFilter
|
||||
filterBase
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func (filter *fuzzyFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
var err error
|
||||
if filter.value, err = regexp.Compile("(?i)" + value); err != nil {
|
||||
filter.IsValid = false
|
||||
}
|
||||
}
|
||||
|
||||
func (filter *fuzzyFilter) GetValue() string {
|
||||
return fmt.Sprintf("%v", filter.value)
|
||||
func (filter *fuzzyFilter) Value() string {
|
||||
return fmt.Sprintf("%v", filter.re)
|
||||
}
|
||||
|
||||
func (filter *fuzzyFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
for _, val := range alert.Annotations {
|
||||
if filter.value.MatchString(val.Value) {
|
||||
filter.Hits++
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
var labelMatch bool
|
||||
alert.Labels.Range(func(l labels.Label) {
|
||||
if filter.value.MatchString(l.Value) {
|
||||
labelMatch = true
|
||||
}
|
||||
})
|
||||
if labelMatch {
|
||||
filter.Hits++
|
||||
for _, val := range alert.Annotations {
|
||||
if filter.re.MatchString(val.Value) {
|
||||
filter.hits++
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
for _, silenceID := range alert.SilencedBy {
|
||||
for _, am := range alert.Alertmanager {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
if filter.value.MatchString(silence.Comment) {
|
||||
filter.Hits++
|
||||
return true
|
||||
}
|
||||
var labelMatch bool
|
||||
alert.Labels.Range(func(l labels.Label) {
|
||||
if filter.re.MatchString(l.Value) {
|
||||
labelMatch = true
|
||||
}
|
||||
})
|
||||
if labelMatch {
|
||||
filter.hits++
|
||||
return true
|
||||
}
|
||||
|
||||
for _, silenceID := range alert.SilencedBy {
|
||||
for _, am := range alert.Alertmanager {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
if filter.re.MatchString(silence.Comment) {
|
||||
filter.hits++
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func newFuzzyFilter() FilterT {
|
||||
f := fuzzyFilter{}
|
||||
return &f
|
||||
func newFuzzyFilter(rawText string) Filter {
|
||||
re, err := regexp.Compile("(?i)" + rawText)
|
||||
if err != nil {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &fuzzyFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: Matcher{Operator: regexpOperator},
|
||||
rawText: rawText,
|
||||
isValid: true,
|
||||
},
|
||||
re: re,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -14,55 +13,55 @@ const (
|
||||
)
|
||||
|
||||
type inhibitedFilter struct {
|
||||
alertFilter
|
||||
value bool
|
||||
filterBase
|
||||
boolValue bool
|
||||
}
|
||||
|
||||
func (filter *inhibitedFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
switch value {
|
||||
case trueValue:
|
||||
filter.value = true
|
||||
case falseValue:
|
||||
filter.value = false
|
||||
default:
|
||||
filter.IsValid = false
|
||||
}
|
||||
}
|
||||
|
||||
func (filter *inhibitedFilter) GetValue() string {
|
||||
return strconv.FormatBool(filter.value)
|
||||
func (filter *inhibitedFilter) Value() string {
|
||||
return strconv.FormatBool(filter.boolValue)
|
||||
}
|
||||
|
||||
func (filter *inhibitedFilter) Match(alert *models.Alert, _ int) (isMatch bool) {
|
||||
if filter.IsValid {
|
||||
for _, am := range alert.Alertmanager {
|
||||
if len(am.InhibitedBy) > 0 == filter.value {
|
||||
isMatch = true
|
||||
}
|
||||
for _, am := range alert.Alertmanager {
|
||||
if len(am.InhibitedBy) > 0 == filter.boolValue {
|
||||
isMatch = true
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func (filter *inhibitedFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
return len(am.InhibitedBy) > 0 == filter.value
|
||||
return len(am.InhibitedBy) > 0 == filter.boolValue
|
||||
}
|
||||
|
||||
func newInhibitedFilter() FilterT {
|
||||
f := inhibitedFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func newInhibitedFilter(name, operator, rawText, value string) Filter {
|
||||
var bv bool
|
||||
switch value {
|
||||
case trueValue:
|
||||
bv = true
|
||||
case falseValue:
|
||||
bv = false
|
||||
default:
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &inhibitedFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
boolValue: bv,
|
||||
}
|
||||
}
|
||||
|
||||
func inhibitedAutocomplete(name string, _ []string, _ []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,66 +1,54 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type inhibitedByFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *inhibitedByFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *inhibitedByFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *inhibitedByFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.InhibitedBy {
|
||||
m := filter.Matcher.Compare(silenceID, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.InhibitedBy {
|
||||
if filter.matcher.Compare(silenceID, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
}
|
||||
|
||||
func (filter *inhibitedByFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
var isMatch bool
|
||||
for _, silenceID := range am.InhibitedBy {
|
||||
m := filter.Matcher.Compare(silenceID, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func newInhibitedByFilter() FilterT {
|
||||
f := inhibitedByFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func (filter *inhibitedByFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
for _, silenceID := range am.InhibitedBy {
|
||||
if filter.matcher.Compare(silenceID, filter.value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newInhibitedByFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &inhibitedByFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func inhibitedByAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package filters
|
||||
|
||||
type alwaysInvalidFilter struct {
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *alwaysInvalidFilter) init(name string, _ *matcherT, rawText string, _ bool, _ string) {
|
||||
filter.Matched = name
|
||||
filter.RawText = rawText
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/prometheus/model/labels"
|
||||
@@ -26,39 +25,31 @@ func isDigits(s string) bool {
|
||||
}
|
||||
|
||||
type labelFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *labelFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *labelFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *labelFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
isMatch := filter.Matcher.Compare(alert.Labels.Get(filter.Matched), filter.value)
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
isMatch := filter.matcher.Compare(alert.Labels.Get(filter.name), filter.value)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func newLabelFilter() FilterT {
|
||||
f := labelFilter{}
|
||||
return &f
|
||||
func newLabelFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &labelFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func LabelAutocomplete(labelPairs [][]labels.Label, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -9,46 +9,41 @@ import (
|
||||
)
|
||||
|
||||
type limitFilter struct {
|
||||
alertFilter
|
||||
value int
|
||||
filterBase
|
||||
limit int
|
||||
}
|
||||
|
||||
func (filter *limitFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
if filter.IsValid {
|
||||
val, err := strconv.Atoi(value)
|
||||
if err != nil || val < 1 {
|
||||
filter.IsValid = false
|
||||
} else {
|
||||
filter.value = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (filter *limitFilter) GetValue() string {
|
||||
return strconv.Itoa(filter.value)
|
||||
func (filter *limitFilter) Value() string {
|
||||
return strconv.Itoa(filter.limit)
|
||||
}
|
||||
|
||||
func (filter *limitFilter) Match(_ *models.Alert, matches int) bool {
|
||||
if filter.IsValid {
|
||||
if matches < filter.value {
|
||||
return true
|
||||
}
|
||||
filter.Hits++
|
||||
return false
|
||||
if matches < filter.limit {
|
||||
return true
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
filter.hits++
|
||||
return false
|
||||
}
|
||||
|
||||
func newLimitFilter() FilterT {
|
||||
f := limitFilter{}
|
||||
return &f
|
||||
func newLimitFilter(name, operator, rawText, value string) Filter {
|
||||
val, err := strconv.Atoi(value)
|
||||
if err != nil || val < 1 {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &limitFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
},
|
||||
limit: val,
|
||||
}
|
||||
}
|
||||
|
||||
func limitAutocomplete(name string, operators []string, _ []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,46 +1,37 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type receiverFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *receiverFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *receiverFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *receiverFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
isMatch := filter.Matcher.Compare(alert.Receiver, filter.value)
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
isMatch := filter.matcher.Compare(alert.Receiver, filter.value)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func newreceiverFilter() FilterT {
|
||||
f := receiverFilter{}
|
||||
return &f
|
||||
func newReceiverFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &receiverFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func receiverAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,72 +1,60 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type silenceAuthorFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *silenceAuthorFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *silenceAuthorFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *silenceAuthorFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
m := filter.Matcher.Compare(silence.CreatedBy, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
if filter.matcher.Compare(silence.CreatedBy, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
}
|
||||
|
||||
func (filter *silenceAuthorFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
var isMatch bool
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
m := filter.Matcher.Compare(silence.CreatedBy, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
}
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func newSilenceAuthorFilter() FilterT {
|
||||
f := silenceAuthorFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func (filter *silenceAuthorFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
if filter.matcher.Compare(silence.CreatedBy, filter.value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newSilenceAuthorFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &silenceAuthorFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func silenceAuthorAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,72 +1,60 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type silenceTicketFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *silenceTicketFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *silenceTicketFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *silenceTicketFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
m := filter.Matcher.Compare(silence.TicketID, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
if filter.matcher.Compare(silence.TicketID, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
}
|
||||
|
||||
func (filter *silenceTicketFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
var isMatch bool
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
m := filter.Matcher.Compare(silence.TicketID, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
}
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func newSilenceTicketFilter() FilterT {
|
||||
f := silenceTicketFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func (filter *silenceTicketFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
silence, found := am.Silences[silenceID]
|
||||
if found {
|
||||
if filter.matcher.Compare(silence.TicketID, filter.value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newSilenceTicketFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &silenceTicketFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func silenceTicketIDAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,66 +1,54 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type silenceIDFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *silenceIDFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
}
|
||||
|
||||
func (filter *silenceIDFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *silenceIDFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
m := filter.Matcher.Compare(silenceID, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
if filter.matcher.Compare(silenceID, filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
}
|
||||
|
||||
func (filter *silenceIDFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
var isMatch bool
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
m := filter.Matcher.Compare(silenceID, filter.value)
|
||||
if m {
|
||||
isMatch = m
|
||||
}
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func newsilenceIDFilter() FilterT {
|
||||
f := silenceIDFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func (filter *silenceIDFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
for _, silenceID := range am.SilencedBy {
|
||||
if filter.matcher.Compare(silenceID, filter.value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func newSilenceIDFilter(name, operator, rawText, value string) Filter {
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &silenceIDFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func silenceIDAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1,59 +1,50 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
type stateFilter struct {
|
||||
value string
|
||||
alertFilter
|
||||
}
|
||||
|
||||
func (filter *stateFilter) init(name string, matcher *matcherT, rawText string, isValid bool, value string) {
|
||||
filter.Matched = name
|
||||
if matcher != nil {
|
||||
filter.Matcher = *matcher
|
||||
}
|
||||
filter.RawText = rawText
|
||||
filter.IsValid = isValid
|
||||
filter.value = value
|
||||
if _, ok := models.AlertStateFromString(value); !ok {
|
||||
filter.IsValid = false
|
||||
}
|
||||
}
|
||||
|
||||
func (filter *stateFilter) GetValue() string {
|
||||
return filter.value
|
||||
filterBase
|
||||
}
|
||||
|
||||
func (filter *stateFilter) Match(alert *models.Alert, _ int) bool {
|
||||
if filter.IsValid {
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.Matcher.Compare(am.State.String(), filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
var isMatch bool
|
||||
for _, am := range alert.Alertmanager {
|
||||
if filter.matcher.Compare(am.State.String(), filter.value) {
|
||||
isMatch = true
|
||||
}
|
||||
if isMatch {
|
||||
filter.Hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
e := fmt.Sprintf("Match() called on invalid filter %#v", filter)
|
||||
panic(e)
|
||||
if isMatch {
|
||||
filter.hits++
|
||||
}
|
||||
return isMatch
|
||||
}
|
||||
|
||||
func (filter *stateFilter) MatchAlertmanager(am *models.AlertmanagerInstance) bool {
|
||||
return filter.Matcher.Compare(am.State.String(), filter.value)
|
||||
return filter.matcher.Compare(am.State.String(), filter.value)
|
||||
}
|
||||
|
||||
func newStateFilter() FilterT {
|
||||
f := stateFilter{}
|
||||
f.IsAlertmanagerFilter = true
|
||||
return &f
|
||||
func newStateFilter(name, operator, rawText, value string) Filter {
|
||||
if _, ok := models.AlertStateFromString(value); !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
m, ok := buildMatcher(operator, value)
|
||||
if !ok {
|
||||
return &filterBase{rawText: rawText}
|
||||
}
|
||||
return &stateFilter{
|
||||
filterBase: filterBase{
|
||||
matcher: m,
|
||||
name: name,
|
||||
rawText: rawText,
|
||||
value: value,
|
||||
isValid: true,
|
||||
isAlertmanagerFilter: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func stateAutocomplete(name string, operators []string, alerts []models.Alert, dst map[string]models.Autocomplete) {
|
||||
|
||||
@@ -1063,18 +1063,18 @@ func TestFilters(t *testing.T) {
|
||||
if f == nil {
|
||||
t.Errorf("[%s] No filter found", ft.Expression)
|
||||
}
|
||||
if f.GetHits() != 0 {
|
||||
t.Errorf("[%s] Hits = %#v after init(), expected 0", ft.Expression, f.GetHits())
|
||||
if f.Hits() != 0 {
|
||||
t.Errorf("[%s] Hits = %#v after init(), expected 0", ft.Expression, f.Hits())
|
||||
}
|
||||
if f.GetIsValid() != ft.IsValid {
|
||||
t.Errorf("[%s] GetIsValid() returned %#v while %#v was expected", ft.Expression, f.GetIsValid(), ft.IsValid)
|
||||
if f.Valid() != ft.IsValid {
|
||||
t.Errorf("[%s] Valid() returned %#v while %#v was expected", ft.Expression, f.Valid(), ft.IsValid)
|
||||
}
|
||||
if f.GetIsValid() {
|
||||
if f.Valid() {
|
||||
isAlertmanagerFilter := slices.Contains(
|
||||
[]string{"@age", "@alertmanager", "@cluster", "@inhibited", "@inhibited_by", "@state", "@silenced_by", "@silence_ticket", "@silence_author", "@fingerprint"},
|
||||
f.GetName())
|
||||
if isAlertmanagerFilter != f.GetIsAlertmanagerFilter() {
|
||||
t.Errorf("[%s] GetIsAlertmanagerFilter() returned %#v while %#v was expected", ft.Expression, f.GetIsAlertmanagerFilter(), isAlertmanagerFilter)
|
||||
f.Name())
|
||||
if isAlertmanagerFilter != f.IsAlertmanagerFilter() {
|
||||
t.Errorf("[%s] IsAlertmanagerFilter() returned %#v while %#v was expected", ft.Expression, f.IsAlertmanagerFilter(), isAlertmanagerFilter)
|
||||
}
|
||||
|
||||
m := f.Match(&alert, 0)
|
||||
@@ -1083,17 +1083,17 @@ func TestFilters(t *testing.T) {
|
||||
s, _ := json.Marshal(ft.Silence)
|
||||
t.Errorf("[%s] Match() returned %#v while %#v was expected\nalert used: %s\nsilence used: %s", ft.Expression, m, ft.IsMatch, j, s)
|
||||
}
|
||||
if ft.IsMatch && f.GetHits() != 1 {
|
||||
t.Errorf("[%s] GetHits() returned %#v after match, expected 1", ft.Expression, f.GetHits())
|
||||
if ft.IsMatch && f.Hits() != 1 {
|
||||
t.Errorf("[%s] Hits() returned %#v after match, expected 1", ft.Expression, f.Hits())
|
||||
}
|
||||
if !ft.IsMatch && f.GetHits() != 0 {
|
||||
t.Errorf("[%s] GetHits() returned %#v after non-match, expected 0", ft.Expression, f.GetHits())
|
||||
if !ft.IsMatch && f.Hits() != 0 {
|
||||
t.Errorf("[%s] Hits() returned %#v after non-match, expected 0", ft.Expression, f.Hits())
|
||||
}
|
||||
if f.GetRawText() != strings.Trim(ft.Expression, " \t") {
|
||||
t.Errorf("[%s] GetRawText() returned %#v != %s passed as the expression", ft.Expression, f.GetRawText(), ft.Expression)
|
||||
if f.RawText() != strings.Trim(ft.Expression, " \t") {
|
||||
t.Errorf("[%s] RawText() returned %#v != %s passed as the expression", ft.Expression, f.RawText(), ft.Expression)
|
||||
}
|
||||
|
||||
if m && f.GetIsAlertmanagerFilter() {
|
||||
if m && f.IsAlertmanagerFilter() {
|
||||
for _, am := range alert.Alertmanager {
|
||||
m := f.MatchAlertmanager(&am)
|
||||
if m != ft.IsAlertmanagerMatch {
|
||||
@@ -1105,19 +1105,11 @@ func TestFilters(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !f.GetIsValid() {
|
||||
func() {
|
||||
didPanic := false
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
didPanic = true
|
||||
}
|
||||
}()
|
||||
f.Match(&alert, 0)
|
||||
if !didPanic {
|
||||
t.Errorf("[%s] Match() on invalid filter didn't cause panic", ft.Expression)
|
||||
}
|
||||
}()
|
||||
if !f.Valid() {
|
||||
m := f.Match(&alert, 0)
|
||||
if m {
|
||||
t.Errorf("[%s] Match() on invalid filter returned true, expected false", ft.Expression)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1181,13 +1173,13 @@ func TestLimitFilter(t *testing.T) {
|
||||
if f == nil {
|
||||
t.Errorf("[%s] No filter found", ft.Expression)
|
||||
}
|
||||
if f.GetHits() != 0 {
|
||||
t.Errorf("[%s] Hits = %#v after init(), expected 0", ft.Expression, f.GetHits())
|
||||
if f.Hits() != 0 {
|
||||
t.Errorf("[%s] Hits = %#v after init(), expected 0", ft.Expression, f.Hits())
|
||||
}
|
||||
if f.GetIsValid() != ft.IsValid {
|
||||
t.Errorf("[%s] GetIsValid() returned %#v while %#v was expected", ft.Expression, f.GetIsValid(), ft.IsValid)
|
||||
if f.Valid() != ft.IsValid {
|
||||
t.Errorf("[%s] Valid() returned %#v while %#v was expected", ft.Expression, f.Valid(), ft.IsValid)
|
||||
}
|
||||
if f.GetIsValid() {
|
||||
if f.Valid() {
|
||||
alert := models.Alert{}
|
||||
var index int
|
||||
for _, isMatch := range ft.IsMatch {
|
||||
@@ -1195,28 +1187,20 @@ func TestLimitFilter(t *testing.T) {
|
||||
if m != isMatch {
|
||||
t.Errorf("[%s] Match() returned %#v while %#v was expected, index %d", ft.Expression, m, isMatch, index)
|
||||
}
|
||||
if f.GetRawText() != ft.Expression {
|
||||
t.Errorf("[%s] GetRawText() returned %#v != %s passed as the expression", ft.Expression, f.GetRawText(), ft.Expression)
|
||||
if f.RawText() != ft.Expression {
|
||||
t.Errorf("[%s] RawText() returned %#v != %s passed as the expression", ft.Expression, f.RawText(), ft.Expression)
|
||||
}
|
||||
index++
|
||||
}
|
||||
if f.GetHits() != ft.Hits {
|
||||
t.Errorf("[%s] GetHits() returned %#v hits, expected %d", ft.Expression, f.GetHits(), ft.Hits)
|
||||
if f.Hits() != ft.Hits {
|
||||
t.Errorf("[%s] Hits() returned %#v hits, expected %d", ft.Expression, f.Hits(), ft.Hits)
|
||||
}
|
||||
} else {
|
||||
func() {
|
||||
didPanic := false
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
didPanic = true
|
||||
}
|
||||
}()
|
||||
alert := models.Alert{}
|
||||
f.Match(&alert, 0)
|
||||
if !didPanic {
|
||||
t.Errorf("[%s] Match() on invalid filter didn't cause panic", ft.Expression)
|
||||
}
|
||||
}()
|
||||
alert := models.Alert{}
|
||||
m := f.Match(&alert, 0)
|
||||
if m {
|
||||
t.Errorf("[%s] Match() on invalid filter returned true, expected false", ft.Expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,42 +6,32 @@ import (
|
||||
"github.com/prymitive/karma/internal/models"
|
||||
)
|
||||
|
||||
func TestMatchOnInvalidFilter(t *testing.T) {
|
||||
for _, ft := range AllFilters {
|
||||
f := ft.Factory()
|
||||
m, _ := newMatcher(f.GetMatcher())
|
||||
f.init(f.GetName(), &m, f.GetRawText(), false, f.GetValue())
|
||||
func() {
|
||||
didPanic := false
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
didPanic = true
|
||||
}
|
||||
}()
|
||||
alert := models.Alert{}
|
||||
f.Match(&alert, 0)
|
||||
if !didPanic {
|
||||
t.Errorf("[%s] Match() on invalid filter didn't cause panic", ft.Label)
|
||||
}
|
||||
}()
|
||||
// verifies that an invalid filter (Valid()==false) returns false from Match
|
||||
// instead of panicking
|
||||
func TestMatchOnInvalidFilterReturnsFalse(t *testing.T) {
|
||||
f := &filterBase{rawText: "invalid", isValid: false}
|
||||
alert := models.Alert{}
|
||||
if f.Match(&alert, 0) {
|
||||
t.Error("Match() on invalid filterBase returned true, expected false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatcherNil(t *testing.T) {
|
||||
f := alertFilter{}
|
||||
m := f.GetMatcher()
|
||||
if m != "" {
|
||||
t.Errorf("Got %q from empty filter GetMatcher()", m)
|
||||
// verifies that MatcherOperation returns empty string when no matcher is set
|
||||
func TestMatcherOperationEmpty(t *testing.T) {
|
||||
f := &filterBase{}
|
||||
if op := f.MatcherOperation(); op != "" {
|
||||
t.Errorf("MatcherOperation() = %q, want empty string", op)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatcherNotNil(t *testing.T) {
|
||||
matcher, _ := newMatcher("=")
|
||||
f := alertFilter{
|
||||
Matcher: matcher,
|
||||
// verifies that MatcherOperation returns the operator when a matcher is set
|
||||
func TestMatcherOperationSet(t *testing.T) {
|
||||
m, err := newMatcher("=")
|
||||
if err != nil {
|
||||
t.Fatalf("newMatcher(\"=\") returned error: %v", err)
|
||||
}
|
||||
m := f.GetMatcher()
|
||||
if m != "=" {
|
||||
t.Errorf("Got %q from empty filter GetMatcher()", m)
|
||||
f := &filterBase{matcher: m}
|
||||
if op := f.MatcherOperation(); op != "=" {
|
||||
t.Errorf("MatcherOperation() = %q, want \"=\"", op)
|
||||
}
|
||||
}
|
||||
|
||||
+50
-120
@@ -4,156 +4,86 @@ import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
)
|
||||
|
||||
var matchCache, _ = lru.New[string, *regexp.Regexp](1000)
|
||||
|
||||
type matcherT interface {
|
||||
setOperator(operator string)
|
||||
GetOperator() string
|
||||
Compare(valA, valB any) bool
|
||||
}
|
||||
|
||||
type abstractMatcher struct {
|
||||
matcherT
|
||||
// Matcher holds the comparison operator and its implementation.
|
||||
type Matcher struct {
|
||||
Operator string
|
||||
Compare func(a, b string) bool
|
||||
}
|
||||
|
||||
func (matcher *abstractMatcher) GetOperator() string {
|
||||
return matcher.Operator
|
||||
}
|
||||
func compareEqual(a, b string) bool { return a == b }
|
||||
func compareNotEqual(a, b string) bool { return a != b }
|
||||
|
||||
type equalMatcher struct {
|
||||
abstractMatcher
|
||||
}
|
||||
|
||||
func (matcher *equalMatcher) Compare(valA, valB any) bool {
|
||||
return valA == valB
|
||||
}
|
||||
|
||||
type notEqualMatcher struct {
|
||||
abstractMatcher
|
||||
}
|
||||
|
||||
func (matcher *notEqualMatcher) Compare(valA, valB any) bool {
|
||||
return valA != valB
|
||||
}
|
||||
|
||||
type moreThanMatcher struct {
|
||||
abstractMatcher
|
||||
}
|
||||
|
||||
func (matcher *moreThanMatcher) Compare(valA, valB any) bool {
|
||||
if valA == nil || valA == "" || valB == nil || valB == "" {
|
||||
func compareMoreThan(a, b string) bool {
|
||||
if a == "" || b == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
intA, okA := castToInt(valA)
|
||||
intB, okB := castToInt(valB)
|
||||
|
||||
intA, okA := tryAtoi(a)
|
||||
intB, okB := tryAtoi(b)
|
||||
if okA && okB {
|
||||
return intA > intB
|
||||
}
|
||||
|
||||
return castToString(valA) > castToString(valB)
|
||||
return a > b
|
||||
}
|
||||
|
||||
type lessThanMatcher struct {
|
||||
abstractMatcher
|
||||
}
|
||||
|
||||
func (matcher *lessThanMatcher) Compare(valA, valB any) bool {
|
||||
if valA == nil || valA == "" || valB == nil || valB == "" {
|
||||
func compareLessThan(a, b string) bool {
|
||||
if a == "" || b == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
intA, okA := castToInt(valA)
|
||||
intB, okB := castToInt(valB)
|
||||
|
||||
intA, okA := tryAtoi(a)
|
||||
intB, okB := tryAtoi(b)
|
||||
if okA && okB {
|
||||
return intA < intB
|
||||
}
|
||||
|
||||
return castToString(valA) < castToString(valB)
|
||||
return a < b
|
||||
}
|
||||
|
||||
type regexpMatcher struct {
|
||||
abstractMatcher
|
||||
}
|
||||
|
||||
func (matcher *regexpMatcher) Compare(valA, valB any) bool {
|
||||
var (
|
||||
re *regexp.Regexp
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
switch v := valB.(type) {
|
||||
case *regexp.Regexp:
|
||||
re = v
|
||||
case string:
|
||||
re, ok = matchCache.Get(v)
|
||||
if !ok {
|
||||
if re, err = regexp.Compile("(?i)" + v); err != nil {
|
||||
return false
|
||||
}
|
||||
matchCache.Add(v, re)
|
||||
}
|
||||
func compareRegexp(re *regexp.Regexp) func(a, _ string) bool {
|
||||
return func(a, _ string) bool {
|
||||
return re.MatchString(a)
|
||||
}
|
||||
return re.MatchString(castToString(valA))
|
||||
}
|
||||
|
||||
type negativeRegexMatcher struct {
|
||||
abstractMatcher
|
||||
}
|
||||
|
||||
func (matcher *negativeRegexMatcher) Compare(valA, valB any) bool {
|
||||
var (
|
||||
re *regexp.Regexp
|
||||
err error
|
||||
ok bool
|
||||
)
|
||||
switch v := valB.(type) {
|
||||
case *regexp.Regexp:
|
||||
re = v
|
||||
case string:
|
||||
re, ok = matchCache.Get(v)
|
||||
if !ok {
|
||||
if re, err = regexp.Compile("(?i)" + v); err != nil {
|
||||
return false
|
||||
}
|
||||
matchCache.Add(v, re)
|
||||
}
|
||||
func compareNegativeRegexp(re *regexp.Regexp) func(a, _ string) bool {
|
||||
return func(a, _ string) bool {
|
||||
return !re.MatchString(a)
|
||||
}
|
||||
return !re.MatchString(castToString(valA))
|
||||
}
|
||||
|
||||
func newMatcher(matchType string) (matcherT, error) {
|
||||
if m, found := matcherConfig[matchType]; found {
|
||||
return m, nil
|
||||
func newMatcher(operator string) (Matcher, error) {
|
||||
switch operator {
|
||||
case equalOperator:
|
||||
return Matcher{Operator: operator, Compare: compareEqual}, nil
|
||||
case notEqualOperator:
|
||||
return Matcher{Operator: operator, Compare: compareNotEqual}, nil
|
||||
case moreThanOperator:
|
||||
return Matcher{Operator: operator, Compare: compareMoreThan}, nil
|
||||
case lessThanOperator:
|
||||
return Matcher{Operator: operator, Compare: compareLessThan}, nil
|
||||
case regexpOperator, negativeRegexOperator:
|
||||
// regex matchers need the pattern to compile, which is not known here.
|
||||
// Return a placeholder; the caller must call newRegexpMatcher instead.
|
||||
return Matcher{Operator: operator}, nil
|
||||
}
|
||||
e := matchType + " not matched with any know match type"
|
||||
return nil, errors.New(e)
|
||||
return Matcher{}, errors.New(operator + " not matched with any known match type")
|
||||
}
|
||||
|
||||
func castToInt(val any) (int, bool) {
|
||||
switch v := val.(type) {
|
||||
case int:
|
||||
// newRegexpMatcher compiles pattern once and returns a Matcher that uses it.
|
||||
func newRegexpMatcher(operator, pattern string) (Matcher, error) {
|
||||
re, err := regexp.Compile("(?i)" + pattern)
|
||||
if err != nil {
|
||||
return Matcher{}, err
|
||||
}
|
||||
if operator == negativeRegexOperator {
|
||||
return Matcher{Operator: operator, Compare: compareNegativeRegexp(re)}, nil
|
||||
}
|
||||
return Matcher{Operator: operator, Compare: compareRegexp(re)}, nil
|
||||
}
|
||||
|
||||
func tryAtoi(s string) (int, bool) {
|
||||
if v, err := strconv.Atoi(s); err == nil {
|
||||
return v, true
|
||||
case string:
|
||||
if atoiA, err := strconv.Atoi(v); err == nil {
|
||||
return atoiA, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func castToString(val any) string {
|
||||
switch v := val.(type) {
|
||||
case string:
|
||||
return v
|
||||
default:
|
||||
return val.(string)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,186 +1,151 @@
|
||||
package filters
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type matchTest struct {
|
||||
ValA any
|
||||
ValB any
|
||||
IsValid bool
|
||||
Expacted bool
|
||||
ValA string
|
||||
ValB string
|
||||
Expected bool
|
||||
}
|
||||
|
||||
func TestEqualMatcher(t *testing.T) {
|
||||
now := time.Now()
|
||||
tests := []matchTest{
|
||||
{"a", "a", true, true},
|
||||
{"abc", "abc", true, true},
|
||||
{123, 123, true, true},
|
||||
{now, now, true, true},
|
||||
{"1", 1, true, false},
|
||||
{"a", "ab", true, false},
|
||||
{12, 13, true, false},
|
||||
{&matchTest{}, &matchTest{}, true, false},
|
||||
// identical strings match
|
||||
{"a", "a", true},
|
||||
{"abc", "abc", true},
|
||||
// different strings do not match
|
||||
{"a", "ab", false},
|
||||
{"1", "2", false},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := equalMatcher{}
|
||||
if result := m.Compare(mt.ValA, mt.ValB); result != mt.Expacted {
|
||||
t.Errorf("EqualMatcher(%#v, %#v) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
if result := compareEqual(mt.ValA, mt.ValB); result != mt.Expected {
|
||||
t.Errorf("compareEqual(%q, %q) = %v, want %v", mt.ValA, mt.ValB, result, mt.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotEqualMatcher(t *testing.T) {
|
||||
now := time.Now()
|
||||
tests := []matchTest{
|
||||
{"a", "a", true, false},
|
||||
{"abc", "abc", true, false},
|
||||
{123, 123, true, false},
|
||||
{now, now, true, false},
|
||||
{"1", 1, true, true},
|
||||
{"a", "ab", true, true},
|
||||
{12, 13, true, true},
|
||||
// identical strings do not match
|
||||
{"a", "a", false},
|
||||
{"abc", "abc", false},
|
||||
// different strings match
|
||||
{"a", "ab", true},
|
||||
{"1", "2", true},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := notEqualMatcher{}
|
||||
if result := m.Compare(mt.ValA, mt.ValB); result != mt.Expacted {
|
||||
t.Errorf("NotEqualMatcher(%#v, %#v) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
if result := compareNotEqual(mt.ValA, mt.ValB); result != mt.Expected {
|
||||
t.Errorf("compareNotEqual(%q, %q) = %v, want %v", mt.ValA, mt.ValB, result, mt.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMoreThanMatcher(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
{10, 1, true, true},
|
||||
{"10", "1", true, true},
|
||||
{8, 8, true, false},
|
||||
{"8", "8", true, false},
|
||||
{4, 9, true, false},
|
||||
{"4", "9", true, false},
|
||||
{"b", "a", true, true},
|
||||
{"a", "a", true, false},
|
||||
{"a", "b", true, false},
|
||||
{"", "", true, false},
|
||||
// numeric comparison
|
||||
{"10", "1", true},
|
||||
{"8", "8", false},
|
||||
{"4", "9", false},
|
||||
// string comparison when non-numeric
|
||||
{"b", "a", true},
|
||||
{"a", "a", false},
|
||||
{"a", "b", false},
|
||||
// empty strings return false
|
||||
{"", "", false},
|
||||
{"1", "", false},
|
||||
{"", "1", false},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := moreThanMatcher{}
|
||||
if result := m.Compare(mt.ValA, mt.ValB); result != mt.Expacted {
|
||||
t.Errorf("MoreThanMatcher(%#v, %#v) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
if result := compareMoreThan(mt.ValA, mt.ValB); result != mt.Expected {
|
||||
t.Errorf("compareMoreThan(%q, %q) = %v, want %v", mt.ValA, mt.ValB, result, mt.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLessThanMatcher(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
{10, 1, true, false},
|
||||
{"10", "1", true, false},
|
||||
{8, 8, true, false},
|
||||
{"8", "8", true, false},
|
||||
{4, 9, true, true},
|
||||
{"4", "9", true, true},
|
||||
{"b", "a", true, false},
|
||||
{"a", "a", true, false},
|
||||
{"a", "b", true, true},
|
||||
{"", "", true, false},
|
||||
// numeric comparison
|
||||
{"10", "1", false},
|
||||
{"8", "8", false},
|
||||
{"4", "9", true},
|
||||
// string comparison when non-numeric
|
||||
{"b", "a", false},
|
||||
{"a", "a", false},
|
||||
{"a", "b", true},
|
||||
// empty strings return false
|
||||
{"", "", false},
|
||||
{"1", "", false},
|
||||
{"", "1", false},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := lessThanMatcher{}
|
||||
if result := m.Compare(mt.ValA, mt.ValB); result != mt.Expacted {
|
||||
t.Errorf("LessThanMatcher(%#v, %#v) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
if result := compareLessThan(mt.ValA, mt.ValB); result != mt.Expected {
|
||||
t.Errorf("compareLessThan(%q, %q) = %v, want %v", mt.ValA, mt.ValB, result, mt.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexpMatcher(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
{"abcdef", "^abc", true, true},
|
||||
{"abc", "^abc", true, true},
|
||||
{"xxabcxx", "abc", true, true},
|
||||
{"123", "123", true, true},
|
||||
{"5", "^[0-9]+", true, true},
|
||||
{"xb", "abc", true, false},
|
||||
{"13", "12", true, false},
|
||||
// matching patterns
|
||||
{"abcdef", "^abc", true},
|
||||
{"abc", "^abc", true},
|
||||
{"xxabcxx", "abc", true},
|
||||
{"123", "123", true},
|
||||
{"5", "^[0-9]+", true},
|
||||
// non-matching patterns
|
||||
{"xb", "abc", false},
|
||||
{"13", "12", false},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := regexpMatcher{}
|
||||
if result := m.Compare(mt.ValA, regexp.MustCompile(mt.ValB.(string))); result != mt.Expacted {
|
||||
t.Errorf("RegexpMatcher(%#v, %#v) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
m, err := newRegexpMatcher(regexpOperator, mt.ValB)
|
||||
if err != nil {
|
||||
t.Fatalf("newRegexpMatcher(%q, %q) error: %v", regexpOperator, mt.ValB, err)
|
||||
}
|
||||
if result := m.Compare(mt.ValA, ""); result != mt.Expected {
|
||||
t.Errorf("regexpMatcher(%q, %q) = %v, want %v", mt.ValA, mt.ValB, result, mt.Expected)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegativeRegexpMatcher(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
{"abcdef", "^abc", true, false},
|
||||
{"abc", "^abc", true, false},
|
||||
{"xxabcxx", "abc", true, false},
|
||||
{"123", "123", true, false},
|
||||
{"5", "^[0-9]+", true, false},
|
||||
{"xb", "abc", true, true},
|
||||
{"13", "12", true, true},
|
||||
// matching patterns return false (negated)
|
||||
{"abcdef", "^abc", false},
|
||||
{"abc", "^abc", false},
|
||||
{"xxabcxx", "abc", false},
|
||||
{"123", "123", false},
|
||||
{"5", "^[0-9]+", false},
|
||||
// non-matching patterns return true (negated)
|
||||
{"xb", "abc", true},
|
||||
{"13", "12", true},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := negativeRegexMatcher{}
|
||||
if result := m.Compare(mt.ValA, regexp.MustCompile(mt.ValB.(string))); result != mt.Expacted {
|
||||
t.Errorf("NegativeRegexMatcher(%#v, %#v) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
m, err := newRegexpMatcher(negativeRegexOperator, mt.ValB)
|
||||
if err != nil {
|
||||
t.Fatalf("newRegexpMatcher(%q, %q) error: %v", negativeRegexOperator, mt.ValB, err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexpMatcherWithStringValB(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
// verifies that passing a raw string pattern (not pre-compiled) compiles and matches
|
||||
{"abcdef", "^abc", true, true},
|
||||
// verifies cache hit on the same string pattern
|
||||
{"abcdef", "^abc", true, true},
|
||||
// verifies that a non-matching raw string pattern returns false
|
||||
{"xyz", "^abc", true, false},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := regexpMatcher{}
|
||||
if result := m.Compare(mt.ValA, mt.ValB); result != mt.Expacted {
|
||||
t.Errorf("RegexpMatcher(%#v, %#v string) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
if result := m.Compare(mt.ValA, ""); result != mt.Expected {
|
||||
t.Errorf("negativeRegexpMatcher(%q, %q) = %v, want %v", mt.ValA, mt.ValB, result, mt.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifies that an invalid regex pattern returns an error from newRegexpMatcher
|
||||
func TestRegexpMatcherWithInvalidPattern(t *testing.T) {
|
||||
// verifies that an invalid regex pattern string returns false instead of panicking
|
||||
m := regexpMatcher{}
|
||||
result := m.Compare("foo", "[invalid")
|
||||
if result != false {
|
||||
t.Errorf("RegexpMatcher with invalid pattern returned %v, expected false", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNegativeRegexpMatcherWithStringValB(t *testing.T) {
|
||||
tests := []matchTest{
|
||||
// verifies that passing a raw string pattern (not pre-compiled) compiles and negation works
|
||||
{"abcdef", "^abcdef$", true, false},
|
||||
// verifies cache hit on the same string pattern
|
||||
{"abcdef", "^abcdef$", true, false},
|
||||
// verifies that a non-matching raw string pattern returns true (negated)
|
||||
{"xyz", "^abcdef$", true, true},
|
||||
}
|
||||
for _, mt := range tests {
|
||||
m := negativeRegexMatcher{}
|
||||
if result := m.Compare(mt.ValA, mt.ValB); result != mt.Expacted {
|
||||
t.Errorf("NegativeRegexMatcher(%#v, %#v string) returned %v when %v was expected", mt.ValA, mt.ValB, result, mt.Expacted)
|
||||
}
|
||||
_, err := newRegexpMatcher(regexpOperator, "[invalid")
|
||||
if err == nil {
|
||||
t.Error("newRegexpMatcher with invalid pattern should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
// verifies that an invalid regex pattern returns an error from newRegexpMatcher
|
||||
// for the negative variant
|
||||
func TestNegativeRegexpMatcherWithInvalidPattern(t *testing.T) {
|
||||
// verifies that an invalid regex pattern string returns false instead of panicking
|
||||
m := negativeRegexMatcher{}
|
||||
result := m.Compare("foo", "[invalid")
|
||||
if result != false {
|
||||
t.Errorf("NegativeRegexMatcher with invalid pattern returned %v, expected false", result)
|
||||
_, err := newRegexpMatcher(negativeRegexOperator, "[invalid")
|
||||
if err == nil {
|
||||
t.Error("newRegexpMatcher with invalid pattern should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,21 +196,19 @@ func TestNewMatcher(t *testing.T) {
|
||||
for _, operator := range operators {
|
||||
m, err := newMatcher(operator)
|
||||
if err != nil {
|
||||
t.Errorf("NewMatcher(%s) returned error: %s", operator, err.Error())
|
||||
t.Errorf("newMatcher(%s) returned error: %s", operator, err.Error())
|
||||
}
|
||||
if m.GetOperator() != operator {
|
||||
t.Errorf("Got wrong matcher for %s: %s", operator, m.GetOperator())
|
||||
if m.Operator != operator {
|
||||
t.Errorf("Got wrong matcher for %s: %s", operator, m.Operator)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifies that an unknown operator returns an error
|
||||
func TestInvalidMatcher(t *testing.T) {
|
||||
operator := "<>"
|
||||
m, err := newMatcher(operator)
|
||||
_, err := newMatcher(operator)
|
||||
if err == nil {
|
||||
t.Errorf("NewMatcher(%s) didn't return any error: %s", operator, m)
|
||||
}
|
||||
if m != nil {
|
||||
t.Errorf("NewMatcher(%s) returned non-nil value: %s", operator, m)
|
||||
t.Errorf("newMatcher(%s) didn't return any error", operator)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,12 @@ var matcherRegex = "[=!<>~]+"
|
||||
// same as matcherRegex but for the filter name part
|
||||
var filterRegex = "^(@)?[a-zA-Z_][a-zA-Z0-9_]*"
|
||||
|
||||
var matcherConfig = map[string]matcherT{
|
||||
equalOperator: &equalMatcher{abstractMatcher{Operator: equalOperator}},
|
||||
notEqualOperator: ¬EqualMatcher{abstractMatcher{Operator: notEqualOperator}},
|
||||
moreThanOperator: &moreThanMatcher{abstractMatcher{Operator: moreThanOperator}},
|
||||
lessThanOperator: &lessThanMatcher{abstractMatcher{Operator: lessThanOperator}},
|
||||
regexpOperator: ®expMatcher{abstractMatcher{Operator: regexpOperator}},
|
||||
negativeRegexOperator: &negativeRegexMatcher{abstractMatcher{Operator: negativeRegexOperator}},
|
||||
}
|
||||
// filterFactory constructs a Filter from parsed expression components.
|
||||
type filterFactory func(name, operator, rawText, value string) Filter
|
||||
|
||||
type filterConfig struct {
|
||||
LabelRe *regexp.Regexp
|
||||
Factory newFilterFactory
|
||||
Factory filterFactory
|
||||
Autocomplete autocompleteFactory
|
||||
Label string
|
||||
SupportedOperators []string
|
||||
@@ -85,7 +79,7 @@ var AllFilters = []filterConfig{
|
||||
Label: "@receiver",
|
||||
LabelRe: regexp.MustCompile("^@receiver$"),
|
||||
SupportedOperators: []string{regexpOperator, negativeRegexOperator, equalOperator, notEqualOperator},
|
||||
Factory: newreceiverFilter,
|
||||
Factory: newReceiverFilter,
|
||||
Autocomplete: receiverAutocomplete,
|
||||
},
|
||||
{
|
||||
@@ -99,7 +93,7 @@ var AllFilters = []filterConfig{
|
||||
Label: "@silenced_by",
|
||||
LabelRe: regexp.MustCompile("^@silenced_by$"),
|
||||
SupportedOperators: []string{equalOperator, notEqualOperator},
|
||||
Factory: newsilenceIDFilter,
|
||||
Factory: newSilenceIDFilter,
|
||||
Autocomplete: silenceIDAutocomplete,
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user