Repository: honeycombio/terraform-provider-honeycombio (language: python)

Issue:
Data source: query result
Make use of the [Query Data API](https://docs.honeycomb.io/api/query-results/) and the existing `honeycombio_query` resource to bring Honeycomb data into Terraform.

Example use case: get the full list of distinct service names (`COUNT group by service.name`) and use them to create other resources with another providers

Data source will have the following attributes:

- `id`
- `series`
- `results`
- `links`


Discussion / hints:

Fix patch:
diff --git a/client/client.go b/client/client.go
index e4e285f1..316d3e2e 100644
--- a/client/client.go
+++ b/client/client.go
@@ -69,6 +69,7 @@ type Client struct {
 	Markers          Markers
 	Queries          Queries
 	QueryAnnotations QueryAnnotations
+	QueryResults     QueryResults
 	Triggers         Triggers
 }
 
@@ -103,6 +104,7 @@ func NewClient(config *Config) (*Client, error) {
 	client.Markers = &markers{client: client}
 	client.Queries = &queries{client: client}
 	client.QueryAnnotations = &queryAnnotations{client: client}
+	client.QueryResults = &queryResults{client: client}
 	client.Triggers = &triggers{client: client}
 
 	return client, nil
diff --git a/client/query_result.go b/client/query_result.go
new file mode 100644
index 00000000..62346944
--- /dev/null
+++ b/client/query_result.go
@@ -0,0 +1,94 @@
+package client
+
+import (
+	"context"
+	"fmt"
+	"time"
+)
+
+// QueryResults describes all the query result-related methods that the
+// Honeycomb API supports.
+//
+// API docs: https://docs.honeycomb.io/api/query-results/
+type QueryResults interface {
+	// Get the query results by ID. Returns ErrNotFound if there is no
+	// query result with the given ID.
+	Get(ctx context.Context, dataset string, q *QueryResult) error
+
+	// Create a new query result with a given query specification.
+	Create(ctx context.Context, dataset string, data *QueryResultRequest) (*QueryResult, error)
+}
+
+// queryResults implements QueryResults.
+type queryResults struct {
+	client *Client
+}
+
+// Compile-time proof of interface implementation by type queryResults.
+var _ QueryResults = (*queryResults)(nil)
+
+// QueryResult represents a Honeycomb query result.
+//
+// API docs: https://docs.honeycomb.io/api/query-results/#get-example-response
+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.
+	ID string `json:"id"`
+
+	// True once the query has completed and the results are populated.
+	Complete bool `json:"complete"`
+
+	// The resulting data of the query
+	Data QueryResultData `json:"data,omitempty"`
+
+	// Permalinks to the query results
+	Links QueryResultLinks `json:"links,omitempty"`
+}
+
+type QueryResultRequest struct {
+	ID string `json:"query_id"`
+}
+
+type QueryResultData struct {
+	Series []struct {
+		Time time.Time              `json:"time"`
+		Data map[string]interface{} `json:"data"`
+	} `json:"series"`
+	Results []struct {
+		Data map[string]interface{} `json:"data"`
+	} `json:"results"`
+}
+
+const QueryResultPollInterval time.Duration = 200 * time.Millisecond
+
+type QueryResultLinks struct {
+	Url      string `json:"query_url"`
+	GraphUrl string `json:"graph_image_url"`
+}
+
+func (s *queryResults) Get(ctx context.Context, dataset string, q *QueryResult) error {
+	var err error
+	resultUri := fmt.Sprintf("/1/query_results/%s/%s", urlEncodeDataset(dataset), q.ID)
+
+	ticker := time.NewTicker(QueryResultPollInterval)
+	for {
+		select {
+		case <-ticker.C:
+			// poll until complete or errored
+			if err = s.client.performRequest(ctx, "GET", resultUri, nil, &q); err != nil {
+				return err
+			}
+			if q.Complete {
+				return nil
+			}
+		case <-ctx.Done():
+			return ctx.Err()
+		}
+	}
+}
+
+func (s *queryResults) Create(ctx context.Context, dataset string, data *QueryResultRequest) (*QueryResult, error) {
+	var q QueryResult
+	err := s.client.performRequest(ctx, "POST", fmt.Sprintf("/1/query_results/%s", urlEncodeDataset(dataset)), data, &q)
+	return &q, err
+}
diff --git a/docs/data-sources/query_result.md b/docs/data-sources/query_result.md
new file mode 100644
index 00000000..4c098ece
--- /dev/null
+++ b/docs/data-sources/query_result.md
@@ -0,0 +1,53 @@
+# Data Source: honeycombio_query_result
+
+The `query_result` data source allows you to execute Honeycomb queries via the [Query Data API](https://docs.honeycomb.io/api/query-results/).
+As this data source is a wrapper around the Query Data API all of its [documented restrictions](https://docs.honeycomb.io/api/query-results/#api-restrictions) apply.
+
+~> **NOTE:** Use of this data source requires a Honeycomb Enterprise plan.
+
+## Example Usage
+
+```hcl
+data "honeycombio_query_specification" "example" {
+  time_range = 7200
+
+  calculation {
+    op = "COUNT"
+  }
+}
+
+resource "honeycombio_query" "example" {
+  dataset    = var.dataset
+  query_json = data.honeycombio_query_specification.example.json
+}
+
+data "honeycombio_query_result" "example" {
+  dataset  = var.dataset
+  query_id = honeycombio_query.example.id
+}
+
+output "event_count" {
+  value = format(
+    "There have been %d events in the last %d seconds.",
+    data.honeycombio_query_result.example.results[0]["COUNT"],
+    data.honeycombio_query_specification.example.time_range
+  )
+}
+```
+
+~> **NOTE:** This data source is experimental and we're actively looking to learn how you are using it! Please consider opening an issue with feedback or joining the conversation in `#terraform-provider` in the [Pollinators Slack Community](https://join.slack.com/t/honeycombpollinators/shared_invite/zt-xqexg936-dckd0l29wdE3WLmUs8Qvpg).
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `dataset` - (Required) The dataset this query is associated with.
+* `query_id` - (Required) The ID of the query that will be executed to obtain the result.
+
+## Attribute Reference
+
+In addition to all arguments above, the following attributes are exported:
+
+* `query_url` - The permalink to the executed query's results.
+* `query_image_url` - The permalink to the visualization of the executed query's results.
+* `results` - The results of the executed query. This will be a list of maps, with each map's keys set to the breakdowns and calculations of the query. Due to a limitation of the Terraform Plugin SDK, all values are transformed into strings.
diff --git a/honeycombio/data_source_query_result.go b/honeycombio/data_source_query_result.go
new file mode 100644
index 00000000..2db27416
--- /dev/null
+++ b/honeycombio/data_source_query_result.go
@@ -0,0 +1,79 @@
+package honeycombio
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+	honeycombio "github.com/honeycombio/terraform-provider-honeycombio/client"
+)
+
+func dataSourceHoneycombioQueryResult() *schema.Resource {
+	return &schema.Resource{
+		ReadContext: dataSourceHoneycombioQueryResultRead,
+
+		Schema: map[string]*schema.Schema{
+			"dataset": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			"query_id": {
+				Type:     schema.TypeString,
+				Required: true,
+			},
+			// outputs
+			"query_url": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+			"graph_image_url": {
+				Type:     schema.TypeString,
+				Computed: true,
+			},
+			"results": {
+				Type:     schema.TypeList,
+				Computed: true,
+				Elem: &schema.Schema{
+					Type: schema.TypeMap,
+				},
+			},
+		},
+	}
+}
+
+func dataSourceHoneycombioQueryResultRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
+	client := meta.(*honeycombio.Client)
+
+	dataset := d.Get("dataset").(string)
+	queryID := d.Get("query_id").(string)
+
+	queryResult, err := client.QueryResults.Create(ctx, dataset, &honeycombio.QueryResultRequest{ID: queryID})
+	if err != nil {
+		return diag.FromErr(err)
+	}
+	err = client.QueryResults.Get(ctx, dataset, queryResult)
+	if err != nil {
+		return diag.FromErr(err)
+	}
+
+	results := make([]map[string]string, len(queryResult.Data.Results))
+	for i, qr := range queryResult.Data.Results {
+		result := make(map[string]string, len(qr.Data))
+		for k, v := range qr.Data {
+			// convert all values to strings as the Plugin SDK can't handle dynamic types/objects.
+			//
+			// The not-yet-stable 'terraform-plugin-go' looks to have support for complex object results
+			// so this may be improved in future
+			result[k] = fmt.Sprintf("%v", v)
+		}
+		results[i] = result
+	}
+
+	d.SetId(queryResult.ID)
+	d.Set("query_url", queryResult.Links.Url)
+	d.Set("graph_image_url", queryResult.Links.GraphUrl)
+	d.Set("results", results)
+
+	return nil
+}
diff --git a/honeycombio/provider.go b/honeycombio/provider.go
index 0960c4d1..96e1764f 100644
--- a/honeycombio/provider.go
+++ b/honeycombio/provider.go
@@ -45,6 +45,7 @@ func Provider() *schema.Provider {
 		},
 		DataSourcesMap: map[string]*schema.Resource{
 			"honeycombio_datasets":            dataSourceHoneycombioDatasets(),
+			"honeycombio_query_result":        dataSourceHoneycombioQueryResult(),
 			"honeycombio_query_specification": dataSourceHoneycombioQuerySpec(),
 			"honeycombio_trigger_recipient":   dataSourceHoneycombioSlackRecipient(),
 		},


Test patch:
diff --git a/client/query_result_test.go b/client/query_result_test.go
new file mode 100644
index 00000000..514712d4
--- /dev/null
+++ b/client/query_result_test.go
@@ -0,0 +1,57 @@
+package client
+
+import (
+	"context"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestQueryResults(t *testing.T) {
+	ctx := context.Background()
+
+	var result *QueryResult
+	var err error
+
+	c := newTestClient(t)
+	dataset := testDataset(t)
+
+	query, err := c.Queries.Create(ctx, dataset, &QuerySpec{
+		Calculations: []CalculationSpec{
+			{
+				Op: "COUNT",
+			},
+		},
+		TimeRange: IntPtr(60 * 60 * 24),
+	})
+
+	t.Run("Create", func(t *testing.T) {
+		result, err = c.QueryResults.Create(ctx, dataset, &QueryResultRequest{
+			ID: *query.ID,
+		})
+
+		assert.Nil(t, err, fmt.Sprintf("result errored: %v", err))
+		assert.NotEmpty(t, result.ID, "result missing ID")
+	})
+
+	t.Run("Get", func(t *testing.T) {
+		err := c.QueryResults.Get(ctx, dataset, result)
+
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		assert.True(t, result.Complete, "query result didn't complete")
+		assert.NotNil(t, result.Data.Series, "empty data series")
+		assert.NotNil(t, result.Data.Results, "empty data results")
+		assert.NotEmpty(t, result.Links.GraphUrl, "empty result graph")
+		assert.NotEmpty(t, result.Links.Url, "empty result Url")
+	})
+
+	t.Run("Get_notFound", func(t *testing.T) {
+		err := c.QueryResults.Get(ctx, dataset, &QueryResult{ID: "abcd1234"})
+
+		assert.ErrorIs(t, err, ErrNotFound)
+	})
+}
diff --git a/honeycombio/data_source_query_result_test.go b/honeycombio/data_source_query_result_test.go
new file mode 100644
index 00000000..5f7e19b0
--- /dev/null
+++ b/honeycombio/data_source_query_result_test.go
@@ -0,0 +1,58 @@
+package honeycombio
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
+)
+
+func TestAccDataSourceHoneycombioQueryResult_basic(t *testing.T) {
+	dataset := testAccDataset()
+
+	resource.Test(t, resource.TestCase{
+		PreCheck:          testAccPreCheck(t),
+		ProviderFactories: testAccProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: testAccDataSourceQueryResultConfig(dataset),
+				Check: resource.ComposeTestCheckFunc(
+					resource.TestCheckOutput("results", "COUNT"),
+				),
+			},
+		},
+	})
+}
+
+func testAccDataSourceQueryResultConfig(dataset string) string {
+	return fmt.Sprintf(`
+data "honeycombio_query_specification" "test" {
+  time_range = 86400
+
+  calculation {
+    op = "COUNT"
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
+data "honeycombio_query_result" "test" {
+  dataset  = "%s"
+  query_id = honeycombio_query.test.id
+}
+
+output "results" {
+  # do some type silly iteration over the result to output the calculation ('COUNT')
+  # as the resulting value is variable
+  value = join(", ",
+    flatten(
+      [
+        for result in data.honeycombio_query_result.test.results : [for k, v in result : "${k}"]
+      ]
+    )
+  )
+}`, dataset, dataset)
+}