Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple alerts in Graph and TimeSeries panels #260

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
4 changes: 3 additions & 1 deletion alert/alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,11 @@ type Alert struct {
Datasource string
DashboardUID string
PanelID string
PanelName string
}

// New creates a new alert.
func New(name string, options ...Option) *Alert {
func New(name, panelName string, options ...Option) *Alert {

This comment was marked as resolved.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not optional parameter, without panel name we can't match alert with panel

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I leave it to the discretion of the owners

nope := false

alert := &Alert{
Expand Down Expand Up @@ -88,6 +89,7 @@ func New(name string, options ...Option) *Alert {
},
},
},
PanelName: panelName,
}

for _, opt := range append(defaults(), options...) {
Expand Down
30 changes: 16 additions & 14 deletions alert/alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
func TestNewAlertCanBeCreated(t *testing.T) {
req := require.New(t)
alertTitle := "some alert"
panelTitle := "some panel"

a := New(alertTitle)
a := New(alertTitle, panelTitle)

req.Len(a.Builder.Rules, 1)

Expand All @@ -25,6 +26,7 @@ func TestConditionsCanBeCombined(t *testing.T) {
req := require.New(t)

a := New(
"",
"",
IfOr(Avg, "A", IsBelow(10)),
IfOr(Avg, "B", IsBelow(8)),
Expand All @@ -36,7 +38,7 @@ func TestConditionsCanBeCombined(t *testing.T) {
func TestPanelIDCanBeHooked(t *testing.T) {
req := require.New(t)

a := New("")
a := New("", "")

a.HookPanelID("id")

Expand All @@ -46,7 +48,7 @@ func TestPanelIDCanBeHooked(t *testing.T) {
func TestDashboardUIDCanBeHooked(t *testing.T) {
req := require.New(t)

a := New("")
a := New("", "")

a.HookDashboardUID("uid")

Expand All @@ -57,7 +59,7 @@ func TestDatasourceUIDCanBeHooked(t *testing.T) {
req := require.New(t)

a := New(
"",
"", "",
WithPrometheusQuery("A", "some prometheus query"),
IfOr(Avg, "1", IsBelow(10)),
)
Expand Down Expand Up @@ -87,63 +89,63 @@ func TestDatasourceUIDCanBeHooked(t *testing.T) {
func TestSummaryCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", Summary("summary content"))
a := New("", "", Summary("summary content"))

req.Equal("summary content", a.Builder.Rules[0].Annotations["summary"])
}

func TestDescriptionCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", Description("description content"))
a := New("", "", Description("description content"))

req.Equal("description content", a.Builder.Rules[0].Annotations["description"])
}

func TestRunbookCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", Runbook("runbook url"))
a := New("", "", Runbook("runbook url"))

req.Equal("runbook url", a.Builder.Rules[0].Annotations["runbook_url"])
}

func TestForIntervalCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", For("1m"))
a := New("", "", For("1m"))

req.Equal("1m", a.Builder.Rules[0].For)
}

func TestFrequencyCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", EvaluateEvery("1m"))
a := New("", "", EvaluateEvery("1m"))

req.Equal("1m", a.Builder.Interval)
}

func TestErrorModeCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", OnExecutionError(ErrorKO))
a := New("", "", OnExecutionError(ErrorKO))

req.Equal(string(ErrorKO), a.Builder.Rules[0].GrafanaAlert.ExecutionErrorState)
}

func TestNoDataModeCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", OnNoData(NoDataAlerting))
a := New("", "", OnNoData(NoDataAlerting))

req.Equal(string(NoDataAlerting), a.Builder.Rules[0].GrafanaAlert.NoDataState)
}

func TestTagsCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", Tags(map[string]string{
a := New("", "", Tags(map[string]string{
"severity": "warning",
}))

Expand All @@ -154,15 +156,15 @@ func TestTagsCanBeSet(t *testing.T) {
func TestConditionsCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", If(Avg, "1", IsBelow(10)))
a := New("", "", If(Avg, "1", IsBelow(10)))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 1)
}

func TestOrConditionsCanBeSet(t *testing.T) {
req := require.New(t)

a := New("", IfOr(Avg, "1", IsBelow(10)))
a := New("", "", IfOr(Avg, "1", IsBelow(10)))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 1)
}
10 changes: 5 additions & 5 deletions alert/queries_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,39 @@ import (
func TestPrometheusQueriesCanBeAdded(t *testing.T) {
req := require.New(t)

a := New("", WithPrometheusQuery("A", "some prometheus query"))
a := New("", "", WithPrometheusQuery("A", "some prometheus query"))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 2)
}

func TestGraphiteQueriesCanBeAdded(t *testing.T) {
req := require.New(t)

a := New("", WithGraphiteQuery("A", "some graphite query"))
a := New("", "", WithGraphiteQuery("A", "some graphite query"))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 2)
}

func TestLokiQueriesCanBeAdded(t *testing.T) {
req := require.New(t)

a := New("", WithLokiQuery("A", "some loki query"))
a := New("", "", WithLokiQuery("A", "some loki query"))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 2)
}

func TestStackdriverQueriesCanBeAdded(t *testing.T) {
req := require.New(t)

a := New("", WithStackdriverQuery(stackdriver.Gauge("A", "cloudsql.googleapis.com/database/cpu/utilization")))
a := New("", "", WithStackdriverQuery(stackdriver.Gauge("A", "cloudsql.googleapis.com/database/cpu/utilization")))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 2)
}

func TestInfluxDBQueriesCanBeAdded(t *testing.T) {
req := require.New(t)

a := New("", WithInfluxDBQuery("A", "some influxdb query"))
a := New("", "", WithInfluxDBQuery("A", "some influxdb query"))

req.Len(a.Builder.Rules[0].GrafanaAlert.Data, 2)
}
5 changes: 0 additions & 5 deletions alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ func (client *Client) AddAlert(ctx context.Context, namespace string, alertDefin

alertDefinition.HookDatasourceUID(datasourceUID)

// Before we can add this alert, we need to delete any other alert that might exist for this dashboard and panel
if err := client.DeleteAlertGroup(ctx, namespace, alertDefinition.Builder.Name); err != nil && !errors.Is(err, ErrAlertNotFound) {
return fmt.Errorf("could not delete existing alerts: %w", err)
}

buf, err := json.Marshal(alertDefinition.Builder)
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions cmd/builder-example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func main() {
timeseries.Legend(timeseries.Last, timeseries.AsTable),
timeseries.Alert(
"Too many heap allocations",
alert.Summary("Too many heap allocations"),
alert.Description("Yup, too much of {{ app }}"),
alert.Runbook("https://google.com"),
alert.Tags(map[string]string{
Expand Down
15 changes: 14 additions & 1 deletion dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,24 @@ func (client *Client) UpsertDashboard(ctx context.Context, folder *Folder, build
return nil, err
}

alertPanelNames := make(map[string]struct{}, len(alerts))
for _, al := range alerts {
alertPanelNames[al.PanelName] = struct{}{}
}

// Clean alerts by panel name
for panelName := range alertPanelNames {
// Before we can add this alert, we need to delete any other alert that might exist for this dashboard and panel
if err := client.DeleteAlertGroup(ctx, folder.Title, panelName); err != nil && !errors.Is(err, ErrAlertNotFound) {
return nil, fmt.Errorf("could not delete existing alerts: %w", err)
}
}

for i := range alerts {
alert := *alerts[i]

alert.HookDashboardUID(dashboardFromGrafana.UID)
alert.HookPanelID(panelIDByTitle(dashboardFromGrafana, alert.Builder.Name))
alert.HookPanelID(panelIDByTitle(dashboardFromGrafana, alert.PanelName))

if err := client.AddAlert(ctx, folder.Title, alert, datasourcesMap); err != nil {
return nil, fmt.Errorf("could not add new alerts for dashboard: %w", err)
Expand Down
5 changes: 3 additions & 2 deletions dashboards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ func TestDashboardsCanBeCreatedWithNewAlertsAndDeletesPreviousAlerts(t *testing.
),
timeseries.Alert(
"Too many heap allocations",
alert.Summary("Too many heap allocations"),
alert.WithPrometheusQuery(
"A",
"sum(go_memstats_heap_alloc_bytes{app!=\"\"}) by (app)",
Expand Down Expand Up @@ -843,13 +844,13 @@ func TestDashboardsCanBeCreatedWithNewAlertsAndDeletesPreviousAlerts(t *testing.
req.NoError(err)

req.JSONEq(`{
"name": "Heap allocations",
"name": "Too many heap allocations",
"interval": "1m",
"rules": [
{
"for": "5m",
"grafana_alert": {
"title": "Heap allocations",
"title": "Too many heap allocations",
"condition": "_alert_condition_",
"no_data_state": "NoData",
"exec_err_state": "Alerting",
Expand Down
14 changes: 7 additions & 7 deletions decoder/alert_targets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestDecodingAPrometheusTarget(t *testing.T) {

req.NoError(err)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)

req.Len(alert.Builder.Rules, 1)
req.Len(alert.Builder.Rules[0].GrafanaAlert.Data, 2) // the query and the condition
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestDecodingALokiTarget(t *testing.T) {

req.NoError(err)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)

req.Len(alert.Builder.Rules, 1)
req.Len(alert.Builder.Rules[0].GrafanaAlert.Data, 2) // the query and the condition
Expand Down Expand Up @@ -156,7 +156,7 @@ func TestDecodingAGraphiteTarget(t *testing.T) {

req.NoError(err)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)

req.Len(alert.Builder.Rules, 1)
req.Len(alert.Builder.Rules[0].GrafanaAlert.Data, 2) // the query and the condition
Expand Down Expand Up @@ -269,7 +269,7 @@ func TestDecodingStackdriverTarget(t *testing.T) {

req.NoError(err, ErrInvalidStackdriverType)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)

req.Len(alert.Builder.Rules, 1)
req.Len(alert.Builder.Rules[0].GrafanaAlert.Data, 2) // the query and the condition
Expand Down Expand Up @@ -335,7 +335,7 @@ func TestDecodingStackdriverPreprocessor(t *testing.T) {

req.NoError(err)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)
query := alert.Builder.Rules[0].GrafanaAlert.Data[1]

req.Equal(tc.expected, query.Model.MetricQuery.Preprocessor)
Expand Down Expand Up @@ -449,7 +449,7 @@ func TestDecodingStackdriverAggregation(t *testing.T) {

req.NoError(err)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)
query := alert.Builder.Rules[0].GrafanaAlert.Data[1]

req.Equal(string(tc.expected), query.Model.MetricQuery.CrossSeriesReducer)
Expand Down Expand Up @@ -591,7 +591,7 @@ func TestDecodingStackdriverAlignment(t *testing.T) {

req.NoError(err)

alert := alertBuilder.New("", opt)
alert := alertBuilder.New("", "", opt)
query := alert.Builder.Rules[0].GrafanaAlert.Data[1]

req.Equal(string(tc.expected), query.Model.MetricQuery.PerSeriesAligner)
Expand Down
2 changes: 1 addition & 1 deletion decoder/alert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestDecodingSimpleAlert(t *testing.T) {
opts, err := alertDef.toOptions()
req.NoError(err)

alertBuilder := alert.New("", opts...)
alertBuilder := alert.New("", "", opts...)
rule := alertBuilder.Builder.Rules[0]

req.Equal("description", rule.Annotations["description"])
Expand Down
6 changes: 5 additions & 1 deletion decoder/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package decoder
import (
"fmt"

"github.com/K-Phoen/grabana/alert"
"github.com/K-Phoen/grabana/axis"
"github.com/K-Phoen/grabana/graph"
"github.com/K-Phoen/grabana/graph/series"
Expand Down Expand Up @@ -82,7 +83,10 @@ func (graphPanel DashboardGraph) toOption() (row.Option, error) {
return nil, err
}

opts = append(opts, graph.Alert(graphPanel.Alert.Summary, alertOpts...))
opts = append(opts, graph.Alert(
graphPanel.Alert.Summary,
append(alertOpts, alert.Summary(graphPanel.Alert.Summary))...),
)
}
if graphPanel.Visualization != nil {
opts = append(opts, graphPanel.Visualization.toOptions()...)
Expand Down
6 changes: 5 additions & 1 deletion decoder/timeseries.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package decoder
import (
"fmt"

"github.com/K-Phoen/grabana/alert"
"github.com/K-Phoen/grabana/row"
"github.com/K-Phoen/grabana/timeseries"
"github.com/K-Phoen/grabana/timeseries/axis"
Expand Down Expand Up @@ -80,7 +81,10 @@ func (timeseriesPanel DashboardTimeSeries) toOption() (row.Option, error) {
return nil, err
}

opts = append(opts, timeseries.Alert(timeseriesPanel.Alert.Summary, alertOpts...))
opts = append(opts, timeseries.Alert(
timeseriesPanel.Alert.Summary,
append(alertOpts, alert.Summary(timeseriesPanel.Alert.Summary))...,
))
}
if timeseriesPanel.Visualization != nil {
vizOpts, err := timeseriesPanel.Visualization.toOptions()
Expand Down
Loading
Loading