mirror of https://github.com/grafana/grafana.git
				
				
				
			Merge branch 'annotations-created' of https://github.com/ryantxu/grafana into ryantxu-annotations-created
This commit is contained in:
		
						commit
						3451f9a9db
					
				|  | @ -36,6 +36,8 @@ Query Parameters: | |||
| - `alertId`: number. Optional. Find annotations for a specified alert. | ||||
| - `dashboardId`: number. Optional. Find annotations that are scoped to a specific dashboard | ||||
| - `panelId`: number. Optional. Find annotations that are scoped to a specific panel | ||||
| - `userId`: number. Optional. Find annotations created by a specific user | ||||
| - `type`: string. Optional. `alert`|`annotation` Return alerts or user created annotations | ||||
| - `tags`: string. Optional. Use this to filter global annotations. Global annotations are annotations from an annotation data source that are not connected specifically to a dashboard or panel. To do an "AND" filtering with multiple tags, specify the tags parameter multiple times e.g. `tags=tag1&tags=tag2`. | ||||
| 
 | ||||
| **Example Response**: | ||||
|  |  | |||
|  | @ -2,7 +2,6 @@ package api | |||
| 
 | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/api/dtos" | ||||
| 	"github.com/grafana/grafana/pkg/components/simplejson" | ||||
|  | @ -15,9 +14,10 @@ import ( | |||
| func GetAnnotations(c *m.ReqContext) Response { | ||||
| 
 | ||||
| 	query := &annotations.ItemQuery{ | ||||
| 		From:        c.QueryInt64("from") / 1000, | ||||
| 		To:          c.QueryInt64("to") / 1000, | ||||
| 		From:        c.QueryInt64("from"), | ||||
| 		To:          c.QueryInt64("to"), | ||||
| 		OrgId:       c.OrgId, | ||||
| 		UserId:      c.QueryInt64("userId"), | ||||
| 		AlertId:     c.QueryInt64("alertId"), | ||||
| 		DashboardId: c.QueryInt64("dashboardId"), | ||||
| 		PanelId:     c.QueryInt64("panelId"), | ||||
|  | @ -37,7 +37,7 @@ func GetAnnotations(c *m.ReqContext) Response { | |||
| 		if item.Email != "" { | ||||
| 			item.AvatarUrl = dtos.GetGravatarUrl(item.Email) | ||||
| 		} | ||||
| 		item.Time = item.Time * 1000 | ||||
| 		item.Time = item.Time | ||||
| 	} | ||||
| 
 | ||||
| 	return JSON(200, items) | ||||
|  | @ -68,16 +68,12 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response { | |||
| 		UserId:      c.UserId, | ||||
| 		DashboardId: cmd.DashboardId, | ||||
| 		PanelId:     cmd.PanelId, | ||||
| 		Epoch:       cmd.Time / 1000, | ||||
| 		Epoch:       cmd.Time, | ||||
| 		Text:        cmd.Text, | ||||
| 		Data:        cmd.Data, | ||||
| 		Tags:        cmd.Tags, | ||||
| 	} | ||||
| 
 | ||||
| 	if item.Epoch == 0 { | ||||
| 		item.Epoch = time.Now().Unix() | ||||
| 	} | ||||
| 
 | ||||
| 	if err := repo.Save(&item); err != nil { | ||||
| 		return Error(500, "Failed to save annotation", err) | ||||
| 	} | ||||
|  | @ -97,7 +93,7 @@ func PostAnnotation(c *m.ReqContext, cmd dtos.PostAnnotationsCmd) Response { | |||
| 		} | ||||
| 
 | ||||
| 		item.Id = 0 | ||||
| 		item.Epoch = cmd.TimeEnd / 1000 | ||||
| 		item.Epoch = cmd.TimeEnd | ||||
| 
 | ||||
| 		if err := repo.Save(&item); err != nil { | ||||
| 			return Error(500, "Failed save annotation for region end time", err) | ||||
|  | @ -132,9 +128,6 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd | |||
| 		return Error(500, "Failed to save Graphite annotation", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if cmd.When == 0 { | ||||
| 		cmd.When = time.Now().Unix() | ||||
| 	} | ||||
| 	text := formatGraphiteAnnotation(cmd.What, cmd.Data) | ||||
| 
 | ||||
| 	// Support tags in prior to Graphite 0.10.0 format (string of tags separated by space)
 | ||||
|  | @ -163,7 +156,7 @@ func PostGraphiteAnnotation(c *m.ReqContext, cmd dtos.PostGraphiteAnnotationsCmd | |||
| 	item := annotations.Item{ | ||||
| 		OrgId:  c.OrgId, | ||||
| 		UserId: c.UserId, | ||||
| 		Epoch:  cmd.When, | ||||
| 		Epoch:  cmd.When * 1000, | ||||
| 		Text:   text, | ||||
| 		Tags:   tagsArray, | ||||
| 	} | ||||
|  | @ -191,7 +184,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response { | |||
| 		OrgId:  c.OrgId, | ||||
| 		UserId: c.UserId, | ||||
| 		Id:     annotationID, | ||||
| 		Epoch:  cmd.Time / 1000, | ||||
| 		Epoch:  cmd.Time, | ||||
| 		Text:   cmd.Text, | ||||
| 		Tags:   cmd.Tags, | ||||
| 	} | ||||
|  | @ -203,7 +196,7 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response { | |||
| 	if cmd.IsRegion { | ||||
| 		itemRight := item | ||||
| 		itemRight.RegionId = item.Id | ||||
| 		itemRight.Epoch = cmd.TimeEnd / 1000 | ||||
| 		itemRight.Epoch = cmd.TimeEnd | ||||
| 
 | ||||
| 		// We don't know id of region right event, so set it to 0 and find then using query like
 | ||||
| 		// ... WHERE region_id = <item.RegionId> AND id != <item.RegionId> ...
 | ||||
|  |  | |||
|  | @ -77,7 +77,7 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error { | |||
| 			Text:        "", | ||||
| 			NewState:    string(evalContext.Rule.State), | ||||
| 			PrevState:   string(evalContext.PrevAlertState), | ||||
| 			Epoch:       time.Now().Unix(), | ||||
| 			Epoch:       time.Now().UnixNano() / int64(time.Millisecond), | ||||
| 			Data:        annotationData, | ||||
| 		} | ||||
| 
 | ||||
|  |  | |||
|  | @ -13,6 +13,7 @@ type ItemQuery struct { | |||
| 	OrgId        int64    `json:"orgId"` | ||||
| 	From         int64    `json:"from"` | ||||
| 	To           int64    `json:"to"` | ||||
| 	UserId       int64    `json:"userId"` | ||||
| 	AlertId      int64    `json:"alertId"` | ||||
| 	DashboardId  int64    `json:"dashboardId"` | ||||
| 	PanelId      int64    `json:"panelId"` | ||||
|  | @ -63,6 +64,8 @@ type Item struct { | |||
| 	PrevState   string           `json:"prevState"` | ||||
| 	NewState    string           `json:"newState"` | ||||
| 	Epoch       int64            `json:"epoch"` | ||||
| 	Created     int64            `json:"created"` | ||||
| 	Updated     int64            `json:"updated"` | ||||
| 	Tags        []string         `json:"tags"` | ||||
| 	Data        *simplejson.Json `json:"data"` | ||||
| 
 | ||||
|  | @ -80,6 +83,8 @@ type ItemDTO struct { | |||
| 	UserId      int64            `json:"userId"` | ||||
| 	NewState    string           `json:"newState"` | ||||
| 	PrevState   string           `json:"prevState"` | ||||
| 	Created     int64            `json:"created"` | ||||
| 	Updated     int64            `json:"updated"` | ||||
| 	Time        int64            `json:"time"` | ||||
| 	Text        string           `json:"text"` | ||||
| 	RegionId    int64            `json:"regionId"` | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import ( | |||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/grafana/grafana/pkg/models" | ||||
| 	"github.com/grafana/grafana/pkg/services/annotations" | ||||
|  | @ -17,6 +18,12 @@ func (r *SqlAnnotationRepo) Save(item *annotations.Item) error { | |||
| 	return inTransaction(func(sess *DBSession) error { | ||||
| 		tags := models.ParseTagPairs(item.Tags) | ||||
| 		item.Tags = models.JoinTagPairs(tags) | ||||
| 		item.Created = time.Now().UnixNano() / int64(time.Millisecond) | ||||
| 		item.Updated = item.Created | ||||
| 		if item.Epoch == 0 { | ||||
| 			item.Epoch = item.Created | ||||
| 		} | ||||
| 
 | ||||
| 		if _, err := sess.Table("annotation").Insert(item); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
|  | @ -79,6 +86,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { | |||
| 			return errors.New("Annotation not found") | ||||
| 		} | ||||
| 
 | ||||
| 		existing.Updated = time.Now().UnixNano() / int64(time.Millisecond) | ||||
| 		existing.Epoch = item.Epoch | ||||
| 		existing.Text = item.Text | ||||
| 		if item.RegionId != 0 { | ||||
|  | @ -102,7 +110,7 @@ func (r *SqlAnnotationRepo) Update(item *annotations.Item) error { | |||
| 
 | ||||
| 		existing.Tags = item.Tags | ||||
| 
 | ||||
| 		_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "tags").Update(existing) | ||||
| 		_, err = sess.Table("annotation").Id(existing.Id).Cols("epoch", "text", "region_id", "updated", "tags").Update(existing) | ||||
| 		return err | ||||
| 	}) | ||||
| } | ||||
|  | @ -124,6 +132,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I | |||
| 			annotation.text, | ||||
| 			annotation.tags, | ||||
| 			annotation.data, | ||||
| 			annotation.created, | ||||
| 			annotation.updated, | ||||
| 			usr.email, | ||||
| 			usr.login, | ||||
| 			alert.name as alert_name | ||||
|  | @ -161,6 +171,11 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I | |||
| 		params = append(params, query.PanelId) | ||||
| 	} | ||||
| 
 | ||||
| 	if query.UserId != 0 { | ||||
| 		sql.WriteString(` AND annotation.user_id = ?`) | ||||
| 		params = append(params, query.UserId) | ||||
| 	} | ||||
| 
 | ||||
| 	if query.From > 0 && query.To > 0 { | ||||
| 		sql.WriteString(` AND annotation.epoch BETWEEN ? AND ?`) | ||||
| 		params = append(params, query.From, query.To) | ||||
|  | @ -168,6 +183,8 @@ func (r *SqlAnnotationRepo) Find(query *annotations.ItemQuery) ([]*annotations.I | |||
| 
 | ||||
| 	if query.Type == "alert" { | ||||
| 		sql.WriteString(` AND annotation.alert_id > 0`) | ||||
| 	} else if query.Type == "annotation" { | ||||
| 		sql.WriteString(` AND annotation.alert_id = 0`) | ||||
| 	} | ||||
| 
 | ||||
| 	if len(query.Tags) > 0 { | ||||
|  |  | |||
|  | @ -79,6 +79,12 @@ func TestAnnotations(t *testing.T) { | |||
| 				Convey("Can read tags", func() { | ||||
| 					So(items[0].Tags, ShouldResemble, []string{"outage", "error", "type:outage", "server:server-1"}) | ||||
| 				}) | ||||
| 
 | ||||
| 				Convey("Has created and updated values", func() { | ||||
| 					So(items[0].Created, ShouldBeGreaterThan, 0) | ||||
| 					So(items[0].Updated, ShouldBeGreaterThan, 0) | ||||
| 					So(items[0].Updated, ShouldEqual, items[0].Created) | ||||
| 				}) | ||||
| 			}) | ||||
| 
 | ||||
| 			Convey("Can query for annotation by id", func() { | ||||
|  | @ -231,6 +237,10 @@ func TestAnnotations(t *testing.T) { | |||
| 					So(items[0].Tags, ShouldResemble, []string{"newtag1", "newtag2"}) | ||||
| 					So(items[0].Text, ShouldEqual, "something new") | ||||
| 				}) | ||||
| 
 | ||||
| 				Convey("Updated time has increased", func() { | ||||
| 					So(items[0].Updated, ShouldBeGreaterThan, items[0].Created) | ||||
| 				}) | ||||
| 			}) | ||||
| 
 | ||||
| 			Convey("Can delete annotation", func() { | ||||
|  |  | |||
|  | @ -90,4 +90,29 @@ func addAnnotationMig(mg *Migrator) { | |||
| 		Sqlite(updateTextFieldSql). | ||||
| 		Postgres(updateTextFieldSql). | ||||
| 		Mysql(updateTextFieldSql)) | ||||
| 
 | ||||
| 	//
 | ||||
| 	// Add a 'created' & 'updated' column
 | ||||
| 	//
 | ||||
| 	mg.AddMigration("Add created time to annotation table", NewAddColumnMigration(table, &Column{ | ||||
| 		Name: "created", Type: DB_BigInt, Nullable: true, Default: "0", | ||||
| 	})) | ||||
| 	mg.AddMigration("Add updated time to annotation table", NewAddColumnMigration(table, &Column{ | ||||
| 		Name: "updated", Type: DB_BigInt, Nullable: true, Default: "0", | ||||
| 	})) | ||||
| 	mg.AddMigration("Add index for created in annotation table", NewAddIndexMigration(table, &Index{ | ||||
| 		Cols: []string{"org_id", "created"}, Type: IndexType, | ||||
| 	})) | ||||
| 	mg.AddMigration("Add index for updated in annotation table", NewAddIndexMigration(table, &Index{ | ||||
| 		Cols: []string{"org_id", "updated"}, Type: IndexType, | ||||
| 	})) | ||||
| 
 | ||||
| 	//
 | ||||
| 	// Convert epoch saved as seconds to miliseconds
 | ||||
| 	//
 | ||||
| 	updateEpochSql := "UPDATE annotation SET epoch = (epoch*1000) where epoch < 9999999999" | ||||
| 	mg.AddMigration("Convert existing annotations from seconds to milliseconds", new(RawSqlMigration). | ||||
| 		Sqlite(updateEpochSql). | ||||
| 		Postgres(updateEpochSql). | ||||
| 		Mysql(updateEpochSql)) | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue