Repository: honeycombio/terraform-provider-honeycombio (go)

Issue:
Support one column vs multi-column view for Board query display
~Currently only possible to configure in the UI~

Unblocked with the recent addition of the `column_layout` field in the Boards API. Defaults to `multi` but `single` is also possible

Pull request description:
boards: add support for managing column layout
Resolves #109 

Patch:
diff --git a/client/board.go b/client/board.go
index ca409e9..4341afb 100644
--- a/client/board.go
+++ b/client/board.go
@@ -46,6 +46,11 @@ type Board struct {
 	Name string `json:"name"`
 	// Description of the board.
 	Description string `json:"description,omitempty"`
+	// The number of columns to be layed out when displaying the board.
+	// Defaults to "multi".
+	//
+	// n.b. 'list' style boards cannot specify a column layout
+	ColumnLayout BoardColumnStyle `json:"column_layout,omitempty"`
 	// How the board should be displayed in the UI, defaults to "list".
 	Style BoardStyle `json:"style,omitempty"`
 	// A list of queries displayed on the board, in order of appearance.
@@ -66,6 +71,13 @@ func BoardStyles() []BoardStyle {
 	return []BoardStyle{BoardStyleList, BoardStyleVisual}
 }
 
+type BoardColumnStyle string
+
+const (
+	BoardColumnStyleMulti  BoardColumnStyle = "multi"
+	BoardColumnStyleSingle BoardColumnStyle = "single"
+)
+
 // BoardQuery represents a query that is part of a board.
 type BoardQuery struct {
 	Caption string `json:"caption,omitempty"`
diff --git a/docs/resources/board.md b/docs/resources/board.md
index a781352..b10735d 100644
--- a/docs/resources/board.md
+++ b/docs/resources/board.md
@@ -95,6 +95,7 @@ The following arguments are supported:
 
 * `name` - (Required) Name of the board.
 * `description` - (Optional) Description of the board. Supports markdown.
+* `column_layout` - (Optional) the number of columns to layout on the board, either `multi` (the default) or `single`. Only `visual` style boards (see below) have a column layout.
 * `style` - (Optional) How the board should be displayed in the UI, either `list` (the default) or `visual`.
 * `query` - (Optional) Zero or more configurations blocks (described below) with the queries of the board.
 
diff --git a/honeycombio/resource_board.go b/honeycombio/resource_board.go
index 0565d72..1e77cfc 100644
--- a/honeycombio/resource_board.go
+++ b/honeycombio/resource_board.go
@@ -2,6 +2,7 @@ package honeycombio
 
 import (
 	"context"
+	"errors"
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -30,6 +31,12 @@ func newBoard() *schema.Resource {
 				Optional:     true,
 				ValidateFunc: validation.StringLenBetween(1, 1023),
 			},
+			"column_layout": {
+				Type:         schema.TypeString,
+				Optional:     true,
+				Computed:     true,
+				ValidateFunc: validation.StringInSlice([]string{"multi", "single"}, false),
+			},
 			"style": {
 				Type:         schema.TypeString,
 				Optional:     true,
@@ -105,6 +112,7 @@ func resourceBoardRead(ctx context.Context, d *schema.ResourceData, meta interfa
 	d.Set("name", b.Name)
 	d.Set("description", b.Description)
 	d.Set("style", b.Style)
+	d.Set("column_layout", b.ColumnLayout)
 
 	queries := make([]map[string]interface{}, len(b.Queries))
 
@@ -167,11 +175,16 @@ func expandBoard(d *schema.ResourceData) (*honeycombio.Board, error) {
 	}
 
 	board := &honeycombio.Board{
-		ID:          d.Id(),
-		Name:        d.Get("name").(string),
-		Description: d.Get("description").(string),
-		Style:       honeycombio.BoardStyle(d.Get("style").(string)),
-		Queries:     queries,
+		ID:           d.Id(),
+		Name:         d.Get("name").(string),
+		Description:  d.Get("description").(string),
+		Style:        honeycombio.BoardStyle(d.Get("style").(string)),
+		ColumnLayout: honeycombio.BoardColumnStyle(d.Get("column_layout").(string)),
+		Queries:      queries,
+	}
+
+	if board.Style == honeycombio.BoardStyleList && board.ColumnLayout != "" {
+		return nil, errors.New("list style boards cannot specify a column layout")
 	}
 	return board, nil
 }


Test patch:
diff --git a/client/board_test.go b/client/board_test.go
index 61b61ae..041547f 100644
--- a/client/board_test.go
+++ b/client/board_test.go
@@ -31,9 +31,10 @@ func TestBoards(t *testing.T) {
 
 	t.Run("Create", func(t *testing.T) {
 		data := &Board{
-			Name:        fmt.Sprintf("Test Board, created at %v", time.Now()),
-			Description: "A board with some queries",
-			Style:       BoardStyleVisual,
+			Name:         fmt.Sprintf("Test Board, created at %v", time.Now()),
+			Description:  "A board with some queries",
+			Style:        BoardStyleVisual,
+			ColumnLayout: BoardColumnStyleSingle,
 			Queries: []BoardQuery{
 				{
 					Caption:    "A sample query",
@@ -79,6 +80,7 @@ func TestBoards(t *testing.T) {
 			TimeRange: IntPtr(7200), // 2 hours
 		})
 		assert.NoError(t, err)
+		b.ColumnLayout = BoardColumnStyleMulti
 		b.Queries = append(b.Queries, BoardQuery{
 			Caption:    "A second query",
 			QueryStyle: BoardQueryStyleGraph,
diff --git a/honeycombio/resource_board_test.go b/honeycombio/resource_board_test.go
index de41997..52db03d 100644
--- a/honeycombio/resource_board_test.go
+++ b/honeycombio/resource_board_test.go
@@ -2,14 +2,12 @@ package honeycombio
 
 import (
 	"context"
-	"errors"
 	"fmt"
+	"regexp"
 	"testing"
 
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
-	honeycombio "github.com/honeycombio/terraform-provider-honeycombio/client"
-	"github.com/stretchr/testify/assert"
 )
 
 func TestAccHoneycombioBoard_basic(t *testing.T) {
@@ -23,6 +21,18 @@ func TestAccHoneycombioBoard_basic(t *testing.T) {
 				Config: testAccBoardConfig(dataset),
 				Check: resource.ComposeTestCheckFunc(
 					testAccCheckBoardExists(t, "honeycombio_board.test"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "name", "Test board from terraform-provider-honeycombio"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "style", "list"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "description", ""),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "query.#", "2"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "query.0.caption", "test query 0"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "query.0.dataset", dataset),
+					resource.TestCheckResourceAttrPair("honeycombio_board.test", "query.0.query_id", "honeycombio_query.test.0", "id"),
+					resource.TestCheckResourceAttrPair("honeycombio_board.test", "query.0.query_annotation_id", "honeycombio_query_annotation.test.0", "id"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "query.1.caption", "test query 1"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "query.1.dataset", dataset),
+					resource.TestCheckResourceAttrPair("honeycombio_board.test", "query.1.query_id", "honeycombio_query.test.1", "id"),
+					resource.TestCheckResourceAttrPair("honeycombio_board.test", "query.1.query_annotation_id", "honeycombio_query_annotation.test.1", "id"),
 				),
 			},
 			{
@@ -32,6 +42,60 @@ func TestAccHoneycombioBoard_basic(t *testing.T) {
 			},
 		},
 	})
+	resource.Test(t, resource.TestCase{
+		PreCheck:          testAccPreCheck(t),
+		ProviderFactories: testAccProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: fmt.Sprintf(`
+data "honeycombio_query_specification" "test" {
+  calculation {
+    op = "COUNT"
+  }
+}
+
+resource "honeycombio_query" "test" {
+  dataset    = "%s"
+  query_json = data.honeycombio_query_specification.test.json
+}
+
+resource "honeycombio_board" "test" {
+  name          = "simple board"
+  style         = "visual"
+  column_layout = "single"
+
+  query {
+    query_id = honeycombio_query.test.id
+  }
+}
+`, dataset),
+				Check: resource.ComposeTestCheckFunc(
+					testAccCheckBoardExists(t, "honeycombio_board.test"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "name", "simple board"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "style", "visual"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "column_layout", "single"),
+					resource.TestCheckResourceAttr("honeycombio_board.test", "query.#", "1"),
+					resource.TestCheckResourceAttrPair("honeycombio_board.test", "query.0.query_id", "honeycombio_query.test", "id"),
+				),
+			},
+		},
+	})
+	resource.Test(t, resource.TestCase{
+		PreCheck:          testAccPreCheck(t),
+		ProviderFactories: testAccProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: `
+resource "honeycombio_board" "test" {
+  name          = "error board"
+  style         = "list"
+  column_layout = "multi"
+}
+`,
+				ExpectError: regexp.MustCompile(`list style boards cannot specify a column layout`),
+			},
+		},
+	})
 }
 
 func testAccBoardConfig(dataset string) string {
@@ -96,38 +160,10 @@ func testAccCheckBoardExists(t *testing.T, name string) resource.TestCheckFunc {
 		}
 
 		client := testAccClient(t)
-		createdBoard, err := client.Boards.Get(context.Background(), resourceState.Primary.ID)
+		_, err := client.Boards.Get(context.Background(), resourceState.Primary.ID)
 		if err != nil {
 			return fmt.Errorf("could not find created board: %w", err)
 		}
-
-		expectedBoard := &honeycombio.Board{
-			ID:          createdBoard.ID,
-			Name:        "Test board from terraform-provider-honeycombio",
-			Description: "",
-			Style:       honeycombio.BoardStyleList,
-			Queries: []honeycombio.BoardQuery{
-				{
-					Caption:           "test query 0",
-					QueryStyle:        honeycombio.BoardQueryStyleGraph,
-					Dataset:           testAccDataset(),
-					QueryID:           createdBoard.Queries[0].QueryID,
-					QueryAnnotationID: createdBoard.Queries[0].QueryAnnotationID,
-				},
-				{
-					Caption:           "test query 1",
-					QueryStyle:        honeycombio.BoardQueryStyleCombo,
-					Dataset:           testAccDataset(),
-					QueryID:           createdBoard.Queries[1].QueryID,
-					QueryAnnotationID: createdBoard.Queries[1].QueryAnnotationID,
-				},
-			},
-		}
-
-		ok = assert.Equal(t, expectedBoard, createdBoard)
-		if !ok {
-			return errors.New("created board did not match expected board")
-		}
 		return nil
 	}
 }