Skip to content

Commit beeb106

Browse files
authored
Add create visualization CLI command (#47)
* feat: add dune viz create command * chore: upgrade duneapi-client-go to v0.4.6 * docs updates * docs: note that visualizations require non-temp queries * docs: list all 10 visualization types in --type flag help
1 parent 39b2126 commit beeb106

5 files changed

Lines changed: 168 additions & 3 deletions

File tree

cli/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/duneanalytics/cli/cmd/query"
2323
"github.com/duneanalytics/cli/cmd/sim"
2424
"github.com/duneanalytics/cli/cmd/usage"
25+
"github.com/duneanalytics/cli/cmd/visualization"
2526
"github.com/duneanalytics/cli/cmd/whoami"
2627
"github.com/duneanalytics/cli/cmdutil"
2728
"github.com/duneanalytics/cli/tracking"
@@ -121,6 +122,7 @@ func init() {
121122
rootCmd.AddCommand(usage.NewUsageCmd())
122123
rootCmd.AddCommand(whoami.NewWhoAmICmd())
123124
rootCmd.AddCommand(sim.NewSimCmd())
125+
rootCmd.AddCommand(visualization.NewVisualizationCmd())
124126
}
125127

126128
// Execute runs the root command via Fang.

cmd/visualization/create.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package visualization
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 visualization on an existing query",
17+
Long: `Create a visualization attached to an existing saved query.
18+
19+
IMPORTANT: Visualizations can only be attached to saved (non-temporary) queries.
20+
If you created the query with --temp, you must recreate it without --temp first.
21+
22+
The --options flag is required for a working visualization. Without proper options,
23+
the visualization will fail to render. The options format depends on the
24+
visualization type.
25+
26+
Visualization types: chart, table, counter, pivot, cohort, funnel, choropleth,
27+
sankey, sunburst_sequence, word_cloud.
28+
29+
For chart type, set globalSeriesType to: column, line, area, scatter, or pie.
30+
31+
COUNTER (simplest — shows a single number):
32+
--type counter --options '{
33+
"counterColName": "<column>",
34+
"rowNumber": 1,
35+
"stringDecimal": 0,
36+
"stringPrefix": "",
37+
"stringSuffix": "",
38+
"counterLabel": "My Label",
39+
"coloredPositiveValues": false,
40+
"coloredNegativeValues": false
41+
}'
42+
43+
TABLE (displays query results as a table):
44+
--type table --options '{
45+
"itemsPerPage": 25,
46+
"columns": [
47+
{"name": "<column>", "title": "Display Name", "type": "normal",
48+
"alignContent": "left", "isHidden": false}
49+
]
50+
}'
51+
52+
COLUMN/LINE/AREA/SCATTER CHART:
53+
--type chart --options '{
54+
"globalSeriesType": "line",
55+
"sortX": true,
56+
"legend": {"enabled": true},
57+
"series": {"stacking": null},
58+
"xAxis": {"title": {"text": "Date"}},
59+
"yAxis": [{"title": {"text": "Value"}}],
60+
"columnMapping": {"<x_column>": "x", "<y_column>": "y"},
61+
"seriesOptions": {
62+
"<y_column>": {"type": "line", "yAxis": 0, "zIndex": 0}
63+
}
64+
}'
65+
66+
PIE CHART:
67+
--type chart --options '{
68+
"globalSeriesType": "pie",
69+
"sortX": true,
70+
"showDataLabels": true,
71+
"columnMapping": {"<category_column>": "x", "<value_column>": "y"},
72+
"seriesOptions": {
73+
"<value_column>": {"type": "pie", "yAxis": 0, "zIndex": 0}
74+
}
75+
}'
76+
77+
Column names in options must match actual query result columns exactly.
78+
79+
Examples:
80+
# Counter showing row count
81+
dune viz create --query-id 12345 --name "Total Count" --type counter \
82+
--options '{"counterColName":"count","rowNumber":1,"stringDecimal":0}'
83+
84+
# Line chart of daily volume
85+
dune viz create --query-id 12345 --name "Daily Volume" --type chart \
86+
--options '{"globalSeriesType":"line","sortX":true,"columnMapping":{"day":"x","volume":"y"},"seriesOptions":{"volume":{"type":"line","yAxis":0,"zIndex":0}},"xAxis":{"title":{"text":"Day"}},"yAxis":[{"title":{"text":"Volume"}}],"legend":{"enabled":true},"series":{"stacking":null}}'
87+
88+
# Simple table
89+
dune viz create --query-id 12345 --name "Results" --type table \
90+
--options '{"itemsPerPage":25,"columns":[{"name":"address","title":"Address","type":"normal","alignContent":"left","isHidden":false},{"name":"balance","title":"Balance","type":"normal","alignContent":"right","isHidden":false}]}'`,
91+
RunE: runCreate,
92+
}
93+
94+
cmd.Flags().Int("query-id", 0, "ID of the query to attach the visualization to (required)")
95+
cmd.Flags().String("name", "", "visualization name, max 300 characters (required)")
96+
cmd.Flags().String("type", "table", "visualization type: chart, table, counter, pivot, cohort, funnel, choropleth, sankey, sunburst_sequence, word_cloud")
97+
cmd.Flags().String("description", "", "visualization description, max 1000 characters")
98+
cmd.Flags().String("options", "", `visualization options JSON (required for working visualizations, see --help for format per type)`)
99+
_ = cmd.MarkFlagRequired("query-id")
100+
_ = cmd.MarkFlagRequired("name")
101+
_ = cmd.MarkFlagRequired("options")
102+
output.AddFormatFlag(cmd, "text")
103+
104+
return cmd
105+
}
106+
107+
func runCreate(cmd *cobra.Command, _ []string) error {
108+
client := cmdutil.ClientFromCmd(cmd)
109+
110+
queryID, _ := cmd.Flags().GetInt("query-id")
111+
name, _ := cmd.Flags().GetString("name")
112+
vizType, _ := cmd.Flags().GetString("type")
113+
description, _ := cmd.Flags().GetString("description")
114+
optionsStr, _ := cmd.Flags().GetString("options")
115+
116+
var options map[string]any
117+
if err := json.Unmarshal([]byte(optionsStr), &options); err != nil {
118+
return fmt.Errorf("invalid --options JSON: %w", err)
119+
}
120+
121+
resp, err := client.CreateVisualization(models.CreateVisualizationRequest{
122+
QueryID: queryID,
123+
Name: name,
124+
Type: vizType,
125+
Description: description,
126+
Options: options,
127+
})
128+
if err != nil {
129+
return err
130+
}
131+
132+
url := fmt.Sprintf("https://dune.com/embeds/%d/%d", queryID, resp.ID)
133+
134+
w := cmd.OutOrStdout()
135+
switch output.FormatFromCmd(cmd) {
136+
case output.FormatJSON:
137+
return output.PrintJSON(w, map[string]any{
138+
"id": resp.ID,
139+
"url": url,
140+
})
141+
default:
142+
fmt.Fprintf(w, "Created visualization %d on query %d\n%s\n", resp.ID, queryID, url)
143+
return nil
144+
}
145+
}

cmd/visualization/visualization.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package visualization
2+
3+
import "github.com/spf13/cobra"
4+
5+
func NewVisualizationCmd() *cobra.Command {
6+
cmd := &cobra.Command{
7+
Use: "visualization",
8+
Aliases: []string{"viz"},
9+
Short: "Create and manage Dune visualizations",
10+
Long: "Create and manage visualizations on Dune queries.\n\n" +
11+
"Visualizations are charts, tables, counters, and other visual representations\n" +
12+
"of query results. Each visualization is attached to a saved query.",
13+
}
14+
15+
cmd.AddCommand(newCreateCmd())
16+
17+
return cmd
18+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.25.6
55
require (
66
github.com/amplitude/analytics-go v1.3.0
77
github.com/charmbracelet/fang v0.4.4
8-
github.com/duneanalytics/duneapi-client-go v0.4.5
8+
github.com/duneanalytics/duneapi-client-go v0.4.6
99
github.com/modelcontextprotocol/go-sdk v1.4.0
1010
github.com/spf13/cobra v1.10.2
1111
github.com/stretchr/testify v1.11.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsV
3131
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
3232
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3333
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34-
github.com/duneanalytics/duneapi-client-go v0.4.5 h1:LSSW2B2KG/OIXWuWHfVpOhsvuuR/lw4nJ7uyYE6NHB4=
35-
github.com/duneanalytics/duneapi-client-go v0.4.5/go.mod h1:7pXXufWvR/Mh2KOehdyBaunJXmHI+pzjUmyQTQhJjdE=
34+
github.com/duneanalytics/duneapi-client-go v0.4.6 h1:ZjW/H86Da6RY3K9rQFUV3pU1w8kTVlgSGH3k4v3VP7Q=
35+
github.com/duneanalytics/duneapi-client-go v0.4.6/go.mod h1:7pXXufWvR/Mh2KOehdyBaunJXmHI+pzjUmyQTQhJjdE=
3636
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
3737
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
3838
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=

0 commit comments

Comments
 (0)