Merge branch 'v3.1.x'

This commit is contained in:
Torkel Ödegaard
2016-07-12 15:54:31 +02:00
39 changed files with 507 additions and 208 deletions
+1 -1
View File
@@ -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)
+17 -14
View File
@@ -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,37 @@ 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)
}
}
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 pluginErr, ok := err.(m.UpdatePluginDashboardError); ok {
message := "The dashboard belongs to plugin " + pluginErr.PluginId + "."
// look up plugin name
if pluginDef, exist := plugins.Plugins[pluginErr.PluginId]; exist {
message = "The dashboard belongs to plugin " + pluginDef.Name + "."
}
return Json(412, util.DynMap{"status": "plugin-dashboard", "message": message})
}
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 {
+1 -1
View File
@@ -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,
+23 -6
View File
@@ -17,6 +17,14 @@ var (
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
)
type UpdatePluginDashboardError struct {
PluginId string
}
func (d UpdatePluginDashboardError) Error() string {
return "Dashboard belong to plugin"
}
var (
DashTypeJson = "file"
DashTypeDB = "db"
@@ -26,11 +34,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
@@ -95,6 +104,7 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard {
dash.UpdatedBy = cmd.UserId
dash.OrgId = cmd.OrgId
dash.PluginId = cmd.PluginId
dash.UpdateSlug()
return dash
}
@@ -119,6 +129,7 @@ type SaveDashboardCommand struct {
UserId int64 `json:"userId"`
OrgId int64 `json:"-"`
Overwrite bool `json:"overwrite"`
PluginId string `json:"-"`
Result *Dashboard
}
@@ -151,7 +162,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 {
+20 -4
View File
@@ -20,6 +20,7 @@ type PluginSetting struct {
Pinned bool
JsonData map[string]interface{}
SecureJsonData SecureJsonData
PluginVersion string
Created time.Time
Updated time.Time
@@ -44,11 +45,19 @@ type UpdatePluginSettingCmd struct {
Pinned bool `json:"pinned"`
JsonData map[string]interface{} `json:"jsonData"`
SecureJsonData map[string]string `json:"secureJsonData"`
PluginVersion string `json:"version"`
PluginId string `json:"-"`
OrgId int64 `json:"-"`
}
// specific command, will only update version
type UpdatePluginSettingVersionCmd struct {
PluginVersion string
PluginId string `json:"-"`
OrgId int64 `json:"-"`
}
func (cmd *UpdatePluginSettingCmd) GetEncryptedJsonData() SecureJsonData {
encrypted := make(SecureJsonData)
for key, data := range cmd.SecureJsonData {
@@ -65,10 +74,11 @@ type GetPluginSettingsQuery struct {
}
type PluginSettingInfoDTO struct {
OrgId int64
PluginId string
Enabled bool
Pinned bool
OrgId int64
PluginId string
Enabled bool
Pinned bool
PluginVersion string
}
type GetPluginSettingByIdQuery struct {
@@ -76,3 +86,9 @@ type GetPluginSettingByIdQuery struct {
OrgId int64
Result *PluginSetting
}
type PluginStateChangedEvent struct {
PluginId string
OrgId int64
Enabled bool
}
+1
View File
@@ -68,6 +68,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error {
OrgId: cmd.OrgId,
UserId: cmd.UserId,
Overwrite: cmd.Overwrite,
PluginId: cmd.PluginId,
}
if err := bus.Dispatch(&saveCmd); err != nil {
+46 -35
View File
@@ -14,10 +14,12 @@ type PluginDashboardInfoDTO struct {
Title string `json:"title"`
Imported bool `json:"imported"`
ImportedUri string `json:"importedUri"`
Slug string `json:"slug"`
ImportedRevision int64 `json:"importedRevision"`
Revision int64 `json:"revision"`
Description string `json:"description"`
Path string `json:"path"`
Removed bool `json:"removed"`
}
func GetPluginDashboards(orgId int64, pluginId string) ([]*PluginDashboardInfoDTO, error) {
@@ -29,14 +31,53 @@ 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
}
existingMatches := make(map[int64]bool)
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)
existingMatches[existingDash.Id] = true
}
}
result = append(result, res)
}
// find deleted dashboards
for _, dash := range query.Result {
if _, exists := existingMatches[dash.Id]; !exists {
result = append(result, &PluginDashboardInfoDTO{
Slug: dash.Slug,
Removed: true,
})
}
}
return result, nil
@@ -64,33 +105,3 @@ func loadPluginDashboard(pluginId, path string) (*m.Dashboard, error) {
return m.NewDashboardFromJson(data), nil
}
func getDashboardImportStatus(orgId int64, plugin *PluginBase, path string) (*PluginDashboardInfoDTO, error) {
res := &PluginDashboardInfoDTO{}
var dashboard *m.Dashboard
var err error
if dashboard, err = loadPluginDashboard(plugin.Id, path); err != nil {
return nil, err
}
res.Path = path
res.PluginId = plugin.Id
res.Title = dashboard.Title
res.Revision = dashboard.Data.Get("revision").MustInt64(1)
query := m.GetDashboardQuery{OrgId: orgId, Slug: dashboard.Slug}
if err := bus.Dispatch(&query); err != nil {
if err != m.ErrDashboardNotFound {
return nil, err
}
} else {
res.Imported = true
res.ImportedUri = "db/" + query.Result.Slug
res.ImportedRevision = query.Result.Data.Get("revision").MustInt64(1)
}
return res, nil
}
+17 -5
View File
@@ -4,6 +4,7 @@ import (
"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"
@@ -31,6 +32,17 @@ func TestPluginDashboards(t *testing.T) {
return m.ErrDashboardNotFound
})
bus.AddHandler("test", func(query *m.GetDashboardsByPluginIdQuery) error {
var data = simplejson.New()
data.Set("title", "Nginx Connections")
data.Set("revision", 22)
query.Result = []*m.Dashboard{
{Slug: "nginx-connections", Data: data},
}
return nil
})
dashboards, err := GetPluginDashboards(1, "test-app")
So(err, ShouldBeNil)
@@ -41,12 +53,12 @@ func TestPluginDashboards(t *testing.T) {
Convey("should include installed version info", func() {
So(dashboards[0].Title, ShouldEqual, "Nginx Connections")
//So(dashboards[0].Revision, ShouldEqual, "1.5")
//So(dashboards[0].InstalledRevision, ShouldEqual, "1.1")
//So(dashboards[0].InstalledUri, ShouldEqual, "db/nginx-connections")
So(dashboards[0].Revision, ShouldEqual, 25)
So(dashboards[0].ImportedRevision, ShouldEqual, 22)
So(dashboards[0].ImportedUri, ShouldEqual, "db/nginx-connections")
//So(dashboards[1].Revision, ShouldEqual, "2.0")
//So(dashboards[1].InstalledRevision, ShouldEqual, "")
So(dashboards[1].Revision, ShouldEqual, 2)
So(dashboards[1].ImportedRevision, ShouldEqual, 0)
})
})
+139
View File
@@ -0,0 +1,139 @@
package plugins
import (
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddEventListener(handlePluginStateChanged)
}
func updateAppDashboards() {
time.Sleep(time.Second * 5)
plog.Debug("Looking for App Dashboard Updates")
query := m.GetPluginSettingsQuery{OrgId: 0}
if err := bus.Dispatch(&query); err != nil {
plog.Error("Failed to get all plugin settings", "error", err)
return
}
for _, pluginSetting := range query.Result {
// ignore disabled plugins
if !pluginSetting.Enabled {
continue
}
if pluginDef, exist := Plugins[pluginSetting.PluginId]; exist {
if pluginDef.Info.Version != pluginSetting.PluginVersion {
syncPluginDashboards(pluginDef, pluginSetting.OrgId)
}
}
}
}
func autoUpdateAppDashboard(pluginDashInfo *PluginDashboardInfoDTO, orgId int64) error {
if dash, err := loadPluginDashboard(pluginDashInfo.PluginId, pluginDashInfo.Path); err != nil {
return err
} else {
plog.Info("Auto updating App dashboard", "dashboard", dash.Title, "newRev", pluginDashInfo.Revision, "oldRev", pluginDashInfo.ImportedRevision)
updateCmd := ImportDashboardCommand{
OrgId: orgId,
PluginId: pluginDashInfo.PluginId,
Overwrite: true,
Dashboard: dash.Data,
UserId: 0,
Path: pluginDashInfo.Path,
}
if err := bus.Dispatch(&updateCmd); err != nil {
return err
}
}
return nil
}
func syncPluginDashboards(pluginDef *PluginBase, orgId int64) {
plog.Info("Syncing plugin dashboards to DB", "pluginId", pluginDef.Id)
// Get plugin dashboards
dashboards, err := GetPluginDashboards(orgId, pluginDef.Id)
if err != nil {
plog.Error("Failed to load app dashboards", "error", err)
return
}
// Update dashboards with updated revisions
for _, dash := range dashboards {
// remove removed ones
if dash.Removed {
plog.Info("Deleting plugin dashboard", "pluginId", pluginDef.Id, "dashboard", dash.Slug)
deleteCmd := m.DeleteDashboardCommand{OrgId: orgId, Slug: dash.Slug}
if err := bus.Dispatch(&deleteCmd); err != nil {
plog.Error("Failed to auto update app dashboard", "pluginId", pluginDef.Id, "error", err)
return
}
continue
}
// update updated ones
if dash.ImportedRevision != dash.Revision {
if err := autoUpdateAppDashboard(dash, orgId); err != nil {
plog.Error("Failed to auto update app dashboard", "pluginId", pluginDef.Id, "error", err)
return
}
}
}
// update version in plugin_setting table to mark that we have processed the update
query := m.GetPluginSettingByIdQuery{PluginId: pluginDef.Id, OrgId: orgId}
if err := bus.Dispatch(&query); err != nil {
plog.Error("Failed to read plugin setting by id", "error", err)
return
}
appSetting := query.Result
cmd := m.UpdatePluginSettingVersionCmd{
OrgId: appSetting.OrgId,
PluginId: appSetting.PluginId,
PluginVersion: pluginDef.Info.Version,
}
if err := bus.Dispatch(&cmd); err != nil {
plog.Error("Failed to update plugin setting version", "error", err)
}
}
func handlePluginStateChanged(event *m.PluginStateChangedEvent) error {
plog.Info("Plugin state changed", "pluginId", event.PluginId, "enabled", event.Enabled)
if event.Enabled {
syncPluginDashboards(Plugins[event.PluginId], event.OrgId)
} else {
query := m.GetDashboardsByPluginIdQuery{PluginId: event.PluginId, OrgId: event.OrgId}
if err := bus.Dispatch(&query); err != nil {
return err
} else {
for _, dash := range query.Result {
deleteCmd := m.DeleteDashboardCommand{OrgId: dash.OrgId, Slug: dash.Slug}
plog.Info("Deleting plugin dashboard", "pluginId", event.PluginId, "dashboard", dash.Slug)
if err := bus.Dispatch(&deleteCmd); err != nil {
return err
}
}
}
}
return nil
}
+2
View File
@@ -77,6 +77,8 @@ func Init() error {
}
go StartPluginUpdateChecker()
go updateAppDashboards()
return nil
}
+21 -2
View File
@@ -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 {
@@ -45,6 +46,11 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
return m.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && cmd.Overwrite == false {
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
}
sameTitleExists, err := sess.Where("org_id=? AND slug=?", dash.OrgId, dash.Slug).Get(&sameTitle)
@@ -245,10 +251,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
@@ -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,
}))
}
@@ -26,4 +26,10 @@ func addAppSettingsMigration(mg *Migrator) {
//------- indexes ------------------
addTableIndicesMigrations(mg, "v1", pluginSettingTable)
// add column to store installed version
mg.AddMigration("Add column plugin_version to plugin_settings", NewAddColumnMigration(pluginSettingTable, &Column{
Name: "plugin_version", Type: DB_NVarchar, Nullable: true, Length: 50,
}))
}
+40 -4
View File
@@ -13,14 +13,20 @@ func init() {
bus.AddHandler("sql", GetPluginSettings)
bus.AddHandler("sql", GetPluginSettingById)
bus.AddHandler("sql", UpdatePluginSetting)
bus.AddHandler("sql", UpdatePluginSettingVersion)
}
func GetPluginSettings(query *m.GetPluginSettingsQuery) error {
sql := `SELECT org_id, plugin_id, enabled, pinned
FROM plugin_setting
WHERE org_id=?`
sql := `SELECT org_id, plugin_id, enabled, pinned, plugin_version
FROM plugin_setting `
params := make([]interface{}, 0)
sess := x.Sql(sql, query.OrgId)
if query.OrgId != 0 {
sql += "WHERE org_id=?"
params = append(params, query.OrgId)
}
sess := x.Sql(sql, params...)
query.Result = make([]*m.PluginSettingInfoDTO, 0)
return sess.Find(&query.Result)
}
@@ -51,22 +57,52 @@ func UpdatePluginSetting(cmd *m.UpdatePluginSettingCmd) error {
Enabled: cmd.Enabled,
Pinned: cmd.Pinned,
JsonData: cmd.JsonData,
PluginVersion: cmd.PluginVersion,
SecureJsonData: cmd.GetEncryptedJsonData(),
Created: time.Now(),
Updated: time.Now(),
}
// add state change event on commit success
sess.events = append(sess.events, &m.PluginStateChangedEvent{
PluginId: cmd.PluginId,
OrgId: cmd.OrgId,
Enabled: cmd.Enabled,
})
_, err = sess.Insert(&pluginSetting)
return err
} else {
for key, data := range cmd.SecureJsonData {
pluginSetting.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
}
// add state change event on commit success
if pluginSetting.Enabled != cmd.Enabled {
sess.events = append(sess.events, &m.PluginStateChangedEvent{
PluginId: cmd.PluginId,
OrgId: cmd.OrgId,
Enabled: cmd.Enabled,
})
}
pluginSetting.Updated = time.Now()
pluginSetting.Enabled = cmd.Enabled
pluginSetting.JsonData = cmd.JsonData
pluginSetting.Pinned = cmd.Pinned
pluginSetting.PluginVersion = cmd.PluginVersion
_, err = sess.Id(pluginSetting.Id).Update(&pluginSetting)
return err
}
})
}
func UpdatePluginSettingVersion(cmd *m.UpdatePluginSettingVersionCmd) error {
return inTransaction2(func(sess *session) error {
_, err := sess.Exec("UPDATE plugin_setting SET plugin_version=? WHERE org_id=? AND plugin_id=?", cmd.PluginVersion, cmd.OrgId, cmd.PluginId)
return err
})
}