Browse Source

more work on dashboard importing and templating

Torkel Ödegaard 9 years ago
parent
commit
c5a817194a

+ 1 - 1
pkg/api/api.go

@@ -176,7 +176,7 @@ func Register(r *macaron.Macaron) {
 			r.Get("/", wrap(GetPluginList))
 
 			r.Get("/dashboards/:pluginId", wrap(GetPluginDashboards))
-			r.Post("/dashboards/install", bind(dtos.InstallPluginDashboardCmd{}), wrap(InstallPluginDashboard))
+			r.Post("/dashboards/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
 
 			r.Get("/:pluginId/settings", wrap(GetPluginSettingById))
 			r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting))

+ 5 - 5
pkg/api/dtos/plugins.go

@@ -26,9 +26,9 @@ type PluginListItem struct {
 	Info    *plugins.PluginInfo `json:"info"`
 }
 
-type InstallPluginDashboardCmd struct {
-	PluginId  string                 `json:"pluginId"`
-	Path      string                 `json:"path"`
-	Reinstall bool                   `json:"reinstall"`
-	Inputs    map[string]interface{} `json:"inputs"`
+type ImportDashboardCommand struct {
+	PluginId  string                         `json:"pluginId"`
+	Path      string                         `json:"path"`
+	Reinstall bool                           `json:"reinstall"`
+	Inputs    []plugins.ImportDashboardInput `json:"inputs"`
 }

+ 2 - 2
pkg/api/plugins.go

@@ -122,9 +122,9 @@ func GetPluginDashboards(c *middleware.Context) Response {
 	}
 }
 
-func InstallPluginDashboard(c *middleware.Context, apiCmd dtos.InstallPluginDashboardCmd) Response {
+func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response {
 
-	cmd := plugins.InstallPluginDashboardCommand{
+	cmd := plugins.ImportDashboardCommand{
 		OrgId:    c.OrgId,
 		UserId:   c.UserId,
 		PluginId: apiCmd.PluginId,

+ 813 - 0
pkg/components/dynmap/dynmap.go

@@ -0,0 +1,813 @@
+// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
+// MIT Licence
+
+package dynmap
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+)
+
+// Error values returned when validation functions fail
+var (
+	ErrNotNull        = errors.New("is not null")
+	ErrNotArray       = errors.New("Not an array")
+	ErrNotNumber      = errors.New("not a number")
+	ErrNotBool        = errors.New("no bool")
+	ErrNotObject      = errors.New("not an object")
+	ErrNotObjectArray = errors.New("not an object array")
+	ErrNotString      = errors.New("not a string")
+)
+
+type KeyNotFoundError struct {
+	Key string
+}
+
+func (k KeyNotFoundError) Error() string {
+	if k.Key != "" {
+		return fmt.Sprintf("key '%s' not found", k.Key)
+	}
+
+	return "key not found"
+}
+
+// Value represents an arbitrary JSON value.
+// It may contain a bool, number, string, object, array or null.
+type Value struct {
+	data   interface{}
+	exists bool // Used to separate nil and non-existing values
+}
+
+// Object represents an object JSON object.
+// It inherets from Value but with an additional method to access
+// a map representation of it's content. It's useful when iterating.
+type Object struct {
+	Value
+	m     map[string]*Value
+	valid bool
+}
+
+// Returns the golang map.
+// Needed when iterating through the values of the object.
+func (v *Object) Map() map[string]*Value {
+	return v.m
+}
+
+func NewFromMap(data map[string]interface{}) *Object {
+	val := &Value{data: data, exists: true}
+	obj, _ := val.Object()
+	return obj
+}
+
+func NewObject() *Object {
+	val := &Value{data: make(map[string]interface{}), exists: true}
+	obj, _ := val.Object()
+	return obj
+}
+
+// Creates a new value from an io.reader.
+// Returns an error if the reader does not contain valid json.
+// Useful for parsing the body of a net/http response.
+// Example: NewFromReader(res.Body)
+func NewValueFromReader(reader io.Reader) (*Value, error) {
+	j := new(Value)
+	d := json.NewDecoder(reader)
+	d.UseNumber()
+	err := d.Decode(&j.data)
+	return j, err
+}
+
+// Creates a new value from bytes.
+// Returns an error if the bytes are not valid json.
+func NewValueFromBytes(b []byte) (*Value, error) {
+	r := bytes.NewReader(b)
+	return NewValueFromReader(r)
+}
+
+func objectFromValue(v *Value, err error) (*Object, error) {
+	if err != nil {
+		return nil, err
+	}
+
+	o, err := v.Object()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return o, nil
+}
+
+func NewObjectFromBytes(b []byte) (*Object, error) {
+	return objectFromValue(NewValueFromBytes(b))
+}
+
+func NewObjectFromReader(reader io.Reader) (*Object, error) {
+	return objectFromValue(NewValueFromReader(reader))
+}
+
+// Marshal into bytes.
+func (v *Value) Marshal() ([]byte, error) {
+	return json.Marshal(v.data)
+}
+
+// Get the interyling data as interface
+func (v *Value) Interface() interface{} {
+	return v.data
+}
+
+// Private Get
+func (v *Value) get(key string) (*Value, error) {
+
+	// Assume this is an object
+	obj, err := v.Object()
+
+	if err == nil {
+		child, ok := obj.Map()[key]
+		if ok {
+			return child, nil
+		} else {
+			return nil, KeyNotFoundError{key}
+		}
+	}
+
+	return nil, err
+}
+
+// Private get path
+func (v *Value) getPath(keys []string) (*Value, error) {
+	current := v
+	var err error
+	for _, key := range keys {
+		current, err = current.get(key)
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	return current, nil
+}
+
+// Gets the value at key path.
+// Returns error if the value does not exist.
+// Consider using the more specific Get<Type>(..) methods instead.
+// Example:
+//		value, err := GetValue("address", "street")
+func (v *Object) GetValue(keys ...string) (*Value, error) {
+	return v.getPath(keys)
+}
+
+// Gets the value at key path and attempts to typecast the value into an object.
+// Returns error if the value is not a json object.
+// Example:
+//		object, err := GetObject("person", "address")
+func (v *Object) GetObject(keys ...string) (*Object, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		obj, err := child.Object()
+
+		if err != nil {
+			return nil, err
+		} else {
+			return obj, nil
+		}
+
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into a string.
+// Returns error if the value is not a json string.
+// Example:
+//		string, err := GetString("address", "street")
+func (v *Object) GetString(keys ...string) (string, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return "", err
+	} else {
+		return child.String()
+	}
+}
+
+func (v *Object) MustGetString(path string, def string) string {
+	keys := strings.Split(path, ".")
+	if str, err := v.GetString(keys...); err != nil {
+		return def
+	} else {
+		return str
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into null.
+// Returns error if the value is not json null.
+// Example:
+//		err := GetNull("address", "street")
+func (v *Object) GetNull(keys ...string) error {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return err
+	}
+
+	return child.Null()
+}
+
+// Gets the value at key path and attempts to typecast the value into a number.
+// Returns error if the value is not a json number.
+// Example:
+//		n, err := GetNumber("address", "street_number")
+func (v *Object) GetNumber(keys ...string) (json.Number, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return "", err
+	} else {
+
+		n, err := child.Number()
+
+		if err != nil {
+			return "", err
+		} else {
+			return n, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into a float64.
+// Returns error if the value is not a json number.
+// Example:
+//		n, err := GetNumber("address", "street_number")
+func (v *Object) GetFloat64(keys ...string) (float64, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return 0, err
+	} else {
+
+		n, err := child.Float64()
+
+		if err != nil {
+			return 0, err
+		} else {
+			return n, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into a float64.
+// Returns error if the value is not a json number.
+// Example:
+//		n, err := GetNumber("address", "street_number")
+func (v *Object) GetInt64(keys ...string) (int64, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return 0, err
+	} else {
+
+		n, err := child.Int64()
+
+		if err != nil {
+			return 0, err
+		} else {
+			return n, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into a float64.
+// Returns error if the value is not a json number.
+// Example:
+//		v, err := GetInterface("address", "anything")
+func (v *Object) GetInterface(keys ...string) (interface{}, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+		return child.Interface(), nil
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into a bool.
+// Returns error if the value is not a json boolean.
+// Example:
+//		married, err := GetBoolean("person", "married")
+func (v *Object) GetBoolean(keys ...string) (bool, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return false, err
+	}
+
+	return child.Boolean()
+}
+
+// Gets the value at key path and attempts to typecast the value into an array.
+// Returns error if the value is not a json array.
+// Consider using the more specific Get<Type>Array() since it may reduce later type casts.
+// Example:
+//		friends, err := GetValueArray("person", "friends")
+//		for i, friend := range friends {
+//			... // friend will be of type Value here
+//		}
+func (v *Object) GetValueArray(keys ...string) ([]*Value, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		return child.Array()
+
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of objects.
+// Returns error if the value is not a json array or if any of the contained objects are not objects.
+// Example:
+//		friends, err := GetObjectArray("person", "friends")
+//		for i, friend := range friends {
+//			... // friend will be of type Object here
+//		}
+func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return nil, err
+		} else {
+
+			typedArray := make([]*Object, len(array))
+
+			for index, arrayItem := range array {
+				typedArrayItem, err := arrayItem.
+					Object()
+
+				if err != nil {
+					return nil, err
+				} else {
+					typedArray[index] = typedArrayItem
+				}
+
+			}
+			return typedArray, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of string.
+// Returns error if the value is not a json array or if any of the contained objects are not strings.
+// Gets the value at key path and attempts to typecast the value into an array of objects.
+// Returns error if the value is not a json array or if any of the contained objects are not objects.
+// Example:
+//		friendNames, err := GetStringArray("person", "friend_names")
+//		for i, friendName := range friendNames {
+//			... // friendName will be of type string here
+//		}
+func (v *Object) GetStringArray(keys ...string) ([]string, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return nil, err
+		} else {
+
+			typedArray := make([]string, len(array))
+
+			for index, arrayItem := range array {
+				typedArrayItem, err := arrayItem.String()
+
+				if err != nil {
+					return nil, err
+				} else {
+					typedArray[index] = typedArrayItem
+				}
+
+			}
+			return typedArray, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of numbers.
+// Returns error if the value is not a json array or if any of the contained objects are not numbers.
+// Example:
+//		friendAges, err := GetNumberArray("person", "friend_ages")
+//		for i, friendAge := range friendAges {
+//			... // friendAge will be of type float64 here
+//		}
+func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return nil, err
+		} else {
+
+			typedArray := make([]json.Number, len(array))
+
+			for index, arrayItem := range array {
+				typedArrayItem, err := arrayItem.Number()
+
+				if err != nil {
+					return nil, err
+				} else {
+					typedArray[index] = typedArrayItem
+				}
+
+			}
+			return typedArray, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of floats.
+// Returns error if the value is not a json array or if any of the contained objects are not numbers.
+func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return nil, err
+		} else {
+
+			typedArray := make([]float64, len(array))
+
+			for index, arrayItem := range array {
+				typedArrayItem, err := arrayItem.Float64()
+
+				if err != nil {
+					return nil, err
+				} else {
+					typedArray[index] = typedArrayItem
+				}
+
+			}
+			return typedArray, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of ints.
+// Returns error if the value is not a json array or if any of the contained objects are not numbers.
+func (v *Object) GetInt64Array(keys ...string) ([]int64, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return nil, err
+		} else {
+
+			typedArray := make([]int64, len(array))
+
+			for index, arrayItem := range array {
+				typedArrayItem, err := arrayItem.Int64()
+
+				if err != nil {
+					return nil, err
+				} else {
+					typedArray[index] = typedArrayItem
+				}
+
+			}
+			return typedArray, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of bools.
+// Returns error if the value is not a json array or if any of the contained objects are not booleans.
+func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return nil, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return nil, err
+		} else {
+
+			typedArray := make([]bool, len(array))
+
+			for index, arrayItem := range array {
+				typedArrayItem, err := arrayItem.Boolean()
+
+				if err != nil {
+					return nil, err
+				} else {
+					typedArray[index] = typedArrayItem
+				}
+
+			}
+			return typedArray, nil
+		}
+	}
+}
+
+// Gets the value at key path and attempts to typecast the value into an array of nulls.
+// Returns length, or an error if the value is not a json array or if any of the contained objects are not nulls.
+func (v *Object) GetNullArray(keys ...string) (int64, error) {
+	child, err := v.getPath(keys)
+
+	if err != nil {
+		return 0, err
+	} else {
+
+		array, err := child.Array()
+
+		if err != nil {
+			return 0, err
+		} else {
+
+			var length int64 = 0
+
+			for _, arrayItem := range array {
+				err := arrayItem.Null()
+
+				if err != nil {
+					return 0, err
+				} else {
+					length++
+				}
+
+			}
+			return length, nil
+		}
+	}
+}
+
+// Returns an error if the value is not actually null
+func (v *Value) Null() error {
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case nil:
+		valid = v.exists // Valid only if j also exists, since other values could possibly also be nil
+		break
+	}
+
+	if valid {
+		return nil
+	}
+
+	return ErrNotNull
+
+}
+
+// Attempts to typecast the current value into an array.
+// Returns error if the current value is not a json array.
+// Example:
+//		friendsArray, err := friendsValue.Array()
+func (v *Value) Array() ([]*Value, error) {
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case []interface{}:
+		valid = true
+		break
+	}
+
+	// Unsure if this is a good way to use slices, it's probably not
+	var slice []*Value
+
+	if valid {
+
+		for _, element := range v.data.([]interface{}) {
+			child := Value{element, true}
+			slice = append(slice, &child)
+		}
+
+		return slice, nil
+	}
+
+	return slice, ErrNotArray
+
+}
+
+// Attempts to typecast the current value into a number.
+// Returns error if the current value is not a json number.
+// Example:
+//		ageNumber, err := ageValue.Number()
+func (v *Value) Number() (json.Number, error) {
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case json.Number:
+		valid = true
+		break
+	}
+
+	if valid {
+		return v.data.(json.Number), nil
+	}
+
+	return "", ErrNotNumber
+}
+
+// Attempts to typecast the current value into a float64.
+// Returns error if the current value is not a json number.
+// Example:
+//		percentage, err := v.Float64()
+func (v *Value) Float64() (float64, error) {
+	n, err := v.Number()
+
+	if err != nil {
+		return 0, err
+	}
+
+	return n.Float64()
+}
+
+// Attempts to typecast the current value into a int64.
+// Returns error if the current value is not a json number.
+// Example:
+//		id, err := v.Int64()
+func (v *Value) Int64() (int64, error) {
+	n, err := v.Number()
+
+	if err != nil {
+		return 0, err
+	}
+
+	return n.Int64()
+}
+
+// Attempts to typecast the current value into a bool.
+// Returns error if the current value is not a json boolean.
+// Example:
+//		marriedBool, err := marriedValue.Boolean()
+func (v *Value) Boolean() (bool, error) {
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case bool:
+		valid = true
+		break
+	}
+
+	if valid {
+		return v.data.(bool), nil
+	}
+
+	return false, ErrNotBool
+}
+
+// Attempts to typecast the current value into an object.
+// Returns error if the current value is not a json object.
+// Example:
+//		friendObject, err := friendValue.Object()
+func (v *Value) Object() (*Object, error) {
+
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case map[string]interface{}:
+		valid = true
+		break
+	}
+
+	if valid {
+		obj := new(Object)
+		obj.valid = valid
+
+		m := make(map[string]*Value)
+
+		if valid {
+			for key, element := range v.data.(map[string]interface{}) {
+				m[key] = &Value{element, true}
+
+			}
+		}
+
+		obj.data = v.data
+		obj.m = m
+
+		return obj, nil
+	}
+
+	return nil, ErrNotObject
+}
+
+// Attempts to typecast the current value into an object arrau.
+// Returns error if the current value is not an array of json objects
+// Example:
+//		friendObjects, err := friendValues.ObjectArray()
+func (v *Value) ObjectArray() ([]*Object, error) {
+
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case []interface{}:
+		valid = true
+		break
+	}
+
+	// Unsure if this is a good way to use slices, it's probably not
+	var slice []*Object
+
+	if valid {
+
+		for _, element := range v.data.([]interface{}) {
+			childValue := Value{element, true}
+			childObject, err := childValue.Object()
+
+			if err != nil {
+				return nil, ErrNotObjectArray
+			}
+			slice = append(slice, childObject)
+		}
+
+		return slice, nil
+	}
+
+	return nil, ErrNotObjectArray
+
+}
+
+// Attempts to typecast the current value into a string.
+// Returns error if the current value is not a json string
+// Example:
+//		nameObject, err := nameValue.String()
+func (v *Value) String() (string, error) {
+	var valid bool
+
+	// Check the type of this data
+	switch v.data.(type) {
+	case string:
+		valid = true
+		break
+	}
+
+	if valid {
+		return v.data.(string), nil
+	}
+
+	return "", ErrNotString
+}
+
+// Returns the value a json formatted string.
+// Note: The method named String() is used by golang's log method for logging.
+// Example:
+func (v *Object) String() string {
+
+	f, err := json.Marshal(v.data)
+	if err != nil {
+		return err.Error()
+	}
+
+	return string(f)
+
+}
+
+func (v *Object) SetValue(key string, value interface{}) *Value {
+	data := v.Interface().(map[string]interface{})
+	data[key] = value
+
+	return &Value{
+		data:   value,
+		exists: true,
+	}
+}

+ 313 - 0
pkg/components/dynmap/dynmap_test.go

@@ -0,0 +1,313 @@
+// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go
+// MIT Licence
+
+package dynmap
+
+import (
+	"log"
+	"testing"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+type Assert struct {
+	T *testing.T
+}
+
+func NewAssert(t *testing.T) *Assert {
+	return &Assert{
+		T: t,
+	}
+}
+
+func (assert *Assert) True(value bool, message string) {
+	if value == false {
+		log.Panicln("Assert: ", message)
+	}
+}
+
+func TestFirst(t *testing.T) {
+
+	assert := NewAssert(t)
+
+	testJSON := `{
+    "name": "anton",
+    "age": 29,
+    "nothing": null,
+    "true": true,
+    "false": false,
+    "list": [
+      "first",
+      "second"
+    ],
+    "list2": [
+      {
+        "street": "Street 42",
+        "city": "Stockholm"
+      },
+      {
+        "street": "Street 42",
+        "city": "Stockholm"
+      }
+    ],
+    "address": {
+      "street": "Street 42",
+      "city": "Stockholm"
+    },
+    "country": {
+      "name": "Sweden"
+    }
+  }`
+
+	j, err := NewObjectFromBytes([]byte(testJSON))
+
+	a, err := j.GetObject("address")
+	assert.True(a != nil && err == nil, "failed to create json from string")
+
+	assert.True(err == nil, "failed to create json from string")
+
+	s, err := j.GetString("name")
+	assert.True(s == "anton" && err == nil, "name should be a string")
+
+	s = j.MustGetString("name", "fallback")
+	assert.True(s == "anton", "must get string")
+
+	s = j.MustGetString("adsasdas", "fallback")
+	assert.True(s == "fallback", "must get string return fallback")
+
+	s, err = j.GetString("name")
+	assert.True(s == "anton" && err == nil, "name shoud match")
+
+	s, err = j.GetString("address", "street")
+	assert.True(s == "Street 42" && err == nil, "street shoud match")
+	//log.Println("s: ", s.String())
+
+	_, err = j.GetNumber("age")
+	assert.True(err == nil, "age should be a number")
+
+	n, err := j.GetInt64("age")
+	assert.True(n == 29 && err == nil, "age mismatch")
+
+	ageInterface, err := j.GetInterface("age")
+	assert.True(ageInterface != nil, "should be defined")
+	assert.True(err == nil, "age interface error")
+
+	invalidInterface, err := j.GetInterface("not_existing")
+	assert.True(invalidInterface == nil, "should not give error here")
+	assert.True(err != nil, "should give error here")
+
+	age, err := j.GetValue("age")
+	assert.True(age != nil && err == nil, "age should exist")
+
+	age2, err := j.GetValue("age2")
+	assert.True(age2 == nil && err != nil, "age2 should not exist")
+
+	address, err := j.GetObject("address")
+	assert.True(address != nil && err == nil, "address should be an object")
+
+	//log.Println("address: ", address)
+
+	s, err = address.GetString("street")
+
+	addressAsString, err := j.GetString("address")
+	assert.True(addressAsString == "" && err != nil, "address should not be an string")
+
+	s, err = j.GetString("address", "street")
+	assert.True(s == "Street 42" && err == nil, "street mismatching")
+
+	s, err = j.GetString("address", "name2")
+	assert.True(s == "" && err != nil, "nonexistent string fail")
+
+	b, err := j.GetBoolean("true")
+	assert.True(b == true && err == nil, "bool true test")
+
+	b, err = j.GetBoolean("false")
+	assert.True(b == false && err == nil, "bool false test")
+
+	b, err = j.GetBoolean("invalid_field")
+	assert.True(b == false && err != nil, "bool invalid test")
+
+	list, err := j.GetValueArray("list")
+	assert.True(list != nil && err == nil, "list should be an array")
+
+	list2, err := j.GetValueArray("list2")
+	assert.True(list2 != nil && err == nil, "list2 should be an array")
+
+	list2Array, err := j.GetValueArray("list2")
+	assert.True(err == nil, "List2 should not return error on AsArray")
+	assert.True(len(list2Array) == 2, "List2 should should have length 2")
+
+	list2Value, err := j.GetValue("list2")
+	assert.True(err == nil, "List2 should not return error on value")
+
+	list2ObjectArray, err := list2Value.ObjectArray()
+	assert.True(err == nil, "list2Value should not return error on ObjectArray")
+	assert.True(len(list2ObjectArray) == 2, "list2ObjectArray should should have length 2")
+
+	for _, elementValue := range list2Array {
+		//assert.True(element.IsObject() == true, "first fail")
+
+		element, err := elementValue.Object()
+
+		s, err = element.GetString("street")
+		assert.True(s == "Street 42" && err == nil, "second fail")
+	}
+
+	obj, err := j.GetObject("country")
+	assert.True(obj != nil && err == nil, "country should not return error on AsObject")
+	for key, value := range obj.Map() {
+
+		assert.True(key == "name", "country name key incorrect")
+
+		s, err = value.String()
+		assert.True(s == "Sweden" && err == nil, "country name should be Sweden")
+	}
+}
+
+func TestSecond(t *testing.T) {
+	json := `
+  {
+   "data": [
+      {
+         "id": "X999_Y999",
+         "from": {
+            "name": "Tom Brady", "id": "X12"
+         },
+         "message": "Looking forward to 2010!",
+         "actions": [
+            {
+               "name": "Comment",
+               "link": "http://www.facebook.com/X999/posts/Y999"
+            },
+            {
+               "name": "Like",
+               "link": "http://www.facebook.com/X999/posts/Y999"
+            }
+         ],
+         "type": "status",
+         "created_time": "2010-08-02T21:27:44+0000",
+         "updated_time": "2010-08-02T21:27:44+0000"
+      },
+      {
+         "id": "X998_Y998",
+         "from": {
+            "name": "Peyton Manning", "id": "X18"
+         },
+         "message": "Where's my contract?",
+         "actions": [
+            {
+               "name": "Comment",
+               "link": "http://www.facebook.com/X998/posts/Y998"
+            },
+            {
+               "name": "Like",
+               "link": "http://www.facebook.com/X998/posts/Y998"
+            }
+         ],
+         "type": "status",
+         "created_time": "2010-08-02T21:27:44+0000",
+         "updated_time": "2010-08-02T21:27:44+0000"
+      }
+   ]
+  }`
+
+	assert := NewAssert(t)
+	j, err := NewObjectFromBytes([]byte(json))
+
+	assert.True(j != nil && err == nil, "failed to parse json")
+
+	dataObject, err := j.GetObject("data")
+	assert.True(dataObject == nil && err != nil, "data should not be an object")
+
+	dataArray, err := j.GetObjectArray("data")
+	assert.True(dataArray != nil && err == nil, "data should be an object array")
+
+	for index, dataItem := range dataArray {
+
+		if index == 0 {
+			id, err := dataItem.GetString("id")
+			assert.True(id == "X999_Y999" && err == nil, "item id mismatch")
+
+			fromName, err := dataItem.GetString("from", "name")
+			assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch")
+
+			actions, err := dataItem.GetObjectArray("actions")
+
+			for index, action := range actions {
+
+				if index == 1 {
+					name, err := action.GetString("name")
+					assert.True(name == "Like" && err == nil, "name mismatch")
+
+					link, err := action.GetString("link")
+					assert.True(link == "http://www.facebook.com/X999/posts/Y999" && err == nil, "Like mismatch")
+
+				}
+
+			}
+		} else if index == 1 {
+			id, err := dataItem.GetString("id")
+			assert.True(id == "X998_Y998" && err == nil, "item id mismatch")
+		}
+
+	}
+
+}
+
+func TestErrors(t *testing.T) {
+	json := `
+  {
+    "string": "hello",
+    "number": 1,
+    "array": [1,2,3]
+  }`
+
+	errstr := "expected an error getting %s, but got '%s'"
+
+	j, err := NewObjectFromBytes([]byte(json))
+	if err != nil {
+		t.Fatal("failed to parse json")
+	}
+
+	if _, err = j.GetObject("string"); err != ErrNotObject {
+		t.Errorf(errstr, "object", err)
+	}
+
+	if err = j.GetNull("string"); err != ErrNotNull {
+		t.Errorf(errstr, "null", err)
+	}
+
+	if _, err = j.GetStringArray("string"); err != ErrNotArray {
+		t.Errorf(errstr, "array", err)
+	}
+
+	if _, err = j.GetStringArray("array"); err != ErrNotString {
+		t.Errorf(errstr, "string array", err)
+	}
+
+	if _, err = j.GetNumber("array"); err != ErrNotNumber {
+		t.Errorf(errstr, "number", err)
+	}
+
+	if _, err = j.GetBoolean("array"); err != ErrNotBool {
+		t.Errorf(errstr, "boolean", err)
+	}
+
+	if _, err = j.GetString("number"); err != ErrNotString {
+		t.Errorf(errstr, "string", err)
+	}
+
+	_, err = j.GetString("not_found")
+	if e, ok := err.(KeyNotFoundError); !ok {
+		t.Errorf(errstr, "key not found error", e)
+	}
+
+}
+
+func TestWriting(t *testing.T) {
+	Convey("When writing", t, func() {
+		j, _ := NewObjectFromBytes([]byte(`{}`))
+		j.SetValue("prop", "value")
+		So(j.MustGetString("prop", ""), ShouldEqual, "value")
+	})
+}

+ 130 - 0
pkg/plugins/dashboard_importer.go

@@ -0,0 +1,130 @@
+package plugins
+
+import (
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/dynmap"
+	"github.com/grafana/grafana/pkg/log"
+	m "github.com/grafana/grafana/pkg/models"
+)
+
+type ImportDashboardCommand struct {
+	Path   string                 `json:"string"`
+	Inputs []ImportDashboardInput `json:"inputs"`
+
+	OrgId    int64  `json:"-"`
+	UserId   int64  `json:"-"`
+	PluginId string `json:"-"`
+	Result   *PluginDashboardInfoDTO
+}
+
+type ImportDashboardInput struct {
+	Type     string `json:"type"`
+	PluginId string `json:"pluginId"`
+	Name     string `json:"name"`
+	Value    string `json:"value"`
+}
+
+func init() {
+	bus.AddHandler("plugins", ImportDashboard)
+}
+
+func ImportDashboard(cmd *ImportDashboardCommand) error {
+	plugin, exists := Plugins[cmd.PluginId]
+
+	if !exists {
+		return PluginNotFoundError{cmd.PluginId}
+	}
+
+	var dashboard *m.Dashboard
+	var err error
+
+	if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil {
+		return err
+	}
+
+	saveCmd := m.SaveDashboardCommand{
+		Dashboard: dashboard.Data,
+		OrgId:     cmd.OrgId,
+		UserId:    cmd.UserId,
+	}
+
+	if err := bus.Dispatch(&saveCmd); err != nil {
+		return err
+	}
+
+	cmd.Result = &PluginDashboardInfoDTO{
+		PluginId:          cmd.PluginId,
+		Title:             dashboard.Title,
+		Path:              cmd.Path,
+		Revision:          dashboard.GetString("revision", "1.0"),
+		InstalledUri:      "db/" + saveCmd.Result.Slug,
+		InstalledRevision: dashboard.GetString("revision", "1.0"),
+		Installed:         true,
+	}
+
+	return nil
+}
+
+type DashTemplateEvaluator struct {
+	template  *dynmap.Object
+	inputs    []ImportDashboardInput
+	variables map[string]string
+	result    *dynmap.Object
+}
+
+// func (this *DashTemplateEvaluator) getObject(path string) map[string]interface{} {
+// 	if obj, exists := this.template[path]; exists {
+// 		return obj.(map[string]interface{})
+// 	}
+// 	return nil
+// }
+
+func (this *DashTemplateEvaluator) findInput(varName string, varDef *dynmap.Object) *ImportDashboardInput {
+	inputType, _ := varDef.GetString("type")
+
+	for _, input := range this.inputs {
+		if inputType == input.Type && (input.Name == varName || input.Name == "*") {
+			return &input
+		}
+	}
+
+	return nil
+}
+
+func (this *DashTemplateEvaluator) Eval() (*dynmap.Object, error) {
+	this.result = dynmap.NewObject()
+	this.variables = make(map[string]string)
+
+	// check that we have all inputs we need
+	if requiredInputs, err := this.template.GetObject("__inputs"); err == nil {
+		for varName, value := range requiredInputs.Map() {
+			varDef, _ := value.Object()
+			input := this.findInput(varName, varDef)
+			this.variables[varName] = input.Value
+		}
+	}
+
+	this.EvalObject(this.template, this.result)
+	return this.result, nil
+}
+
+func (this *DashTemplateEvaluator) EvalObject(source *dynmap.Object, writer *dynmap.Object) {
+
+	for key, value := range source.Map() {
+		if key == "__input" {
+			continue
+		}
+
+		goValue := value.Interface()
+		switch goValue.(type) {
+		case string:
+			writer.SetValue(key, goValue)
+		case map[string]interface{}:
+			childSource, _ := value.Object()
+			childWriter, _ := writer.SetValue(key, map[string]interface{}{}).Object()
+			this.EvalObject(childSource, childWriter)
+		default:
+			log.Error(3, "Unknown json type key: %v , type: %v", key, goValue)
+		}
+	}
+}

+ 81 - 0
pkg/plugins/dashboard_importer_test.go

@@ -0,0 +1,81 @@
+package plugins
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/dynmap"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/setting"
+	. "github.com/smartystreets/goconvey/convey"
+	"gopkg.in/ini.v1"
+)
+
+func TestDashboardImport(t *testing.T) {
+
+	Convey("When importing plugin dashboard", t, func() {
+		setting.Cfg = ini.Empty()
+		sec, _ := setting.Cfg.NewSection("plugin.test-app")
+		sec.NewKey("path", "../../tests/test-app")
+		err := Init()
+
+		So(err, ShouldBeNil)
+
+		var importedDash *m.Dashboard
+		bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
+			importedDash = cmd.GetDashboardModel()
+			cmd.Result = importedDash
+			return nil
+		})
+
+		cmd := ImportDashboardCommand{
+			PluginId: "test-app",
+			Path:     "dashboards/connections.json",
+			OrgId:    1,
+			UserId:   1,
+			Inputs:   []ImportDashboardInput{},
+		}
+
+		err = ImportDashboard(&cmd)
+		So(err, ShouldBeNil)
+
+		Convey("should install dashboard", func() {
+			So(importedDash, ShouldNotBeNil)
+			rows := importedDash.Data["rows"].([]interface{})
+			row1 := rows[0].(map[string]interface{})
+			panels := row1["panels"].([]interface{})
+			panel := panels[0].(map[string]interface{})
+
+			So(panel["datasource"], ShouldEqual, "graphite")
+			So(importedDash.Data["__inputs"], ShouldBeNil)
+		})
+	})
+
+	Convey("When evaling dashboard template", t, func() {
+		template, _ := dynmap.NewObjectFromBytes([]byte(`{
+      "__input": {
+        "graphite": {
+          "type": "datasource"
+        }
+      },
+      "test": {
+        "prop": "$__graphite"
+      }
+    }`))
+
+		evaluator := &DashTemplateEvaluator{
+			template: template,
+			inputs: []ImportDashboardInput{
+				{Name: "*", Type: "datasource", Value: "my-server"},
+			},
+		}
+
+		res, err := evaluator.Eval()
+		So(err, ShouldBeNil)
+
+		Convey("should render template", func() {
+			So(res.MustGetString("test.prop", ""), ShouldEqual, "my-server")
+		})
+	})
+
+}

+ 0 - 57
pkg/plugins/dashboard_installer.go

@@ -1,57 +0,0 @@
-package plugins
-
-import (
-	"github.com/grafana/grafana/pkg/bus"
-	m "github.com/grafana/grafana/pkg/models"
-)
-
-type InstallPluginDashboardCommand struct {
-	Path   string                 `json:"string"`
-	Inputs map[string]interface{} `json:"inputs"`
-
-	OrgId    int64  `json:"-"`
-	UserId   int64  `json:"-"`
-	PluginId string `json:"-"`
-	Result   *PluginDashboardInfoDTO
-}
-
-func init() {
-	bus.AddHandler("plugins", InstallPluginDashboard)
-}
-
-func InstallPluginDashboard(cmd *InstallPluginDashboardCommand) error {
-	plugin, exists := Plugins[cmd.PluginId]
-
-	if !exists {
-		return PluginNotFoundError{cmd.PluginId}
-	}
-
-	var dashboard *m.Dashboard
-	var err error
-
-	if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil {
-		return err
-	}
-
-	saveCmd := m.SaveDashboardCommand{
-		Dashboard: dashboard.Data,
-		OrgId:     cmd.OrgId,
-		UserId:    cmd.UserId,
-	}
-
-	if err := bus.Dispatch(&saveCmd); err != nil {
-		return err
-	}
-
-	cmd.Result = &PluginDashboardInfoDTO{
-		PluginId:          cmd.PluginId,
-		Title:             dashboard.Title,
-		Path:              cmd.Path,
-		Revision:          dashboard.GetString("revision", "1.0"),
-		InstalledUri:      "db/" + saveCmd.Result.Slug,
-		InstalledRevision: dashboard.GetString("revision", "1.0"),
-		Installed:         true,
-	}
-
-	return nil
-}

+ 13 - 2
public/app/features/plugins/import_list/import_list.ts

@@ -7,6 +7,7 @@ import coreModule from 'app/core/core_module';
 export class DashImportListCtrl {
   dashboards: any[];
   plugin: any;
+  datasource: any;
 
   constructor(private $http, private backendSrv, private $rootScope) {
     this.dashboards = [];
@@ -21,9 +22,18 @@ export class DashImportListCtrl {
       pluginId: this.plugin.id,
       path: dash.path,
       reinstall: reinstall,
-      inputs: {}
+      inputs: []
     };
 
+    if (this.datasource) {
+      installCmd.inputs.push({
+        name: '*',
+        type: 'datasource',
+        pluginId: this.datasource.type,
+        value: this.datasource.name
+      });
+    }
+
     this.backendSrv.post(`/api/plugins/dashboards/install`, installCmd).then(res => {
       this.$rootScope.appEvent('alert-success', ['Dashboard Installed', dash.title]);
       _.extend(dash, res);
@@ -46,7 +56,8 @@ export function dashboardImportList() {
     bindToController: true,
     controllerAs: 'ctrl',
     scope: {
-      plugin: "="
+      plugin: "=",
+      datasource: "="
     }
   };
 }

+ 1 - 1
public/app/features/plugins/partials/ds_edit.html

@@ -74,7 +74,7 @@
   </div>
 
   <div ng-if="ctrl.tabIndex === 1" class="tab-content">
-    <dashboard-import-list plugin="ctrl.datasourceMeta"></dashboard-import-list>
+    <dashboard-import-list plugin="ctrl.datasourceMeta" datasource="ctrl.current" ></dashboard-import-list>
   </div>
 
 </div>

+ 1 - 0
public/app/plugins/datasource/graphite/dashboards/carbon_stats.json

@@ -2,6 +2,7 @@
   "__inputs": {
     "graphite": {
       "type": "datasource",
+      "pluginId": "graphite",
       "description": "Graphite datasource"
     }
   },

+ 20 - 1
tests/test-app/dashboards/connections.json

@@ -1,5 +1,24 @@
 {
+  "__inputs": {
+    "graphite": {
+      "type": "datasource",
+      "pluginId": "graphite",
+      "description": "Graphite datasource"
+    }
+  },
+
   "title": "Nginx Connections",
   "revision": "1.5",
-  "schemaVersion": 11
+  "schemaVersion": 11,
+
+  "rows": [
+    {
+      "panels": [
+        {
+          "type": "graph",
+          "datasource": "$__graphite"
+        }
+      ]
+    }
+  ]
 }