Skip to content

Commit 9b57389

Browse files
authored
multi-dashboard annotations, add RMN dummy (#1635)
1 parent c6274a5 commit 9b57389

File tree

4 files changed

+219
-117
lines changed

4 files changed

+219
-117
lines changed

framework/.changeset/v0.5.2.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Update chaos playground for RMN
2+
- Allow multiple dashboard UIDs

framework/grafana/grafana.go

Lines changed: 36 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package framework
22

33
import (
4-
"fmt"
5-
"net/url"
64
"time"
75

86
"github.com/go-resty/resty/v2"
@@ -13,47 +11,19 @@ type Client struct {
1311
}
1412

1513
// NewGrafanaClient initializes a new Grafana client with the specified URL and API key.
16-
func NewGrafanaClient(url, apiKey string) *Client {
14+
func NewGrafanaClient(url, bearerToken string) *Client {
1715
return &Client{
1816
resty: resty.New().
1917
SetBaseURL(url).
20-
SetHeader("Authorization", "Bearer "+apiKey),
18+
SetHeader("Authorization", "Bearer "+bearerToken),
2119
}
2220
}
2321

2422
type Annotation struct {
25-
ID int64 `json:"id"`
26-
AlertID int64 `json:"alertId"`
27-
DashboardID int64 `json:"dashboardId"`
28-
DashboardUID string `json:"dashboardUID"`
29-
PanelID int64 `json:"panelId"`
30-
PrevState string `json:"prevState"`
31-
NewState string `json:"newState"`
32-
Text string `json:"text"`
33-
Time time.Time `json:"time"`
34-
TimeEnd time.Time `json:"timeEnd"`
35-
Created time.Time `json:"created"`
36-
Updated time.Time `json:"updated"`
37-
Tags []interface{} `json:"tags"`
38-
Data interface{} `json:"data"`
39-
}
40-
41-
type AnnotationsQueryParams struct {
42-
Limit *int
43-
AlertID *int
44-
DashboardID *int
45-
DashboardUID *string
46-
Type *string
47-
From *time.Time
48-
To *time.Time
49-
}
50-
51-
type PostAnnotation struct {
52-
DashboardID *int
5323
PanelID *int
54-
DashboardUID string
55-
Time *time.Time
56-
TimeEnd *time.Time
24+
DashboardUID []string
25+
StartTime *time.Time
26+
EndTime *time.Time
5727
Tags []string
5828
Text string
5929
}
@@ -63,67 +33,39 @@ type PostAnnotationResponse struct {
6333
ID int64 `json:"id"`
6434
}
6535

66-
// GetAnnotations retrieves a list of annotations based on specified query parameters.
67-
func (c *Client) GetAnnotations(params AnnotationsQueryParams) ([]Annotation, *resty.Response, error) {
68-
query := make(url.Values)
69-
if params.Limit != nil {
70-
query.Set("limit", fmt.Sprintf("%d", *params.Limit))
71-
}
72-
if params.AlertID != nil {
73-
query.Set("alertId", fmt.Sprintf("%d", *params.AlertID))
74-
}
75-
if params.DashboardID != nil {
76-
query.Set("dashboardId", fmt.Sprintf("%d", *params.DashboardID))
77-
}
78-
if params.DashboardUID != nil {
79-
query.Set("dashboardUID", *params.DashboardUID)
80-
}
81-
if params.Type != nil {
82-
query.Set("type", *params.Type)
83-
}
36+
// Annotate adds annotation to all the dashboards, works for both single point annotation with just StartTime and for ranges with StartTime/EndTime
37+
func (c *Client) Annotate(annotation Annotation) ([]PostAnnotationResponse, []*resty.Response, error) {
38+
var results []PostAnnotationResponse
39+
var responses []*resty.Response
8440

85-
if (params.From != nil && params.To == nil) || (params.To != nil && params.From == nil) {
86-
return nil, nil, fmt.Errorf("both From and To must be set")
87-
}
41+
for _, uid := range annotation.DashboardUID {
42+
a := map[string]interface{}{
43+
"dashboardUID": uid,
44+
"tags": annotation.Tags,
45+
"text": annotation.Text,
46+
}
47+
if annotation.PanelID != nil {
48+
a["panelId"] = *annotation.PanelID
49+
}
50+
if annotation.StartTime != nil {
51+
a["time"] = annotation.StartTime.UnixMilli()
52+
}
53+
if annotation.EndTime != nil {
54+
a["timeEnd"] = annotation.EndTime.UnixMilli()
55+
}
8856

89-
if params.From != nil {
90-
query.Set("from", fmt.Sprintf("%d", params.From.UnixMilli()))
91-
}
92-
if params.To != nil {
93-
query.Set("to", fmt.Sprintf("%d", params.To.UnixMilli()))
94-
}
57+
var result PostAnnotationResponse
58+
r, err := c.resty.R().
59+
SetBody(a).
60+
SetResult(&result).
61+
Post("/api/annotations")
62+
if err != nil {
63+
return nil, nil, err // Return early if any request fails
64+
}
9565

96-
var result []Annotation
97-
r, err := c.resty.R().
98-
SetResult(&result).
99-
SetQueryString(query.Encode()).
100-
Get("/api/annotations")
101-
return result, r, err
102-
}
103-
104-
// PostAnnotation sends a new annotation to a specified dashboard.
105-
func (c *Client) PostAnnotation(annotation PostAnnotation) (PostAnnotationResponse, *resty.Response, error) {
106-
a := map[string]interface{}{
107-
"dashboardUID": annotation.DashboardUID,
108-
"tags": annotation.Tags,
109-
"text": annotation.Text,
110-
}
111-
if annotation.DashboardID != nil {
112-
a["dashboardId"] = *annotation.DashboardID
66+
results = append(results, result)
67+
responses = append(responses, r)
11368
}
114-
if annotation.PanelID != nil {
115-
a["panelId"] = *annotation.PanelID
116-
}
117-
if annotation.Time != nil {
118-
a["time"] = annotation.Time.UnixMilli()
119-
}
120-
if annotation.TimeEnd != nil {
121-
a["timeEnd"] = annotation.TimeEnd.UnixMilli()
122-
}
123-
var result PostAnnotationResponse
124-
r, err := c.resty.R().
125-
SetBody(a).
126-
SetResult(&result).
127-
Post("/api/annotations")
128-
return result, r, err
69+
70+
return results, responses, nil
12971
}

framework/grafana/grafana_test.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package framework
22

33
import (
4+
"github.com/stretchr/testify/assert"
45
"os"
56
"testing"
67
"time"
7-
8-
"github.com/stretchr/testify/assert"
98
)
109

11-
// TestPostAnnotationIntegration tests the PostAnnotation method against a real Grafana instance.
10+
// TestPostAnnotationIntegration tests the Annotation method against a real Grafana instance.
1211
func TestPostAnnotationIntegration(t *testing.T) {
1312
t.Skip("manual grafana integration test")
1413
grafanaURL := os.Getenv("GRAFANA_URL")
@@ -18,18 +17,21 @@ func TestPostAnnotationIntegration(t *testing.T) {
1817
}
1918
client := NewGrafanaClient(grafanaURL, apiKey)
2019

21-
annotation := PostAnnotation{
22-
DashboardUID: "WaspDebug",
23-
Text: "CTFv2 test annotation",
24-
Tags: []string{"tag-1", "tag-2"},
25-
Time: Ptr(time.Now().Add(-1 * time.Minute)),
26-
TimeEnd: Ptr(time.Now()),
20+
annotation := Annotation{
21+
Text: "CTFv2 test annotation",
22+
StartTime: Ptr(time.Now().Add(-11 * time.Minute)),
23+
//EndTime: Ptr(time.Now()),
24+
DashboardUID: []string{"WaspDebug", "e98b5451-12dc-4a8b-9576-2c0b67ddbd0c"},
25+
Tags: []string{"tag-3", "tag-4"},
2726
}
28-
response, resp, err := client.PostAnnotation(annotation)
29-
assert.NoError(t, err, "PostAnnotation should not return an error")
30-
assert.Equal(t, 200, resp.StatusCode(), "Expected HTTP status code 200")
31-
assert.NotEmpty(t, response.ID, "Annotation ID should not be empty")
32-
assert.NotEmpty(t, response.Message, "Annotation message should not be empty")
27+
response, resp, err := client.Annotate(annotation)
28+
assert.NoError(t, err, "Annotate should not return an error")
29+
assert.Equal(t, 200, resp[0].StatusCode(), "Expected HTTP status code 200")
30+
assert.NotEmpty(t, response[0].ID, "Annotation ID should not be empty")
31+
assert.NotEmpty(t, response[0].Message, "Annotation message should not be empty")
32+
assert.Equal(t, 200, resp[1].StatusCode(), "Expected HTTP status code 200")
33+
assert.NotEmpty(t, response[1].ID, "Annotation ID should not be empty")
34+
assert.NotEmpty(t, response[1].Message, "Annotation message should not be empty")
3335
}
3436

3537
func Ptr[T any](value T) *T {

0 commit comments

Comments
 (0)