Skip to content

Commit 64fe656

Browse files
committed
Use yaml for dashboard interactions instead of json
More user friendly and matches the behavior we have for the `settings` command.
1 parent cf24b1e commit 64fe656

File tree

7 files changed

+97
-107
lines changed

7 files changed

+97
-107
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ release-notes.md
2323
release-notes.json
2424

2525
.localdev/
26+
stackstate-cli

cmd/dashboard/dashboard_apply.go

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dashboard
22

33
import (
4-
"encoding/json"
54
"fmt"
65
"os"
76
"path/filepath"
@@ -11,6 +10,7 @@ import (
1110
"github.com/stackvista/stackstate-cli/generated/stackstate_api"
1211
"github.com/stackvista/stackstate-cli/internal/common"
1312
"github.com/stackvista/stackstate-cli/internal/di"
13+
"sigs.k8s.io/kustomize/kyaml/yaml"
1414
)
1515

1616
// ApplyArgs contains arguments for dashboard apply command
@@ -22,12 +22,12 @@ func DashboardApplyCommand(cli *di.Deps) *cobra.Command {
2222
args := &ApplyArgs{}
2323
cmd := &cobra.Command{
2424
Use: "apply",
25-
Short: "Create or edit a dashboard from JSON",
26-
Long: "Create or edit a dashboard from JSON file.",
25+
Short: "Create or edit a dashboard from YAML",
26+
Long: "Create or edit a dashboard from YAML file.",
2727
RunE: cli.CmdRunEWithApi(RunDashboardApplyCommand(args)),
2828
}
2929

30-
common.AddRequiredFileFlagVar(cmd, &args.File, "Path to a .json file with the dashboard definition")
30+
common.AddRequiredFileFlagVar(cmd, &args.File, "Path to a .yaml file with the dashboard definition")
3131

3232
return cmd
3333
}
@@ -41,26 +41,26 @@ func RunDashboardApplyCommand(args *ApplyArgs) di.CmdWithApiFn {
4141

4242
// Determine file type by extension
4343
ext := strings.ToLower(filepath.Ext(args.File))
44-
if ext != ".json" {
45-
return common.NewCLIArgParseError(fmt.Errorf("unsupported file type: %s. Only .json files are supported", ext))
44+
if ext != ".yaml" {
45+
return common.NewCLIArgParseError(fmt.Errorf("unsupported file type: %s. Only .yaml files are supported", ext))
4646
}
4747

48-
return applyJSONDashboard(cli, api, fileBytes)
48+
return applyYAMLDashboard(cli, api, fileBytes)
4949
}
5050
}
5151

52-
// applyJSONDashboard processes JSON dashboard file and determines create vs update operation
53-
func applyJSONDashboard(cli *di.Deps, api *stackstate_api.APIClient, fileBytes []byte) common.CLIError {
52+
// applyYAMLDashboard processes JSON dashboard file and determines create vs update operation
53+
func applyYAMLDashboard(cli *di.Deps, api *stackstate_api.APIClient, fileBytes []byte) common.CLIError {
5454
// Parse the JSON to determine if it's a create or update operation
5555
var dashboardData map[string]interface{}
56-
if err := json.Unmarshal(fileBytes, &dashboardData); err != nil {
57-
return common.NewCLIArgParseError(fmt.Errorf("failed to parse JSON: %v", err))
56+
if err := yaml.Unmarshal(fileBytes, &dashboardData); err != nil {
57+
return common.NewCLIArgParseError(fmt.Errorf("failed to parse YAML: %v", err))
5858
}
5959

6060
// Check if it has an ID field (indicates update operation)
6161
if idField, hasId := dashboardData["id"]; hasId {
6262
// Update existing dashboard
63-
dashboardId := fmt.Sprintf("%.0f", idField.(float64))
63+
dashboardId := fmt.Sprintf("%.0d", idField.(int))
6464
return updateDashboard(cli, api, dashboardId, dashboardData)
6565
} else {
6666
// Create new dashboard
@@ -71,8 +71,8 @@ func applyJSONDashboard(cli *di.Deps, api *stackstate_api.APIClient, fileBytes [
7171
// createDashboard creates a new dashboard from JSON schema
7272
func createDashboard(cli *di.Deps, api *stackstate_api.APIClient, fileBytes []byte) common.CLIError {
7373
var writeSchema stackstate_api.DashboardWriteSchema
74-
if err := json.Unmarshal(fileBytes, &writeSchema); err != nil {
75-
return common.NewCLIArgParseError(fmt.Errorf("failed to parse JSON as DashboardWriteSchema: %v", err))
74+
if err := yaml.Unmarshal(fileBytes, &writeSchema); err != nil {
75+
return common.NewCLIArgParseError(fmt.Errorf("failed to parse YAML as DashboardWriteSchema: %v", err))
7676
}
7777

7878
// Validate required fields
@@ -115,10 +115,10 @@ func updateDashboard(cli *di.Deps, api *stackstate_api.APIClient, dashboardId st
115115
}
116116
if dashboardContent, ok := dashboardData["dashboard"]; ok {
117117
// Convert dashboard content to PersesDashboard
118-
dashboardBytes, err := json.Marshal(dashboardContent)
118+
dashboardBytes, err := yaml.Marshal(dashboardContent)
119119
if err == nil {
120120
var persesDashboard stackstate_api.PersesDashboard
121-
if err := json.Unmarshal(dashboardBytes, &persesDashboard); err == nil {
121+
if err := yaml.Unmarshal(dashboardBytes, &persesDashboard); err == nil {
122122
patchSchema.SetDashboard(persesDashboard)
123123
}
124124
}

cmd/dashboard/dashboard_apply_test.go

Lines changed: 44 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,25 @@ func createTestApplyResult() sts.DashboardReadFullSchema {
4141

4242
func TestShouldApplyDashboardCreate(t *testing.T) {
4343
// Create a temporary file with dashboard JSON
44-
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.json")
44+
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.yaml")
4545
if err != nil {
4646
panic(err)
4747
}
4848
defer os.Remove(file.Name())
4949

50-
dashboardJSON := `{
51-
"name": "applied-dashboard",
52-
"description": "Dashboard created via apply",
53-
"scope": "publicDashboard",
54-
"dashboard": {
55-
"spec": {
56-
"layouts": [
57-
{
58-
"kind": "Grid",
59-
"spec": {
60-
"items": []
61-
}
62-
}
63-
],
64-
"panels": {}
65-
}
66-
}
67-
}`
68-
69-
_, err = file.WriteString(dashboardJSON)
50+
dashboardYAML := `name: applied-dashboard
51+
description: Dashboard created via apply
52+
scope: publicDashboard
53+
dashboard:
54+
spec:
55+
layouts:
56+
- kind: Grid
57+
spec:
58+
items: []
59+
panels: {}
60+
`
61+
62+
_, err = file.WriteString(dashboardYAML)
7063
assert.Nil(t, err)
7164
file.Close()
7265

@@ -86,20 +79,19 @@ func TestShouldApplyDashboardCreate(t *testing.T) {
8679

8780
func TestShouldApplyDashboardUpdate(t *testing.T) {
8881
// Create a temporary file with dashboard update JSON (includes ID)
89-
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.json")
82+
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.yaml")
9083
if err != nil {
9184
panic(err)
9285
}
9386
defer os.Remove(file.Name())
9487

95-
updateJSON := `{
96-
"id": 1234,
97-
"name": "updated-dashboard",
98-
"description": "Updated dashboard description",
99-
"scope": "privateDashboard"
100-
}`
88+
updateYAML := `id: 1234
89+
name: updated-dashboard
90+
description: Updated dashboard description
91+
scope: privateDashboard
92+
`
10193

102-
_, err = file.WriteString(updateJSON)
94+
_, err = file.WriteString(updateYAML)
10395
assert.Nil(t, err)
10496
file.Close()
10597

@@ -125,25 +117,22 @@ func TestShouldApplyDashboardUpdate(t *testing.T) {
125117
}
126118

127119
func TestShouldApplyDashboardWithJson(t *testing.T) {
128-
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.json")
120+
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.yaml")
129121
if err != nil {
130122
panic(err)
131123
}
132124
defer os.Remove(file.Name())
133125

134-
dashboardJSON := `{
135-
"name": "json-output-dashboard",
136-
"description": "Dashboard for JSON output test",
137-
"scope": "publicDashboard",
138-
"dashboard": {
139-
"spec": {
140-
"layouts": [],
141-
"panels": {}
142-
}
143-
}
144-
}`
145-
146-
_, err = file.WriteString(dashboardJSON)
126+
dashboardYAML := `name: yaml-output-dashboard
127+
description: Dashboard for JSON output test
128+
scope: publicDashboard
129+
dashboard:
130+
spec:
131+
layouts: []
132+
panels: {}
133+
`
134+
135+
_, err = file.WriteString(dashboardYAML)
147136
assert.Nil(t, err)
148137
file.Close()
149138

@@ -174,28 +163,30 @@ func TestApplyDashboardInvalidFileType(t *testing.T) {
174163
_, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", file.Name())
175164

176165
assert.NotNil(t, err)
177-
assert.Contains(t, err.Error(), "unsupported file type: .txt. Only .json files are supported")
166+
assert.Contains(t, err.Error(), "unsupported file type: .txt. Only .yaml files are supported")
178167
}
179168

180169
func TestApplyDashboardMissingFile(t *testing.T) {
181170
cli, cmd := setDashboardApplyCmd(t)
182171

183-
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", "/nonexistent/file.json")
172+
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", "/nonexistent/file.yaml")
184173

185174
assert.NotNil(t, err)
186175
assert.Contains(t, err.Error(), "cannot read file")
187176
}
188177

189178
func TestApplyDashboardInvalidJSON(t *testing.T) {
190-
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.json")
179+
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.yaml")
191180
if err != nil {
192181
panic(err)
193182
}
194183
defer os.Remove(file.Name())
195184

196-
invalidJSON := `{"name": "test", "invalid": json}`
185+
invalidYAML := `name: test
186+
invalid yaml
187+
`
197188

198-
_, err = file.WriteString(invalidJSON)
189+
_, err = file.WriteString(invalidYAML)
199190
assert.Nil(t, err)
200191
file.Close()
201192

@@ -204,20 +195,19 @@ func TestApplyDashboardInvalidJSON(t *testing.T) {
204195
_, err = di.ExecuteCommandWithContext(&cli.Deps, cmd, "--file", file.Name())
205196

206197
assert.NotNil(t, err)
207-
assert.Contains(t, err.Error(), "failed to parse JSON")
198+
assert.Contains(t, err.Error(), "failed to parse YAML")
208199
}
209200

210201
func TestApplyDashboardMissingName(t *testing.T) {
211-
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.json")
202+
file, err := os.CreateTemp(os.TempDir(), "test_dashboard_*.yaml")
212203
if err != nil {
213204
panic(err)
214205
}
215206
defer os.Remove(file.Name())
216207

217-
invalidDashboard := `{
218-
"description": "Dashboard without name",
219-
"scope": "publicDashboard"
220-
}`
208+
invalidDashboard := `description: Dashboard without name
209+
scope: publicDashboard
210+
`
221211

222212
_, err = file.WriteString(invalidDashboard)
223213
assert.Nil(t, err)

cmd/dashboard/dashboard_describe.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dashboard
22

33
import (
4-
"encoding/json"
54
"fmt"
65

76
"github.com/spf13/cobra"
@@ -10,6 +9,7 @@ import (
109
"github.com/stackvista/stackstate-cli/internal/common"
1110
"github.com/stackvista/stackstate-cli/internal/di"
1211
"github.com/stackvista/stackstate-cli/internal/util"
12+
"sigs.k8s.io/kustomize/kyaml/yaml"
1313
)
1414

1515
type DescribeArgs struct {
@@ -22,7 +22,7 @@ func DashboardDescribeCommand(cli *di.Deps) *cobra.Command {
2222
args := &DescribeArgs{}
2323
cmd := &cobra.Command{
2424
Use: "describe",
25-
Short: "Describe a dashboard in STY format",
25+
Short: "Describe a dashboard in YAML format",
2626
Long: "Describe a dashboard in StackState Templated YAML.",
2727
RunE: cli.CmdRunEWithApi(RunDashboardDescribeCommand(args)),
2828
}
@@ -48,11 +48,11 @@ func RunDashboardDescribeCommand(args *DescribeArgs) di.CmdWithApiFn {
4848
return common.NewResponseError(err, resp)
4949
}
5050

51-
jsonData, err := json.MarshalIndent(dashboard, "", " ")
51+
yamlData, err := yaml.Marshal(dashboard)
5252
if err != nil {
5353
return common.NewExecutionError(fmt.Errorf("failed to marshal dashboard: %v", err))
5454
}
55-
data := string(jsonData)
55+
data := string(yamlData)
5656

5757
if args.FilePath != "" {
5858
if err := util.WriteFile(args.FilePath, []byte(data)); err != nil {

cmd/dashboard/dashboard_describe_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ func TestDashboardDescribe(t *testing.T) {
4848
_, err := di.ExecuteCommandWithContext(&cli.Deps, cmd, "--id", "1")
4949
assert.Nil(t, err)
5050

51-
// Verify that the command printed the dashboard JSON
51+
// Verify that the command printed the dashboard YAML
5252
assert.Len(t, *cli.MockPrinter.PrintLnCalls, 1)
5353
printedOutput := (*cli.MockPrinter.PrintLnCalls)[0]
54-
assert.Contains(t, printedOutput, `"id": 1`)
55-
assert.Contains(t, printedOutput, `"name": "aDashboard"`)
56-
assert.Contains(t, printedOutput, `"identifier": "urn:custom:dashboard:aDashboard"`)
54+
assert.Contains(t, printedOutput, `id: 1`)
55+
assert.Contains(t, printedOutput, `name: aDashboard`)
56+
assert.Contains(t, printedOutput, `identifier: urn:custom:dashboard:aDashboard`)
5757
}
5858

5959
func TestDashboardDescribeWithIdentifier(t *testing.T) {
@@ -106,8 +106,8 @@ func TestDashboardDescribeToFile(t *testing.T) {
106106
// Verify file contents
107107
body, err := os.ReadFile(file.Name())
108108
assert.Nil(t, err)
109-
assert.Contains(t, string(body), `"id": 1`)
110-
assert.Contains(t, string(body), `"name": "aDashboard"`)
109+
assert.Contains(t, string(body), `id: 1`)
110+
assert.Contains(t, string(body), `name: aDashboard`)
111111
}
112112

113113
func TestDashboardDescribeToFileJson(t *testing.T) {

cmd/dashboard/dashboard_edit.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
stscobra "github.com/stackvista/stackstate-cli/internal/cobra"
1111
"github.com/stackvista/stackstate-cli/internal/common"
1212
"github.com/stackvista/stackstate-cli/internal/di"
13+
"sigs.k8s.io/kustomize/kyaml/yaml"
1314
)
1415

1516
const LongDescription = `Edit a dashboard.
@@ -60,19 +61,19 @@ func RunDashboardEditCommand(args *EditArgs) di.CmdWithApiFn {
6061
}
6162

6263
// Convert dashboard to pretty JSON for editing
63-
originalJSON, err := json.MarshalIndent(dashboard, "", " ")
64+
originalYAML, err := yaml.Marshal(dashboard)
6465
if err != nil {
65-
return common.NewExecutionError(fmt.Errorf("failed to marshal dashboard to JSON: %v", err))
66+
return common.NewExecutionError(fmt.Errorf("failed to marshal dashboard to YAML: %v", err))
6667
}
6768

6869
// Open editor with the dashboard JSON
69-
editedContent, err := cli.Editor.Edit("dashboard-", ".json", strings.NewReader(string(originalJSON)))
70+
editedContent, err := cli.Editor.Edit("dashboard-", ".yaml", strings.NewReader(string(originalYAML)))
7071
if err != nil {
7172
return common.NewExecutionError(fmt.Errorf("failed to open editor: %v", err))
7273
}
7374

7475
// Check if any changes were made
75-
if strings.Compare(string(originalJSON), string(editedContent)) == 0 {
76+
if strings.Compare(string(originalYAML), string(editedContent)) == 0 {
7677
if cli.IsJson() {
7778
cli.Printer.PrintJson(map[string]interface{}{"message": "No changes made"})
7879
} else {
@@ -81,13 +82,13 @@ func RunDashboardEditCommand(args *EditArgs) di.CmdWithApiFn {
8182
return nil
8283
}
8384

84-
// Parse the edited JSON
85+
// Parse the edited YAML
8586
var editedDashboard map[string]interface{}
86-
if err := json.Unmarshal(editedContent, &editedDashboard); err != nil {
87-
return common.NewExecutionError(fmt.Errorf("failed to parse edited JSON: %v", err))
87+
if err := yaml.Unmarshal(editedContent, &editedDashboard); err != nil {
88+
return common.NewExecutionError(fmt.Errorf("failed to parse edited YAML: %v", err))
8889
}
8990

90-
// Create patch schema from the edited JSON
91+
// Create patch schema from the edited YAML
9192
patchSchema := stackstate_api.NewDashboardPatchSchema()
9293

9394
if name, ok := editedDashboard["name"].(string); ok && name != "" {

0 commit comments

Comments
 (0)