Просмотр исходного кода

Merge branch 'dashboard-install'

Torkel Ödegaard 9 лет назад
Родитель
Сommit
53dc145ce7
64 измененных файлов с 4434 добавлено и 382 удалено
  1. 14 4
      Godeps/Godeps.json
  2. 7 0
      Godeps/_workspace/src/github.com/bmizerany/assert/.gitignore
  3. 45 0
      Godeps/_workspace/src/github.com/bmizerany/assert/README.md
  4. 76 0
      Godeps/_workspace/src/github.com/bmizerany/assert/assert.go
  5. 15 0
      Godeps/_workspace/src/github.com/bmizerany/assert/assert_test.go
  6. 5 0
      Godeps/_workspace/src/github.com/bmizerany/assert/example/point.go
  7. 13 0
      Godeps/_workspace/src/github.com/bmizerany/assert/example/point_test.go
  8. 0 168
      Godeps/_workspace/src/github.com/gopherjs/gopherjs/js/js.go
  9. 4 0
      Godeps/_workspace/src/github.com/kr/pretty/.gitignore
  10. 21 0
      Godeps/_workspace/src/github.com/kr/pretty/License
  11. 9 0
      Godeps/_workspace/src/github.com/kr/pretty/Readme
  12. 158 0
      Godeps/_workspace/src/github.com/kr/pretty/diff.go
  13. 74 0
      Godeps/_workspace/src/github.com/kr/pretty/diff_test.go
  14. 20 0
      Godeps/_workspace/src/github.com/kr/pretty/example_test.go
  15. 337 0
      Godeps/_workspace/src/github.com/kr/pretty/formatter.go
  16. 261 0
      Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go
  17. 98 0
      Godeps/_workspace/src/github.com/kr/pretty/pretty.go
  18. 41 0
      Godeps/_workspace/src/github.com/kr/pretty/zero.go
  19. 19 0
      Godeps/_workspace/src/github.com/kr/text/License
  20. 3 0
      Godeps/_workspace/src/github.com/kr/text/Readme
  21. 5 0
      Godeps/_workspace/src/github.com/kr/text/colwriter/Readme
  22. 147 0
      Godeps/_workspace/src/github.com/kr/text/colwriter/column.go
  23. 90 0
      Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go
  24. 3 0
      Godeps/_workspace/src/github.com/kr/text/doc.go
  25. 74 0
      Godeps/_workspace/src/github.com/kr/text/indent.go
  26. 119 0
      Godeps/_workspace/src/github.com/kr/text/indent_test.go
  27. 9 0
      Godeps/_workspace/src/github.com/kr/text/mc/Readme
  28. 62 0
      Godeps/_workspace/src/github.com/kr/text/mc/mc.go
  29. 86 0
      Godeps/_workspace/src/github.com/kr/text/wrap.go
  30. 62 0
      Godeps/_workspace/src/github.com/kr/text/wrap_test.go
  31. 1 1
      pkg/api/api.go
  32. 5 2
      pkg/api/cloudwatch/metrics.go
  33. 0 2
      pkg/api/dashboard_snapshot.go
  34. 18 17
      pkg/api/dtos/models.go
  35. 5 5
      pkg/api/dtos/plugins.go
  36. 1 1
      pkg/api/frontendsettings.go
  37. 2 2
      pkg/api/plugins.go
  38. 817 0
      pkg/components/dynmap/dynmap.go
  39. 313 0
      pkg/components/dynmap/dynmap_test.go
  40. 468 0
      pkg/components/simplejson/simplejson.go
  41. 89 0
      pkg/components/simplejson/simplejson_go11.go
  42. 248 0
      pkg/components/simplejson/simplejson_test.go
  43. 9 5
      pkg/models/dashboard_snapshot.go
  44. 21 31
      pkg/models/dashboards.go
  45. 4 4
      pkg/models/dashboards_test.go
  46. 29 27
      pkg/models/datasource.go
  47. 171 0
      pkg/plugins/dashboard_importer.go
  48. 93 0
      pkg/plugins/dashboard_importer_test.go
  49. 0 57
      pkg/plugins/dashboard_installer.go
  50. 3 5
      pkg/plugins/dashboards.go
  51. 1 1
      pkg/plugins/dashboards_test.go
  52. 3 5
      pkg/services/search/json_index.go
  53. 2 2
      pkg/services/sqlstore/dashboard.go
  54. 4 3
      pkg/services/sqlstore/dashboard_snapshot_test.go
  55. 9 8
      pkg/services/sqlstore/dashboard_test.go
  56. 2 2
      public/app/features/dashboard/dashnav/dashnav.ts
  57. 3 3
      public/app/features/plugins/import_list/import_list.html
  58. 14 3
      public/app/features/plugins/import_list/import_list.ts
  59. 1 1
      public/app/features/plugins/partials/ds_edit.html
  60. 176 0
      public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json
  61. 0 21
      public/app/plugins/datasource/graphite/dashboards/carbon_stats.json
  62. 1 1
      public/app/plugins/datasource/graphite/plugin.json
  63. 24 1
      tests/test-app/dashboards/connections.json
  64. 20 0
      tests/test-app/dashboards/connections_result.json

+ 14 - 4
Godeps/Godeps.json

@@ -64,6 +64,11 @@
 			"Comment": "v1.0.0",
 			"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
 		},
+		{
+			"ImportPath": "github.com/bmizerany/assert",
+			"Comment": "release.r60-6-ge17e998",
+			"Rev": "e17e99893cb6509f428e1728281c2ad60a6b31e3"
+		},
 		{
 			"ImportPath": "github.com/bradfitz/gomemcache/memcache",
 			"Comment": "release.r60-40-g72a6864",
@@ -123,10 +128,6 @@
 			"Comment": "v0.4.4-44-gf561133",
 			"Rev": "f56113384f2c63dfe4cd8e768e349f1c35122b58"
 		},
-		{
-			"ImportPath": "github.com/gopherjs/gopherjs/js",
-			"Rev": "14d893dca2e4adb93a5ccc9494040acc0821cd8d"
-		},
 		{
 			"ImportPath": "github.com/gosimple/slug",
 			"Rev": "8d258463b4459f161f51d6a357edacd3eef9d663"
@@ -160,6 +161,15 @@
 			"ImportPath": "github.com/klauspost/crc32",
 			"Rev": "6834731faf32e62a2dd809d99fb24d1e4ae5a92d"
 		},
+		{
+			"ImportPath": "github.com/kr/pretty",
+			"Comment": "go.weekly.2011-12-22-27-ge6ac2fc",
+			"Rev": "e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb"
+		},
+		{
+			"ImportPath": "github.com/kr/text",
+			"Rev": "bb797dc4fb8320488f47bf11de07a733d7233e1f"
+		},
 		{
 			"ImportPath": "github.com/lib/pq",
 			"Comment": "go1.0-cutoff-13-g19eeca3",

+ 7 - 0
Godeps/_workspace/src/github.com/bmizerany/assert/.gitignore

@@ -0,0 +1,7 @@
+_go_.*
+_gotest_.*
+_obj
+_test
+_testmain.go
+*.out
+*.[568]

+ 45 - 0
Godeps/_workspace/src/github.com/bmizerany/assert/README.md

@@ -0,0 +1,45 @@
+# Assert (c) Blake Mizerany and Keith Rarick -- MIT LICENCE
+
+## Assertions for Go tests
+
+## Install
+
+    $ go get github.com/bmizerany/assert
+
+## Use
+
+**point.go**
+
+    package point
+
+    type Point struct {
+        x, y int
+    }
+
+**point_test.go**
+
+
+    package point
+
+    import (
+        "testing"
+        "github.com/bmizerany/assert"
+    )
+
+    func TestAsserts(t *testing.T) {
+        p1 := Point{1, 1}
+        p2 := Point{2, 1}
+
+        assert.Equal(t, p1, p2)
+    }
+
+**output**
+    $ go test
+     --- FAIL: TestAsserts (0.00 seconds)
+	 assert.go:15: /Users/flavio.barbosa/dev/stewie/src/point_test.go:12
+         assert.go:24: ! X: 1 != 2
+	 FAIL
+
+## Docs
+
+    http://github.com/bmizerany/assert

+ 76 - 0
Godeps/_workspace/src/github.com/bmizerany/assert/assert.go

@@ -0,0 +1,76 @@
+package assert
+// Testing helpers for doozer.
+
+import (
+	"github.com/kr/pretty"
+	"reflect"
+	"testing"
+	"runtime"
+	"fmt"
+)
+
+func assert(t *testing.T, result bool, f func(), cd int) {
+	if !result {
+		_, file, line, _ := runtime.Caller(cd + 1)
+		t.Errorf("%s:%d", file, line)
+		f()
+		t.FailNow()
+	}
+}
+
+func equal(t *testing.T, exp, got interface{}, cd int, args ...interface{}) {
+	fn := func() {
+		for _, desc := range pretty.Diff(exp, got) {
+			t.Error("!", desc)
+		}
+		if len(args) > 0 {
+			t.Error("!", " -", fmt.Sprint(args...))
+		}
+	}
+	result := reflect.DeepEqual(exp, got)
+	assert(t, result, fn, cd+1)
+}
+
+func tt(t *testing.T, result bool, cd int, args ...interface{}) {
+	fn := func() {
+		t.Errorf("!  Failure")
+		if len(args) > 0 {
+			t.Error("!", " -", fmt.Sprint(args...))
+		}
+	}
+	assert(t, result, fn, cd+1)
+}
+
+func T(t *testing.T, result bool, args ...interface{}) {
+	tt(t, result, 1, args...)
+}
+
+func Tf(t *testing.T, result bool, format string, args ...interface{}) {
+	tt(t, result, 1, fmt.Sprintf(format, args...))
+}
+
+func Equal(t *testing.T, exp, got interface{}, args ...interface{}) {
+	equal(t, exp, got, 1, args...)
+}
+
+func Equalf(t *testing.T, exp, got interface{}, format string, args ...interface{}) {
+	equal(t, exp, got, 1, fmt.Sprintf(format, args...))
+}
+
+func NotEqual(t *testing.T, exp, got interface{}, args ...interface{}) {
+	fn := func() {
+		t.Errorf("!  Unexpected: <%#v>", exp)
+		if len(args) > 0 {
+			t.Error("!", " -", fmt.Sprint(args...))
+		}
+	}
+	result := !reflect.DeepEqual(exp, got)
+	assert(t, result, fn, 1)
+}
+
+func Panic(t *testing.T, err interface{}, fn func()) {
+	defer func() {
+		equal(t, err, recover(), 3)
+	}()
+	fn()
+}

+ 15 - 0
Godeps/_workspace/src/github.com/bmizerany/assert/assert_test.go

@@ -0,0 +1,15 @@
+package assert
+
+import (
+	"testing"
+)
+
+func TestLineNumbers(t *testing.T) {
+	Equal(t, "foo", "foo", "msg!")
+	//Equal(t, "foo", "bar", "this should blow up")
+}
+
+func TestNotEqual(t *testing.T) {
+	NotEqual(t, "foo", "bar", "msg!")
+	//NotEqual(t, "foo", "foo", "this should blow up")
+}

+ 5 - 0
Godeps/_workspace/src/github.com/bmizerany/assert/example/point.go

@@ -0,0 +1,5 @@
+package point
+
+type Point struct {
+	X, Y int
+}

+ 13 - 0
Godeps/_workspace/src/github.com/bmizerany/assert/example/point_test.go

@@ -0,0 +1,13 @@
+package point
+
+import (
+	"testing"
+	"assert"
+)
+
+func TestAsserts(t *testing.T) {
+	p1 := Point{1, 1}
+	p2 := Point{2, 1}
+
+	assert.Equal(t, p1, p2)
+}

+ 0 - 168
Godeps/_workspace/src/github.com/gopherjs/gopherjs/js/js.go

@@ -1,168 +0,0 @@
-// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax.
-//
-// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed:
-//
-//  | Go type               | JavaScript type       | Conversions back to interface{} |
-//  | --------------------- | --------------------- | ------------------------------- |
-//  | bool                  | Boolean               | bool                            |
-//  | integers and floats   | Number                | float64                         |
-//  | string                | String                | string                          |
-//  | []int8                | Int8Array             | []int8                          |
-//  | []int16               | Int16Array            | []int16                         |
-//  | []int32, []int        | Int32Array            | []int                           |
-//  | []uint8               | Uint8Array            | []uint8                         |
-//  | []uint16              | Uint16Array           | []uint16                        |
-//  | []uint32, []uint      | Uint32Array           | []uint                          |
-//  | []float32             | Float32Array          | []float32                       |
-//  | []float64             | Float64Array          | []float64                       |
-//  | all other slices      | Array                 | []interface{}                   |
-//  | arrays                | see slice type        | see slice type                  |
-//  | functions             | Function              | func(...interface{}) *js.Object |
-//  | time.Time             | Date                  | time.Time                       |
-//  | -                     | instanceof Node       | *js.Object                      |
-//  | maps, structs         | instanceof Object     | map[string]interface{}          |
-//
-// Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa.
-package js
-
-// Object is a container for a native JavaScript object. Calls to its methods are treated specially by GopherJS and translated directly to their JavaScript syntax. A nil pointer to Object is equal to JavaScript's "null". Object can not be used as a map key.
-type Object struct{ object *Object }
-
-// Get returns the object's property with the given key.
-func (o *Object) Get(key string) *Object { return o.object.Get(key) }
-
-// Set assigns the value to the object's property with the given key.
-func (o *Object) Set(key string, value interface{}) { o.object.Set(key, value) }
-
-// Delete removes the object's property with the given key.
-func (o *Object) Delete(key string) { o.object.Delete(key) }
-
-// Length returns the object's "length" property, converted to int.
-func (o *Object) Length() int { return o.object.Length() }
-
-// Index returns the i'th element of an array.
-func (o *Object) Index(i int) *Object { return o.object.Index(i) }
-
-// SetIndex sets the i'th element of an array.
-func (o *Object) SetIndex(i int, value interface{}) { o.object.SetIndex(i, value) }
-
-// Call calls the object's method with the given name.
-func (o *Object) Call(name string, args ...interface{}) *Object { return o.object.Call(name, args...) }
-
-// Invoke calls the object itself. This will fail if it is not a function.
-func (o *Object) Invoke(args ...interface{}) *Object { return o.object.Invoke(args...) }
-
-// New creates a new instance of this type object. This will fail if it not a function (constructor).
-func (o *Object) New(args ...interface{}) *Object { return o.object.New(args...) }
-
-// Bool returns the object converted to bool according to JavaScript type conversions.
-func (o *Object) Bool() bool { return o.object.Bool() }
-
-// String returns the object converted to string according to JavaScript type conversions.
-func (o *Object) String() string { return o.object.String() }
-
-// Int returns the object converted to int according to JavaScript type conversions (parseInt).
-func (o *Object) Int() int { return o.object.Int() }
-
-// Int64 returns the object converted to int64 according to JavaScript type conversions (parseInt).
-func (o *Object) Int64() int64 { return o.object.Int64() }
-
-// Uint64 returns the object converted to uint64 according to JavaScript type conversions (parseInt).
-func (o *Object) Uint64() uint64 { return o.object.Uint64() }
-
-// Float returns the object converted to float64 according to JavaScript type conversions (parseFloat).
-func (o *Object) Float() float64 { return o.object.Float() }
-
-// Interface returns the object converted to interface{}. See GopherJS' README for details.
-func (o *Object) Interface() interface{} { return o.object.Interface() }
-
-// Unsafe returns the object as an uintptr, which can be converted via unsafe.Pointer. Not intended for public use.
-func (o *Object) Unsafe() uintptr { return o.object.Unsafe() }
-
-// Error encapsulates JavaScript errors. Those are turned into a Go panic and may be recovered, giving an *Error that holds the JavaScript error object.
-type Error struct {
-	*Object
-}
-
-// Error returns the message of the encapsulated JavaScript error object.
-func (err *Error) Error() string {
-	return "JavaScript error: " + err.Get("message").String()
-}
-
-// Stack returns the stack property of the encapsulated JavaScript error object.
-func (err *Error) Stack() string {
-	return err.Get("stack").String()
-}
-
-// Global gives JavaScript's global object ("window" for browsers and "GLOBAL" for Node.js).
-var Global *Object
-
-// Module gives the value of the "module" variable set by Node.js. Hint: Set a module export with 'js.Module.Get("exports").Set("exportName", ...)'.
-var Module *Object
-
-// Undefined gives the JavaScript value "undefined".
-var Undefined *Object
-
-// Debugger gets compiled to JavaScript's "debugger;" statement.
-func Debugger() {}
-
-// InternalObject returns the internal JavaScript object that represents i. Not intended for public use.
-func InternalObject(i interface{}) *Object {
-	return nil
-}
-
-// MakeFunc wraps a function and gives access to the values of JavaScript's "this" and "arguments" keywords.
-func MakeFunc(func(this *Object, arguments []*Object) interface{}) *Object {
-	return nil
-}
-
-// Keys returns the keys of the given JavaScript object.
-func Keys(o *Object) []string {
-	if o == nil || o == Undefined {
-		return nil
-	}
-	a := Global.Get("Object").Call("keys", o)
-	s := make([]string, a.Length())
-	for i := 0; i < a.Length(); i++ {
-		s[i] = a.Index(i).String()
-	}
-	return s
-}
-
-// MakeWrapper creates a JavaScript object which has wrappers for the exported methods of i. Use explicit getter and setter methods to expose struct fields to JavaScript.
-func MakeWrapper(i interface{}) *Object {
-	v := InternalObject(i)
-	o := Global.Get("Object").New()
-	o.Set("__internal_object__", v)
-	methods := v.Get("constructor").Get("methods")
-	for i := 0; i < methods.Length(); i++ {
-		m := methods.Index(i)
-		if m.Get("pkg").String() != "" { // not exported
-			continue
-		}
-		o.Set(m.Get("name").String(), func(args ...*Object) *Object {
-			return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true).Call("apply", v, args)
-		})
-	}
-	return o
-}
-
-// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice.
-func NewArrayBuffer(b []byte) *Object {
-	slice := InternalObject(b)
-	offset := slice.Get("$offset").Int()
-	length := slice.Get("$length").Int()
-	return slice.Get("$array").Get("buffer").Call("slice", offset, offset+length)
-}
-
-// M is a simple map type. It is intended as a shorthand for JavaScript objects (before conversion).
-type M map[string]interface{}
-
-// S is a simple slice type. It is intended as a shorthand for JavaScript arrays (before conversion).
-type S []interface{}
-
-func init() {
-	// avoid dead code elimination
-	e := Error{}
-	_ = e
-}

+ 4 - 0
Godeps/_workspace/src/github.com/kr/pretty/.gitignore

@@ -0,0 +1,4 @@
+[568].out
+_go*
+_test*
+_obj

+ 21 - 0
Godeps/_workspace/src/github.com/kr/pretty/License

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright 2012 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 9 - 0
Godeps/_workspace/src/github.com/kr/pretty/Readme

@@ -0,0 +1,9 @@
+package pretty
+
+    import "github.com/kr/pretty"
+
+    Package pretty provides pretty-printing for Go values.
+
+Documentation
+
+    http://godoc.org/github.com/kr/pretty

+ 158 - 0
Godeps/_workspace/src/github.com/kr/pretty/diff.go

@@ -0,0 +1,158 @@
+package pretty
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+)
+
+type sbuf []string
+
+func (s *sbuf) Write(b []byte) (int, error) {
+	*s = append(*s, string(b))
+	return len(b), nil
+}
+
+// Diff returns a slice where each element describes
+// a difference between a and b.
+func Diff(a, b interface{}) (desc []string) {
+	Fdiff((*sbuf)(&desc), a, b)
+	return desc
+}
+
+// Fdiff writes to w a description of the differences between a and b.
+func Fdiff(w io.Writer, a, b interface{}) {
+	diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b))
+}
+
+type diffWriter struct {
+	w io.Writer
+	l string // label
+}
+
+func (w diffWriter) printf(f string, a ...interface{}) {
+	var l string
+	if w.l != "" {
+		l = w.l + ": "
+	}
+	fmt.Fprintf(w.w, l+f, a...)
+}
+
+func (w diffWriter) diff(av, bv reflect.Value) {
+	if !av.IsValid() && bv.IsValid() {
+		w.printf("nil != %#v", bv.Interface())
+		return
+	}
+	if av.IsValid() && !bv.IsValid() {
+		w.printf("%#v != nil", av.Interface())
+		return
+	}
+	if !av.IsValid() && !bv.IsValid() {
+		return
+	}
+
+	at := av.Type()
+	bt := bv.Type()
+	if at != bt {
+		w.printf("%v != %v", at, bt)
+		return
+	}
+
+	// numeric types, including bool
+	if at.Kind() < reflect.Array {
+		a, b := av.Interface(), bv.Interface()
+		if a != b {
+			w.printf("%#v != %#v", a, b)
+		}
+		return
+	}
+
+	switch at.Kind() {
+	case reflect.String:
+		a, b := av.Interface(), bv.Interface()
+		if a != b {
+			w.printf("%q != %q", a, b)
+		}
+	case reflect.Ptr:
+		switch {
+		case av.IsNil() && !bv.IsNil():
+			w.printf("nil != %v", bv.Interface())
+		case !av.IsNil() && bv.IsNil():
+			w.printf("%v != nil", av.Interface())
+		case !av.IsNil() && !bv.IsNil():
+			w.diff(av.Elem(), bv.Elem())
+		}
+	case reflect.Struct:
+		for i := 0; i < av.NumField(); i++ {
+			w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i))
+		}
+	case reflect.Slice:
+		lenA := av.Len()
+		lenB := bv.Len()
+		if lenA != lenB {
+			w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB)
+			break
+		}
+		for i := 0; i < lenA; i++ {
+			w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i))
+		}
+	case reflect.Map:
+		ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys())
+		for _, k := range ak {
+			w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
+			w.printf("%q != (missing)", av.MapIndex(k))
+		}
+		for _, k := range both {
+			w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
+			w.diff(av.MapIndex(k), bv.MapIndex(k))
+		}
+		for _, k := range bk {
+			w := w.relabel(fmt.Sprintf("[%#v]", k.Interface()))
+			w.printf("(missing) != %q", bv.MapIndex(k))
+		}
+	case reflect.Interface:
+		w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface()))
+	default:
+		if !reflect.DeepEqual(av.Interface(), bv.Interface()) {
+			w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface()))
+		}
+	}
+}
+
+func (d diffWriter) relabel(name string) (d1 diffWriter) {
+	d1 = d
+	if d.l != "" && name[0] != '[' {
+		d1.l += "."
+	}
+	d1.l += name
+	return d1
+}
+
+func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) {
+	for _, av := range a {
+		inBoth := false
+		for _, bv := range b {
+			if reflect.DeepEqual(av.Interface(), bv.Interface()) {
+				inBoth = true
+				both = append(both, av)
+				break
+			}
+		}
+		if !inBoth {
+			ak = append(ak, av)
+		}
+	}
+	for _, bv := range b {
+		inBoth := false
+		for _, av := range a {
+			if reflect.DeepEqual(av.Interface(), bv.Interface()) {
+				inBoth = true
+				break
+			}
+		}
+		if !inBoth {
+			bk = append(bk, bv)
+		}
+	}
+	return
+}

+ 74 - 0
Godeps/_workspace/src/github.com/kr/pretty/diff_test.go

@@ -0,0 +1,74 @@
+package pretty
+
+import (
+	"testing"
+)
+
+type difftest struct {
+	a   interface{}
+	b   interface{}
+	exp []string
+}
+
+type S struct {
+	A int
+	S *S
+	I interface{}
+	C []int
+}
+
+var diffs = []difftest{
+	{a: nil, b: nil},
+	{a: S{A: 1}, b: S{A: 1}},
+
+	{0, "", []string{`int != string`}},
+	{0, 1, []string{`0 != 1`}},
+	{S{}, new(S), []string{`pretty.S != *pretty.S`}},
+	{"a", "b", []string{`"a" != "b"`}},
+	{S{}, S{A: 1}, []string{`A: 0 != 1`}},
+	{new(S), &S{A: 1}, []string{`A: 0 != 1`}},
+	{S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}},
+	{S{}, S{I: 0}, []string{`I: nil != 0`}},
+	{S{I: 1}, S{I: "x"}, []string{`I: int != string`}},
+	{S{}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
+	{S{C: []int{}}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}},
+	{S{C: []int{1, 2, 3}}, S{C: []int{1, 2, 4}}, []string{`C[2]: 3 != 4`}},
+	{S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 <nil> <nil> []}`}},
+}
+
+func TestDiff(t *testing.T) {
+	for _, tt := range diffs {
+		got := Diff(tt.a, tt.b)
+		eq := len(got) == len(tt.exp)
+		if eq {
+			for i := range got {
+				eq = eq && got[i] == tt.exp[i]
+			}
+		}
+		if !eq {
+			t.Errorf("diffing % #v", tt.a)
+			t.Errorf("with    % #v", tt.b)
+			diffdiff(t, got, tt.exp)
+			continue
+		}
+	}
+}
+
+func diffdiff(t *testing.T, got, exp []string) {
+	minus(t, "unexpected:", got, exp)
+	minus(t, "missing:", exp, got)
+}
+
+func minus(t *testing.T, s string, a, b []string) {
+	var i, j int
+	for i = 0; i < len(a); i++ {
+		for j = 0; j < len(b); j++ {
+			if a[i] == b[j] {
+				break
+			}
+		}
+		if j == len(b) {
+			t.Error(s, a[i])
+		}
+	}
+}

+ 20 - 0
Godeps/_workspace/src/github.com/kr/pretty/example_test.go

@@ -0,0 +1,20 @@
+package pretty_test
+
+import (
+	"fmt"
+	"github.com/kr/pretty"
+)
+
+func Example() {
+	type myType struct {
+		a, b int
+	}
+	var x = []myType{{1, 2}, {3, 4}, {5, 6}}
+	fmt.Printf("%# v", pretty.Formatter(x))
+	// output:
+	// []pretty_test.myType{
+	//     {a:1, b:2},
+	//     {a:3, b:4},
+	//     {a:5, b:6},
+	// }
+}

+ 337 - 0
Godeps/_workspace/src/github.com/kr/pretty/formatter.go

@@ -0,0 +1,337 @@
+package pretty
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+	"strconv"
+	"text/tabwriter"
+
+	"github.com/kr/text"
+)
+
+const (
+	limit = 50
+)
+
+type formatter struct {
+	x     interface{}
+	force bool
+	quote bool
+}
+
+// Formatter makes a wrapper, f, that will format x as go source with line
+// breaks and tabs. Object f responds to the "%v" formatting verb when both the
+// "#" and " " (space) flags are set, for example:
+//
+//     fmt.Sprintf("%# v", Formatter(x))
+//
+// If one of these two flags is not set, or any other verb is used, f will
+// format x according to the usual rules of package fmt.
+// In particular, if x satisfies fmt.Formatter, then x.Format will be called.
+func Formatter(x interface{}) (f fmt.Formatter) {
+	return formatter{x: x, quote: true}
+}
+
+func (fo formatter) String() string {
+	return fmt.Sprint(fo.x) // unwrap it
+}
+
+func (fo formatter) passThrough(f fmt.State, c rune) {
+	s := "%"
+	for i := 0; i < 128; i++ {
+		if f.Flag(i) {
+			s += string(i)
+		}
+	}
+	if w, ok := f.Width(); ok {
+		s += fmt.Sprintf("%d", w)
+	}
+	if p, ok := f.Precision(); ok {
+		s += fmt.Sprintf(".%d", p)
+	}
+	s += string(c)
+	fmt.Fprintf(f, s, fo.x)
+}
+
+func (fo formatter) Format(f fmt.State, c rune) {
+	if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') {
+		w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0)
+		p := &printer{tw: w, Writer: w, visited: make(map[visit]int)}
+		p.printValue(reflect.ValueOf(fo.x), true, fo.quote)
+		w.Flush()
+		return
+	}
+	fo.passThrough(f, c)
+}
+
+type printer struct {
+	io.Writer
+	tw      *tabwriter.Writer
+	visited map[visit]int
+	depth   int
+}
+
+func (p *printer) indent() *printer {
+	q := *p
+	q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0)
+	q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'})
+	return &q
+}
+
+func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) {
+	if showType {
+		io.WriteString(p, v.Type().String())
+		fmt.Fprintf(p, "(%#v)", x)
+	} else {
+		fmt.Fprintf(p, "%#v", x)
+	}
+}
+
+// printValue must keep track of already-printed pointer values to avoid
+// infinite recursion.
+type visit struct {
+	v   uintptr
+	typ reflect.Type
+}
+
+func (p *printer) printValue(v reflect.Value, showType, quote bool) {
+	if p.depth > 10 {
+		io.WriteString(p, "!%v(DEPTH EXCEEDED)")
+		return
+	}
+
+	switch v.Kind() {
+	case reflect.Bool:
+		p.printInline(v, v.Bool(), showType)
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		p.printInline(v, v.Int(), showType)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		p.printInline(v, v.Uint(), showType)
+	case reflect.Float32, reflect.Float64:
+		p.printInline(v, v.Float(), showType)
+	case reflect.Complex64, reflect.Complex128:
+		fmt.Fprintf(p, "%#v", v.Complex())
+	case reflect.String:
+		p.fmtString(v.String(), quote)
+	case reflect.Map:
+		t := v.Type()
+		if showType {
+			io.WriteString(p, t.String())
+		}
+		writeByte(p, '{')
+		if nonzero(v) {
+			expand := !canInline(v.Type())
+			pp := p
+			if expand {
+				writeByte(p, '\n')
+				pp = p.indent()
+			}
+			keys := v.MapKeys()
+			for i := 0; i < v.Len(); i++ {
+				showTypeInStruct := true
+				k := keys[i]
+				mv := v.MapIndex(k)
+				pp.printValue(k, false, true)
+				writeByte(pp, ':')
+				if expand {
+					writeByte(pp, '\t')
+				}
+				showTypeInStruct = t.Elem().Kind() == reflect.Interface
+				pp.printValue(mv, showTypeInStruct, true)
+				if expand {
+					io.WriteString(pp, ",\n")
+				} else if i < v.Len()-1 {
+					io.WriteString(pp, ", ")
+				}
+			}
+			if expand {
+				pp.tw.Flush()
+			}
+		}
+		writeByte(p, '}')
+	case reflect.Struct:
+		t := v.Type()
+		if v.CanAddr() {
+			addr := v.UnsafeAddr()
+			vis := visit{addr, t}
+			if vd, ok := p.visited[vis]; ok && vd < p.depth {
+				p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false)
+				break // don't print v again
+			}
+			p.visited[vis] = p.depth
+		}
+
+		if showType {
+			io.WriteString(p, t.String())
+		}
+		writeByte(p, '{')
+		if nonzero(v) {
+			expand := !canInline(v.Type())
+			pp := p
+			if expand {
+				writeByte(p, '\n')
+				pp = p.indent()
+			}
+			for i := 0; i < v.NumField(); i++ {
+				showTypeInStruct := true
+				if f := t.Field(i); f.Name != "" {
+					io.WriteString(pp, f.Name)
+					writeByte(pp, ':')
+					if expand {
+						writeByte(pp, '\t')
+					}
+					showTypeInStruct = labelType(f.Type)
+				}
+				pp.printValue(getField(v, i), showTypeInStruct, true)
+				if expand {
+					io.WriteString(pp, ",\n")
+				} else if i < v.NumField()-1 {
+					io.WriteString(pp, ", ")
+				}
+			}
+			if expand {
+				pp.tw.Flush()
+			}
+		}
+		writeByte(p, '}')
+	case reflect.Interface:
+		switch e := v.Elem(); {
+		case e.Kind() == reflect.Invalid:
+			io.WriteString(p, "nil")
+		case e.IsValid():
+			pp := *p
+			pp.depth++
+			pp.printValue(e, showType, true)
+		default:
+			io.WriteString(p, v.Type().String())
+			io.WriteString(p, "(nil)")
+		}
+	case reflect.Array, reflect.Slice:
+		t := v.Type()
+		if showType {
+			io.WriteString(p, t.String())
+		}
+		if v.Kind() == reflect.Slice && v.IsNil() && showType {
+			io.WriteString(p, "(nil)")
+			break
+		}
+		if v.Kind() == reflect.Slice && v.IsNil() {
+			io.WriteString(p, "nil")
+			break
+		}
+		writeByte(p, '{')
+		expand := !canInline(v.Type())
+		pp := p
+		if expand {
+			writeByte(p, '\n')
+			pp = p.indent()
+		}
+		for i := 0; i < v.Len(); i++ {
+			showTypeInSlice := t.Elem().Kind() == reflect.Interface
+			pp.printValue(v.Index(i), showTypeInSlice, true)
+			if expand {
+				io.WriteString(pp, ",\n")
+			} else if i < v.Len()-1 {
+				io.WriteString(pp, ", ")
+			}
+		}
+		if expand {
+			pp.tw.Flush()
+		}
+		writeByte(p, '}')
+	case reflect.Ptr:
+		e := v.Elem()
+		if !e.IsValid() {
+			writeByte(p, '(')
+			io.WriteString(p, v.Type().String())
+			io.WriteString(p, ")(nil)")
+		} else {
+			pp := *p
+			pp.depth++
+			writeByte(pp, '&')
+			pp.printValue(e, true, true)
+		}
+	case reflect.Chan:
+		x := v.Pointer()
+		if showType {
+			writeByte(p, '(')
+			io.WriteString(p, v.Type().String())
+			fmt.Fprintf(p, ")(%#v)", x)
+		} else {
+			fmt.Fprintf(p, "%#v", x)
+		}
+	case reflect.Func:
+		io.WriteString(p, v.Type().String())
+		io.WriteString(p, " {...}")
+	case reflect.UnsafePointer:
+		p.printInline(v, v.Pointer(), showType)
+	case reflect.Invalid:
+		io.WriteString(p, "nil")
+	}
+}
+
+func canInline(t reflect.Type) bool {
+	switch t.Kind() {
+	case reflect.Map:
+		return !canExpand(t.Elem())
+	case reflect.Struct:
+		for i := 0; i < t.NumField(); i++ {
+			if canExpand(t.Field(i).Type) {
+				return false
+			}
+		}
+		return true
+	case reflect.Interface:
+		return false
+	case reflect.Array, reflect.Slice:
+		return !canExpand(t.Elem())
+	case reflect.Ptr:
+		return false
+	case reflect.Chan, reflect.Func, reflect.UnsafePointer:
+		return false
+	}
+	return true
+}
+
+func canExpand(t reflect.Type) bool {
+	switch t.Kind() {
+	case reflect.Map, reflect.Struct,
+		reflect.Interface, reflect.Array, reflect.Slice,
+		reflect.Ptr:
+		return true
+	}
+	return false
+}
+
+func labelType(t reflect.Type) bool {
+	switch t.Kind() {
+	case reflect.Interface, reflect.Struct:
+		return true
+	}
+	return false
+}
+
+func (p *printer) fmtString(s string, quote bool) {
+	if quote {
+		s = strconv.Quote(s)
+	}
+	io.WriteString(p, s)
+}
+
+func tryDeepEqual(a, b interface{}) bool {
+	defer func() { recover() }()
+	return reflect.DeepEqual(a, b)
+}
+
+func writeByte(w io.Writer, b byte) {
+	w.Write([]byte{b})
+}
+
+func getField(v reflect.Value, i int) reflect.Value {
+	val := v.Field(i)
+	if val.Kind() == reflect.Interface && !val.IsNil() {
+		val = val.Elem()
+	}
+	return val
+}

+ 261 - 0
Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go

@@ -0,0 +1,261 @@
+package pretty
+
+import (
+	"fmt"
+	"io"
+	"strings"
+	"testing"
+	"unsafe"
+)
+
+type test struct {
+	v interface{}
+	s string
+}
+
+type LongStructTypeName struct {
+	longFieldName      interface{}
+	otherLongFieldName interface{}
+}
+
+type SA struct {
+	t *T
+	v T
+}
+
+type T struct {
+	x, y int
+}
+
+type F int
+
+func (f F) Format(s fmt.State, c rune) {
+	fmt.Fprintf(s, "F(%d)", int(f))
+}
+
+var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+var gosyntax = []test{
+	{nil, `nil`},
+	{"", `""`},
+	{"a", `"a"`},
+	{1, "int(1)"},
+	{1.0, "float64(1)"},
+	{[]int(nil), "[]int(nil)"},
+	{[0]int{}, "[0]int{}"},
+	{complex(1, 0), "(1+0i)"},
+	//{make(chan int), "(chan int)(0x1234)"},
+	{unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))},
+	{func(int) {}, "func(int) {...}"},
+	{map[int]int{1: 1}, "map[int]int{1:1}"},
+	{int32(1), "int32(1)"},
+	{io.EOF, `&errors.errorString{s:"EOF"}`},
+	{[]string{"a"}, `[]string{"a"}`},
+	{
+		[]string{long},
+		`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
+	},
+	{F(5), "pretty.F(5)"},
+	{
+		SA{&T{1, 2}, T{3, 4}},
+		`pretty.SA{
+    t:  &pretty.T{x:1, y:2},
+    v:  pretty.T{x:3, y:4},
+}`,
+	},
+	{
+		map[int][]byte{1: {}},
+		`map[int][]uint8{
+    1:  {},
+}`,
+	},
+	{
+		map[int]T{1: {}},
+		`map[int]pretty.T{
+    1:  {},
+}`,
+	},
+	{
+		long,
+		`"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`,
+	},
+	{
+		LongStructTypeName{
+			longFieldName:      LongStructTypeName{},
+			otherLongFieldName: long,
+		},
+		`pretty.LongStructTypeName{
+    longFieldName:      pretty.LongStructTypeName{},
+    otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+}`,
+	},
+	{
+		&LongStructTypeName{
+			longFieldName:      &LongStructTypeName{},
+			otherLongFieldName: (*LongStructTypeName)(nil),
+		},
+		`&pretty.LongStructTypeName{
+    longFieldName:      &pretty.LongStructTypeName{},
+    otherLongFieldName: (*pretty.LongStructTypeName)(nil),
+}`,
+	},
+	{
+		[]LongStructTypeName{
+			{nil, nil},
+			{3, 3},
+			{long, nil},
+		},
+		`[]pretty.LongStructTypeName{
+    {},
+    {
+        longFieldName:      int(3),
+        otherLongFieldName: int(3),
+    },
+    {
+        longFieldName:      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+        otherLongFieldName: nil,
+    },
+}`,
+	},
+	{
+		[]interface{}{
+			LongStructTypeName{nil, nil},
+			[]byte{1, 2, 3},
+			T{3, 4},
+			LongStructTypeName{long, nil},
+		},
+		`[]interface {}{
+    pretty.LongStructTypeName{},
+    []uint8{0x1, 0x2, 0x3},
+    pretty.T{x:3, y:4},
+    pretty.LongStructTypeName{
+        longFieldName:      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
+        otherLongFieldName: nil,
+    },
+}`,
+	},
+}
+
+func TestGoSyntax(t *testing.T) {
+	for _, tt := range gosyntax {
+		s := fmt.Sprintf("%# v", Formatter(tt.v))
+		if tt.s != s {
+			t.Errorf("expected %q", tt.s)
+			t.Errorf("got      %q", s)
+			t.Errorf("expraw\n%s", tt.s)
+			t.Errorf("gotraw\n%s", s)
+		}
+	}
+}
+
+type I struct {
+	i int
+	R interface{}
+}
+
+func (i *I) I() *I { return i.R.(*I) }
+
+func TestCycle(t *testing.T) {
+	type A struct{ *A }
+	v := &A{}
+	v.A = v
+
+	// panics from stack overflow without cycle detection
+	t.Logf("Example cycle:\n%# v", Formatter(v))
+
+	p := &A{}
+	s := fmt.Sprintf("%# v", Formatter([]*A{p, p}))
+	if strings.Contains(s, "CYCLIC") {
+		t.Errorf("Repeated address detected as cyclic reference:\n%s", s)
+	}
+
+	type R struct {
+		i int
+		*R
+	}
+	r := &R{
+		i: 1,
+		R: &R{
+			i: 2,
+			R: &R{
+				i: 3,
+			},
+		},
+	}
+	r.R.R.R = r
+	t.Logf("Example longer cycle:\n%# v", Formatter(r))
+
+	r = &R{
+		i: 1,
+		R: &R{
+			i: 2,
+			R: &R{
+				i: 3,
+				R: &R{
+					i: 4,
+					R: &R{
+						i: 5,
+						R: &R{
+							i: 6,
+							R: &R{
+								i: 7,
+								R: &R{
+									i: 8,
+									R: &R{
+										i: 9,
+										R: &R{
+											i: 10,
+											R: &R{
+												i: 11,
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	// here be pirates
+	r.R.R.R.R.R.R.R.R.R.R.R = r
+	t.Logf("Example very long cycle:\n%# v", Formatter(r))
+
+	i := &I{
+		i: 1,
+		R: &I{
+			i: 2,
+			R: &I{
+				i: 3,
+				R: &I{
+					i: 4,
+					R: &I{
+						i: 5,
+						R: &I{
+							i: 6,
+							R: &I{
+								i: 7,
+								R: &I{
+									i: 8,
+									R: &I{
+										i: 9,
+										R: &I{
+											i: 10,
+											R: &I{
+												i: 11,
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+	iv := i.I().I().I().I().I().I().I().I().I().I()
+	*iv = *i
+	t.Logf("Example long interface cycle:\n%# v", Formatter(i))
+}

+ 98 - 0
Godeps/_workspace/src/github.com/kr/pretty/pretty.go

@@ -0,0 +1,98 @@
+// Package pretty provides pretty-printing for Go values. This is
+// useful during debugging, to avoid wrapping long output lines in
+// the terminal.
+//
+// It provides a function, Formatter, that can be used with any
+// function that accepts a format string. It also provides
+// convenience wrappers for functions in packages fmt and log.
+package pretty
+
+import (
+	"fmt"
+	"io"
+	"log"
+)
+
+// Errorf is a convenience wrapper for fmt.Errorf.
+//
+// Calling Errorf(f, x, y) is equivalent to
+// fmt.Errorf(f, Formatter(x), Formatter(y)).
+func Errorf(format string, a ...interface{}) error {
+	return fmt.Errorf(format, wrap(a, false)...)
+}
+
+// Fprintf is a convenience wrapper for fmt.Fprintf.
+//
+// Calling Fprintf(w, f, x, y) is equivalent to
+// fmt.Fprintf(w, f, Formatter(x), Formatter(y)).
+func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) {
+	return fmt.Fprintf(w, format, wrap(a, false)...)
+}
+
+// Log is a convenience wrapper for log.Printf.
+//
+// Calling Log(x, y) is equivalent to
+// log.Print(Formatter(x), Formatter(y)), but each operand is
+// formatted with "%# v".
+func Log(a ...interface{}) {
+	log.Print(wrap(a, true)...)
+}
+
+// Logf is a convenience wrapper for log.Printf.
+//
+// Calling Logf(f, x, y) is equivalent to
+// log.Printf(f, Formatter(x), Formatter(y)).
+func Logf(format string, a ...interface{}) {
+	log.Printf(format, wrap(a, false)...)
+}
+
+// Logln is a convenience wrapper for log.Printf.
+//
+// Calling Logln(x, y) is equivalent to
+// log.Println(Formatter(x), Formatter(y)), but each operand is
+// formatted with "%# v".
+func Logln(a ...interface{}) {
+	log.Println(wrap(a, true)...)
+}
+
+// Print pretty-prints its operands and writes to standard output.
+//
+// Calling Print(x, y) is equivalent to
+// fmt.Print(Formatter(x), Formatter(y)), but each operand is
+// formatted with "%# v".
+func Print(a ...interface{}) (n int, errno error) {
+	return fmt.Print(wrap(a, true)...)
+}
+
+// Printf is a convenience wrapper for fmt.Printf.
+//
+// Calling Printf(f, x, y) is equivalent to
+// fmt.Printf(f, Formatter(x), Formatter(y)).
+func Printf(format string, a ...interface{}) (n int, errno error) {
+	return fmt.Printf(format, wrap(a, false)...)
+}
+
+// Println pretty-prints its operands and writes to standard output.
+//
+// Calling Print(x, y) is equivalent to
+// fmt.Println(Formatter(x), Formatter(y)), but each operand is
+// formatted with "%# v".
+func Println(a ...interface{}) (n int, errno error) {
+	return fmt.Println(wrap(a, true)...)
+}
+
+// Sprintf is a convenience wrapper for fmt.Sprintf.
+//
+// Calling Sprintf(f, x, y) is equivalent to
+// fmt.Sprintf(f, Formatter(x), Formatter(y)).
+func Sprintf(format string, a ...interface{}) string {
+	return fmt.Sprintf(format, wrap(a, false)...)
+}
+
+func wrap(a []interface{}, force bool) []interface{} {
+	w := make([]interface{}, len(a))
+	for i, x := range a {
+		w[i] = formatter{x: x, force: force}
+	}
+	return w
+}

+ 41 - 0
Godeps/_workspace/src/github.com/kr/pretty/zero.go

@@ -0,0 +1,41 @@
+package pretty
+
+import (
+	"reflect"
+)
+
+func nonzero(v reflect.Value) bool {
+	switch v.Kind() {
+	case reflect.Bool:
+		return v.Bool()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return v.Int() != 0
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+		return v.Uint() != 0
+	case reflect.Float32, reflect.Float64:
+		return v.Float() != 0
+	case reflect.Complex64, reflect.Complex128:
+		return v.Complex() != complex(0, 0)
+	case reflect.String:
+		return v.String() != ""
+	case reflect.Struct:
+		for i := 0; i < v.NumField(); i++ {
+			if nonzero(getField(v, i)) {
+				return true
+			}
+		}
+		return false
+	case reflect.Array:
+		for i := 0; i < v.Len(); i++ {
+			if nonzero(v.Index(i)) {
+				return true
+			}
+		}
+		return false
+	case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func:
+		return !v.IsNil()
+	case reflect.UnsafePointer:
+		return v.Pointer() != 0
+	}
+	return true
+}

+ 19 - 0
Godeps/_workspace/src/github.com/kr/text/License

@@ -0,0 +1,19 @@
+Copyright 2012 Keith Rarick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 3 - 0
Godeps/_workspace/src/github.com/kr/text/Readme

@@ -0,0 +1,3 @@
+This is a Go package for manipulating paragraphs of text.
+
+See http://go.pkgdoc.org/github.com/kr/text for full documentation.

+ 5 - 0
Godeps/_workspace/src/github.com/kr/text/colwriter/Readme

@@ -0,0 +1,5 @@
+Package colwriter provides a write filter that formats
+input lines in multiple columns.
+
+The package is a straightforward translation from
+/src/cmd/draw/mc.c in Plan 9 from User Space.

+ 147 - 0
Godeps/_workspace/src/github.com/kr/text/colwriter/column.go

@@ -0,0 +1,147 @@
+// Package colwriter provides a write filter that formats
+// input lines in multiple columns.
+//
+// The package is a straightforward translation from
+// /src/cmd/draw/mc.c in Plan 9 from User Space.
+package colwriter
+
+import (
+	"bytes"
+	"io"
+	"unicode/utf8"
+)
+
+const (
+	tab = 4
+)
+
+const (
+	// Print each input line ending in a colon ':' separately.
+	BreakOnColon uint = 1 << iota
+)
+
+// A Writer is a filter that arranges input lines in as many columns as will
+// fit in its width. Tab '\t' chars in the input are translated to sequences
+// of spaces ending at multiples of 4 positions.
+//
+// If BreakOnColon is set, each input line ending in a colon ':' is written
+// separately.
+//
+// The Writer assumes that all Unicode code points have the same width; this
+// may not be true in some fonts.
+type Writer struct {
+	w     io.Writer
+	buf   []byte
+	width int
+	flag  uint
+}
+
+// NewWriter allocates and initializes a new Writer writing to w.
+// Parameter width controls the total number of characters on each line
+// across all columns.
+func NewWriter(w io.Writer, width int, flag uint) *Writer {
+	return &Writer{
+		w:     w,
+		width: width,
+		flag:  flag,
+	}
+}
+
+// Write writes p to the writer w. The only errors returned are ones
+// encountered while writing to the underlying output stream.
+func (w *Writer) Write(p []byte) (n int, err error) {
+	var linelen int
+	var lastWasColon bool
+	for i, c := range p {
+		w.buf = append(w.buf, c)
+		linelen++
+		if c == '\t' {
+			w.buf[len(w.buf)-1] = ' '
+			for linelen%tab != 0 {
+				w.buf = append(w.buf, ' ')
+				linelen++
+			}
+		}
+		if w.flag&BreakOnColon != 0 && c == ':' {
+			lastWasColon = true
+		} else if lastWasColon {
+			if c == '\n' {
+				pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'})
+				if pos < 0 {
+					pos = 0
+				}
+				line := w.buf[pos:]
+				w.buf = w.buf[:pos]
+				if err = w.columnate(); err != nil {
+					if len(line) < i {
+						return i - len(line), err
+					}
+					return 0, err
+				}
+				if n, err := w.w.Write(line); err != nil {
+					if r := len(line) - n; r < i {
+						return i - r, err
+					}
+					return 0, err
+				}
+			}
+			lastWasColon = false
+		}
+		if c == '\n' {
+			linelen = 0
+		}
+	}
+	return len(p), nil
+}
+
+// Flush should be called after the last call to Write to ensure that any data
+// buffered in the Writer is written to output.
+func (w *Writer) Flush() error {
+	return w.columnate()
+}
+
+func (w *Writer) columnate() error {
+	words := bytes.Split(w.buf, []byte{'\n'})
+	w.buf = nil
+	if len(words[len(words)-1]) == 0 {
+		words = words[:len(words)-1]
+	}
+	maxwidth := 0
+	for _, wd := range words {
+		if n := utf8.RuneCount(wd); n > maxwidth {
+			maxwidth = n
+		}
+	}
+	maxwidth++ // space char
+	wordsPerLine := w.width / maxwidth
+	if wordsPerLine <= 0 {
+		wordsPerLine = 1
+	}
+	nlines := (len(words) + wordsPerLine - 1) / wordsPerLine
+	for i := 0; i < nlines; i++ {
+		col := 0
+		endcol := 0
+		for j := i; j < len(words); j += nlines {
+			endcol += maxwidth
+			_, err := w.w.Write(words[j])
+			if err != nil {
+				return err
+			}
+			col += utf8.RuneCount(words[j])
+			if j+nlines < len(words) {
+				for col < endcol {
+					_, err := w.w.Write([]byte{' '})
+					if err != nil {
+						return err
+					}
+					col++
+				}
+			}
+		}
+		_, err := w.w.Write([]byte{'\n'})
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}

+ 90 - 0
Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go

@@ -0,0 +1,90 @@
+package colwriter
+
+import (
+	"bytes"
+	"testing"
+)
+
+var src = `
+.git
+.gitignore
+.godir
+Procfile:
+README.md
+api.go
+apps.go
+auth.go
+darwin.go
+data.go
+dyno.go:
+env.go
+git.go
+help.go
+hkdist
+linux.go
+ls.go
+main.go
+plugin.go
+run.go
+scale.go
+ssh.go
+tail.go
+term
+unix.go
+update.go
+version.go
+windows.go
+`[1:]
+
+var tests = []struct {
+	wid  int
+	flag uint
+	src  string
+	want string
+}{
+	{80, 0, "", ""},
+	{80, 0, src, `
+.git       README.md  darwin.go  git.go     ls.go      scale.go   unix.go
+.gitignore api.go     data.go    help.go    main.go    ssh.go     update.go
+.godir     apps.go    dyno.go:   hkdist     plugin.go  tail.go    version.go
+Procfile:  auth.go    env.go     linux.go   run.go     term       windows.go
+`[1:]},
+	{80, BreakOnColon, src, `
+.git       .gitignore .godir
+
+Procfile:
+README.md api.go    apps.go   auth.go   darwin.go data.go
+
+dyno.go:
+env.go     hkdist     main.go    scale.go   term       version.go
+git.go     linux.go   plugin.go  ssh.go     unix.go    windows.go
+help.go    ls.go      run.go     tail.go    update.go
+`[1:]},
+	{20, 0, `
+Hello
+Γειά σου
+안녕
+今日は
+`[1:], `
+Hello    안녕
+Γειά σου 今日は
+`[1:]},
+}
+
+func TestWriter(t *testing.T) {
+	for _, test := range tests {
+		b := new(bytes.Buffer)
+		w := NewWriter(b, test.wid, test.flag)
+		if _, err := w.Write([]byte(test.src)); err != nil {
+			t.Error(err)
+		}
+		if err := w.Flush(); err != nil {
+			t.Error(err)
+		}
+		if g := b.String(); test.want != g {
+			t.Log("\n" + test.want)
+			t.Log("\n" + g)
+			t.Errorf("%q != %q", test.want, g)
+		}
+	}
+}

+ 3 - 0
Godeps/_workspace/src/github.com/kr/text/doc.go

@@ -0,0 +1,3 @@
+// Package text provides rudimentary functions for manipulating text in
+// paragraphs.
+package text

+ 74 - 0
Godeps/_workspace/src/github.com/kr/text/indent.go

@@ -0,0 +1,74 @@
+package text
+
+import (
+	"io"
+)
+
+// Indent inserts prefix at the beginning of each non-empty line of s. The
+// end-of-line marker is NL.
+func Indent(s, prefix string) string {
+	return string(IndentBytes([]byte(s), []byte(prefix)))
+}
+
+// IndentBytes inserts prefix at the beginning of each non-empty line of b.
+// The end-of-line marker is NL.
+func IndentBytes(b, prefix []byte) []byte {
+	var res []byte
+	bol := true
+	for _, c := range b {
+		if bol && c != '\n' {
+			res = append(res, prefix...)
+		}
+		res = append(res, c)
+		bol = c == '\n'
+	}
+	return res
+}
+
+// Writer indents each line of its input.
+type indentWriter struct {
+	w   io.Writer
+	bol bool
+	pre [][]byte
+	sel int
+	off int
+}
+
+// NewIndentWriter makes a new write filter that indents the input
+// lines. Each line is prefixed in order with the corresponding
+// element of pre. If there are more lines than elements, the last
+// element of pre is repeated for each subsequent line.
+func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer {
+	return &indentWriter{
+		w:   w,
+		pre: pre,
+		bol: true,
+	}
+}
+
+// The only errors returned are from the underlying indentWriter.
+func (w *indentWriter) Write(p []byte) (n int, err error) {
+	for _, c := range p {
+		if w.bol {
+			var i int
+			i, err = w.w.Write(w.pre[w.sel][w.off:])
+			w.off += i
+			if err != nil {
+				return n, err
+			}
+		}
+		_, err = w.w.Write([]byte{c})
+		if err != nil {
+			return n, err
+		}
+		n++
+		w.bol = c == '\n'
+		if w.bol {
+			w.off = 0
+			if w.sel < len(w.pre)-1 {
+				w.sel++
+			}
+		}
+	}
+	return n, nil
+}

+ 119 - 0
Godeps/_workspace/src/github.com/kr/text/indent_test.go

@@ -0,0 +1,119 @@
+package text
+
+import (
+	"bytes"
+	"testing"
+)
+
+type T struct {
+	inp, exp, pre string
+}
+
+var tests = []T{
+	{
+		"The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n",
+		"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n",
+		"xxx",
+	},
+	{
+		"The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.",
+		"xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.",
+		"xxx",
+	},
+}
+
+func TestIndent(t *testing.T) {
+	for _, test := range tests {
+		got := Indent(test.inp, test.pre)
+		if got != test.exp {
+			t.Errorf("mismatch %q != %q", got, test.exp)
+		}
+	}
+}
+
+type IndentWriterTest struct {
+	inp, exp string
+	pre      []string
+}
+
+var ts = []IndentWriterTest{
+	{
+		`
+The quick brown fox
+jumps over the lazy
+dog.
+But not quickly.
+`[1:],
+		`
+xxxThe quick brown fox
+xxxjumps over the lazy
+xxxdog.
+xxxBut not quickly.
+`[1:],
+		[]string{"xxx"},
+	},
+	{
+		`
+The quick brown fox
+jumps over the lazy
+dog.
+But not quickly.
+`[1:],
+		`
+xxaThe quick brown fox
+xxxjumps over the lazy
+xxxdog.
+xxxBut not quickly.
+`[1:],
+		[]string{"xxa", "xxx"},
+	},
+	{
+		`
+The quick brown fox
+jumps over the lazy
+dog.
+But not quickly.
+`[1:],
+		`
+xxaThe quick brown fox
+xxbjumps over the lazy
+xxcdog.
+xxxBut not quickly.
+`[1:],
+		[]string{"xxa", "xxb", "xxc", "xxx"},
+	},
+	{
+		`
+The quick brown fox
+jumps over the lazy
+dog.
+
+But not quickly.`[1:],
+		`
+xxaThe quick brown fox
+xxxjumps over the lazy
+xxxdog.
+xxx
+xxxBut not quickly.`[1:],
+		[]string{"xxa", "xxx"},
+	},
+}
+
+func TestIndentWriter(t *testing.T) {
+	for _, test := range ts {
+		b := new(bytes.Buffer)
+		pre := make([][]byte, len(test.pre))
+		for i := range test.pre {
+			pre[i] = []byte(test.pre[i])
+		}
+		w := NewIndentWriter(b, pre...)
+		if _, err := w.Write([]byte(test.inp)); err != nil {
+			t.Error(err)
+		}
+		if got := b.String(); got != test.exp {
+			t.Errorf("mismatch %q != %q", got, test.exp)
+			t.Log(got)
+			t.Log(test.exp)
+		}
+	}
+}

+ 9 - 0
Godeps/_workspace/src/github.com/kr/text/mc/Readme

@@ -0,0 +1,9 @@
+Command mc prints in multiple columns.
+
+  Usage: mc [-] [-N] [file...]
+
+Mc splits the input into as many columns as will fit in N
+print positions. If the output is a tty, the default N is
+the number of characters in a terminal line; otherwise the
+default N is 80. Under option - each input line ending in
+a colon ':' is printed separately.

+ 62 - 0
Godeps/_workspace/src/github.com/kr/text/mc/mc.go

@@ -0,0 +1,62 @@
+// Command mc prints in multiple columns.
+//
+//   Usage: mc [-] [-N] [file...]
+//
+// Mc splits the input into as many columns as will fit in N
+// print positions. If the output is a tty, the default N is
+// the number of characters in a terminal line; otherwise the
+// default N is 80. Under option - each input line ending in
+// a colon ':' is printed separately.
+package main
+
+import (
+	"github.com/kr/pty"
+	"github.com/kr/text/colwriter"
+	"io"
+	"log"
+	"os"
+	"strconv"
+)
+
+func main() {
+	var width int
+	var flag uint
+	args := os.Args[1:]
+	for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' {
+		if len(args[0]) > 1 {
+			width, _ = strconv.Atoi(args[0][1:])
+		} else {
+			flag |= colwriter.BreakOnColon
+		}
+		args = args[1:]
+	}
+	if width < 1 {
+		_, width, _ = pty.Getsize(os.Stdout)
+	}
+	if width < 1 {
+		width = 80
+	}
+
+	w := colwriter.NewWriter(os.Stdout, width, flag)
+	if len(args) > 0 {
+		for _, s := range args {
+			if f, err := os.Open(s); err == nil {
+				copyin(w, f)
+				f.Close()
+			} else {
+				log.Println(err)
+			}
+		}
+	} else {
+		copyin(w, os.Stdin)
+	}
+}
+
+func copyin(w *colwriter.Writer, r io.Reader) {
+	if _, err := io.Copy(w, r); err != nil {
+		log.Println(err)
+	}
+	if err := w.Flush(); err != nil {
+		log.Println(err)
+	}
+}

+ 86 - 0
Godeps/_workspace/src/github.com/kr/text/wrap.go

@@ -0,0 +1,86 @@
+package text
+
+import (
+	"bytes"
+	"math"
+)
+
+var (
+	nl = []byte{'\n'}
+	sp = []byte{' '}
+)
+
+const defaultPenalty = 1e5
+
+// Wrap wraps s into a paragraph of lines of length lim, with minimal
+// raggedness.
+func Wrap(s string, lim int) string {
+	return string(WrapBytes([]byte(s), lim))
+}
+
+// WrapBytes wraps b into a paragraph of lines of length lim, with minimal
+// raggedness.
+func WrapBytes(b []byte, lim int) []byte {
+	words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp)
+	var lines [][]byte
+	for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
+		lines = append(lines, bytes.Join(line, sp))
+	}
+	return bytes.Join(lines, nl)
+}
+
+// WrapWords is the low-level line-breaking algorithm, useful if you need more
+// control over the details of the text wrapping process. For most uses, either
+// Wrap or WrapBytes will be sufficient and more convenient.
+//
+// WrapWords splits a list of words into lines with minimal "raggedness",
+// treating each byte as one unit, accounting for spc units between adjacent
+// words on each line, and attempting to limit lines to lim units. Raggedness
+// is the total error over all lines, where error is the square of the
+// difference of the length of the line and lim. Too-long lines (which only
+// happen when a single word is longer than lim units) have pen penalty units
+// added to the error.
+func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte {
+	n := len(words)
+
+	length := make([][]int, n)
+	for i := 0; i < n; i++ {
+		length[i] = make([]int, n)
+		length[i][i] = len(words[i])
+		for j := i + 1; j < n; j++ {
+			length[i][j] = length[i][j-1] + spc + len(words[j])
+		}
+	}
+
+	nbrk := make([]int, n)
+	cost := make([]int, n)
+	for i := range cost {
+		cost[i] = math.MaxInt32
+	}
+	for i := n - 1; i >= 0; i-- {
+		if length[i][n-1] <= lim || i == n-1 {
+			cost[i] = 0
+			nbrk[i] = n
+		} else {
+			for j := i + 1; j < n; j++ {
+				d := lim - length[i][j-1]
+				c := d*d + cost[j]
+				if length[i][j-1] > lim {
+					c += pen // too-long lines get a worse penalty
+				}
+				if c < cost[i] {
+					cost[i] = c
+					nbrk[i] = j
+				}
+			}
+		}
+	}
+
+	var lines [][][]byte
+	i := 0
+	for i < n {
+		lines = append(lines, words[i:nbrk[i]])
+		i = nbrk[i]
+	}
+	return lines
+}

+ 62 - 0
Godeps/_workspace/src/github.com/kr/text/wrap_test.go

@@ -0,0 +1,62 @@
+package text
+
+import (
+	"bytes"
+	"testing"
+)
+
+var text = "The quick brown fox jumps over the lazy dog."
+
+func TestWrap(t *testing.T) {
+	exp := [][]string{
+		{"The", "quick", "brown", "fox"},
+		{"jumps", "over", "the", "lazy", "dog."},
+	}
+	words := bytes.Split([]byte(text), sp)
+	got := WrapWords(words, 1, 24, defaultPenalty)
+	if len(exp) != len(got) {
+		t.Fail()
+	}
+	for i := range exp {
+		if len(exp[i]) != len(got[i]) {
+			t.Fail()
+		}
+		for j := range exp[i] {
+			if exp[i][j] != string(got[i][j]) {
+				t.Fatal(i, exp[i][j], got[i][j])
+			}
+		}
+	}
+}
+
+func TestWrapNarrow(t *testing.T) {
+	exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog."
+	if Wrap(text, 5) != exp {
+		t.Fail()
+	}
+}
+
+func TestWrapOneLine(t *testing.T) {
+	exp := "The quick brown fox jumps over the lazy dog."
+	if Wrap(text, 500) != exp {
+		t.Fail()
+	}
+}
+
+func TestWrapBug1(t *testing.T) {
+	cases := []struct {
+		limit int
+		text  string
+		want  string
+	}{
+		{4, "aaaaa", "aaaaa"},
+		{4, "a aaaaa", "a\naaaaa"},
+	}
+
+	for _, test := range cases {
+		got := Wrap(test.text, test.limit)
+		if got != test.want {
+			t.Errorf("Wrap(%q, %d) = %q want %q", test.text, test.limit, got, test.want)
+		}
+	}
+}

+ 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 - 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{}{}

+ 0 - 2
pkg/api/dashboard_snapshot.go

@@ -53,7 +53,6 @@ func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapsho
 }
 
 func GetDashboardSnapshot(c *middleware.Context) {
-
 	key := c.Params(":key")
 	query := &m.GetDashboardSnapshotQuery{Key: key}
 
@@ -136,5 +135,4 @@ func SearchDashboardSnapshots(c *middleware.Context) Response {
 	}
 
 	return Json(200, dtos)
-	//return Json(200, searchQuery.Result)
 }

+ 18 - 17
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"
 )
@@ -52,26 +53,26 @@ type DashboardMeta struct {
 }
 
 type DashboardFullWithMeta struct {
-	Meta      DashboardMeta          `json:"meta"`
-	Dashboard map[string]interface{} `json:"dashboard"`
+	Meta      DashboardMeta    `json:"meta"`
+	Dashboard *simplejson.Json `json:"dashboard"`
 }
 
 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 {

+ 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"`
 }

+ 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
 		}
 

+ 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,

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

@@ -0,0 +1,817 @@
+// 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
+}
+
+func (v *Value) StringMap() map[string]interface{} {
+	return v.data.(map[string]interface{})
+}
+
+// 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")
+	})
+}

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

@@ -0,0 +1,468 @@
+package simplejson
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"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) {
+	if j == nil || j.data == nil {
+		return nil, nil
+	}
+
+	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{}),
+	}
+}
+
+// New returns a pointer to a new, empty `Json` object
+func NewFromAny(data interface{}) *Json {
+	return &Json{data: data}
+}
+
+// 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) {
+	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
+}

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

@@ -0,0 +1,89 @@
+// +build go1.1
+
+package simplejson
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"io"
+	"reflect"
+	"strconv"
+)
+
+// Implements the json.Unmarshaler interface.
+func (j *Json) UnmarshalJSON(p []byte) error {
+	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)
+}

+ 9 - 5
pkg/models/dashboard_snapshot.go

@@ -1,6 +1,10 @@
 package models
 
-import "time"
+import (
+	"time"
+
+	"github.com/grafana/grafana/pkg/components/simplejson"
+)
 
 // DashboardSnapshot model
 type DashboardSnapshot struct {
@@ -17,7 +21,7 @@ type DashboardSnapshot struct {
 	Created time.Time
 	Updated time.Time
 
-	Dashboard map[string]interface{}
+	Dashboard *simplejson.Json
 }
 
 // DashboardSnapshotDTO without dashboard map
@@ -40,9 +44,9 @@ type DashboardSnapshotDTO struct {
 // COMMANDS
 
 type CreateDashboardSnapshotCommand struct {
-	Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
-	Name      string                 `json:"name" binding:"Required"`
-	Expires   int64                  `json:"expires"`
+	Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
+	Name      string           `json:"name" binding:"Required"`
+	Expires   int64            `json:"expires"`
 
 	// these are passed when storing an external snapshot ref
 	External  bool   `json:"external"`

+ 21 - 31
pkg/models/dashboards.go

@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/gosimple/slug"
+	"github.com/grafana/grafana/pkg/components/simplejson"
 )
 
 // Typed errors
@@ -37,14 +38,14 @@ type Dashboard struct {
 	CreatedBy int64
 
 	Title string
-	Data  map[string]interface{}
+	Data  *simplejson.Json
 }
 
 // NewDashboard creates a new dashboard
 func NewDashboard(title string) *Dashboard {
 	dash := &Dashboard{}
-	dash.Data = make(map[string]interface{})
-	dash.Data["title"] = title
+	dash.Data = simplejson.New()
+	dash.Data.Set("title", title)
 	dash.Title = title
 	dash.Created = time.Now()
 	dash.Updated = time.Now()
@@ -54,34 +55,24 @@ func NewDashboard(title string) *Dashboard {
 
 // GetTags turns the tags in data json into go string array
 func (dash *Dashboard) GetTags() []string {
-	jsonTags := dash.Data["tags"]
-	if jsonTags == nil || jsonTags == "" {
-		return []string{}
-	}
-
-	arr := jsonTags.([]interface{})
-	b := make([]string, len(arr))
-	for i := range arr {
-		b[i] = arr[i].(string)
-	}
-	return b
+	return dash.Data.Get("tags").MustStringArray()
 }
 
-func NewDashboardFromJson(data map[string]interface{}) *Dashboard {
+func NewDashboardFromJson(data *simplejson.Json) *Dashboard {
 	dash := &Dashboard{}
 	dash.Data = data
-	dash.Title = dash.Data["title"].(string)
+	dash.Title = dash.Data.Get("title").MustString()
 	dash.UpdateSlug()
 
-	if dash.Data["id"] != nil {
-		dash.Id = int64(dash.Data["id"].(float64))
+	if id, err := dash.Data.Get("id").Float64(); err == nil {
+		dash.Id = int64(id)
 
-		if dash.Data["version"] != nil {
-			dash.Version = int(dash.Data["version"].(float64))
+		if version, err := dash.Data.Get("version").Float64(); err == nil {
+			dash.Version = int(version)
 			dash.Updated = time.Now()
 		}
 	} else {
-		dash.Data["version"] = 0
+		dash.Data.Set("version", 0)
 		dash.Created = time.Now()
 		dash.Updated = time.Now()
 	}
@@ -92,9 +83,11 @@ func NewDashboardFromJson(data map[string]interface{}) *Dashboard {
 // GetDashboardModel turns the command into the savable model
 func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
 	dash := NewDashboardFromJson(cmd.Dashboard)
-	if dash.Data["version"] == 0 {
+
+	if dash.Data.Get("version").MustInt(0) == 0 {
 		dash.CreatedBy = cmd.UserId
 	}
+
 	dash.UpdatedBy = cmd.UserId
 	dash.OrgId = cmd.OrgId
 	dash.UpdateSlug()
@@ -103,15 +96,12 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
 
 // GetString a
 func (dash *Dashboard) GetString(prop string, defaultValue string) string {
-	if val, exists := dash.Data[prop]; exists {
-		return val.(string)
-	}
-	return defaultValue
+	return dash.Data.Get(prop).MustString(defaultValue)
 }
 
 // UpdateSlug updates the slug
 func (dash *Dashboard) UpdateSlug() {
-	title := strings.ToLower(dash.Data["title"].(string))
+	title := strings.ToLower(dash.Data.Get("title").MustString())
 	dash.Slug = slug.Make(title)
 }
 
@@ -120,10 +110,10 @@ func (dash *Dashboard) UpdateSlug() {
 //
 
 type SaveDashboardCommand struct {
-	Dashboard map[string]interface{} `json:"dashboard" binding:"Required"`
-	UserId    int64                  `json:"userId"`
-	OrgId     int64                  `json:"-"`
-	Overwrite bool                   `json:"overwrite"`
+	Dashboard *simplejson.Json `json:"dashboard" binding:"Required"`
+	UserId    int64            `json:"userId"`
+	OrgId     int64            `json:"-"`
+	Overwrite bool             `json:"overwrite"`
 
 	Result *Dashboard
 }

+ 4 - 4
pkg/models/dashboards_test.go

@@ -3,6 +3,7 @@ package models
 import (
 	"testing"
 
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	. "github.com/smartystreets/goconvey/convey"
 )
 
@@ -16,12 +17,11 @@ func TestDashboardModel(t *testing.T) {
 	})
 
 	Convey("Given a dashboard json", t, func() {
-		json := map[string]interface{}{
-			"title": "test dash",
-		}
+		json := simplejson.New()
+		json.Set("title", "test dash")
 
 		Convey("With tags as string value", func() {
-			json["tags"] = ""
+			json.Set("tags", "")
 			dash := NewDashboardFromJson(json)
 
 			So(len(dash.GetTags()), ShouldEqual, 0)

+ 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:"-"`

+ 171 - 0
pkg/plugins/dashboard_importer.go

@@ -0,0 +1,171 @@
+package plugins
+
+import (
+	"encoding/json"
+	"fmt"
+	"regexp"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"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"`
+}
+
+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)
+}
+
+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
+	}
+
+	evaluator := &DashTemplateEvaluator{
+		template: dashboard.Data,
+		inputs:   cmd.Inputs,
+	}
+
+	generatedDash, err := evaluator.Eval()
+	if err != nil {
+		return err
+	}
+
+	saveCmd := m.SaveDashboardCommand{
+		Dashboard: generatedDash,
+		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  *simplejson.Json
+	inputs    []ImportDashboardInput
+	variables map[string]string
+	result    *simplejson.Json
+	varRegex  *regexp.Regexp
+}
+
+func (this *DashTemplateEvaluator) findInput(varName string, varDef *simplejson.Json) *ImportDashboardInput {
+	inputType := varDef.Get("type").MustString()
+
+	for _, input := range this.inputs {
+		if inputType == input.Type && (input.Name == varName || input.Name == "*") {
+			return &input
+		}
+	}
+
+	return nil
+}
+
+func (this *DashTemplateEvaluator) Eval() (*simplejson.Json, error) {
+	this.result = simplejson.New()
+	this.variables = make(map[string]string)
+	this.varRegex, _ = regexp.Compile("\\$__(\\w+)")
+
+	// check that we have all inputs we need
+	if inputDefs := this.template.Get("__inputs"); inputDefs != nil {
+		for varName, value := range inputDefs.MustMap() {
+			input := this.findInput(varName, simplejson.NewFromAny(value))
+
+			if input == nil {
+				return nil, &DashboardInputMissingError{VariableName: varName}
+			}
+
+			this.variables["$__"+varName] = input.Value
+		}
+	} else {
+		log.Info("Import: dashboard has no __import section")
+	}
+
+	return simplejson.NewFromAny(this.evalObject(this.template)), nil
+}
+
+func (this *DashTemplateEvaluator) evalValue(source *simplejson.Json) interface{} {
+
+	sourceValue := source.Interface()
+
+	switch v := sourceValue.(type) {
+	case string:
+		interpolated := this.varRegex.ReplaceAllStringFunc(v, func(match string) string {
+			return this.variables[match]
+		})
+		return interpolated
+	case bool:
+		return v
+	case json.Number:
+		return v
+	case map[string]interface{}:
+		return this.evalObject(source)
+	case []interface{}:
+		array := make([]interface{}, 0)
+		for _, item := range v {
+			array = append(array, this.evalValue(simplejson.NewFromAny(item)))
+		}
+		return array
+	}
+
+	return nil
+}
+
+func (this *DashTemplateEvaluator) evalObject(source *simplejson.Json) interface{} {
+	result := make(map[string]interface{})
+
+	for key, value := range source.MustMap() {
+		if key == "__inputs" {
+			continue
+		}
+		result[key] = this.evalValue(simplejson.NewFromAny(value))
+	}
+
+	return result
+}

+ 93 - 0
pkg/plugins/dashboard_importer_test.go

@@ -0,0 +1,93 @@
+package plugins
+
+import (
+	"io/ioutil"
+	"testing"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	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{
+				{Name: "*", Type: "datasource", Value: "graphite"},
+			},
+		}
+
+		err = ImportDashboard(&cmd)
+		So(err, ShouldBeNil)
+
+		Convey("should install dashboard", func() {
+			So(importedDash, ShouldNotBeNil)
+
+			resultStr, _ := importedDash.Data.EncodePretty()
+			expectedBytes, _ := ioutil.ReadFile("../../tests/test-app/dashboards/connections_result.json")
+			expectedJson, _ := simplejson.NewJson(expectedBytes)
+			expectedStr, _ := expectedJson.EncodePretty()
+
+			So(string(resultStr), ShouldEqual, string(expectedStr))
+
+			panel := importedDash.Data.Get("rows").GetIndex(0).Get("panels").GetIndex(0)
+			So(panel.Get("datasource").MustString(), ShouldEqual, "graphite")
+		})
+	})
+
+	Convey("When evaling dashboard template", t, func() {
+		template, _ := simplejson.NewJson([]byte(`{
+      "__inputs": {
+        "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.GetPath("test", "prop").MustString(), ShouldEqual, "my-server")
+		})
+
+		Convey("should not include inputs in output", func() {
+			inputs := res.Get("__inputs")
+			So(inputs.Interface(), ShouldBeNil)
+		})
+
+	})
+
+}

+ 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
-}

+ 3 - 5
pkg/plugins/dashboards.go

@@ -1,11 +1,11 @@
 package plugins
 
 import (
-	"encoding/json"
 	"os"
 	"path/filepath"
 
 	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	m "github.com/grafana/grafana/pkg/models"
 )
 
@@ -52,10 +52,8 @@ func loadPluginDashboard(plugin *PluginBase, path string) (*m.Dashboard, error)
 
 	defer reader.Close()
 
-	jsonParser := json.NewDecoder(reader)
-	var data map[string]interface{}
-
-	if err := jsonParser.Decode(&data); err != nil {
+	data, err := simplejson.NewFromReader(reader)
+	if err != nil {
 		return nil, err
 	}
 

+ 1 - 1
pkg/plugins/dashboards_test.go

@@ -23,7 +23,7 @@ func TestPluginDashboards(t *testing.T) {
 		bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
 			if query.Slug == "nginx-connections" {
 				dash := m.NewDashboard("Nginx Connections")
-				dash.Data["revision"] = "1.1"
+				dash.Data.Set("revision", "1.1")
 				query.Result = dash
 				return nil
 			}

+ 3 - 5
pkg/services/search/json_index.go

@@ -1,12 +1,12 @@
 package search
 
 import (
-	"encoding/json"
 	"os"
 	"path/filepath"
 	"strings"
 	"time"
 
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/log"
 	m "github.com/grafana/grafana/pkg/models"
 )
@@ -120,10 +120,8 @@ func loadDashboardFromFile(filename string) (*JsonDashIndexItem, error) {
 	}
 	defer reader.Close()
 
-	jsonParser := json.NewDecoder(reader)
-	var data map[string]interface{}
-
-	if err := jsonParser.Decode(&data); err != nil {
+	data, err := simplejson.NewFromReader(reader)
+	if err != nil {
 		return nil, err
 	}
 

+ 2 - 2
pkg/services/sqlstore/dashboard.go

@@ -69,7 +69,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
 			affectedRows, err = sess.Insert(dash)
 		} else {
 			dash.Version += 1
-			dash.Data["version"] = dash.Version
+			dash.Data.Set("version", dash.Version)
 			affectedRows, err = sess.Id(dash.Id).Update(dash)
 		}
 
@@ -108,7 +108,7 @@ func GetDashboard(query *m.GetDashboardQuery) error {
 		return m.ErrDashboardNotFound
 	}
 
-	dashboard.Data["id"] = dashboard.Id
+	dashboard.Data.Set("id", dashboard.Id)
 	query.Result = &dashboard
 
 	return nil

+ 4 - 3
pkg/services/sqlstore/dashboard_snapshot_test.go

@@ -5,6 +5,7 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	m "github.com/grafana/grafana/pkg/models"
 )
 
@@ -16,9 +17,9 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
 		Convey("Given saved snaphot", func() {
 			cmd := m.CreateDashboardSnapshotCommand{
 				Key: "hej",
-				Dashboard: map[string]interface{}{
+				Dashboard: simplejson.NewFromAny(map[string]interface{}{
 					"hello": "mupp",
-				},
+				}),
 			}
 			err := CreateDashboardSnapshot(&cmd)
 			So(err, ShouldBeNil)
@@ -29,7 +30,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
 				So(err, ShouldBeNil)
 
 				So(query.Result, ShouldNotBeNil)
-				So(query.Result.Dashboard["hello"], ShouldEqual, "mupp")
+				So(query.Result.Dashboard.Get("hello").MustString(), ShouldEqual, "mupp")
 			})
 
 		})

+ 9 - 8
pkg/services/sqlstore/dashboard_test.go

@@ -5,6 +5,7 @@ import (
 
 	. "github.com/smartystreets/goconvey/convey"
 
+	"github.com/grafana/grafana/pkg/components/simplejson"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/services/search"
 )
@@ -12,11 +13,11 @@ import (
 func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard {
 	cmd := m.SaveDashboardCommand{
 		OrgId: orgId,
-		Dashboard: map[string]interface{}{
+		Dashboard: simplejson.NewFromAny(map[string]interface{}{
 			"id":    nil,
 			"title": title,
 			"tags":  tags,
-		},
+		}),
 	}
 
 	err := SaveDashboard(&cmd)
@@ -58,11 +59,11 @@ func TestDashboardDataAccess(t *testing.T) {
 				cmd := m.SaveDashboardCommand{
 					OrgId:     1,
 					Overwrite: true,
-					Dashboard: map[string]interface{}{
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
 						"id":    float64(123412321),
 						"title": "Expect error",
 						"tags":  []interface{}{},
-					},
+					}),
 				}
 
 				err := SaveDashboard(&cmd)
@@ -76,11 +77,11 @@ func TestDashboardDataAccess(t *testing.T) {
 				cmd := m.SaveDashboardCommand{
 					OrgId:     2,
 					Overwrite: true,
-					Dashboard: map[string]interface{}{
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
 						"id":    float64(query.Result.Id),
 						"title": "Expect error",
 						"tags":  []interface{}{},
-					},
+					}),
 				}
 
 				err := SaveDashboard(&cmd)
@@ -135,11 +136,11 @@ func TestDashboardDataAccess(t *testing.T) {
 			Convey("Should not be able to save dashboard with same name", func() {
 				cmd := m.SaveDashboardCommand{
 					OrgId: 1,
-					Dashboard: map[string]interface{}{
+					Dashboard: simplejson.NewFromAny(map[string]interface{}{
 						"id":    nil,
 						"title": "test dash 23",
 						"tags":  []interface{}{},
-					},
+					}),
 				}
 
 				err := SaveDashboard(&cmd)

+ 2 - 2
public/app/features/dashboard/dashnav/dashnav.ts

@@ -137,8 +137,8 @@ export class DashNavCtrl {
 
     $scope.deleteDashboard = function() {
       $scope.appEvent('confirm-modal', {
-        title: 'Delete dashboard',
-        text: 'Do you want to delete dashboard?',
+        title: 'Delete',
+        text: 'Do you want to delete this dashboard?',
         text2: $scope.dashboard.title,
         icon: 'fa-trash',
         yesText: 'Delete',

+ 3 - 3
public/app/features/plugins/import_list/import_list.html

@@ -21,13 +21,13 @@
 				</td>
 				<td style="text-align: right">
 					<button class="btn btn-secondary" ng-click="ctrl.import(dash, false)" ng-show="!dash.installed">
-						Install
+						Import
 					</button>
 					<button class="btn btn-secondary" ng-click="ctrl.import(dash, true)" ng-show="dash.installed">
-						Re-Install
+						Re-Import
 					</button>
 					<button class="btn btn-danger" ng-click="ctrl.remove(dash)" ng-show="dash.installed">
-						Un-install
+						Delete
 					</button>
 				</td>
 			</tr>

+ 14 - 3
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,10 +22,19 @@ export class DashImportListCtrl {
       pluginId: this.plugin.id,
       path: dash.path,
       reinstall: reinstall,
-      inputs: {}
+      inputs: []
     };
 
-    this.backendSrv.post(`/api/plugins/dashboards/install`, installCmd).then(res => {
+    if (this.datasource) {
+      installCmd.inputs.push({
+        name: '*',
+        type: 'datasource',
+        pluginId: this.datasource.type,
+        value: this.datasource.name
+      });
+    }
+
+    this.backendSrv.post(`/api/plugins/dashboards/import`, 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>

+ 176 - 0
public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json

@@ -0,0 +1,176 @@
+{
+  "__inputs": {
+    "graphite": {
+      "type": "datasource",
+      "pluginId": "graphite",
+      "description": "Graphite datasource"
+    }
+  },
+
+  "revision": "1.0",
+  "title": "Graphite Carbon Metrics",
+  "tags": ["graphite", "carbon"],
+  "style": "dark",
+  "timezone": "browser",
+  "editable": true,
+  "hideControls": false,
+  "sharedCrosshair": false,
+  "rows": [
+    {
+      "collapsable": true,
+      "collapse": false,
+      "editable": true,
+      "height": "350px",
+      "notice": false,
+      "panels": [
+        {
+          "aliasColors": {},
+          "annotate": {
+            "enable": false
+          },
+          "bars": false,
+          "datasource": "$__graphite",
+          "editable": true,
+          "fill": 0,
+          "grid": {
+            "leftLogBase": 1,
+            "leftMax": null,
+            "leftMin": null,
+            "max": null,
+            "min": 0,
+            "rightLogBase": 1,
+            "rightMax": null,
+            "rightMin": null,
+            "threshold1": null,
+            "threshold1Color": "rgba(216, 200, 27, 0.27)",
+            "threshold2": null,
+            "threshold2Color": "rgba(234, 112, 112, 0.22)"
+          },
+          "id": 1,
+          "legend": {
+            "avg": false,
+            "current": false,
+            "max": false,
+            "min": false,
+            "show": true,
+            "total": false,
+            "values": false
+          },
+          "lines": true,
+          "linewidth": 1,
+          "loadingEditor": false,
+          "nullPointMode": "null as zero",
+          "percentage": false,
+          "pointradius": 5,
+          "points": false,
+          "renderer": "flot",
+          "resolution": 100,
+          "scale": 1,
+          "seriesOverrides": [
+            {
+              "alias": "Points Per Update",
+              "yaxis": 2
+            },
+            {
+              "alias": "CPU",
+              "yaxis": 2
+            }
+          ],
+          "span": 12,
+          "stack": false,
+          "steppedLine": false,
+          "targets": [
+            {
+              "refId": "A",
+              "target": "alias(sumSeries(carbon.agents.*.updateOperations),\"Updates\") "
+            },
+            {
+              "refId": "B",
+              "target": "alias(sumSeries(carbon.agents.*.metricsReceived),'Metrics Received')"
+            },
+            {
+              "refId": "C",
+              "target": "alias(sumSeries(carbon.agents.*.committedPoints),'Committed Points')"
+            },
+            {
+              "refId": "D",
+              "target": "alias(sumSeries(carbon.agents.*.pointsPerUpdate),'Points Per Update')"
+            },
+            {
+              "refId": "E",
+              "target": "alias(averageSeries(carbon.agents.*.cpuUsage),'CPU')"
+            },
+            {
+              "refId": "F",
+              "target": "alias(sumSeries(carbon.agents.*.creates),'Creates')"
+            }
+          ],
+          "timeFrom": null,
+          "timeShift": null,
+          "title": "Graphite Carbon Metrics",
+          "tooltip": {
+            "query_as_alias": true,
+            "shared": false,
+            "value_type": "cumulative"
+          },
+          "type": "graph",
+          "x-axis": true,
+          "y-axis": true,
+          "y_formats": [
+            "short",
+            "short"
+          ],
+          "zerofill": true
+        }
+      ],
+      "title": "Row1"
+    }
+  ],
+  "time": {
+    "from": "now-3h",
+    "to": "now"
+  },
+  "timepicker": {
+    "collapse": false,
+    "enable": true,
+    "notice": false,
+    "now": true,
+    "refresh_intervals": [
+      "5s",
+      "10s",
+      "30s",
+      "1m",
+      "5m",
+      "15m",
+      "30m",
+      "1h",
+      "2h",
+      "1d"
+    ],
+    "status": "Stable",
+    "time_options": [
+      "5m",
+      "15m",
+      "1h",
+      "6h",
+      "12h",
+      "24h",
+      "2d",
+      "7d",
+      "30d"
+    ],
+    "type": "timepicker"
+  },
+  "templating": {
+    "enable": false,
+    "list": []
+  },
+  "annotations": {
+    "enable": false,
+    "list": []
+  },
+  "refresh": false,
+  "schemaVersion": 8,
+  "version": 2,
+  "links": []
+}

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

@@ -1,21 +0,0 @@
-{
-  "__inputs": {
-    "graphite": {
-      "type": "datasource",
-      "description": "Graphite datasource"
-    }
-  },
-
-  "title": "Carbon Cache Stats",
-  "version": 1,
-  "rows": [
-    {
-      "panels": [
-        {
-          "type": "graph",
-          "datasource": "__$graphite"
-        }
-      ]
-    }
-  ]
-}

+ 1 - 1
public/app/plugins/datasource/graphite/plugin.json

@@ -4,7 +4,7 @@
   "id": "graphite",
 
   "includes": [
-    {"type": "dashboard", "name": "Carbon Cache Stats", "path": "dashboards/carbon_stats.json"}
+    {"type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json"}
   ],
 
   "metrics": true,

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

@@ -1,5 +1,28 @@
 {
+  "__inputs": {
+    "graphite": {
+      "type": "datasource",
+      "pluginId": "graphite",
+      "description": "Graphite datasource"
+    }
+  },
+
   "title": "Nginx Connections",
   "revision": "1.5",
-  "schemaVersion": 11
+  "schemaVersion": 11,
+  "tags": ["tag1", "tag2"],
+  "number_array": [1,2,3,10.33],
+  "boolean_true": true,
+  "boolean_false": false,
+
+  "rows": [
+    {
+      "panels": [
+        {
+          "type": "graph",
+          "datasource": "$__graphite"
+        }
+      ]
+    }
+  ]
 }

+ 20 - 0
tests/test-app/dashboards/connections_result.json

@@ -0,0 +1,20 @@
+{
+  "revision": "1.5",
+  "tags": ["tag1", "tag2"],
+  "boolean_false": false,
+  "boolean_true": true,
+  "number_array": [1,2,3,10.33],
+  "rows": [
+    {
+      "panels": [
+        {
+          "datasource": "graphite",
+          "type": "graph"
+        }
+      ]
+    }
+  ],
+  "schemaVersion": 11,
+  "title": "Nginx Connections",
+  "version": 0
+}