How to make user-configurable HTTP requests from your data source plugin

If your data source retrieves data from an official, well-defined API, you can simplify your data source configuration by only asking for the relevant data, such as the API token. However, if the user should be able to access data from any given HTTP endpoint, you probably want to let them configure things like authentication headers and query parameters to fit their needs. In this post, I’ll explain how you can build a configurable HTTP-based backend data source.

Note: This post requires version 0.106.0 or greater of Grafana SDK for Go.

Creating a config editor for all possible HTTP options is a lot work. Instead of doing it yourself, consider using the DataSourceHTTPSettings component from the @grafana/ui package:

import { DataSourceHttpSettings } from '@grafana/ui';
<DataSourceHttpSettings 
  defaultUrl="http://localhost:8080" 
  dataSourceConfig={options} 
  onChange={onOptionsChange} 
/>

In the following example, you can see how this can simplify your config editor to a few lines of code:

import React from 'react';
import { DataSourceHttpSettings } from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps } from '@grafana/data';
import { MyDataSourceOptions } from './types';

interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions> {}

export const ConfigEditor = ({ options, onOptionsChange }: Props) => {
  return (
    <DataSourceHttpSettings defaultUrl="http://localhost:8080" dataSourceConfig={options} onChange={onOptionsChange} />
  );
};

Here’s what your users will see when they configure your data source:

The DataSourceHttpSettings component lets the user configure the properties in the instance settings, including the jsonData, and makes it available to our backend. Pretty neat!

The next step is to actually use the user-defined HTTP configuration when you create the requests on the backend. The httpclient package from the Go SDK is a useful wrapper that lets us construct a http.Client based on the data source instance settings:

package plugin

import (
	"context"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
	"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
)

type SampleDatasource struct {
	settings   backend.DataSourceInstanceSettings
	httpClient *http.Client
}

func NewSampleDatasource(settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
	httpOptions, err := settings.HTTPClientOptions()
	if err != nil {
		return nil, err
	}

	client, err := httpclient.New(httpOptions)
	if err != nil {
		return nil, err
	}

	return &SampleDatasource{
		settings:   settings,
		httpClient: client,
	}, nil
}

Note: By adding the http.Client in the data source struct, we can reuse the client object across queries. This lets the HTTP client reuse open connections rather than having to create new ones for each query.

For example, here’s how you can use the HTTP client to perform a data source health check:

func (d *SampleDatasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
	httpReq, err := http.NewRequestWithContext(ctx, "GET", d.settings.URL, nil)
	if err != nil {
		return nil, err
	}

	resp, err := d.httpClient.Do(httpReq)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		return &backend.CheckHealthResult{
			Status:  backend.HealthStatusError,
			Message: "Something went wrong: " + resp.Status,
		}, nil
	}

	return &backend.CheckHealthResult{
		Status:  backend.HealthStatusOk,
		Message: "Success",
	}, nil
}

Combine the DataSourceHttpSettings component in @grafana/ui together with the httpclient from the Go SDK to effortlessly configure and make API requests based on user-defined HTTP configuration.

5 Likes