Skip to main content

Migrate plugins from Grafana 9.3.x to 9.4.x

Follow these instructions to migrate from Grafana 9.3.x to 9.4.x.

New navigation layout is supported​

First, enable the topnav feature flag in custom.ini to check how your plugin renders in the new navigation layout:

[feature_toggles]
enable = topnav

Migrate from onNavChanged​

If your plugin uses the onNavChanged callback to inform Grafana of its navigation model and child pages, you should see that this results in duplicated navigation elements. If you disable topnav, then it should look just like before.

If topnav is enabled, then we need to update the plugin to take advantage of the new PluginPage component. In this case, we do not call onNavChanged, which is now deprecated.

Switch to PluginPage component​

Grafana now exposes a new PluginPage component from @grafana/runtime that hooks into the new navigation and page layouts. This new component also supports the old page layouts when the topnav feature is disabled.

The new PluginPage component will also handle rendering the section navigation. The section navigation can include other core sections and other plugins. To control what pages are displayed in the section navigation for a specific plugin, Grafana uses the pages that have been added in plugin.json in which addToNav was set to true.

To use this component, simply wrap it around your page content:

import { PluginPage } from '@grafana/runtime';

...

return (
<PluginPage>
{your page content here}
</PluginPage>
);

Grafana looks at the URL to know what plugin and page should be active in the section nav. Accordingly, this component only works for pages that you have specified in plugin.json. The PluginPage will then render a page header based on the page name specified in plugin.json.

Use PluginPage for pages not defined in plugin.json​

The PluginPage component also exposes a pageNav property that is a NavModelItem. This pageNav property is useful for pages that are not defined in plugin.json (for example, individual item pages). The text and description that you specify in the pageNav model are used to populate the breadcrumbs and the page header.

Example:

const pageNav = {
text: 'Write errors cortex-prod-04',
description: 'Incident timeline and details'
};

return (
<PluginPage pageNav={pageNav}>
{your page content here}
</PluginPage>
);

The way the active page is matched in the breadcrumbs and section nav relies on the page routes being hierarchical. If you have a list page and an item page, then you need to make the item page into a subroute of the list page. Furthermore, you also need to specify the list page URL in your plugin.json.

For example, you might have a list of users at /users. This means that the item page for a specific user needs to be at /users/:id. This may require some refactoring of your routes.

Use PluginPage with tabs​

You can also create a further layer of hierarchy by specifying children in the pageNav model to create a page with tabbed navigation.

Example:

const pageNav = {
text: 'My page',
description: 'Incident timeline and details',
url: '/a/myorgid-pluginname-app',
children: [
{
url: '/a/myorgid-pluginname-app/tab1',
text: 'Tab1',
active: true,
},
{
url: '/a/myorgid-pluginname-app/tab2',
text: 'Tab1',
},
],
};

return (
<PluginPage pageNav={pageNav}>
{your page content here}
</PluginPage>
);

Use PluginPage in a backwards-compatible way​

If you want to maintain backwards-compatibility with older versions of Grafana, one way is to implement a PluginPage wrapper. If PluginPage is available and the topnav feature is enabled, then use the real PluginPage. In other scenarios, fall back to whatever each plugin is doing today (such as making a call to onNavChanged).

Example:

import { PluginPageProps, PluginPage as RealPluginPage, config } from '@grafana/runtime';

export const PluginPage = RealPluginPage && config.featureToggles.topnav ? RealPluginPage : PluginPageFallback;

function PluginPageFallback(props: PluginPageProps) {
return props.children;
}

There’s an additional step (and if block) that is needed to hide or show tabs depending on whether config.features.topnav is true. Your plugin needs to include these changes in the useNavModel.ts file:

// useNavModel.ts

import { config } from '@grafana/runtime';

...

export function useNavModel({ meta, rootPath, onNavChanged }: Args) {
const { pathname, search } = useLocation();
useEffect(() => {
if (config.featureToggles.topnav) {
return;
}
}, [config]);

...

Forwarded HTTP headers in the plugin SDK for Go​

We recommended to use the <request>.GetHTTPHeader or <request>.GetHTTPHeaders methods when retrieving forwarded HTTP headers. See Forward OAuth identity for the logged-in user, Forward cookies for the logged-in user or Forward user header for the logged-in user for example usages.

Technical details​

The Grafana SDK for Go v0.147.0 introduces a new interface ForwardHTTPHeaders that QueryDataRequest, CheckHealthRequest and CallResourceRequest implements.

Newly introduced forwarded HTTP headers in Grafana v9.4.0 are X-Grafana-User, X-Panel-Id, X-Dashboard-Uid, X-Datasource-Uid and X-Grafana-Org-Id. Internally, we prefix these with http_ and they are sent as http_<HTTP header name> in CheckHealthRequest.Headers and QueryDataRequest.Headers.

We recommend using the ForwardHTTPHeaders methods so that you're guaranteed to be able to operate on HTTP headers without using the prefix. That is, you can operate on X-Grafana-User, X-Panel-Id, X-Dashboard-Uid, X-Datasource-Uid and X-Grafana-Org-Id.