diff --git a/decoder/dashboard_test.go b/decoder/dashboard_test.go index 1fb40477..0930666d 100644 --- a/decoder/dashboard_test.go +++ b/decoder/dashboard_test.go @@ -675,6 +675,8 @@ rows: title: Total Request per Second description: Does it perform? span: 12 + links: + - { title: linky, url: http://linky } targets: - prometheus: query: "go_memstats_heap_alloc_bytes" diff --git a/decoder/graph.go b/decoder/graph.go index d130e9da..431c5226 100644 --- a/decoder/graph.go +++ b/decoder/graph.go @@ -20,6 +20,7 @@ type DashboardGraph struct { Datasource string `yaml:",omitempty"` Repeat string `yaml:",omitempty"` Targets []Target + Links DashboardPanelLinks `yaml:",omitempty"` Axes *GraphAxes `yaml:",omitempty"` Legend []string `yaml:",omitempty,flow"` Alert *Alert `yaml:",omitempty"` @@ -47,6 +48,9 @@ func (graphPanel DashboardGraph) toOption() (row.Option, error) { if graphPanel.Repeat != "" { opts = append(opts, graph.Repeat(graphPanel.Repeat)) } + if len(graphPanel.Links) != 0 { + opts = append(opts, graph.Links(graphPanel.Links.toModel()...)) + } if graphPanel.Axes != nil && graphPanel.Axes.Right != nil { opts = append(opts, graph.RightYAxis(graphPanel.Axes.Right.toOptions()...)) } diff --git a/decoder/heatmap.go b/decoder/heatmap.go index 1f28b304..b5e0d7a7 100644 --- a/decoder/heatmap.go +++ b/decoder/heatmap.go @@ -13,15 +13,16 @@ var ErrInvalidDataFormat = fmt.Errorf("invalid data format") // DashboardHeatmap represents a heatmap panel. type DashboardHeatmap struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - Datasource string `yaml:",omitempty"` - Repeat string `yaml:",omitempty"` - DataFormat string `yaml:"data_format,omitempty"` - HideZeroBuckets bool `yaml:"hide_zero_buckets"` - HighlightCards bool `yaml:"highlight_cards"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Datasource string `yaml:",omitempty"` + Repeat string `yaml:",omitempty"` + DataFormat string `yaml:"data_format,omitempty"` + HideZeroBuckets bool `yaml:"hide_zero_buckets"` + HighlightCards bool `yaml:"highlight_cards"` + Links DashboardPanelLinks `yaml:",omitempty"` Targets []Target ReverseYBuckets bool `yaml:"reverse_y_buckets,omitempty"` Tooltip *HeatmapTooltip `yaml:",omitempty"` @@ -108,6 +109,9 @@ func (heatmapPanel DashboardHeatmap) toOption() (row.Option, error) { if heatmapPanel.Repeat != "" { opts = append(opts, heatmap.Repeat(heatmapPanel.Repeat)) } + if len(heatmapPanel.Links) != 0 { + opts = append(opts, heatmap.Links(heatmapPanel.Links.toModel()...)) + } if heatmapPanel.DataFormat != "" { switch heatmapPanel.DataFormat { case "time_series_buckets": diff --git a/decoder/logs.go b/decoder/logs.go index 9e41ad93..d8ae44c1 100644 --- a/decoder/logs.go +++ b/decoder/logs.go @@ -12,14 +12,15 @@ var ErrInvalidDeduplicationStrategy = fmt.Errorf("invalid deduplication strategy type DashboardLogs struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - Datasource string `yaml:",omitempty"` - Repeat string `yaml:",omitempty"` - Targets []LogsTarget `yaml:",omitempty"` - Visualization *LogsVisualization `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Datasource string `yaml:",omitempty"` + Repeat string `yaml:",omitempty"` + Links DashboardPanelLinks `yaml:",omitempty"` + Targets []LogsTarget `yaml:",omitempty"` + Visualization *LogsVisualization `yaml:",omitempty"` } type LogsTarget struct { @@ -47,6 +48,9 @@ func (panel DashboardLogs) toOption() (row.Option, error) { if panel.Repeat != "" { opts = append(opts, logs.Repeat(panel.Repeat)) } + if len(panel.Links) != 0 { + opts = append(opts, logs.Links(panel.Links.toModel()...)) + } for _, t := range panel.Targets { opt, err := panel.target(t) if err != nil { diff --git a/decoder/panellink.go b/decoder/panellink.go new file mode 100644 index 00000000..69da2160 --- /dev/null +++ b/decoder/panellink.go @@ -0,0 +1,31 @@ +package decoder + +import ( + "github.com/K-Phoen/grabana/links" +) + +type DashboardPanelLinks []DashboardPanelLink + +func (collection DashboardPanelLinks) toModel() []links.Link { + models := make([]links.Link, 0, len(collection)) + + for _, link := range collection { + models = append(models, link.toModel()) + } + + return models +} + +type DashboardPanelLink struct { + Title string + URL string `yaml:"url"` + OpenInNewTab bool `yaml:"open_in_new_tab,omitempty"` +} + +func (l DashboardPanelLink) toModel() links.Link { + if l.OpenInNewTab { + return links.New(l.Title, l.URL, links.OpenBlank()) + } + + return links.New(l.Title, l.URL) +} diff --git a/decoder/panellink_test.go b/decoder/panellink_test.go new file mode 100644 index 00000000..055e8967 --- /dev/null +++ b/decoder/panellink_test.go @@ -0,0 +1,36 @@ +package decoder + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPanelLink(t *testing.T) { + req := require.New(t) + + yamlLink := DashboardPanelLink{ + Title: "joe", + URL: "http://foo", + OpenInNewTab: true, + } + + model := yamlLink.toModel() + + req.Equal("joe", model.Builder.Title) + req.Equal("http://foo", *model.Builder.URL) + req.True(*model.Builder.TargetBlank) +} + +func TestPanelLinks(t *testing.T) { + req := require.New(t) + + yamlLinks := DashboardPanelLinks{ + {Title: "joe", URL: "http://foo"}, + {Title: "bar", URL: "http://baz"}, + } + + model := yamlLinks.toModel() + + req.Len(model, 2) +} diff --git a/decoder/singlestat.go b/decoder/singlestat.go index 5909e206..8376cf0f 100644 --- a/decoder/singlestat.go +++ b/decoder/singlestat.go @@ -13,12 +13,13 @@ var ErrInvalidSingleStatValueType = fmt.Errorf("invalid single stat value type") type DashboardSingleStat struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - Datasource string `yaml:",omitempty"` - Repeat string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Datasource string `yaml:",omitempty"` + Repeat string `yaml:",omitempty"` + Links DashboardPanelLinks `yaml:",omitempty"` Unit string Decimals *int `yaml:",omitempty"` ValueType string `yaml:"value_type"` @@ -54,6 +55,9 @@ func (singleStatPanel DashboardSingleStat) toOption() (row.Option, error) { if singleStatPanel.Repeat != "" { opts = append(opts, singlestat.Repeat(singleStatPanel.Repeat)) } + if len(singleStatPanel.Links) != 0 { + opts = append(opts, singlestat.Links(singleStatPanel.Links.toModel()...)) + } if singleStatPanel.Unit != "" { opts = append(opts, singlestat.Unit(singleStatPanel.Unit)) } diff --git a/decoder/stat.go b/decoder/stat.go index 26a8a5ea..44027501 100644 --- a/decoder/stat.go +++ b/decoder/stat.go @@ -20,12 +20,13 @@ type StatThresholdStep struct { type DashboardStat struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - Datasource string `yaml:",omitempty"` - Repeat string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Datasource string `yaml:",omitempty"` + Repeat string `yaml:",omitempty"` + Links DashboardPanelLinks `yaml:",omitempty"` Targets []Target Unit string `yaml:",omitempty"` @@ -64,6 +65,9 @@ func (statPanel DashboardStat) toOption() (row.Option, error) { if statPanel.Repeat != "" { opts = append(opts, stat.Repeat(statPanel.Repeat)) } + if len(statPanel.Links) != 0 { + opts = append(opts, stat.Links(statPanel.Links.toModel()...)) + } if statPanel.Unit != "" { opts = append(opts, stat.Unit(statPanel.Unit)) } diff --git a/decoder/table.go b/decoder/table.go index 41ad9f07..55abc7bd 100644 --- a/decoder/table.go +++ b/decoder/table.go @@ -8,11 +8,12 @@ import ( // DashboardTable represents a table panel. type DashboardTable struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - Datasource string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Datasource string `yaml:",omitempty"` + Links DashboardPanelLinks `yaml:",omitempty"` Targets []Target HiddenColumns []string `yaml:"hidden_columns,flow"` TimeSeriesAggregations []table.Aggregation `yaml:"time_series_aggregations"` @@ -36,6 +37,9 @@ func (tablePanel DashboardTable) toOption() (row.Option, error) { if tablePanel.Datasource != "" { opts = append(opts, table.DataSource(tablePanel.Datasource)) } + if len(tablePanel.Links) != 0 { + opts = append(opts, table.Links(tablePanel.Links.toModel()...)) + } for _, t := range tablePanel.Targets { opt, err := tablePanel.target(t) diff --git a/decoder/testdata/timeseries_panel.json b/decoder/testdata/timeseries_panel.json index 5eae45de..4f04b64e 100644 --- a/decoder/testdata/timeseries_panel.json +++ b/decoder/testdata/timeseries_panel.json @@ -77,6 +77,14 @@ "description": "Does it perform?", "transparent": false, "type": "timeseries", + "links": [ + { + "includeVars": false, + "title": "linky", + "url": "http://linky", + "type": "" + } + ], "targets": [ { "refId": "", diff --git a/decoder/text.go b/decoder/text.go index 3b337874..b469444d 100644 --- a/decoder/text.go +++ b/decoder/text.go @@ -7,12 +7,13 @@ import ( type DashboardText struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - HTML string `yaml:",omitempty"` - Markdown string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Links DashboardPanelLinks `yaml:",omitempty"` + HTML string `yaml:",omitempty"` + Markdown string `yaml:",omitempty"` } func (textPanel DashboardText) toOption() row.Option { @@ -24,6 +25,9 @@ func (textPanel DashboardText) toOption() row.Option { if textPanel.Span != 0 { opts = append(opts, text.Span(textPanel.Span)) } + if len(textPanel.Links) != 0 { + opts = append(opts, text.Links(textPanel.Links.toModel()...)) + } if textPanel.Height != "" { opts = append(opts, text.Height(textPanel.Height)) } diff --git a/decoder/timeseries.go b/decoder/timeseries.go index 5466afeb..4ec89382 100644 --- a/decoder/timeseries.go +++ b/decoder/timeseries.go @@ -16,12 +16,13 @@ var ErrInvalidAxisScale = fmt.Errorf("invalid axis scale") type DashboardTimeSeries struct { Title string - Description string `yaml:",omitempty"` - Span float32 `yaml:",omitempty"` - Height string `yaml:",omitempty"` - Transparent bool `yaml:",omitempty"` - Datasource string `yaml:",omitempty"` - Repeat string `yaml:",omitempty"` + Description string `yaml:",omitempty"` + Span float32 `yaml:",omitempty"` + Height string `yaml:",omitempty"` + Transparent bool `yaml:",omitempty"` + Datasource string `yaml:",omitempty"` + Repeat string `yaml:",omitempty"` + Links DashboardPanelLinks `yaml:",omitempty"` Targets []Target Legend []string `yaml:",omitempty,flow"` Alert *Alert `yaml:",omitempty"` @@ -50,6 +51,9 @@ func (timeseriesPanel DashboardTimeSeries) toOption() (row.Option, error) { if timeseriesPanel.Repeat != "" { opts = append(opts, timeseries.Repeat(timeseriesPanel.Repeat)) } + if len(timeseriesPanel.Links) != 0 { + opts = append(opts, timeseries.Links(timeseriesPanel.Links.toModel()...)) + } if len(timeseriesPanel.Legend) != 0 { legendOpts, err := timeseriesPanel.legend() if err != nil { diff --git a/graph/graph.go b/graph/graph.go index adb4042b..74aeae2c 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -7,6 +7,7 @@ import ( "github.com/K-Phoen/grabana/axis" "github.com/K-Phoen/grabana/errors" "github.com/K-Phoen/grabana/graph/series" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/graphite" "github.com/K-Phoen/grabana/target/influxdb" "github.com/K-Phoen/grabana/target/prometheus" @@ -120,6 +121,19 @@ func defaultAxes() Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(graph *Graph) error { + graph.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + graph.Builder.Links = append(graph.Builder.Links, link.Builder) + } + + return nil + } +} + // WithPrometheusTarget adds a prometheus query to the graph. func WithPrometheusTarget(query string, options ...prometheus.Option) Option { target := prometheus.New(query, options...) diff --git a/graph/graph_test.go b/graph/graph_test.go index 878220e9..8b347112 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -6,6 +6,7 @@ import ( "github.com/K-Phoen/grabana/axis" "github.com/K-Phoen/grabana/errors" "github.com/K-Phoen/grabana/graph/series" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/stackdriver" "github.com/stretchr/testify/require" ) @@ -21,6 +22,15 @@ func TestNewGraphPanelsCanBeCreated(t *testing.T) { req.Equal(float32(6), panel.Builder.Span) } +func TestGraphPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestGraphPanelCanHavePrometheusTargets(t *testing.T) { req := require.New(t) diff --git a/heatmap/heatmap.go b/heatmap/heatmap.go index 7fff5ff1..5931c4e4 100644 --- a/heatmap/heatmap.go +++ b/heatmap/heatmap.go @@ -5,6 +5,7 @@ import ( "github.com/K-Phoen/grabana/errors" "github.com/K-Phoen/grabana/heatmap/axis" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/graphite" "github.com/K-Phoen/grabana/target/influxdb" "github.com/K-Phoen/grabana/target/prometheus" @@ -108,6 +109,19 @@ func defaultYAxis() Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(heatmap *Heatmap) error { + heatmap.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + heatmap.Builder.Links = append(heatmap.Builder.Links, link.Builder) + } + + return nil + } +} + // DataSource sets the data source to be used by the panel. func DataSource(source string) Option { return func(heatmap *Heatmap) error { diff --git a/heatmap/heatmap_test.go b/heatmap/heatmap_test.go index 3b50e0f8..d86873f0 100644 --- a/heatmap/heatmap_test.go +++ b/heatmap/heatmap_test.go @@ -5,6 +5,7 @@ import ( "github.com/K-Phoen/grabana/errors" "github.com/K-Phoen/grabana/heatmap/axis" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/stackdriver" "github.com/stretchr/testify/require" ) @@ -20,6 +21,15 @@ func TestNewHeatmapPanelsCanBeCreated(t *testing.T) { req.Equal(float32(6), panel.Builder.Span) } +func TestHeatmapPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestHeatmapPanelCanHavePrometheusTargets(t *testing.T) { req := require.New(t) diff --git a/links/links.go b/links/links.go new file mode 100644 index 00000000..084a9006 --- /dev/null +++ b/links/links.go @@ -0,0 +1,35 @@ +package links + +import ( + "github.com/K-Phoen/sdk" +) + +// Option represents an option that can be used to configure a panel link. +type Option func(link *Link) + +// Link represents a panel link. +type Link struct { + Builder sdk.Link +} + +// New creates a new logs panel. +func New(title string, url string, options ...Option) Link { + link := &Link{Builder: sdk.Link{ + Title: title, + URL: &url, + }} + + for _, opt := range options { + opt(link) + } + + return *link +} + +// OpenBlank configures the link to open in a new tab. +func OpenBlank() Option { + return func(link *Link) { + yep := true + link.Builder.TargetBlank = &yep + } +} diff --git a/links/links_test.go b/links/links_test.go new file mode 100644 index 00000000..6e616180 --- /dev/null +++ b/links/links_test.go @@ -0,0 +1,24 @@ +package links + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestNewPanelLinksCanBeCreated(t *testing.T) { + req := require.New(t) + + link := New("link name", "https://github.com/grafana/grafana/") + + req.Equal("link name", link.Builder.Title) + req.Equal("https://github.com/grafana/grafana/", *link.Builder.URL) +} + +func TestLinksCanBeOpenedAsBlank(t *testing.T) { + req := require.New(t) + + link := New("", "", OpenBlank()) + + req.True(*link.Builder.TargetBlank) +} diff --git a/logs/logs.go b/logs/logs.go index c1ff0e58..13171cfd 100644 --- a/logs/logs.go +++ b/logs/logs.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/loki" "github.com/K-Phoen/sdk" ) @@ -56,6 +57,19 @@ func defaults() []Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(logs *Logs) error { + logs.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + logs.Builder.Links = append(logs.Builder.Links, link.Builder) + } + + return nil + } +} + // DataSource sets the data source to be used by the panel. func DataSource(source string) Option { return func(logs *Logs) error { diff --git a/logs/logs_test.go b/logs/logs_test.go index 55258ce3..050078f4 100644 --- a/logs/logs_test.go +++ b/logs/logs_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/stretchr/testify/require" ) @@ -20,6 +21,15 @@ func TestNewLogsPanelsCanBeCreated(t *testing.T) { req.True(panel.Builder.LogsPanel.Options.EnableLogDetails) } +func TestLogsPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestLogsPanelCanHaveLokiTargets(t *testing.T) { req := require.New(t) diff --git a/singlestat/singlestat.go b/singlestat/singlestat.go index b0d7ef9a..3e5731fa 100644 --- a/singlestat/singlestat.go +++ b/singlestat/singlestat.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/graphite" "github.com/K-Phoen/grabana/target/influxdb" "github.com/K-Phoen/grabana/target/prometheus" @@ -128,6 +129,19 @@ func defaults() []Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(singleStat *SingleStat) error { + singleStat.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + singleStat.Builder.Links = append(singleStat.Builder.Links, link.Builder) + } + + return nil + } +} + // DataSource sets the data source to be used by the panel. func DataSource(source string) Option { return func(singleStat *SingleStat) error { diff --git a/singlestat/singlestat_test.go b/singlestat/singlestat_test.go index 9c5b1ff3..07b8d063 100644 --- a/singlestat/singlestat_test.go +++ b/singlestat/singlestat_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/stackdriver" "github.com/stretchr/testify/require" ) @@ -19,6 +20,15 @@ func TestNewSingleStatPanelsCanBeCreated(t *testing.T) { req.Equal(float32(6), panel.Builder.Span) } +func TestSingleStatPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestSingleStatPanelCanHavePrometheusTargets(t *testing.T) { req := require.New(t) diff --git a/stat/stat.go b/stat/stat.go index 3dfa004e..4d89e866 100644 --- a/stat/stat.go +++ b/stat/stat.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/scheme" "github.com/K-Phoen/grabana/target/graphite" "github.com/K-Phoen/grabana/target/influxdb" @@ -114,6 +115,19 @@ func defaults() []Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(stat *Stat) error { + stat.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + stat.Builder.Links = append(stat.Builder.Links, link.Builder) + } + + return nil + } +} + // DataSource sets the data source to be used by the panel. func DataSource(source string) Option { return func(stat *Stat) error { diff --git a/stat/stat_test.go b/stat/stat_test.go index d606451f..ac082ea4 100644 --- a/stat/stat_test.go +++ b/stat/stat_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/stackdriver" "github.com/stretchr/testify/require" ) @@ -19,6 +20,15 @@ func TestNewStatPanelsCanBeCreated(t *testing.T) { req.Equal(float32(6), panel.Builder.Span) } +func TestStatPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestStatPanelCanHavePrometheusTargets(t *testing.T) { req := require.New(t) diff --git a/table/table.go b/table/table.go index 39777b9e..dec5de69 100644 --- a/table/table.go +++ b/table/table.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/target/graphite" "github.com/K-Phoen/grabana/target/influxdb" "github.com/K-Phoen/grabana/target/prometheus" @@ -75,6 +76,19 @@ func defaults() []Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(table *Table) error { + table.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + table.Builder.Links = append(table.Builder.Links, link.Builder) + } + + return nil + } +} + // WithPrometheusTarget adds a prometheus target to the table. func WithPrometheusTarget(query string, options ...prometheus.Option) Option { target := prometheus.New(query, options...) diff --git a/table/table_test.go b/table/table_test.go index f6ac8021..836b835f 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/stretchr/testify/require" ) @@ -18,6 +19,15 @@ func TestNewTablePanelsCanBeCreated(t *testing.T) { req.Equal(float32(6), panel.Builder.Span) } +func TestTablePanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestTablePanelWidthCanBeConfigured(t *testing.T) { req := require.New(t) diff --git a/text/text.go b/text/text.go index 1b62c752..8e738402 100644 --- a/text/text.go +++ b/text/text.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/sdk" ) @@ -31,6 +32,19 @@ func New(title string, options ...Option) (*Text, error) { return panel, nil } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(text *Text) error { + text.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + text.Builder.Links = append(text.Builder.Links, link.Builder) + } + + return nil + } +} + // HTML sets the content of the panel, to be rendered as HTML. func HTML(content string) Option { return func(text *Text) error { diff --git a/text/text_test.go b/text/text_test.go index f959718e..dac588ed 100644 --- a/text/text_test.go +++ b/text/text_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/stretchr/testify/require" ) @@ -20,6 +21,15 @@ func TestNewTextPanelsCanBeCreated(t *testing.T) { req.Empty(panel.Builder.TextPanel.Mode) } +func TestTextPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestTextPanelsCanBeHTML(t *testing.T) { req := require.New(t) content := "lala" diff --git a/timeseries/timeseries.go b/timeseries/timeseries.go index 1a3239f1..2892ad32 100644 --- a/timeseries/timeseries.go +++ b/timeseries/timeseries.go @@ -5,6 +5,7 @@ import ( "github.com/K-Phoen/grabana/alert" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/scheme" "github.com/K-Phoen/grabana/timeseries/axis" "github.com/K-Phoen/grabana/timeseries/fields" @@ -144,6 +145,19 @@ func defaults() []Option { } } +// Links adds links to be displayed on this panel. +func Links(panelLinks ...links.Link) Option { + return func(timeseries *TimeSeries) error { + timeseries.Builder.Links = make([]sdk.Link, 0, len(panelLinks)) + + for _, link := range panelLinks { + timeseries.Builder.Links = append(timeseries.Builder.Links, link.Builder) + } + + return nil + } +} + // DataSource sets the data source to be used by the graph. func DataSource(source string) Option { return func(timeseries *TimeSeries) error { diff --git a/timeseries/timeseries_test.go b/timeseries/timeseries_test.go index faa36178..b7d0e0b6 100644 --- a/timeseries/timeseries_test.go +++ b/timeseries/timeseries_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/K-Phoen/grabana/errors" + "github.com/K-Phoen/grabana/links" "github.com/K-Phoen/grabana/scheme" "github.com/K-Phoen/grabana/target/stackdriver" "github.com/K-Phoen/grabana/timeseries/axis" @@ -24,6 +25,15 @@ func TestNewTimeSeriesPanelsCanBeCreated(t *testing.T) { req.Equal(float32(6), panel.Builder.Span) } +func TestTimeSeriesPanelCanHaveLinks(t *testing.T) { + req := require.New(t) + + panel, err := New("", Links(links.New("", ""))) + + req.NoError(err) + req.Len(panel.Builder.Links, 1) +} + func TestTimeSeriesPanelCanHavePrometheusTargets(t *testing.T) { req := require.New(t)