diff --git a/CHANGELOG.md b/CHANGELOG.md index 871ae0137ee..d803ed74f77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ * **Optimzation**: Never issue refresh event when Grafana tab is not visible [#7218](https://github.com/grafana/grafana/issues/7218), thx [@mtanda](https://github.com/mtanda) * **Browser History**: Browser back/forward now works time ranges / zoom, [#7259](https://github.com/grafana/grafana/issues/7259) +## Bugfixes +* **Alerting**: Fixes missing support for no_data and execution error when testing alerts [#7149](https://github.com/grafana/grafana/issues/7149) + # 4.1.1 (2017-01-11) ### Bugfixes diff --git a/docs/sources/http_api/dashboard.md b/docs/sources/http_api/dashboard.md index 5e9891e0a89..5fed69b4a93 100644 --- a/docs/sources/http_api/dashboard.md +++ b/docs/sources/http_api/dashboard.md @@ -200,7 +200,7 @@ Get all tags of dashboards **Example Request**: - GET /api/dashboards/home HTTP/1.1 + GET /api/dashboards/tags HTTP/1.1 Accept: application/json Content-Type: application/json Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk diff --git a/packaging/rpm/control/postinst b/packaging/rpm/control/postinst index fce80719115..331d9927871 100644 --- a/packaging/rpm/control/postinst +++ b/packaging/rpm/control/postinst @@ -6,6 +6,7 @@ set -e startGrafana() { if [ -x /bin/systemctl ] ; then + /bin/systemctl daemon-reload /bin/systemctl start grafana-server.service elif [ -x /etc/init.d/grafana-server ] ; then /etc/init.d/grafana-server start diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 143c72106fe..d3cb3b817fa 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -120,10 +120,10 @@ func AlertTest(c *middleware.Context, dto dtos.AlertTestCommand) Response { } res := backendCmd.Result - dtoRes := &dtos.AlertTestResult{ Firing: res.Firing, ConditionEvals: res.ConditionEvals, + State: res.Rule.State, } if res.Error != nil { diff --git a/pkg/api/dtos/alerting.go b/pkg/api/dtos/alerting.go index f3d64f89524..4f3df88be9f 100644 --- a/pkg/api/dtos/alerting.go +++ b/pkg/api/dtos/alerting.go @@ -3,6 +3,7 @@ package dtos import ( "time" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" ) @@ -36,6 +37,7 @@ type AlertTestCommand struct { type AlertTestResult struct { Firing bool `json:"firing"` + State m.AlertStateType `json:"state"` ConditionEvals string `json:"conditionEvals"` TimeMs string `json:"timeMs"` Error string `json:"error,omitempty"` @@ -51,7 +53,7 @@ type AlertTestResultLog struct { type EvalMatch struct { Tags map[string]string `json:"tags,omitempty"` Metric string `json:"metric"` - Value float64 `json:"value"` + Value null.Float `json:"value"` } type NotificationTestCommand struct { diff --git a/vendor/gopkg.in/guregu/null.v3/float.go b/pkg/components/null/float.go similarity index 92% rename from vendor/gopkg.in/guregu/null.v3/float.go rename to pkg/components/null/float.go index 1f57b959ab7..1e78946e878 100644 --- a/vendor/gopkg.in/guregu/null.v3/float.go +++ b/pkg/components/null/float.go @@ -96,6 +96,16 @@ func (f Float) MarshalText() ([]byte, error) { return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil } +// MarshalText implements encoding.TextMarshaler. +// It will encode a blank string if this Float is null. +func (f Float) String() string { + if !f.Valid { + return "null" + } + + return fmt.Sprintf("%1.3f", f.Float64) +} + // SetValid changes this Float's value and also sets it to be non-null. func (f *Float) SetValid(n float64) { f.Float64 = n diff --git a/pkg/services/alerting/conditions/evaluator.go b/pkg/services/alerting/conditions/evaluator.go index 57abb2edd25..1b8fb952f65 100644 --- a/pkg/services/alerting/conditions/evaluator.go +++ b/pkg/services/alerting/conditions/evaluator.go @@ -3,9 +3,9 @@ package conditions import ( "encoding/json" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/services/alerting" - "gopkg.in/guregu/null.v3" ) var ( diff --git a/pkg/services/alerting/conditions/evaluator_test.go b/pkg/services/alerting/conditions/evaluator_test.go index e2a3cb48396..a97e83247ef 100644 --- a/pkg/services/alerting/conditions/evaluator_test.go +++ b/pkg/services/alerting/conditions/evaluator_test.go @@ -3,10 +3,10 @@ package conditions import ( "testing" - "gopkg.in/guregu/null.v3" - - "github.com/grafana/grafana/pkg/components/simplejson" . "github.com/smartystreets/goconvey/convey" + + "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/components/simplejson" ) func evalutorScenario(json string, reducedValue float64, datapoints ...float64) bool { diff --git a/pkg/services/alerting/conditions/query.go b/pkg/services/alerting/conditions/query.go index d7e0a0c29e3..eafb233a60b 100644 --- a/pkg/services/alerting/conditions/query.go +++ b/pkg/services/alerting/conditions/query.go @@ -6,6 +6,7 @@ import ( "time" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" @@ -45,18 +46,18 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio emptySerieCount := 0 evalMatchCount := 0 var matches []*alerting.EvalMatch + for _, series := range seriesList { reducedValue := c.Reducer.Reduce(series) evalMatch := c.Evaluator.Eval(reducedValue) if reducedValue.Valid == false { emptySerieCount++ - continue } if context.IsTestRun { context.Logs = append(context.Logs, &alerting.ResultLogEntry{ - Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %1.3f", c.Index, evalMatch, series.Name, reducedValue.Float64), + Message: fmt.Sprintf("Condition[%d]: Eval: %v, Metric: %s, Value: %s", c.Index, evalMatch, series.Name, reducedValue), }) } @@ -65,11 +66,28 @@ func (c *QueryCondition) Eval(context *alerting.EvalContext) (*alerting.Conditio matches = append(matches, &alerting.EvalMatch{ Metric: series.Name, - Value: reducedValue.Float64, + Value: reducedValue, }) } } + // handle no series special case + if len(seriesList) == 0 { + // eval condition for null value + evalMatch := c.Evaluator.Eval(null.FloatFromPtr(nil)) + + if context.IsTestRun { + context.Logs = append(context.Logs, &alerting.ResultLogEntry{ + Message: fmt.Sprintf("Condition[%d]: Eval: %v, Query Returned No Series (reduced to null/no value)", evalMatch), + }) + } + + if evalMatch { + evalMatchCount++ + matches = append(matches, &alerting.EvalMatch{Metric: "NoData", Value: null.FloatFromPtr(nil)}) + } + } + return &alerting.ConditionResult{ Firing: evalMatchCount > 0, NoDataFound: emptySerieCount == len(seriesList), diff --git a/pkg/services/alerting/conditions/query_test.go b/pkg/services/alerting/conditions/query_test.go index c3797beaf37..17b47b2832b 100644 --- a/pkg/services/alerting/conditions/query_test.go +++ b/pkg/services/alerting/conditions/query_test.go @@ -4,9 +4,8 @@ import ( "context" "testing" - null "gopkg.in/guregu/null.v3" - "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" @@ -72,7 +71,38 @@ func TestQueryCondition(t *testing.T) { So(cr.Firing, ShouldBeTrue) }) + Convey("No series", func() { + Convey("Should set NoDataFound when condition is gt", func() { + ctx.series = tsdb.TimeSeriesSlice{} + cr, err := ctx.exec() + + So(err, ShouldBeNil) + So(cr.Firing, ShouldBeFalse) + So(cr.NoDataFound, ShouldBeTrue) + }) + + Convey("Should be firing when condition is no_value", func() { + ctx.evaluator = `{"type": "no_value", "params": []}` + ctx.series = tsdb.TimeSeriesSlice{} + cr, err := ctx.exec() + + So(err, ShouldBeNil) + So(cr.Firing, ShouldBeTrue) + }) + }) + Convey("Empty series", func() { + Convey("Should set Firing if eval match", func() { + ctx.evaluator = `{"type": "no_value", "params": []}` + ctx.series = tsdb.TimeSeriesSlice{ + tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()), + } + cr, err := ctx.exec() + + So(err, ShouldBeNil) + So(cr.Firing, ShouldBeTrue) + }) + Convey("Should set NoDataFound both series are empty", func() { ctx.series = tsdb.TimeSeriesSlice{ tsdb.NewTimeSeries("test1", tsdb.NewTimeSeriesPointsFromArgs()), diff --git a/pkg/services/alerting/conditions/reducer.go b/pkg/services/alerting/conditions/reducer.go index b7816a61d1d..0f396e2ffc3 100644 --- a/pkg/services/alerting/conditions/reducer.go +++ b/pkg/services/alerting/conditions/reducer.go @@ -5,8 +5,8 @@ import ( "sort" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/tsdb" - "gopkg.in/guregu/null.v3" ) type QueryReducer interface { diff --git a/pkg/services/alerting/conditions/reducer_test.go b/pkg/services/alerting/conditions/reducer_test.go index 7d6cee466fc..ff6105a06ec 100644 --- a/pkg/services/alerting/conditions/reducer_test.go +++ b/pkg/services/alerting/conditions/reducer_test.go @@ -3,10 +3,10 @@ package conditions import ( "testing" - "gopkg.in/guregu/null.v3" - - "github.com/grafana/grafana/pkg/tsdb" . "github.com/smartystreets/goconvey/convey" + + "github.com/grafana/grafana/pkg/components/null" + "github.com/grafana/grafana/pkg/tsdb" ) func TestSimpleReducer(t *testing.T) { @@ -57,6 +57,16 @@ func TestSimpleReducer(t *testing.T) { So(result, ShouldEqual, float64(2)) }) + Convey("avg with only nulls", func() { + reducer := NewSimpleReducer("avg") + series := &tsdb.TimeSeries{ + Name: "test time serie", + } + + series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), 1)) + So(reducer.Reduce(series).Valid, ShouldEqual, false) + }) + Convey("avg of number values and null values should ignore nulls", func() { reducer := NewSimpleReducer("avg") series := &tsdb.TimeSeries{ diff --git a/pkg/services/alerting/eval_handler.go b/pkg/services/alerting/eval_handler.go index 867226738b8..4958cab097b 100644 --- a/pkg/services/alerting/eval_handler.go +++ b/pkg/services/alerting/eval_handler.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/metrics" + "github.com/grafana/grafana/pkg/models" ) type DefaultEvalHandler struct { @@ -60,6 +61,40 @@ func (e *DefaultEvalHandler) Eval(context *EvalContext) { context.Firing = firing context.NoDataFound = noDataFound context.EndTime = time.Now() + context.Rule.State = e.getNewState(context) + elapsedTime := context.EndTime.Sub(context.StartTime) / time.Millisecond metrics.M_Alerting_Execution_Time.Update(elapsedTime) } + +// This should be move into evalContext once its been refactored. +func (handler *DefaultEvalHandler) getNewState(evalContext *EvalContext) models.AlertStateType { + if evalContext.Error != nil { + handler.log.Error("Alert Rule Result Error", + "ruleId", evalContext.Rule.Id, + "name", evalContext.Rule.Name, + "error", evalContext.Error, + "changing state to", evalContext.Rule.ExecutionErrorState.ToAlertState()) + + if evalContext.Rule.ExecutionErrorState == models.ExecutionErrorKeepState { + return evalContext.PrevAlertState + } else { + return evalContext.Rule.ExecutionErrorState.ToAlertState() + } + } else if evalContext.Firing { + return models.AlertStateAlerting + } else if evalContext.NoDataFound { + handler.log.Info("Alert Rule returned no data", + "ruleId", evalContext.Rule.Id, + "name", evalContext.Rule.Name, + "changing state to", evalContext.Rule.NoDataState.ToAlertState()) + + if evalContext.Rule.NoDataState == models.NoDataKeepState { + return evalContext.PrevAlertState + } else { + return evalContext.Rule.NoDataState.ToAlertState() + } + } + + return models.AlertStateOK +} diff --git a/pkg/services/alerting/eval_handler_test.go b/pkg/services/alerting/eval_handler_test.go index 73d675e3fa7..82cc5d43baf 100644 --- a/pkg/services/alerting/eval_handler_test.go +++ b/pkg/services/alerting/eval_handler_test.go @@ -2,8 +2,10 @@ package alerting import ( "context" + "fmt" "testing" + "github.com/grafana/grafana/pkg/models" . "github.com/smartystreets/goconvey/convey" ) @@ -18,8 +20,8 @@ func (c *conditionStub) Eval(context *EvalContext) (*ConditionResult, error) { return &ConditionResult{Firing: c.firing, EvalMatches: c.matches, Operator: c.operator, NoDataFound: c.noData}, nil } -func TestAlertingExecutor(t *testing.T) { - Convey("Test alert execution", t, func() { +func TestAlertingEvaluationHandler(t *testing.T) { + Convey("Test alert evaluation handler", t, func() { handler := NewEvalHandler() Convey("Show return triggered with single passing condition", func() { @@ -164,5 +166,73 @@ func TestAlertingExecutor(t *testing.T) { handler.Eval(context) So(context.NoDataFound, ShouldBeTrue) }) + + Convey("EvalHandler can replace alert state based for errors and no_data", func() { + ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) + dummieError := fmt.Errorf("dummie error") + Convey("Should update alert state", func() { + + Convey("ok -> alerting", func() { + ctx.PrevAlertState = models.AlertStateOK + ctx.Firing = true + + So(handler.getNewState(ctx), ShouldEqual, models.AlertStateAlerting) + }) + + Convey("ok -> error(alerting)", func() { + ctx.PrevAlertState = models.AlertStateOK + ctx.Error = dummieError + ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting + + ctx.Rule.State = handler.getNewState(ctx) + So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) + }) + + Convey("ok -> error(keep_last)", func() { + ctx.PrevAlertState = models.AlertStateOK + ctx.Error = dummieError + ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState + + ctx.Rule.State = handler.getNewState(ctx) + So(ctx.Rule.State, ShouldEqual, models.AlertStateOK) + }) + + Convey("pending -> error(keep_last)", func() { + ctx.PrevAlertState = models.AlertStatePending + ctx.Error = dummieError + ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState + + ctx.Rule.State = handler.getNewState(ctx) + So(ctx.Rule.State, ShouldEqual, models.AlertStatePending) + }) + + Convey("ok -> no_data(alerting)", func() { + ctx.PrevAlertState = models.AlertStateOK + ctx.Rule.NoDataState = models.NoDataSetAlerting + ctx.NoDataFound = true + + ctx.Rule.State = handler.getNewState(ctx) + So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) + }) + + Convey("ok -> no_data(keep_last)", func() { + ctx.PrevAlertState = models.AlertStateOK + ctx.Rule.NoDataState = models.NoDataKeepState + ctx.NoDataFound = true + + ctx.Rule.State = handler.getNewState(ctx) + So(ctx.Rule.State, ShouldEqual, models.AlertStateOK) + }) + + Convey("pending -> no_data(keep_last)", func() { + ctx.PrevAlertState = models.AlertStatePending + ctx.Rule.NoDataState = models.NoDataKeepState + ctx.NoDataFound = true + + ctx.Rule.State = handler.getNewState(ctx) + So(ctx.Rule.State, ShouldEqual, models.AlertStatePending) + }) + }) + }) }) } diff --git a/pkg/services/alerting/extractor.go b/pkg/services/alerting/extractor.go index 6e064c8cf76..28a6b8c61a0 100644 --- a/pkg/services/alerting/extractor.go +++ b/pkg/services/alerting/extractor.go @@ -60,12 +60,25 @@ func findPanelQueryByRefId(panel *simplejson.Json, refId string) *simplejson.Jso return nil } +func copyJson(in *simplejson.Json) (*simplejson.Json, error) { + rawJson, err := in.MarshalJSON() + if err != nil { + return nil, err + } + + return simplejson.NewJson(rawJson) +} + func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) { e.log.Debug("GetAlerts") - alerts := make([]*m.Alert, 0) + dashboardJson, err := copyJson(e.Dash.Data) + if err != nil { + return nil, err + } - for _, rowObj := range e.Dash.Data.Get("rows").MustArray() { + alerts := make([]*m.Alert, 0) + for _, rowObj := range dashboardJson.Get("rows").MustArray() { row := simplejson.NewFromAny(rowObj) for _, panelObj := range row.Get("panels").MustArray() { diff --git a/pkg/services/alerting/extractor_test.go b/pkg/services/alerting/extractor_test.go index e72e6d938d8..cb174783478 100644 --- a/pkg/services/alerting/extractor_test.go +++ b/pkg/services/alerting/extractor_test.go @@ -110,6 +110,34 @@ func TestAlertRuleExtraction(t *testing.T) { ] }` + Convey("Extractor should not modify the original json", func() { + dashJson, err := simplejson.NewJson([]byte(json)) + So(err, ShouldBeNil) + + dash := m.NewDashboardFromJson(dashJson) + + getTarget := func(j *simplejson.Json) string { + rowObj := j.Get("rows").MustArray()[0] + row := simplejson.NewFromAny(rowObj) + panelObj := row.Get("panels").MustArray()[0] + panel := simplejson.NewFromAny(panelObj) + conditionObj := panel.Get("alert").Get("conditions").MustArray()[0] + condition := simplejson.NewFromAny(conditionObj) + return condition.Get("query").Get("model").Get("target").MustString() + } + + Convey("Dashboard json rows.panels.alert.query.model.target should be empty", func() { + So(getTarget(dashJson), ShouldEqual, "") + }) + + extractor := NewDashAlertExtractor(dash, 1) + _, _ = extractor.GetAlerts() + + Convey("Dashboard json should not be updated after extracting rules", func() { + So(getTarget(dashJson), ShouldEqual, "") + }) + }) + Convey("Parsing and validating dashboard containing graphite alerts", func() { dashJson, err := simplejson.NewJson([]byte(json)) diff --git a/pkg/services/alerting/models.go b/pkg/services/alerting/models.go index 140c545fb2f..bbd8b98eea2 100644 --- a/pkg/services/alerting/models.go +++ b/pkg/services/alerting/models.go @@ -1,5 +1,7 @@ package alerting +import "github.com/grafana/grafana/pkg/components/null" + type Job struct { Offset int64 OffsetWait bool @@ -14,7 +16,7 @@ type ResultLogEntry struct { } type EvalMatch struct { - Value float64 `json:"value"` + Value null.Float `json:"value"` Metric string `json:"metric"` Tags map[string]string `json:"tags"` } diff --git a/pkg/services/alerting/result_handler.go b/pkg/services/alerting/result_handler.go index aab109088cb..8a78ca2a749 100644 --- a/pkg/services/alerting/result_handler.go +++ b/pkg/services/alerting/result_handler.go @@ -27,43 +27,10 @@ func NewResultHandler() *DefaultResultHandler { } } -func (handler *DefaultResultHandler) GetStateFromEvaluation(evalContext *EvalContext) m.AlertStateType { - if evalContext.Error != nil { - handler.log.Error("Alert Rule Result Error", - "ruleId", evalContext.Rule.Id, - "name", evalContext.Rule.Name, - "error", evalContext.Error, - "changing state to", evalContext.Rule.ExecutionErrorState.ToAlertState()) - - if evalContext.Rule.ExecutionErrorState == m.ExecutionErrorKeepState { - return evalContext.PrevAlertState - } else { - return evalContext.Rule.ExecutionErrorState.ToAlertState() - } - } else if evalContext.Firing { - return m.AlertStateAlerting - } else if evalContext.NoDataFound { - handler.log.Info("Alert Rule returned no data", - "ruleId", evalContext.Rule.Id, - "name", evalContext.Rule.Name, - "changing state to", evalContext.Rule.NoDataState.ToAlertState()) - - if evalContext.Rule.NoDataState == m.NoDataKeepState { - return evalContext.PrevAlertState - } else { - return evalContext.Rule.NoDataState.ToAlertState() - } - } - - return m.AlertStateOK -} - func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { executionError := "" annotationData := simplejson.New() - evalContext.Rule.State = handler.GetStateFromEvaluation(evalContext) - if evalContext.Error != nil { executionError = evalContext.Error.Error() annotationData.Set("errorMessage", executionError) diff --git a/pkg/services/alerting/result_handler_test.go b/pkg/services/alerting/result_handler_test.go deleted file mode 100644 index 321838d4f4c..00000000000 --- a/pkg/services/alerting/result_handler_test.go +++ /dev/null @@ -1,90 +0,0 @@ -package alerting - -import ( - "context" - "testing" - - "fmt" - - "github.com/grafana/grafana/pkg/models" - . "github.com/smartystreets/goconvey/convey" -) - -func TestAlertingResultHandler(t *testing.T) { - Convey("Result handler", t, func() { - ctx := NewEvalContext(context.TODO(), &Rule{Conditions: []Condition{&conditionStub{firing: true}}}) - dummieError := fmt.Errorf("dummie") - handler := NewResultHandler() - - Convey("Should update alert state", func() { - - Convey("ok -> alerting", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Firing = true - - So(handler.GetStateFromEvaluation(ctx), ShouldEqual, models.AlertStateAlerting) - So(ctx.ShouldUpdateAlertState(), ShouldBeTrue) - }) - - Convey("ok -> error(alerting)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Error = dummieError - ctx.Rule.ExecutionErrorState = models.ExecutionErrorSetAlerting - - ctx.Rule.State = handler.GetStateFromEvaluation(ctx) - So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) - So(ctx.ShouldUpdateAlertState(), ShouldBeTrue) - }) - - Convey("ok -> error(keep_last)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Error = dummieError - ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState - - ctx.Rule.State = handler.GetStateFromEvaluation(ctx) - So(ctx.Rule.State, ShouldEqual, models.AlertStateOK) - So(ctx.ShouldUpdateAlertState(), ShouldBeFalse) - }) - - Convey("pending -> error(keep_last)", func() { - ctx.PrevAlertState = models.AlertStatePending - ctx.Error = dummieError - ctx.Rule.ExecutionErrorState = models.ExecutionErrorKeepState - - ctx.Rule.State = handler.GetStateFromEvaluation(ctx) - So(ctx.Rule.State, ShouldEqual, models.AlertStatePending) - So(ctx.ShouldUpdateAlertState(), ShouldBeFalse) - }) - - Convey("ok -> no_data(alerting)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Rule.NoDataState = models.NoDataSetAlerting - ctx.NoDataFound = true - - ctx.Rule.State = handler.GetStateFromEvaluation(ctx) - So(ctx.Rule.State, ShouldEqual, models.AlertStateAlerting) - So(ctx.ShouldUpdateAlertState(), ShouldBeTrue) - }) - - Convey("ok -> no_data(keep_last)", func() { - ctx.PrevAlertState = models.AlertStateOK - ctx.Rule.NoDataState = models.NoDataKeepState - ctx.NoDataFound = true - - ctx.Rule.State = handler.GetStateFromEvaluation(ctx) - So(ctx.Rule.State, ShouldEqual, models.AlertStateOK) - So(ctx.ShouldUpdateAlertState(), ShouldBeFalse) - }) - - Convey("pending -> no_data(keep_last)", func() { - ctx.PrevAlertState = models.AlertStatePending - ctx.Rule.NoDataState = models.NoDataKeepState - ctx.NoDataFound = true - - ctx.Rule.State = handler.GetStateFromEvaluation(ctx) - So(ctx.Rule.State, ShouldEqual, models.AlertStatePending) - So(ctx.ShouldUpdateAlertState(), ShouldBeFalse) - }) - }) - }) -} diff --git a/pkg/services/alerting/test_notification.go b/pkg/services/alerting/test_notification.go index fd908d6f95d..a40d0ac3c5c 100644 --- a/pkg/services/alerting/test_notification.go +++ b/pkg/services/alerting/test_notification.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" @@ -63,12 +64,12 @@ func evalMatchesBasedOnState() []*EvalMatch { matches := make([]*EvalMatch, 0) matches = append(matches, &EvalMatch{ Metric: "High value", - Value: 100, + Value: null.FloatFrom(100), }) matches = append(matches, &EvalMatch{ Metric: "Higher Value", - Value: 200, + Value: null.FloatFrom(200), }) return matches diff --git a/pkg/tsdb/influxdb/response_parser.go b/pkg/tsdb/influxdb/response_parser.go index 8e9fa0022c3..b7db6182241 100644 --- a/pkg/tsdb/influxdb/response_parser.go +++ b/pkg/tsdb/influxdb/response_parser.go @@ -7,8 +7,8 @@ import ( "strconv" "strings" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/tsdb" - "gopkg.in/guregu/null.v3" ) type ResponseParser struct{} diff --git a/pkg/tsdb/models.go b/pkg/tsdb/models.go index e95713e7077..68f26a68d50 100644 --- a/pkg/tsdb/models.go +++ b/pkg/tsdb/models.go @@ -1,9 +1,9 @@ package tsdb import ( + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models" - "gopkg.in/guregu/null.v3" ) type Query struct { diff --git a/pkg/tsdb/mqe/response_parser.go b/pkg/tsdb/mqe/response_parser.go index 283098e74f3..d40106fc197 100644 --- a/pkg/tsdb/mqe/response_parser.go +++ b/pkg/tsdb/mqe/response_parser.go @@ -5,10 +5,9 @@ import ( "io/ioutil" "net/http" - null "gopkg.in/guregu/null.v3" - "fmt" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/tsdb" ) diff --git a/pkg/tsdb/opentsdb/opentsdb.go b/pkg/tsdb/opentsdb/opentsdb.go index f5b6ffda7d2..684f48bfc59 100644 --- a/pkg/tsdb/opentsdb/opentsdb.go +++ b/pkg/tsdb/opentsdb/opentsdb.go @@ -14,8 +14,7 @@ import ( "net/http" "net/url" - "gopkg.in/guregu/null.v3" - + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" diff --git a/pkg/tsdb/prometheus/prometheus.go b/pkg/tsdb/prometheus/prometheus.go index c391f00920a..f3704c2a430 100644 --- a/pkg/tsdb/prometheus/prometheus.go +++ b/pkg/tsdb/prometheus/prometheus.go @@ -7,10 +7,9 @@ import ( "strings" "time" - "gopkg.in/guregu/null.v3" - "net/http" + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/tsdb" diff --git a/pkg/tsdb/testdata/scenarios.go b/pkg/tsdb/testdata/scenarios.go index 667b7fffcba..0c59973cd7b 100644 --- a/pkg/tsdb/testdata/scenarios.go +++ b/pkg/tsdb/testdata/scenarios.go @@ -6,8 +6,7 @@ import ( "strings" "time" - "gopkg.in/guregu/null.v3" - + "github.com/grafana/grafana/pkg/components/null" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/tsdb" ) diff --git a/public/app/core/components/jsontree/jsontree.ts b/public/app/core/components/jsontree/jsontree.ts index 14f6012ce12..5c39e2ab53c 100644 --- a/public/app/core/components/jsontree/jsontree.ts +++ b/public/app/core/components/jsontree/jsontree.ts @@ -117,6 +117,7 @@ coreModule.directive('jsonNode', ['ajsRecursiveDirectiveHelper', function jsonNo var isArray = utils.is(scope.value, 'Array'); scope.preview = isArray ? '[ ' : '{ '; utils.forKeys(scope.value, function jsonNodeDirectiveLinkForKeys(key, value) { + if (value === null) { scope.value[key] = 'null'; } if (isArray) { scope.preview += value + ', '; } else { diff --git a/public/app/features/org/orgApiKeysCtrl.js b/public/app/features/org/orgApiKeysCtrl.js index 7d2bc4be1c6..d0b08b57d32 100644 --- a/public/app/features/org/orgApiKeysCtrl.js +++ b/public/app/features/org/orgApiKeysCtrl.js @@ -30,6 +30,7 @@ function (angular) { var modalScope = $scope.$new(true); modalScope.key = result.key; + modalScope.rootPath = window.location.origin + $scope.$root.appSubUrl; $scope.appEvent('show-modal', { src: 'public/app/features/org/partials/apikeyModal.html', diff --git a/public/app/features/org/partials/apikeyModal.html b/public/app/features/org/partials/apikeyModal.html index a4f59e1b86e..eeefcafc634 100644 --- a/public/app/features/org/partials/apikeyModal.html +++ b/public/app/features/org/partials/apikeyModal.html @@ -27,7 +27,7 @@

-curl -H "Authorization: Bearer your_key_above" http://your.grafana.com/api/dashboards/db/mydash
+curl -H "Authorization: Bearer {{key}}" {{rootPath}}/api/dashboards/home
 			
diff --git a/vendor/gopkg.in/guregu/null.v3/LICENSE b/vendor/gopkg.in/guregu/null.v3/LICENSE deleted file mode 100644 index 69062b45b16..00000000000 --- a/vendor/gopkg.in/guregu/null.v3/LICENSE +++ /dev/null @@ -1,10 +0,0 @@ -Copyright (c) 2014, Greg Roseberry -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/vendor/gopkg.in/guregu/null.v3/README.md b/vendor/gopkg.in/guregu/null.v3/README.md deleted file mode 100644 index 62bf48be93e..00000000000 --- a/vendor/gopkg.in/guregu/null.v3/README.md +++ /dev/null @@ -1,75 +0,0 @@ -## null [![GoDoc](https://godoc.org/github.com/guregu/null?status.svg)](https://godoc.org/github.com/guregu/null) [![Coverage](http://gocover.io/_badge/github.com/guregu/null)](http://gocover.io/github.com/guregu/null) -`import "gopkg.in/guregu/null.v3"` - -null is a library with reasonable options for dealing with nullable SQL and JSON values - -There are two packages: `null` and its subpackage `zero`. - -Types in `null` will only be considered null on null input, and will JSON encode to `null`. If you need zero and null be considered separate values, use these. - -Types in `zero` are treated like zero values in Go: blank string input will produce a null `zero.String`, and null Strings will JSON encode to `""`. Zero values of these types will be considered null to SQL. If you need zero and null treated the same, use these. - -All types implement `sql.Scanner` and `driver.Valuer`, so you can use this library in place of `sql.NullXXX`. All types also implement: `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`. - -### null package - -`import "gopkg.in/guregu/null.v3"` - -#### null.String -Nullable string. - -Marshals to JSON null if SQL source data is null. Zero (blank) input will not produce a null String. Can unmarshal from `sql.NullString` JSON input or string input. - -#### null.Int -Nullable int64. - -Marshals to JSON null if SQL source data is null. Zero input will not produce a null Int. Can unmarshal from `sql.NullInt64` JSON input. - -#### null.Float -Nullable float64. - -Marshals to JSON null if SQL source data is null. Zero input will not produce a null Float. Can unmarshal from `sql.NullFloat64` JSON input. - -#### null.Bool -Nullable bool. - -Marshals to JSON null if SQL source data is null. False input will not produce a null Bool. Can unmarshal from `sql.NullBool` JSON input. - -#### null.Time - -Marshals to JSON null if SQL source data is null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input. - -### zero package - -`import "gopkg.in/guregu/null.v3/zero"` - -#### zero.String -Nullable string. - -Will marshal to a blank string if null. Blank string input produces a null String. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullString` JSON input. - -#### zero.Int -Nullable int64. - -Will marshal to 0 if null. 0 produces a null Int. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullInt64` JSON input. - -#### zero.Float -Nullable float64. - -Will marshal to 0 if null. 0.0 produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullFloat64` JSON input. - -#### zero.Bool -Nullable bool. - -Will marshal to false if null. `false` produces a null Float. Null values and zero values are considered equivalent. Can unmarshal from `sql.NullBool` JSON input. - -#### zero.Time - -Will marshal to the zero time if null. Uses `time.Time`'s marshaler. Can unmarshal from `pq.NullTime` and similar JSON input. - - -### Bugs -`json`'s `",omitempty"` struct tag does not work correctly right now. It will never omit a null or empty String. This might be [fixed eventually](https://github.com/golang/go/issues/4357). - -### License -BSD diff --git a/vendor/gopkg.in/guregu/null.v3/bool.go b/vendor/gopkg.in/guregu/null.v3/bool.go deleted file mode 100644 index 1ec13961bfe..00000000000 --- a/vendor/gopkg.in/guregu/null.v3/bool.go +++ /dev/null @@ -1,129 +0,0 @@ -package null - -import ( - "database/sql" - "encoding/json" - "errors" - "fmt" - "reflect" -) - -// Bool is a nullable bool. -// It does not consider false values to be null. -// It will decode to null, not false, if null. -type Bool struct { - sql.NullBool -} - -// NewBool creates a new Bool -func NewBool(b bool, valid bool) Bool { - return Bool{ - NullBool: sql.NullBool{ - Bool: b, - Valid: valid, - }, - } -} - -// BoolFrom creates a new Bool that will always be valid. -func BoolFrom(b bool) Bool { - return NewBool(b, true) -} - -// BoolFromPtr creates a new Bool that will be null if f is nil. -func BoolFromPtr(b *bool) Bool { - if b == nil { - return NewBool(false, false) - } - return NewBool(*b, true) -} - -// UnmarshalJSON implements json.Unmarshaler. -// It supports number and null input. -// 0 will not be considered a null Bool. -// It also supports unmarshalling a sql.NullBool. -func (b *Bool) UnmarshalJSON(data []byte) error { - var err error - var v interface{} - if err = json.Unmarshal(data, &v); err != nil { - return err - } - switch x := v.(type) { - case bool: - b.Bool = x - case map[string]interface{}: - err = json.Unmarshal(data, &b.NullBool) - case nil: - b.Valid = false - return nil - default: - err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Bool", reflect.TypeOf(v).Name()) - } - b.Valid = err == nil - return err -} - -// UnmarshalText implements encoding.TextUnmarshaler. -// It will unmarshal to a null Bool if the input is a blank or not an integer. -// It will return an error if the input is not an integer, blank, or "null". -func (b *Bool) UnmarshalText(text []byte) error { - str := string(text) - switch str { - case "", "null": - b.Valid = false - return nil - case "true": - b.Bool = true - case "false": - b.Bool = false - default: - b.Valid = false - return errors.New("invalid input:" + str) - } - b.Valid = true - return nil -} - -// MarshalJSON implements json.Marshaler. -// It will encode null if this Bool is null. -func (b Bool) MarshalJSON() ([]byte, error) { - if !b.Valid { - return []byte("null"), nil - } - if !b.Bool { - return []byte("false"), nil - } - return []byte("true"), nil -} - -// MarshalText implements encoding.TextMarshaler. -// It will encode a blank string if this Bool is null. -func (b Bool) MarshalText() ([]byte, error) { - if !b.Valid { - return []byte{}, nil - } - if !b.Bool { - return []byte("false"), nil - } - return []byte("true"), nil -} - -// SetValid changes this Bool's value and also sets it to be non-null. -func (b *Bool) SetValid(v bool) { - b.Bool = v - b.Valid = true -} - -// Ptr returns a pointer to this Bool's value, or a nil pointer if this Bool is null. -func (b Bool) Ptr() *bool { - if !b.Valid { - return nil - } - return &b.Bool -} - -// IsZero returns true for invalid Bools, for future omitempty support (Go 1.4?) -// A non-null Bool with a 0 value will not be considered zero. -func (b Bool) IsZero() bool { - return !b.Valid -} diff --git a/vendor/gopkg.in/guregu/null.v3/int.go b/vendor/gopkg.in/guregu/null.v3/int.go deleted file mode 100644 index 981d17b09aa..00000000000 --- a/vendor/gopkg.in/guregu/null.v3/int.go +++ /dev/null @@ -1,118 +0,0 @@ -package null - -import ( - "database/sql" - "encoding/json" - "fmt" - "reflect" - "strconv" -) - -// Int is an nullable int64. -// It does not consider zero values to be null. -// It will decode to null, not zero, if null. -type Int struct { - sql.NullInt64 -} - -// NewInt creates a new Int -func NewInt(i int64, valid bool) Int { - return Int{ - NullInt64: sql.NullInt64{ - Int64: i, - Valid: valid, - }, - } -} - -// IntFrom creates a new Int that will always be valid. -func IntFrom(i int64) Int { - return NewInt(i, true) -} - -// IntFromPtr creates a new Int that be null if i is nil. -func IntFromPtr(i *int64) Int { - if i == nil { - return NewInt(0, false) - } - return NewInt(*i, true) -} - -// UnmarshalJSON implements json.Unmarshaler. -// It supports number and null input. -// 0 will not be considered a null Int. -// It also supports unmarshalling a sql.NullInt64. -func (i *Int) UnmarshalJSON(data []byte) error { - var err error - var v interface{} - if err = json.Unmarshal(data, &v); err != nil { - return err - } - switch v.(type) { - case float64: - // Unmarshal again, directly to int64, to avoid intermediate float64 - err = json.Unmarshal(data, &i.Int64) - case map[string]interface{}: - err = json.Unmarshal(data, &i.NullInt64) - case nil: - i.Valid = false - return nil - default: - err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Int", reflect.TypeOf(v).Name()) - } - i.Valid = err == nil - return err -} - -// UnmarshalText implements encoding.TextUnmarshaler. -// It will unmarshal to a null Int if the input is a blank or not an integer. -// It will return an error if the input is not an integer, blank, or "null". -func (i *Int) UnmarshalText(text []byte) error { - str := string(text) - if str == "" || str == "null" { - i.Valid = false - return nil - } - var err error - i.Int64, err = strconv.ParseInt(string(text), 10, 64) - i.Valid = err == nil - return err -} - -// MarshalJSON implements json.Marshaler. -// It will encode null if this Int is null. -func (i Int) MarshalJSON() ([]byte, error) { - if !i.Valid { - return []byte("null"), nil - } - return []byte(strconv.FormatInt(i.Int64, 10)), nil -} - -// MarshalText implements encoding.TextMarshaler. -// It will encode a blank string if this Int is null. -func (i Int) MarshalText() ([]byte, error) { - if !i.Valid { - return []byte{}, nil - } - return []byte(strconv.FormatInt(i.Int64, 10)), nil -} - -// SetValid changes this Int's value and also sets it to be non-null. -func (i *Int) SetValid(n int64) { - i.Int64 = n - i.Valid = true -} - -// Ptr returns a pointer to this Int's value, or a nil pointer if this Int is null. -func (i Int) Ptr() *int64 { - if !i.Valid { - return nil - } - return &i.Int64 -} - -// IsZero returns true for invalid Ints, for future omitempty support (Go 1.4?) -// A non-null Int with a 0 value will not be considered zero. -func (i Int) IsZero() bool { - return !i.Valid -} diff --git a/vendor/gopkg.in/guregu/null.v3/string.go b/vendor/gopkg.in/guregu/null.v3/string.go deleted file mode 100644 index 554aac820e3..00000000000 --- a/vendor/gopkg.in/guregu/null.v3/string.go +++ /dev/null @@ -1,110 +0,0 @@ -// Package null contains SQL types that consider zero input and null input as separate values, -// with convenient support for JSON and text marshaling. -// Types in this package will always encode to their null value if null. -// Use the zero subpackage if you want zero values and null to be treated the same. -package null - -import ( - "database/sql" - "encoding/json" - "fmt" - "reflect" -) - -// String is a nullable string. It supports SQL and JSON serialization. -// It will marshal to null if null. Blank string input will be considered null. -type String struct { - sql.NullString -} - -// StringFrom creates a new String that will never be blank. -func StringFrom(s string) String { - return NewString(s, true) -} - -// StringFromPtr creates a new String that be null if s is nil. -func StringFromPtr(s *string) String { - if s == nil { - return NewString("", false) - } - return NewString(*s, true) -} - -// NewString creates a new String -func NewString(s string, valid bool) String { - return String{ - NullString: sql.NullString{ - String: s, - Valid: valid, - }, - } -} - -// UnmarshalJSON implements json.Unmarshaler. -// It supports string and null input. Blank string input does not produce a null String. -// It also supports unmarshalling a sql.NullString. -func (s *String) UnmarshalJSON(data []byte) error { - var err error - var v interface{} - if err = json.Unmarshal(data, &v); err != nil { - return err - } - switch x := v.(type) { - case string: - s.String = x - case map[string]interface{}: - err = json.Unmarshal(data, &s.NullString) - case nil: - s.Valid = false - return nil - default: - err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.String", reflect.TypeOf(v).Name()) - } - s.Valid = err == nil - return err -} - -// MarshalJSON implements json.Marshaler. -// It will encode null if this String is null. -func (s String) MarshalJSON() ([]byte, error) { - if !s.Valid { - return []byte("null"), nil - } - return json.Marshal(s.String) -} - -// MarshalText implements encoding.TextMarshaler. -// It will encode a blank string when this String is null. -func (s String) MarshalText() ([]byte, error) { - if !s.Valid { - return []byte{}, nil - } - return []byte(s.String), nil -} - -// UnmarshalText implements encoding.TextUnmarshaler. -// It will unmarshal to a null String if the input is a blank string. -func (s *String) UnmarshalText(text []byte) error { - s.String = string(text) - s.Valid = s.String != "" - return nil -} - -// SetValid changes this String's value and also sets it to be non-null. -func (s *String) SetValid(v string) { - s.String = v - s.Valid = true -} - -// Ptr returns a pointer to this String's value, or a nil pointer if this String is null. -func (s String) Ptr() *string { - if !s.Valid { - return nil - } - return &s.String -} - -// IsZero returns true for null strings, for potential future omitempty support. -func (s String) IsZero() bool { - return !s.Valid -} diff --git a/vendor/gopkg.in/guregu/null.v3/time.go b/vendor/gopkg.in/guregu/null.v3/time.go deleted file mode 100644 index a4d843920b4..00000000000 --- a/vendor/gopkg.in/guregu/null.v3/time.go +++ /dev/null @@ -1,135 +0,0 @@ -package null - -import ( - "database/sql/driver" - "encoding/json" - "fmt" - "reflect" - "time" -) - -// Time is a nullable time.Time. It supports SQL and JSON serialization. -// It will marshal to null if null. -type Time struct { - Time time.Time - Valid bool -} - -// Scan implements the Scanner interface. -func (t *Time) Scan(value interface{}) error { - var err error - switch x := value.(type) { - case time.Time: - t.Time = x - case nil: - t.Valid = false - return nil - default: - err = fmt.Errorf("null: cannot scan type %T into null.Time: %v", value, value) - } - t.Valid = err == nil - return err -} - -// Value implements the driver Valuer interface. -func (t Time) Value() (driver.Value, error) { - if !t.Valid { - return nil, nil - } - return t.Time, nil -} - -// NewTime creates a new Time. -func NewTime(t time.Time, valid bool) Time { - return Time{ - Time: t, - Valid: valid, - } -} - -// TimeFrom creates a new Time that will always be valid. -func TimeFrom(t time.Time) Time { - return NewTime(t, true) -} - -// TimeFromPtr creates a new Time that will be null if t is nil. -func TimeFromPtr(t *time.Time) Time { - if t == nil { - return NewTime(time.Time{}, false) - } - return NewTime(*t, true) -} - -// MarshalJSON implements json.Marshaler. -// It will encode null if this time is null. -func (t Time) MarshalJSON() ([]byte, error) { - if !t.Valid { - return []byte("null"), nil - } - return t.Time.MarshalJSON() -} - -// UnmarshalJSON implements json.Unmarshaler. -// It supports string, object (e.g. pq.NullTime and friends) -// and null input. -func (t *Time) UnmarshalJSON(data []byte) error { - var err error - var v interface{} - if err = json.Unmarshal(data, &v); err != nil { - return err - } - switch x := v.(type) { - case string: - err = t.Time.UnmarshalJSON(data) - case map[string]interface{}: - ti, tiOK := x["Time"].(string) - valid, validOK := x["Valid"].(bool) - if !tiOK || !validOK { - return fmt.Errorf(`json: unmarshalling object into Go value of type null.Time requires key "Time" to be of type string and key "Valid" to be of type bool; found %T and %T, respectively`, x["Time"], x["Valid"]) - } - err = t.Time.UnmarshalText([]byte(ti)) - t.Valid = valid - return err - case nil: - t.Valid = false - return nil - default: - err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Time", reflect.TypeOf(v).Name()) - } - t.Valid = err == nil - return err -} - -func (t Time) MarshalText() ([]byte, error) { - if !t.Valid { - return []byte("null"), nil - } - return t.Time.MarshalText() -} - -func (t *Time) UnmarshalText(text []byte) error { - str := string(text) - if str == "" || str == "null" { - t.Valid = false - return nil - } - if err := t.Time.UnmarshalText(text); err != nil { - return err - } - t.Valid = true - return nil -} - -// SetValid changes this Time's value and sets it to be non-null. -func (t *Time) SetValid(v time.Time) { - t.Time = v - t.Valid = true -} - -// Ptr returns a pointer to this Time's value, or a nil pointer if this Time is null. -func (t Time) Ptr() *time.Time { - if !t.Valid { - return nil - } - return &t.Time -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 239b7c5e851..772bd3834f2 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -411,12 +411,6 @@ "path": "gopkg.in/gomail.v2", "revision": "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1", "revisionTime": "2016-04-11T21:29:32Z" - }, - { - "checksumSHA1": "PoHLopxwkiXxa3uVhezeq/qJ/Vo=", - "path": "gopkg.in/guregu/null.v3", - "revision": "41961cea0328defc5f95c1c473f89ebf0d1813f6", - "revisionTime": "2016-02-28T00:53:16Z" } ], "rootPath": "github.com/grafana/grafana"