Skip to content

Commit 5ce4baf

Browse files
authored
Correlations: Add CreateCorrelation HTTP API (grafana#51630)
* Correlations: add migration * Correlations: Add CreateCorrelation API * Correlations: Make correlations work with provisioning * Handle version changes * Fix lining error * lint fixes * rebuild betterer results * add a UID to each correlation * Fix lint errors * add docs * better wording in API docs * remove leftover comment * handle ds updates * Fix error message typo * add bad data test * make correlations a separate table * skip readonly check when provisioning correlations * delete stale correlations when datasources are deleted * restore provisioned readonly ds * publish deletion event with full data * generate swagger and HTTP API docs * apply source datasource permission to create correlation API * Fix tests & lint errors * ignore empty deletion events * fix last lint errors * fix more lint error * Only publish deletion event if datasource was actually deleted * delete DS provisioning deletes correlations, added & fixed tests * Fix unmarshalling tests * Fix linting errors * Fix deltion event tests * fix small linting error * fix lint errors * update betterer * fix test * make path singular * Revert "make path singular" This reverts commit 420c3d3. * add integration tests * remove unneeded id from correlations table * update spec * update leftover references to CorrelationDTO * fix tests * cleanup tests * fix lint error
1 parent dbc2171 commit 5ce4baf

File tree

27 files changed

+1326
-48
lines changed

27 files changed

+1326
-48
lines changed

devenv/datasources.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,15 @@ datasources:
247247
access: proxy
248248
url: http://localhost:3100
249249
editable: false
250+
correlations:
251+
- targetUid: gdev-jaeger
252+
label: "Jaeger traces"
253+
description: "Related traces stored in Jaeger"
254+
- targetUid: gdev-zipkin
255+
label: "Zipkin traces"
256+
description: "Related traces stored in Zipkin"
250257
jsonData:
258+
something: here
251259
manageAlerts: false
252260
derivedFields:
253261
- name: "traceID"
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
aliases:
3+
- /docs/grafana/latest/developers/http_api/correlations/
4+
- /docs/grafana/latest/http_api/correlations/
5+
description: Grafana Correlations HTTP API
6+
keywords:
7+
- grafana
8+
- http
9+
- documentation
10+
- api
11+
- correlations
12+
- Glue
13+
title: 'Correlations HTTP API '
14+
---
15+
16+
# Correlations API
17+
18+
This API can be used to define correlations between data sources.
19+
20+
## Create correlations
21+
22+
`POST /api/datasources/uid/:sourceUid/correlations`
23+
24+
Creates a correlation between two data sources - the source data source indicated by the path UID, and the target data source which is specified in the body.
25+
26+
**Example request:**
27+
28+
```http
29+
POST /api/datasources/uid/uyBf2637k/correlations HTTP/1.1
30+
Accept: application/json
31+
Content-Type: application/json
32+
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
33+
{
34+
"targetUid": "PDDA8E780A17E7EF1",
35+
"label": "My Label",
36+
"description": "Logs to Traces",
37+
}
38+
```
39+
40+
JSON body schema:
41+
42+
- **targetUid** – Target data source uid.
43+
- **label** – A label for the correlation.
44+
- **description** – A description for the correlation.
45+
46+
**Example response:**
47+
48+
```http
49+
HTTP/1.1 200
50+
Content-Type: application/json
51+
{
52+
"message": "Correlation created",
53+
"result": {
54+
"description": "Logs to Traces",
55+
"label": "My Label",
56+
"sourceUid": "uyBf2637k",
57+
"targetUid": "PDDA8E780A17E7EF1",
58+
"uid": "50xhMlg9k"
59+
}
60+
}
61+
```
62+
63+
Status codes:
64+
65+
- **200** – OK
66+
- **400** - Errors (invalid JSON, missing or invalid fields)
67+
- **401** – Unauthorized
68+
- **403** – Forbidden, source data source is read-only
69+
- **404** – Not found, either source or target data source could not be found
70+
- **500** – Internal error
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package definitions
2+
3+
import (
4+
"github.com/grafana/grafana/pkg/services/correlations"
5+
)
6+
7+
// swagger:route POST /datasources/uid/{uid}/correlations correlations createCorrelation
8+
//
9+
// Add correlation.
10+
//
11+
// Responses:
12+
// 200: createCorrelationResponse
13+
// 400: badRequestError
14+
// 401: unauthorisedError
15+
// 403: forbiddenError
16+
// 404: notFoundError
17+
// 500: internalServerError
18+
19+
// swagger:parameters createCorrelation
20+
type CreateCorrelationParams struct {
21+
// in:body
22+
// required:true
23+
Body correlations.CreateCorrelationCommand `json:"body"`
24+
// in:path
25+
// required:true
26+
SourceUID string `json:"uid"`
27+
}
28+
29+
//swagger:response createCorrelationResponse
30+
type CreateCorrelationResponse struct {
31+
// in: body
32+
Body correlations.CreateCorrelationResponse `json:"body"`
33+
}

pkg/api/http_server.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import (
6565
"github.com/grafana/grafana/pkg/services/provisioning"
6666
"github.com/grafana/grafana/pkg/services/quota"
6767

68+
"github.com/grafana/grafana/pkg/services/correlations"
6869
publicdashboardsApi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
6970
"github.com/grafana/grafana/pkg/services/query"
7071
"github.com/grafana/grafana/pkg/services/queryhistory"
@@ -122,6 +123,7 @@ type HTTPServer struct {
122123
SearchService search.Service
123124
ShortURLService shorturls.Service
124125
QueryHistoryService queryhistory.Service
126+
CorrelationsService correlations.Service
125127
Live *live.GrafanaLive
126128
LivePushGateway *pushhttp.Gateway
127129
ThumbService thumbs.Service
@@ -188,7 +190,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
188190
pluginDashboardService plugindashboards.Service, pluginStore plugins.Store, pluginClient plugins.Client,
189191
pluginErrorResolver plugins.ErrorResolver, pluginManager plugins.Manager, settingsProvider setting.Provider,
190192
dataSourceCache datasources.CacheService, userTokenService models.UserTokenService,
191-
cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, queryHistoryService queryhistory.Service,
193+
cleanUpService *cleanup.CleanUpService, shortURLService shorturls.Service, queryHistoryService queryhistory.Service, correlationsService correlations.Service,
192194
thumbService thumbs.Service, remoteCache *remotecache.RemoteCache, provisioningService provisioning.ProvisioningService,
193195
loginService login.Service, authenticator loginpkg.Authenticator, accessControl accesscontrol.AccessControl,
194196
dataSourceProxy *datasourceproxy.DataSourceProxyService, searchService *search.SearchService,
@@ -239,6 +241,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
239241
cleanUpService: cleanUpService,
240242
ShortURLService: shortURLService,
241243
QueryHistoryService: queryHistoryService,
244+
CorrelationsService: correlationsService,
242245
Features: features,
243246
ThumbService: thumbService,
244247
StorageService: storageService,

pkg/server/wire.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import (
4545
"github.com/grafana/grafana/pkg/services/comments"
4646
"github.com/grafana/grafana/pkg/services/contexthandler"
4747
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
48+
"github.com/grafana/grafana/pkg/services/correlations"
4849
"github.com/grafana/grafana/pkg/services/dashboardimport"
4950
dashboardimportservice "github.com/grafana/grafana/pkg/services/dashboardimport/service"
5051
"github.com/grafana/grafana/pkg/services/dashboards"
@@ -183,6 +184,8 @@ var wireBasicSet = wire.NewSet(
183184
wire.Bind(new(shorturls.Service), new(*shorturls.ShortURLService)),
184185
queryhistory.ProvideService,
185186
wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)),
187+
correlations.ProvideService,
188+
wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)),
186189
quotaimpl.ProvideService,
187190
remotecache.ProvideService,
188191
loginservice.ProvideService,

pkg/services/correlations/api.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package correlations
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
"github.com/grafana/grafana/pkg/api/response"
8+
"github.com/grafana/grafana/pkg/api/routing"
9+
"github.com/grafana/grafana/pkg/middleware"
10+
"github.com/grafana/grafana/pkg/models"
11+
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
12+
"github.com/grafana/grafana/pkg/services/datasources"
13+
14+
"github.com/grafana/grafana/pkg/web"
15+
)
16+
17+
func (s *CorrelationsService) registerAPIEndpoints() {
18+
uidScope := datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":uid"))
19+
authorize := ac.Middleware(s.AccessControl)
20+
21+
s.RouteRegister.Group("/api/datasources/uid/:uid/correlations", func(entities routing.RouteRegister) {
22+
entities.Post("/", middleware.ReqSignedIn, authorize(ac.ReqOrgAdmin, ac.EvalPermission(datasources.ActionWrite, uidScope)), routing.Wrap(s.createHandler))
23+
})
24+
}
25+
26+
// createHandler handles POST /datasources/uid/:uid/correlations
27+
func (s *CorrelationsService) createHandler(c *models.ReqContext) response.Response {
28+
cmd := CreateCorrelationCommand{}
29+
if err := web.Bind(c.Req, &cmd); err != nil {
30+
return response.Error(http.StatusBadRequest, "bad request data", err)
31+
}
32+
cmd.SourceUID = web.Params(c.Req)[":uid"]
33+
cmd.OrgId = c.OrgId
34+
35+
correlation, err := s.CreateCorrelation(c.Req.Context(), cmd)
36+
if err != nil {
37+
if errors.Is(err, ErrSourceDataSourceDoesNotExists) || errors.Is(err, ErrTargetDataSourceDoesNotExists) {
38+
return response.Error(http.StatusNotFound, "Data source not found", err)
39+
}
40+
41+
if errors.Is(err, ErrSourceDataSourceReadOnly) {
42+
return response.Error(http.StatusForbidden, "Data source is read only", err)
43+
}
44+
45+
return response.Error(http.StatusInternalServerError, "Failed to add correlation", err)
46+
}
47+
48+
return response.JSON(http.StatusOK, CreateCorrelationResponse{Result: correlation, Message: "Correlation created"})
49+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package correlations
2+
3+
import (
4+
"context"
5+
6+
"github.com/grafana/grafana/pkg/api/routing"
7+
"github.com/grafana/grafana/pkg/bus"
8+
"github.com/grafana/grafana/pkg/events"
9+
10+
"github.com/grafana/grafana/pkg/infra/log"
11+
"github.com/grafana/grafana/pkg/services/accesscontrol"
12+
"github.com/grafana/grafana/pkg/services/datasources"
13+
"github.com/grafana/grafana/pkg/services/sqlstore"
14+
)
15+
16+
func ProvideService(sqlStore *sqlstore.SQLStore, routeRegister routing.RouteRegister, ds datasources.DataSourceService, ac accesscontrol.AccessControl, bus bus.Bus) *CorrelationsService {
17+
s := &CorrelationsService{
18+
SQLStore: sqlStore,
19+
RouteRegister: routeRegister,
20+
log: log.New("correlations"),
21+
DataSourceService: ds,
22+
AccessControl: ac,
23+
}
24+
25+
s.registerAPIEndpoints()
26+
27+
bus.AddEventListener(s.handleDatasourceDeletion)
28+
29+
return s
30+
}
31+
32+
type Service interface {
33+
CreateCorrelation(ctx context.Context, cmd CreateCorrelationCommand) (Correlation, error)
34+
DeleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error
35+
DeleteCorrelationsByTargetUID(ctx context.Context, cmd DeleteCorrelationsByTargetUIDCommand) error
36+
}
37+
38+
type CorrelationsService struct {
39+
SQLStore *sqlstore.SQLStore
40+
RouteRegister routing.RouteRegister
41+
log log.Logger
42+
DataSourceService datasources.DataSourceService
43+
AccessControl accesscontrol.AccessControl
44+
}
45+
46+
func (s CorrelationsService) CreateCorrelation(ctx context.Context, cmd CreateCorrelationCommand) (Correlation, error) {
47+
return s.createCorrelation(ctx, cmd)
48+
}
49+
50+
func (s CorrelationsService) DeleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error {
51+
return s.deleteCorrelationsBySourceUID(ctx, cmd)
52+
}
53+
54+
func (s CorrelationsService) DeleteCorrelationsByTargetUID(ctx context.Context, cmd DeleteCorrelationsByTargetUIDCommand) error {
55+
return s.deleteCorrelationsByTargetUID(ctx, cmd)
56+
}
57+
58+
func (s CorrelationsService) handleDatasourceDeletion(ctx context.Context, event *events.DataSourceDeleted) error {
59+
return s.SQLStore.InTransaction(ctx, func(ctx context.Context) error {
60+
if err := s.deleteCorrelationsBySourceUID(ctx, DeleteCorrelationsBySourceUIDCommand{
61+
SourceUID: event.UID,
62+
}); err != nil {
63+
return err
64+
}
65+
66+
if err := s.deleteCorrelationsByTargetUID(ctx, DeleteCorrelationsByTargetUIDCommand{
67+
TargetUID: event.UID,
68+
}); err != nil {
69+
return err
70+
}
71+
72+
return nil
73+
})
74+
}

pkg/services/correlations/database.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package correlations
2+
3+
import (
4+
"context"
5+
6+
"github.com/grafana/grafana/pkg/services/datasources"
7+
"github.com/grafana/grafana/pkg/services/sqlstore"
8+
"github.com/grafana/grafana/pkg/util"
9+
)
10+
11+
// createCorrelation adds a correlation
12+
func (s CorrelationsService) createCorrelation(ctx context.Context, cmd CreateCorrelationCommand) (Correlation, error) {
13+
correlation := Correlation{
14+
UID: util.GenerateShortUID(),
15+
SourceUID: cmd.SourceUID,
16+
TargetUID: cmd.TargetUID,
17+
Label: cmd.Label,
18+
Description: cmd.Description,
19+
}
20+
21+
err := s.SQLStore.WithTransactionalDbSession(ctx, func(session *sqlstore.DBSession) error {
22+
var err error
23+
24+
query := &datasources.GetDataSourceQuery{
25+
OrgId: cmd.OrgId,
26+
Uid: cmd.SourceUID,
27+
}
28+
if err = s.DataSourceService.GetDataSource(ctx, query); err != nil {
29+
return ErrSourceDataSourceDoesNotExists
30+
}
31+
32+
if !cmd.SkipReadOnlyCheck && query.Result.ReadOnly {
33+
return ErrSourceDataSourceReadOnly
34+
}
35+
36+
if err = s.DataSourceService.GetDataSource(ctx, &datasources.GetDataSourceQuery{
37+
OrgId: cmd.OrgId,
38+
Uid: cmd.TargetUID,
39+
}); err != nil {
40+
return ErrTargetDataSourceDoesNotExists
41+
}
42+
43+
_, err = session.Insert(correlation)
44+
if err != nil {
45+
return err
46+
}
47+
48+
return nil
49+
})
50+
51+
if err != nil {
52+
return Correlation{}, err
53+
}
54+
55+
return correlation, nil
56+
}
57+
58+
func (s CorrelationsService) deleteCorrelationsBySourceUID(ctx context.Context, cmd DeleteCorrelationsBySourceUIDCommand) error {
59+
return s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error {
60+
_, err := session.Delete(&Correlation{SourceUID: cmd.SourceUID})
61+
return err
62+
})
63+
}
64+
65+
func (s CorrelationsService) deleteCorrelationsByTargetUID(ctx context.Context, cmd DeleteCorrelationsByTargetUIDCommand) error {
66+
return s.SQLStore.WithDbSession(ctx, func(session *sqlstore.DBSession) error {
67+
_, err := session.Delete(&Correlation{TargetUID: cmd.TargetUID})
68+
return err
69+
})
70+
}

0 commit comments

Comments
 (0)