Merge branch 'master' into mssql_datasource

This commit is contained in:
Marcus Efraimsson
2018-03-13 16:03:02 +01:00
2626 changed files with 339802 additions and 172728 deletions
+50 -43
View File
@@ -10,11 +10,13 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
// timeNow makes it possible to test usage of time
var timeNow = time.Now
func init() {
bus.AddHandler("sql", SaveAlerts)
bus.AddHandler("sql", HandleAlertsQuery)
bus.AddHandler("sql", GetAlertById)
bus.AddHandler("sql", DeleteAlertById)
bus.AddHandler("sql", GetAllAlertQueryHandler)
bus.AddHandler("sql", SetAlertState)
bus.AddHandler("sql", GetAlertStatesForDashboard)
@@ -24,7 +26,7 @@ func init() {
func GetAlertById(query *m.GetAlertByIdQuery) error {
alert := m.Alert{}
has, err := x.Id(query.Id).Get(&alert)
has, err := x.ID(query.Id).Get(&alert)
if !has {
return fmt.Errorf("could not find alert")
}
@@ -61,59 +63,62 @@ func deleteAlertByIdInternal(alertId int64, reason string, sess *DBSession) erro
return nil
}
func DeleteAlertById(cmd *m.DeleteAlertCommand) error {
return inTransaction(func(sess *DBSession) error {
return deleteAlertByIdInternal(cmd.AlertId, "DeleteAlertCommand", sess)
})
}
func HandleAlertsQuery(query *m.GetAlertsQuery) error {
var sql bytes.Buffer
params := make([]interface{}, 0)
builder := SqlBuilder{}
sql.WriteString(`SELECT *
from alert
`)
builder.Write(`SELECT
alert.id,
alert.dashboard_id,
alert.panel_id,
alert.name,
alert.state,
alert.new_state_date,
alert.eval_date,
alert.execution_error,
dashboard.uid as dashboard_uid,
dashboard.slug as dashboard_slug
FROM alert
INNER JOIN dashboard on dashboard.id = alert.dashboard_id `)
sql.WriteString(`WHERE org_id = ?`)
params = append(params, query.OrgId)
builder.Write(`WHERE alert.org_id = ?`, query.OrgId)
if query.DashboardId != 0 {
sql.WriteString(` AND dashboard_id = ?`)
params = append(params, query.DashboardId)
builder.Write(` AND alert.dashboard_id = ?`, query.DashboardId)
}
if query.PanelId != 0 {
sql.WriteString(` AND panel_id = ?`)
params = append(params, query.PanelId)
builder.Write(` AND alert.panel_id = ?`, query.PanelId)
}
if len(query.State) > 0 && query.State[0] != "ALL" {
sql.WriteString(` AND (`)
if len(query.State) > 0 && query.State[0] != "all" {
builder.Write(` AND (`)
for i, v := range query.State {
if i > 0 {
sql.WriteString(" OR ")
builder.Write(" OR ")
}
if strings.HasPrefix(v, "not_") {
sql.WriteString("state <> ? ")
builder.Write("state <> ? ")
v = strings.TrimPrefix(v, "not_")
} else {
sql.WriteString("state = ? ")
builder.Write("state = ? ")
}
params = append(params, v)
builder.AddParams(v)
}
sql.WriteString(")")
builder.Write(")")
}
sql.WriteString(" ORDER BY name ASC")
if query.User.OrgRole != m.ROLE_ADMIN {
builder.writeDashboardPermissionFilter(query.User, m.PERMISSION_EDIT)
}
builder.Write(" ORDER BY name ASC")
if query.Limit != 0 {
sql.WriteString(" LIMIT ?")
params = append(params, query.Limit)
builder.Write(" LIMIT ?", query.Limit)
}
alerts := make([]*m.Alert, 0)
if err := x.Sql(sql.String(), params...).Find(&alerts); err != nil {
alerts := make([]*m.AlertListItemDTO, 0)
if err := x.SQL(builder.GetSqlString(), builder.params...).Find(&alerts); err != nil {
return err
}
@@ -127,7 +132,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
return nil
}
func DeleteAlertDefinition(dashboardId int64, sess *DBSession) error {
func deleteAlertDefinition(dashboardId int64, sess *DBSession) error {
alerts := make([]*m.Alert, 0)
sess.Where("dashboard_id = ?", dashboardId).Find(&alerts)
@@ -145,7 +150,7 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error {
return err
}
if err := upsertAlerts(existingAlerts, cmd, sess); err != nil {
if err := updateAlerts(existingAlerts, cmd, sess); err != nil {
return err
}
@@ -157,7 +162,7 @@ func SaveAlerts(cmd *m.SaveAlertsCommand) error {
})
}
func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBSession) error {
func updateAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBSession) error {
for _, alert := range cmd.Alerts {
update := false
var alertToUpdate *m.Alert
@@ -173,7 +178,7 @@ func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
if update {
if alertToUpdate.ContainsUpdates(alert) {
alert.Updated = time.Now()
alert.Updated = timeNow()
alert.State = alertToUpdate.State
sess.MustCols("message")
_, err := sess.Id(alert.Id).Update(alert)
@@ -184,10 +189,10 @@ func upsertAlerts(existingAlerts []*m.Alert, cmd *m.SaveAlertsCommand, sess *DBS
sqlog.Debug("Alert updated", "name", alert.Name, "id", alert.Id)
}
} else {
alert.Updated = time.Now()
alert.Created = time.Now()
alert.Updated = timeNow()
alert.Created = timeNow()
alert.State = m.AlertStatePending
alert.NewStateDate = time.Now()
alert.NewStateDate = timeNow()
_, err := sess.Insert(alert)
if err != nil {
@@ -251,7 +256,7 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
alert.State = cmd.State
alert.StateChanges += 1
alert.NewStateDate = time.Now()
alert.NewStateDate = timeNow()
alert.EvalData = cmd.EvalData
if cmd.Error == "" {
@@ -260,7 +265,7 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
alert.ExecutionError = cmd.Error
}
sess.Id(alert.Id).Update(&alert)
sess.ID(alert.Id).Update(&alert)
return nil
})
}
@@ -274,11 +279,13 @@ func PauseAlert(cmd *m.PauseAlertCommand) error {
var buffer bytes.Buffer
params := make([]interface{}, 0)
buffer.WriteString(`UPDATE alert SET state = ?`)
buffer.WriteString(`UPDATE alert SET state = ?, new_state_date = ?`)
if cmd.Paused {
params = append(params, string(m.AlertStatePaused))
params = append(params, timeNow())
} else {
params = append(params, string(m.AlertStatePending))
params = append(params, timeNow())
}
buffer.WriteString(` WHERE id IN (?` + strings.Repeat(",?", len(cmd.AlertIds)-1) + `)`)
@@ -304,7 +311,7 @@ func PauseAllAlerts(cmd *m.PauseAllAlertCommand) error {
newState = string(m.AlertStatePending)
}
res, err := sess.Exec(`UPDATE alert SET state = ?`, newState)
res, err := sess.Exec(`UPDATE alert SET state = ?, new_state_date = ?`, newState, timeNow())
if err != nil {
return err
}
@@ -324,7 +331,7 @@ func GetAlertStatesForDashboard(query *m.GetAlertStatesForDashboardQuery) error
WHERE org_id = ? AND dashboard_id = ?`
query.Result = make([]*m.AlertStateInfoDTO, 0)
err := x.Sql(rawSql, query.OrgId, query.DashboardId).Find(&query.Result)
err := x.SQL(rawSql, query.OrgId, query.DashboardId).Find(&query.Result)
return err
}
+3 -3
View File
@@ -76,7 +76,7 @@ func GetAlertNotificationsToSend(query *m.GetAlertNotificationsToSendQuery) erro
sql.WriteString(`)`)
results := make([]*m.AlertNotification, 0)
if err := x.Sql(sql.String(), params...).Find(&results); err != nil {
if err := x.SQL(sql.String(), params...).Find(&results); err != nil {
return err
}
@@ -165,7 +165,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
return inTransaction(func(sess *DBSession) (err error) {
current := m.AlertNotification{}
if _, err = sess.Id(cmd.Id).Get(&current); err != nil {
if _, err = sess.ID(cmd.Id).Get(&current); err != nil {
return err
}
@@ -187,7 +187,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
sess.UseBool("is_default")
if affected, err := sess.Id(cmd.Id).Update(current); err != nil {
if affected, err := sess.ID(cmd.Id).Update(current); err != nil {
return err
} else if affected == 0 {
return fmt.Errorf("Could not find alert notification")
+134 -13
View File
@@ -6,9 +6,26 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
"time"
)
func mockTimeNow() {
var timeSeed int64
timeNow = func() time.Time {
fakeNow := time.Unix(timeSeed, 0)
timeSeed += 1
return fakeNow
}
}
func resetTimeNow() {
timeNow = time.Now
}
func TestAlertingDataAccess(t *testing.T) {
mockTimeNow()
defer resetTimeNow()
Convey("Testing Alerting data access", t, func() {
InitTestDB(t)
@@ -50,13 +67,11 @@ func TestAlertingDataAccess(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("can pause alert", func() {
cmd := &m.PauseAllAlertCommand{
Paused: true,
}
alert, _ := getAlertById(1)
stateDateBeforePause := alert.NewStateDate
err = PauseAllAlerts(cmd)
So(err, ShouldBeNil)
Convey("can pause all alerts", func() {
pauseAllAlerts(true)
Convey("cannot updated paused alert", func() {
cmd := &m.SetAlertStateCommand{
@@ -67,19 +82,38 @@ func TestAlertingDataAccess(t *testing.T) {
err = SetAlertState(cmd)
So(err, ShouldNotBeNil)
})
Convey("pausing alerts should update their NewStateDate", func() {
alert, _ = getAlertById(1)
stateDateAfterPause := alert.NewStateDate
So(stateDateBeforePause, ShouldHappenBefore, stateDateAfterPause)
})
Convey("unpausing alerts should update their NewStateDate again", func() {
pauseAllAlerts(false)
alert, _ = getAlertById(1)
stateDateAfterUnpause := alert.NewStateDate
So(stateDateBeforePause, ShouldHappenBefore, stateDateAfterUnpause)
})
})
})
Convey("Can read properties", func() {
alertQuery := m.GetAlertsQuery{DashboardId: testDash.Id, PanelId: 1, OrgId: 1}
alertQuery := m.GetAlertsQuery{DashboardId: testDash.Id, PanelId: 1, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
err2 := HandleAlertsQuery(&alertQuery)
alert := alertQuery.Result[0]
So(err2, ShouldBeNil)
So(alert.Name, ShouldEqual, "Alerting title")
So(alert.Message, ShouldEqual, "Alerting message")
So(alert.State, ShouldEqual, "pending")
So(alert.Frequency, ShouldEqual, 1)
})
Convey("Viewer cannot read alerts", func() {
alertQuery := m.GetAlertsQuery{DashboardId: testDash.Id, PanelId: 1, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_VIEWER}}
err2 := HandleAlertsQuery(&alertQuery)
So(err2, ShouldBeNil)
So(alertQuery.Result, ShouldHaveLength, 0)
})
Convey("Alerts with same dashboard id and panel id should update", func() {
@@ -100,7 +134,7 @@ func TestAlertingDataAccess(t *testing.T) {
})
Convey("Alerts should be updated", func() {
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
err2 := HandleAlertsQuery(&query)
So(err2, ShouldBeNil)
@@ -149,7 +183,7 @@ func TestAlertingDataAccess(t *testing.T) {
Convey("Should save 3 dashboards", func() {
So(err, ShouldBeNil)
queryForDashboard := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
queryForDashboard := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
err2 := HandleAlertsQuery(&queryForDashboard)
So(err2, ShouldBeNil)
@@ -163,7 +197,7 @@ func TestAlertingDataAccess(t *testing.T) {
err = SaveAlerts(&cmd)
Convey("should delete the missing alert", func() {
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
err2 := HandleAlertsQuery(&query)
So(err2, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
@@ -198,7 +232,7 @@ func TestAlertingDataAccess(t *testing.T) {
So(err, ShouldBeNil)
Convey("Alerts should be removed", func() {
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1}
query := m.GetAlertsQuery{DashboardId: testDash.Id, OrgId: 1, User: &m.SignedInUser{OrgRole: m.ROLE_ADMIN}}
err2 := HandleAlertsQuery(&query)
So(testDash.Id, ShouldEqual, 1)
@@ -208,3 +242,90 @@ func TestAlertingDataAccess(t *testing.T) {
})
})
}
func TestPausingAlerts(t *testing.T) {
mockTimeNow()
defer resetTimeNow()
Convey("Given an alert", t, func() {
InitTestDB(t)
testDash := insertTestDashboard("dashboard with alerts", 1, 0, false, "alert")
alert, _ := insertTestAlert("Alerting title", "Alerting message", testDash.OrgId, testDash.Id, simplejson.New())
stateDateBeforePause := alert.NewStateDate
stateDateAfterPause := stateDateBeforePause
Convey("when paused", func() {
pauseAlert(testDash.OrgId, 1, true)
Convey("the NewStateDate should be updated", func() {
alert, _ := getAlertById(1)
stateDateAfterPause = alert.NewStateDate
So(stateDateBeforePause, ShouldHappenBefore, stateDateAfterPause)
})
})
Convey("when unpaused", func() {
pauseAlert(testDash.OrgId, 1, false)
Convey("the NewStateDate should be updated again", func() {
alert, _ := getAlertById(1)
stateDateAfterUnpause := alert.NewStateDate
So(stateDateAfterPause, ShouldHappenBefore, stateDateAfterUnpause)
})
})
})
}
func pauseAlert(orgId int64, alertId int64, pauseState bool) (int64, error) {
cmd := &m.PauseAlertCommand{
OrgId: orgId,
AlertIds: []int64{alertId},
Paused: pauseState,
}
err := PauseAlert(cmd)
So(err, ShouldBeNil)
return cmd.ResultCount, err
}
func insertTestAlert(title string, message string, orgId int64, dashId int64, settings *simplejson.Json) (*m.Alert, error) {
items := []*m.Alert{
{
PanelId: 1,
DashboardId: dashId,
OrgId: orgId,
Name: title,
Message: message,
Settings: settings,
Frequency: 1,
},
}
cmd := m.SaveAlertsCommand{
Alerts: items,
DashboardId: dashId,
OrgId: orgId,
UserId: 1,
}
err := SaveAlerts(&cmd)
return cmd.Alerts[0], err
}
func getAlertById(id int64) (*m.Alert, error) {
q := &m.GetAlertByIdQuery{
Id: id,
}
err := GetAlertById(q)
So(err, ShouldBeNil)
return q.Result, err
}
func pauseAllAlerts(pauseState bool) error {
cmd := &m.PauseAllAlertCommand{
Paused: pauseState,
}
err := PauseAllAlerts(cmd)
So(err, ShouldBeNil)
return err
}
+12
View File
@@ -138,6 +138,17 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
sql.WriteString(`WHERE annotation.org_id = ?`)
params = append(params, query.OrgId)
if query.AnnotationId != 0 {
fmt.Print("annotation query")
sql.WriteString(` AND annotation.id = ?`)
params = append(params, query.AnnotationId)
}
if query.RegionId != 0 {
sql.WriteString(` AND annotation.region_id = ?`)
params = append(params, query.RegionId)
}
if query.AlertId != 0 {
sql.WriteString(` AND annotation.alert_id = ?`)
params = append(params, query.AlertId)
@@ -197,6 +208,7 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I
sql.WriteString(fmt.Sprintf(" ORDER BY epoch DESC LIMIT %v", query.Limit))
items := make([]*annotations.ItemDTO, 0)
if err := x.Sql(sql.String(), params...).Find(&items); err != nil {
return nil, err
}
+36
View File
@@ -51,6 +51,20 @@ func TestAnnotations(t *testing.T) {
So(err, ShouldBeNil)
So(annotation.Id, ShouldBeGreaterThan, 0)
annotation2 := &annotations.Item{
OrgId: 1,
UserId: 1,
DashboardId: 2,
Text: "hello",
Type: "alert",
Epoch: 20,
Tags: []string{"outage", "error", "type:outage", "server:server-1"},
RegionId: 1,
}
err = repo.Save(annotation2)
So(err, ShouldBeNil)
So(annotation2.Id, ShouldBeGreaterThan, 0)
Convey("Can query for annotation", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
@@ -67,6 +81,28 @@ func TestAnnotations(t *testing.T) {
})
})
Convey("Can query for annotation by id", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
AnnotationId: annotation2.Id,
})
So(err, ShouldBeNil)
So(items, ShouldHaveLength, 1)
So(items[0].Id, ShouldEqual, annotation2.Id)
})
Convey("Can query for annotation by region id", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
RegionId: annotation2.RegionId,
})
So(err, ShouldBeNil)
So(items, ShouldHaveLength, 1)
So(items[0].Id, ShouldEqual, annotation2.Id)
})
Convey("Should not find any when item is outside time range", func() {
items, err := repo.Find(&annotations.ItemQuery{
OrgId: 1,
+369 -134
View File
@@ -1,12 +1,14 @@
package sqlstore
import (
"strings"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/metrics"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/util"
)
func init() {
@@ -17,155 +19,155 @@ func init() {
bus.AddHandler("sql", SearchDashboards)
bus.AddHandler("sql", GetDashboardTags)
bus.AddHandler("sql", GetDashboardSlugById)
bus.AddHandler("sql", GetDashboardUIDById)
bus.AddHandler("sql", GetDashboardsByPluginId)
bus.AddHandler("sql", GetDashboardPermissionsForUser)
bus.AddHandler("sql", GetDashboardsBySlug)
bus.AddHandler("sql", ValidateDashboardBeforeSave)
}
var generateNewUid func() string = util.GenerateShortUid
func SaveDashboard(cmd *m.SaveDashboardCommand) error {
return inTransaction(func(sess *DBSession) error {
dash := cmd.GetDashboardModel()
// try get existing dashboard
var existing, sameTitle m.Dashboard
if dash.Id > 0 {
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
if err != nil {
return err
}
if !dashWithIdExists {
return m.ErrDashboardNotFound
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
dash.Version = existing.Version
} else {
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)
if err != nil {
return err
}
if sameTitleExists {
// another dashboard with same name
if dash.Id != sameTitle.Id {
if cmd.Overwrite {
dash.Id = sameTitle.Id
dash.Version = sameTitle.Version
} else {
return m.ErrDashboardWithSameNameExists
}
}
}
err = setHasAcl(sess, dash)
if err != nil {
return err
}
parentVersion := dash.Version
affectedRows := int64(0)
if dash.Id == 0 {
dash.Version = 1
metrics.M_Api_Dashboard_Insert.Inc()
dash.Data.Set("version", dash.Version)
affectedRows, err = sess.Insert(dash)
} else {
dash.Version++
dash.Data.Set("version", dash.Version)
if !cmd.UpdatedAt.IsZero() {
dash.Updated = cmd.UpdatedAt
}
affectedRows, err = sess.MustCols("folder_id", "has_acl").Id(dash.Id).Update(dash)
}
if err != nil {
return err
}
if affectedRows == 0 {
return m.ErrDashboardNotFound
}
dashVersion := &m.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: parentVersion,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
// insert version entry
if affectedRows, err = sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return m.ErrDashboardNotFound
}
// delete existing tags
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
if err != nil {
return err
}
// insert new tags
tags := dash.GetTags()
if len(tags) > 0 {
for _, tag := range tags {
if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
return err
}
}
}
cmd.Result = dash
return err
return saveDashboard(sess, cmd)
})
}
func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
// check if parent has acl
if dash.FolderId > 0 {
var parent m.Dashboard
if hasParent, err := sess.Where("folder_id=?", dash.FolderId).Get(&parent); err != nil {
func saveDashboard(sess *DBSession, cmd *m.SaveDashboardCommand) error {
dash := cmd.GetDashboardModel()
userId := cmd.UserId
if userId == 0 {
userId = -1
}
if dash.Id > 0 {
var existing m.Dashboard
dashWithIdExists, err := sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existing)
if err != nil {
return err
} else if hasParent && parent.HasAcl {
dash.HasAcl = true
}
if !dashWithIdExists {
return m.ErrDashboardNotFound
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
dash.SetVersion(existing.Version)
} else {
return m.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && cmd.Overwrite == false {
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
}
// check if dash has its own acl
if dash.Id > 0 {
if res, err := sess.Query("SELECT 1 from dashboard_acl WHERE dashboard_id =?", dash.Id); err != nil {
if dash.Uid == "" {
uid, err := generateNewDashboardUid(sess, dash.OrgId)
if err != nil {
return err
}
dash.SetUid(uid)
}
parentVersion := dash.Version
affectedRows := int64(0)
var err error
if dash.Id == 0 {
dash.SetVersion(1)
dash.Created = time.Now()
dash.CreatedBy = userId
dash.Updated = time.Now()
dash.UpdatedBy = userId
metrics.M_Api_Dashboard_Insert.Inc()
affectedRows, err = sess.Insert(dash)
} else {
dash.SetVersion(dash.Version + 1)
if !cmd.UpdatedAt.IsZero() {
dash.Updated = cmd.UpdatedAt
} else {
if len(res) > 0 {
dash.HasAcl = true
dash.Updated = time.Now()
}
dash.UpdatedBy = userId
affectedRows, err = sess.MustCols("folder_id").ID(dash.Id).Update(dash)
}
if err != nil {
return err
}
if affectedRows == 0 {
return m.ErrDashboardNotFound
}
dashVersion := &m.DashboardVersion{
DashboardId: dash.Id,
ParentVersion: parentVersion,
RestoredFrom: cmd.RestoredFrom,
Version: dash.Version,
Created: time.Now(),
CreatedBy: dash.UpdatedBy,
Message: cmd.Message,
Data: dash.Data,
}
// insert version entry
if affectedRows, err = sess.Insert(dashVersion); err != nil {
return err
} else if affectedRows == 0 {
return m.ErrDashboardNotFound
}
// delete existing tags
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
if err != nil {
return err
}
// insert new tags
tags := dash.GetTags()
if len(tags) > 0 {
for _, tag := range tags {
if _, err := sess.Insert(&DashboardTag{DashboardId: dash.Id, Term: tag}); err != nil {
return err
}
}
}
return nil
cmd.Result = dash
return err
}
func generateNewDashboardUid(sess *DBSession, orgId int64) (string, error) {
for i := 0; i < 3; i++ {
uid := generateNewUid()
exists, err := sess.Where("org_id=? AND uid=?", orgId, uid).Get(&m.Dashboard{})
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", m.ErrDashboardFailedGenerateUniqueUid
}
func GetDashboard(query *m.GetDashboardQuery) error {
dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id}
dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id, Uid: query.Uid}
has, err := x.Get(&dashboard)
if err != nil {
@@ -174,18 +176,21 @@ func GetDashboard(query *m.GetDashboardQuery) error {
return m.ErrDashboardNotFound
}
dashboard.Data.Set("id", dashboard.Id)
dashboard.SetId(dashboard.Id)
dashboard.SetUid(dashboard.Uid)
query.Result = &dashboard
return nil
}
type DashboardSearchProjection struct {
Id int64
Uid string
Title string
Slug string
Term string
IsFolder bool
FolderId int64
FolderUid string
FolderSlug string
FolderTitle string
}
@@ -196,7 +201,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
limit = 1000
}
sb := NewSearchBuilder(query.SignedInUser, limit).
sb := NewSearchBuilder(query.SignedInUser, limit, query.Permission).
WithTags(query.Tags).
WithDashboardIdsIn(query.DashboardIds)
@@ -258,15 +263,21 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
if !exists {
hit = &search.Hit{
Id: item.Id,
Uid: item.Uid,
Title: item.Title,
Uri: "db/" + item.Slug,
Slug: item.Slug,
Url: m.GetDashboardFolderUrl(item.IsFolder, item.Uid, item.Slug),
Type: getHitType(item),
FolderId: item.FolderId,
FolderUid: item.FolderUid,
FolderTitle: item.FolderTitle,
FolderSlug: item.FolderSlug,
Tags: []string{},
}
if item.FolderId > 0 {
hit.FolderUrl = m.GetFolderUrl(item.FolderUid, item.FolderSlug)
}
query.Result = append(query.Result, hit)
hits[item.Id] = hit
}
@@ -309,6 +320,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
"DELETE FROM dashboard_version WHERE dashboard_id = ?",
"DELETE FROM dashboard WHERE folder_id = ?",
"DELETE FROM annotation WHERE dashboard_id = ?",
"DELETE FROM dashboard_provisioning WHERE dashboard_id = ?",
}
for _, sql := range deletes {
@@ -318,7 +330,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
}
}
if err := DeleteAlertDefinition(dashboard.Id, sess); err != nil {
if err := deleteAlertDefinition(dashboard.Id, sess); err != nil {
return nil
}
@@ -343,6 +355,76 @@ func GetDashboards(query *m.GetDashboardsQuery) error {
return nil
}
// GetDashboardPermissionsForUser returns the maximum permission the specified user has for a dashboard(s)
// The function takes in a list of dashboard ids and the user id and role
func GetDashboardPermissionsForUser(query *m.GetDashboardPermissionsForUserQuery) error {
if len(query.DashboardIds) == 0 {
return m.ErrCommandValidationFailed
}
if query.OrgRole == m.ROLE_ADMIN {
var permissions = make([]*m.DashboardPermissionForUser, 0)
for _, d := range query.DashboardIds {
permissions = append(permissions, &m.DashboardPermissionForUser{
DashboardId: d,
Permission: m.PERMISSION_ADMIN,
PermissionName: m.PERMISSION_ADMIN.String(),
})
}
query.Result = permissions
return nil
}
params := make([]interface{}, 0)
// check dashboards that have ACLs via user id, team id or role
sql := `SELECT d.id AS dashboard_id, MAX(COALESCE(da.permission, pt.permission)) AS permission
FROM dashboard AS d
LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ?
`
params = append(params, query.UserId)
//check the user's role for dashboards that do not have hasAcl set
sql += `LEFT JOIN org_user ouRole ON ouRole.user_id = ? AND ouRole.org_id = ?`
params = append(params, query.UserId)
params = append(params, query.OrgId)
sql += `
LEFT JOIN (SELECT 1 AS permission, 'Viewer' AS role
UNION SELECT 2 AS permission, 'Editor' AS role
UNION SELECT 4 AS permission, 'Admin' AS role) pt ON ouRole.role = pt.role
WHERE
d.Id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `) `
for _, id := range query.DashboardIds {
params = append(params, id)
}
sql += ` AND
d.org_id = ? AND
(
(d.has_acl = ? AND (da.user_id = ? OR ugm.user_id = ? OR ou.id IS NOT NULL))
OR (d.has_acl = ? AND ouRole.id IS NOT NULL)
)
group by d.id
order by d.id asc`
params = append(params, query.OrgId)
params = append(params, dialect.BooleanStr(true))
params = append(params, query.UserId)
params = append(params, query.UserId)
params = append(params, dialect.BooleanStr(false))
err := x.Sql(sql, params...).Find(&query.Result)
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
}
return err
}
func GetDashboardsByPluginId(query *m.GetDashboardsByPluginIdQuery) error {
var dashboards = make([]*m.Dashboard, 0)
whereExpr := "org_id=? AND plugin_id=? AND is_folder=" + dialect.BooleanStr(false)
@@ -365,7 +447,7 @@ func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
var rawSql = `SELECT slug from dashboard WHERE Id=?`
var slug = DashboardSlugDTO{}
exists, err := x.Sql(rawSql, query.Id).Get(&slug)
exists, err := x.SQL(rawSql, query.Id).Get(&slug)
if err != nil {
return err
@@ -376,3 +458,156 @@ func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
query.Result = slug.Slug
return nil
}
func GetDashboardsBySlug(query *m.GetDashboardsBySlugQuery) error {
var dashboards []*m.Dashboard
if err := x.Where("org_id=? AND slug=?", query.OrgId, query.Slug).Find(&dashboards); err != nil {
return err
}
query.Result = dashboards
return nil
}
func GetDashboardUIDById(query *m.GetDashboardRefByIdQuery) error {
var rawSql = `SELECT uid, slug from dashboard WHERE Id=?`
us := &m.DashboardRef{}
exists, err := x.SQL(rawSql, query.Id).Get(us)
if err != nil {
return err
} else if exists == false {
return m.ErrDashboardNotFound
}
query.Result = us
return nil
}
func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
dash := cmd.Dashboard
dashWithIdExists := false
var existingById m.Dashboard
if dash.Id > 0 {
dashWithIdExists, err = sess.Where("id=? AND org_id=?", dash.Id, dash.OrgId).Get(&existingById)
if err != nil {
return err
}
if !dashWithIdExists {
return m.ErrDashboardNotFound
}
if dash.Uid == "" {
dash.SetUid(existingById.Uid)
}
}
dashWithUidExists := false
var existingByUid m.Dashboard
if dash.Uid != "" {
dashWithUidExists, err = sess.Where("org_id=? AND uid=?", dash.OrgId, dash.Uid).Get(&existingByUid)
if err != nil {
return err
}
}
if dash.FolderId > 0 {
var existingFolder m.Dashboard
folderExists, folderErr := sess.Where("org_id=? AND id=? AND is_folder=?", dash.OrgId, dash.FolderId, dialect.BooleanStr(true)).Get(&existingFolder)
if folderErr != nil {
return folderErr
}
if !folderExists {
return m.ErrDashboardFolderNotFound
}
}
if !dashWithIdExists && !dashWithUidExists {
return nil
}
if dashWithIdExists && dashWithUidExists && existingById.Id != existingByUid.Id {
return m.ErrDashboardWithSameUIDExists
}
existing := existingById
if !dashWithIdExists && dashWithUidExists {
dash.SetId(existingByUid.Id)
dash.SetUid(existingByUid.Uid)
existing = existingByUid
}
if (existing.IsFolder && !dash.IsFolder) ||
(!existing.IsFolder && dash.IsFolder) {
return m.ErrDashboardTypeMismatch
}
// check for is someone else has written in between
if dash.Version != existing.Version {
if cmd.Overwrite {
dash.SetVersion(existing.Version)
} else {
return m.ErrDashboardVersionMismatch
}
}
// do not allow plugin dashboard updates without overwrite flag
if existing.PluginId != "" && cmd.Overwrite == false {
return m.UpdatePluginDashboardError{PluginId: existing.PluginId}
}
return nil
}
func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashboardBeforeSaveCommand) error {
dash := cmd.Dashboard
var existing m.Dashboard
exists, err := sess.Where("org_id=? AND slug=? AND (is_folder=? OR folder_id=?)", dash.OrgId, dash.Slug, dialect.BooleanStr(true), dash.FolderId).Get(&existing)
if err != nil {
return err
}
if exists && dash.Id != existing.Id {
if existing.IsFolder && !dash.IsFolder {
return m.ErrDashboardWithSameNameAsFolder
}
if !existing.IsFolder && dash.IsFolder {
return m.ErrDashboardFolderWithSameNameAsDashboard
}
if cmd.Overwrite {
dash.SetId(existing.Id)
dash.SetUid(existing.Uid)
dash.SetVersion(existing.Version)
} else {
return m.ErrDashboardWithSameNameInFolderExists
}
}
return nil
}
func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
return inTransaction(func(sess *DBSession) error {
if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
return err
}
if err = getExistingDashboardByTitleAndFolder(sess, cmd); err != nil {
return err
}
return nil
})
}
+63 -126
View File
@@ -1,17 +1,12 @@
package sqlstore
import (
"fmt"
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", SetDashboardAcl)
bus.AddHandler("sql", UpdateDashboardAcl)
bus.AddHandler("sql", RemoveDashboardAcl)
bus.AddHandler("sql", GetDashboardAclInfoList)
}
@@ -24,7 +19,7 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
}
for _, item := range cmd.Items {
if item.UserId == 0 && item.TeamId == 0 && !item.Role.IsValid() {
if item.UserId == 0 && item.TeamId == 0 && (item.Role == nil || !item.Role.IsValid()) {
return m.ErrDashboardAclInfoMissing
}
@@ -40,100 +35,25 @@ func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
// Update dashboard HasAcl flag
dashboard := m.Dashboard{HasAcl: true}
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
if _, err := sess.Cols("has_acl").Where("id=?", cmd.DashboardId).Update(&dashboard); err != nil {
return err
}
return nil
})
}
func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
return inTransaction(func(sess *DBSession) error {
if cmd.UserId == 0 && cmd.TeamId == 0 {
return m.ErrDashboardAclInfoMissing
}
if cmd.DashboardId == 0 {
return m.ErrDashboardPermissionDashboardEmpty
}
if res, err := sess.Query("SELECT 1 from "+dialect.Quote("dashboard_acl")+" WHERE dashboard_id =? and (team_id=? or user_id=?)", cmd.DashboardId, cmd.TeamId, cmd.UserId); err != nil {
return err
} else if len(res) == 1 {
entity := m.DashboardAcl{
Permission: cmd.Permission,
Updated: time.Now(),
}
if _, err := sess.Cols("updated", "permission").Where("dashboard_id =? and (team_id=? or user_id=?)", cmd.DashboardId, cmd.TeamId, cmd.UserId).Update(&entity); err != nil {
return err
}
return nil
}
entity := m.DashboardAcl{
OrgId: cmd.OrgId,
TeamId: cmd.TeamId,
UserId: cmd.UserId,
Created: time.Now(),
Updated: time.Now(),
DashboardId: cmd.DashboardId,
Permission: cmd.Permission,
}
cols := []string{"org_id", "created", "updated", "dashboard_id", "permission"}
if cmd.UserId != 0 {
cols = append(cols, "user_id")
}
if cmd.TeamId != 0 {
cols = append(cols, "team_id")
}
_, err := sess.Cols(cols...).Insert(&entity)
if err != nil {
return err
}
cmd.Result = entity
// Update dashboard HasAcl flag
dashboard := m.Dashboard{
HasAcl: true,
}
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
return err
}
return nil
})
}
func RemoveDashboardAcl(cmd *m.RemoveDashboardAclCommand) error {
return inTransaction(func(sess *DBSession) error {
var rawSQL = "DELETE FROM " + dialect.Quote("dashboard_acl") + " WHERE org_id =? and id=?"
_, err := sess.Exec(rawSQL, cmd.OrgId, cmd.AclId)
if err != nil {
return err
}
return err
})
}
// GetDashboardAclInfoList returns a list of permissions for a dashboard. They can be fetched from three
// different places.
// 1) Permissions for the dashboard
// 2) permissions for its parent folder
// 3) if no specific permissions have been set for the dashboard or its parent folder then get the default permissions
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
dashboardFilter := fmt.Sprintf(`IN (
SELECT %d
UNION
SELECT folder_id from dashboard where id = %d
)`, query.DashboardId, query.DashboardId)
var err error
rawSQL := `
SELECT
falseStr := dialect.BooleanStr(false)
if query.DashboardId == 0 {
sql := `SELECT
da.id,
da.org_id,
da.dashboard_id,
@@ -143,43 +63,60 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team
FROM` + dialect.Quote("dashboard_acl") + ` as da
LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT OUTER JOIN team ug on ug.id = da.team_id
WHERE dashboard_id ` + dashboardFilter + ` AND da.org_id = ?
'' as user_login,
'' as user_email,
'' as team,
'' as title,
'' as slug,
'' as uid,` +
falseStr + ` AS is_folder
FROM dashboard_acl as da
WHERE da.dashboard_id = -1`
query.Result = make([]*m.DashboardAclInfoDTO, 0)
err = x.SQL(sql).Find(&query.Result)
-- Also include default permission if has_acl = 0
} else {
UNION
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
'' as user_login,
'' as user_email,
'' as team
FROM dashboard_acl as da,
dashboard as dash
LEFT JOIN dashboard folder on dash.folder_id = folder.id
WHERE
dash.id = ? AND (
dash.has_acl = ` + dialect.BooleanStr(false) + ` or
folder.has_acl = ` + dialect.BooleanStr(false) + `
) AND
da.dashboard_id = -1
`
rawSQL := `
-- get permissions for the dashboard and its parent folder
SELECT
da.id,
da.org_id,
da.dashboard_id,
da.user_id,
da.team_id,
da.permission,
da.role,
da.created,
da.updated,
u.login AS user_login,
u.email AS user_email,
ug.name AS team,
d.title,
d.slug,
d.uid,
d.is_folder
FROM dashboard as d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
LEFT JOIN team ug on ug.id = da.team_id
WHERE d.org_id = ? AND d.id = ? AND da.id IS NOT NULL
ORDER BY 1 ASC
`
query.Result = make([]*m.DashboardAclInfoDTO, 0)
err := x.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
query.Result = make([]*m.DashboardAclInfoDTO, 0)
err = x.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
}
for _, p := range query.Result {
p.PermissionName = p.Permission.String()
+44 -63
View File
@@ -17,7 +17,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
Convey("When adding dashboard permission with userId and teamId set to 0", func() {
err := SetDashboardAcl(&m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
OrgId: 1,
DashboardId: savedFolder.Id,
Permission: m.PERMISSION_EDIT,
@@ -41,8 +41,25 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
})
Convey("Given dashboard folder with removed default permissions", func() {
err := UpdateDashboardAcl(&m.UpdateDashboardAclCommand{
DashboardId: savedFolder.Id,
Items: []*m.DashboardAcl{},
})
So(err, ShouldBeNil)
Convey("When reading dashboard acl should return no acl items", func() {
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
err := GetDashboardAclInfoList(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 0)
})
})
Convey("Given dashboard folder permission", func() {
err := SetDashboardAcl(&m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
OrgId: 1,
UserId: currentUser.Id,
DashboardId: savedFolder.Id,
@@ -61,7 +78,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
Convey("Given child dashboard permission", func() {
err := SetDashboardAcl(&m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{
OrgId: 1,
UserId: currentUser.Id,
DashboardId: childDash.Id,
@@ -83,7 +100,7 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
Convey("Given child dashboard permission in folder with no permissions", func() {
err := SetDashboardAcl(&m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{
OrgId: 1,
UserId: currentUser.Id,
DashboardId: childDash.Id,
@@ -108,17 +125,12 @@ func TestDashboardAclDataAccess(t *testing.T) {
})
Convey("Should be able to add dashboard permission", func() {
setDashAclCmd := m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
OrgId: 1,
UserId: currentUser.Id,
DashboardId: savedFolder.Id,
Permission: m.PERMISSION_EDIT,
}
err := SetDashboardAcl(&setDashAclCmd)
So(err, ShouldBeNil)
So(setDashAclCmd.Result.Id, ShouldEqual, 3)
})
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
err = GetDashboardAclInfoList(q1)
@@ -130,42 +142,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(q1.Result[0].UserId, ShouldEqual, currentUser.Id)
So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login)
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
So(q1.Result[0].Id, ShouldEqual, setDashAclCmd.Result.Id)
Convey("Should update hasAcl field to true for dashboard folder and its children", func() {
q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}}
err := GetDashboards(q2)
So(err, ShouldBeNil)
So(q2.Result[0].HasAcl, ShouldBeTrue)
So(q2.Result[1].HasAcl, ShouldBeTrue)
})
Convey("Should be able to update an existing permission", func() {
err := SetDashboardAcl(&m.SetDashboardAclCommand{
OrgId: 1,
UserId: 1,
DashboardId: savedFolder.Id,
Permission: m.PERMISSION_ADMIN,
})
So(err, ShouldBeNil)
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
err = GetDashboardAclInfoList(q3)
So(err, ShouldBeNil)
So(len(q3.Result), ShouldEqual, 1)
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
So(q3.Result[0].UserId, ShouldEqual, 1)
})
Convey("Should be able to delete an existing permission", func() {
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
OrgId: 1,
AclId: setDashAclCmd.Result.Id,
})
err := testHelperUpdateDashboardAcl(savedFolder.Id)
So(err, ShouldBeNil)
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
@@ -181,14 +160,12 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(err, ShouldBeNil)
Convey("Should be able to add a user permission for a team", func() {
setDashAclCmd := m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
OrgId: 1,
TeamId: group1.Result.Id,
DashboardId: savedFolder.Id,
Permission: m.PERMISSION_EDIT,
}
err := SetDashboardAcl(&setDashAclCmd)
})
So(err, ShouldBeNil)
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
@@ -197,23 +174,10 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
So(q1.Result[0].TeamId, ShouldEqual, group1.Result.Id)
Convey("Should be able to delete an existing permission for a team", func() {
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
OrgId: 1,
AclId: setDashAclCmd.Result.Id,
})
So(err, ShouldBeNil)
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
err = GetDashboardAclInfoList(q3)
So(err, ShouldBeNil)
So(len(q3.Result), ShouldEqual, 0)
})
})
Convey("Should be able to update an existing permission for a team", func() {
err := SetDashboardAcl(&m.SetDashboardAclCommand{
err := testHelperUpdateDashboardAcl(savedFolder.Id, m.DashboardAcl{
OrgId: 1,
TeamId: group1.Result.Id,
DashboardId: savedFolder.Id,
@@ -229,7 +193,24 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
So(q3.Result[0].TeamId, ShouldEqual, group1.Result.Id)
})
})
})
Convey("Given a root folder", func() {
var rootFolderId int64 = 0
Convey("When reading dashboard acl should return default permissions", func() {
query := m.GetDashboardAclInfoListQuery{DashboardId: rootFolderId, OrgId: 1}
err := GetDashboardAclInfoList(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
defaultPermissionsId := -1
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
})
})
})
@@ -0,0 +1,358 @@
package sqlstore
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
)
func TestDashboardFolderDataAccess(t *testing.T) {
Convey("Testing DB", t, func() {
InitTestDB(t)
Convey("Given one dashboard folder with two dashboards and one dashboard in the root folder", func() {
folder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
childDash := insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
currentUser := createUser("viewer", "Viewer", false)
Convey("and no acls are set", func() {
Convey("should return all dashboards", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and acl is set for dashboard folder", func() {
var otherUser int64 = 999
testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
Convey("should not return folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
Convey("when the user is given permission", func() {
testHelperUpdateDashboardAcl(folder.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT})
Convey("should be able to access folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("when the user is an admin", func() {
Convey("should be able to access folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{
UserId: currentUser.Id,
OrgId: 1,
OrgRole: m.ROLE_ADMIN,
},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
})
Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
var otherUser int64 = 999
testHelperUpdateDashboardAcl(folder.Id)
testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: folder.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
Convey("should not return folder or child", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
Convey("when the user is given permission to child", func() {
testHelperUpdateDashboardAcl(childDash.Id, m.DashboardAcl{DashboardId: childDash.Id, OrgId: 1, UserId: currentUser.Id, Permission: m.PERMISSION_EDIT})
Convey("should be able to search for child dashboard but not folder", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, childDash.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("when the user is an admin", func() {
Convey("should be able to search for child dash and folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{
UserId: currentUser.Id,
OrgId: 1,
OrgRole: m.ROLE_ADMIN,
},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id, childDash.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, childDash.Id)
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
})
})
})
})
Convey("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func() {
folder1 := insertTestDashboard("1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard("2 test dash folder", 1, 0, true, "prod")
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod")
childDash1 := insertTestDashboard("child dash 1", 1, folder1.Id, false, "prod")
childDash2 := insertTestDashboard("child dash 2", 1, folder2.Id, false, "prod")
currentUser := createUser("viewer", "Viewer", false)
var rootFolderId int64 = 0
Convey("and one folder is expanded, the other collapsed", func() {
Convey("should return dashboards in root and expanded folder", func() {
query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER}, OrgId: 1}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 4)
So(query.Result[0].Id, ShouldEqual, folder1.Id)
So(query.Result[1].Id, ShouldEqual, folder2.Id)
So(query.Result[2].Id, ShouldEqual, childDash1.Id)
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and acl is set for one dashboard folder", func() {
var otherUser int64 = 999
testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
moveDashboard(1, childDash2.Data, folder1.Id)
Convey("should not return folder with acl or its children", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
OrgId: 1,
DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() {
moveDashboard(1, childDash1.Data, folder2.Id)
Convey("should return folder without acl and its children", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
OrgId: 1,
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 4)
So(query.Result[0].Id, ShouldEqual, folder2.Id)
So(query.Result[1].Id, ShouldEqual, childDash1.Id)
So(query.Result[2].Id, ShouldEqual, childDash2.Id)
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
testHelperUpdateDashboardAcl(childDash1.Id, m.DashboardAcl{DashboardId: childDash1.Id, OrgId: 1, UserId: otherUser, Permission: m.PERMISSION_EDIT})
moveDashboard(1, childDash1.Data, folder2.Id)
Convey("should return folder without acl but not the dashboard with acl", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1, OrgRole: m.ROLE_VIEWER},
OrgId: 1,
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 4)
So(query.Result[0].Id, ShouldEqual, folder2.Id)
So(query.Result[1].Id, ShouldEqual, childDash1.Id)
So(query.Result[2].Id, ShouldEqual, childDash2.Id)
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
})
})
})
})
Convey("Given two dashboard folders", func() {
folder1 := insertTestDashboard("1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard("2 test dash folder", 1, 0, true, "prod")
insertTestDashboard("folder in another org", 2, 0, true, "prod")
adminUser := createUser("admin", "Admin", true)
editorUser := createUser("editor", "Editor", false)
viewerUser := createUser("viewer", "Viewer", false)
Convey("Admin users", func() {
Convey("Should have write access to all dashboard folders in their org", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgRole: m.ROLE_ADMIN, OrgId: 1},
Permission: m.PERMISSION_VIEW,
Type: "dash-folder",
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder1.Id)
So(query.Result[1].Id, ShouldEqual, folder2.Id)
})
Convey("should have write access to all folders and dashboards", func() {
query := m.GetDashboardPermissionsForUserQuery{
DashboardIds: []int64{folder1.Id, folder2.Id},
OrgId: 1,
UserId: adminUser.Id,
OrgRole: m.ROLE_ADMIN,
}
err := GetDashboardPermissionsForUser(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
So(query.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_ADMIN)
})
})
Convey("Editor users", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgRole: m.ROLE_EDITOR, OrgId: 1},
Permission: m.PERMISSION_EDIT,
}
Convey("Should have write access to all dashboard folders with default ACL", func() {
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder1.Id)
So(query.Result[1].Id, ShouldEqual, folder2.Id)
})
Convey("should have edit access to folders with default ACL", func() {
query := m.GetDashboardPermissionsForUserQuery{
DashboardIds: []int64{folder1.Id, folder2.Id},
OrgId: 1,
UserId: editorUser.Id,
OrgRole: m.ROLE_EDITOR,
}
err := GetDashboardPermissionsForUser(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
So(query.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_EDIT)
})
Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: editorUser.Id, Permission: m.PERMISSION_VIEW})
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, folder2.Id)
})
})
Convey("Viewer users", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgRole: m.ROLE_VIEWER, OrgId: 1},
Permission: m.PERMISSION_EDIT,
}
Convey("Should have no write access to any dashboard folders with default ACL", func() {
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 0)
})
Convey("should have view access to folders with default ACL", func() {
query := m.GetDashboardPermissionsForUserQuery{
DashboardIds: []int64{folder1.Id, folder2.Id},
OrgId: 1,
UserId: viewerUser.Id,
OrgRole: m.ROLE_VIEWER,
}
err := GetDashboardPermissionsForUser(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].DashboardId, ShouldEqual, folder1.Id)
So(query.Result[0].Permission, ShouldEqual, m.PERMISSION_VIEW)
So(query.Result[1].DashboardId, ShouldEqual, folder2.Id)
So(query.Result[1].Permission, ShouldEqual, m.PERMISSION_VIEW)
})
Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
testHelperUpdateDashboardAcl(folder1.Id, m.DashboardAcl{DashboardId: folder1.Id, OrgId: 1, UserId: viewerUser.Id, Permission: m.PERMISSION_EDIT})
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, folder1.Id)
})
})
})
})
}
@@ -0,0 +1,66 @@
package sqlstore
import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
bus.AddHandler("sql", SaveProvisionedDashboard)
}
type DashboardExtras struct {
Id int64
DashboardId int64
Key string
Value string
}
func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error {
return inTransaction(func(sess *DBSession) error {
err := saveDashboard(sess, cmd.DashboardCmd)
if err != nil {
return err
}
cmd.Result = cmd.DashboardCmd.Result
if cmd.DashboardProvisioning.Updated == 0 {
cmd.DashboardProvisioning.Updated = cmd.Result.Updated.Unix()
}
return saveProvionedData(sess, cmd.DashboardProvisioning, cmd.Result)
})
}
func saveProvionedData(sess *DBSession, cmd *models.DashboardProvisioning, dashboard *models.Dashboard) error {
result := &models.DashboardProvisioning{}
exist, err := sess.Where("dashboard_id=?", dashboard.Id).Get(result)
if err != nil {
return err
}
cmd.Id = result.Id
cmd.DashboardId = dashboard.Id
if exist {
_, err = sess.ID(result.Id).Update(cmd)
} else {
_, err = sess.Insert(cmd)
}
return err
}
func GetProvisionedDashboardDataQuery(cmd *models.GetProvisionedDashboardDataQuery) error {
var result []*models.DashboardProvisioning
if err := x.Where("name = ?", cmd.Name).Find(&result); err != nil {
return err
}
cmd.Result = result
return nil
}
@@ -0,0 +1,55 @@
package sqlstore
import (
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardProvisioningTest(t *testing.T) {
Convey("Testing Dashboard provisioning", t, func() {
InitTestDB(t)
saveDashboardCmd := &models.SaveDashboardCommand{
OrgId: 1,
FolderId: 0,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "test dashboard",
}),
}
Convey("Saving dashboards with extras", func() {
now := time.Now()
cmd := &models.SaveProvisionedDashboardCommand{
DashboardCmd: saveDashboardCmd,
DashboardProvisioning: &models.DashboardProvisioning{
Name: "default",
ExternalId: "/var/grafana.json",
Updated: now.Unix(),
},
}
err := SaveProvisionedDashboard(cmd)
So(err, ShouldBeNil)
So(cmd.Result, ShouldNotBeNil)
So(cmd.Result.Id, ShouldNotEqual, 0)
dashId := cmd.Result.Id
Convey("Can query for provisioned dashboards", func() {
query := &models.GetProvisionedDashboardDataQuery{Name: "default"}
err := GetProvisionedDashboardDataQuery(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].DashboardId, ShouldEqual, dashId)
So(query.Result[0].Updated, ShouldEqual, now.Unix())
})
})
})
}
@@ -0,0 +1,932 @@
package sqlstore
import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestIntegratedDashboardService(t *testing.T) {
Convey("Dashboard service integration tests", t, func() {
InitTestDB(t)
var testOrgId int64 = 1
Convey("Given saved folders and dashboards in organization A", func() {
bus.AddHandler("test", func(cmd *models.ValidateDashboardAlertsCommand) error {
return nil
})
bus.AddHandler("test", func(cmd *models.UpdateDashboardAlertsCommand) error {
return nil
})
savedFolder := saveTestFolder("Saved folder", testOrgId)
savedDashInFolder := saveTestDashboard("Saved dash in folder", testOrgId, savedFolder.Id)
saveTestDashboard("Other saved dash in folder", testOrgId, savedFolder.Id)
savedDashInGeneralFolder := saveTestDashboard("Saved dashboard in general folder", testOrgId, 0)
otherSavedFolder := saveTestFolder("Other saved folder", testOrgId)
Convey("Should return dashboard model", func() {
So(savedFolder.Title, ShouldEqual, "Saved folder")
So(savedFolder.Slug, ShouldEqual, "saved-folder")
So(savedFolder.Id, ShouldNotEqual, 0)
So(savedFolder.IsFolder, ShouldBeTrue)
So(savedFolder.FolderId, ShouldEqual, 0)
So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
So(savedDashInFolder.Title, ShouldEqual, "Saved dash in folder")
So(savedDashInFolder.Slug, ShouldEqual, "saved-dash-in-folder")
So(savedDashInFolder.Id, ShouldNotEqual, 0)
So(savedDashInFolder.IsFolder, ShouldBeFalse)
So(savedDashInFolder.FolderId, ShouldEqual, savedFolder.Id)
So(len(savedDashInFolder.Uid), ShouldBeGreaterThan, 0)
})
// Basic validation tests
Convey("When saving a dashboard with non-existing id", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(123412321),
"title": "Expect error",
}),
}
err := callSaveWithError(cmd)
Convey("It should result in not found error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardNotFound)
})
})
// Given other organization
Convey("Given organization B", func() {
var otherOrgId int64 = 2
Convey("When saving a dashboard with id that are saved in organization A", func() {
cmd := models.SaveDashboardCommand{
OrgId: otherOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInFolder.Id,
"title": "Expect error",
}),
Overwrite: false,
}
err := callSaveWithError(cmd)
Convey("It should result in not found error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardNotFound)
})
})
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
Convey("When saving a dashboard with uid that are saved in organization A", func() {
var otherOrgId int64 = 2
cmd := models.SaveDashboardCommand{
OrgId: otherOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": savedDashInFolder.Uid,
"title": "Dash with existing uid in other org",
}),
Overwrite: false,
}
res := callSaveWithResult(cmd)
Convey("It should create dashboard in other organization", func() {
So(res, ShouldNotBeNil)
query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Id, ShouldNotEqual, savedDashInFolder.Id)
So(query.Result.Id, ShouldEqual, res.Id)
So(query.Result.OrgId, ShouldEqual, otherOrgId)
So(query.Result.Uid, ShouldEqual, savedDashInFolder.Uid)
})
})
})
})
// Given user has no permission to save
permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
Convey("When trying to create a new dashboard in the General folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash",
}),
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd)
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
})
})
Convey("When trying to create a new dashboard in other folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash",
}),
FolderId: otherSavedFolder.Id,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd)
Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
})
})
Convey("When trying to update a dashboard by existing id in the General folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInGeneralFolder.Id,
"title": "Dash",
}),
FolderId: savedDashInGeneralFolder.FolderId,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd)
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInGeneralFolder.Id)
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
})
})
Convey("When trying to update a dashboard by existing id in other folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInFolder.Id,
"title": "Dash",
}),
FolderId: savedDashInFolder.FolderId,
UserId: 10000,
Overwrite: true,
}
err := callSaveWithError(cmd)
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedDashInFolder.Id)
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
})
})
})
// Given user has permission to save
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
Convey("and overwrite flag is set to false", func() {
shouldOverwrite := false
Convey("When creating a dashboard in General folder with same name as dashboard in other folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInFolder.Title,
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Id, ShouldEqual, res.Id)
So(query.Result.FolderId, ShouldEqual, 0)
})
})
Convey("When creating a dashboard in other folder with same name as dashboard in General folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInGeneralFolder.Title,
}),
FolderId: savedFolder.Id,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
})
})
Convey("When creating a folder with same name as dashboard in other folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInFolder.Title,
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new folder", func() {
So(res.Id, ShouldNotEqual, savedDashInGeneralFolder.Id)
So(res.IsFolder, ShouldBeTrue)
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.FolderId, ShouldEqual, 0)
So(query.Result.IsFolder, ShouldBeTrue)
})
})
Convey("When saving a dashboard without id and uid and unique title in folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Dash without id and uid",
}),
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
So(res.Id, ShouldBeGreaterThan, 0)
So(len(res.Uid), ShouldBeGreaterThan, 0)
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Id, ShouldEqual, res.Id)
So(query.Result.Uid, ShouldEqual, res.Uid)
})
})
Convey("When saving a dashboard when dashboard id is zero ", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": 0,
"title": "Dash with zero id",
}),
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should create a new dashboard", func() {
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Id, ShouldEqual, res.Id)
})
})
Convey("When saving a dashboard in non-existing folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": "Expect error",
}),
FolderId: 123412321,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in folder not found error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardFolderNotFound)
})
})
Convey("When updating an existing dashboard by id without current version", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInGeneralFolder.Id,
"title": "test dash 23",
}),
FolderId: savedFolder.Id,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in version mismatch error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
})
})
Convey("When updating an existing dashboard by id with current version", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInGeneralFolder.Id,
"title": "Updated title",
"version": savedDashInGeneralFolder.Version,
}),
FolderId: savedFolder.Id,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Title, ShouldEqual, "Updated title")
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
})
})
Convey("When updating an existing dashboard by uid without current version", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": savedDashInFolder.Uid,
"title": "test dash 23",
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in version mismatch error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardVersionMismatch)
})
})
Convey("When updating an existing dashboard by uid with current version", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": savedDashInFolder.Uid,
"title": "Updated title",
"version": savedDashInFolder.Version,
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Title, ShouldEqual, "Updated title")
So(query.Result.FolderId, ShouldEqual, 0)
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
})
})
Convey("When creating a dashboard with same name as dashboard in other folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInFolder.Title,
}),
FolderId: savedDashInFolder.FolderId,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in dashboard with same name in folder error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
})
})
Convey("When creating a dashboard with same name as dashboard in General folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInGeneralFolder.Title,
}),
FolderId: savedDashInGeneralFolder.FolderId,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in dashboard with same name in folder error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
})
})
Convey("When creating a folder with same name as existing folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedFolder.Title,
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in dashboard with same name in folder error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardWithSameNameInFolderExists)
})
})
})
Convey("and overwrite flag is set to true", func() {
shouldOverwrite := true
Convey("When updating an existing dashboard by id without current version", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInGeneralFolder.Id,
"title": "Updated title",
}),
FolderId: savedFolder.Id,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInGeneralFolder.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Title, ShouldEqual, "Updated title")
So(query.Result.FolderId, ShouldEqual, savedFolder.Id)
So(query.Result.Version, ShouldBeGreaterThan, savedDashInGeneralFolder.Version)
})
})
Convey("When updating an existing dashboard by uid without current version", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": savedDashInFolder.Uid,
"title": "Updated title",
}),
FolderId: 0,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
So(res, ShouldNotBeNil)
Convey("It should update dashboard", func() {
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Title, ShouldEqual, "Updated title")
So(query.Result.FolderId, ShouldEqual, 0)
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
})
})
Convey("When updating uid for existing dashboard using id", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInFolder.Id,
"uid": "new-uid",
"title": savedDashInFolder.Title,
}),
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
Convey("It should update dashboard", func() {
So(res, ShouldNotBeNil)
So(res.Id, ShouldEqual, savedDashInFolder.Id)
So(res.Uid, ShouldEqual, "new-uid")
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: savedDashInFolder.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Uid, ShouldEqual, "new-uid")
So(query.Result.Version, ShouldBeGreaterThan, savedDashInFolder.Version)
})
})
Convey("When updating uid to an existing uid for existing dashboard using id", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInFolder.Id,
"uid": savedDashInGeneralFolder.Uid,
"title": savedDashInFolder.Title,
}),
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in same uid exists error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardWithSameUIDExists)
})
})
Convey("When creating a dashboard with same name as dashboard in other folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInFolder.Title,
}),
FolderId: savedDashInFolder.FolderId,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
Convey("It should overwrite existing dashboard", func() {
So(res, ShouldNotBeNil)
So(res.Id, ShouldEqual, savedDashInFolder.Id)
So(res.Uid, ShouldEqual, savedDashInFolder.Uid)
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Id, ShouldEqual, res.Id)
So(query.Result.Uid, ShouldEqual, res.Uid)
})
})
Convey("When creating a dashboard with same name as dashboard in General folder", func() {
cmd := models.SaveDashboardCommand{
OrgId: testOrgId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": savedDashInGeneralFolder.Title,
}),
FolderId: savedDashInGeneralFolder.FolderId,
Overwrite: shouldOverwrite,
}
res := callSaveWithResult(cmd)
Convey("It should overwrite existing dashboard", func() {
So(res, ShouldNotBeNil)
So(res.Id, ShouldEqual, savedDashInGeneralFolder.Id)
So(res.Uid, ShouldEqual, savedDashInGeneralFolder.Uid)
query := models.GetDashboardQuery{OrgId: cmd.OrgId, Id: res.Id}
err := bus.Dispatch(&query)
So(err, ShouldBeNil)
So(query.Result.Id, ShouldEqual, res.Id)
So(query.Result.Uid, ShouldEqual, res.Uid)
})
})
Convey("When trying to update existing folder to a dashboard using id", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedFolder.Id,
"title": "new title",
}),
IsFolder: false,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in type mismatch error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
})
})
Convey("When trying to update existing dashboard to a folder using id", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDashInFolder.Id,
"title": "new folder title",
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in type mismatch error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
})
})
Convey("When trying to update existing folder to a dashboard using uid", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": savedFolder.Uid,
"title": "new title",
}),
IsFolder: false,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in type mismatch error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
})
})
Convey("When trying to update existing dashboard to a folder using uid", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"uid": savedDashInFolder.Uid,
"title": "new folder title",
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in type mismatch error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardTypeMismatch)
})
})
Convey("When trying to update existing folder to a dashboard using title", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": savedFolder.Title,
}),
IsFolder: false,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in dashboard with same name as folder error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardWithSameNameAsFolder)
})
})
Convey("When trying to update existing dashboard to a folder using title", func() {
cmd := models.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"title": savedDashInGeneralFolder.Title,
}),
IsFolder: true,
Overwrite: shouldOverwrite,
}
err := callSaveWithError(cmd)
Convey("It should result in folder with same name as dashboard error", func() {
So(err, ShouldNotBeNil)
So(err, ShouldEqual, models.ErrDashboardFolderWithSameNameAsDashboard)
})
})
})
})
})
})
}
type scenarioContext struct {
dashboardGuardianMock *guardian.FakeDashboardGuardian
}
type scenarioFunc func(c *scenarioContext)
func dashboardGuardianScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
Convey(desc, func() {
origNewDashboardGuardian := guardian.New
guardian.MockDashboardGuardian(mock)
sc := &scenarioContext{
dashboardGuardianMock: mock,
}
defer func() {
guardian.New = origNewDashboardGuardian
}()
fn(sc)
})
}
type dashboardPermissionScenarioContext struct {
dashboardGuardianMock *guardian.FakeDashboardGuardian
}
type dashboardPermissionScenarioFunc func(sc *dashboardPermissionScenarioContext)
func dashboardPermissionScenario(desc string, mock *guardian.FakeDashboardGuardian, fn dashboardPermissionScenarioFunc) {
Convey(desc, func() {
origNewDashboardGuardian := guardian.New
guardian.MockDashboardGuardian(mock)
sc := &dashboardPermissionScenarioContext{
dashboardGuardianMock: mock,
}
defer func() {
guardian.New = origNewDashboardGuardian
}()
fn(sc)
})
}
func permissionScenario(desc string, canSave bool, fn dashboardPermissionScenarioFunc) {
mock := &guardian.FakeDashboardGuardian{
CanSaveValue: canSave,
}
dashboardPermissionScenario(desc, mock, fn)
}
func callSaveWithResult(cmd models.SaveDashboardCommand) *models.Dashboard {
dto := toSaveDashboardDto(cmd)
res, _ := dashboards.NewService().SaveDashboard(&dto)
return res
}
func callSaveWithError(cmd models.SaveDashboardCommand) error {
dto := toSaveDashboardDto(cmd)
_, err := dashboards.NewService().SaveDashboard(&dto)
return err
}
func dashboardServiceScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
Convey(desc, func() {
origNewDashboardGuardian := guardian.New
guardian.MockDashboardGuardian(mock)
sc := &scenarioContext{
dashboardGuardianMock: mock,
}
defer func() {
guardian.New = origNewDashboardGuardian
}()
fn(sc)
})
}
func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: folderId,
IsFolder: false,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
}),
}
dto := dashboards.SaveDashboardDTO{
OrgId: orgId,
Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{
UserId: 1,
OrgRole: models.ROLE_ADMIN,
},
}
res, err := dashboards.NewService().SaveDashboard(&dto)
So(err, ShouldBeNil)
return res
}
func saveTestFolder(title string, orgId int64) *models.Dashboard {
cmd := models.SaveDashboardCommand{
OrgId: orgId,
FolderId: 0,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": title,
}),
}
dto := dashboards.SaveDashboardDTO{
OrgId: orgId,
Dashboard: cmd.GetDashboardModel(),
User: &models.SignedInUser{
UserId: 1,
OrgRole: models.ROLE_ADMIN,
},
}
res, err := dashboards.NewService().SaveDashboard(&dto)
So(err, ShouldBeNil)
return res
}
func toSaveDashboardDto(cmd models.SaveDashboardCommand) dashboards.SaveDashboardDTO {
dash := (&cmd).GetDashboardModel()
return dashboards.SaveDashboardDTO{
Dashboard: dash,
Message: cmd.Message,
OrgId: cmd.OrgId,
User: &models.SignedInUser{UserId: cmd.UserId},
Overwrite: cmd.Overwrite,
}
}
+26 -12
View File
@@ -16,20 +16,23 @@ func init() {
bus.AddHandler("sql", DeleteExpiredSnapshots)
}
// DeleteExpiredSnapshots removes snapshots with old expiry dates.
// SnapShotRemoveExpired is deprecated and should be removed in the future.
// Snapshot expiry is decided by the user when they share the snapshot.
func DeleteExpiredSnapshots(cmd *m.DeleteExpiredSnapshotsCommand) error {
return inTransaction(func(sess *DBSession) error {
var expiredCount int64 = 0
if setting.SnapShotRemoveExpired {
deleteExpiredSql := "DELETE FROM dashboard_snapshot WHERE expires < ?"
expiredResponse, err := x.Exec(deleteExpiredSql, time.Now)
if err != nil {
return err
}
expiredCount, _ = expiredResponse.RowsAffected()
if !setting.SnapShotRemoveExpired {
sqlog.Warn("[Deprecated] The snapshot_remove_expired setting is outdated. Please remove from your config.")
return nil
}
sqlog.Debug("Deleted old/expired snaphots", "expired", expiredCount)
deleteExpiredSql := "DELETE FROM dashboard_snapshot WHERE expires < ?"
expiredResponse, err := sess.Exec(deleteExpiredSql, time.Now())
if err != nil {
return err
}
cmd.DeletedRows, _ = expiredResponse.RowsAffected()
return nil
})
}
@@ -72,7 +75,7 @@ func DeleteDashboardSnapshot(cmd *m.DeleteDashboardSnapshotCommand) error {
}
func GetDashboardSnapshot(query *m.GetDashboardSnapshotQuery) error {
snapshot := m.DashboardSnapshot{Key: query.Key}
snapshot := m.DashboardSnapshot{Key: query.Key, DeleteKey: query.DeleteKey}
has, err := x.Get(&snapshot)
if err != nil {
@@ -85,6 +88,8 @@ func GetDashboardSnapshot(query *m.GetDashboardSnapshotQuery) error {
return nil
}
// SearchDashboardSnapshots returns a list of all snapshots for admins
// for other roles, it returns snapshots created by the user
func SearchDashboardSnapshots(query *m.GetDashboardSnapshotsQuery) error {
var snapshots = make(m.DashboardSnapshotsList, 0)
@@ -95,7 +100,16 @@ func SearchDashboardSnapshots(query *m.GetDashboardSnapshotsQuery) error {
sess.Where("name LIKE ?", query.Name)
}
sess.Where("org_id = ?", query.OrgId)
// admins can see all snapshots, everyone else can only see their own snapshots
if query.SignedInUser.OrgRole == m.ROLE_ADMIN {
sess.Where("org_id = ?", query.OrgId)
} else if !query.SignedInUser.IsAnonymous {
sess.Where("org_id = ? AND user_id = ?", query.OrgId, query.SignedInUser.UserId)
} else {
query.Result = snapshots
return nil
}
err := sess.Find(&snapshots)
query.Result = snapshots
return err
@@ -2,11 +2,14 @@ package sqlstore
import (
"testing"
"time"
"github.com/go-xorm/xorm"
. "github.com/smartystreets/goconvey/convey"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
)
func TestDashboardSnapshotDBAccess(t *testing.T) {
@@ -14,17 +17,19 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
Convey("Testing DashboardSnapshot data access", t, func() {
InitTestDB(t)
Convey("Given saved snaphot", func() {
Convey("Given saved snapshot", func() {
cmd := m.CreateDashboardSnapshotCommand{
Key: "hej",
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"hello": "mupp",
}),
UserId: 1000,
OrgId: 1,
}
err := CreateDashboardSnapshot(&cmd)
So(err, ShouldBeNil)
Convey("Should be able to get snaphot by key", func() {
Convey("Should be able to get snapshot by key", func() {
query := m.GetDashboardSnapshotQuery{Key: "hej"}
err = GetDashboardSnapshot(&query)
So(err, ShouldBeNil)
@@ -33,6 +38,135 @@ func TestDashboardSnapshotDBAccess(t *testing.T) {
So(query.Result.Dashboard.Get("hello").MustString(), ShouldEqual, "mupp")
})
Convey("And the user has the admin role", func() {
Convey("Should return all the snapshots", func() {
query := m.GetDashboardSnapshotsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN},
}
err := SearchDashboardSnapshots(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(len(query.Result), ShouldEqual, 1)
})
})
Convey("And the user has the editor role and has created a snapshot", func() {
Convey("Should return all the snapshots", func() {
query := m.GetDashboardSnapshotsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR, UserId: 1000},
}
err := SearchDashboardSnapshots(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(len(query.Result), ShouldEqual, 1)
})
})
Convey("And the user has the editor role and has not created any snapshot", func() {
Convey("Should not return any snapshots", func() {
query := m.GetDashboardSnapshotsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR, UserId: 2},
}
err := SearchDashboardSnapshots(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(len(query.Result), ShouldEqual, 0)
})
})
Convey("And the user is anonymous", func() {
cmd := m.CreateDashboardSnapshotCommand{
Key: "strangesnapshotwithuserid0",
DeleteKey: "adeletekey",
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"hello": "mupp",
}),
UserId: 0,
OrgId: 1,
}
err := CreateDashboardSnapshot(&cmd)
So(err, ShouldBeNil)
Convey("Should not return any snapshots", func() {
query := m.GetDashboardSnapshotsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR, IsAnonymous: true, UserId: 0},
}
err := SearchDashboardSnapshots(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldNotBeNil)
So(len(query.Result), ShouldEqual, 0)
})
})
})
})
}
func TestDeleteExpiredSnapshots(t *testing.T) {
Convey("Testing dashboard snapshots clean up", t, func() {
x := InitTestDB(t)
setting.SnapShotRemoveExpired = true
notExpiredsnapshot := createTestSnapshot(x, "key1", 1000)
createTestSnapshot(x, "key2", -1000)
createTestSnapshot(x, "key3", -1000)
Convey("Clean up old dashboard snapshots", func() {
err := DeleteExpiredSnapshots(&m.DeleteExpiredSnapshotsCommand{})
So(err, ShouldBeNil)
query := m.GetDashboardSnapshotsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN},
}
err = SearchDashboardSnapshots(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Key, ShouldEqual, notExpiredsnapshot.Key)
})
Convey("Don't delete anything if there are no expired snapshots", func() {
err := DeleteExpiredSnapshots(&m.DeleteExpiredSnapshotsCommand{})
So(err, ShouldBeNil)
query := m.GetDashboardSnapshotsQuery{
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_ADMIN},
}
SearchDashboardSnapshots(&query)
So(len(query.Result), ShouldEqual, 1)
})
})
}
func createTestSnapshot(x *xorm.Engine, key string, expires int64) *m.DashboardSnapshot {
cmd := m.CreateDashboardSnapshotCommand{
Key: key,
DeleteKey: "delete" + key,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"hello": "mupp",
}),
UserId: 1000,
OrgId: 1,
Expires: expires,
}
err := CreateDashboardSnapshot(&cmd)
So(err, ShouldBeNil)
// Set expiry date manually - to be able to create expired snapshots
expireDate := time.Now().Add(time.Second * time.Duration(expires))
_, err = x.Exec("update dashboard_snapshot set expires = ? where "+dialect.Quote("key")+" = ?", expireDate, key)
So(err, ShouldBeNil)
return cmd.Result
}
+177 -327
View File
@@ -1,22 +1,21 @@
package sqlstore
import (
"fmt"
"testing"
"github.com/go-xorm/xorm"
. "github.com/smartystreets/goconvey/convey"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardDataAccess(t *testing.T) {
var x *xorm.Engine
Convey("Testing DB", t, func() {
x = InitTestDB(t)
InitTestDB(t)
Convey("Given saved dashboard", func() {
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
@@ -30,15 +29,33 @@ func TestDashboardDataAccess(t *testing.T) {
So(savedDash.Id, ShouldNotEqual, 0)
So(savedDash.IsFolder, ShouldBeFalse)
So(savedDash.FolderId, ShouldBeGreaterThan, 0)
So(len(savedDash.Uid), ShouldBeGreaterThan, 0)
So(savedFolder.Title, ShouldEqual, "1 test dash folder")
So(savedFolder.Slug, ShouldEqual, "1-test-dash-folder")
So(savedFolder.Id, ShouldNotEqual, 0)
So(savedFolder.IsFolder, ShouldBeTrue)
So(savedFolder.FolderId, ShouldEqual, 0)
So(len(savedFolder.Uid), ShouldBeGreaterThan, 0)
})
Convey("Should be able to get dashboard", func() {
Convey("Should be able to get dashboard by id", func() {
query := m.GetDashboardQuery{
Id: savedDash.Id,
OrgId: 1,
}
err := GetDashboard(&query)
So(err, ShouldBeNil)
So(query.Result.Title, ShouldEqual, "test dash 23")
So(query.Result.Slug, ShouldEqual, "test-dash-23")
So(query.Result.Id, ShouldEqual, savedDash.Id)
So(query.Result.Uid, ShouldEqual, savedDash.Uid)
So(query.Result.IsFolder, ShouldBeFalse)
})
Convey("Should be able to get dashboard by slug", func() {
query := m.GetDashboardQuery{
Slug: "test-dash-23",
OrgId: 1,
@@ -49,6 +66,24 @@ func TestDashboardDataAccess(t *testing.T) {
So(query.Result.Title, ShouldEqual, "test dash 23")
So(query.Result.Slug, ShouldEqual, "test-dash-23")
So(query.Result.Id, ShouldEqual, savedDash.Id)
So(query.Result.Uid, ShouldEqual, savedDash.Uid)
So(query.Result.IsFolder, ShouldBeFalse)
})
Convey("Should be able to get dashboard by uid", func() {
query := m.GetDashboardQuery{
Uid: savedDash.Uid,
OrgId: 1,
}
err := GetDashboard(&query)
So(err, ShouldBeNil)
So(query.Result.Title, ShouldEqual, "test dash 23")
So(query.Result.Slug, ShouldEqual, "test-dash-23")
So(query.Result.Id, ShouldEqual, savedDash.Id)
So(query.Result.Uid, ShouldEqual, savedDash.Uid)
So(query.Result.IsFolder, ShouldBeFalse)
})
@@ -63,124 +98,59 @@ func TestDashboardDataAccess(t *testing.T) {
So(err, ShouldBeNil)
})
Convey("Should return error if no dashboard is updated", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(123412321),
"title": "Expect error",
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
So(err, ShouldNotBeNil)
})
Convey("Should not be able to overwrite dashboard in another org", func() {
query := m.GetDashboardQuery{Slug: "test-dash-23", OrgId: 1}
GetDashboard(&query)
cmd := m.SaveDashboardCommand{
OrgId: 2,
Overwrite: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(query.Result.Id),
"title": "Expect error",
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
So(err, ShouldNotBeNil)
})
Convey("Should be able to search for dashboard folder", func() {
query := search.FindPersistedDashboardsQuery{
Title: "1 test dash folder",
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
hit := query.Result[0]
So(hit.Type, ShouldEqual, search.DashHitFolder)
})
Convey("Should be able to search for a dashboard folder's children", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
FolderIds: []int64{savedFolder.Id},
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
hit := query.Result[0]
So(hit.Id, ShouldEqual, savedDash.Id)
})
Convey("Should be able to search for dashboard by dashboard ids", func() {
Convey("should be able to find two dashboards by id", func() {
query := search.FindPersistedDashboardsQuery{
DashboardIds: []int64{2, 3},
SignedInUser: &m.SignedInUser{OrgId: 1},
Convey("Should retry generation of uid once if it fails.", func() {
timesCalled := 0
generateNewUid = func() string {
timesCalled += 1
if timesCalled <= 2 {
return savedDash.Uid
} else {
return util.GenerateShortUid()
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
hit := query.Result[0]
So(len(hit.Tags), ShouldEqual, 2)
hit2 := query.Result[1]
So(len(hit2.Tags), ShouldEqual, 1)
})
Convey("DashboardIds that does not exists should not cause errors", func() {
query := search.FindPersistedDashboardsQuery{
DashboardIds: []int64{1000},
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 0)
})
})
Convey("Should not be able to save dashboard with same name", func() {
}
cmd := m.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": nil,
"title": "test dash 23",
"title": "new dash 12334",
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
So(err, ShouldNotBeNil)
So(err, ShouldBeNil)
generateNewUid = util.GenerateShortUid
})
Convey("Should be able to update dashboard and remove folderId", func() {
Convey("Should be able to create dashboard", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": 1,
"title": "folderId",
"tags": []interface{}{},
}),
UserId: 100,
}
err := SaveDashboard(&cmd)
So(err, ShouldBeNil)
So(cmd.Result.CreatedBy, ShouldEqual, 100)
So(cmd.Result.Created.IsZero(), ShouldBeFalse)
So(cmd.Result.UpdatedBy, ShouldEqual, 100)
So(cmd.Result.Updated.IsZero(), ShouldBeFalse)
})
Convey("Should be able to update dashboard by id and remove folderId", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": savedDash.Id,
"title": "folderId",
"tags": []interface{}{},
}),
Overwrite: true,
FolderId: 2,
UserId: 100,
}
err := SaveDashboard(&cmd)
@@ -190,25 +160,30 @@ func TestDashboardDataAccess(t *testing.T) {
cmd = m.SaveDashboardCommand{
OrgId: 1,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": 1,
"id": savedDash.Id,
"title": "folderId",
"tags": []interface{}{},
}),
FolderId: 0,
Overwrite: true,
UserId: 100,
}
err = SaveDashboard(&cmd)
So(err, ShouldBeNil)
query := m.GetDashboardQuery{
Slug: cmd.Result.Slug,
Id: savedDash.Id,
OrgId: 1,
}
err = GetDashboard(&query)
So(err, ShouldBeNil)
So(query.Result.FolderId, ShouldEqual, 0)
So(query.Result.CreatedBy, ShouldEqual, savedDash.CreatedBy)
So(query.Result.Created, ShouldEqual, savedDash.Created.Truncate(time.Second))
So(query.Result.UpdatedBy, ShouldEqual, 100)
So(query.Result.Updated.IsZero(), ShouldBeFalse)
})
Convey("Should be able to delete a dashboard folder and its children", func() {
@@ -228,6 +203,36 @@ func TestDashboardDataAccess(t *testing.T) {
So(len(query.Result), ShouldEqual, 0)
})
Convey("Should return error if no dashboard is found for update when dashboard id is greater than zero", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": float64(123412321),
"title": "Expect error",
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
So(err, ShouldEqual, m.ErrDashboardNotFound)
})
Convey("Should not return error if no dashboard is found for update when dashboard id is zero", func() {
cmd := m.SaveDashboardCommand{
OrgId: 1,
Overwrite: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": 0,
"title": "New dash",
"tags": []interface{}{},
}),
}
err := SaveDashboard(&cmd)
So(err, ShouldBeNil)
})
Convey("Should be able to get dashboard tags", func() {
query := m.GetDashboardTagsQuery{OrgId: 1}
@@ -237,6 +242,63 @@ func TestDashboardDataAccess(t *testing.T) {
So(len(query.Result), ShouldEqual, 2)
})
Convey("Should be able to search for dashboard folder", func() {
query := search.FindPersistedDashboardsQuery{
Title: "1 test dash folder",
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgId: 1, OrgRole: m.ROLE_EDITOR},
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
hit := query.Result[0]
So(hit.Type, ShouldEqual, search.DashHitFolder)
So(hit.Url, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
So(hit.FolderTitle, ShouldEqual, "")
})
Convey("Should be able to search for a dashboard folder's children", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
FolderIds: []int64{savedFolder.Id},
SignedInUser: &m.SignedInUser{OrgId: 1, OrgRole: m.ROLE_EDITOR},
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
hit := query.Result[0]
So(hit.Id, ShouldEqual, savedDash.Id)
So(hit.Url, ShouldEqual, fmt.Sprintf("/d/%s/%s", savedDash.Uid, savedDash.Slug))
So(hit.FolderId, ShouldEqual, savedFolder.Id)
So(hit.FolderUid, ShouldEqual, savedFolder.Uid)
So(hit.FolderTitle, ShouldEqual, savedFolder.Title)
So(hit.FolderUrl, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
})
Convey("Should be able to search for dashboard by dashboard ids", func() {
Convey("should be able to find two dashboards by id", func() {
query := search.FindPersistedDashboardsQuery{
DashboardIds: []int64{2, 3},
SignedInUser: &m.SignedInUser{OrgId: 1, OrgRole: m.ROLE_EDITOR},
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
hit := query.Result[0]
So(len(hit.Tags), ShouldEqual, 2)
hit2 := query.Result[1]
So(len(hit2.Tags), ShouldEqual, 1)
})
})
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
starredDash := insertTestDashboard("starred dash", 1, 0, false)
StarDashboard(&m.StarDashboardCommand{
@@ -250,7 +312,10 @@ func TestDashboardDataAccess(t *testing.T) {
})
Convey("Should be able to search for starred dashboards", func() {
query := search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1}, IsStarred: true}
query := search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1, OrgRole: m.ROLE_EDITOR},
IsStarred: true,
}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
@@ -260,205 +325,6 @@ func TestDashboardDataAccess(t *testing.T) {
})
})
Convey("Given one dashboard folder with two dashboards and one dashboard in the root folder", func() {
folder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
childDash := insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
currentUser := createUser("viewer", "Viewer", false)
Convey("and no acls are set", func() {
Convey("should return all dashboards", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and acl is set for dashboard folder", func() {
var otherUser int64 = 999
updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
Convey("should not return folder", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
Convey("when the user is given permission", func() {
updateTestDashboardWithAcl(folder.Id, currentUser.Id, m.PERMISSION_EDIT)
Convey("should be able to access folder", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("when the user is an admin", func() {
Convey("should be able to access folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{
UserId: currentUser.Id,
OrgId: 1,
OrgRole: m.ROLE_ADMIN,
},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
})
Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
var otherUser int64 = 999
aclId := updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
removeAcl(aclId)
updateTestDashboardWithAcl(childDash.Id, otherUser, m.PERMISSION_EDIT)
Convey("should not return folder or child", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
Convey("when the user is given permission to child", func() {
updateTestDashboardWithAcl(childDash.Id, currentUser.Id, m.PERMISSION_EDIT)
Convey("should be able to search for child dashboard but not folder", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, childDash.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("when the user is an admin", func() {
Convey("should be able to search for child dash and folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{
UserId: currentUser.Id,
OrgId: 1,
OrgRole: m.ROLE_ADMIN,
},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id, childDash.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, childDash.Id)
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
})
})
})
})
Convey("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func() {
folder1 := insertTestDashboard("1 test dash folder", 1, 0, true, "prod")
folder2 := insertTestDashboard("2 test dash folder", 1, 0, true, "prod")
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod")
childDash1 := insertTestDashboard("child dash 1", 1, folder1.Id, false, "prod")
childDash2 := insertTestDashboard("child dash 2", 1, folder2.Id, false, "prod")
currentUser := createUser("viewer", "Viewer", false)
var rootFolderId int64 = 0
Convey("and one folder is expanded, the other collapsed", func() {
Convey("should return dashboards in root and expanded folder", func() {
query := &search.FindPersistedDashboardsQuery{FolderIds: []int64{rootFolderId, folder1.Id}, SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 4)
So(query.Result[0].Id, ShouldEqual, folder1.Id)
So(query.Result[1].Id, ShouldEqual, folder2.Id)
So(query.Result[2].Id, ShouldEqual, childDash1.Id)
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and acl is set for one dashboard folder", func() {
var otherUser int64 = 999
updateTestDashboardWithAcl(folder1.Id, otherUser, m.PERMISSION_EDIT)
Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
movedDash := moveDashboard(1, childDash2.Data, folder1.Id)
So(movedDash.HasAcl, ShouldBeTrue)
Convey("should not return folder with acl or its children", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
OrgId: 1,
DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() {
movedDash := moveDashboard(1, childDash1.Data, folder2.Id)
So(movedDash.HasAcl, ShouldBeFalse)
Convey("should return folder without acl and its children", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
OrgId: 1,
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 4)
So(query.Result[0].Id, ShouldEqual, folder2.Id)
So(query.Result[1].Id, ShouldEqual, childDash1.Id)
So(query.Result[2].Id, ShouldEqual, childDash2.Id)
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
updateTestDashboardWithAcl(childDash1.Id, otherUser, m.PERMISSION_EDIT)
movedDash := moveDashboard(1, childDash1.Data, folder2.Id)
So(movedDash.HasAcl, ShouldBeTrue)
Convey("should return folder without acl but not the dashboard with acl", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
OrgId: 1,
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3)
So(query.Result[0].Id, ShouldEqual, folder2.Id)
So(query.Result[1].Id, ShouldEqual, childDash2.Id)
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
})
})
})
})
Convey("Given a plugin with imported dashboards", func() {
pluginId := "test-app"
@@ -495,6 +361,9 @@ func insertTestDashboard(title string, orgId int64, folderId int64, isFolder boo
err := SaveDashboard(&cmd)
So(err, ShouldBeNil)
cmd.Result.Data.Set("id", cmd.Result.Id)
cmd.Result.Data.Set("uid", cmd.Result.Uid)
return cmd.Result
}
@@ -531,25 +400,6 @@ func createUser(name string, role string, isAdmin bool) m.User {
return currentUserCmd.Result
}
func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.PermissionType) int64 {
cmd := &m.SetDashboardAclCommand{
OrgId: 1,
UserId: userId,
DashboardId: dashId,
Permission: permissions,
}
err := SetDashboardAcl(cmd)
So(err, ShouldBeNil)
return cmd.Result.Id
}
func removeAcl(aclId int64) {
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{AclId: aclId, OrgId: 1})
So(err, ShouldBeNil)
}
func moveDashboard(orgId int64, dashboard *simplejson.Json, newFolderId int64) *m.Dashboard {
cmd := m.SaveDashboardCommand{
OrgId: orgId,
+1 -3
View File
@@ -69,7 +69,6 @@ func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
func DeleteExpiredVersions(cmd *m.DeleteExpiredVersionsCommand) error {
return inTransaction(func(sess *DBSession) error {
expiredCount := int64(0)
versions := []DashboardVersionExp{}
versionsToKeep := setting.DashboardVersionsToKeep
@@ -98,8 +97,7 @@ func DeleteExpiredVersions(cmd *m.DeleteExpiredVersionsCommand) error {
if err != nil {
return err
}
expiredCount, _ = expiredResponse.RowsAffected()
sqlog.Debug("Deleted old/expired dashboard versions", "expired", expiredCount)
cmd.DeletedRows, _ = expiredResponse.RowsAffected()
}
return nil
@@ -12,7 +12,7 @@ import (
)
func updateTestDashboard(dashboard *m.Dashboard, data map[string]interface{}) {
data["title"] = dashboard.Title
data["id"] = dashboard.Id
saveCmd := m.SaveDashboardCommand{
OrgId: dashboard.OrgId,
@@ -44,7 +44,7 @@ func TestGetDashboardVersion(t *testing.T) {
dashCmd := m.GetDashboardQuery{
OrgId: savedDash.OrgId,
Slug: savedDash.Slug,
Uid: savedDash.Uid,
}
err = GetDashboard(&dashCmd)
+3
View File
@@ -27,6 +27,9 @@ func GetDataSourceById(query *m.GetDataSourceByIdQuery) error {
datasource := m.DataSource{OrgId: query.OrgId, Id: query.Id}
has, err := x.Get(&datasource)
if err != nil {
return err
}
if !has {
return m.ErrDataSourceNotFound
-23
View File
@@ -3,34 +3,11 @@ package sqlstore
import (
"testing"
"github.com/go-xorm/xorm"
. "github.com/smartystreets/goconvey/convey"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
)
func InitTestDB(t *testing.T) *xorm.Engine {
x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
// x.ShowSQL()
if err != nil {
t.Fatalf("Failed to init in memory sqllite3 db %v", err)
}
sqlutil.CleanDB(x)
if err := SetEngine(x); err != nil {
t.Fatal(err)
}
return x
}
type Test struct {
Id int64
Name string
+91
View File
@@ -0,0 +1,91 @@
package sqlstore
import (
"strconv"
"time"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
var getTimeNow = time.Now
func init() {
bus.AddHandler("sql", CreateLoginAttempt)
bus.AddHandler("sql", DeleteOldLoginAttempts)
bus.AddHandler("sql", GetUserLoginAttemptCount)
}
func CreateLoginAttempt(cmd *m.CreateLoginAttemptCommand) error {
return inTransaction(func(sess *DBSession) error {
loginAttempt := m.LoginAttempt{
Username: cmd.Username,
IpAddress: cmd.IpAddress,
Created: getTimeNow().Unix(),
}
if _, err := sess.Insert(&loginAttempt); err != nil {
return err
}
cmd.Result = loginAttempt
return nil
})
}
func DeleteOldLoginAttempts(cmd *m.DeleteOldLoginAttemptsCommand) error {
return inTransaction(func(sess *DBSession) error {
var maxId int64
sql := "SELECT max(id) as id FROM login_attempt WHERE created < ?"
result, err := sess.Query(sql, cmd.OlderThan.Unix())
if err != nil {
return err
}
maxId = toInt64(result[0]["id"])
if maxId == 0 {
return nil
}
sql = "DELETE FROM login_attempt WHERE id <= ?"
if result, err := sess.Exec(sql, maxId); err != nil {
return err
} else if cmd.DeletedRows, err = result.RowsAffected(); err != nil {
return err
}
return nil
})
}
func GetUserLoginAttemptCount(query *m.GetUserLoginAttemptCountQuery) error {
loginAttempt := new(m.LoginAttempt)
total, err := x.
Where("username = ?", query.Username).
And("created >= ?", query.Since.Unix()).
Count(loginAttempt)
if err != nil {
return err
}
query.Result = total
return nil
}
func toInt64(i interface{}) int64 {
switch i.(type) {
case []byte:
n, _ := strconv.ParseInt(string(i.([]byte)), 10, 64)
return n
case int:
return int64(i.(int))
case int64:
return i.(int64)
}
return 0
}
+125
View File
@@ -0,0 +1,125 @@
package sqlstore
import (
"testing"
"time"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func mockTime(mock time.Time) time.Time {
getTimeNow = func() time.Time { return mock }
return mock
}
func TestLoginAttempts(t *testing.T) {
Convey("Testing Login Attempts DB Access", t, func() {
InitTestDB(t)
user := "user"
beginningOfTime := mockTime(time.Date(2017, 10, 22, 8, 0, 0, 0, time.Local))
err := CreateLoginAttempt(&m.CreateLoginAttemptCommand{
Username: user,
IpAddress: "192.168.0.1",
})
So(err, ShouldBeNil)
timePlusOneMinute := mockTime(beginningOfTime.Add(time.Minute * 1))
err = CreateLoginAttempt(&m.CreateLoginAttemptCommand{
Username: user,
IpAddress: "192.168.0.1",
})
So(err, ShouldBeNil)
timePlusTwoMinutes := mockTime(beginningOfTime.Add(time.Minute * 2))
err = CreateLoginAttempt(&m.CreateLoginAttemptCommand{
Username: user,
IpAddress: "192.168.0.1",
})
So(err, ShouldBeNil)
Convey("Should return a total count of zero login attempts when comparing since beginning of time + 2min and 1s", func() {
query := m.GetUserLoginAttemptCountQuery{
Username: user,
Since: timePlusTwoMinutes.Add(time.Second * 1),
}
err := GetUserLoginAttemptCount(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldEqual, 0)
})
Convey("Should return the total count of login attempts since beginning of time", func() {
query := m.GetUserLoginAttemptCountQuery{
Username: user,
Since: beginningOfTime,
}
err := GetUserLoginAttemptCount(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldEqual, 3)
})
Convey("Should return the total count of login attempts since beginning of time + 1min", func() {
query := m.GetUserLoginAttemptCountQuery{
Username: user,
Since: timePlusOneMinute,
}
err := GetUserLoginAttemptCount(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldEqual, 2)
})
Convey("Should return the total count of login attempts since beginning of time + 2min", func() {
query := m.GetUserLoginAttemptCountQuery{
Username: user,
Since: timePlusTwoMinutes,
}
err := GetUserLoginAttemptCount(&query)
So(err, ShouldBeNil)
So(query.Result, ShouldEqual, 1)
})
Convey("Should return deleted rows older than beginning of time", func() {
cmd := m.DeleteOldLoginAttemptsCommand{
OlderThan: beginningOfTime,
}
err := DeleteOldLoginAttempts(&cmd)
So(err, ShouldBeNil)
So(cmd.DeletedRows, ShouldEqual, 0)
})
Convey("Should return deleted rows older than beginning of time + 1min", func() {
cmd := m.DeleteOldLoginAttemptsCommand{
OlderThan: timePlusOneMinute,
}
err := DeleteOldLoginAttempts(&cmd)
So(err, ShouldBeNil)
So(cmd.DeletedRows, ShouldEqual, 1)
})
Convey("Should return deleted rows older than beginning of time + 2min", func() {
cmd := m.DeleteOldLoginAttemptsCommand{
OlderThan: timePlusTwoMinutes,
}
err := DeleteOldLoginAttempts(&cmd)
So(err, ShouldBeNil)
So(cmd.DeletedRows, ShouldEqual, 2)
})
Convey("Should return deleted rows older than beginning of time + 2min and 1s", func() {
cmd := m.DeleteOldLoginAttemptsCommand{
OlderThan: timePlusTwoMinutes.Add(time.Second * 1),
}
err := DeleteOldLoginAttempts(&cmd)
So(err, ShouldBeNil)
So(cmd.DeletedRows, ShouldEqual, 3)
})
})
}
@@ -24,3 +24,24 @@ func addTableRenameMigration(mg *Migrator, oldName string, newName string, versi
migrationId := fmt.Sprintf("Rename table %s to %s - %s", oldName, newName, versionSuffix)
mg.AddMigration(migrationId, NewRenameTableMigration(oldName, newName))
}
func addTableReplaceMigrations(mg *Migrator, from Table, to Table, migrationVersion int64, tableDataMigration map[string]string) {
fromV := version(migrationVersion - 1)
toV := version(migrationVersion)
tmpTableName := to.Name + "_tmp_qwerty"
createTable := fmt.Sprintf("create %v %v", to.Name, toV)
copyTableData := fmt.Sprintf("copy %v %v to %v", to.Name, fromV, toV)
dropTable := fmt.Sprintf("drop %v", tmpTableName)
addDropAllIndicesMigrations(mg, fromV, from)
addTableRenameMigration(mg, from.Name, tmpTableName, fromV)
mg.AddMigration(createTable, NewAddTableMigration(to))
addTableIndicesMigrations(mg, toV, to)
mg.AddMigration(copyTableData, NewCopyTableDataMigration(to.Name, tmpTableName, tableDataMigration))
mg.AddMigration(dropTable, NewDropTableMigration(tmpTableName))
}
func version(v int64) string {
return fmt.Sprintf("v%v", v)
}
@@ -1,6 +1,8 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
import (
. "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
func addDashboardMigration(mg *Migrator) {
var dashboardV1 = Table{
@@ -150,4 +152,65 @@ func addDashboardMigration(mg *Migrator) {
mg.AddMigration("Add column has_acl in dashboard", NewAddColumnMigration(dashboardV2, &Column{
Name: "has_acl", Type: DB_Bool, Nullable: false, Default: "0",
}))
mg.AddMigration("Add column uid in dashboard", NewAddColumnMigration(dashboardV2, &Column{
Name: "uid", Type: DB_NVarchar, Length: 40, Nullable: true,
}))
mg.AddMigration("Update uid column values in dashboard", new(RawSqlMigration).
Sqlite("UPDATE dashboard SET uid=printf('%09d',id) WHERE uid IS NULL;").
Postgres("UPDATE dashboard SET uid=lpad('' || id,9,'0') WHERE uid IS NULL;").
Mysql("UPDATE dashboard SET uid=lpad(id,9,'0') WHERE uid IS NULL;"))
mg.AddMigration("Add unique index dashboard_org_id_uid", NewAddIndexMigration(dashboardV2, &Index{
Cols: []string{"org_id", "uid"}, Type: UniqueIndex,
}))
mg.AddMigration("Remove unique index org_id_slug", NewDropIndexMigration(dashboardV2, &Index{
Cols: []string{"org_id", "slug"}, Type: UniqueIndex,
}))
mg.AddMigration("Update dashboard title length", NewTableCharsetMigration("dashboard", []*Column{
{Name: "title", Type: DB_NVarchar, Length: 189, Nullable: false},
}))
mg.AddMigration("Add unique index for dashboard_org_id_title_folder_id", NewAddIndexMigration(dashboardV2, &Index{
Cols: []string{"org_id", "folder_id", "title"}, Type: UniqueIndex,
}))
dashboardExtrasTable := Table{
Name: "dashboard_provisioning",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "dashboard_id", Type: DB_BigInt, Nullable: true},
{Name: "name", Type: DB_NVarchar, Length: 150, Nullable: false},
{Name: "external_id", Type: DB_Text, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{},
}
mg.AddMigration("create dashboard_provisioning", NewAddTableMigration(dashboardExtrasTable))
dashboardExtrasTableV2 := Table{
Name: "dashboard_provisioning",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "dashboard_id", Type: DB_BigInt, Nullable: true},
{Name: "name", Type: DB_NVarchar, Length: 150, Nullable: false},
{Name: "external_id", Type: DB_Text, Nullable: false},
{Name: "updated", Type: DB_Int, Default: "0", Nullable: false},
},
Indices: []*Index{
{Cols: []string{"dashboard_id"}},
{Cols: []string{"dashboard_id", "name"}, Type: IndexType},
},
}
addTableReplaceMigrations(mg, dashboardExtrasTable, dashboardExtrasTableV2, 2, map[string]string{
"id": "id",
"dashboard_id": "dashboard_id",
"name": "name",
"external_id": "external_id",
})
}
@@ -0,0 +1,42 @@
package migrations
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
func addLoginAttemptMigrations(mg *Migrator) {
loginAttemptV1 := Table{
Name: "login_attempt",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "username", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "ip_address", Type: DB_NVarchar, Length: 30, Nullable: false},
{Name: "created", Type: DB_DateTime, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"username"}},
},
}
// create table
mg.AddMigration("create login attempt table", NewAddTableMigration(loginAttemptV1))
// add indices
mg.AddMigration("add index login_attempt.username", NewAddIndexMigration(loginAttemptV1, loginAttemptV1.Indices[0]))
loginAttemptV2 := Table{
Name: "login_attempt",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "username", Type: DB_NVarchar, Length: 190, Nullable: false},
{Name: "ip_address", Type: DB_NVarchar, Length: 30, Nullable: false},
{Name: "created", Type: DB_Int, Default: "0", Nullable: false},
},
Indices: []*Index{
{Cols: []string{"username"}},
},
}
addTableReplaceMigrations(mg, loginAttemptV1, loginAttemptV2, 2, map[string]string{
"id": "id",
"username": "username",
"ip_address": "ip_address",
})
}
@@ -29,6 +29,7 @@ func AddMigrations(mg *Migrator) {
addTeamMigrations(mg)
addDashboardAclMigrations(mg)
addTagMigration(mg)
addLoginAttemptMigrations(mg)
}
func addMigrationLogMigrations(mg *Migrator) {
@@ -14,13 +14,15 @@ import (
var indexTypes = []string{"Unknown", "INDEX", "UNIQUE INDEX"}
func TestMigrations(t *testing.T) {
//log.NewLogger(0, "console", `{"level": 0}`)
testDBs := []sqlutil.TestDB{
sqlutil.TestDB_Sqlite3,
}
for _, testDB := range testDBs {
sql := `select count(*) as count from migration_log`
r := struct {
Count int64
}{}
Convey("Initial "+testDB.DriverName+" migration", t, func() {
x, err := xorm.NewEngine(testDB.DriverName, testDB.ConnStr)
@@ -28,30 +30,31 @@ func TestMigrations(t *testing.T) {
sqlutil.CleanDB(x)
has, err := x.SQL(sql).Get(&r)
So(err, ShouldNotBeNil)
mg := NewMigrator(x)
AddMigrations(mg)
err = mg.Start()
So(err, ShouldBeNil)
// tables, err := x.DBMetas()
// So(err, ShouldBeNil)
//
// fmt.Printf("\nDB Schema after migration: table count: %v\n", len(tables))
//
// for _, table := range tables {
// fmt.Printf("\nTable: %v \n", table.Name)
// for _, column := range table.Columns() {
// fmt.Printf("\t %v \n", column.String(x.Dialect()))
// }
//
// if len(table.Indexes) > 0 {
// fmt.Printf("\n\tIndexes:\n")
// for _, index := range table.Indexes {
// fmt.Printf("\t %v (%v) %v \n", index.Name, strings.Join(index.Cols, ","), indexTypes[index.Type])
// }
// }
// }
has, err = x.SQL(sql).Get(&r)
So(err, ShouldBeNil)
So(has, ShouldBeTrue)
expectedMigrations := mg.MigrationsCount() - 2 //we currently skip to migrations. We should rewrite skipped migrations to write in the log as well. until then we have to keep this
So(r.Count, ShouldEqual, expectedMigrations)
mg = NewMigrator(x)
AddMigrations(mg)
err = mg.Start()
So(err, ShouldBeNil)
has, err = x.SQL(sql).Get(&r)
So(err, ShouldBeNil)
So(has, ShouldBeTrue)
So(r.Count, ShouldEqual, expectedMigrations)
})
}
}
@@ -19,6 +19,7 @@ type Dialect interface {
LikeStr() string
Default(col *Column) string
BooleanStr(bool) string
DateTimeFunc(string) string
CreateIndexSql(tableName string, index *Index) string
CreateTableSql(table *Table) string
@@ -78,6 +79,10 @@ func (b *BaseDialect) Default(col *Column) string {
return col.Default
}
func (db *BaseDialect) DateTimeFunc(value string) string {
return value
}
func (b *BaseDialect) CreateTableSql(table *Table) string {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "
@@ -35,6 +35,10 @@ func NewMigrator(engine *xorm.Engine) *Migrator {
return mg
}
func (mg *Migrator) MigrationsCount() int {
return len(mg.migrations)
}
func (mg *Migrator) AddMigration(id string, m Migration) {
m.SetId(id)
mg.migrations = append(mg.migrations, m)
@@ -36,6 +36,10 @@ func (db *Sqlite3) BooleanStr(value bool) string {
return "0"
}
func (db *Sqlite3) DateTimeFunc(value string) string {
return "datetime(" + value + ")"
}
func (db *Sqlite3) SqlType(c *Column) string {
switch c.Type {
case DB_Date, DB_DateTime, DB_TimeStamp, DB_Time:
+38 -2
View File
@@ -123,6 +123,31 @@ func TestAccountDataAccess(t *testing.T) {
So(query.Result[0].Role, ShouldEqual, "Admin")
})
Convey("Can get organization users with query", func() {
query := m.GetOrgUsersQuery{
OrgId: ac1.OrgId,
Query: "ac1",
}
err := GetOrgUsers(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Email, ShouldEqual, ac1.Email)
})
Convey("Can get organization users with query and limit", func() {
query := m.GetOrgUsersQuery{
OrgId: ac1.OrgId,
Query: "ac",
Limit: 1,
}
err := GetOrgUsers(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Email, ShouldEqual, ac1.Email)
})
Convey("Can set using org", func() {
cmd := m.SetUsingOrgCommand{UserId: ac2.Id, OrgId: ac1.Id}
err := SetUsingOrg(&cmd)
@@ -174,10 +199,13 @@ func TestAccountDataAccess(t *testing.T) {
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 3)
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
dash1 := insertTestDashboard("1 test dash", ac1.OrgId, 0, false, "prod", "webapp")
dash2 := insertTestDashboard("2 test dash", ac3.OrgId, 0, false, "prod", "webapp")
err = testHelperUpdateDashboardAcl(dash1.Id, m.DashboardAcl{DashboardId: dash1.Id, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
So(err, ShouldBeNil)
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
err = testHelperUpdateDashboardAcl(dash2.Id, m.DashboardAcl{DashboardId: dash2.Id, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
So(err, ShouldBeNil)
Convey("When org user is deleted", func() {
@@ -209,3 +237,11 @@ func TestAccountDataAccess(t *testing.T) {
})
})
}
func testHelperUpdateDashboardAcl(dashboardId int64, items ...m.DashboardAcl) error {
cmd := m.UpdateDashboardAclCommand{DashboardId: dashboardId}
for _, item := range items {
cmd.Items = append(cmd.Items, &item)
}
return UpdateDashboardAcl(&cmd)
}
+23 -1
View File
@@ -2,6 +2,7 @@ package sqlstore
import (
"fmt"
"strings"
"time"
"github.com/grafana/grafana/pkg/bus"
@@ -69,9 +70,30 @@ func UpdateOrgUser(cmd *m.UpdateOrgUserCommand) error {
func GetOrgUsers(query *m.GetOrgUsersQuery) error {
query.Result = make([]*m.OrgUserDTO, 0)
sess := x.Table("org_user")
sess.Join("INNER", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
sess.Where("org_user.org_id=?", query.OrgId)
whereConditions := make([]string, 0)
whereParams := make([]interface{}, 0)
whereConditions = append(whereConditions, "org_user.org_id = ?")
whereParams = append(whereParams, query.OrgId)
if query.Query != "" {
queryWithWildcards := "%" + query.Query + "%"
whereConditions = append(whereConditions, "(email "+dialect.LikeStr()+" ? OR name "+dialect.LikeStr()+" ? OR login "+dialect.LikeStr()+" ?)")
whereParams = append(whereParams, queryWithWildcards, queryWithWildcards, queryWithWildcards)
}
if len(whereConditions) > 0 {
sess.Where(strings.Join(whereConditions, " AND "), whereParams...)
}
if query.Limit > 0 {
sess.Limit(query.Limit, 0)
}
sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role", "user.last_seen_at")
sess.Asc("user.email", "user.login")
+3 -10
View File
@@ -1,8 +1,6 @@
package sqlstore
import (
"fmt"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
@@ -17,17 +15,13 @@ func init() {
}
func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
var err error
playlist := m.Playlist{
Name: cmd.Name,
Interval: cmd.Interval,
OrgId: cmd.OrgId,
}
_, err = x.Insert(&playlist)
fmt.Printf("%v", playlist.Id)
_, err := x.Insert(&playlist)
playlistItems := make([]m.PlaylistItem, 0)
for _, item := range cmd.Items {
@@ -47,7 +41,6 @@ func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
}
func UpdatePlaylist(cmd *m.UpdatePlaylistCommand) error {
var err error
playlist := m.Playlist{
Id: cmd.Id,
OrgId: cmd.OrgId,
@@ -68,7 +61,7 @@ func UpdatePlaylist(cmd *m.UpdatePlaylistCommand) error {
Interval: playlist.Interval,
}
_, err = x.Id(cmd.Id).Cols("id", "name", "interval").Update(&playlist)
_, err := x.ID(cmd.Id).Cols("id", "name", "interval").Update(&playlist)
if err != nil {
return err
@@ -104,7 +97,7 @@ func GetPlaylist(query *m.GetPlaylistByIdQuery) error {
}
playlist := m.Playlist{}
_, err := x.Id(query.Id).Get(&playlist)
_, err := x.ID(query.Id).Get(&playlist)
query.Result = &playlist
+8 -25
View File
@@ -1,7 +1,6 @@
package sqlstore
import (
"bytes"
"strings"
m "github.com/grafana/grafana/pkg/models"
@@ -9,6 +8,7 @@ import (
// SearchBuilder is a builder/object mother that builds a dashboard search query
type SearchBuilder struct {
SqlBuilder
tags []string
isStarred bool
limit int
@@ -18,14 +18,14 @@ type SearchBuilder struct {
whereTypeFolder bool
whereTypeDash bool
whereFolderIds []int64
sql bytes.Buffer
params []interface{}
permission m.PermissionType
}
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int) *SearchBuilder {
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int, permission m.PermissionType) *SearchBuilder {
searchBuilder := &SearchBuilder{
signedInUser: signedInUser,
limit: limit,
permission: permission,
}
return searchBuilder
@@ -101,11 +101,13 @@ func (sb *SearchBuilder) buildSelect() {
sb.sql.WriteString(
`SELECT
dashboard.id,
dashboard.uid,
dashboard.title,
dashboard.slug,
dashboard_tag.term,
dashboard.is_folder,
dashboard.folder_id,
folder.uid as folder_uid,
folder.slug as folder_slug,
folder.title as folder_title
FROM `)
@@ -151,10 +153,7 @@ func (sb *SearchBuilder) buildMainQuery() {
sb.sql.WriteString(` WHERE `)
sb.buildSearchWhereClause()
sb.sql.WriteString(`
LIMIT ?) as ids
INNER JOIN dashboard on ids.id = dashboard.id
`)
sb.sql.WriteString(` LIMIT ?) as ids INNER JOIN dashboard on ids.id = dashboard.id `)
sb.params = append(sb.params, sb.limit)
}
@@ -174,23 +173,7 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
}
}
if sb.signedInUser.OrgRole != m.ROLE_ADMIN {
allowedDashboardsSubQuery := ` AND (dashboard.has_acl = ` + dialect.BooleanStr(false) + ` OR dashboard.id in (
SELECT distinct d.id AS DashboardId
FROM dashboard AS d
LEFT JOIN dashboard_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
LEFT JOIN org_user ou on ou.role = da.role
WHERE
d.has_acl = ` + dialect.BooleanStr(true) + ` and
(da.user_id = ? or ugm.user_id = ? or ou.id is not null)
and d.org_id = ?
)
)`
sb.sql.WriteString(allowedDashboardsSubQuery)
sb.params = append(sb.params, sb.signedInUser.UserId, sb.signedInUser.UserId, sb.signedInUser.OrgId)
}
sb.writeDashboardPermissionFilter(sb.signedInUser, sb.permission)
if len(sb.whereTitle) > 0 {
sb.sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
+2 -1
View File
@@ -16,7 +16,8 @@ func TestSearchBuilder(t *testing.T) {
OrgId: 1,
UserId: 1,
}
sb := NewSearchBuilder(signedInUser, 1000)
sb := NewSearchBuilder(signedInUser, 1000, m.PERMISSION_VIEW)
Convey("When building a normal search", func() {
sql, params := sb.IsStarred().WithTitle("test").ToSql()
+75
View File
@@ -0,0 +1,75 @@
package sqlstore
import (
"bytes"
"strings"
m "github.com/grafana/grafana/pkg/models"
)
type SqlBuilder struct {
sql bytes.Buffer
params []interface{}
}
func (sb *SqlBuilder) Write(sql string, params ...interface{}) {
sb.sql.WriteString(sql)
if len(params) > 0 {
sb.params = append(sb.params, params...)
}
}
func (sb *SqlBuilder) GetSqlString() string {
return sb.sql.String()
}
func (sb *SqlBuilder) AddParams(params ...interface{}) {
sb.params = append(sb.params, params...)
}
func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, permission m.PermissionType) {
if user.OrgRole == m.ROLE_ADMIN {
return
}
okRoles := []interface{}{user.OrgRole}
if user.OrgRole == m.ROLE_EDITOR {
okRoles = append(okRoles, m.ROLE_VIEWER)
}
falseStr := dialect.BooleanStr(false)
sb.sql.WriteString(` AND
(
dashboard.id IN (
SELECT distinct d.id AS DashboardId
FROM dashboard AS d
LEFT JOIN dashboard folder on folder.id = d.folder_id
LEFT JOIN dashboard_acl AS da ON
da.dashboard_id = d.id OR
da.dashboard_id = d.folder_id OR
(
-- include default permissions -->
da.org_id = -1 AND (
(folder.id IS NOT NULL AND folder.has_acl = ` + falseStr + `) OR
(folder.id IS NULL AND d.has_acl = ` + falseStr + `)
)
)
LEFT JOIN team_member as ugm on ugm.team_id = da.team_id
WHERE
d.org_id = ? AND
da.permission >= ? AND
(
da.user_id = ? OR
ugm.user_id = ? OR
da.role IN (?` + strings.Repeat(",?", len(okRoles)-1) + `)
)
)
)`)
sb.params = append(sb.params, user.OrgId, permission, user.UserId, user.UserId)
sb.params = append(sb.params, okRoles...)
}
+45 -2
View File
@@ -7,14 +7,15 @@ import (
"path"
"path/filepath"
"strings"
"testing"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting"
"github.com/go-sql-driver/mysql"
@@ -103,7 +104,6 @@ func SetEngine(engine *xorm.Engine) (err error) {
// Init repo instances
annotations.SetRepository(&SqlAnnotationRepo{})
dashboards.SetRepository(&dashboards.DashboardRepository{})
return nil
}
@@ -218,3 +218,46 @@ func LoadConfig() {
DbCfg.ServerCertName = sec.Key("server_cert_name").String()
DbCfg.Path = sec.Key("path").MustString("data/grafana.db")
}
var (
dbSqlite = "sqlite"
dbMySql = "mysql"
dbPostgres = "postgres"
)
func InitTestDB(t *testing.T) *xorm.Engine {
selectedDb := dbSqlite
//selectedDb := dbMySql
//selectedDb := dbPostgres
var x *xorm.Engine
var err error
// environment variable present for test db?
if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
selectedDb = db
}
switch strings.ToLower(selectedDb) {
case dbMySql:
x, err = xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
case dbPostgres:
x, err = xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
default:
x, err = xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
}
// x.ShowSQL()
if err != nil {
t.Fatalf("Failed to init in memory sqllite3 db %v", err)
}
sqlutil.CleanDB(x)
if err := SetEngine(x); err != nil {
t.Fatal(err)
}
return x
}
+1 -1
View File
@@ -11,7 +11,7 @@ type TestDB struct {
ConnStr string
}
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:?_loc=Local"}
var TestDB_Sqlite3 = TestDB{DriverName: "sqlite3", ConnStr: ":memory:"}
var TestDB_Mysql = TestDB{DriverName: "mysql", ConnStr: "grafana:password@tcp(localhost:3306)/grafana_tests?collation=utf8mb4_unicode_ci"}
var TestDB_Postgres = TestDB{DriverName: "postgres", ConnStr: "user=grafanatest password=grafanatest host=localhost port=5432 dbname=grafanatest sslmode=disable"}
var TestDB_Mssql = TestDB{DriverName: "mssql", ConnStr: "server=localhost;port=1433;database=grafana_tests;user id=grafana;password=password"}
+65 -62
View File
@@ -13,12 +13,12 @@ func init() {
bus.AddHandler("sql", GetAdminStats)
}
var activeUserTimeLimit time.Duration = time.Hour * 24 * 14
var activeUserTimeLimit time.Duration = time.Hour * 24 * 30
func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error {
var rawSql = `SELECT COUNT(*) as count, type FROM data_source GROUP BY type`
query.Result = make([]*m.DataSourceStats, 0)
err := x.Sql(rawSql).Find(&query.Result)
err := x.SQL(rawSql).Find(&query.Result)
if err != nil {
return err
}
@@ -30,36 +30,39 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
var rawSql = `SELECT
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
) AS users,
FROM ` + dialect.Quote("user") + `
) AS users,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*) FROM ` + dialect.Quote("star") + `
) AS stars,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
(
SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` where last_seen_at > ?
) as active_users
) as active_users
`
activeUserDeadlineDate := time.Now().Add(-activeUserTimeLimit)
var stats m.SystemStats
_, err := x.Sql(rawSql, activeUserDeadlineDate).Get(&stats)
_, err := x.SQL(rawSql, activeUserDeadlineDate).Get(&stats)
if err != nil {
return err
}
@@ -70,51 +73,51 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
func GetAdminStats(query *m.GetAdminStatsQuery) error {
var rawSql = `SELECT
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
) AS users,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard_snapshot") + `
) AS snapshots,
(
SELECT COUNT( DISTINCT ( ` + dialect.Quote("term") + ` ))
FROM ` + dialect.Quote("dashboard_tag") + `
) AS tags,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*) FROM ` + dialect.Quote("star") + `
) AS stars,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
) AS users,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard_snapshot") + `
) AS snapshots,
(
SELECT COUNT( DISTINCT ( ` + dialect.Quote("term") + ` ))
FROM ` + dialect.Quote("dashboard_tag") + `
) AS tags,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*) FROM ` + dialect.Quote("star") + `
) AS stars,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
(
SELECT COUNT(*)
from ` + dialect.Quote("user") + ` where last_seen_at > ?
from ` + dialect.Quote("user") + ` where last_seen_at > ?
) as active_users
`
`
activeUserDeadlineDate := time.Now().Add(-activeUserTimeLimit)
var stats m.AdminStats
_, err := x.Sql(rawSql, activeUserDeadlineDate).Get(&stats)
_, err := x.SQL(rawSql, activeUserDeadlineDate).Get(&stats)
if err != nil {
return err
}
+44 -18
View File
@@ -25,7 +25,7 @@ func init() {
func CreateTeam(cmd *m.CreateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if isNameTaken, err := isTeamNameTaken(cmd.Name, 0, sess); err != nil {
if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, 0, sess); err != nil {
return err
} else if isNameTaken {
return m.ErrTeamNameTaken
@@ -50,7 +50,7 @@ func CreateTeam(cmd *m.CreateTeamCommand) error {
func UpdateTeam(cmd *m.UpdateTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if isNameTaken, err := isTeamNameTaken(cmd.Name, cmd.Id, sess); err != nil {
if isNameTaken, err := isTeamNameTaken(cmd.OrgId, cmd.Name, cmd.Id, sess); err != nil {
return err
} else if isNameTaken {
return m.ErrTeamNameTaken
@@ -78,22 +78,23 @@ func UpdateTeam(cmd *m.UpdateTeamCommand) error {
})
}
// DeleteTeam will delete a team, its member and any permissions connected to the team
func DeleteTeam(cmd *m.DeleteTeamCommand) error {
return inTransaction(func(sess *DBSession) error {
if res, err := sess.Query("SELECT 1 from team WHERE id=?", cmd.Id); err != nil {
if teamExists, err := teamExists(cmd.OrgId, cmd.Id, sess); err != nil {
return err
} else if len(res) != 1 {
} else if !teamExists {
return m.ErrTeamNotFound
}
deletes := []string{
"DELETE FROM team_member WHERE team_id = ?",
"DELETE FROM team WHERE id = ?",
"DELETE FROM dashboard_acl WHERE team_id = ?",
"DELETE FROM team_member WHERE org_id=? and team_id = ?",
"DELETE FROM team WHERE org_id=? and id = ?",
"DELETE FROM dashboard_acl WHERE org_id=? and team_id = ?",
}
for _, sql := range deletes {
_, err := sess.Exec(sql, cmd.Id)
_, err := sess.Exec(sql, cmd.OrgId, cmd.Id)
if err != nil {
return err
}
@@ -102,9 +103,19 @@ func DeleteTeam(cmd *m.DeleteTeamCommand) error {
})
}
func isTeamNameTaken(name string, existingId int64, sess *DBSession) (bool, error) {
func teamExists(orgId int64, teamId int64, sess *DBSession) (bool, error) {
if res, err := sess.Query("SELECT 1 from team WHERE org_id=? and id=?", orgId, teamId); err != nil {
return false, err
} else if len(res) != 1 {
return false, nil
}
return true, nil
}
func isTeamNameTaken(orgId int64, name string, existingId int64, sess *DBSession) (bool, error) {
var team m.Team
exists, err := sess.Where("name=?", name).Get(&team)
exists, err := sess.Where("org_id=? and name=?", orgId, name).Get(&team)
if err != nil {
return false, nil
@@ -128,6 +139,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
sql.WriteString(`select
team.id as id,
team.org_id,
team.name as name,
team.email as email,
(select count(*) from team_member where team_member.team_id = team.id) as member_count
@@ -176,7 +188,7 @@ func SearchTeams(query *m.SearchTeamsQuery) error {
func GetTeamById(query *m.GetTeamByIdQuery) error {
var team m.Team
exists, err := x.Id(query.Id).Get(&team)
exists, err := x.Where("org_id=? and id=?", query.OrgId, query.Id).Get(&team)
if err != nil {
return err
}
@@ -189,12 +201,13 @@ func GetTeamById(query *m.GetTeamByIdQuery) error {
return nil
}
// GetTeamsByUser is used by the Guardian when checking a users' permissions
func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
query.Result = make([]*m.Team, 0)
sess := x.Table("team")
sess.Join("INNER", "team_member", "team.id=team_member.team_id")
sess.Where("team_member.user_id=?", query.UserId)
sess.Where("team.org_id=? and team_member.user_id=?", query.OrgId, query.UserId)
err := sess.Find(&query.Result)
if err != nil {
@@ -204,17 +217,18 @@ func GetTeamsByUser(query *m.GetTeamsByUserQuery) error {
return nil
}
// AddTeamMember adds a user to a team
func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
if res, err := sess.Query("SELECT 1 from team_member WHERE team_id=? and user_id=?", cmd.TeamId, cmd.UserId); err != nil {
if res, err := sess.Query("SELECT 1 from team_member WHERE org_id=? and team_id=? and user_id=?", cmd.OrgId, cmd.TeamId, cmd.UserId); err != nil {
return err
} else if len(res) == 1 {
return m.ErrTeamMemberAlreadyAdded
}
if res, err := sess.Query("SELECT 1 from team WHERE id=?", cmd.TeamId); err != nil {
if teamExists, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
return err
} else if len(res) != 1 {
} else if !teamExists {
return m.ErrTeamNotFound
}
@@ -231,23 +245,35 @@ func AddTeamMember(cmd *m.AddTeamMemberCommand) error {
})
}
// RemoveTeamMember removes a member from a team
func RemoveTeamMember(cmd *m.RemoveTeamMemberCommand) error {
return inTransaction(func(sess *DBSession) error {
var rawSql = "DELETE FROM team_member WHERE team_id=? and user_id=?"
_, err := sess.Exec(rawSql, cmd.TeamId, cmd.UserId)
if teamExists, err := teamExists(cmd.OrgId, cmd.TeamId, sess); err != nil {
return err
} else if !teamExists {
return m.ErrTeamNotFound
}
var rawSql = "DELETE FROM team_member WHERE org_id=? and team_id=? and user_id=?"
res, err := sess.Exec(rawSql, cmd.OrgId, cmd.TeamId, cmd.UserId)
if err != nil {
return err
}
rows, err := res.RowsAffected()
if rows == 0 {
return m.ErrTeamMemberNotFound
}
return err
})
}
// GetTeamMembers return a list of members for the specified team
func GetTeamMembers(query *m.GetTeamMembersQuery) error {
query.Result = make([]*m.TeamMemberDTO, 0)
sess := x.Table("team_member")
sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user")))
sess.Where("team_member.team_id=?", query.TeamId)
sess.Where("team_member.org_id=? and team_member.team_id=?", query.OrgId, query.TeamId)
sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login")
sess.Asc("user.login", "user.email")
+25 -19
View File
@@ -27,8 +27,9 @@ func TestTeamCommandsAndQueries(t *testing.T) {
userIds = append(userIds, userCmd.Result.Id)
}
group1 := m.CreateTeamCommand{Name: "group1 name", Email: "test1@test.com"}
group2 := m.CreateTeamCommand{Name: "group2 name", Email: "test2@test.com"}
var testOrgId int64 = 1
group1 := m.CreateTeamCommand{OrgId: testOrgId, Name: "group1 name", Email: "test1@test.com"}
group2 := m.CreateTeamCommand{OrgId: testOrgId, Name: "group2 name", Email: "test2@test.com"}
err := CreateTeam(&group1)
So(err, ShouldBeNil)
@@ -36,7 +37,7 @@ func TestTeamCommandsAndQueries(t *testing.T) {
So(err, ShouldBeNil)
Convey("Should be able to create teams and add users", func() {
query := &m.SearchTeamsQuery{Name: "group1 name", Page: 1, Limit: 10}
query := &m.SearchTeamsQuery{OrgId: testOrgId, Name: "group1 name", Page: 1, Limit: 10}
err = SearchTeams(query)
So(err, ShouldBeNil)
So(query.Page, ShouldEqual, 1)
@@ -44,25 +45,27 @@ func TestTeamCommandsAndQueries(t *testing.T) {
team1 := query.Result.Teams[0]
So(team1.Name, ShouldEqual, "group1 name")
So(team1.Email, ShouldEqual, "test1@test.com")
So(team1.OrgId, ShouldEqual, testOrgId)
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: team1.Id, UserId: userIds[0]})
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: team1.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
q1 := &m.GetTeamMembersQuery{TeamId: team1.Id}
q1 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: team1.Id}
err = GetTeamMembers(q1)
So(err, ShouldBeNil)
So(q1.Result[0].TeamId, ShouldEqual, team1.Id)
So(q1.Result[0].Login, ShouldEqual, "loginuser0")
So(q1.Result[0].OrgId, ShouldEqual, testOrgId)
})
Convey("Should be able to search for teams", func() {
query := &m.SearchTeamsQuery{Query: "group", Page: 1}
query := &m.SearchTeamsQuery{OrgId: testOrgId, Query: "group", Page: 1}
err = SearchTeams(query)
So(err, ShouldBeNil)
So(len(query.Result.Teams), ShouldEqual, 2)
So(query.Result.TotalCount, ShouldEqual, 2)
query2 := &m.SearchTeamsQuery{Query: ""}
query2 := &m.SearchTeamsQuery{OrgId: testOrgId, Query: ""}
err = SearchTeams(query2)
So(err, ShouldBeNil)
So(len(query2.Result.Teams), ShouldEqual, 2)
@@ -70,9 +73,9 @@ func TestTeamCommandsAndQueries(t *testing.T) {
Convey("Should be able to return all teams a user is member of", func() {
groupId := group2.Result.Id
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[0]})
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[0]})
query := &m.GetTeamsByUserQuery{UserId: userIds[0]}
query := &m.GetTeamsByUserQuery{OrgId: testOrgId, UserId: userIds[0]}
err = GetTeamsByUser(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
@@ -81,31 +84,34 @@ func TestTeamCommandsAndQueries(t *testing.T) {
})
Convey("Should be able to remove users from a group", func() {
err = RemoveTeamMember(&m.RemoveTeamMemberCommand{TeamId: group1.Result.Id, UserId: userIds[0]})
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
q1 := &m.GetTeamMembersQuery{TeamId: group1.Result.Id}
err = GetTeamMembers(q1)
err = RemoveTeamMember(&m.RemoveTeamMemberCommand{OrgId: testOrgId, TeamId: group1.Result.Id, UserId: userIds[0]})
So(err, ShouldBeNil)
So(len(q1.Result), ShouldEqual, 0)
q2 := &m.GetTeamMembersQuery{OrgId: testOrgId, TeamId: group1.Result.Id}
err = GetTeamMembers(q2)
So(err, ShouldBeNil)
So(len(q2.Result), ShouldEqual, 0)
})
Convey("Should be able to remove a group with users and permissions", func() {
groupId := group2.Result.Id
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[1]})
err := AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[1]})
So(err, ShouldBeNil)
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: 1, TeamId: groupId, UserId: userIds[2]})
err = AddTeamMember(&m.AddTeamMemberCommand{OrgId: testOrgId, TeamId: groupId, UserId: userIds[2]})
So(err, ShouldBeNil)
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permission: m.PERMISSION_EDIT, TeamId: groupId})
err = testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: testOrgId, Permission: m.PERMISSION_EDIT, TeamId: groupId})
err = DeleteTeam(&m.DeleteTeamCommand{Id: groupId})
err = DeleteTeam(&m.DeleteTeamCommand{OrgId: testOrgId, Id: groupId})
So(err, ShouldBeNil)
query := &m.GetTeamByIdQuery{Id: groupId}
query := &m.GetTeamByIdQuery{OrgId: testOrgId, Id: groupId}
err = GetTeamById(query)
So(err, ShouldEqual, m.ErrTeamNotFound)
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: 1}
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: testOrgId}
err = GetDashboardAclInfoList(permQuery)
So(err, ShouldBeNil)
+1 -1
View File
@@ -99,7 +99,7 @@ func TestUserDataAccess(t *testing.T) {
err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId})
So(err, ShouldBeNil)
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT})
testHelperUpdateDashboardAcl(1, m.DashboardAcl{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT})
So(err, ShouldBeNil)
err = SavePreferences(&m.SavePreferencesCommand{UserId: users[0].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark"})