From 1b8760794193d3abfe28011ef81d4da8e4976004 Mon Sep 17 00:00:00 2001 From: srjames90 Date: Fri, 21 Jun 2024 17:29:05 -0400 Subject: [PATCH 1/9] support thresholds in dashboard chart --- client/metric_dashboards.go | 10 +++ lightstep/resource_dashboard.go | 43 ++++++++++++ lightstep/resource_metric_dashboard.go | 7 ++ lightstep/resource_metric_dashboard_test.go | 73 +++++++++++++++++++++ 4 files changed, 133 insertions(+) diff --git a/client/metric_dashboards.go b/client/metric_dashboards.go index b6dce751..4a7405a3 100644 --- a/client/metric_dashboards.go +++ b/client/metric_dashboards.go @@ -51,6 +51,7 @@ type UnifiedChart struct { YAxis *YAxis `json:"y-axis"` MetricQueries []MetricQueryWithAttributes `json:"metric-queries"` Text string `json:"text"` + Thresholds []Threshold `json:"thresholds"` Subtitle *string `json:"subtitle,omitempty"` } @@ -67,6 +68,15 @@ type Panel struct { Body map[string]any `json:"body"` } +type Threshold struct { + // enum: E,GE,GT,LE,LT + Operator string `json:"operator"` + Value float64 `json:"value"` + // An alpha hex color + Color string `json:"color"` + Label *string `json:"label"` +} + type YAxis struct { Min float64 `json:"min"` Max float64 `json:"max"` diff --git a/lightstep/resource_dashboard.go b/lightstep/resource_dashboard.go index 45bd33f5..33f1da53 100644 --- a/lightstep/resource_dashboard.go +++ b/lightstep/resource_dashboard.go @@ -9,6 +9,49 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) +func getThresholdSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "color": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "#AA3018", + "#B67D0C", + "#3C864F", + "#1B7BBB", + "#DC7847", + "#78469B", + "#37A2AE", + "#B03B7F", + "#1F40C1", + "#8B7255", + "#826CEF", + "#D56DD5", + "#6699CC", + }, false), + }, + "label": { + Type: schema.TypeString, + Optional: true, + }, + "operator": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + "E", + "GE", + "GT", + "LE", + "LT", + }, false), + }, + "value": { + Type: schema.TypeFloat, + Required: true, + }, + } +} + func getUnifiedQuerySchemaMap() map[string]*schema.Schema { sma := map[string]*schema.Schema{ "hidden": { diff --git a/lightstep/resource_metric_dashboard.go b/lightstep/resource_metric_dashboard.go index caec8a32..122d3a9b 100644 --- a/lightstep/resource_metric_dashboard.go +++ b/lightstep/resource_metric_dashboard.go @@ -294,6 +294,13 @@ func getChartSchema(chartSchemaType ChartSchemaType) map[string]*schema.Schema { Optional: true, ValidateFunc: validation.StringLenBetween(0, 256), }, + "threshold": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: getThresholdSchema(), + }, + }, }, ) } diff --git a/lightstep/resource_metric_dashboard_test.go b/lightstep/resource_metric_dashboard_test.go index 55e78620..e52c18d5 100644 --- a/lightstep/resource_metric_dashboard_test.go +++ b/lightstep/resource_metric_dashboard_test.go @@ -464,6 +464,79 @@ resource "lightstep_metric_dashboard" "test" { }) } +func TestAccDashboardChartThresholds(t *testing.T) { + var dashboard client.UnifiedDashboard + + dashboardConfig := ` +resource "lightstep_metric_dashboard" "test" { + project_name = "` + testProject + `" + dashboard_name = "Acceptance Test Dashboard (TestAccDashboardChartThresholds)" + dashboard_description = "Dashboard to test thresholds in charts" + + group { + rank = 0 + visibility_type = "implicit" + + chart { + name = "cpu" + rank = 1 + type = "timeseries" + + query { + display = "line" + hidden = false + query_name = "a" + tql = "metric cpu.utilization | latest | group_by [], sum" + } + + threshold { + color = "#AA3018" + label = "critical" + operator = "GT" + value = 99 + } + + threshold { + color = "#6699CC" + operator = "GT" + value = 199 + } + } + } +} +` + resourceName := "lightstep_metric_dashboard.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testGetMetricDashboardDestroy, + Steps: []resource.TestStep{ + { + // Create the initial dashboard with a chart and make sure it has thresholds + Config: dashboardConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckMetricDashboardExists(resourceName, &dashboard), + + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.name", "cpu"), + + // First threshold in chart + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.color", "#AA3018"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.label", "critical"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.operator", "GT"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.value", "99"), + + // Second threshold in chart + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.color", "#6699CC"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.operator", "GT"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.value", "199"), + resource.TestCheckNoResourceAttr(resourceName, "group.0.chart.0.threshold.0.label"), + ), + }, + }, + }) +} + func TestAccDashboardEventQueries(t *testing.T) { var dashboard client.UnifiedDashboard var eventQuery client.EventQueryAttributes From d2e9c4752ba67bc84d5346f1aac1ca4fac07598b Mon Sep 17 00:00:00 2001 From: srjames90 Date: Fri, 21 Jun 2024 20:44:24 -0400 Subject: [PATCH 2/9] actually add tresholds to resource and fix tests --- lightstep/resource_metric_dashboard.go | 92 +++++++++++++++++++++ lightstep/resource_metric_dashboard_test.go | 10 +-- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/lightstep/resource_metric_dashboard.go b/lightstep/resource_metric_dashboard.go index 122d3a9b..b4b69348 100644 --- a/lightstep/resource_metric_dashboard.go +++ b/lightstep/resource_metric_dashboard.go @@ -587,6 +587,11 @@ func buildCharts(chartsIn []interface{}) ([]client.UnifiedChart, error) { return nil, err } c.MetricQueries = queries + thresholds, err := buildChartThresholds(chart["threshold"].([]interface{})) + if err != nil { + return nil, err + } + c.Thresholds = thresholds yaxis, err := buildYAxis(chart["y_axis"].([]interface{})) if err != nil { @@ -606,6 +611,78 @@ func buildCharts(chartsIn []interface{}) ([]client.UnifiedChart, error) { return newCharts, nil } +var colors = map[string]bool{ + "#AA3018": true, + "#B67D0C": true, + "#3C864F": true, + "#1B7BBB": true, + "#DC7847": true, + "#78469B": true, + "#37A2AE": true, + "#B03B7F": true, + "#1F40C1": true, + "#8B7255": true, + "#826CEF": true, + "#D56DD5": true, + "#6699CC": true, +} + +var operators = map[string]bool{ + "E": true, + "GE": true, + "GT": true, + "LE": true, + "LT": true, +} + +func buildChartThresholds(thresholdsIn []interface{}) ([]client.Threshold, error) { + var thresholds []client.Threshold + if len(thresholdsIn) < 1 { + return thresholds, nil + } + for _, t := range thresholdsIn { + threshold := t.(map[string]interface{}) + color, ok := threshold["color"].(string) + + if !ok { + return []client.Threshold{}, fmt.Errorf("missing required attribute 'color' for threshold") + } + + if !colors[color] { + return []client.Threshold{}, fmt.Errorf("invalid value for attribute 'color' for threshold") + } + + operator, ok := threshold["operator"].(string) + if !ok { + return []client.Threshold{}, fmt.Errorf("missing required attribute 'operator' for threshold") + } + + if !operators[operator] { + return []client.Threshold{}, fmt.Errorf("invalid value for attribute 'operator' for threshold") + } + + label, ok := threshold["label"].(string) + + if !ok { + return []client.Threshold{}, fmt.Errorf("missing required attribute 'label' for threshold") + } + + value, ok := threshold["value"].(float64) + + if !ok { + return []client.Threshold{}, fmt.Errorf("missing required attribute 'value' for threshold") + } + + thresholds = append(thresholds, client.Threshold{ + Color: color, + Label: &label, + Operator: operator, + Value: value, + }) + } + return thresholds, nil +} + func buildYAxis(yAxisIn []interface{}) (*client.YAxis, error) { if len(yAxisIn) < 1 { return nil, nil @@ -852,11 +929,26 @@ func assembleCharts( resource["query"] = queries } + resource["threshold"] = getThresholdsFromResourceData(c.Thresholds) + chartResources = append(chartResources, resource) } return chartResources, nil } +func getThresholdsFromResourceData(thresholdsIn []client.Threshold) []interface{} { + var thresholds []interface{} + for _, t := range thresholdsIn { + thresholds = append(thresholds, map[string]interface{}{ + "color": t.Color, + "label": t.Label, + "operator": t.Operator, + "value": t.Value, + }) + } + return thresholds +} + // isLegacyImplicitGroup defines the logic for determining if the charts in this dashboard need to be unwrapped to // maintain backwards compatibility with the pre group definition func isLegacyImplicitGroup(groups []client.UnifiedGroup, hasLegacyChartsIn bool) bool { diff --git a/lightstep/resource_metric_dashboard_test.go b/lightstep/resource_metric_dashboard_test.go index e52c18d5..45c913a8 100644 --- a/lightstep/resource_metric_dashboard_test.go +++ b/lightstep/resource_metric_dashboard_test.go @@ -517,8 +517,8 @@ resource "lightstep_metric_dashboard" "test" { Config: dashboardConfig, Check: resource.ComposeTestCheckFunc( testAccCheckMetricDashboardExists(resourceName, &dashboard), - resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.name", "cpu"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.#", "2"), // First threshold in chart resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.color", "#AA3018"), @@ -527,10 +527,10 @@ resource "lightstep_metric_dashboard" "test" { resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.value", "99"), // Second threshold in chart - resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.color", "#6699CC"), - resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.operator", "GT"), - resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.value", "199"), - resource.TestCheckNoResourceAttr(resourceName, "group.0.chart.0.threshold.0.label"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.color", "#6699CC"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.operator", "GT"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.value", "199"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.label", ""), ), }, }, From 5ae61072513e2ac15f5ca28904832729fc6079cc Mon Sep 17 00:00:00 2001 From: srjames90 Date: Fri, 21 Jun 2024 20:52:07 -0400 Subject: [PATCH 3/9] add updated config test --- lightstep/resource_metric_dashboard_test.go | 60 +++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/lightstep/resource_metric_dashboard_test.go b/lightstep/resource_metric_dashboard_test.go index 45c913a8..e47381e0 100644 --- a/lightstep/resource_metric_dashboard_test.go +++ b/lightstep/resource_metric_dashboard_test.go @@ -504,6 +504,45 @@ resource "lightstep_metric_dashboard" "test" { } } } +` + updatedDashboardConfig := ` +resource "lightstep_metric_dashboard" "test" { + project_name = "` + testProject + `" + dashboard_name = "Acceptance Test Dashboard (TestAccDashboardChartThresholds)" + dashboard_description = "Dashboard to test thresholds in charts" + + group { + rank = 0 + visibility_type = "implicit" + + chart { + name = "cpu" + rank = 1 + type = "timeseries" + + query { + display = "line" + hidden = false + query_name = "a" + tql = "metric cpu.utilization | latest | group_by [], sum" + } + + threshold { + color = "#AA3018" + label = "critical" + operator = "GT" + value = 99 + } + + threshold { + color = "#6699CC" + label = "extra critical" + operator = "GT" + value = 199 + } + } + } +} ` resourceName := "lightstep_metric_dashboard.test" @@ -533,6 +572,27 @@ resource "lightstep_metric_dashboard" "test" { resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.label", ""), ), }, + { + // Updated config will contain the a label for the second threshold + Config: updatedDashboardConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckMetricDashboardExists(resourceName, &dashboard), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.name", "cpu"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.#", "2"), + + // First threshold in chart + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.color", "#AA3018"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.label", "critical"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.operator", "GT"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.0.value", "99"), + + // Second threshold in chart + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.color", "#6699CC"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.operator", "GT"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.value", "199"), + resource.TestCheckResourceAttr(resourceName, "group.0.chart.0.threshold.1.label", "extra critical"), + ), + }, }, }) } From e5621188fdb42bf1badee2fb04d454b6fc7fd674 Mon Sep 17 00:00:00 2001 From: srjames90 Date: Mon, 24 Jun 2024 14:13:01 -0400 Subject: [PATCH 4/9] update version --- .go-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.go-version b/.go-version index 00bce9ce..3ce51a95 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.95.8 +1.95.9 From d67ebb91a79f746924bdef0dd2b23e2fff10f8ed Mon Sep 17 00:00:00 2001 From: srjames90 Date: Mon, 24 Jun 2024 18:18:04 +0000 Subject: [PATCH 5/9] added terraform docs --- docs/resources/dashboard.md | 30 ++++++++++++++++++++++++++++++ docs/resources/metric_dashboard.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/docs/resources/dashboard.md b/docs/resources/dashboard.md index 0860c52d..0261009c 100644 --- a/docs/resources/dashboard.md +++ b/docs/resources/dashboard.md @@ -92,6 +92,7 @@ Optional: - `description` (String) - `height` (Number) - `subtitle` (String) Subtitle to show beneath big number, unused in other chart types +- `threshold` (Block List) (see [below for nested schema](#nestedblock--chart--threshold)) - `width` (Number) - `x_pos` (Number) - `y_axis` (Block List, Max: 1, Deprecated) (see [below for nested schema](#nestedblock--chart--y_axis)) @@ -143,6 +144,20 @@ Optional: + +### Nested Schema for `chart.threshold` + +Required: + +- `color` (String) +- `operator` (String) +- `value` (Number) + +Optional: + +- `label` (String) + + ### Nested Schema for `chart.y_axis` @@ -244,6 +259,7 @@ Optional: - `description` (String) - `height` (Number) - `subtitle` (String) Subtitle to show beneath big number, unused in other chart types +- `threshold` (Block List) (see [below for nested schema](#nestedblock--group--chart--threshold)) - `width` (Number) - `x_pos` (Number) - `y_axis` (Block List, Max: 1, Deprecated) (see [below for nested schema](#nestedblock--group--chart--y_axis)) @@ -295,6 +311,20 @@ Optional: + +### Nested Schema for `group.chart.threshold` + +Required: + +- `color` (String) +- `operator` (String) +- `value` (Number) + +Optional: + +- `label` (String) + + ### Nested Schema for `group.chart.y_axis` diff --git a/docs/resources/metric_dashboard.md b/docs/resources/metric_dashboard.md index 60f08fe3..d6d099ca 100644 --- a/docs/resources/metric_dashboard.md +++ b/docs/resources/metric_dashboard.md @@ -115,6 +115,7 @@ Optional: - `description` (String) - `height` (Number) - `subtitle` (String) Subtitle to show beneath big number, unused in other chart types +- `threshold` (Block List) (see [below for nested schema](#nestedblock--chart--threshold)) - `width` (Number) - `x_pos` (Number) - `y_axis` (Block List, Max: 1, Deprecated) (see [below for nested schema](#nestedblock--chart--y_axis)) @@ -180,6 +181,20 @@ Optional: + +### Nested Schema for `chart.threshold` + +Required: + +- `color` (String) +- `operator` (String) +- `value` (Number) + +Optional: + +- `label` (String) + + ### Nested Schema for `chart.y_axis` @@ -281,6 +296,7 @@ Optional: - `description` (String) - `height` (Number) - `subtitle` (String) Subtitle to show beneath big number, unused in other chart types +- `threshold` (Block List) (see [below for nested schema](#nestedblock--group--chart--threshold)) - `width` (Number) - `x_pos` (Number) - `y_axis` (Block List, Max: 1, Deprecated) (see [below for nested schema](#nestedblock--group--chart--y_axis)) @@ -346,6 +362,20 @@ Optional: + +### Nested Schema for `group.chart.threshold` + +Required: + +- `color` (String) +- `operator` (String) +- `value` (Number) + +Optional: + +- `label` (String) + + ### Nested Schema for `group.chart.y_axis` From f1f2e888fce2df6d1557527a86ba1e12ca977d0f Mon Sep 17 00:00:00 2001 From: srjames90 Date: Mon, 24 Jun 2024 15:11:11 -0400 Subject: [PATCH 6/9] remove superfluous color and operator check --- lightstep/resource_metric_dashboard.go | 32 -------------------------- 1 file changed, 32 deletions(-) diff --git a/lightstep/resource_metric_dashboard.go b/lightstep/resource_metric_dashboard.go index b4b69348..4c70b4c2 100644 --- a/lightstep/resource_metric_dashboard.go +++ b/lightstep/resource_metric_dashboard.go @@ -611,30 +611,6 @@ func buildCharts(chartsIn []interface{}) ([]client.UnifiedChart, error) { return newCharts, nil } -var colors = map[string]bool{ - "#AA3018": true, - "#B67D0C": true, - "#3C864F": true, - "#1B7BBB": true, - "#DC7847": true, - "#78469B": true, - "#37A2AE": true, - "#B03B7F": true, - "#1F40C1": true, - "#8B7255": true, - "#826CEF": true, - "#D56DD5": true, - "#6699CC": true, -} - -var operators = map[string]bool{ - "E": true, - "GE": true, - "GT": true, - "LE": true, - "LT": true, -} - func buildChartThresholds(thresholdsIn []interface{}) ([]client.Threshold, error) { var thresholds []client.Threshold if len(thresholdsIn) < 1 { @@ -648,19 +624,11 @@ func buildChartThresholds(thresholdsIn []interface{}) ([]client.Threshold, error return []client.Threshold{}, fmt.Errorf("missing required attribute 'color' for threshold") } - if !colors[color] { - return []client.Threshold{}, fmt.Errorf("invalid value for attribute 'color' for threshold") - } - operator, ok := threshold["operator"].(string) if !ok { return []client.Threshold{}, fmt.Errorf("missing required attribute 'operator' for threshold") } - if !operators[operator] { - return []client.Threshold{}, fmt.Errorf("invalid value for attribute 'operator' for threshold") - } - label, ok := threshold["label"].(string) if !ok { From f37772723f873a7107f2fd4c19db4c7bf8a82f9e Mon Sep 17 00:00:00 2001 From: srjames90 Date: Mon, 24 Jun 2024 15:11:53 -0400 Subject: [PATCH 7/9] update version --- .go-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.go-version b/.go-version index 3ce51a95..9141007a 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.95.9 +1.96.0 From 2173d00ea13d75867de0bbc5dc29986e3fc917cf Mon Sep 17 00:00:00 2001 From: srjames90 Date: Mon, 24 Jun 2024 16:28:20 -0400 Subject: [PATCH 8/9] remove color validation --- lightstep/resource_dashboard.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lightstep/resource_dashboard.go b/lightstep/resource_dashboard.go index 33f1da53..f32424fc 100644 --- a/lightstep/resource_dashboard.go +++ b/lightstep/resource_dashboard.go @@ -14,21 +14,6 @@ func getThresholdSchema() map[string]*schema.Schema { "color": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{ - "#AA3018", - "#B67D0C", - "#3C864F", - "#1B7BBB", - "#DC7847", - "#78469B", - "#37A2AE", - "#B03B7F", - "#1F40C1", - "#8B7255", - "#826CEF", - "#D56DD5", - "#6699CC", - }, false), }, "label": { Type: schema.TypeString, From 56baf3bdcb4c4ddbfae684173379eef9b0eb81fa Mon Sep 17 00:00:00 2001 From: srjames90 Date: Mon, 24 Jun 2024 16:47:00 -0400 Subject: [PATCH 9/9] replace label point with string and remove short circuit --- client/metric_dashboards.go | 4 ++-- lightstep/resource_metric_dashboard.go | 8 ++------ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/client/metric_dashboards.go b/client/metric_dashboards.go index 4a7405a3..177d6485 100644 --- a/client/metric_dashboards.go +++ b/client/metric_dashboards.go @@ -73,8 +73,8 @@ type Threshold struct { Operator string `json:"operator"` Value float64 `json:"value"` // An alpha hex color - Color string `json:"color"` - Label *string `json:"label"` + Color string `json:"color"` + Label string `json:"label"` } type YAxis struct { diff --git a/lightstep/resource_metric_dashboard.go b/lightstep/resource_metric_dashboard.go index 4c70b4c2..c42db3ea 100644 --- a/lightstep/resource_metric_dashboard.go +++ b/lightstep/resource_metric_dashboard.go @@ -629,11 +629,7 @@ func buildChartThresholds(thresholdsIn []interface{}) ([]client.Threshold, error return []client.Threshold{}, fmt.Errorf("missing required attribute 'operator' for threshold") } - label, ok := threshold["label"].(string) - - if !ok { - return []client.Threshold{}, fmt.Errorf("missing required attribute 'label' for threshold") - } + label := threshold["label"].(string) value, ok := threshold["value"].(float64) @@ -643,7 +639,7 @@ func buildChartThresholds(thresholdsIn []interface{}) ([]client.Threshold, error thresholds = append(thresholds, client.Threshold{ Color: color, - Label: &label, + Label: label, Operator: operator, Value: value, })