Przeglądaj źródła

lib(): added simplejson lib

Torkel Ödegaard 9 lat temu
rodzic
commit
1f9f439acb

+ 5 - 2
pkg/api/cloudwatch/metrics.go

@@ -130,11 +130,14 @@ func handleGetNamespaces(req *cwRequest, c *middleware.Context) {
 	for key := range metricsMap {
 		keys = append(keys, key)
 	}
-	if customMetricsNamespaces, ok := req.DataSource.JsonData["customMetricsNamespaces"].(string); ok {
-		for _, key := range strings.Split(customMetricsNamespaces, ",") {
+
+	customNamespaces := req.DataSource.JsonData.Get("customMetricsNamespaces").MustString()
+	if customNamespaces != "" {
+		for _, key := range strings.Split(customNamespaces, ",") {
 			keys = append(keys, key)
 		}
 	}
+
 	sort.Sort(sort.StringSlice(keys))
 
 	result := []interface{}{}

+ 16 - 15
pkg/api/dtos/models.go

@@ -6,6 +6,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 )
@@ -57,21 +58,21 @@ type DashboardFullWithMeta struct {
 }
 
 type DataSource struct {
-	Id                int64                  `json:"id"`
-	OrgId             int64                  `json:"orgId"`
-	Name              string                 `json:"name"`
-	Type              string                 `json:"type"`
-	Access            m.DsAccess             `json:"access"`
-	Url               string                 `json:"url"`
-	Password          string                 `json:"password"`
-	User              string                 `json:"user"`
-	Database          string                 `json:"database"`
-	BasicAuth         bool                   `json:"basicAuth"`
-	BasicAuthUser     string                 `json:"basicAuthUser"`
-	BasicAuthPassword string                 `json:"basicAuthPassword"`
-	WithCredentials   bool                   `json:"withCredentials"`
-	IsDefault         bool                   `json:"isDefault"`
-	JsonData          map[string]interface{} `json:"jsonData,omitempty"`
+	Id                int64            `json:"id"`
+	OrgId             int64            `json:"orgId"`
+	Name              string           `json:"name"`
+	Type              string           `json:"type"`
+	Access            m.DsAccess       `json:"access"`
+	Url               string           `json:"url"`
+	Password          string           `json:"password"`
+	User              string           `json:"user"`
+	Database          string           `json:"database"`
+	BasicAuth         bool             `json:"basicAuth"`
+	BasicAuthUser     string           `json:"basicAuthUser"`
+	BasicAuthPassword string           `json:"basicAuthPassword"`
+	WithCredentials   bool             `json:"withCredentials"`
+	IsDefault         bool             `json:"isDefault"`
+	JsonData          *simplejson.Json `json:"jsonData,omitempty"`
 }
 
 type MetricQueryResultDto struct {

+ 1 - 1
pkg/api/frontendsettings.go

@@ -59,7 +59,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
 			defaultDatasource = ds.Name
 		}
 
-		if len(ds.JsonData) > 0 {
+		if len(ds.JsonData.MustMap()) > 0 {
 			dsMap["jsonData"] = ds.JsonData
 		}
 

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

@@ -120,6 +120,10 @@ func (v *Value) Interface() interface{} {
 	return v.data
 }
 
+func (v *Value) StringMap() map[string]interface{} {
+	return v.data.(map[string]interface{})
+}
+
 // Private Get
 func (v *Value) get(key string) (*Value, error) {
 

+ 461 - 0
pkg/components/simplejson/simplejson.go

@@ -0,0 +1,461 @@
+package simplejson
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+)
+
+// returns the current implementation version
+func Version() string {
+	return "0.5.0"
+}
+
+type Json struct {
+	data interface{}
+}
+
+func (j *Json) FromDB(data []byte) error {
+	j.data = make(map[string]interface{})
+
+	dec := json.NewDecoder(bytes.NewBuffer(data))
+	dec.UseNumber()
+	return dec.Decode(&j.data)
+}
+
+func (j *Json) ToDB() ([]byte, error) {
+	return j.Encode()
+}
+
+// NewJson returns a pointer to a new `Json` object
+// after unmarshaling `body` bytes
+func NewJson(body []byte) (*Json, error) {
+	j := new(Json)
+	err := j.UnmarshalJSON(body)
+	if err != nil {
+		return nil, err
+	}
+	return j, nil
+}
+
+// New returns a pointer to a new, empty `Json` object
+func New() *Json {
+	return &Json{
+		data: make(map[string]interface{}),
+	}
+}
+
+// Interface returns the underlying data
+func (j *Json) Interface() interface{} {
+	return j.data
+}
+
+// Encode returns its marshaled data as `[]byte`
+func (j *Json) Encode() ([]byte, error) {
+	return j.MarshalJSON()
+}
+
+// EncodePretty returns its marshaled data as `[]byte` with indentation
+func (j *Json) EncodePretty() ([]byte, error) {
+	return json.MarshalIndent(&j.data, "", "  ")
+}
+
+// Implements the json.Marshaler interface.
+func (j *Json) MarshalJSON() ([]byte, error) {
+	fmt.Printf("MarshalJSON")
+	return json.Marshal(&j.data)
+}
+
+// Set modifies `Json` map by `key` and `value`
+// Useful for changing single key/value in a `Json` object easily.
+func (j *Json) Set(key string, val interface{}) {
+	m, err := j.Map()
+	if err != nil {
+		return
+	}
+	m[key] = val
+}
+
+// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path,
+// and then finally writing in the value
+func (j *Json) SetPath(branch []string, val interface{}) {
+	if len(branch) == 0 {
+		j.data = val
+		return
+	}
+
+	// in order to insert our branch, we need map[string]interface{}
+	if _, ok := (j.data).(map[string]interface{}); !ok {
+		// have to replace with something suitable
+		j.data = make(map[string]interface{})
+	}
+	curr := j.data.(map[string]interface{})
+
+	for i := 0; i < len(branch)-1; i++ {
+		b := branch[i]
+		// key exists?
+		if _, ok := curr[b]; !ok {
+			n := make(map[string]interface{})
+			curr[b] = n
+			curr = n
+			continue
+		}
+
+		// make sure the value is the right sort of thing
+		if _, ok := curr[b].(map[string]interface{}); !ok {
+			// have to replace with something suitable
+			n := make(map[string]interface{})
+			curr[b] = n
+		}
+
+		curr = curr[b].(map[string]interface{})
+	}
+
+	// add remaining k/v
+	curr[branch[len(branch)-1]] = val
+}
+
+// Del modifies `Json` map by deleting `key` if it is present.
+func (j *Json) Del(key string) {
+	m, err := j.Map()
+	if err != nil {
+		return
+	}
+	delete(m, key)
+}
+
+// Get returns a pointer to a new `Json` object
+// for `key` in its `map` representation
+//
+// useful for chaining operations (to traverse a nested JSON):
+//    js.Get("top_level").Get("dict").Get("value").Int()
+func (j *Json) Get(key string) *Json {
+	m, err := j.Map()
+	if err == nil {
+		if val, ok := m[key]; ok {
+			return &Json{val}
+		}
+	}
+	return &Json{nil}
+}
+
+// GetPath searches for the item as specified by the branch
+// without the need to deep dive using Get()'s.
+//
+//   js.GetPath("top_level", "dict")
+func (j *Json) GetPath(branch ...string) *Json {
+	jin := j
+	for _, p := range branch {
+		jin = jin.Get(p)
+	}
+	return jin
+}
+
+// GetIndex returns a pointer to a new `Json` object
+// for `index` in its `array` representation
+//
+// this is the analog to Get when accessing elements of
+// a json array instead of a json object:
+//    js.Get("top_level").Get("array").GetIndex(1).Get("key").Int()
+func (j *Json) GetIndex(index int) *Json {
+	a, err := j.Array()
+	if err == nil {
+		if len(a) > index {
+			return &Json{a[index]}
+		}
+	}
+	return &Json{nil}
+}
+
+// CheckGet returns a pointer to a new `Json` object and
+// a `bool` identifying success or failure
+//
+// useful for chained operations when success is important:
+//    if data, ok := js.Get("top_level").CheckGet("inner"); ok {
+//        log.Println(data)
+//    }
+func (j *Json) CheckGet(key string) (*Json, bool) {
+	m, err := j.Map()
+	if err == nil {
+		if val, ok := m[key]; ok {
+			return &Json{val}, true
+		}
+	}
+	return nil, false
+}
+
+// Map type asserts to `map`
+func (j *Json) Map() (map[string]interface{}, error) {
+	if m, ok := (j.data).(map[string]interface{}); ok {
+		return m, nil
+	}
+	return nil, errors.New("type assertion to map[string]interface{} failed")
+}
+
+// Array type asserts to an `array`
+func (j *Json) Array() ([]interface{}, error) {
+	if a, ok := (j.data).([]interface{}); ok {
+		return a, nil
+	}
+	return nil, errors.New("type assertion to []interface{} failed")
+}
+
+// Bool type asserts to `bool`
+func (j *Json) Bool() (bool, error) {
+	if s, ok := (j.data).(bool); ok {
+		return s, nil
+	}
+	return false, errors.New("type assertion to bool failed")
+}
+
+// String type asserts to `string`
+func (j *Json) String() (string, error) {
+	if s, ok := (j.data).(string); ok {
+		return s, nil
+	}
+	return "", errors.New("type assertion to string failed")
+}
+
+// Bytes type asserts to `[]byte`
+func (j *Json) Bytes() ([]byte, error) {
+	if s, ok := (j.data).(string); ok {
+		return []byte(s), nil
+	}
+	return nil, errors.New("type assertion to []byte failed")
+}
+
+// StringArray type asserts to an `array` of `string`
+func (j *Json) StringArray() ([]string, error) {
+	arr, err := j.Array()
+	if err != nil {
+		return nil, err
+	}
+	retArr := make([]string, 0, len(arr))
+	for _, a := range arr {
+		if a == nil {
+			retArr = append(retArr, "")
+			continue
+		}
+		s, ok := a.(string)
+		if !ok {
+			return nil, err
+		}
+		retArr = append(retArr, s)
+	}
+	return retArr, nil
+}
+
+// MustArray guarantees the return of a `[]interface{}` (with optional default)
+//
+// useful when you want to interate over array values in a succinct manner:
+//		for i, v := range js.Get("results").MustArray() {
+//			fmt.Println(i, v)
+//		}
+func (j *Json) MustArray(args ...[]interface{}) []interface{} {
+	var def []interface{}
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustArray() received too many arguments %d", len(args))
+	}
+
+	a, err := j.Array()
+	if err == nil {
+		return a
+	}
+
+	return def
+}
+
+// MustMap guarantees the return of a `map[string]interface{}` (with optional default)
+//
+// useful when you want to interate over map values in a succinct manner:
+//		for k, v := range js.Get("dictionary").MustMap() {
+//			fmt.Println(k, v)
+//		}
+func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} {
+	var def map[string]interface{}
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustMap() received too many arguments %d", len(args))
+	}
+
+	a, err := j.Map()
+	if err == nil {
+		return a
+	}
+
+	return def
+}
+
+// MustString guarantees the return of a `string` (with optional default)
+//
+// useful when you explicitly want a `string` in a single value return context:
+//     myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default"))
+func (j *Json) MustString(args ...string) string {
+	var def string
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustString() received too many arguments %d", len(args))
+	}
+
+	s, err := j.String()
+	if err == nil {
+		return s
+	}
+
+	return def
+}
+
+// MustStringArray guarantees the return of a `[]string` (with optional default)
+//
+// useful when you want to interate over array values in a succinct manner:
+//		for i, s := range js.Get("results").MustStringArray() {
+//			fmt.Println(i, s)
+//		}
+func (j *Json) MustStringArray(args ...[]string) []string {
+	var def []string
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustStringArray() received too many arguments %d", len(args))
+	}
+
+	a, err := j.StringArray()
+	if err == nil {
+		return a
+	}
+
+	return def
+}
+
+// MustInt guarantees the return of an `int` (with optional default)
+//
+// useful when you explicitly want an `int` in a single value return context:
+//     myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150))
+func (j *Json) MustInt(args ...int) int {
+	var def int
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustInt() received too many arguments %d", len(args))
+	}
+
+	i, err := j.Int()
+	if err == nil {
+		return i
+	}
+
+	return def
+}
+
+// MustFloat64 guarantees the return of a `float64` (with optional default)
+//
+// useful when you explicitly want a `float64` in a single value return context:
+//     myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150))
+func (j *Json) MustFloat64(args ...float64) float64 {
+	var def float64
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustFloat64() received too many arguments %d", len(args))
+	}
+
+	f, err := j.Float64()
+	if err == nil {
+		return f
+	}
+
+	return def
+}
+
+// MustBool guarantees the return of a `bool` (with optional default)
+//
+// useful when you explicitly want a `bool` in a single value return context:
+//     myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true))
+func (j *Json) MustBool(args ...bool) bool {
+	var def bool
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustBool() received too many arguments %d", len(args))
+	}
+
+	b, err := j.Bool()
+	if err == nil {
+		return b
+	}
+
+	return def
+}
+
+// MustInt64 guarantees the return of an `int64` (with optional default)
+//
+// useful when you explicitly want an `int64` in a single value return context:
+//     myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150))
+func (j *Json) MustInt64(args ...int64) int64 {
+	var def int64
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustInt64() received too many arguments %d", len(args))
+	}
+
+	i, err := j.Int64()
+	if err == nil {
+		return i
+	}
+
+	return def
+}
+
+// MustUInt64 guarantees the return of an `uint64` (with optional default)
+//
+// useful when you explicitly want an `uint64` in a single value return context:
+//     myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150))
+func (j *Json) MustUint64(args ...uint64) uint64 {
+	var def uint64
+
+	switch len(args) {
+	case 0:
+	case 1:
+		def = args[0]
+	default:
+		log.Panicf("MustUint64() received too many arguments %d", len(args))
+	}
+
+	i, err := j.Uint64()
+	if err == nil {
+		return i
+	}
+
+	return def
+}

+ 91 - 0
pkg/components/simplejson/simplejson_go11.go

@@ -0,0 +1,91 @@
+// +build go1.1
+
+package simplejson
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"reflect"
+	"strconv"
+)
+
+// Implements the json.Unmarshaler interface.
+func (j *Json) UnmarshalJSON(p []byte) error {
+	fmt.Printf("UnmarshalJSON")
+	dec := json.NewDecoder(bytes.NewBuffer(p))
+	dec.UseNumber()
+	return dec.Decode(&j.data)
+}
+
+// NewFromReader returns a *Json by decoding from an io.Reader
+func NewFromReader(r io.Reader) (*Json, error) {
+	j := new(Json)
+	dec := json.NewDecoder(r)
+	dec.UseNumber()
+	err := dec.Decode(&j.data)
+	return j, err
+}
+
+// Float64 coerces into a float64
+func (j *Json) Float64() (float64, error) {
+	switch j.data.(type) {
+	case json.Number:
+		return j.data.(json.Number).Float64()
+	case float32, float64:
+		return reflect.ValueOf(j.data).Float(), nil
+	case int, int8, int16, int32, int64:
+		return float64(reflect.ValueOf(j.data).Int()), nil
+	case uint, uint8, uint16, uint32, uint64:
+		return float64(reflect.ValueOf(j.data).Uint()), nil
+	}
+	return 0, errors.New("invalid value type")
+}
+
+// Int coerces into an int
+func (j *Json) Int() (int, error) {
+	switch j.data.(type) {
+	case json.Number:
+		i, err := j.data.(json.Number).Int64()
+		return int(i), err
+	case float32, float64:
+		return int(reflect.ValueOf(j.data).Float()), nil
+	case int, int8, int16, int32, int64:
+		return int(reflect.ValueOf(j.data).Int()), nil
+	case uint, uint8, uint16, uint32, uint64:
+		return int(reflect.ValueOf(j.data).Uint()), nil
+	}
+	return 0, errors.New("invalid value type")
+}
+
+// Int64 coerces into an int64
+func (j *Json) Int64() (int64, error) {
+	switch j.data.(type) {
+	case json.Number:
+		return j.data.(json.Number).Int64()
+	case float32, float64:
+		return int64(reflect.ValueOf(j.data).Float()), nil
+	case int, int8, int16, int32, int64:
+		return reflect.ValueOf(j.data).Int(), nil
+	case uint, uint8, uint16, uint32, uint64:
+		return int64(reflect.ValueOf(j.data).Uint()), nil
+	}
+	return 0, errors.New("invalid value type")
+}
+
+// Uint64 coerces into an uint64
+func (j *Json) Uint64() (uint64, error) {
+	switch j.data.(type) {
+	case json.Number:
+		return strconv.ParseUint(j.data.(json.Number).String(), 10, 64)
+	case float32, float64:
+		return uint64(reflect.ValueOf(j.data).Float()), nil
+	case int, int8, int16, int32, int64:
+		return uint64(reflect.ValueOf(j.data).Int()), nil
+	case uint, uint8, uint16, uint32, uint64:
+		return reflect.ValueOf(j.data).Uint(), nil
+	}
+	return 0, errors.New("invalid value type")
+}

+ 248 - 0
pkg/components/simplejson/simplejson_test.go

@@ -0,0 +1,248 @@
+package simplejson
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/bmizerany/assert"
+)
+
+func TestSimplejson(t *testing.T) {
+	var ok bool
+	var err error
+
+	js, err := NewJson([]byte(`{
+		"test": {
+			"string_array": ["asdf", "ghjk", "zxcv"],
+			"string_array_null": ["abc", null, "efg"],
+			"array": [1, "2", 3],
+			"arraywithsubs": [{"subkeyone": 1},
+			{"subkeytwo": 2, "subkeythree": 3}],
+			"int": 10,
+			"float": 5.150,
+			"string": "simplejson",
+			"bool": true,
+			"sub_obj": {"a": 1}
+		}
+	}`))
+
+	assert.NotEqual(t, nil, js)
+	assert.Equal(t, nil, err)
+
+	_, ok = js.CheckGet("test")
+	assert.Equal(t, true, ok)
+
+	_, ok = js.CheckGet("missing_key")
+	assert.Equal(t, false, ok)
+
+	aws := js.Get("test").Get("arraywithsubs")
+	assert.NotEqual(t, nil, aws)
+	var awsval int
+	awsval, _ = aws.GetIndex(0).Get("subkeyone").Int()
+	assert.Equal(t, 1, awsval)
+	awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int()
+	assert.Equal(t, 2, awsval)
+	awsval, _ = aws.GetIndex(1).Get("subkeythree").Int()
+	assert.Equal(t, 3, awsval)
+
+	i, _ := js.Get("test").Get("int").Int()
+	assert.Equal(t, 10, i)
+
+	f, _ := js.Get("test").Get("float").Float64()
+	assert.Equal(t, 5.150, f)
+
+	s, _ := js.Get("test").Get("string").String()
+	assert.Equal(t, "simplejson", s)
+
+	b, _ := js.Get("test").Get("bool").Bool()
+	assert.Equal(t, true, b)
+
+	mi := js.Get("test").Get("int").MustInt()
+	assert.Equal(t, 10, mi)
+
+	mi2 := js.Get("test").Get("missing_int").MustInt(5150)
+	assert.Equal(t, 5150, mi2)
+
+	ms := js.Get("test").Get("string").MustString()
+	assert.Equal(t, "simplejson", ms)
+
+	ms2 := js.Get("test").Get("missing_string").MustString("fyea")
+	assert.Equal(t, "fyea", ms2)
+
+	ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"})
+	assert.Equal(t, ma2, []interface{}{"1", 2, "3"})
+
+	msa := js.Get("test").Get("string_array").MustStringArray()
+	assert.Equal(t, msa[0], "asdf")
+	assert.Equal(t, msa[1], "ghjk")
+	assert.Equal(t, msa[2], "zxcv")
+
+	msa2 := js.Get("test").Get("string_array").MustStringArray([]string{"1", "2", "3"})
+	assert.Equal(t, msa2[0], "asdf")
+	assert.Equal(t, msa2[1], "ghjk")
+	assert.Equal(t, msa2[2], "zxcv")
+
+	msa3 := js.Get("test").Get("missing_array").MustStringArray([]string{"1", "2", "3"})
+	assert.Equal(t, msa3, []string{"1", "2", "3"})
+
+	mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false})
+	assert.Equal(t, mm2, map[string]interface{}{"found": false})
+
+	strs, err := js.Get("test").Get("string_array").StringArray()
+	assert.Equal(t, err, nil)
+	assert.Equal(t, strs[0], "asdf")
+	assert.Equal(t, strs[1], "ghjk")
+	assert.Equal(t, strs[2], "zxcv")
+
+	strs2, err := js.Get("test").Get("string_array_null").StringArray()
+	assert.Equal(t, err, nil)
+	assert.Equal(t, strs2[0], "abc")
+	assert.Equal(t, strs2[1], "")
+	assert.Equal(t, strs2[2], "efg")
+
+	gp, _ := js.GetPath("test", "string").String()
+	assert.Equal(t, "simplejson", gp)
+
+	gp2, _ := js.GetPath("test", "int").Int()
+	assert.Equal(t, 10, gp2)
+
+	assert.Equal(t, js.Get("test").Get("bool").MustBool(), true)
+
+	js.Set("float2", 300.0)
+	assert.Equal(t, js.Get("float2").MustFloat64(), 300.0)
+
+	js.Set("test2", "setTest")
+	assert.Equal(t, "setTest", js.Get("test2").MustString())
+
+	js.Del("test2")
+	assert.NotEqual(t, "setTest", js.Get("test2").MustString())
+
+	js.Get("test").Get("sub_obj").Set("a", 2)
+	assert.Equal(t, 2, js.Get("test").Get("sub_obj").Get("a").MustInt())
+
+	js.GetPath("test", "sub_obj").Set("a", 3)
+	assert.Equal(t, 3, js.GetPath("test", "sub_obj", "a").MustInt())
+}
+
+func TestStdlibInterfaces(t *testing.T) {
+	val := new(struct {
+		Name   string `json:"name"`
+		Params *Json  `json:"params"`
+	})
+	val2 := new(struct {
+		Name   string `json:"name"`
+		Params *Json  `json:"params"`
+	})
+
+	raw := `{"name":"myobject","params":{"string":"simplejson"}}`
+
+	assert.Equal(t, nil, json.Unmarshal([]byte(raw), val))
+
+	assert.Equal(t, "myobject", val.Name)
+	assert.NotEqual(t, nil, val.Params.data)
+	s, _ := val.Params.Get("string").String()
+	assert.Equal(t, "simplejson", s)
+
+	p, err := json.Marshal(val)
+	assert.Equal(t, nil, err)
+	assert.Equal(t, nil, json.Unmarshal(p, val2))
+	assert.Equal(t, val, val2) // stable
+}
+
+func TestSet(t *testing.T) {
+	js, err := NewJson([]byte(`{}`))
+	assert.Equal(t, nil, err)
+
+	js.Set("baz", "bing")
+
+	s, err := js.GetPath("baz").String()
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "bing", s)
+}
+
+func TestReplace(t *testing.T) {
+	js, err := NewJson([]byte(`{}`))
+	assert.Equal(t, nil, err)
+
+	err = js.UnmarshalJSON([]byte(`{"baz":"bing"}`))
+	assert.Equal(t, nil, err)
+
+	s, err := js.GetPath("baz").String()
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "bing", s)
+}
+
+func TestSetPath(t *testing.T) {
+	js, err := NewJson([]byte(`{}`))
+	assert.Equal(t, nil, err)
+
+	js.SetPath([]string{"foo", "bar"}, "baz")
+
+	s, err := js.GetPath("foo", "bar").String()
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "baz", s)
+}
+
+func TestSetPathNoPath(t *testing.T) {
+	js, err := NewJson([]byte(`{"some":"data","some_number":1.0,"some_bool":false}`))
+	assert.Equal(t, nil, err)
+
+	f := js.GetPath("some_number").MustFloat64(99.0)
+	assert.Equal(t, f, 1.0)
+
+	js.SetPath([]string{}, map[string]interface{}{"foo": "bar"})
+
+	s, err := js.GetPath("foo").String()
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "bar", s)
+
+	f = js.GetPath("some_number").MustFloat64(99.0)
+	assert.Equal(t, f, 99.0)
+}
+
+func TestPathWillAugmentExisting(t *testing.T) {
+	js, err := NewJson([]byte(`{"this":{"a":"aa","b":"bb","c":"cc"}}`))
+	assert.Equal(t, nil, err)
+
+	js.SetPath([]string{"this", "d"}, "dd")
+
+	cases := []struct {
+		path    []string
+		outcome string
+	}{
+		{
+			path:    []string{"this", "a"},
+			outcome: "aa",
+		},
+		{
+			path:    []string{"this", "b"},
+			outcome: "bb",
+		},
+		{
+			path:    []string{"this", "c"},
+			outcome: "cc",
+		},
+		{
+			path:    []string{"this", "d"},
+			outcome: "dd",
+		},
+	}
+
+	for _, tc := range cases {
+		s, err := js.GetPath(tc.path...).String()
+		assert.Equal(t, nil, err)
+		assert.Equal(t, tc.outcome, s)
+	}
+}
+
+func TestPathWillOverwriteExisting(t *testing.T) {
+	// notice how "a" is 0.1 - but then we'll try to set at path a, foo
+	js, err := NewJson([]byte(`{"this":{"a":0.1,"b":"bb","c":"cc"}}`))
+	assert.Equal(t, nil, err)
+
+	js.SetPath([]string{"this", "a", "foo"}, "bar")
+
+	s, err := js.GetPath("this", "a", "foo").String()
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "bar", s)
+}

+ 29 - 27
pkg/models/datasource.go

@@ -3,6 +3,8 @@ package models
 import (
 	"errors"
 	"time"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
 )
 
 const (
@@ -42,7 +44,7 @@ type DataSource struct {
 	BasicAuthPassword string
 	WithCredentials   bool
 	IsDefault         bool
-	JsonData          map[string]interface{}
+	JsonData          *simplejson.Json
 
 	Created time.Time
 	Updated time.Time
@@ -74,19 +76,19 @@ func IsKnownDataSourcePlugin(dsType string) bool {
 
 // Also acts as api DTO
 type AddDataSourceCommand struct {
-	Name              string                 `json:"name" binding:"Required"`
-	Type              string                 `json:"type" binding:"Required"`
-	Access            DsAccess               `json:"access" binding:"Required"`
-	Url               string                 `json:"url"`
-	Password          string                 `json:"password"`
-	Database          string                 `json:"database"`
-	User              string                 `json:"user"`
-	BasicAuth         bool                   `json:"basicAuth"`
-	BasicAuthUser     string                 `json:"basicAuthUser"`
-	BasicAuthPassword string                 `json:"basicAuthPassword"`
-	WithCredentials   bool                   `json:"withCredentials"`
-	IsDefault         bool                   `json:"isDefault"`
-	JsonData          map[string]interface{} `json:"jsonData"`
+	Name              string           `json:"name" binding:"Required"`
+	Type              string           `json:"type" binding:"Required"`
+	Access            DsAccess         `json:"access" binding:"Required"`
+	Url               string           `json:"url"`
+	Password          string           `json:"password"`
+	Database          string           `json:"database"`
+	User              string           `json:"user"`
+	BasicAuth         bool             `json:"basicAuth"`
+	BasicAuthUser     string           `json:"basicAuthUser"`
+	BasicAuthPassword string           `json:"basicAuthPassword"`
+	WithCredentials   bool             `json:"withCredentials"`
+	IsDefault         bool             `json:"isDefault"`
+	JsonData          *simplejson.Json `json:"jsonData"`
 
 	OrgId int64 `json:"-"`
 
@@ -95,19 +97,19 @@ type AddDataSourceCommand struct {
 
 // Also acts as api DTO
 type UpdateDataSourceCommand struct {
-	Name              string                 `json:"name" binding:"Required"`
-	Type              string                 `json:"type" binding:"Required"`
-	Access            DsAccess               `json:"access" binding:"Required"`
-	Url               string                 `json:"url"`
-	Password          string                 `json:"password"`
-	User              string                 `json:"user"`
-	Database          string                 `json:"database"`
-	BasicAuth         bool                   `json:"basicAuth"`
-	BasicAuthUser     string                 `json:"basicAuthUser"`
-	BasicAuthPassword string                 `json:"basicAuthPassword"`
-	WithCredentials   bool                   `json:"withCredentials"`
-	IsDefault         bool                   `json:"isDefault"`
-	JsonData          map[string]interface{} `json:"jsonData"`
+	Name              string           `json:"name" binding:"Required"`
+	Type              string           `json:"type" binding:"Required"`
+	Access            DsAccess         `json:"access" binding:"Required"`
+	Url               string           `json:"url"`
+	Password          string           `json:"password"`
+	User              string           `json:"user"`
+	Database          string           `json:"database"`
+	BasicAuth         bool             `json:"basicAuth"`
+	BasicAuthUser     string           `json:"basicAuthUser"`
+	BasicAuthPassword string           `json:"basicAuthPassword"`
+	WithCredentials   bool             `json:"withCredentials"`
+	IsDefault         bool             `json:"isDefault"`
+	JsonData          *simplejson.Json `json:"jsonData"`
 
 	OrgId int64 `json:"-"`
 	Id    int64 `json:"-"`

+ 42 - 12
pkg/plugins/dashboard_importer.go

@@ -1,6 +1,10 @@
 package plugins
 
 import (
+	"fmt"
+	"reflect"
+	"regexp"
+
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/dynmap"
 	"github.com/grafana/grafana/pkg/log"
@@ -24,6 +28,14 @@ type ImportDashboardInput struct {
 	Value    string `json:"value"`
 }
 
+type DashboardInputMissingError struct {
+	VariableName string
+}
+
+func (e DashboardInputMissingError) Error() string {
+	return fmt.Sprintf("Dashbord input variable: %v missing from import command", e.VariableName)
+}
+
 func init() {
 	bus.AddHandler("plugins", ImportDashboard)
 }
@@ -42,8 +54,19 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
 		return err
 	}
 
+	template := dynmap.NewFromMap(dashboard.Data)
+	evaluator := &DashTemplateEvaluator{
+		template: template,
+		inputs:   cmd.Inputs,
+	}
+
+	generatedDash, err := evaluator.Eval()
+	if err != nil {
+		return err
+	}
+
 	saveCmd := m.SaveDashboardCommand{
-		Dashboard: dashboard.Data,
+		Dashboard: generatedDash.StringMap(),
 		OrgId:     cmd.OrgId,
 		UserId:    cmd.UserId,
 	}
@@ -70,15 +93,9 @@ type DashTemplateEvaluator struct {
 	inputs    []ImportDashboardInput
 	variables map[string]string
 	result    *dynmap.Object
+	varRegex  *regexp.Regexp
 }
 
-// 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")
 
@@ -94,14 +111,22 @@ func (this *DashTemplateEvaluator) findInput(varName string, varDef *dynmap.Obje
 func (this *DashTemplateEvaluator) Eval() (*dynmap.Object, error) {
 	this.result = dynmap.NewObject()
 	this.variables = make(map[string]string)
+	this.varRegex, _ = regexp.Compile("\\$__(\\w+)")
 
 	// 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
+
+			if input == nil {
+				return nil, &DashboardInputMissingError{VariableName: varName}
+			}
+
+			this.variables["$__"+varName] = input.Value
 		}
+	} else {
+		log.Info("Import: dashboard has no __import section")
 	}
 
 	this.EvalObject(this.template, this.result)
@@ -111,19 +136,24 @@ func (this *DashTemplateEvaluator) Eval() (*dynmap.Object, error) {
 func (this *DashTemplateEvaluator) EvalObject(source *dynmap.Object, writer *dynmap.Object) {
 
 	for key, value := range source.Map() {
-		if key == "__input" {
+		if key == "__inputs" {
 			continue
 		}
 
 		goValue := value.Interface()
-		switch goValue.(type) {
+
+		switch v := goValue.(type) {
 		case string:
-			writer.SetValue(key, goValue)
+			interpolated := this.varRegex.ReplaceAllStringFunc(v, func(match string) string {
+				return this.variables[match]
+			})
+			writer.SetValue(key, interpolated)
 		case map[string]interface{}:
 			childSource, _ := value.Object()
 			childWriter, _ := writer.SetValue(key, map[string]interface{}{}).Object()
 			this.EvalObject(childSource, childWriter)
 		default:
+			log.Info("type: %v", reflect.TypeOf(goValue))
 			log.Error(3, "Unknown json type key: %v , type: %v", key, goValue)
 		}
 	}

+ 14 - 2
pkg/plugins/dashboard_importer_test.go

@@ -33,7 +33,9 @@ func TestDashboardImport(t *testing.T) {
 			Path:     "dashboards/connections.json",
 			OrgId:    1,
 			UserId:   1,
-			Inputs:   []ImportDashboardInput{},
+			Inputs: []ImportDashboardInput{
+				{Name: "*", Type: "datasource"},
+			},
 		}
 
 		err = ImportDashboard(&cmd)
@@ -41,6 +43,10 @@ func TestDashboardImport(t *testing.T) {
 
 		Convey("should install dashboard", func() {
 			So(importedDash, ShouldNotBeNil)
+
+			dashData := dynmap.NewFromMap(importedDash.Data)
+			So(dashData.String(), ShouldEqual, "")
+
 			rows := importedDash.Data["rows"].([]interface{})
 			row1 := rows[0].(map[string]interface{})
 			panels := row1["panels"].([]interface{})
@@ -53,7 +59,7 @@ func TestDashboardImport(t *testing.T) {
 
 	Convey("When evaling dashboard template", t, func() {
 		template, _ := dynmap.NewObjectFromBytes([]byte(`{
-      "__input": {
+      "__inputs": {
         "graphite": {
           "type": "datasource"
         }
@@ -76,6 +82,12 @@ func TestDashboardImport(t *testing.T) {
 		Convey("should render template", func() {
 			So(res.MustGetString("test.prop", ""), ShouldEqual, "my-server")
 		})
+
+		Convey("should not include inputs in output", func() {
+			_, err := res.GetObject("__inputs")
+			So(err, ShouldNotBeNil)
+		})
+
 	})
 
 }

+ 2 - 1
public/app/features/plugins/ds_edit_ctrl.ts

@@ -24,6 +24,7 @@ export class DataSourceEditCtrl {
   datasourceMeta: any;
   tabIndex: number;
   hasDashboards: boolean;
+  editForm: any;
 
   /** @ngInject */
   constructor(
@@ -114,7 +115,7 @@ export class DataSourceEditCtrl {
     }
 
     saveChanges(test) {
-      if (!this.$scope.editForm.$valid) {
+      if (!this.editForm.$valid) {
         return;
       }
 

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

@@ -25,7 +25,7 @@
 
   <div ng-if="ctrl.tabIndex === 0" class="tab-content">
 
-    <form name="editForm">
+    <form name="ctrl.editForm">
       <div class="gf-form-group">
         <div class="gf-form">
           <span class="gf-form-label width-7">Name</span>
@@ -52,12 +52,12 @@
         </plugin-component>
       </rebuild-on-change>
 
-      <div ng-if="testing" style="margin-top: 25px">
-        <h5 ng-show="!testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
-        <h5 ng-show="testing.done">Test results</h5>
-        <div class="alert-{{testing.status}} alert">
-          <div class="alert-title">{{testing.title}}</div>
-          <div ng-bind='testing.message'></div>
+      <div ng-if="ctrl.testing" style="margin-top: 25px">
+        <h5 ng-show="!ctrl.testing.done">Testing.... <i class="fa fa-spiner fa-spin"></i></h5>
+        <h5 ng-show="ctrl.testing.done">Test results</h5>
+        <div class="alert-{{ctrl.testing.status}} alert">
+          <div class="alert-title">{{ctrl.testing.title}}</div>
+          <div ng-bind='ctrl.testing.message'></div>
         </div>
       </div>
 

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

@@ -14,7 +14,7 @@
       "panels": [
         {
           "type": "graph",
-          "datasource": "__$graphite"
+          "datasource": "$__graphite"
         }
       ]
     }