From d44325affd6d5cd245ff514e04cf0fa9f53d89a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Torkel=20=C3=96degaard?= <torkel.odegaard@gmail.com>
Date: Fri, 8 Jul 2016 09:35:06 +0200
Subject: [PATCH] feat(apps): progress on app dashboard imports

---
 pkg/api/api.go                                |  2 +-
 pkg/api/dashboard.go                          | 37 ++++++++++++-------
 pkg/api/playlist_play.go                      |  2 +-
 pkg/models/dashboards.go                      | 19 +++++++---
 pkg/plugins/dashboards.go                     | 37 ++++++++++++++++---
 pkg/services/sqlstore/dashboard.go            | 18 ++++++++-
 .../sqlstore/migrations/dashboard_mig.go      |  9 +++++
 public/app/core/services/alert_srv.ts         |  4 --
 public/app/features/dashboard/dashboardSrv.js |  1 +
 .../app/features/dashboard/dashnav/dashnav.ts | 15 ++++++++
 .../features/dashboard/saveDashboardAsCtrl.js |  7 +++-
 11 files changed, 116 insertions(+), 35 deletions(-)

diff --git a/pkg/api/api.go b/pkg/api/api.go
index 4cebd56d5b1..8c45c8e6d8b 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -211,7 +211,7 @@ func Register(r *macaron.Macaron) {
 		// Dashboard
 		r.Group("/dashboards", func() {
 			r.Combo("/db/:slug").Get(GetDashboard).Delete(DeleteDashboard)
-			r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), PostDashboard)
+			r.Post("/db", reqEditorRole, bind(m.SaveDashboardCommand{}), wrap(PostDashboard))
 			r.Get("/file/:file", GetDashboardFromJsonFile)
 			r.Get("/home", wrap(GetHomeDashboard))
 			r.Get("/tags", GetDashboardTags)
diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go
index cbad74444bf..0a29a91347a 100644
--- a/pkg/api/dashboard.go
+++ b/pkg/api/dashboard.go
@@ -12,6 +12,7 @@ import (
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/services/search"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/util"
@@ -109,7 +110,7 @@ func DeleteDashboard(c *middleware.Context) {
 	c.JSON(200, resp)
 }
 
-func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
+func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
 	cmd.OrgId = c.OrgId
 
 	if !c.IsSignedIn {
@@ -122,35 +123,43 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) {
 	if dash.Id == 0 {
 		limitReached, err := middleware.QuotaReached(c, "dashboard")
 		if err != nil {
-			c.JsonApiErr(500, "failed to get quota", err)
-			return
+			return ApiError(500, "failed to get quota", err)
 		}
 		if limitReached {
-			c.JsonApiErr(403, "Quota reached", nil)
-			return
+			return ApiError(403, "Quota reached", nil)
+		}
+	}
+
+	if !cmd.Overwrite {
+		if autoUpdate, exists := dash.Data.CheckGet("autoUpdate"); exists {
+			message := "Dashboard marked as auto updated."
+
+			if pluginId, err := autoUpdate.Get("pluginId").String(); err == nil {
+				if pluginDef, ok := plugins.Plugins[pluginId]; ok {
+					message = "Dashboard updated automatically when plugin " + pluginDef.Name + " is updated."
+				}
+			}
+
+			return Json(412, util.DynMap{"status": "auto-update-dashboard", "message": message})
 		}
 	}
 
 	err := bus.Dispatch(&cmd)
 	if err != nil {
 		if err == m.ErrDashboardWithSameNameExists {
-			c.JSON(412, util.DynMap{"status": "name-exists", "message": err.Error()})
-			return
+			return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
 		}
 		if err == m.ErrDashboardVersionMismatch {
-			c.JSON(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
-			return
+			return Json(412, util.DynMap{"status": "version-mismatch", "message": err.Error()})
 		}
 		if err == m.ErrDashboardNotFound {
-			c.JSON(404, util.DynMap{"status": "not-found", "message": err.Error()})
-			return
+			return Json(404, util.DynMap{"status": "not-found", "message": err.Error()})
 		}
-		c.JsonApiErr(500, "Failed to save dashboard", err)
-		return
+		return ApiError(500, "Failed to save dashboard", err)
 	}
 
 	c.TimeRequest(metrics.M_Api_Dashboard_Save)
-	c.JSON(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug, "version": cmd.Result.Version})
+	return Json(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug, "version": cmd.Result.Version})
 }
 
 func canEditDashboard(role m.RoleType) bool {
diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go
index f3bae6cbcd3..e4feb3442fb 100644
--- a/pkg/api/playlist_play.go
+++ b/pkg/api/playlist_play.go
@@ -18,7 +18,7 @@ func populateDashboardsById(dashboardByIds []int64) ([]m.PlaylistDashboardDto, e
 			return result, err
 		}
 
-		for _, item := range *dashboardQuery.Result {
+		for _, item := range dashboardQuery.Result {
 			result = append(result, m.PlaylistDashboardDto{
 				Id:    item.Id,
 				Slug:  item.Slug,
diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go
index 610f29e70aa..733d989b278 100644
--- a/pkg/models/dashboards.go
+++ b/pkg/models/dashboards.go
@@ -26,11 +26,12 @@ var (
 
 // Dashboard model
 type Dashboard struct {
-	Id      int64
-	Slug    string
-	OrgId   int64
-	GnetId  int64
-	Version int
+	Id       int64
+	Slug     string
+	OrgId    int64
+	GnetId   int64
+	Version  int
+	PluginId string
 
 	Created time.Time
 	Updated time.Time
@@ -151,7 +152,13 @@ type GetDashboardTagsQuery struct {
 
 type GetDashboardsQuery struct {
 	DashboardIds []int64
-	Result       *[]Dashboard
+	Result       []*Dashboard
+}
+
+type GetDashboardsByPluginIdQuery struct {
+	OrgId    int64
+	PluginId string
+	Result   []*Dashboard
 }
 
 type GetDashboardSlugByIdQuery struct {
diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go
index 1a160fe6632..8bde60b64c8 100644
--- a/pkg/plugins/dashboards.go
+++ b/pkg/plugins/dashboards.go
@@ -18,6 +18,7 @@ type PluginDashboardInfoDTO struct {
 	Revision         int64  `json:"revision"`
 	Description      string `json:"description"`
 	Path             string `json:"path"`
+	Removed          bool
 }
 
 func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDTO, error) {
@@ -29,14 +30,40 @@ func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDT
 
 	result := make([]*PluginDashboardInfoDTO, 0)
 
+	// load current dashboards
+	query := m.GetDashboardsByPluginIdQuery{OrgId: orgId, PluginId: pluginId}
+	if err := bus.Dispatch(&query); err != nil {
+		return nil, err
+	}
+
 	for _, include := range plugin.Includes {
-		if include.Type == PluginTypeDashboard {
-			if dashInfo, err := getDashboardImportStatus(orgId, plugin, include.Path); err != nil {
-				return nil, err
-			} else {
-				result = append(result, dashInfo)
+		if include.Type != PluginTypeDashboard {
+			continue
+		}
+
+		res := &PluginDashboardInfoDTO{}
+		var dashboard *m.Dashboard
+		var err error
+
+		if dashboard, err = loadPluginDashboard(plugin.Id, include.Path); err != nil {
+			return nil, err
+		}
+
+		res.Path = include.Path
+		res.PluginId = plugin.Id
+		res.Title = dashboard.Title
+		res.Revision = dashboard.Data.Get("revision").MustInt64(1)
+
+		// find existing dashboard
+		for _, existingDash := range query.Result {
+			if existingDash.Slug == dashboard.Slug {
+				res.Imported = true
+				res.ImportedUri = "db/" + existingDash.Slug
+				res.ImportedRevision = existingDash.Data.Get("revision").MustInt64(1)
 			}
 		}
+
+		result = append(result, res)
 	}
 
 	return result, nil
diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go
index ef36fd75ab6..3712ce6579f 100644
--- a/pkg/services/sqlstore/dashboard.go
+++ b/pkg/services/sqlstore/dashboard.go
@@ -19,6 +19,7 @@ func init() {
 	bus.AddHandler("sql", SearchDashboards)
 	bus.AddHandler("sql", GetDashboardTags)
 	bus.AddHandler("sql", GetDashboardSlugById)
+	bus.AddHandler("sql", GetDashboardsByPluginId)
 }
 
 func SaveDashboard(cmd *m.SaveDashboardCommand) error {
@@ -245,10 +246,23 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
 		return m.ErrCommandValidationFailed
 	}
 
-	var dashboards = make([]m.Dashboard, 0)
+	var dashboards = make([]*m.Dashboard, 0)
 
 	err := x.In("id", query.DashboardIds).Find(&dashboards)
-	query.Result = &dashboards
+	query.Result = dashboards
+
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
+	var dashboards = make([]*m.Dashboard, 0)
+
+	err := x.Where("org_id=? AND plugin_id=?", query.OrgId, query.PluginId).Find(&dashboards)
+	query.Result = dashboards
 
 	if err != nil {
 		return err
diff --git a/pkg/services/sqlstore/migrations/dashboard_mig.go b/pkg/services/sqlstore/migrations/dashboard_mig.go
index 38c25a315f2..4f286dce68a 100644
--- a/pkg/services/sqlstore/migrations/dashboard_mig.go
+++ b/pkg/services/sqlstore/migrations/dashboard_mig.go
@@ -111,4 +111,13 @@ func addDashboardMigration(mg *Migrator) {
 	mg.AddMigration("Add index for gnetId in dashboard", NewAddIndexMigration(dashboardV2, &Index{
 		Cols: []string{"gnet_id"}, Type: IndexType,
 	}))
+
+	// add column to store plugin_id
+	mg.AddMigration("Add column plugin_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{
+		Name: "plugin_id", Type: DB_NVarchar, Nullable: true, Length: 255,
+	}))
+
+	mg.AddMigration("Add index for plugin_id in dashboard", NewAddIndexMigration(dashboardV2, &Index{
+		Cols: []string{"org_id", "plugin_id"}, Type: IndexType,
+	}))
 }
diff --git a/public/app/core/services/alert_srv.ts b/public/app/core/services/alert_srv.ts
index 971743b7285..3003e59c74b 100644
--- a/public/app/core/services/alert_srv.ts
+++ b/public/app/core/services/alert_srv.ts
@@ -28,10 +28,6 @@ export class AlertSrv {
     }, this.$rootScope);
 
     appEvents.on('confirm-modal', this.showConfirmModal.bind(this));
-
-    this.$rootScope.onAppEvent('confirm-modal', (e, data) => {
-      this.showConfirmModal(data);
-    }, this.$rootScope);
   }
 
   set(title, text, severity, timeout) {
diff --git a/public/app/features/dashboard/dashboardSrv.js b/public/app/features/dashboard/dashboardSrv.js
index 0a3c1b95f95..937d5266c56 100644
--- a/public/app/features/dashboard/dashboardSrv.js
+++ b/public/app/features/dashboard/dashboardSrv.js
@@ -22,6 +22,7 @@ function (angular, $, _, moment) {
 
       this.id = data.id || null;
       this.title = data.title || 'No Title';
+      this.autoUpdate = data.autoUpdate;
       this.description = data.description;
       this.tags = data.tags || [];
       this.style = data.style || "dark";
diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts
index 62f598e1cbd..25bb587edeb 100644
--- a/public/app/features/dashboard/dashnav/dashnav.ts
+++ b/public/app/features/dashboard/dashnav/dashnav.ts
@@ -134,6 +134,21 @@ export class DashNavCtrl {
           }
         });
       }
+
+      if (err.data && err.data.status === "auto-update-dashboard") {
+        err.isHandled = true;
+
+        $scope.appEvent('confirm-modal', {
+          title: 'Auto Update Dashboard',
+          text: err.data.message,
+          text2: 'Use Save As... to create copy or ignore this warning.',
+          yesText: "Save & Overwrite",
+          icon: "fa-warning",
+          onConfirm: function() {
+            $scope.saveDashboard({overwrite: true});
+          }
+        });
+      }
     };
 
     $scope.deleteDashboard = function() {
diff --git a/public/app/features/dashboard/saveDashboardAsCtrl.js b/public/app/features/dashboard/saveDashboardAsCtrl.js
index 92c93b7e485..211863b66e6 100644
--- a/public/app/features/dashboard/saveDashboardAsCtrl.js
+++ b/public/app/features/dashboard/saveDashboardAsCtrl.js
@@ -12,6 +12,8 @@ function (angular) {
       $scope.clone.id = null;
       $scope.clone.editable = true;
       $scope.clone.title = $scope.clone.title + " Copy";
+      // remove auto update
+      delete $scope.clone.autoUpdate;
     };
 
     function saveDashboard(options) {
@@ -37,8 +39,9 @@ function (angular) {
           err.isHandled = true;
 
           $scope.appEvent('confirm-modal', {
-            title: 'Another dashboard with the same name exists',
-            text: "Would you still like to save this dashboard?",
+            title: 'Conflict',
+            text: 'Dashboard with the same name exists.',
+            text2: 'Would you still like to save this dashboard?',
             yesText: "Save & Overwrite",
             icon: "fa-warning",
             onConfirm: function() {