浏览代码

Added PATCH verb end point for annotation op

Added new PATCH verb annotation endpoint

Removed unwanted fmt

Added test cases for PATCH verb annotation endpoint

Fixed formatting issue

Check arr len before proceeding

Updated doc to include PATCH verb annotation endpt
SamuelToh 7 年之前
父节点
当前提交
a7a964ec19
共有 5 个文件被更改,包括 156 次插入1 次删除
  1. 26 1
      docs/sources/http_api/annotations.md
  2. 59 0
      pkg/api/annotations.go
  3. 62 0
      pkg/api/annotations_test.go
  4. 1 0
      pkg/api/api.go
  5. 8 0
      pkg/api/dtos/annotations.go

+ 26 - 1
docs/sources/http_api/annotations.md

@@ -160,15 +160,18 @@ Content-Type: application/json
 }
 ```
 
-## Update Annotation
+## Replace Annotation
 
 `PUT /api/annotations/:id`
 
+Replaces the annotation that matches the specified id.
+
 **Example Request**:
 
 ```json
 PUT /api/annotations/1141 HTTP/1.1
 Accept: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
 Content-Type: application/json
 
 {
@@ -180,6 +183,28 @@ Content-Type: application/json
 }
 ```
 
+## Update Annotation
+
+`PATCH /api/annotations/:id`
+
+Updates one or more properties of an annotation that matches the specified id.
+
+**Example Request**:
+
+```json
+PATCH /api/annotations/1145 HTTP/1.1
+Accept: application/json
+Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
+Content-Type: application/json
+
+{
+  "time":1507037197000,
+  "timeEnd":1507180807095,
+  "text":"New Annotation Description",
+  "tags":["tag6","tag7","tag8"]
+}
+```
+
 ## Delete Annotation By Id
 
 `DELETE /api/annotations/:id`

+ 59 - 0
pkg/api/annotations.go

@@ -210,6 +210,65 @@ func UpdateAnnotation(c *m.ReqContext, cmd dtos.UpdateAnnotationsCmd) Response {
 	return Success("Annotation updated")
 }
 
+func PatchAnnotation(c *m.ReqContext, cmd dtos.PatchAnnotationsCmd) Response {
+	annotationID := c.ParamsInt64(":annotationId")
+
+	repo := annotations.GetRepository()
+
+	if resp := canSave(c, repo, annotationID); resp != nil {
+		return resp
+	}
+
+	items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: c.OrgId})
+
+	if err != nil || len(items) == 0 {
+		return Error(500, "Could not find annotation to update", err)
+	}
+
+	existing := annotations.Item{
+		OrgId:    c.OrgId,
+		UserId:   c.UserId,
+		Id:       annotationID,
+		Epoch:    items[0].Time,
+		Text:     items[0].Text,
+		Tags:     items[0].Tags,
+		RegionId: items[0].RegionId,
+	}
+
+	if cmd.Tags != nil {
+		existing.Tags = cmd.Tags
+	}
+
+	if cmd.Text != "" && cmd.Text != existing.Text {
+		existing.Text = cmd.Text
+	}
+
+	if cmd.Time > 0 && cmd.Time != existing.Epoch {
+		existing.Epoch = cmd.Time
+	}
+
+	if err := repo.Update(&existing); err != nil {
+		return Error(500, "Failed to update annotation", err)
+	}
+
+	// Update region end time if provided
+	if existing.RegionId != 0 && cmd.TimeEnd > 0 {
+		itemRight := existing
+		itemRight.RegionId = existing.Id
+		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> ...
+		itemRight.Id = 0
+
+		if err := repo.Update(&itemRight); err != nil {
+			return Error(500, "Failed to update annotation for region end time", err)
+		}
+	}
+
+	return Success("Annotation patched")
+}
+
 func DeleteAnnotations(c *m.ReqContext, cmd dtos.DeleteAnnotationsCmd) Response {
 	repo := annotations.GetRepository()
 

+ 62 - 0
pkg/api/annotations_test.go

@@ -27,6 +27,12 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 			IsRegion: false,
 		}
 
+		patchCmd := dtos.PatchAnnotationsCmd{
+			Time: 1000,
+			Text: "annotation text",
+			Tags: []string{"tag1", "tag2"},
+		}
+
 		Convey("When user is an Org Viewer", func() {
 			role := m.ROLE_VIEWER
 			Convey("Should not be allowed to save an annotation", func() {
@@ -40,6 +46,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 					So(sc.resp.Code, ShouldEqual, 403)
 				})
 
+				patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
+					sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
+					So(sc.resp.Code, ShouldEqual, 403)
+				})
+
 				loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
 					sc.handlerFunc = DeleteAnnotationByID
 					sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -67,6 +78,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 					So(sc.resp.Code, ShouldEqual, 200)
 				})
 
+				patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
+					sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
+					So(sc.resp.Code, ShouldEqual, 200)
+				})
+
 				loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
 					sc.handlerFunc = DeleteAnnotationByID
 					sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -100,6 +116,13 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 			Id:       1,
 		}
 
+		patchCmd := dtos.PatchAnnotationsCmd{
+			Time: 8000,
+			Text: "annotation text 50",
+			Tags: []string{"foo", "bar"},
+			Id:   1,
+		}
+
 		deleteCmd := dtos.DeleteAnnotationsCmd{
 			DashboardId: 1,
 			PanelId:     1,
@@ -136,6 +159,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 					So(sc.resp.Code, ShouldEqual, 403)
 				})
 
+				patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
+					sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
+					So(sc.resp.Code, ShouldEqual, 403)
+				})
+
 				loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
 					sc.handlerFunc = DeleteAnnotationByID
 					sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -163,6 +191,11 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 					So(sc.resp.Code, ShouldEqual, 200)
 				})
 
+				patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
+					sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
+					So(sc.resp.Code, ShouldEqual, 200)
+				})
+
 				loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/annotations/1", "/api/annotations/:annotationId", role, func(sc *scenarioContext) {
 					sc.handlerFunc = DeleteAnnotationByID
 					sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
@@ -189,6 +222,12 @@ func TestAnnotationsApiEndpoint(t *testing.T) {
 					sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
 					So(sc.resp.Code, ShouldEqual, 200)
 				})
+
+				patchAnnotationScenario("When calling PATCH on", "/api/annotations/1", "/api/annotations/:annotationId", role, patchCmd, func(sc *scenarioContext) {
+					sc.fakeReqWithParams("PATCH", sc.url, map[string]string{}).exec()
+					So(sc.resp.Code, ShouldEqual, 200)
+				})
+
 				deleteAnnotationsScenario("When calling POST on", "/api/annotations/mass-delete", "/api/annotations/mass-delete", role, deleteCmd, func(sc *scenarioContext) {
 					sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
 					So(sc.resp.Code, ShouldEqual, 200)
@@ -264,6 +303,29 @@ func putAnnotationScenario(desc string, url string, routePattern string, role m.
 	})
 }
 
+func patchAnnotationScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.PatchAnnotationsCmd, fn scenarioFunc) {
+	Convey(desc+" "+url, func() {
+		defer bus.ClearBusHandlers()
+
+		sc := setupScenarioContext(url)
+		sc.defaultHandler = Wrap(func(c *m.ReqContext) Response {
+			sc.context = c
+			sc.context.UserId = TestUserID
+			sc.context.OrgId = TestOrgID
+			sc.context.OrgRole = role
+
+			return PatchAnnotation(c, cmd)
+		})
+
+		fakeAnnoRepo = &fakeAnnotationsRepo{}
+		annotations.SetRepository(fakeAnnoRepo)
+
+		sc.m.Patch(routePattern, sc.defaultHandler)
+
+		fn(sc)
+	})
+}
+
 func deleteAnnotationsScenario(desc string, url string, routePattern string, role m.RoleType, cmd dtos.DeleteAnnotationsCmd, fn scenarioFunc) {
 	Convey(desc+" "+url, func() {
 		defer bus.ClearBusHandlers()

+ 1 - 0
pkg/api/api.go

@@ -354,6 +354,7 @@ func (hs *HTTPServer) registerRoutes() {
 			annotationsRoute.Post("/", bind(dtos.PostAnnotationsCmd{}), Wrap(PostAnnotation))
 			annotationsRoute.Delete("/:annotationId", Wrap(DeleteAnnotationByID))
 			annotationsRoute.Put("/:annotationId", bind(dtos.UpdateAnnotationsCmd{}), Wrap(UpdateAnnotation))
+			annotationsRoute.Patch("/:annotationId", bind(dtos.PatchAnnotationsCmd{}), Wrap(PatchAnnotation))
 			annotationsRoute.Delete("/region/:regionId", Wrap(DeleteAnnotationRegion))
 			annotationsRoute.Post("/graphite", reqEditorRole, bind(dtos.PostGraphiteAnnotationsCmd{}), Wrap(PostGraphiteAnnotation))
 		})

+ 8 - 0
pkg/api/dtos/annotations.go

@@ -22,6 +22,14 @@ type UpdateAnnotationsCmd struct {
 	TimeEnd  int64    `json:"timeEnd"`
 }
 
+type PatchAnnotationsCmd struct {
+	Id       int64    `json:"id"`
+	Time     int64    `json:"time"`
+	Text     string   `json:"text"`
+	Tags     []string `json:"tags"`
+	TimeEnd  int64    `json:"timeEnd"`
+}
+
 type DeleteAnnotationsCmd struct {
 	AlertId      int64 `json:"alertId"`
 	DashboardId  int64 `json:"dashboardId"`