The primary way for a data source to retrieve data is through the query method. But sometimes your data source needs to request auxiliary data on demand, for example to offer auto-completion inside the data source’s query editor.
In this post, I want to show you how to add a resource handler to your data source. By adding a resource handler to your backend plugin, you can extend the Grafana HTTP API with your own data source-specific routes.
Resource handler are great, not only for auto-completion, but for building control panels where the user should be able to write back to the data source. For example, to update the state of an IoT device.
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:
https://localhost:3000/api/datasources/<DATASOURCE_ID>/resources/
DATASOURCE_ID
is an integer that uniquely identifies your data source.
Pro tip: To verify the data source ID, you can enter
window.grafanaBootData.settings.datasources
in the Developer Console, to list the data source definitions in your Grafana instance.
To support multiple routes, 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,
})
}
}
With this, your plugin now has its own REST API that you can query from your query editor, using BackendSrv.fetch()
:
const observable = getBackendSrv()
.fetch({
url: `/api/datasources/${props.datasource.id}/resources/namespaces`,
});
const response = await lastValueFrom(observable);
props.datasource.id
gives you the data source ID of the query that’s being edited.
For more information on how to use BackendSrv.fetch()
, check out my previous post.
Adding resource handlers to your backend plugins opens up more opportunities to make your plugin more dynamic.
Are you using resource handlers already, let me know what you’re using them for!
Update (2022-01-17):
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" });