Guiding panel users with a wizard

In Data frame design: Basics, I stressed the importance to document the data response that your panel plugin expects from the data source. While you can document it in your README, that would mean the user needs to switch to another tab to understand how to use your panel. In this post, I’ll share a different approach, that lets the user configure the query without leaving Grafana.

Data sources produce data and panels consume it. Since panels can’t request data they need themselves, it needs to be passed to them by the data source. Some visualizations, like the Table, can visualize any data response, but many have specific requirements on what fields the data response should include.

This means it’s up to the user to craft a query that produces the data the panel expects—a source of confusion for many users. Even if the panel documents the data format it expects, it’s not always easy to understand what the data source actually produces.

For example, if a panel expects a Time field with Unix timestamps, the data source might return a Number field with the same timestamps. The user would look at the response and see that they’re indeed sending timestamps, and conclude that the panel isn’t working.

Introducing the PanelWizard

Instead of relying on external documentation, what if you could tell the user what’s missing? I created the PanelWizard to solve this problem. Let me show you how it works!

When you start configuring one of my panel plugins, such as Treemap, Hourly heatmap, Gantt, or the Hexmap, you’ll see something like this:

From the example above, you can tell that the panel needs your query to return three fields of type String, Time, and Time. You can also tell that the query already returns one Time field.

Note that while the query may return 20 other fields, all the panel cares about is that the data response contains at least these three fields.

Here’s what the user sees:

  • Empty circle for missing fields.
  • Circle with checkmark for present fields.
  • Label with the type of the required field.
  • Description of what the panel uses the field for.
  • A link to external documentation.

Any changes the user makes to their query are reflected in the wizard. See below how the wizard updates whenever the query changes (using the Static data source):


Displaying errors: You may wonder why I don’t display the wizard as an error. In general, I try to reserve the top-left error triangle for exceptional errors. During normal use, the user should never see it. For example, if a user wants to visualize a time series, but the data response doesn’t contain a Time field, it’s not really an error. They just haven’t configured the panel yet. Instead of slapping the user on the wrist, I prefer to give them actionable suggestions on what to do next.

How to use it

To add the panel wizard in your plugin, copy the source code into your plugin project. I won’t dive into the source code for the wizard in this post, and instead focus on how to use it. If you make any changes to the original implementation, feel free to share them in this post so that others can benefit as well!

To use the panel wizard, create an object that contains the basic usage information. Since you might validate at multiple locations, this lets you reuse the schema and url props, which tend to stay the same.

const usage = {
  schema: [
    { type: FieldType.string, description: 'Task name' },
    { type: FieldType.time, description: 'Task start time' },
    { type: FieldType.time, description: 'Task end time' },
  url: '',
  • schema is an array with the type and purpose of each required field.
  • url lets the user discover your external documentation if they get stuck.

To display the wizard when the data source hasn’t returned a valid data frame:

  if (!frame) {
    return (
      <div style={{ width, height, overflow: 'auto' }}>
        <PanelWizard {...usage} />

Even if it’s just a glorified info box at this point, it gives a more consistent experience for when the user hasn’t yet configure a query that we can validate.

To check the present fields, pass frame.fields as a fields prop:

  if (!textField || !startField || !endField) {
    return (
      <div style={{ width, height, overflow: 'auto' }}>
        <PanelWizard {...usage} fields={frame.fields} />

Note that you still need to perform the actual validation to determine whether to display the wizard or not.

What’s next?

The panel wizard only works for simple use cases at this point, but I see potential in expanding it to support more advanced data formats.

  • Optional fields: Since the wizard doesn’t handle validation—it only displays the fields that are present—you can support optional fields by adding “(Optional)” to the description. But it would be nice if you can specify fields as optional in the schema prop.

  • Multi-frame responses: The wizard doesn’t support multi-frame formats very well, i.e. when the data prop contains more than one data frame. None of my panels support multi-frame data, so I haven’t explored this yet, but I’d be interested in supporting it.

I’ve found that the panel wizard maps well with panel dimensions, and I wonder if there’s a way to combine the panel dimensions pattern with the panel wizard for a more declarative and generic panel configuration.

I’d love to hear what you’re doing to help your panel users. How are you documenting your panel requirements?