Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 48 additions & 3 deletions api/v2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,16 @@ type API struct {
groupMutedFunc groupMutedFunc
uptime time.Time

// mtx protects alertmanagerConfig, setAlertStatus and route.
// mtx protects alertmanagerConfig, setAlertStatus, route, and hiddenReceivers.
mtx sync.RWMutex
// resolveTimeout represents the default resolve timeout that an alert is
// assigned if no end time is specified.
alertmanagerConfig *config.Config
route *dispatch.Route
setAlertStatus setAlertStatusFn
// hiddenReceivers is a set of receiver names marked as hidden in config.
// Pre-computed on config reload for O(1) lookups.
hiddenReceivers map[string]struct{}

logger *slog.Logger
m *metrics.Alerts
Expand Down Expand Up @@ -171,6 +174,14 @@ func (api *API) Update(cfg *config.Config, setAlertStatus setAlertStatusFn) {
api.alertmanagerConfig = cfg
api.route = dispatch.NewRoute(cfg.Route, nil)
api.setAlertStatus = setAlertStatus

// Pre-compute hidden receivers set for O(1) lookups.
api.hiddenReceivers = make(map[string]struct{})
for _, r := range cfg.Receivers {
if r.Hidden {
api.hiddenReceivers[r.Name] = struct{}{}
}
}
}

func (api *API) getStatusHandler(params general_ops.GetStatusParams) middleware.Responder {
Expand Down Expand Up @@ -304,6 +315,15 @@ func (api *API) getAlertsHandler(params alert_ops.GetAlertsParams) middleware.Re
continue
}

// Filter hidden receivers unless explicitly requested
if params.IncludeHidden == nil || !*params.IncludeHidden {
var hasVisible bool
receivers, hasVisible = api.filterHiddenReceivers(receivers)
if !hasVisible {
continue // Skip alert entirely if all receivers are hidden
}
}

if !alertFilter(alert, now) {
continue
}
Expand Down Expand Up @@ -422,15 +442,21 @@ func (api *API) getAlertGroupsHandler(params alertgroup_ops.GetAlertGroupsParams
}
}

rf := func(receiverFilter *regexp.Regexp) func(r *dispatch.Route) bool {
includeHidden := params.IncludeHidden != nil && *params.IncludeHidden
rf := func(receiverFilter *regexp.Regexp, includeHidden bool) func(r *dispatch.Route) bool {
return func(r *dispatch.Route) bool {
receiver := r.RouteOpts.Receiver
// Filter by hidden status
if !includeHidden && api.isReceiverHidden(receiver) {
return false
}
// Filter by receiver regex
if receiverFilter != nil && !receiverFilter.MatchString(receiver) {
return false
}
return true
}
}(receiverFilter)
}(receiverFilter, includeHidden)

af := api.alertFilter(matchers, *params.Silenced, *params.Inhibited, *params.Active)
alertGroups, allReceivers, err := api.alertGroups(ctx, rf, af)
Expand Down Expand Up @@ -500,6 +526,25 @@ func (api *API) alertFilter(matchers []*labels.Matcher, silenced, inhibited, act
}
}

// isReceiverHidden checks if a receiver is marked as hidden in the config.
// Uses pre-computed map for O(1) lookup.
func (api *API) isReceiverHidden(receiverName string) bool {
_, hidden := api.hiddenReceivers[receiverName]
return hidden
}

// filterHiddenReceivers removes hidden receivers from a slice.
// Returns filtered receivers and whether any visible receivers remain.
func (api *API) filterHiddenReceivers(receivers []string) ([]string, bool) {
filtered := make([]string, 0, len(receivers))
for _, r := range receivers {
if _, hidden := api.hiddenReceivers[r]; !hidden {
filtered = append(filtered, r)
}
}
return filtered, len(filtered) > 0
}

func removeEmptyLabels(ls prometheus_model.LabelSet) {
for k, v := range ls {
if string(v) == "" {
Expand Down
107 changes: 107 additions & 0 deletions api/v2/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -603,3 +603,110 @@ receivers:
require.Equal(t, tc.body, string(body))
}
}

func TestIsReceiverHidden(t *testing.T) {
in := `
route:
receiver: team-X

receivers:
- name: 'team-X'
hidden: true
- name: 'team-Y'
hidden: false
- name: 'team-Z'
`
cfg, err := config.Load(in)
require.NoError(t, err)

api := API{}
api.Update(cfg, nil)

testCases := []struct {
receiver string
expected bool
}{
{"team-X", true},
{"team-Y", false},
{"team-Z", false},
{"nonexistent", false},
}

for _, tc := range testCases {
t.Run(tc.receiver, func(t *testing.T) {
require.Equal(t, tc.expected, api.isReceiverHidden(tc.receiver))
})
}
}

func TestFilterHiddenReceivers(t *testing.T) {
in := `
route:
receiver: visible

receivers:
- name: 'visible'
- name: 'hidden-1'
hidden: true
- name: 'hidden-2'
hidden: true
- name: 'also-visible'
`
cfg, err := config.Load(in)
require.NoError(t, err)

api := API{}
api.Update(cfg, nil)

testCases := []struct {
name string
receivers []string
expectedFiltered []string
expectedVisible bool
}{
{
name: "all visible",
receivers: []string{"visible", "also-visible"},
expectedFiltered: []string{"visible", "also-visible"},
expectedVisible: true,
},
{
name: "all hidden",
receivers: []string{"hidden-1", "hidden-2"},
expectedFiltered: []string{},
expectedVisible: false,
},
{
name: "mixed",
receivers: []string{"visible", "hidden-1", "also-visible"},
expectedFiltered: []string{"visible", "also-visible"},
expectedVisible: true,
},
{
name: "empty input",
receivers: []string{},
expectedFiltered: []string{},
expectedVisible: false,
},
{
name: "single hidden",
receivers: []string{"hidden-1"},
expectedFiltered: []string{},
expectedVisible: false,
},
{
name: "single visible",
receivers: []string{"visible"},
expectedFiltered: []string{"visible"},
expectedVisible: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
filtered, hasVisible := api.filterHiddenReceivers(tc.receivers)
require.Equal(t, tc.expectedFiltered, filtered)
require.Equal(t, tc.expectedVisible, hasVisible)
})
}
}
45 changes: 41 additions & 4 deletions api/v2/client/alert/get_alerts_parameters.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 41 additions & 4 deletions api/v2/client/alertgroup/get_alert_groups_parameters.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions api/v2/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ paths:
description: A regex matching receivers to filter alerts by
required: false
type: string
- in: query
name: includeHidden
type: boolean
description: Include alerts routed to hidden receivers. If false (default), alerts to hidden receivers are excluded.
default: false
responses:
'200':
description: Get alerts response
Expand Down Expand Up @@ -241,6 +246,11 @@ paths:
description: A regex matching receivers to filter alerts by
required: false
type: string
- in: query
name: includeHidden
type: boolean
description: Include alert groups for hidden receivers. If false (default), groups for hidden receivers are excluded.
default: false
responses:
'200':
description: Get alert groups response
Expand Down
Loading
Loading