Merge remote-tracking branch 'origin/master' into develop

This commit is contained in:
Torkel Ödegaard
2017-12-13 19:18:10 +01:00
45 changed files with 200 additions and 194 deletions
+1 -5
View File
@@ -229,10 +229,6 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
return Json(200, util.DynMap{"status": "success", "slug": dashboard.Slug, "version": dashboard.Version, "id": dashboard.Id})
}
func canEditDashboard(role m.RoleType) bool {
return role == m.ROLE_ADMIN || role == m.ROLE_EDITOR || role == m.ROLE_READ_ONLY_EDITOR
}
func GetHomeDashboard(c *middleware.Context) Response {
prefsQuery := m.GetPreferencesWithDefaultsQuery{OrgId: c.OrgId, UserId: c.UserId}
if err := bus.Dispatch(&prefsQuery); err != nil {
@@ -258,7 +254,7 @@ func GetHomeDashboard(c *middleware.Context) Response {
dash := dtos.DashboardFullWithMeta{}
dash.Meta.IsHome = true
dash.Meta.CanEdit = c.SignedInUser.HasRole(m.ROLE_READ_ONLY_EDITOR)
dash.Meta.CanEdit = c.SignedInUser.HasRole(m.ROLE_EDITOR)
dash.Meta.FolderTitle = "Root"
jsonParser := json.NewDecoder(file)
+6 -15
View File
@@ -18,14 +18,13 @@ var (
type RoleType string
const (
ROLE_VIEWER RoleType = "Viewer"
ROLE_EDITOR RoleType = "Editor"
ROLE_READ_ONLY_EDITOR RoleType = "Read Only Editor"
ROLE_ADMIN RoleType = "Admin"
ROLE_VIEWER RoleType = "Viewer"
ROLE_EDITOR RoleType = "Editor"
ROLE_ADMIN RoleType = "Admin"
)
func (r RoleType) IsValid() bool {
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR
return r == ROLE_VIEWER || r == ROLE_ADMIN || r == ROLE_EDITOR
}
func (r RoleType) Includes(other RoleType) bool {
@@ -33,16 +32,8 @@ func (r RoleType) Includes(other RoleType) bool {
return true
}
if other == ROLE_READ_ONLY_EDITOR {
return r == ROLE_EDITOR || r == ROLE_READ_ONLY_EDITOR
}
if other == ROLE_EDITOR {
return r == ROLE_EDITOR
}
if other == ROLE_VIEWER {
return r == ROLE_READ_ONLY_EDITOR || r == ROLE_EDITOR || r == ROLE_VIEWER
if r == ROLE_EDITOR {
return other != ROLE_ADMIN
}
return false
-8
View File
@@ -75,14 +75,6 @@ func (c *EvalContext) ShouldUpdateAlertState() bool {
return c.Rule.State != c.PrevAlertState
}
func (c *EvalContext) ShouldSendNotification() bool {
if (c.PrevAlertState == m.AlertStatePending) && (c.Rule.State == m.AlertStateOK) {
return false
}
return true
}
func (a *EvalContext) GetDurationMs() float64 {
return float64(a.EndTime.Nanosecond()-a.StartTime.Nanosecond()) / float64(1000000)
}
@@ -28,21 +28,5 @@ func TestAlertingEvalContext(t *testing.T) {
So(ctx.ShouldUpdateAlertState(), ShouldBeFalse)
})
})
Convey("Should send notifications", func() {
Convey("pending -> ok", func() {
ctx.PrevAlertState = models.AlertStatePending
ctx.Rule.State = models.AlertStateOK
So(ctx.ShouldSendNotification(), ShouldBeFalse)
})
Convey("ok -> alerting", func() {
ctx.PrevAlertState = models.AlertStateOK
ctx.Rule.State = models.AlertStateAlerting
So(ctx.ShouldSendNotification(), ShouldBeTrue)
})
})
})
}
+5
View File
@@ -39,6 +39,11 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) {
break
}
if i == 0 {
firing = cr.Firing
noDataFound = cr.NoDataFound
}
// calculating Firing based on operator
if cr.Operator == "or" {
firing = firing || cr.Firing
@@ -36,6 +36,16 @@ func TestAlertingEvaluationHandler(t *testing.T) {
So(context.ConditionEvals, ShouldEqual, "true = true")
})
Convey("Show return triggered with single passing condition2", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{&conditionStub{firing: true, operator: "and"}},
})
handler.Eval(context)
So(context.Firing, ShouldEqual, true)
So(context.ConditionEvals, ShouldEqual, "true = true")
})
Convey("Show return false with not passing asdf", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
@@ -131,6 +141,33 @@ func TestAlertingEvaluationHandler(t *testing.T) {
So(context.ConditionEvals, ShouldEqual, "[[true OR false] OR true] = true")
})
Convey("Should return false if no condition is firing using OR operator", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
&conditionStub{firing: false, operator: "or"},
&conditionStub{firing: false, operator: "or"},
&conditionStub{firing: false, operator: "or"},
},
})
handler.Eval(context)
So(context.Firing, ShouldEqual, false)
So(context.ConditionEvals, ShouldEqual, "[[false OR false] OR false] = false")
})
Convey("Should retuasdfrn no data if one condition has nodata", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
&conditionStub{operator: "or", noData: false},
&conditionStub{operator: "or", noData: false},
&conditionStub{operator: "or", noData: false},
},
})
handler.Eval(context)
So(context.NoDataFound, ShouldBeFalse)
})
Convey("Should return no data if one condition has nodata", func() {
context := NewEvalContext(context.TODO(), &Rule{
Conditions: []Condition{
+1 -1
View File
@@ -15,7 +15,7 @@ type Notifier interface {
Notify(evalContext *EvalContext) error
GetType() string
NeedsImage() bool
PassesFilter(rule *Rule) bool
ShouldNotify(evalContext *EvalContext) bool
GetNotifierId() int64
GetIsDefault() bool
+5 -19
View File
@@ -24,7 +24,7 @@ type NotifierPlugin struct {
}
type NotificationService interface {
Send(context *EvalContext) error
SendIfNeeded(context *EvalContext) error
}
func NewNotificationService() NotificationService {
@@ -41,14 +41,12 @@ func newNotificationService() *notificationService {
}
}
func (n *notificationService) Send(context *EvalContext) error {
notifiers, err := n.getNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
func (n *notificationService) SendIfNeeded(context *EvalContext) error {
notifiers, err := n.getNeededNotifiers(context.Rule.OrgId, context.Rule.Notifications, context)
if err != nil {
return err
}
n.log.Info("Sending notifications for", "ruleId", context.Rule.Id, "sent count", len(notifiers))
if len(notifiers) == 0 {
return nil
}
@@ -110,7 +108,7 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) {
return nil
}
func (n *notificationService) getNotifiers(orgId int64, notificationIds []int64, context *EvalContext) (NotifierSlice, error) {
func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds []int64, context *EvalContext) (NotifierSlice, error) {
query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds}
if err := bus.Dispatch(query); err != nil {
@@ -122,7 +120,7 @@ func (n *notificationService) getNotifiers(orgId int64, notificationIds []int64,
if not, err := n.createNotifierFor(notification); err != nil {
return nil, err
} else {
if shouldUseNotification(not, context) {
if not.ShouldNotify(context) {
result = append(result, not)
}
}
@@ -140,18 +138,6 @@ func (n *notificationService) createNotifierFor(model *m.AlertNotification) (Not
return notifierPlugin.Factory(model)
}
func shouldUseNotification(notifier Notifier, context *EvalContext) bool {
if !context.Firing {
return true
}
if context.Error != nil {
return true
}
return notifier.PassesFilter(context.Rule)
}
type NotifierFactory func(notification *m.AlertNotification) (Notifier, error)
var notifierFactories map[string]*NotifierPlugin = make(map[string]*NotifierPlugin)
-89
View File
@@ -1,89 +0,0 @@
package alerting
import (
"testing"
"fmt"
"github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
type FakeNotifier struct {
FakeMatchResult bool
}
func (fn *FakeNotifier) GetType() string {
return "FakeNotifier"
}
func (fn *FakeNotifier) NeedsImage() bool {
return true
}
func (n *FakeNotifier) GetNotifierId() int64 {
return 0
}
func (n *FakeNotifier) GetIsDefault() bool {
return false
}
func (fn *FakeNotifier) Notify(alertResult *EvalContext) error { return nil }
func (fn *FakeNotifier) PassesFilter(rule *Rule) bool {
return fn.FakeMatchResult
}
func TestAlertNotificationExtraction(t *testing.T) {
Convey("Notifier tests", t, func() {
Convey("none firing alerts", func() {
ctx := &EvalContext{
Firing: false,
Rule: &Rule{
State: m.AlertStateAlerting,
},
}
notifier := &FakeNotifier{FakeMatchResult: false}
So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
})
Convey("execution error cannot be ignored", func() {
ctx := &EvalContext{
Firing: true,
Error: fmt.Errorf("I used to be a programmer just like you"),
Rule: &Rule{
State: m.AlertStateOK,
},
}
notifier := &FakeNotifier{FakeMatchResult: false}
So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
})
Convey("firing alert that match", func() {
ctx := &EvalContext{
Firing: true,
Rule: &Rule{
State: models.AlertStateAlerting,
},
}
notifier := &FakeNotifier{FakeMatchResult: true}
So(shouldUseNotification(notifier, ctx), ShouldBeTrue)
})
Convey("firing alert that dont match", func() {
ctx := &EvalContext{
Firing: true,
Rule: &Rule{State: m.AlertStateOK},
}
notifier := &FakeNotifier{FakeMatchResult: false}
So(shouldUseNotification(notifier, ctx), ShouldBeFalse)
})
})
}
+8 -1
View File
@@ -2,6 +2,7 @@ package notifiers
import (
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
)
@@ -25,7 +26,13 @@ func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model
}
}
func (n *NotifierBase) PassesFilter(rule *alerting.Rule) bool {
func defaultShouldNotify(context *alerting.EvalContext) bool {
if context.PrevAlertState == context.Rule.State {
return false
}
if (context.PrevAlertState == m.AlertStatePending) && (context.Rule.State == m.AlertStateOK) {
return false
}
return true
}
@@ -0,0 +1,32 @@
package notifiers
import (
"context"
"testing"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
)
func TestBaseNotifier(t *testing.T) {
Convey("Base notifier tests", t, func() {
Convey("should notify", func() {
Convey("pending -> ok", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: m.AlertStatePending,
})
context.Rule.State = m.AlertStateOK
So(defaultShouldNotify(context), ShouldBeFalse)
})
Convey("ok -> alerting", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: m.AlertStateOK,
})
context.Rule.State = m.AlertStateAlerting
So(defaultShouldNotify(context), ShouldBeTrue)
})
})
})
}
@@ -38,6 +38,10 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}, nil
}
func (this *DingDingNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
type DingDingNotifier struct {
NotifierBase
Url string
@@ -9,7 +9,7 @@ import (
)
func TestDingDingNotifier(t *testing.T) {
Convey("Line notifier tests", t, func() {
Convey("Dingding notifier tests", t, func() {
Convey("empty settings should return error", func() {
json := `{ }`
+4
View File
@@ -58,6 +58,10 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}, nil
}
func (this *EmailNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending alert notification to", "addresses", this.Addresses)
@@ -75,6 +75,10 @@ type HipChatNotifier struct {
log log.Logger
}
func (this *HipChatNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
+4
View File
@@ -57,6 +57,10 @@ type KafkaNotifier struct {
log log.Logger
}
func (this *KafkaNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
state := evalContext.Rule.State
+4
View File
@@ -51,6 +51,10 @@ type LineNotifier struct {
log log.Logger
}
func (this *LineNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *LineNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing line notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
@@ -62,6 +62,10 @@ type OpsGenieNotifier struct {
log log.Logger
}
func (this *OpsGenieNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error {
var err error
@@ -63,6 +63,10 @@ type PagerdutyNotifier struct {
log log.Logger
}
func (this *PagerdutyNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
if evalContext.Rule.State == m.AlertStateOK && !this.AutoResolve {
@@ -123,6 +123,10 @@ type PushoverNotifier struct {
log log.Logger
}
func (this *PushoverNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
ruleUrl, err := evalContext.GetRuleUrl()
if err != nil {
+4
View File
@@ -71,6 +71,10 @@ type SensuNotifier struct {
log log.Logger
}
func (this *SensuNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending sensu result")
+4
View File
@@ -98,6 +98,10 @@ type SlackNotifier struct {
log log.Logger
}
func (this *SlackNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing slack notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
@@ -78,7 +78,6 @@ func TestSlackNotifier(t *testing.T) {
So(slackNotifier.Mention, ShouldEqual, "@carl")
So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX")
})
})
})
}
+4
View File
@@ -47,6 +47,10 @@ type TeamsNotifier struct {
log log.Logger
}
func (this *TeamsNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
@@ -76,6 +76,10 @@ func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}, nil
}
func (this *TelegramNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending alert notification to", "bot_token", this.BotToken)
this.log.Info("Sending alert notification to", "chat_id", this.ChatID)
@@ -114,6 +114,10 @@ func NewThreemaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}, nil
}
func (this *ThreemaNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error {
notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID)
notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID)
@@ -68,6 +68,10 @@ type VictoropsNotifier struct {
log log.Logger
}
func (this *VictoropsNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
// Notify sends notification to Victorops via POST to URL endpoint
func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name)
@@ -65,6 +65,10 @@ type WebhookNotifier struct {
log log.Logger
}
func (this *WebhookNotifier) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
}
func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Sending webhook")
@@ -18,7 +18,7 @@ func TestWebhookNotifier(t *testing.T) {
settingsJSON, _ := simplejson.NewJson([]byte(json))
model := &m.AlertNotification{
Name: "ops",
Type: "email",
Type: "webhook",
Settings: settingsJSON,
}
@@ -35,7 +35,7 @@ func TestWebhookNotifier(t *testing.T) {
settingsJSON, _ := simplejson.NewJson([]byte(json))
model := &m.AlertNotification{
Name: "ops",
Type: "email",
Type: "webhook",
Settings: settingsJSON,
}
@@ -44,7 +44,7 @@ func TestWebhookNotifier(t *testing.T) {
So(err, ShouldBeNil)
So(webhookNotifier.Name, ShouldEqual, "ops")
So(webhookNotifier.Type, ShouldEqual, "email")
So(webhookNotifier.Type, ShouldEqual, "webhook")
So(webhookNotifier.Url, ShouldEqual, "http://google.com")
})
})
+2 -4
View File
@@ -85,11 +85,9 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
if err := annotationRepo.Save(&item); err != nil {
handler.log.Error("Failed to save annotation for new alert state", "error", err)
}
if evalContext.ShouldSendNotification() {
handler.notifier.Send(evalContext)
}
}
handler.notifier.SendIfNeeded(evalContext)
return nil
}
-4
View File
@@ -51,10 +51,6 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
}
orgRole := g.user.OrgRole
if orgRole == m.ROLE_READ_ONLY_EDITOR {
orgRole = m.ROLE_VIEWER
}
teamAclItems := []*m.DashboardAclInfoDTO{}
for _, p := range acl {
@@ -83,4 +83,10 @@ func addOrgMigrations(mg *Migrator) {
mg.AddMigration("Update org_user table charset", NewTableCharsetMigration("org_user", []*Column{
{Name: "role", Type: DB_NVarchar, Length: 20},
}))
const migrateReadOnlyViewersToViewers = `UPDATE org_user SET role = 'Viewer' WHERE role = 'Read Only Editor'`
mg.AddMigration("Migrate all Read Only Viewers to Viewers", new(RawSqlMigration).
Sqlite(migrateReadOnlyViewersToViewers).
Postgres(migrateReadOnlyViewersToViewers).
Mysql(migrateReadOnlyViewersToViewers))
}
+3 -1
View File
@@ -106,6 +106,7 @@ var (
ExternalUserMngLinkUrl string
ExternalUserMngLinkName string
ExternalUserMngInfo string
ViewersCanEdit bool
// Http auth
AdminUser string
@@ -540,13 +541,14 @@ func NewConfigContext(args *CommandLineArgs) error {
AllowUserSignUp = users.Key("allow_sign_up").MustBool(true)
AllowUserOrgCreate = users.Key("allow_org_create").MustBool(true)
AutoAssignOrg = users.Key("auto_assign_org").MustBool(true)
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Read Only Editor", "Viewer"})
AutoAssignOrgRole = users.Key("auto_assign_org_role").In("Editor", []string{"Editor", "Admin", "Viewer"})
VerifyEmailEnabled = users.Key("verify_email_enabled").MustBool(false)
LoginHint = users.Key("login_hint").String()
DefaultTheme = users.Key("default_theme").String()
ExternalUserMngLinkUrl = users.Key("external_manage_link_url").String()
ExternalUserMngLinkName = users.Key("external_manage_link_name").String()
ExternalUserMngInfo = users.Key("external_manage_info").String()
ViewersCanEdit = users.Key("viewers_can_edit").MustBool(false)
// auth
auth := Cfg.Section("auth")