mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge pull request #15197 from SamuelToh/12546_annotation_nulls_existing_val
Add http patch support for annotations
This commit is contained in:
commit
afa87e6ab4
@ -97,7 +97,7 @@ Creates an annotation in the Grafana database. The `dashboardId` and `panelId` f
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```json
|
||||
```http
|
||||
POST /api/annotations HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
@ -115,7 +115,7 @@ Content-Type: application/json
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
@ -135,7 +135,7 @@ format (string with multiple tags being separated by a space).
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```json
|
||||
```http
|
||||
POST /api/annotations/graphite HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
@ -150,7 +150,7 @@ Content-Type: application/json
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```json
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
@ -164,11 +164,14 @@ Content-Type: application/json
|
||||
|
||||
`PUT /api/annotations/:id`
|
||||
|
||||
Updates all properties of an annotation that matches the specified id. To only update certain property, consider using the [Patch Annotation](#patch-annotation) operation.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```json
|
||||
```http
|
||||
PUT /api/annotations/1141 HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
@ -180,6 +183,50 @@ Content-Type: application/json
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"message":"Annotation updated"
|
||||
}
|
||||
```
|
||||
|
||||
## Patch Annotation
|
||||
|
||||
`PATCH /api/annotations/:id`
|
||||
|
||||
Updates one or more properties of an annotation that matches the specified id.
|
||||
|
||||
This operation currently supports updating of the `text`, `tags`, `time` and `timeEnd` properties. It does not handle updating of the `isRegion` and `regionId` properties. To make an annotation regional or vice versa, consider using the [Update Annotation](#update-annotation) operation.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
PATCH /api/annotations/1145 HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"text":"New Annotation Description",
|
||||
"tags":["tag6","tag7","tag8"]
|
||||
}
|
||||
```
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"message":"Annotation patched"
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Annotation By Id
|
||||
|
||||
`DELETE /api/annotations/:id`
|
||||
@ -201,7 +248,9 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Annotation deleted"}
|
||||
{
|
||||
"message":"Annotation deleted"
|
||||
}
|
||||
```
|
||||
|
||||
## Delete Annotation By RegionId
|
||||
@ -225,5 +274,7 @@ Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{"message":"Annotation region deleted"}
|
||||
{
|
||||
"message":"Annotation region deleted"
|
||||
}
|
||||
```
|
||||
|
@ -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(404, "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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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))
|
||||
})
|
||||
|
@ -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"`
|
||||
|
Loading…
Reference in New Issue
Block a user