The state of React in Grafana
Grafana started to migrate from AngularJS to React in late 2018.This migration changes the development process and introduces new features for plugins.
This post describes a way to create a plugin using grafana-plugin-template-webpack and similar templates provided by CorpGlory.
If you want to create a new React plugin or update an existing one
- It's easier to write code on React
- Grafana will provide React components which you can use in your code
- You need to upgrade your plugin eventually because Grafana breaks compatibility
- You can get new UI elements in your plugins from the new Grafana React library
CorpGlory background in developing Grafana plugins
We developed grafana-plugin-template-webpack
and we are pioneers in developing plugins for Grafana on Webpack.
We made types-grafana and were first
who tried to make Grafana plugins on TypeScript. We provided consulting for Grafana Labs where we were responsible for developing
premium plugins.
We really glad that Grafana made a gread job in improving ecosystem,
but this post describes an alternative way of developing plugins
based on our react-typescript template.
We want to give the alternative because it gives more control over your software: control of how you build and test plugin, as well as it's dependencies.
About React and TypeScript
First time Grafana Labs announced React in Grafana at GrafanaCon 2018 AMS, talk statements:
- It's easier to find developers
- higher development speed
- React powers grid system for panels (so height and width of panel work different)
React components let you split the UI into independent, reusable pieces.
Of course, there is a reason to use TypeScript. We recommend a talk of
Node.js about why TypeScript is awesome.
About Grafana plugins
Types of Grafana plugins:
- panel — visualization plugin
- datasource — UI and logic for querying your datasource
- application — panels + datasources + dashboards + pages
Main differences between AngularJS and React plugins
@grafana/ui
@grafana/ui is a collection of React-components and types for React-plugins development used by Grafana.
@grafana/ui
is necessary for React plugins development. It has all the types you may need to develop a React plugin.
@grafana/toolkit is in a sense a wrapper around webpack-plugin-template with the following commands:
grafana-toolkit plugin:create
- creates plugin template
grafana-toolkit plugin:build
- compiles the plugin into bundle in dist/
directory
grafana-toolkit plugin:dev
- runs development process in watch
mode
grafana-toolkit plugin:test
- runs Jest tests for a plugin
The same commands in our templates:
npm run build
npm run dev
npm run test
@grafana/toolkit
is a convenient way to build plugins but it's not flexible:
@grafana/toolkit
can be used if you don't need to customize the build process and need built-in features such as:
@grafana/runtime
Internal Grafana services are moved into @grafana/runtime instead of being injected by Angular.
For example:
Before:
class MyPanelCtrl extends MetricsPanelCtrl {
constructor(private backendSrv) { }
}
After:
import { getBackendSrv } from '@grafana/runtime';
const backendSrv = getBackendSrv();
Datasource plugins
React datasources are supported since Grafana 6.3.3 (you'll get Object prototype may only be an Object or null: undefined
error in Grafana < 6.3.3).
Observable
Datasource query()
method can either return Promise<result>
or Observable<result>
.
Observables help you to stream data from your datasource plugin. Streaming data is a great way to reduce your backend / network load. You don't have to create a separate network connection for each query.
Streaming datasource example: https://github.com/seanlaff/simple-streaming-datasource.
Example of Promise
-> Observable
conversion: https://github.com/grafana/grafana/pull/19037.
Application plugins
React support for apps was introduced in Grafana 6.3.3.
There are new types of application components:
- root page
- tabs in app-config (config pages)
Application config can still be written only in Angular.
See React app example.
Explore
There is a new feature introduced in Grafana 6.0 called Explore. Explore can be used for datasource debugging and data exploration.
React datasources can be used in Explore. There is a setExploreLogsQueryField
method in DataSourcePlugin
class for this purpose.
PR which adds Explore support for ElasticSearch datasource: https://github.com/grafana/grafana/pull/17605.
Explore: graph visualisation
Explore: table visualisation
React components
React components are files with .tsx
extension.
Conceptually, components are like JavaScript functions. They accept arbitrary inputs (called “props”) and return React elements describing what should appear on the screen.
More info about components here.
React components can be defined by subclassing React.Component
or React.PureComponent
. Read more about difference between React.Component
or React.PureComponent
here.
Import React
To be able to import React
import React, { PureComponent } from 'react';
tsconfig.json
should contain follow:
"compilerOptions": {
"allowSyntheticDefaultImports": true
}
Render function
It's not the render function you know from Angular-way development.
Now it's React's standart render function.
render
should return only one html tag, one div
for example:
render() {
return (
<div>
some html inner code
</div>
);
}
PanelOptions
Work with panel options is different now.
React panels don't have direct access to panel.json
so you should specify panel options you're going to store.
PanelOptions
is passed as a PanelProps
generic argument. Grafana will pass options from editor to your panel component as props.
export const plugin = new PanelPlugin<MyPanelOptions>(MyPanel)
.setDefaults(defaults)
.setEditor(MyPanelEditor);
Options usage example:
export interface MyPanelOptions {
someText: string;
}
export class MyPanel extends PureComponent<PanelProps<MyPanelOptions>> {
render() {
const { options } = this.props;
return (
<div>
Text from editor: { options.someText }
</div>
);
}
}
PanelEditor
PanelEditor
is the separate component which represents "Visualization" tab and is used for configuring a panel plugin.
Panel editor example:
export class GaugePanelEditor extends PureComponent<PanelEditorProps<GaugeOptions>> {
labelWidth = 6;
render() {
const { options, onChange } = this.props;
return (
<PanelOptionsGrid>
<PanelOptionsGroup title="Some options">
<Input
className="gf-form-input width-5"
type="text"
value={options.someText}
placeholder="Enter some text"
onChange={event => {
onChange({
...options
});
}}
/>
</PanelOptionsGroup>
</PanelOptionsGrid>
);
}
}