Repository: honeycombio/terraform-provider-honeycombio
Pull request #686 (2025-05-28 20:45:00)

fix(client): use proper format for Retry-After fallback
Matching the soon to be released upstream change, this updates `Retry-After` to be parsed properly if we happen to fallback on using it (which, is unlikely to be honest).


Patch:
diff --git a/.env b/.env.example
similarity index 100%
rename from .env
rename to .env.example
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e9bbc55..62bd260 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -54,7 +54,7 @@ HONEYCOMB_API_KEY=<your API key> HONEYCOMB_DATASET=<dataset> ./scripts/setup-tes
 ```
 
 Finally, **run the full testsuite**!
-There is a `.env` file checked into the root of the repository which can be used to store the relevant environment variables required for the tests:
+There is a `.env.example` template file checked into the root of the repository. Create a local `.env` file based on the template which can be used to store the relevant environment variables required for the tests:
 
 - `HONEYCOMB_API_KEY`: a Configuration Key for a Honeycomb Team
 - `HONEYCOMB_DATASET`: name of the test dataset to run tests against
diff --git a/client/board.go b/client/board.go
index 44f0b04..4e92dfe 100644
--- a/client/board.go
+++ b/client/board.go
@@ -67,6 +67,8 @@ type Board struct {
 	Queries []BoardQuery `json:"queries"`
 	// A list of SLO IDs to be added to the board
 	SLOs []string `json:"slos"`
+	// A list of tags to organize the Board, for flexible boards only
+	Tags []Tag `json:"tags"`
 }
 
 // BoardPanel represents a single panel on a board.
diff --git a/client/internal/limits/limits.go b/client/internal/limits/limits.go
index 71a9d2c..5178083 100644
--- a/client/internal/limits/limits.go
+++ b/client/internal/limits/limits.go
@@ -71,7 +71,7 @@ func rateLimitBackoff(mini, maxi time.Duration, r *http.Response) time.Duration
 	// try the retry-after header
 	if reset == 0 {
 		if v := r.Header.Get(HeaderRetryAfter); v != "" {
-			retryTime, err := time.Parse(time.RFC3339, v)
+			retryTime, err := time.Parse(http.TimeFormat, v)
 			if err == nil {
 				reset = time.Until(retryTime)
 			}
diff --git a/client/query_result.go b/client/query_result.go
index 771ba0d..8bd78ca 100644
--- a/client/query_result.go
+++ b/client/query_result.go
@@ -28,7 +28,7 @@ var _ QueryResults = (*queryResults)(nil)
 
 // QueryResult represents a Honeycomb query result.
 //
-// API docs: https://docs.honeycomb.io/api/query-results/#get-example-response
+// API docs: https://api-docs.honeycomb.io/api/query-data/getqueryresult
 type QueryResult struct {
 	// ID of a query result is only set when the query is returned from the
 	// Query Result API. This value should not be set when creating queries results.
@@ -46,6 +46,10 @@ type QueryResult struct {
 
 type QueryResultRequest struct {
 	ID string `json:"query_id"`
+	// If DisableSeries is true, Limit allows the default query limit of 1000 results to be overridden up to 10,000 results.
+	Limit int `json:"limit,omitempty"`
+	// DisableSeries controls whether timeseries data will be returned in the series response field. Defaults to false.
+	DisableSeries bool `json:"disable_series,omitempty"`
 }
 
 type QueryResultData struct {
diff --git a/client/trigger.go b/client/trigger.go
index b50c06e..9dd5b0f 100644
--- a/client/trigger.go
+++ b/client/trigger.go
@@ -159,6 +159,7 @@ func (t *Trigger) MarshalJSON() ([]byte, error) {
 			Recipients:             t.Recipients,
 			EvaluationScheduleType: t.EvaluationScheduleType,
 			EvaluationSchedule:     t.EvaluationSchedule,
+			Tags:                   t.Tags,
 		}
 		return json.Marshal(&struct{ *ATrigger }{ATrigger: a})
 	}
diff --git a/docs/resources/flexible_board.md b/docs/resources/flexible_board.md
index 6bdc98b..4282ba8 100644
--- a/docs/resources/flexible_board.md
+++ b/docs/resources/flexible_board.md
@@ -82,6 +82,11 @@ resource "honeycombio_flexible_board" "overview" {
   name        = "Service Overview"
   description = "My flexible baord description"
 
+  tags = {
+    team    = "web"
+    project = "secret"
+  }
+
   panel {
     type = "query"
 
@@ -124,6 +129,7 @@ The following arguments are supported for flexible boards:
 - `name` - (Required) Name of the board.
 - `description` - (Optional) Description of the board. Supports Markdown.
 - `panel` - (Optional) zero or more configurations blocks
+- `tags` - (Optional) Map of up to ten (10) tags to assign to the resource.
 
 Each board configuration may have zero or more `panel` blocks which accept the following arguments:
 
@@ -154,7 +160,7 @@ Each `visualization_settings` configuration accepts the following arguments:
 - `use_utc_xaxis` - (Optional) Display UTC Time X-Axis or Localtime X-Axis.
 - `hide_markers` - (Optional) Hide [markers](https://docs.honeycomb.io/investigate/query/customize-results/#markers) from appearing on graph.
 - `hide_hovers` - (Optional) Disable Graph tooltips in the results display when hovering over a graph.
-- `overlaid_charts` - (Optional) Combine any visualized AVG, MIN, MAX, and PERCENTILE clauses into a single chart.
+- `prefer_overlaid_charts` - (Optional) Combine any visualized AVG, MIN, MAX, and PERCENTILE clauses into a single chart.
 - `chart` - (Optional) a configuration block to manage the query's charts.
 
 Each `chart` configuration accepts the following arguments:
diff --git a/docs/resources/trigger.md b/docs/resources/trigger.md
index 40ad3eb..feec44c 100644
--- a/docs/resources/trigger.md
+++ b/docs/resources/trigger.md
@@ -231,12 +231,37 @@ resource "honeycombio_trigger" "example" {
 }
 ```
 
+### Example - Example an Environment-wide Trigger
+```hcl
+
+data "honeycombio_query_specification" "example" {
+    calculation {
+        op     = "AVG"
+        column = "duration_ms"
+    }
+}
+
+resource "honeycombio_trigger" "example" {
+    name        = "Requests are slower than usual"
+    description = "Average duration of all requests for the last 10 minutes."
+
+    query_json = data.honeycombio_query_specification.example.json
+
+    frequency = 600 // in seconds, 10 minutes
+
+    threshold {
+        op             = ">="
+        value          = 1000
+    }
+}
+```
+
 ## Argument Reference
 
 The following arguments are supported:
 
 * `name` - (Required) Name of the trigger.
-* `dataset` - (Required) The dataset this trigger is associated with.
+* `dataset` - (Optional) The dataset this trigger is associated with. If omitted, the lookup will be Environment-wide.
 * `query_id` - (Optional) The ID of the Query that the Trigger will execute. Conflicts with `query_json`.
 * `query_json` - (Optional) The Query Specfication JSON for the Trigger to execute.
 Providing the Query Specification as JSON -- as opposed to a Query ID -- enables additional validation during the validate and plan stages.
@@ -254,6 +279,7 @@ Outside of the window, the trigger will not be run.
 If no schedule is specified, the trigger will be run at the specified frequency at all times.
 * `baseline_details` - (Optional) A configuration block (described below) allows you to receive notifications when the delta between values in your data, compared to a previous time period, cross thresholds you configure.
 * `recipient` - (Optional) Zero or more configuration blocks (described below) with the recipients to notify when the trigger fires.
+* `tags` - (Optional) Map of up to ten (10) tags to assign to the resource.
 
 One of `query_id` or `query_json` are required.
 
diff --git a/example/trigger_env_wide/main.tf b/example/trigger_env_wide/main.tf
new file mode 100644
index 0000000..27cf07e
--- /dev/null
+++ b/example/trigger_env_wide/main.tf
@@ -0,0 +1,44 @@
+terraform {
+  required_providers {
+    honeycombio = {
+      source = "honeycombio/honeycombio"
+    }
+  }
+}
+
+data "honeycombio_query_specification" "query" {
+  calculation {
+    op     = "AVG"
+    column = "db_dur_ms"
+  }
+
+  time_range = 900 // in seconds, 15 minutes
+  
+}
+
+
+resource "honeycombio_trigger" "trigger" {
+  name        = "Requests are slower than usual"
+  description = "Average duration of all requests for ThatSpecialTenant for the last 15 minutes."
+
+  disabled = false
+
+  query_json = data.honeycombio_query_specification.query.json
+
+  frequency = 900 // in seconds, 100 minutes
+
+  threshold {
+    op    = ">="
+    value = 1000
+  }
+
+  # zero or more recipients
+  recipient {
+    type   = "email"
+    target = "hello@example.com"
+  }
+  recipient {
+    type   = "marker"
+    target = "Trigger - slow requests" # name of the marker
+  }
+}
\ No newline at end of file
diff --git a/internal/models/flexible_board.go b/internal/models/flexible_board.go
index 8b0ab75..bd28c10 100644
--- a/internal/models/flexible_board.go
+++ b/internal/models/flexible_board.go
@@ -11,6 +11,7 @@ type FlexibleBoardResourceModel struct {
 	Description types.String `tfsdk:"description"`
 	URL         types.String `tfsdk:"board_url"`
 	Panels      types.List   `tfsdk:"panel"`
+	Tags        types.Map    `tfsdk:"tags"`
 }
 
 type BoardPanelModel struct {
diff --git a/internal/provider/flexible_board_resource.go b/internal/provider/flexible_board_resource.go
index 2803cce..c5fccd1 100644
--- a/internal/provider/flexible_board_resource.go
+++ b/internal/provider/flexible_board_resource.go
@@ -95,6 +95,7 @@ func (*flexibleBoardResource) Schema(_ context.Context, _ resource.SchemaRequest
 					stringplanmodifier.UseStateForUnknown(),
 				},
 			},
+			"tags": tagsSchema(),
 		},
 		Blocks: map[string]schema.Block{
 			"panel": schema.ListNestedBlock{
@@ -296,12 +297,18 @@ func (r *flexibleBoardResource) Create(ctx context.Context, req resource.CreateR
 		return
 	}
 
+	planTags, diags := helper.MapToTags(ctx, plan.Tags)
+	if diags.HasError() {
+		return
+	}
+
 	panelFromConfig := expandBoardPanels(ctx, plan.Panels, &resp.Diagnostics)
 	createRequest := &client.Board{
 		Name:        plan.Name.ValueString(),
 		Description: plan.Description.ValueString(),
 		BoardType:   client.BoardTypeFlexible,
 		Panels:      removeDefaultNegativeNumbers(panelFromConfig),
+		Tags:        planTags,
 	}
 
 	if resp.Diagnostics.HasError() {
@@ -343,6 +350,13 @@ func (r *flexibleBoardResource) Create(ctx context.Context, req resource.CreateR
 		state.Panels = panels
 	}
 
+	tags, diags := helper.TagsToMap(ctx, board.Tags)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+	state.Tags = tags
+
 	resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
 }
 
@@ -418,8 +432,15 @@ func (r *flexibleBoardResource) Read(ctx context.Context, req resource.ReadReque
 		resp.Diagnostics.Append(diag...)
 		state.Panels = panels
 	}
+	tags, diags := helper.TagsToMap(ctx, board.Tags)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+	state.Tags = tags
 
 	resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+
 }
 
 func (r *flexibleBoardResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
@@ -430,6 +451,11 @@ func (r *flexibleBoardResource) Update(ctx context.Context, req resource.UpdateR
 		return
 	}
 
+	planTags, diags := helper.MapToTags(ctx, plan.Tags)
+	if diags.HasError() {
+		return
+	}
+
 	panelConfig := expandBoardPanels(ctx, plan.Panels, &resp.Diagnostics)
 	updateRequest := &client.Board{
 		ID:          plan.ID.ValueString(),
@@ -437,6 +463,7 @@ func (r *flexibleBoardResource) Update(ctx context.Context, req resource.UpdateR
 		Description: plan.Description.ValueString(),
 		BoardType:   client.BoardTypeFlexible,
 		Panels:      removeDefaultNegativeNumbers(panelConfig),
+		Tags:        planTags,
 	}
 	if resp.Diagnostics.HasError() {
 		return
@@ -483,7 +510,15 @@ func (r *flexibleBoardResource) Update(ctx context.Context, req resource.UpdateR
 		state.Panels = panels
 	}
 
+	tags, diags := helper.TagsToMap(ctx, board.Tags)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+	state.Tags = tags
+
 	resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+
 }
 
 func (r *flexibleBoardResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
diff --git a/internal/provider/trigger_resource.go b/internal/provider/trigger_resource.go
index a2d8b09..4fc7aca 100644
--- a/internal/provider/trigger_resource.go
+++ b/internal/provider/trigger_resource.go
@@ -78,10 +78,12 @@ func (r *triggerResource) Schema(_ context.Context, _ resource.SchemaRequest, re
 				},
 			},
 			"dataset": schema.StringAttribute{
-				Required:    true,
+				Optional:    true,
+				Computed:    true,
 				Description: "The dataset this Trigger is associated with.",
 				PlanModifiers: []planmodifier.String{
 					stringplanmodifier.RequiresReplace(),
+					modifiers.DatasetDeprecation(true),
 				},
 			},
 			"description": schema.StringAttribute{
@@ -307,7 +309,9 @@ func (r *triggerResource) Create(ctx context.Context, req resource.CreateRequest
 		newTrigger.EvaluationScheduleType = client.TriggerEvaluationScheduleWindow
 	}
 
-	trigger, err := r.client.Triggers.Create(ctx, plan.Dataset.ValueString(), newTrigger)
+	dataset := helper.GetDatasetOrAll(plan.Dataset)
+
+	trigger, err := r.client.Triggers.Create(ctx, dataset.ValueString(), newTrigger)
 	if helper.AddDiagnosticOnError(&resp.Diagnostics, "Creating Honeycomb Trigger", err) {
 		return
 	}
@@ -353,8 +357,10 @@ func (r *triggerResource) Read(ctx context.Context, req resource.ReadRequest, re
 		return
 	}
 
+	dataset := helper.GetDatasetOrAll(state.Dataset)
+
 	var detailedErr client.DetailedError
-	trigger, err := r.client.Triggers.Get(ctx, state.Dataset.ValueString(), state.ID.ValueString())
+	trigger, err := r.client.Triggers.Get(ctx, dataset.ValueString(), state.ID.ValueString())
 	if errors.As(err, &detailedErr) {
 		if detailedErr.IsNotFound() {
 			// if not found consider it deleted -- so just remove it from state
@@ -470,18 +476,20 @@ func (r *triggerResource) Update(ctx context.Context, req resource.UpdateRequest
 		updatedTrigger.EvaluationScheduleType = client.TriggerEvaluationScheduleFrequency
 	}
 
-	_, err := r.client.Triggers.Update(ctx, plan.Dataset.ValueString(), updatedTrigger)
+	dataset := helper.GetDatasetOrAll(plan.Dataset)
+
+	_, err := r.client.Triggers.Update(ctx, dataset.ValueString(), updatedTrigger)
 	if helper.AddDiagnosticOnError(&resp.Diagnostics, "Updating Honeycomb Trigger", err) {
 		return
 	}
 
-	trigger, err := r.client.Triggers.Get(ctx, plan.Dataset.ValueString(), plan.ID.ValueString())
+	trigger, err := r.client.Triggers.Get(ctx, dataset.ValueString(), plan.ID.ValueString())
 	if helper.AddDiagnosticOnError(&resp.Diagnostics, "Updating Honeycomb Trigger", err) {
 		return
 	}
 
 	var state models.TriggerResourceModel
-	state.Dataset = plan.Dataset
+	state.Dataset = dataset
 	state.ID = types.StringValue(trigger.ID)
 	state.Name = types.StringValue(trigger.Name)
 	state.Description = types.StringValue(trigger.Description)
@@ -521,8 +529,10 @@ func (r *triggerResource) Delete(ctx context.Context, req resource.DeleteRequest
 		return
 	}
 
+	dataset := helper.GetDatasetOrAll(state.Dataset)
+
 	var detailedErr client.DetailedError
-	err := r.client.Triggers.Delete(ctx, state.Dataset.ValueString(), state.ID.ValueString())
+	err := r.client.Triggers.Delete(ctx, dataset.ValueString(), state.ID.ValueString())
 	if err != nil {
 		if errors.As(err, &detailedErr) {
 			// if not found consider it deleted -- so don't error


Test patch:
diff --git a/client/board_test.go b/client/board_test.go
index 9d91cdb..3c5ca4c 100644
--- a/client/board_test.go
+++ b/client/board_test.go
@@ -248,6 +248,61 @@ func TestFlexibleBoards(t *testing.T) {
 		assert.Equal(t, data, flexibleBoard)
 	})
 
+	t.Run("Create flexible board with tags", func(t *testing.T) {
+		data := &client.Board{
+			Name:        test.RandomStringWithPrefix("test.", 8),
+			BoardType:   "flexible",
+			Description: "A board with some tags",
+			Panels: []client.BoardPanel{
+				{
+					PanelType: client.BoardPanelTypeQuery,
+					PanelPosition: client.BoardPanelPosition{
+						X:      0,
+						Y:      0,
+						Height: 3,
+						Width:  4,
+					},
+					QueryPanel: &client.BoardQueryPanel{
+						QueryID:           *query.ID,
+						QueryAnnotationID: queryAnnotation.ID,
+						Style:             client.BoardQueryStyleGraph,
+					},
+				},
+				{
+					PanelType: client.BoardPanelTypeSLO,
+					PanelPosition: client.BoardPanelPosition{
+						X:      6,
+						Y:      0,
+						Height: 3,
+						Width:  4,
+					},
+					SLOPanel: &client.BoardSLOPanel{
+						SLOID: slo.ID,
+					},
+				},
+			},
+			Tags: []client.Tag{
+				{Key: "color", Value: "blue"},
+			},
+		}
+		flexibleBoard, err = c.Boards.Create(ctx, data)
+		require.NoError(t, err)
+		assert.NotNil(t, flexibleBoard.ID)
+
+		// copy ID before asserting equality
+		data.ID = flexibleBoard.ID
+
+		// ensure the board URL got populated
+		assert.NotEmpty(t, flexibleBoard.Links.BoardURL)
+		data.Links.BoardURL = flexibleBoard.Links.BoardURL
+
+		// ensure the tags were added
+		assert.NotEmpty(t, flexibleBoard.Tags)
+		assert.ElementsMatch(t, flexibleBoard.Tags, data.Tags, "tags do not match")
+
+		assert.Equal(t, data, flexibleBoard)
+	})
+
 	t.Run("List", func(t *testing.T) {
 		result, err := c.Boards.List(ctx)
 		require.NoError(t, err)
@@ -259,6 +314,8 @@ func TestFlexibleBoards(t *testing.T) {
 		board, err := c.Boards.Get(ctx, flexibleBoard.ID)
 		require.NoError(t, err)
 
+		assert.ElementsMatch(t, board.Tags, flexibleBoard.Tags, "tags do not match")
+
 		assert.Equal(t, *board, *flexibleBoard)
 	})
 }
diff --git a/client/internal/limits/limits_test.go b/client/internal/limits/limits_test.go
index c1462f6..04c6491 100644
--- a/client/internal/limits/limits_test.go
+++ b/client/internal/limits/limits_test.go
@@ -48,7 +48,7 @@ func TestClient_rateLimitBackoff(t *testing.T) {
 		{
 			name:          "valid retry-after header",
 			headerName:    HeaderRetryAfter,
-			headerValue:   now.Add(2 * time.Minute).UTC().Format(time.RFC3339),
+			headerValue:   now.Add(2 * time.Minute).UTC().Format(http.TimeFormat),
 			expectedValue: 2 * time.Minute,
 		},
 		{
@@ -82,7 +82,7 @@ func TestClient_rateLimitBackoff(t *testing.T) {
 	t.Run("ratelimit header takes precedence", func(t *testing.T) {
 		w := httptest.NewRecorder()
 		w.Header().Add(HeaderRateLimit, "limit=100, remaining=50, reset=60")
-		w.Header().Add(HeaderRetryAfter, now.Add(2*time.Minute).UTC().Format(time.RFC3339))
+		w.Header().Add(HeaderRetryAfter, now.Add(2*time.Minute).UTC().Format(http.TimeFormat))
 		w.WriteHeader(http.StatusTooManyRequests)
 
 		r := rateLimitBackoff(mini, maxi, w.Result())
diff --git a/client/trigger_test.go b/client/trigger_test.go
index 3c9526d..7b4d8c2 100644
--- a/client/trigger_test.go
+++ b/client/trigger_test.go
@@ -61,6 +61,9 @@ func TestTriggers(t *testing.T) {
 					Target: "This marker is created by a trigger",
 				},
 			},
+			Tags: []client.Tag{
+				{Key: "color", Value: "blue"},
+			},
 		}
 		trigger, err = c.Triggers.Create(ctx, dataset, data)
 		require.NoError(t, err)
@@ -81,6 +84,10 @@ func TestTriggers(t *testing.T) {
 		// set the default threshold exceeded limit
 		data.Threshold.ExceededLimit = 1
 
+		assert.NotEmpty(t, trigger.Tags)
+		assert.ElementsMatch(t, trigger.Tags, data.Tags, "tags do not match")
+		// trigger.Tags = data.Tags
+
 		assert.Equal(t, data, trigger)
 	})
 
@@ -112,6 +119,9 @@ func TestTriggers(t *testing.T) {
 				EndTime:    "21:00",
 			},
 		}
+		trigger.Tags = []client.Tag{
+			{Key: "team", Value: "t-team"},
+		}
 		// update the threshold exceeded limit to 3
 		trigger.Threshold.ExceededLimit = 3
 
@@ -120,7 +130,8 @@ func TestTriggers(t *testing.T) {
 		// copy IDs before asserting equality
 		trigger.QueryID = result.QueryID
 		require.NoError(t, err)
-		assert.Equal(t, trigger, result)
+		require.ElementsMatch(t, trigger.Tags, result.Tags, "tags do not match")
+		assert.Equal(t, trigger, result, "full trigger does not match")
 	})
 
 	t.Run("Delete", func(t *testing.T) {
@@ -305,3 +316,86 @@ func TestTriggersWithBaselineDetails(t *testing.T) {
 	})
 
 }
+
+func TestTriggersEnvironmentWide(t *testing.T) {
+	t.Parallel()
+
+	ctx := context.Background()
+	c := newTestClient(t)
+
+	if c.IsClassic(ctx) {
+		t.Skip("classic does not support environment-wide triggers")
+	}
+
+	var trigger *client.Trigger
+	var err error
+
+	// delete the trigger after the tests run
+	t.Cleanup(func() {
+		if trigger != nil && trigger.ID != "" {
+			c.Triggers.Delete(ctx, client.EnvironmentWideSlug, trigger.ID)
+		}
+	})
+
+	t.Run("Create - environment-wide", func(t *testing.T) {
+		data := &client.Trigger{
+			Name:        test.RandomStringWithPrefix("test.", 8),
+			Description: "Environment-wide trigger test",
+			Query: &client.QuerySpec{
+				Calculations: []client.CalculationSpec{
+					{
+						Op: client.CalculationOpCount,
+					},
+				},
+				TimeRange: client.ToPtr(900),
+			},
+			Frequency: 900,
+			Threshold: &client.TriggerThreshold{
+				Op:    client.TriggerThresholdOpGreaterThan,
+				Value: 1000,
+			},
+			Recipients: []client.NotificationRecipient{
+				{
+					Type:   client.RecipientTypeMarker,
+					Target: "Environment-wide trigger fired",
+				},
+			},
+		}
+		trigger, err = c.Triggers.Create(ctx, client.EnvironmentWideSlug, data)
+		require.NoError(t, err)
+		assert.NotNil(t, trigger.ID)
+
+		// copy IDs before asserting equality
+		data.ID = trigger.ID
+		data.QueryID = trigger.QueryID
+		data.AlertType = trigger.AlertType
+		data.Recipients[0].ID = trigger.Recipients[0].ID
+
+		// set the default alert type
+		data.AlertType = client.TriggerAlertTypeOnChange
+		// set the default evaluation window type
+		data.EvaluationScheduleType = client.TriggerEvaluationScheduleFrequency
+		// set the default threshold exceeded limit
+		data.Threshold.ExceededLimit = 1
+
+		assert.Equal(t, data, trigger)
+	})
+
+	t.Run("Get - environment-wide", func(t *testing.T) {
+		getTrigger, err := c.Triggers.Get(ctx, client.EnvironmentWideSlug, trigger.ID)
+
+		require.NoError(t, err)
+		assert.Equal(t, *trigger, *getTrigger)
+	})
+
+	t.Run("Update - environment-wide", func(t *testing.T) {
+		trigger.Description = "Updated environment-wide trigger description"
+		trigger.Threshold.Value = 2000
+
+		result, err := c.Triggers.Update(ctx, client.EnvironmentWideSlug, trigger)
+
+		require.NoError(t, err)
+		assert.Equal(t, trigger.Description, result.Description)
+		assert.InDelta(t, trigger.Threshold.Value, result.Threshold.Value, 0.001)
+	})
+}
diff --git a/internal/provider/flexible_board_resource_test.go b/internal/provider/flexible_board_resource_test.go
index 63bd957..10c66f0 100644
--- a/internal/provider/flexible_board_resource_test.go
+++ b/internal/provider/flexible_board_resource_test.go
@@ -68,11 +68,16 @@ func TestAccHoneycombioFlexibleBoard(t *testing.T) {
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "panel.1.query_panel.0.visualization_settings.0.chart.0.use_log_scale", "false"),
 				),
 			},
-			// remove board's panels
+			// remove board's panels, add tags
 			{
 				Config: `
 resource "honeycombio_flexible_board" "test" {
 	name          = "simple flexible board"
+
+	tags = {
+	  team = "blue"
+	  env  = "dev"
+	}
 }
 			  `,
 				Check: resource.ComposeTestCheckFunc(
@@ -80,9 +85,12 @@ resource "honeycombio_flexible_board" "test" {
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "name", "simple flexible board"),
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "description", ""),
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "panel.#", "0"),
+					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "tags.%", "2"),
+					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "tags.team", "blue"),
+					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "tags.env", "dev"),
 				),
 			},
-			// now add a query panel and ensure the board is updated
+			// now add a query panel, update tags, and ensure the board is updated
 			{
 				Config: fmt.Sprintf(`
 data "honeycombio_query_specification" "test" {
@@ -106,6 +114,9 @@ resource "honeycombio_query_annotation" "test" {
 resource "honeycombio_flexible_board" "test" {
   name        = "simple flexible board updated"
   description = "simple flexible board description"
+  tags = {
+	team = "green"
+  } 
   panel {
     type = "query"
     query_panel {
@@ -131,9 +142,12 @@ resource "honeycombio_flexible_board" "test" {
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "panel.#", "1"),
 					resource.TestCheckResourceAttrPair("honeycombio_flexible_board.test", "panel.0.query_panel.0.query_id", "honeycombio_query.test", "id"),
 					resource.TestCheckResourceAttrPair("honeycombio_flexible_board.test", "panel.0.query_panel.0.query_annotation_id", "honeycombio_query_annotation.test", "id"),
+					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "tags.%", "1"),
+					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "tags.team", "green"),
+					resource.TestCheckNoResourceAttr("honeycombio_flexible_board.test", "tags.env"),
 				),
 			},
-			// now add an SLO panel and remove chart settings from the query panel
+			// now add an SLO panel, remove chart settings from the query panel, remove tags
 			{
 				Config: fmt.Sprintf(`
 data "honeycombio_query_specification" "test" {
@@ -205,6 +219,7 @@ resource "honeycombio_flexible_board" "test" {
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "panel.1.position.0.y_coordinate", "0"),
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "panel.1.position.0.height", "4"),
 					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "panel.1.position.0.width", "3"),
+					resource.TestCheckResourceAttr("honeycombio_flexible_board.test", "tags.%", "0"),
 				),
 			},
 			// re-order the panels and remove viz settings for the query panel
diff --git a/internal/provider/trigger_resource_test.go b/internal/provider/trigger_resource_test.go
index ccecf12..c669ed2 100644
--- a/internal/provider/trigger_resource_test.go
+++ b/internal/provider/trigger_resource_test.go
@@ -277,6 +277,39 @@ func TestAcc_TriggerResource(t *testing.T) {
 			},
 		})
 	})
+
+	t.Run("environment-wide trigger", func(t *testing.T) {
+		ctx := context.Background()
+		c := testAccClient(t)
+
+		if c.IsClassic(ctx) {
+			t.Skip("classic does not support environment-wide triggers")
+		}
+
+		resource.Test(t, resource.TestCase{
+			PreCheck:                 testAccPreCheck(t),
+			ProtoV5ProviderFactories: testAccProtoV5MuxServerFactory,
+			Steps: []resource.TestStep{
+				{
+					Config: testAccConfigEnvironmentWideTrigger(name),
+					Check: resource.ComposeAggregateTestCheckFunc(
+						testAccEnsureTriggerExists(t, "honeycombio_trigger.test"),
+						resource.TestCheckResourceAttr("honeycombio_trigger.test", "name", name),
+						resource.TestCheckNoResourceAttr("honeycombio_trigger.test", "dataset"),
+						resource.TestCheckResourceAttr("honeycombio_trigger.test", "description", "Environment-wide trigger"),
+						resource.TestCheckResourceAttr("honeycombio_trigger.test", "frequency", "1800"),
+						resource.TestCheckResourceAttr("honeycombio_trigger.test", "threshold.0.op", ">"),
+						resource.TestCheckResourceAttr("honeycombio_trigger.test", "threshold.0.value", "1000"),
+					),
+				},
+				{
+					ResourceName:        "honeycombio_trigger.test",
+					ImportStateIdPrefix: "__all__/",
+					ImportState:         true,
+				},
+			},
+		})
+	})
 }
 
 // TestAcc_TriggerResourceUpgradeFromVersion014 is intended to test the migration
@@ -1510,7 +1543,12 @@ func testAccEnsureTriggerExists(t *testing.T, name string) resource.TestCheckFun
 		}
 
 		client := testAccClient(t)
-		_, err := client.Triggers.Get(context.Background(), resourceState.Primary.Attributes["dataset"], resourceState.Primary.ID)
+		dataset := resourceState.Primary.Attributes["dataset"]
+		if dataset == "" {
+			// For environment-wide triggers, use "__all__" as the dataset
+			dataset = "__all__"
+		}
+		_, err := client.Triggers.Get(context.Background(), dataset, resourceState.Primary.ID)
 		if err != nil {
 			return fmt.Errorf("failed to fetch created trigger: %w", err)
 		}
@@ -1518,3 +1556,33 @@ func testAccEnsureTriggerExists(t *testing.T, name string) resource.TestCheckFun
 		return nil
 	}
 }
+
+func testAccConfigEnvironmentWideTrigger(name string) string {
+	return fmt.Sprintf(`
+data "honeycombio_query_specification" "test" {
+  calculation {
+    op     = "COUNT"
+  }
+
+  time_range = 1800
+}
+
+resource "honeycombio_trigger" "test" {
+  name        = "%s"
+  description = "Environment-wide trigger"
+
+  query_json = data.honeycombio_query_specification.test.json
+
+  threshold {
+    op    = ">"
+    value = 1000
+  }
+
+  frequency = 1800
+
+  recipient {
+    type   = "marker"
+    target = "Environment-wide trigger fired"
+  }
+}`, name)
+}