Skip to content

Add dashboard create command #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ Delete a dashboard with the following command:
Extract a template from an existing dashboard. The template can be saved in two formats: json or yaml. The default format is yaml:

`$ arduino-cloud-cli dashboard extract --id <dashboardID> --outfile <templateFile> --format <yaml|json>`

Create a dashboard: dashboards can be created only starting from a template. Supported dashboard template formats are JSON and YAML. The name parameter is optional. If it is provided then it overrides the name retrieved from the template. The `override` flag can be used to override the template `thing_id` placeholder with the actual ID of the thing to be used.

`$ arduino-cloud-cli dashboard create --name <dashboardName> --template <template.(json|yaml)> --override <thing-0>=<actualThingID>,<thing-1>=<otherActualThingID>`
92 changes: 92 additions & 0 deletions cli/dashboard/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package dashboard

import (
"fmt"
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cloud-cli/command/dashboard"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var createFlags struct {
name string
template string
override map[string]string
}

func initCreateCommand() *cobra.Command {
createCommand := &cobra.Command{
Use: "create",
Short: "Create a dashboard from a template",
Long: "Create a dashboard from a template for Arduino IoT Cloud",
Run: runCreateCommand,
}
createCommand.Flags().StringVarP(&createFlags.name, "name", "n", "", "Dashboard name")
createCommand.Flags().StringVarP(&createFlags.template, "template", "t", "",
"File containing a dashboard template, JSON and YAML format are supported",
)
createCommand.Flags().StringToStringVarP(&createFlags.override, "override", "o", nil,
"Map stating the items to be overridden. Ex: 'thing-0=xxxxxxxx,thing-1=yyyyyyyy'")

createCommand.MarkFlagRequired("template")
return createCommand
}

func runCreateCommand(cmd *cobra.Command, args []string) {
logrus.Infof("Creating dashboard from template %s\n", createFlags.template)

params := &dashboard.CreateParams{
Template: createFlags.template,
Override: createFlags.override,
}
if createFlags.name != "" {
params.Name = &createFlags.name
}

dashboard, err := dashboard.Create(params)
if err != nil {
feedback.Errorf("Error during dashboard create: %v", err)
os.Exit(errorcodes.ErrGeneric)
}

feedback.PrintResult(createResult{dashboard})
}

type createResult struct {
dashboard *dashboard.DashboardInfo
}

func (r createResult) Data() interface{} {
return r.dashboard
}

func (r createResult) String() string {
return fmt.Sprintf(
"name: %s\nid: %s\nupdated_at: %s\nwidgets: %s",
r.dashboard.Name,
r.dashboard.ID,
r.dashboard.UpdatedAt,
strings.Join(r.dashboard.Widgets, ", "),
)
}
1 change: 1 addition & 0 deletions cli/dashboard/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func NewCommand() *cobra.Command {
Long: "Dashboard commands.",
}

dashboardCommand.AddCommand(initCreateCommand())
dashboardCommand.AddCommand(initListCommand())
dashboardCommand.AddCommand(initDeleteCommand())
dashboardCommand.AddCommand(initExtractCommand())
Expand Down
66 changes: 66 additions & 0 deletions command/dashboard/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package dashboard

import (
"errors"

"github.com/arduino/arduino-cloud-cli/internal/config"
"github.com/arduino/arduino-cloud-cli/internal/iot"
"github.com/arduino/arduino-cloud-cli/internal/template"
)

// CreateParams contains the parameters needed to create a new dashboard.
type CreateParams struct {
Name *string // Name of the new dashboard
Override map[string]string // Template parameters to be overridden
Template string // Path of the template file
}

// Create allows to create a new dashboard
func Create(params *CreateParams) (*DashboardInfo, error) {
conf, err := config.Retrieve()
if err != nil {
return nil, err
}
iotClient, err := iot.NewClient(conf.Client, conf.Secret)
if err != nil {
return nil, err
}

dashboard, err := template.LoadDashboard(params.Template, params.Override, iotClient)
if err != nil {
return nil, err
}

// Name passed as parameter has priority over name from template
if params.Name != nil {
dashboard.Name = *params.Name
}
// If name is not specified in the template, it should be passed as parameter
if dashboard.Name == "" {
return nil, errors.New("dashboard name not specified")
}

newDashboard, err := iotClient.DashboardCreate(dashboard)
if err != nil {
return nil, err
}

return getDashboardInfo(newDashboard), nil
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ require (
github.com/arduino/arduino-cli v0.0.0-20210607095659-16f41352eac3
github.com/arduino/go-paths-helper v1.6.1
github.com/arduino/iot-client-go v1.3.4-0.20210930122852-04551f4cb061
github.com/gofrs/uuid v4.0.0+incompatible
github.com/google/go-cmp v0.5.6
github.com/howeyc/crc16 v0.0.0-20171223171357-2b2a61e366a6
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5
github.com/sirupsen/logrus v1.8.1
Expand Down
5 changes: 4 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down Expand Up @@ -177,8 +179,9 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
Expand Down
10 changes: 10 additions & 0 deletions internal/iot/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Client interface {
ThingDelete(id string) error
ThingShow(id string) (*iotclient.ArduinoThing, error)
ThingList(ids []string, device *string, props bool) ([]iotclient.ArduinoThing, error)
DashboardCreate(dashboard *iotclient.Dashboardv2) (*iotclient.ArduinoDashboardv2, error)
DashboardShow(id string) (*iotclient.ArduinoDashboardv2, error)
DashboardDelete(id string) error
DashboardList() ([]iotclient.ArduinoDashboardv2, error)
Expand Down Expand Up @@ -205,6 +206,15 @@ func (cl *client) ThingList(ids []string, device *string, props bool) ([]iotclie
return things, nil
}

// DashboardCreate adds a new dashboard on Arduino IoT Cloud.
func (cl *client) DashboardCreate(dashboard *iotclient.Dashboardv2) (*iotclient.ArduinoDashboardv2, error) {
newDashboard, _, err := cl.api.DashboardsV2Api.DashboardsV2Create(cl.ctx, *dashboard)
if err != nil {
return nil, fmt.Errorf("%s: %w", "adding new dashboard", errorDetail(err))
}
return &newDashboard, nil
}

// DashboardShow allows to retrieve a specific dashboard, given its id,
// from Arduino IoT Cloud.
func (cl *client) DashboardShow(id string) (*iotclient.ArduinoDashboardv2, error) {
Expand Down
79 changes: 79 additions & 0 deletions internal/template/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// This file is part of arduino-cloud-cli.
//
// Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package template

import (
"encoding/json"
"fmt"

"github.com/arduino/arduino-cloud-cli/internal/iot"
)

type dashboardTemplate struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Widgets []widgetTemplate `json:"widgets,omitempty" yaml:"widgets,omitempty"`
}

type widgetTemplate struct {
Height int64 `json:"height" yaml:"height"`
HeightMobile int64 `json:"height_mobile,omitempty" yaml:"height_mobile,omitempty"`
Id string `json:"id" yaml:"id"`
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Options map[string]interface{} `json:"options" yaml:"options"`
Type string `json:"type" yaml:"type"`
Variables []variableTemplate `json:"variables,omitempty" yaml:"variables,omitempty"`
Width int64 `json:"width" yaml:"width"`
WidthMobile int64 `json:"width_mobile,omitempty" yaml:"width_mobile,omitempty"`
X int64 `json:"x" yaml:"x"`
XMobile int64 `json:"x_mobile,omitempty" yaml:"x_mobile,omitempty"`
Y int64 `json:"y" yaml:"y"`
YMobile int64 `json:"y_mobile,omitempty" yaml:"y_mobile,omitempty"`
}

type variableTemplate struct {
ThingID string `json:"thing_id" yaml:"thing_id"`
VariableName string `json:"variable_id" yaml:"variable_id"`
VariableID string
}

// MarshalJSON satisfies the Marshaler interface from json package.
// With this, when a variableTemplate is marshaled, it only marshals
// its VariableID. In this way, a widgetTemplate can be
// marshaled and then unmarshaled into a iot.Widget struct.
// In the same way, a dashboardTemplate can now be converted
// into a iot.DashboardV2 leveraging the JSON marshal/unmarshal.
func (v *variableTemplate) MarshalJSON() ([]byte, error) {
return json.Marshal(v.VariableID)
}

// getVariableID returns the id of a variable, given its name and its thing id.
// If the variable is not found, an error is returned.
func getVariableID(thingID string, variableName string, iotClient iot.Client) (string, error) {
thing, err := iotClient.ThingShow(thingID)
if err != nil {
return "", fmt.Errorf("getting variables of thing %s: %w", thingID, err)
}

for _, v := range thing.Properties {
if v.Name == variableName {
return v.Id, nil
}
}

return "", fmt.Errorf("thing with id %s doesn't have variable with name %s : %w", thingID, variableName, err)
}
1 change: 0 additions & 1 deletion internal/template/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ func FromDashboard(dashboard *iotclient.ArduinoDashboardv2) map[string]interface
if len(w.Options) > 0 {
widget["options"] = w.Options
}

widgets = append(widgets, widget)
}
if len(widgets) > 0 {
Expand Down
Loading