mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 11:22:20 +08:00
Correlations: Add UpdateCorrelation HTTP API (#52444)
* Correlations: add UpdateCorrelation HTTP API * handle correlation not found * add tests * fix lint errors * add bad request to API spec * change casing * fix casing in docs * fix tests * update spec
This commit is contained in:
@ -101,3 +101,52 @@ Status codes:
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
{
|
{
|
||||||
|
```
|
||||||
|
|
||||||
|
JSON body schema:
|
||||||
|
|
||||||
|
- **label** – A label for the correlation.
|
||||||
|
- **description** – A description for the correlation.
|
||||||
|
|
||||||
|
**Example response:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
```
|
||||||
|
|
||||||
|
Status codes:
|
||||||
|
|
||||||
|
- **200** – OK
|
||||||
|
- **401** – Unauthorized
|
||||||
|
- **403** – Forbidden, source data source is read-only
|
||||||
|
- **404** – Not found, either source or target data source could not be found
|
||||||
|
- **500** – Internal error
|
||||||
|
- **label** – A label for the correlation.
|
||||||
|
- **description** – A description for the correlation.
|
||||||
|
|
||||||
|
**Example response:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200
|
||||||
|
Content-Type: application/json
|
||||||
|
{
|
||||||
|
"message": "Correlation updated",
|
||||||
|
"result": {
|
||||||
|
"description": "Logs to Traces",
|
||||||
|
"label": "My Label",
|
||||||
|
"sourceUID": "uyBf2637k",
|
||||||
|
"targetUID": "PDDA8E780A17E7EF1",
|
||||||
|
"uid": "J6gn7d31L"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Status codes:
|
||||||
|
|
||||||
|
- **200** – OK
|
||||||
|
- **401** – Unauthorized
|
||||||
|
- **403** – Forbidden, source data source is read-only
|
||||||
|
- **404** – Not found, either source or target data source could not be found
|
||||||
|
- **500** – Internal error
|
||||||
|
@ -21,6 +21,7 @@ func (s *CorrelationsService) registerAPIEndpoints() {
|
|||||||
s.RouteRegister.Group("/api/datasources/uid/:uid/correlations", func(entities routing.RouteRegister) {
|
s.RouteRegister.Group("/api/datasources/uid/:uid/correlations", func(entities routing.RouteRegister) {
|
||||||
entities.Post("/", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.createHandler))
|
entities.Post("/", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.createHandler))
|
||||||
entities.Delete("/:correlationUID", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.deleteHandler))
|
entities.Delete("/:correlationUID", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.deleteHandler))
|
||||||
|
entities.Patch("/:correlationUID", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.updateHandler))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,3 +128,66 @@ type DeleteCorrelationResponse struct {
|
|||||||
// in: body
|
// in: body
|
||||||
Body DeleteCorrelationResponseBody `json:"body"`
|
Body DeleteCorrelationResponseBody `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:route PATCH /datasources/uid/{sourceUID}/correlations/{correlationUID} correlations updateCorrelation
|
||||||
|
//
|
||||||
|
// Updates a correlation.
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: updateCorrelationResponse
|
||||||
|
// 400: badRequestError
|
||||||
|
// 401: unauthorisedError
|
||||||
|
// 403: forbiddenError
|
||||||
|
// 404: notFoundError
|
||||||
|
// 500: internalServerError
|
||||||
|
func (s *CorrelationsService) updateHandler(c *models.ReqContext) response.Response {
|
||||||
|
cmd := UpdateCorrelationCommand{}
|
||||||
|
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||||
|
return response.Error(http.StatusBadRequest, "bad request data", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.UID = web.Params(c.Req)[":correlationUID"]
|
||||||
|
cmd.SourceUID = web.Params(c.Req)[":uid"]
|
||||||
|
cmd.OrgId = c.OrgId
|
||||||
|
|
||||||
|
correlation, err := s.UpdateCorrelation(c.Req.Context(), cmd)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, ErrUpdateCorrelationEmptyParams) {
|
||||||
|
return response.Error(http.StatusBadRequest, "At least one of label, description is required", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrSourceDataSourceDoesNotExists) {
|
||||||
|
return response.Error(http.StatusNotFound, "Data source not found", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrCorrelationNotFound) {
|
||||||
|
return response.Error(http.StatusNotFound, "Correlation not found", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, ErrSourceDataSourceReadOnly) {
|
||||||
|
return response.Error(http.StatusForbidden, "Data source is read only", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Error(http.StatusInternalServerError, "Failed to update correlation", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.JSON(http.StatusOK, UpdateCorrelationResponseBody{Message: "Correlation updated", Result: correlation})
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:parameters updateCorrelation
|
||||||
|
type UpdateCorrelationParams struct {
|
||||||
|
// in:path
|
||||||
|
// required:true
|
||||||
|
DatasourceUID string `json:"sourceUID"`
|
||||||
|
// in:path
|
||||||
|
// required:true
|
||||||
|
CorrelationUID string `json:"correlationUID"`
|
||||||
|
// in: body
|
||||||
|
Body UpdateCorrelationCommand `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//swagger:response updateCorrelationResponse
|
||||||
|
type UpdateCorrelationResponse struct {
|
||||||
|
// in: body
|
||||||
|
Body UpdateCorrelationResponseBody `json:"body"`
|
||||||
|
}
|
||||||
|
@ -52,6 +52,10 @@ func (s CorrelationsService) DeleteCorrelation(ctx context.Context, cmd DeleteCo
|
|||||||
return s.deleteCorrelation(ctx, cmd)
|
return s.deleteCorrelation(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s CorrelationsService) UpdateCorrelation(ctx context.Context, cmd UpdateCorrelationCommand) (Correlation, error) {
|
||||||
|
return s.updateCorrelation(ctx, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
func (s CorrelationsService) DeleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error {
|
func (s CorrelationsService) DeleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error {
|
||||||
return s.deleteCorrelationsBySourceUID(ctx, cmd)
|
return s.deleteCorrelationsBySourceUID(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,61 @@ func (s CorrelationsService) deleteCorrelation(ctx context.Context, cmd DeleteCo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s CorrelationsService) updateCorrelation(ctx context.Context, cmd UpdateCorrelationCommand) (Correlation, error) {
|
||||||
|
correlation := Correlation{
|
||||||
|
UID: cmd.UID,
|
||||||
|
SourceUID: cmd.SourceUID,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.SQLStore.WithTransactionalDbSession(ctx, func(session *sqlstore.DBSession) error {
|
||||||
|
query := &datasources.GetDataSourceQuery{
|
||||||
|
OrgId: cmd.OrgId,
|
||||||
|
Uid: cmd.SourceUID,
|
||||||
|
}
|
||||||
|
if err := s.DataSourceService.GetDataSource(ctx, query); err != nil {
|
||||||
|
return ErrSourceDataSourceDoesNotExists
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.Result.ReadOnly {
|
||||||
|
return ErrSourceDataSourceReadOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Label == nil && cmd.Description == nil {
|
||||||
|
return ErrUpdateCorrelationEmptyParams
|
||||||
|
}
|
||||||
|
update := Correlation{}
|
||||||
|
if cmd.Label != nil {
|
||||||
|
update.Label = *cmd.Label
|
||||||
|
session.MustCols("label")
|
||||||
|
}
|
||||||
|
if cmd.Description != nil {
|
||||||
|
update.Description = *cmd.Description
|
||||||
|
session.MustCols("description")
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCount, err := session.Where("uid = ? AND source_uid = ?", correlation.UID, correlation.SourceUID).Limit(1).Update(update)
|
||||||
|
if updateCount == 0 {
|
||||||
|
return ErrCorrelationNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := session.Get(&correlation)
|
||||||
|
if !found {
|
||||||
|
return ErrCorrelationNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return Correlation{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return correlation, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s CorrelationsService) deleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error {
|
func (s CorrelationsService) deleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error {
|
||||||
return s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error {
|
return s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error {
|
||||||
_, err := session.Delete(&Correlation{SourceUID: cmd.SourceUID})
|
_, err := session.Delete(&Correlation{SourceUID: cmd.SourceUID})
|
||||||
|
@ -9,8 +9,8 @@ var (
|
|||||||
ErrSourceDataSourceDoesNotExists = errors.New("source data source does not exist")
|
ErrSourceDataSourceDoesNotExists = errors.New("source data source does not exist")
|
||||||
ErrTargetDataSourceDoesNotExists = errors.New("target data source does not exist")
|
ErrTargetDataSourceDoesNotExists = errors.New("target data source does not exist")
|
||||||
ErrCorrelationFailedGenerateUniqueUid = errors.New("failed to generate unique correlation UID")
|
ErrCorrelationFailedGenerateUniqueUid = errors.New("failed to generate unique correlation UID")
|
||||||
ErrCorrelationIdentifierNotSet = errors.New("source identifier and org id are needed to be able to edit correlations")
|
|
||||||
ErrCorrelationNotFound = errors.New("correlation not found")
|
ErrCorrelationNotFound = errors.New("correlation not found")
|
||||||
|
ErrUpdateCorrelationEmptyParams = errors.New("not enough parameters to edit correlation")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Correlation is the model for correlations definitions
|
// Correlation is the model for correlations definitions
|
||||||
@ -72,6 +72,28 @@ type DeleteCorrelationCommand struct {
|
|||||||
OrgId int64
|
OrgId int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// swagger:model
|
||||||
|
type UpdateCorrelationResponseBody struct {
|
||||||
|
Result Correlation `json:"result"`
|
||||||
|
// example: Correlation updated
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCorrelationCommand is the command for updating a correlation
|
||||||
|
type UpdateCorrelationCommand struct {
|
||||||
|
// UID of the correlation to be deleted.
|
||||||
|
UID string `json:"-"`
|
||||||
|
SourceUID string `json:"-"`
|
||||||
|
OrgId int64 `json:"-"`
|
||||||
|
|
||||||
|
// Optional label identifying the correlation
|
||||||
|
// example: My label
|
||||||
|
Label *string `json:"label"`
|
||||||
|
// Optional description of the correlation
|
||||||
|
// example: Logs to Traces
|
||||||
|
Description *string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteCorrelationsBySourceUIDCommand struct {
|
type DeleteCorrelationsBySourceUIDCommand struct {
|
||||||
SourceUID string
|
SourceUID string
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,25 @@ func (c TestContext) Post(params PostParams) *http.Response {
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PatchParams struct {
|
||||||
|
url string
|
||||||
|
body string
|
||||||
|
user User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c TestContext) Patch(params PatchParams) *http.Response {
|
||||||
|
c.t.Helper()
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPatch, c.getURL(params.url, params.user), bytes.NewBuffer([]byte(params.body)))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteParams struct {
|
type DeleteParams struct {
|
||||||
url string
|
url string
|
||||||
user User
|
user User
|
||||||
|
356
pkg/tests/api/correlations/correlations_update_test.go
Normal file
356
pkg/tests/api/correlations/correlations_update_test.go
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
package correlations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/correlations"
|
||||||
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIntegrationUpdateCorrelation(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
ctx := NewTestEnv(t)
|
||||||
|
|
||||||
|
adminUser := User{
|
||||||
|
username: "admin",
|
||||||
|
password: "admin",
|
||||||
|
}
|
||||||
|
editorUser := User{
|
||||||
|
username: "editor",
|
||||||
|
password: "editor",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.createUser(user.CreateUserCommand{
|
||||||
|
DefaultOrgRole: string(models.ROLE_EDITOR),
|
||||||
|
Password: editorUser.password,
|
||||||
|
Login: editorUser.username,
|
||||||
|
})
|
||||||
|
ctx.createUser(user.CreateUserCommand{
|
||||||
|
DefaultOrgRole: string(models.ROLE_ADMIN),
|
||||||
|
Password: adminUser.password,
|
||||||
|
Login: adminUser.username,
|
||||||
|
})
|
||||||
|
|
||||||
|
createDsCommand := &datasources.AddDataSourceCommand{
|
||||||
|
Name: "read-only",
|
||||||
|
Type: "loki",
|
||||||
|
ReadOnly: true,
|
||||||
|
OrgId: 1,
|
||||||
|
}
|
||||||
|
ctx.createDs(createDsCommand)
|
||||||
|
readOnlyDS := createDsCommand.Result.Uid
|
||||||
|
|
||||||
|
createDsCommand = &datasources.AddDataSourceCommand{
|
||||||
|
Name: "writable",
|
||||||
|
Type: "loki",
|
||||||
|
OrgId: 1,
|
||||||
|
}
|
||||||
|
ctx.createDs(createDsCommand)
|
||||||
|
writableDs := createDsCommand.Result.Uid
|
||||||
|
writableDsOrgId := createDsCommand.Result.OrgId
|
||||||
|
|
||||||
|
t.Run("Unauthenticated users shouldn't be able to update correlations", func(t *testing.T) {
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", "some-ds-uid", "some-correlation-uid"),
|
||||||
|
body: ``,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response errorResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Unauthorized", response.Message)
|
||||||
|
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("non org admin shouldn't be able to update correlations", func(t *testing.T) {
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", "some-ds-uid", "some-correlation-uid"),
|
||||||
|
body: `{}`,
|
||||||
|
user: editorUser,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response errorResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Contains(t, response.Message, "Permissions needed: datasources:write")
|
||||||
|
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inexistent source data source should result in a 404", func(t *testing.T) {
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", "some-ds-uid", "some-correlation-uid"),
|
||||||
|
body: `{}`,
|
||||||
|
user: adminUser,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response errorResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Data source not found", response.Message)
|
||||||
|
require.Equal(t, correlations.ErrSourceDataSourceDoesNotExists.Error(), response.Error)
|
||||||
|
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("inexistent correlation should result in a 404", func(t *testing.T) {
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", writableDs, "nonexistent-correlation-uid"),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"label": ""
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response errorResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Correlation not found", response.Message)
|
||||||
|
require.Equal(t, correlations.ErrCorrelationNotFound.Error(), response.Error)
|
||||||
|
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("updating a correlation originating from a read-only data source should result in a 403", func(t *testing.T) {
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", readOnlyDS, "nonexistent-correlation-uid"),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusForbidden, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response errorResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Data source is read only", response.Message)
|
||||||
|
require.Equal(t, correlations.ErrSourceDataSourceReadOnly.Error(), response.Error)
|
||||||
|
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("updating a without data should result in a 400", func(t *testing.T) {
|
||||||
|
correlation := ctx.createCorrelation(correlations.CreateCorrelationCommand{
|
||||||
|
SourceUID: writableDs,
|
||||||
|
TargetUID: writableDs,
|
||||||
|
OrgId: writableDsOrgId,
|
||||||
|
})
|
||||||
|
|
||||||
|
// no params
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response errorResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "At least one of label, description is required", response.Message)
|
||||||
|
require.Equal(t, correlations.ErrUpdateCorrelationEmptyParams.Error(), response.Error)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
|
||||||
|
// empty body
|
||||||
|
res = ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: ``,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err = ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "At least one of label, description is required", response.Message)
|
||||||
|
require.Equal(t, correlations.ErrUpdateCorrelationEmptyParams.Error(), response.Error)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
|
||||||
|
// all set to null
|
||||||
|
res = ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"label": null,
|
||||||
|
"description": null
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err = ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "At least one of label, description is required", response.Message)
|
||||||
|
require.Equal(t, correlations.ErrUpdateCorrelationEmptyParams.Error(), response.Error)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("updating a correlation pointing to a read-only data source should work", func(t *testing.T) {
|
||||||
|
correlation := ctx.createCorrelation(correlations.CreateCorrelationCommand{
|
||||||
|
SourceUID: writableDs,
|
||||||
|
TargetUID: writableDs,
|
||||||
|
OrgId: writableDsOrgId,
|
||||||
|
Label: "a label",
|
||||||
|
})
|
||||||
|
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"label": "updated label"
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response correlations.UpdateCorrelationResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Correlation updated", response.Message)
|
||||||
|
require.Equal(t, "updated label", response.Result.Label)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should correctly update correlations", func(t *testing.T) {
|
||||||
|
correlation := ctx.createCorrelation(correlations.CreateCorrelationCommand{
|
||||||
|
SourceUID: writableDs,
|
||||||
|
TargetUID: writableDs,
|
||||||
|
OrgId: writableDsOrgId,
|
||||||
|
Label: "0",
|
||||||
|
Description: "0",
|
||||||
|
})
|
||||||
|
|
||||||
|
// updating all
|
||||||
|
res := ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"label": "1",
|
||||||
|
"description": "1"
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err := ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var response correlations.UpdateCorrelationResponseBody
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Correlation updated", response.Message)
|
||||||
|
require.Equal(t, "1", response.Result.Label)
|
||||||
|
require.Equal(t, "1", response.Result.Label)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
|
||||||
|
// partially updating only label
|
||||||
|
res = ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"label": "2"
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err = ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Correlation updated", response.Message)
|
||||||
|
require.Equal(t, "2", response.Result.Label)
|
||||||
|
require.Equal(t, "1", response.Result.Description)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
|
||||||
|
// partially updating only description
|
||||||
|
res = ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"description": "2"
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err = ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Correlation updated", response.Message)
|
||||||
|
require.Equal(t, "2", response.Result.Label)
|
||||||
|
require.Equal(t, "2", response.Result.Description)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
|
||||||
|
// setting both to empty strings (testing wether empty strings are handled correctly)
|
||||||
|
res = ctx.Patch(PatchParams{
|
||||||
|
url: fmt.Sprintf("/api/datasources/uid/%s/correlations/%s", correlation.SourceUID, correlation.UID),
|
||||||
|
user: adminUser,
|
||||||
|
body: `{
|
||||||
|
"label": "",
|
||||||
|
"description": ""
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||||
|
|
||||||
|
responseBody, err = ioutil.ReadAll(res.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = json.Unmarshal(responseBody, &response)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "Correlation updated", response.Message)
|
||||||
|
require.Equal(t, "", response.Result.Label)
|
||||||
|
require.Equal(t, "", response.Result.Description)
|
||||||
|
require.NoError(t, res.Body.Close())
|
||||||
|
})
|
||||||
|
}
|
@ -4446,6 +4446,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/datasources/uid/{sourceUID}/correlations/{correlationUID}": {
|
||||||
|
"patch": {
|
||||||
|
"tags": [
|
||||||
|
"correlations"
|
||||||
|
],
|
||||||
|
"summary": "Updates a correlation.",
|
||||||
|
"operationId": "updateCorrelation",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "sourceUID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "correlationUID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateCorrelationCommand"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/updateCorrelationResponse"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/badRequestError"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/responses/unauthorisedError"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbiddenError"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFoundError"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$ref": "#/responses/internalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/datasources/uid/{uid}": {
|
"/datasources/uid/{uid}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:kLtEtcRGk` (single data source).",
|
"description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:kLtEtcRGk` (single data source).",
|
||||||
@ -17439,6 +17489,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"UpdateCorrelationCommand": {
|
||||||
|
"description": "UpdateCorrelationCommand is the command for updating a correlation",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "Optional description of the correlation",
|
||||||
|
"type": "string",
|
||||||
|
"example": "Logs to Traces"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"description": "Optional label identifying the correlation",
|
||||||
|
"type": "string",
|
||||||
|
"example": "My label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UpdateCorrelationResponseBody": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Correlation updated"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/Correlation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"UpdateDashboardACLCommand": {
|
"UpdateDashboardACLCommand": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -19773,6 +19851,12 @@
|
|||||||
"$ref": "#/definitions/ErrorResponseBody"
|
"$ref": "#/definitions/ErrorResponseBody"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"updateCorrelationResponse": {
|
||||||
|
"description": "(empty)",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateCorrelationResponseBody"
|
||||||
|
}
|
||||||
|
},
|
||||||
"updatePlaylistResponse": {
|
"updatePlaylistResponse": {
|
||||||
"description": "(empty)",
|
"description": "(empty)",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -3799,6 +3799,56 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/datasources/uid/{sourceUID}/correlations/{correlationUID}": {
|
||||||
|
"patch": {
|
||||||
|
"tags": [
|
||||||
|
"correlations"
|
||||||
|
],
|
||||||
|
"summary": "Updates a correlation.",
|
||||||
|
"operationId": "updateCorrelation",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "sourceUID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "correlationUID",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateCorrelationCommand"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/updateCorrelationResponse"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/badRequestError"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"$ref": "#/responses/unauthorisedError"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbiddenError"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFoundError"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$ref": "#/responses/internalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/datasources/uid/{uid}": {
|
"/datasources/uid/{uid}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:kLtEtcRGk` (single data source).",
|
"description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:kLtEtcRGk` (single data source).",
|
||||||
@ -14095,6 +14145,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"UpdateCorrelationCommand": {
|
||||||
|
"description": "UpdateCorrelationCommand is the command for updating a correlation",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"description": {
|
||||||
|
"description": "Optional description of the correlation",
|
||||||
|
"type": "string",
|
||||||
|
"example": "Logs to Traces"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"description": "Optional label identifying the correlation",
|
||||||
|
"type": "string",
|
||||||
|
"example": "My label"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UpdateCorrelationResponseBody": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Correlation updated"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"$ref": "#/definitions/Correlation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"UpdateDashboardACLCommand": {
|
"UpdateDashboardACLCommand": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -15772,6 +15850,12 @@
|
|||||||
"$ref": "#/definitions/ErrorResponseBody"
|
"$ref": "#/definitions/ErrorResponseBody"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"updateCorrelationResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/UpdateCorrelationResponseBody"
|
||||||
|
}
|
||||||
|
},
|
||||||
"updatePlaylistResponse": {
|
"updatePlaylistResponse": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
Reference in New Issue
Block a user