Add resource handler for data source plugins
You can add a resource handler to your data source backend to extend the Grafana HTTP API with your own data source-specific routes. This guide explains why you may want to add resource handlers and some common ways for doing so.
Uses of resource handlers
The primary way for a data source to retrieve data from a backend is through the query method. But sometimes your data source needs to request data on demand; for example, to offer auto-completion automatically inside the data source’s query editor.
Resource handlers are also useful for building control panels that allow the user to write back to the data source. For example, you could add a resource handler to update the state of an IoT device.
Implement the resource handler interface
To add a resource handler to your backend plugin, you need to implement the backend.CallResourceHandler
interface for your data source struct.
func (d *MyDatasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: []byte("Hello, world!"),
})
}
You can then access your resources through the following endpoint: http://<GRAFANA_HOSTNAME>:<PORT>/api/datasources/uid/<DATASOURCE_UID>/resources
In this example code, DATASOURCE_UID
is the data source unique identifier (UID) that uniquely identifies your data source.
To verify the data source UID, you can enter window.grafanaBootData.settings.datasources
in your browser's developer tools console, to list all the configured data sources in your Grafana instance.
Add support for multiple routes
To support multiple routes in your data source plugin, you can use a switch with the req.Path
:
func (d *MyDatasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
switch req.Path {
case "namespaces":
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: []byte(`{ namespaces: ["ns-1", "ns-2"] }`),
})
case "projects":
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Body: []byte(`{ projects: ["project-1", "project-2"] }`),
})
default:
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusNotFound,
})
}
}
You can also query your resources using the getResource
and postResource
helpers from the DataSourceWithBackend
class.
For example, in your query editor component, you can access the data source instance from the props
object:
const namespaces = await props.datasource.getResource('namespaces');
props.datasource.postResource('device', { state: "on" });
Advanced use cases
If you have some more advanced use cases or want to use a more Go-agnostic approach for handling resources, you can use the regular http.Handler
. You can do so by using a package provided by the Grafana Plugin SDK for Go named httpadapter
. This package provides support for handling resource calls using an http.Handler
.
Using http.Handler
allows you to also use Go’s built-in router functionality called ServeMux
or your preferred HTTP router library (for example, gorilla/mux
).
An alternative to using the CallResource
method shown in the above example is to use httpadapter
and ServeMux
as shown below:
package mydatasource
import (
"context"
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
)
type MyDatasource struct {
resourceHandler backend.CallResourceHandler
}
func New() *MyDatasource {
ds := &MyDatasource{}
mux := http.NewServeMux()
mux.HandleFunc("/namespaces", ds.handleNamespaces)
mux.HandleFunc("/projects", ds.handleProjects)
ds.resourceHandler := httpadapter.New(mux)
return ds
}
func (d *MyDatasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return d.resourceHandler.CallResource(ctx, req)
}
func (d *MyDatasource) handleNamespaces(rw http.ResponseWriter, req *http.Request) {
_, err := rw.Write([]byte(`{ namespaces: ["ns-1", "ns-2"] }`))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}
func (d *MyDatasource) handleProjects(rw http.ResponseWriter, req *http.Request) {
_, err := rw.Write([]byte(`{ projects: ["project-1", "project-2"] }`))
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}
Using some other HTTP router library with above example should be straightforward. Just replace the use of ServeMux
with another router.
What if you need access to the backend plugin context?
Use the PluginConfigFromContext
function to access backend.PluginContext
:
func (d *MyDatasource) handleNamespaces(rw http.ResponseWriter, req *http.Request) {
pCtx := httpadapter.PluginConfigFromContext(req.Context())
bytes, err := json.Marshal(pCtx.User)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
}
_, err := rw.Write(bytes)
if err != nil {
return
}
rw.WriteHeader(http.StatusOK)
}
Additional examples
Some other examples of using resource handlers and the httpadapter
package:
- The datasource-basic example:
- create resource handler and register routes in the backend.
- fetch and populate query types in a drop-down in the query editor component in the frontend. Fetching is done in a separate function which calls the getAvailableQueryTypes function of the datasource.
- Grafana's built-in TestData datasource, create resource handler and register routes.