Skip to content

Commit 51164ed

Browse files
authored
Dashboard management via cli (#49)
* Dashboard management via cli * use most recent dune go version * added missing flag
1 parent f9a9e94 commit 51164ed

8 files changed

Lines changed: 423 additions & 3 deletions

File tree

cli/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/duneanalytics/cli/authconfig"
1717
"github.com/duneanalytics/cli/cmd/auth"
1818
duneconfig "github.com/duneanalytics/cli/cmd/config"
19+
"github.com/duneanalytics/cli/cmd/dashboard"
1920
"github.com/duneanalytics/cli/cmd/dataset"
2021
"github.com/duneanalytics/cli/cmd/docs"
2122
"github.com/duneanalytics/cli/cmd/execution"
@@ -41,6 +42,7 @@ var rootCmd = &cobra.Command{
4142
" - Create, update, archive, and retrieve saved DuneSQL queries\n" +
4243
" - Execute saved queries or raw DuneSQL and display results\n" +
4344
" - Create and manage visualizations (charts, tables, counters) on query results\n" +
45+
" - Create and manage dashboards with visualizations and text widgets\n" +
4446
" - Browse Dune documentation for DuneSQL syntax, API references, and guides\n" +
4547
" - Query real-time wallet and token data via the Sim API\n" +
4648
" - Monitor credit usage, storage consumption, and billing periods\n\n" +
@@ -125,6 +127,7 @@ func init() {
125127
rootCmd.AddCommand(whoami.NewWhoAmICmd())
126128
rootCmd.AddCommand(sim.NewSimCmd())
127129
rootCmd.AddCommand(visualization.NewVisualizationCmd())
130+
rootCmd.AddCommand(dashboard.NewDashboardCmd())
128131
}
129132

130133
// Execute runs the root command via Fang.

cmd/dashboard/archive.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package dashboard
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/duneanalytics/cli/cmdutil"
8+
"github.com/duneanalytics/cli/output"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func newArchiveCmd() *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "archive <dashboard_id>",
15+
Short: "Archive a dashboard by its ID",
16+
Long: `Archive a dashboard by its ID. Archived dashboards are hidden from public view
17+
but can be restored later.
18+
19+
This also deletes any scheduled refresh jobs associated with the dashboard.
20+
21+
Examples:
22+
dune dashboard archive 12345
23+
dune dashboard archive 12345 -o json`,
24+
Args: cobra.ExactArgs(1),
25+
RunE: runArchive,
26+
}
27+
28+
output.AddFormatFlag(cmd, "text")
29+
30+
return cmd
31+
}
32+
33+
func runArchive(cmd *cobra.Command, args []string) error {
34+
dashboardID, err := strconv.Atoi(args[0])
35+
if err != nil {
36+
return fmt.Errorf("invalid dashboard ID %q: must be an integer", args[0])
37+
}
38+
39+
client := cmdutil.ClientFromCmd(cmd)
40+
41+
resp, err := client.ArchiveDashboard(dashboardID)
42+
if err != nil {
43+
return err
44+
}
45+
46+
w := cmd.OutOrStdout()
47+
switch output.FormatFromCmd(cmd) {
48+
case output.FormatJSON:
49+
return output.PrintJSON(w, resp)
50+
default:
51+
fmt.Fprintf(w, "Archived dashboard %d\n", dashboardID)
52+
return nil
53+
}
54+
}

cmd/dashboard/create.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package dashboard
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/duneanalytics/cli/cmdutil"
8+
"github.com/duneanalytics/cli/output"
9+
"github.com/duneanalytics/duneapi-client-go/models"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func newCreateCmd() *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "create",
16+
Short: "Create a new dashboard",
17+
Long: `Create a new dashboard with optional visualization and text widgets.
18+
19+
Visualizations are placed in a grid layout controlled by --columns-per-row:
20+
1 = full-width charts, 2 = half-width (default), 3 = compact overview.
21+
Text widgets always span the full width above visualizations.
22+
23+
The --visualization-ids flag accepts a comma-separated list of visualization IDs
24+
(from 'dune viz create' or 'dune viz list' output).
25+
26+
The --text-widgets flag accepts a JSON array of text widget objects:
27+
--text-widgets '[{"text":"# Dashboard Title"},{"text":"Description here"}]'
28+
29+
Examples:
30+
# Empty dashboard
31+
dune dashboard create --name "My Dashboard" -o json
32+
33+
# Dashboard with visualizations
34+
dune dashboard create --name "DEX Overview" --visualization-ids 111,222,333 -o json
35+
36+
# Dashboard with text header and visualizations
37+
dune dashboard create --name "ETH Analysis" \
38+
--text-widgets '[{"text":"# Ethereum Analysis\nDaily metrics"}]' \
39+
--visualization-ids 111,222 --columns-per-row 1 -o json
40+
41+
# Private dashboard
42+
dune dashboard create --name "Internal Metrics" --private -o json`,
43+
RunE: runCreate,
44+
}
45+
46+
cmd.Flags().String("name", "", "dashboard name (required)")
47+
cmd.Flags().Bool("private", false, "make the dashboard private")
48+
cmd.Flags().Int64Slice("visualization-ids", nil, "visualization IDs to add (comma-separated)")
49+
cmd.Flags().String("text-widgets", "", `text widgets JSON array, e.g. '[{"text":"# Title"}]'`)
50+
cmd.Flags().Int32("columns-per-row", 2, "visualizations per row: 1, 2, or 3")
51+
_ = cmd.MarkFlagRequired("name")
52+
output.AddFormatFlag(cmd, "text")
53+
54+
return cmd
55+
}
56+
57+
func runCreate(cmd *cobra.Command, _ []string) error {
58+
client := cmdutil.ClientFromCmd(cmd)
59+
60+
name, _ := cmd.Flags().GetString("name")
61+
private, _ := cmd.Flags().GetBool("private")
62+
vizIDs, _ := cmd.Flags().GetInt64Slice("visualization-ids")
63+
textWidgetsStr, _ := cmd.Flags().GetString("text-widgets")
64+
columnsPerRow, _ := cmd.Flags().GetInt32("columns-per-row")
65+
66+
req := models.CreateDashboardRequest{
67+
Name: name,
68+
}
69+
70+
if cmd.Flags().Changed("private") {
71+
req.IsPrivate = &private
72+
}
73+
if len(vizIDs) > 0 {
74+
req.VisualizationIDs = vizIDs
75+
}
76+
if textWidgetsStr != "" {
77+
var textWidgets []models.TextWidgetInput
78+
if err := json.Unmarshal([]byte(textWidgetsStr), &textWidgets); err != nil {
79+
return fmt.Errorf("invalid --text-widgets JSON: %w", err)
80+
}
81+
req.TextWidgets = textWidgets
82+
}
83+
if cmd.Flags().Changed("columns-per-row") {
84+
req.ColumnsPerRow = &columnsPerRow
85+
}
86+
87+
resp, err := client.CreateDashboard(req)
88+
if err != nil {
89+
return err
90+
}
91+
92+
w := cmd.OutOrStdout()
93+
switch output.FormatFromCmd(cmd) {
94+
case output.FormatJSON:
95+
return output.PrintJSON(w, resp)
96+
default:
97+
fmt.Fprintf(w, "Created dashboard %d\n%s\n", resp.DashboardID, resp.DashboardURL)
98+
return nil
99+
}
100+
}

cmd/dashboard/dashboard.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dashboard
2+
3+
import "github.com/spf13/cobra"
4+
5+
func NewDashboardCmd() *cobra.Command {
6+
cmd := &cobra.Command{
7+
Use: "dashboard",
8+
Aliases: []string{"dash"},
9+
Short: "Create and manage Dune dashboards",
10+
Long: "Create and manage dashboards on Dune.\n\n" +
11+
"Dashboards are collections of visualizations and text widgets that display\n" +
12+
"blockchain and crypto data. Each dashboard has a unique URL and can be\n" +
13+
"public or private.\n\n" +
14+
"Visualizations are arranged in a 6-column grid. Use --columns-per-row to\n" +
15+
"control layout: 1 for full-width, 2 for half-width (default), 3 for compact.",
16+
}
17+
18+
cmd.AddCommand(newCreateCmd())
19+
cmd.AddCommand(newGetCmd())
20+
cmd.AddCommand(newUpdateCmd())
21+
cmd.AddCommand(newArchiveCmd())
22+
23+
return cmd
24+
}

cmd/dashboard/get.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package dashboard
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
8+
"github.com/duneanalytics/cli/cmdutil"
9+
"github.com/duneanalytics/cli/output"
10+
"github.com/duneanalytics/duneapi-client-go/models"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func newGetCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "get [dashboard_id]",
17+
Short: "Get details of a dashboard",
18+
Long: `Retrieve the full state of a dashboard including metadata and widgets.
19+
20+
Lookup by ID (positional argument):
21+
dune dashboard get 12345
22+
23+
Lookup by owner and slug:
24+
dune dashboard get --owner duneanalytics --slug ethereum-overview
25+
26+
Use -o json to get the full response including all widget details.
27+
28+
Examples:
29+
dune dashboard get 12345 -o json
30+
dune dashboard get --owner alice --slug my-dashboard -o json`,
31+
Args: cobra.MaximumNArgs(1),
32+
RunE: runGet,
33+
}
34+
35+
cmd.Flags().String("owner", "", "owner handle (username or team handle)")
36+
cmd.Flags().String("slug", "", "dashboard URL slug")
37+
output.AddFormatFlag(cmd, "text")
38+
39+
return cmd
40+
}
41+
42+
func runGet(cmd *cobra.Command, args []string) error {
43+
client := cmdutil.ClientFromCmd(cmd)
44+
owner, _ := cmd.Flags().GetString("owner")
45+
slug, _ := cmd.Flags().GetString("slug")
46+
47+
hasID := len(args) > 0
48+
hasSlug := owner != "" && slug != ""
49+
50+
if !hasID && !hasSlug {
51+
return fmt.Errorf("provide either a dashboard ID or both --owner and --slug")
52+
}
53+
if hasID && hasSlug {
54+
return fmt.Errorf("provide either a dashboard ID or --owner/--slug, not both")
55+
}
56+
57+
var resp *models.DashboardResponse
58+
var err error
59+
60+
if hasID {
61+
dashboardID, parseErr := strconv.Atoi(args[0])
62+
if parseErr != nil {
63+
return fmt.Errorf("invalid dashboard ID %q: must be an integer", args[0])
64+
}
65+
resp, err = client.GetDashboard(dashboardID)
66+
} else {
67+
resp, err = client.GetDashboardBySlug(owner, slug)
68+
}
69+
if err != nil {
70+
return err
71+
}
72+
73+
w := cmd.OutOrStdout()
74+
switch output.FormatFromCmd(cmd) {
75+
case output.FormatJSON:
76+
return output.PrintJSON(w, resp)
77+
default:
78+
tags := ""
79+
if len(resp.Tags) > 0 {
80+
tags = strings.Join(resp.Tags, ", ")
81+
}
82+
output.PrintTable(w,
83+
[]string{"Field", "Value"},
84+
[][]string{
85+
{"ID", fmt.Sprintf("%d", resp.DashboardID)},
86+
{"Name", resp.Name},
87+
{"Slug", resp.Slug},
88+
{"Private", fmt.Sprintf("%t", resp.IsPrivate)},
89+
{"Tags", tags},
90+
{"URL", resp.DashboardURL},
91+
{"Visualizations", fmt.Sprintf("%d", len(resp.VisualizationWidgets))},
92+
{"Text Widgets", fmt.Sprintf("%d", len(resp.TextWidgets))},
93+
},
94+
)
95+
return nil
96+
}
97+
}

0 commit comments

Comments
 (0)