Skip to content

Commit 67c6495

Browse files
Paolo Calaopolldo
Paolo Calao
authored andcommitted
Add dashboard extract command (#47)
Add a command to extract templates from dashboards: '$ arduino-cloud-cli dashboard extract --id <dashboardID> --outfile <templateFile> --format <yaml|json>' * Add dashboard extract command * Include dashboard variables only if any var is present * Add widget options filter * Include widgets only if not empty * Update readme
1 parent 382b7c2 commit 67c6495

File tree

7 files changed

+260
-0
lines changed

7 files changed

+260
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -128,3 +128,7 @@ Print a list of available dashboards and their widgets by using this command:
128128
Delete a dashboard with the following command:
129129

130130
`$ arduino-cloud-cli dashboard delete --id <dashboardID>`
131+
132+
Extract a template from an existing dashboard. The template can be saved in two formats: json or yaml. The default format is yaml:
133+
134+
`$ arduino-cloud-cli dashboard extract --id <dashboardID> --outfile <templateFile> --format <yaml|json>`

cli/dashboard/dashboard.go

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func NewCommand() *cobra.Command {
3030

3131
dashboardCommand.AddCommand(initListCommand())
3232
dashboardCommand.AddCommand(initDeleteCommand())
33+
dashboardCommand.AddCommand(initExtractCommand())
3334

3435
return dashboardCommand
3536
}

cli/dashboard/extract.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package dashboard
19+
20+
import (
21+
"os"
22+
23+
"github.com/arduino/arduino-cli/cli/errorcodes"
24+
"github.com/arduino/arduino-cli/cli/feedback"
25+
"github.com/arduino/arduino-cloud-cli/command/dashboard"
26+
"github.com/sirupsen/logrus"
27+
"github.com/spf13/cobra"
28+
)
29+
30+
var extractFlags struct {
31+
id string
32+
outfile string
33+
format string
34+
}
35+
36+
func initExtractCommand() *cobra.Command {
37+
extractCommand := &cobra.Command{
38+
Use: "extract",
39+
Short: "Extract a template from a dashboard",
40+
Long: "Extract a template from a Arduino IoT Cloud dashboard and save it in a file",
41+
Run: runExtractCommand,
42+
}
43+
extractCommand.Flags().StringVarP(&extractFlags.id, "id", "i", "", "Dashboard ID")
44+
extractCommand.Flags().StringVarP(&extractFlags.outfile, "outfile", "o", "", "Template file destination path")
45+
extractCommand.Flags().StringVar(
46+
&extractFlags.format,
47+
"format",
48+
"yaml",
49+
"Format of template file, can be {json|yaml}. Default is 'yaml'",
50+
)
51+
52+
extractCommand.MarkFlagRequired("id")
53+
return extractCommand
54+
}
55+
56+
func runExtractCommand(cmd *cobra.Command, args []string) {
57+
logrus.Infof("Extracting template from dashboard %s\n", extractFlags.id)
58+
59+
params := &dashboard.ExtractParams{
60+
ID: extractFlags.id,
61+
Format: extractFlags.format,
62+
}
63+
if extractFlags.outfile != "" {
64+
params.Outfile = &extractFlags.outfile
65+
}
66+
67+
err := dashboard.Extract(params)
68+
if err != nil {
69+
feedback.Errorf("Error during template extraction: %v", err)
70+
os.Exit(errorcodes.ErrGeneric)
71+
}
72+
73+
logrus.Info("Template successfully extracted")
74+
}

command/dashboard/extract.go

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package dashboard
19+
20+
import (
21+
"errors"
22+
"fmt"
23+
"strings"
24+
25+
"github.com/arduino/arduino-cloud-cli/internal/config"
26+
"github.com/arduino/arduino-cloud-cli/internal/iot"
27+
"github.com/arduino/arduino-cloud-cli/internal/template"
28+
)
29+
30+
// ExtractParams contains the parameters needed to
31+
// extract a template dashboard from Arduino IoT Cloud and save it on local storage.
32+
type ExtractParams struct {
33+
ID string
34+
Format string // Format determines the file format of the template ("json" or "yaml")
35+
Outfile *string // Destination path of the extracted template
36+
}
37+
38+
// Extract command is used to extract a dashboard template
39+
// from a dashboard on Arduino IoT Cloud.
40+
func Extract(params *ExtractParams) error {
41+
params.Format = strings.ToLower(params.Format)
42+
if params.Format != "json" && params.Format != "yaml" {
43+
return errors.New("format is not valid: only 'json' and 'yaml' are supported")
44+
}
45+
46+
conf, err := config.Retrieve()
47+
if err != nil {
48+
return err
49+
}
50+
iotClient, err := iot.NewClient(conf.Client, conf.Secret)
51+
if err != nil {
52+
return err
53+
}
54+
55+
dashboard, err := iotClient.DashboardShow(params.ID)
56+
if err != nil {
57+
err = fmt.Errorf("%s: %w", "cannot extract dashboard: ", err)
58+
return err
59+
}
60+
61+
templ, err := template.FromDashboard(dashboard)
62+
if err != nil {
63+
return err
64+
}
65+
66+
if params.Outfile == nil {
67+
name, ok := templ["name"].(string)
68+
if name == "" || !ok {
69+
return errors.New("dashboard template does not have a valid name")
70+
}
71+
outfile := name + "-dashboard" + "." + params.Format
72+
params.Outfile = &outfile
73+
}
74+
75+
err = template.ToFile(templ, *params.Outfile, params.Format)
76+
if err != nil {
77+
return fmt.Errorf("saving template: %w", err)
78+
}
79+
80+
return nil
81+
}

internal/iot/client.go

+12
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ type Client interface {
3939
ThingDelete(id string) error
4040
ThingShow(id string) (*iotclient.ArduinoThing, error)
4141
ThingList(ids []string, device *string, props bool) ([]iotclient.ArduinoThing, error)
42+
DashboardShow(id string) (*iotclient.ArduinoDashboardv2, error)
4243
DashboardDelete(id string) error
4344
DashboardList() ([]iotclient.ArduinoDashboardv2, error)
4445
}
@@ -204,6 +205,17 @@ func (cl *client) ThingList(ids []string, device *string, props bool) ([]iotclie
204205
return things, nil
205206
}
206207

208+
// DashboardShow allows to retrieve a specific dashboard, given its id,
209+
// from Arduino IoT Cloud.
210+
func (cl *client) DashboardShow(id string) (*iotclient.ArduinoDashboardv2, error) {
211+
dashboard, _, err := cl.api.DashboardsV2Api.DashboardsV2Show(cl.ctx, id)
212+
if err != nil {
213+
err = fmt.Errorf("retrieving dashboard, %w", errorDetail(err))
214+
return nil, err
215+
}
216+
return &dashboard, nil
217+
}
218+
207219
// DashboardList returns a list of dashboards on Arduino IoT Cloud.
208220
func (cl *client) DashboardList() ([]iotclient.ArduinoDashboardv2, error) {
209221
dashboards, _, err := cl.api.DashboardsV2Api.DashboardsV2List(cl.ctx, nil)

internal/template/extract.go

+47
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,53 @@ func FromThing(thing *iotclient.ArduinoThing) (map[string]interface{}, error) {
5050
return template, nil
5151
}
5252

53+
// FromDashboard extracts a template of type map[string]interface{} from a dashboard.
54+
func FromDashboard(dashboard *iotclient.ArduinoDashboardv2) (map[string]interface{}, error) {
55+
template := make(map[string]interface{})
56+
template["name"] = dashboard.Name
57+
58+
// Extract template from dashboard structure
59+
var widgets []map[string]interface{}
60+
for _, w := range dashboard.Widgets {
61+
widget := make(map[string]interface{})
62+
widget["type"] = w.Type
63+
widget["name"] = w.Name
64+
widget["width"] = w.Width
65+
widget["height"] = w.Height
66+
widget["x"] = w.X
67+
widget["y"] = w.Y
68+
69+
if w.WidthMobile != 0 && w.HeightMobile != 0 {
70+
widget["width_mobile"] = w.WidthMobile
71+
widget["height_mobile"] = w.HeightMobile
72+
widget["x_mobile"] = w.XMobile
73+
widget["y_mobile"] = w.YMobile
74+
}
75+
76+
var vars []map[string]interface{}
77+
for _, v := range w.Variables {
78+
variable := make(map[string]interface{})
79+
variable["thing_id"] = v.ThingName
80+
variable["variable_id"] = v.VariableName
81+
vars = append(vars, variable)
82+
}
83+
if len(vars) > 0 {
84+
widget["variables"] = vars
85+
}
86+
87+
filterWidgetOptions(w.Options)
88+
if len(w.Options) > 0 {
89+
widget["options"] = w.Options
90+
}
91+
92+
widgets = append(widgets, widget)
93+
}
94+
if len(widgets) > 0 {
95+
template["widgets"] = widgets
96+
}
97+
return template, nil
98+
}
99+
53100
// ToFile takes a generic template and saves it into a file,
54101
// in the specified format (yaml or json).
55102
func ToFile(template map[string]interface{}, outfile string, format string) error {

internal/template/filter.go

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// This file is part of arduino-cloud-cli.
2+
//
3+
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This program is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU Affero General Public License as published
7+
// by the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// This program is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU Affero General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU Affero General Public License
16+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
18+
package template
19+
20+
var (
21+
widgetOptWhitelist = map[string]struct{}{
22+
"showThing": {},
23+
"frameless": {},
24+
"interpolation": {},
25+
"max": {},
26+
"min": {},
27+
"mode": {},
28+
"percentage": {},
29+
"showLabels": {},
30+
"step": {},
31+
"vertical": {},
32+
}
33+
)
34+
35+
func filterWidgetOptions(opts map[string]interface{}) {
36+
for opt := range opts {
37+
if _, ok := widgetOptWhitelist[opt]; !ok {
38+
delete(opts, opt)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)