diff --git a/.go-version b/.go-version
index 00bce9c..9141007 100644
--- a/.go-version
+++ b/.go-version
@@ -1 +1 @@
-1.95.8
+1.96.0
diff --git a/client/metric_dashboards.go b/client/metric_dashboards.go
index b6dce75..177d648 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/docs/resources/dashboard.md b/docs/resources/dashboard.md
index 0860c52..0261009 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 60f08fe..d6d099c 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`
diff --git a/lightstep/resource_dashboard.go b/lightstep/resource_dashboard.go
index 45bd33f..f32424f 100644
--- a/lightstep/resource_dashboard.go
+++ b/lightstep/resource_dashboard.go
@@ -9,6 +9,34 @@ 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,
+ },
+ "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 caec8a3..c42db3e 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(),
+ },
+ },
},
)
}
@@ -580,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 {
@@ -599,6 +611,42 @@ func buildCharts(chartsIn []interface{}) ([]client.UnifiedChart, error) {
return newCharts, nil
}
+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")
+ }
+
+ operator, ok := threshold["operator"].(string)
+ if !ok {
+ return []client.Threshold{}, fmt.Errorf("missing required attribute 'operator' for threshold")
+ }
+
+ label := threshold["label"].(string)
+
+ 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
@@ -845,11 +893,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 55e7862..e47381e 100644
--- a/lightstep/resource_metric_dashboard_test.go
+++ b/lightstep/resource_metric_dashboard_test.go
@@ -464,6 +464,139 @@ 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
+ }
+ }
+ }
+}
+`
+ 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"
+
+ 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"),
+ 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", ""),
+ ),
+ },
+ {
+ // 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"),
+ ),
+ },
+ },
+ })
+}
+
func TestAccDashboardEventQueries(t *testing.T) {
var dashboard client.UnifiedDashboard
var eventQuery client.EventQueryAttributes