From b97b1bcbb88683d31470ce1c368b165aa14981b2 Mon Sep 17 00:00:00 2001 From: Paul Seiffert Date: Wed, 20 May 2015 17:34:24 +0200 Subject: [PATCH 1/5] Add basic support for Screenboards --- screen_widgets.go | 152 +++++++++++++++++++++++++++++++++++++++++++++ screenboards.go | 155 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 screen_widgets.go create mode 100644 screenboards.go diff --git a/screen_widgets.go b/screen_widgets.go new file mode 100644 index 0000000..9a5dd45 --- /dev/null +++ b/screen_widgets.go @@ -0,0 +1,152 @@ +package datadog + +import ( + "fmt" + "strconv" +) + +type Widget interface{} + +type TimeseriesWidget struct { + BoardId int `json:"board_id,omitempty"` + Height int `json:"height"` + Legend bool `json:"legend"` + TileDef TileDef `json:"tile_def"` + Timeframe string `json:"timeframe"` + Title bool `json:"title"` + TitleAlign string `json:"string"` + TitleSize TextSize `json:"title_size"` + TitleText string `json:"title_text"` + Type string `json:"string"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` +} + +type TextSize struct { + Size int + Auto bool +} + +func (size *TextSize) UnmarshalJSON(data []byte) error { + if string(data) == "\"auto\"" { + size.Auto = true + return nil + } + + num, err := strconv.Atoi(string(data)) + if err != nil { + return err + } + size.Size = num + + return nil +} +func (size *TextSize) MarshalJSON() ([]byte, error) { + if size.Auto { + return []byte("\"auto\""), nil + } + + return []byte(fmt.Sprintf("%d", size.Size)), nil +} + +type TileDef struct { + Events []TileDefEvent `json:"events"` + Requests []TileDefRequest `json:"requests"` + Viz string `json:"viz"` +} + +type TileDefRequest struct { + Query string `json:"q"` + Type string `json:"type,omitempty"` + ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"` + Style TileDefRequestStyle `json:"style,omitempty"` +} + +type TileDefRequestStyle struct { + Palette string `json:"palette"` +} + +type TileDefEvent struct { + Query string `json:"q"` +} + +type QueryValueWidget struct { + Timeframe string `json:"timeframe"` + TimeframeAggregator string `json:"aggr"` + Aggregator string `json:"aggregator"` + BoardId int `json:"board_id,omitempty"` + CalcFunc string `json:"calc_func"` + ConditionalFormats []ConditionalFormat `json:"conditional_formats"` + Height int `json:"height"` + IsValidQuery bool `json:is_valid_query,omitempty"` + Metric string `json:"metric"` + MetricType string `json:"metric_type"` + Precision int `json:"precision"` + Query string `json:"query"` + ResultCalcFunc string `json:"res_calc_func"` + Tags []string `json:"tags"` + TextAlign string `json:"text_align"` + TextSize TextSize `json:"text_size"` + Title bool `json:"title"` + TitleAlign string `json:"title_align"` + TitleSize TextSize `json:"title_size"` + TitleText string `json:"title_text"` + Type string `json:"type"` + Unit string `json:"auto"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` +} +type ConditionalFormat struct { + Color string `json:"color"` + Comparator string `json:"comparator"` + Inverted bool `json:"invert"` + Value int `json:"value"` +} + +type ToplistWidget struct { + Height int `json:"height"` + Legend bool `json:"legend"` + LegendSize string `json:"legend_size"` + TileDef TileDef `json:"tile_def"` + Timeframe string `json:"timeframe"` + Title bool `json:"title"` + TitleAlign string `json:"title_align"` + TitleSize TextSize `json:"title_size"` + Type string `json:"type"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` +} + +type EventStreamWidget struct { + EventSize string `json:"event_size"` + Height int `json:"height"` + Query string `json:"query"` + Timeframe string `json:"timeframe"` + Title bool `json:"title"` + TitleAlign string `json:"title_align"` + TitleSize TextSize `json:"title_size"` + Type string `json:"type"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` +} + +type FreeTextWidget struct { + BoardId int `json:"board_id,omitempty"` + Color string `json:"color,omitempty"` + FontSize string `json:"font_size,omitempty"` + Height int `json:"height,omitempty"` + Text string `json:"text"` + TextAlign string `json:"text_align"` + Title bool `json:"title"` + TitleAlign string `json:"title_align"` + TitleSize TextSize `json:"title_size"` + TitleText string `json:"title_text"` + Type string `json:"type"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` +} diff --git a/screenboards.go b/screenboards.go new file mode 100644 index 0000000..46f6ac8 --- /dev/null +++ b/screenboards.go @@ -0,0 +1,155 @@ +/* + * Datadog API for Go + * + * Please see the included LICENSE file for licensing information. + * + * Copyright 2013 by authors and contributors. + */ + +package datadog + +import ( + "encoding/json" + "fmt" +) + +// Screenboard represents a user created screenboard. This is the full screenboard +// struct when we load a screenboard in detail. +type Screenboard struct { + Id int `json:"id,omitempty"` + Title string `json:"board_title"` + Height int `json:"height,omitempty"` + Width string `json:"width,omitempty"` + Shared bool `json:"shared"` + Templated bool `json:"templated,omitempty"` + TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` + Widgets []Widget `json:"widgets"` +} +type TemplateVariable struct { + Default string `json:"default"` + Name string `json:"name"` + Prefix string `json:"prefix"` +} + +func (s *Screenboard) UnmarshalJSON(data []byte) error { + dest := struct { + Id int `json:"id"` + Title string `json:"board_title"` + Height int `json:"height"` + Width string `json:"width"` + Shared bool `json:"shared"` + Templated bool `json:"templated"` + TemplateVariables []TemplateVariable `json:"template_variables"` + Widgets []json.RawMessage `json:"widgets"` + }{} + err := json.Unmarshal(data, &dest) + if err != nil { + return err + } + + widgets := []Widget{} + for _, rawWidget := range dest.Widgets { + typeStruct := struct { + Type string `json:"type"` + }{} + if err := json.Unmarshal(rawWidget, &typeStruct); err != nil { + return fmt.Errorf("Could not detect widget type: %s", err) + } + + widget, err := unmarshalWidget(typeStruct.Type, rawWidget) + if err != nil { + return fmt.Errorf("Could not unmarshal widget (type %s): %s", typeStruct.Type, err) + } + + widgets = append(widgets, widget) + } + + s.Id = dest.Id + s.Title = dest.Title + s.Height = dest.Height + s.Width = dest.Width + s.Shared = dest.Shared + s.Templated = dest.Templated + s.TemplateVariables = dest.TemplateVariables + s.Widgets = widgets + + return nil +} +func unmarshalWidget(widgetType string, data json.RawMessage) (Widget, error) { + var dest Widget + + switch widgetType { + case "timeseries": + dest = &TimeseriesWidget{} + case "query_value": + dest = &QueryValueWidget{} + case "event_stream": + dest = &EventStreamWidget{} + case "free_text": + dest = &FreeTextWidget{} + case "toplist": + dest = &ToplistWidget{} + default: + return nil, fmt.Errorf("Could not unmarshal unknown widget type %s.", widgetType) + } + + if err := json.Unmarshal(data, dest); err != nil { + return nil, err + } + + return dest, nil +} + +// ScreenboardLite represents a user created screenboard. This is the mini +// struct when we load the summaries. +type ScreenboardLite struct { + Id int `json:"id"` + Resource string `json:"resource"` + Title string `json:"title"` +} + +// reqGetScreenboards from /api/v1/screen +type reqGetScreenboards struct { + Screenboards []*ScreenboardLite `json:"screenboards"` +} + +// GetScreenboard returns a single screenboard created on this account. +func (self *Client) GetScreenboard(id int) (*Screenboard, error) { + out := &Screenboard{} + err := self.doJsonRequest("GET", fmt.Sprintf("/v1/screen/%d", id), nil, out) + if err != nil { + return nil, err + } + return out, nil +} + +// GetScreenboards returns a list of all screenboards created on this account. +func (self *Client) GetScreenboards() ([]*ScreenboardLite, error) { + var out reqGetScreenboards + err := self.doJsonRequest("GET", "/v1/screen", nil, &out) + if err != nil { + return nil, err + } + return out.Screenboards, nil +} + +// DeleteScreenboard deletes a screenboard by the identifier. +func (self *Client) DeleteScreenboard(id int) error { + return self.doJsonRequest("DELETE", fmt.Sprintf("/v1/screen/%d", id), nil, nil) +} + +// CreateScreenboard creates a new screenboard when given a Screenboard struct. Note +// that the Id, Resource, Url and similar elements are not used in creation. +func (self *Client) CreateScreenboard(board *Screenboard) (*Screenboard, error) { + out := &Screenboard{} + if err := self.doJsonRequest("POST", "/v1/screen", board, out); err != nil { + return nil, err + } + return out, nil +} + +// UpdateScreenboard in essence takes a Screenboard struct and persists it back to +// the server. Use this if you've updated your local and need to push it back. +func (self *Client) UpdateScreenboard(board *Screenboard) error { + return self.doJsonRequest("PUT", fmt.Sprintf("/v1/screen/%d", board.Id), board, nil) +} From e7b5df642f9d40cd391c602d133dbcefa8f717f4 Mon Sep 17 00:00:00 2001 From: Paul Seiffert Date: Thu, 21 May 2015 15:34:47 +0200 Subject: [PATCH 2/5] Add constructors for widgets --- screen_widgets.go | 132 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 9 deletions(-) diff --git a/screen_widgets.go b/screen_widgets.go index 9a5dd45..0ec1fe7 100644 --- a/screen_widgets.go +++ b/screen_widgets.go @@ -7,6 +7,29 @@ import ( type Widget interface{} +func NewTimeseriesWidget( + x, y, width, height int, + title bool, titleAlign string, titleSize TextSize, titleText string, + timeframe string, + requests []TimeseriesRequest) Widget { + return &TimeseriesWidget{ + X: x, + Y: y, + Width: width, + Height: height, + Title: title, + TitleAlign: titleAlign, + TitleSize: titleSize, + TitleText: titleText, + Timeframe: timeframe, + TileDef: TileDef{ + Viz: "timeseries", + Requests: requests, + }, + Type: "timeseries", + } +} + type TimeseriesWidget struct { BoardId int `json:"board_id,omitempty"` Height int `json:"height"` @@ -51,19 +74,26 @@ func (size *TextSize) MarshalJSON() ([]byte, error) { } type TileDef struct { - Events []TileDefEvent `json:"events"` - Requests []TileDefRequest `json:"requests"` - Viz string `json:"viz"` + Events []TileDefEvent `json:"events"` + Requests []TimeseriesRequest `json:"requests"` + Viz string `json:"viz"` +} + +func NewTimeseriesRequest(rtype string, query string) TimeseriesRequest { + return TimeseriesRequest{ + Query: query, + Type: rtype, + } } -type TileDefRequest struct { - Query string `json:"q"` - Type string `json:"type,omitempty"` - ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"` - Style TileDefRequestStyle `json:"style,omitempty"` +type TimeseriesRequest struct { + Query string `json:"q"` + Type string `json:"type,omitempty"` + ConditionalFormats []ConditionalFormat `json:"conditional_formats,omitempty"` + Style TimeseriesRequestStyle `json:"style,omitempty"` } -type TileDefRequestStyle struct { +type TimeseriesRequestStyle struct { Palette string `json:"palette"` } @@ -71,6 +101,31 @@ type TileDefEvent struct { Query string `json:"q"` } +func NewQueryValueWidget( + x, y, width, height int, + title bool, titleAlign string, titleSize TextSize, titleText, + textAlign string, textSize TextSize, + timeframe, timeframeAggregator, + aggregator, query string) Widget { + return &QueryValueWidget{ + X: x, + Y: y, + Width: width, + Height: height, + Title: title, + TitleAlign: titleAlign, + TitleSize: titleSize, + TitleText: titleText, + TextAlign: textAlign, + TextSize: textSize, + Timeframe: timeframe, + TimeframeAggregator: timeframeAggregator, + Aggregator: aggregator, + Query: query, + Type: "query_value", + } +} + type QueryValueWidget struct { Timeframe string `json:"timeframe"` TimeframeAggregator string `json:"aggr"` @@ -105,6 +160,29 @@ type ConditionalFormat struct { Value int `json:"value"` } +func NewToplistWidget( + x, y, width, height int, + title bool, titleAlign string, titleSize TextSize, titleText string, + timeframe string, + request TimeseriesRequest) Widget { + return &ToplistWidget{ + X: x, + Y: y, + Width: width, + Height: height, + Title: title, + TitleAlign: titleAlign, + TitleSize: titleSize, + TitleText: titleText, + TileDef: TileDef{ + Viz: "toplist", + Requests: []TimeseriesRequest{request}, + }, + Timeframe: timeframe, + Type: "toplist", + } +} + type ToplistWidget struct { Height int `json:"height"` Legend bool `json:"legend"` @@ -114,12 +192,34 @@ type ToplistWidget struct { Title bool `json:"title"` TitleAlign string `json:"title_align"` TitleSize TextSize `json:"title_size"` + TitleText string `json:"title_text"` Type string `json:"type"` Width int `json:"width"` X int `json:"x"` Y int `json:"y"` } +func NewEventStreamWidget( + x, y, width, height int, + title bool, titleAlign string, titleSize TextSize, titleText string, + timeframe string, + query, eventSize string) Widget { + return &EventStreamWidget{ + X: x, + Y: y, + Width: width, + Height: height, + Title: title, + TitleAlign: titleAlign, + TitleSize: titleSize, + TitleText: titleText, + Timeframe: timeframe, + Query: query, + EventSize: eventSize, + Type: "event_stream", + } +} + type EventStreamWidget struct { EventSize string `json:"event_size"` Height int `json:"height"` @@ -128,12 +228,26 @@ type EventStreamWidget struct { Title bool `json:"title"` TitleAlign string `json:"title_align"` TitleSize TextSize `json:"title_size"` + TitleText string `json:"title_text"` Type string `json:"type"` Width int `json:"width"` X int `json:"x"` Y int `json:"y"` } +func NewFreeTextWidget(x, y, width, height int, text string, size int, align string) Widget { + return &FreeTextWidget{ + X: x, + Y: y, + Width: width, + Height: height, + Text: text, + FontSize: fmt.Sprintf("%d", size), + TextAlign: align, + Type: "free_text", + } +} + type FreeTextWidget struct { BoardId int `json:"board_id,omitempty"` Color string `json:"color,omitempty"` From fdb7cb42cba17357ad40a7eff21a3d08093894cd Mon Sep 17 00:00:00 2001 From: Paul Seiffert Date: Thu, 21 May 2015 15:35:07 +0200 Subject: [PATCH 3/5] Fix json field name --- screen_widgets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screen_widgets.go b/screen_widgets.go index 0ec1fe7..384e0e1 100644 --- a/screen_widgets.go +++ b/screen_widgets.go @@ -40,7 +40,7 @@ type TimeseriesWidget struct { TitleAlign string `json:"string"` TitleSize TextSize `json:"title_size"` TitleText string `json:"title_text"` - Type string `json:"string"` + Type string `json:"type"` Width int `json:"width"` X int `json:"x"` Y int `json:"y"` From 72ab29b70bef2befe59998927773db9032d3a47d Mon Sep 17 00:00:00 2001 From: Paul Seiffert Date: Thu, 21 May 2015 18:06:42 +0200 Subject: [PATCH 4/5] A few integration tests --- integration_test/screen_widgets_test.go | 150 +++++++++++++++++++++++ integration_test/screenboards_test.go | 152 ++++++++++++++++++++++++ screen_widgets.go | 25 ++-- screenboards.go | 4 +- 4 files changed, 313 insertions(+), 18 deletions(-) create mode 100644 integration_test/screen_widgets_test.go create mode 100644 integration_test/screenboards_test.go diff --git a/integration_test/screen_widgets_test.go b/integration_test/screen_widgets_test.go new file mode 100644 index 0000000..56ad87e --- /dev/null +++ b/integration_test/screen_widgets_test.go @@ -0,0 +1,150 @@ +package integration_test + +import ( + "testing" + + "github.com/seiffert/go-datadog-api" +) + +func TestFreeTextWidget(t *testing.T) { + board := createTestScreenboard(t) + widget := datadog.NewFreeTextWidget( + 1, 1, 10, 10, "Test", 16, "center", + ) + expected := *(widget.(*datadog.FreeTextWidget)) + + board.Widgets = append(board.Widgets, widget) + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retreiving a screenboard failed: %s", err) + } + + actualWidget, ok := actual.Widgets[0].(*datadog.FreeTextWidget) + if !ok { + t.Fatalf("Widget type does not match: %v", actual.Widgets[0]) + } + + assertEquals(t, "font-size", actualWidget.FontSize, expected.FontSize) + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "text", actualWidget.Text, expected.Text) + assertEquals(t, "text-align", actualWidget.TextAlign, expected.TextAlign) + assertEquals(t, "type", actualWidget.Type, expected.Type) + + cleanUpScreenboard(t, board.Id) +} + +func TestTimeseriesWidget(t *testing.T) { + board := createTestScreenboard(t) + widget := datadog.NewTimeseriesWidget( + 1, 1, 20, 30, + true, "center", datadog.TextSize{Size: 16}, "Test", + "1m", + []datadog.TimeseriesRequest{ + datadog.NewTimeseriesRequest("line", "system.cpu.idle"), + }, + ) + expected := *(widget.(*datadog.TimeseriesWidget)) + + board.Widgets = append(board.Widgets, widget) + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retreiving a screenboard failed: %s", err) + } + + actualWidget, ok := actual.Widgets[0].(*datadog.TimeseriesWidget) + if !ok { + t.Fatalf("Widget type does not match: %v", actual.Widgets[0]) + } + + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "title-align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title-size.size", actualWidget.TitleSize.Size, expected.TitleSize.Size) + assertEquals(t, "title-size.auto", actualWidget.TitleSize.Auto, expected.TitleSize.Auto) + assertEquals(t, "title-text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "legend", actualWidget.Legend, expected.Legend) + assertTileDefEquals(t, actualWidget.TileDef, expected.TileDef) + + cleanUpScreenboard(t, board.Id) +} + +func TestQueryValueWidget(t *testing.T) { + board := createTestScreenboard(t) + widget := datadog.NewQueryValueWidget( + 1, 1, 20, 10, + true, "center", datadog.TextSize{Size: 16}, "Test", + "left", datadog.TextSize{Size: 32}, + "1m", "sum", + "min", "docker.containers.running", + ) + expected := *(widget.(*datadog.QueryValueWidget)) + + board.Widgets = append(board.Widgets, widget) + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retreiving a screenboard failed: %s", err) + } + + actualWidget, ok := actual.Widgets[0].(*datadog.QueryValueWidget) + if !ok { + t.Fatalf("Widget type does not match: %v", actual.Widgets[0]) + } + + assertEquals(t, "height", actualWidget.Height, expected.Height) + assertEquals(t, "width", actualWidget.Width, expected.Width) + assertEquals(t, "x", actualWidget.X, expected.X) + assertEquals(t, "y", actualWidget.Y, expected.Y) + assertEquals(t, "title", actualWidget.Title, expected.Title) + assertEquals(t, "title-align", actualWidget.TitleAlign, expected.TitleAlign) + assertEquals(t, "title-size.size", actualWidget.TitleSize.Size, expected.TitleSize.Size) + assertEquals(t, "title-size.auto", actualWidget.TitleSize.Auto, expected.TitleSize.Auto) + assertEquals(t, "title-text", actualWidget.TitleText, expected.TitleText) + assertEquals(t, "type", actualWidget.Type, expected.Type) + assertEquals(t, "timeframe", actualWidget.Timeframe, expected.Timeframe) + assertEquals(t, "timeframe-aggregator", actualWidget.TimeframeAggregator, expected.TimeframeAggregator) + assertEquals(t, "aggregator", actualWidget.Aggregator, expected.Aggregator) + assertEquals(t, "query", actualWidget.Query, expected.Query) + + cleanUpScreenboard(t, board.Id) +} + +func assertTileDefEquals(t *testing.T, actual datadog.TileDef, expected datadog.TileDef) { + assertEquals(t, "num-events", len(actual.Events), len(expected.Events)) + assertEquals(t, "num-requests", len(actual.Requests), len(expected.Requests)) + assertEquals(t, "viz", actual.Viz, expected.Viz) + + for i, event := range actual.Events { + assertEquals(t, "event-query", event.Query, expected.Events[i].Query) + } + + for i, request := range actual.Requests { + assertEquals(t, "request-query", request.Query, expected.Requests[i].Query) + assertEquals(t, "request-type", request.Type, expected.Requests[i].Type) + } +} + +func assertEquals(t *testing.T, attribute string, a, b interface{}) { + if a != b { + t.Errorf("The two %s values '%v' and '%v' are not equal", attribute, a, b) + } +} diff --git a/integration_test/screenboards_test.go b/integration_test/screenboards_test.go new file mode 100644 index 0000000..a2d16e3 --- /dev/null +++ b/integration_test/screenboards_test.go @@ -0,0 +1,152 @@ +package integration_test + +import ( + "log" + "os" + "testing" + + "github.com/seiffert/go-datadog-api" +) + +var ( + apiKey string + appKey string + client *datadog.Client +) + +func init() { + apiKey = os.Getenv("DD_API_KEY") + appKey = os.Getenv("DD_APP_KEY") + + if apiKey == "" || appKey == "" { + log.Fatal("Please make sure to set the env variables 'DD_API_KEY' and 'DD_APP_KEY' before running this test") + } + + client = datadog.NewClient(apiKey, appKey) +} + +func TestMain(m *testing.M) { + num := countScreenboards() + + result := m.Run() + + if num != countScreenboards() { + log.Fatal("Tests didn't clean-up all created screenboards. Manual clean-up required.") + } + + os.Exit(result) +} + +func TestCreateAndDeleteScreenboard(t *testing.T) { + expected := getTestScreenboard() + // create the screenboard and compare it + actual, err := client.CreateScreenboard(expected) + if err != nil { + t.Fatalf("Creating a screenboard failed when it shouldn't. (%s)", err) + } + assertScreenboardEquals(t, actual, expected) + + // now try to fetch it freshly and compare it again + actual, err = client.GetScreenboard(actual.Id) + if err != nil { + t.Fatalf("Retreiving a screenboard failed when it shouldn't. (%s)", err) + } + assertScreenboardEquals(t, actual, expected) + cleanUpScreenboard(t, actual.Id) +} + +func TestUpdateScreenboard(t *testing.T) { + board := createTestScreenboard(t) + + board.Title = "___New-Test-Board___" + if err := client.UpdateScreenboard(board); err != nil { + t.Fatalf("Updating a screenboard failed when it shouldn't: %s", err) + } + + actual, err := client.GetScreenboard(board.Id) + if err != nil { + t.Fatalf("Retreiving a screenboard failed when it shouldn't: %s", err) + } + + assertScreenboardEquals(t, actual, board) + cleanUpScreenboard(t, actual.Id) +} + +func TestGetScreenboards(t *testing.T) { + boards, err := client.GetScreenboards() + if err != nil { + t.Fatalf("Retreiving screenboards failed when it shouldn't: %s", err) + } + num := len(boards) + + board := createTestScreenboard(t) + + boards, err = client.GetScreenboards() + if err != nil { + t.Fatalf("Retreiving screenboards failed when it shouldn't: %s", err) + } + + if num+1 != len(boards) { + t.Fatalf("Number of screenboards didn't match expected: %d != %d", len(boards), num+1) + } + + cleanUpScreenboard(t, board.Id) +} + +func getTestScreenboard() *datadog.Screenboard { + return &datadog.Screenboard{ + Title: "___Test-Board___", + Height: 600, + Width: 800, + Widgets: []datadog.Widget{}, + } +} + +func createTestScreenboard(t *testing.T) *datadog.Screenboard { + board := getTestScreenboard() + board, err := client.CreateScreenboard(board) + if err != nil { + t.Fatalf("Creating a screenboard failed when it shouldn't: %s", err) + } + + return board +} + +func cleanUpScreenboard(t *testing.T, id int) { + if err := client.DeleteScreenboard(id); err != nil { + t.Fatalf("Deleting a screenboard failed when it shouldn't. (%s)", err) + } + + deletedBoard, err := client.GetScreenboard(id) + if deletedBoard != nil { + t.Fatal("Screenboard hasn't been deleted when it should have been.") + } + + if err == nil { + t.Fatal("Fetching deleted screenboard didn't lead to an error.") + } +} + +func countScreenboards() int { + boards, err := client.GetScreenboards() + if err != nil { + log.Fatalf("Error retreiving screenboards: %s", err) + } + + return len(boards) +} + +func assertScreenboardEquals(t *testing.T, actual, expected *datadog.Screenboard) { + if actual.Title != expected.Title { + t.Errorf("Screenboard title does not match: %s != %s", actual.Title, expected.Title) + } + if actual.Width != expected.Width { + t.Errorf("Screenboard width does not match: %d != %d", actual.Width, expected.Width) + } + if actual.Height != expected.Height { + t.Errorf("Screenboard width does not match: %d != %d", actual.Height, expected.Height) + } + if len(actual.Widgets) != len(expected.Widgets) { + t.Errorf("Number of Screenboard widgets does not match: %d != %d", len(actual.Widgets), len(expected.Widgets)) + } +} diff --git a/screen_widgets.go b/screen_widgets.go index 384e0e1..e411372 100644 --- a/screen_widgets.go +++ b/screen_widgets.go @@ -31,7 +31,6 @@ func NewTimeseriesWidget( } type TimeseriesWidget struct { - BoardId int `json:"board_id,omitempty"` Height int `json:"height"` Legend bool `json:"legend"` TileDef TileDef `json:"tile_def"` @@ -130,7 +129,6 @@ type QueryValueWidget struct { Timeframe string `json:"timeframe"` TimeframeAggregator string `json:"aggr"` Aggregator string `json:"aggregator"` - BoardId int `json:"board_id,omitempty"` CalcFunc string `json:"calc_func"` ConditionalFormats []ConditionalFormat `json:"conditional_formats"` Height int `json:"height"` @@ -249,18 +247,13 @@ func NewFreeTextWidget(x, y, width, height int, text string, size int, align str } type FreeTextWidget struct { - BoardId int `json:"board_id,omitempty"` - Color string `json:"color,omitempty"` - FontSize string `json:"font_size,omitempty"` - Height int `json:"height,omitempty"` - Text string `json:"text"` - TextAlign string `json:"text_align"` - Title bool `json:"title"` - TitleAlign string `json:"title_align"` - TitleSize TextSize `json:"title_size"` - TitleText string `json:"title_text"` - Type string `json:"type"` - Width int `json:"width"` - X int `json:"x"` - Y int `json:"y"` + Color string `json:"color,omitempty"` + FontSize string `json:"font_size,omitempty"` + Height int `json:"height,omitempty"` + Text string `json:"text"` + TextAlign string `json:"text_align"` + Type string `json:"type"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` } diff --git a/screenboards.go b/screenboards.go index 46f6ac8..32e131a 100644 --- a/screenboards.go +++ b/screenboards.go @@ -19,7 +19,7 @@ type Screenboard struct { Id int `json:"id,omitempty"` Title string `json:"board_title"` Height int `json:"height,omitempty"` - Width string `json:"width,omitempty"` + Width int `json:"width,omitempty"` Shared bool `json:"shared"` Templated bool `json:"templated,omitempty"` TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` @@ -36,7 +36,7 @@ func (s *Screenboard) UnmarshalJSON(data []byte) error { Id int `json:"id"` Title string `json:"board_title"` Height int `json:"height"` - Width string `json:"width"` + Width int `json:"width"` Shared bool `json:"shared"` Templated bool `json:"templated"` TemplateVariables []TemplateVariable `json:"template_variables"` From d6e1b8aded54cf19d3acaf1386703c69fb5c9ca2 Mon Sep 17 00:00:00 2001 From: Paul Seiffert Date: Fri, 22 May 2015 00:15:18 +0200 Subject: [PATCH 5/5] Add image widget --- screen_widgets.go | 32 +++++++++++++++++++++++++++++++- screenboards.go | 14 ++++++++------ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/screen_widgets.go b/screen_widgets.go index e411372..3f13607 100644 --- a/screen_widgets.go +++ b/screen_widgets.go @@ -36,7 +36,7 @@ type TimeseriesWidget struct { TileDef TileDef `json:"tile_def"` Timeframe string `json:"timeframe"` Title bool `json:"title"` - TitleAlign string `json:"string"` + TitleAlign string `json:"title_align"` TitleSize TextSize `json:"title_size"` TitleText string `json:"title_text"` Type string `json:"type"` @@ -257,3 +257,33 @@ type FreeTextWidget struct { X int `json:"x"` Y int `json:"y"` } + +func NewImageWidget(x, y, width, height int, sizing, url string, title bool, titleAlign string, titleSize TextSize, titleText string, +) Widget { + return &ImageWidget{ + X: x, + Y: y, + Width: width, + Height: height, + Type: "image", + Title: title, + TitleAlign: titleAlign, + TitleSize: titleSize, + TitleText: titleText, + Url: url, + } +} + +type ImageWidget struct { + Height int `json:"height"` + Sizing string `json:"sizing"` + Title bool `json:"title"` + TitleAlign string `json:"title_align"` + TitleSize TextSize `json:"title_size"` + TitleText string `json:"title_text"` + Type string `json:"type"` + Url string `json:"url"` + Width int `json:"width"` + X int `json:"x"` + Y int `json:"y"` +} diff --git a/screenboards.go b/screenboards.go index 32e131a..b1d3f13 100644 --- a/screenboards.go +++ b/screenboards.go @@ -18,8 +18,8 @@ import ( type Screenboard struct { Id int `json:"id,omitempty"` Title string `json:"board_title"` - Height int `json:"height,omitempty"` - Width int `json:"width,omitempty"` + Height string `json:"height,omitempty"` + Width string `json:"width,omitempty"` Shared bool `json:"shared"` Templated bool `json:"templated,omitempty"` TemplateVariables []TemplateVariable `json:"template_variables,omitempty"` @@ -35,8 +35,8 @@ func (s *Screenboard) UnmarshalJSON(data []byte) error { dest := struct { Id int `json:"id"` Title string `json:"board_title"` - Height int `json:"height"` - Width int `json:"width"` + Height json.RawMessage `json:"height"` + Width json.RawMessage `json:"width"` Shared bool `json:"shared"` Templated bool `json:"templated"` TemplateVariables []TemplateVariable `json:"template_variables"` @@ -66,8 +66,8 @@ func (s *Screenboard) UnmarshalJSON(data []byte) error { s.Id = dest.Id s.Title = dest.Title - s.Height = dest.Height - s.Width = dest.Width + s.Height = string(dest.Height) + s.Width = string(dest.Width) s.Shared = dest.Shared s.Templated = dest.Templated s.TemplateVariables = dest.TemplateVariables @@ -89,6 +89,8 @@ func unmarshalWidget(widgetType string, data json.RawMessage) (Widget, error) { dest = &FreeTextWidget{} case "toplist": dest = &ToplistWidget{} + case "image": + dest = &ImageWidget{} default: return nil, fmt.Errorf("Could not unmarshal unknown widget type %s.", widgetType) }