From 56e2f537b291d5393b7aaa442e19a7362efafd54 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Tue, 25 Nov 2025 14:32:49 +0100 Subject: [PATCH 01/20] wip --- packages/create-plugin/templates/datasource/AGENTS.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 packages/create-plugin/templates/datasource/AGENTS.md diff --git a/packages/create-plugin/templates/datasource/AGENTS.md b/packages/create-plugin/templates/datasource/AGENTS.md new file mode 100644 index 0000000000..9131eeca06 --- /dev/null +++ b/packages/create-plugin/templates/datasource/AGENTS.md @@ -0,0 +1,10 @@ +--- +name: datasource-plugin-agent +description: Develops Grafana datasource plugins +--- + +## Project knowledge + +This repository contains a **Grafana datasource plugin**. Follow the [instructions](./.config/AGENTS/instructions.md) before doing any changes. + +All build, lint, test, and Docker dev-server commands are documented in the "Getting started" section of [README.md](./README.md). Prefer running the non-watch versions of these commands. From 43c0bc2e4f5ac33b4a95deb60b919f6ba173d53e Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Tue, 25 Nov 2025 16:19:06 +0100 Subject: [PATCH 02/20] wip --- .../datasource/.config/AGENTS/instructions.md | 86 +++++++++++++++++++ .../panel/.config/AGENTS/instructions.md | 5 -- 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md new file mode 100644 index 0000000000..c5a4b50979 --- /dev/null +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -0,0 +1,86 @@ +You are an expert Grafana datasource plugin developer for this project. + +## Your role + +- You are fluent in TypeScript and React (frontend) +- You are fluent in Go (backend) +- You know how to use Grafana dashboards + +## Project knowledge + +This repository contains a **Grafana datasource plugin**, providing a custom visualization for Grafana dashboards. +Panel plugins are used to: + +- Display data from Grafana data sources in custom ways +- Add interactive behavior (drill-downs, navigation, etc.) +- Visualize or control external systems (IoT, integrations, custom controls) + +### Plugin anatomy + +A typical datasource with backend plugin includes: + +**plugin.json** + +- Declares plugin ID, type (`datasource`), name, version +- Loaded by Grafana at startup + +**Main module (`src/module.ts`)** + +- Exports: `new PanelPlugin(PanelComponent)` +- Registers panel options, migrations, defaults, ui extensions + +**Panel component (`src/components/Panel.tsx`)** + +- React component receiving: `data`, `timeRange`, `width`, `height`, `options` +- Renders visualization using Grafana data frames and field configs + +### Repository layout + +- `plugin.json` — Datasource plugin manifest +- `src/*` - Frontend part of plugin +- `src/module.ts` — Plugin entry (frontend) +- `src/datasource.ts` - Datasource implementation +- `src/components/` — Datasource React components +- `src/components/QueryEditor.tsx` — UI for building queries +- `src/components/ConfigEditor.tsx` — UI for datasource config +- `src/types.ts` — Query and model types +- `tests/` — E2E tests (if present) +- `provisioning/` — Local development provisioning +- `README.md` — Human documentation +- `pkg/*` - Backend part of plugin +- `pkg/main.go` - Plugin entry (backend) +- `pkg/datasource.go` - Datasource implementation + +## Coding guidelines + +- Use **TypeScript** (in strict mode), functional React components, and idiomatic patterns +- Use **@grafana/ui**, **@grafana/data**, **@grafana/runtime** +- Use **`useTheme2()`** for all colors, spacing, typography +- **Never hardcode** colors, spacing, padding, or font sizes +- Use **Emotion** + `useStyles2()` + theme tokens for styling +- Keep layouts responsive (use `width`/`height`) +- Avoid new dependencies unless necessary + Grafana-compatible +- Maintain consistent file structure and predictable types +- Use **`@grafana/plugin-e2e`** for E2E tests and **always use versioned selectors** to interact with the Grafana UI. + +## Boundaries + +You must **NOT**: + +- Change plugin ID or plugin type in `plugin.json` +- Modify anything inside `.config/*` +- Add a backend (panel plugins = frontend only) +- Remove/change existing options without a migration handler +- Break public APIs (options, field configs, panel props) +- Store, read, or handle credentials + +You **SHOULD**: + +- Maintain backward compatibility +- Preserve option schema unless migration handler is added +- Follow official Grafana panel plugin patterns +- Use idiomatic React + TypeScript + +## Instructions for specific tasks + +- [Add panel options](./tasks/add-panel-options.md) diff --git a/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md b/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md index 3e07dd0727..a75789bc45 100644 --- a/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md @@ -1,8 +1,3 @@ ---- -name: panel-plugin-agent-fundamentals -description: Develops Grafana panel plugins ---- - You are an expert Grafana panel plugin developer for this project. ## Your role From 1718e20aa1979448014419d311f37516245ad7eb Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Wed, 26 Nov 2025 16:04:08 +0100 Subject: [PATCH 03/20] wip --- .../datasource/.config/AGENTS/instructions.md | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index c5a4b50979..8806221a5b 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -5,15 +5,12 @@ You are an expert Grafana datasource plugin developer for this project. - You are fluent in TypeScript and React (frontend) - You are fluent in Go (backend) - You know how to use Grafana dashboards +- You know how to setup and manage Grafana datasources ## Project knowledge -This repository contains a **Grafana datasource plugin**, providing a custom visualization for Grafana dashboards. -Panel plugins are used to: - -- Display data from Grafana data sources in custom ways -- Add interactive behavior (drill-downs, navigation, etc.) -- Visualize or control external systems (IoT, integrations, custom controls) +This repository contains a **Grafana datasource plugin**, providing a custom datasource for Grafana. +Datasource plugins are used to fetch and query data from external systems. ### Plugin anatomy @@ -26,30 +23,36 @@ A typical datasource with backend plugin includes: **Main module (`src/module.ts`)** -- Exports: `new PanelPlugin(PanelComponent)` -- Registers panel options, migrations, defaults, ui extensions +- Exports: `new DataSourcePlugin(DataSource)` +- Registers query editor, config editor. + +**Data source (`src/datasource.ts`)** + +- Defines the class that extends DataSourceWithBackend. +- Connects the UI to the backend, provides the default query, applies template variables, filters queries, and sends them to the Go backend for execution. -**Panel component (`src/components/Panel.tsx`)** +**Query editor (`src/QueryEditor.tsx`)** -- React component receiving: `data`, `timeRange`, `width`, `height`, `options` -- Renders visualization using Grafana data frames and field configs +**Config editor (`src/ConfigEditor.tsx`)** ### Repository layout -- `plugin.json` — Datasource plugin manifest -- `src/*` - Frontend part of plugin -- `src/module.ts` — Plugin entry (frontend) +- `src/` - Frontend (TypeScript/React) +- `src/plugin.json` — Plugin manifest (metadata) +- `src/module.ts` — Frontend entry point - `src/datasource.ts` - Datasource implementation -- `src/components/` — Datasource React components -- `src/components/QueryEditor.tsx` — UI for building queries -- `src/components/ConfigEditor.tsx` — UI for datasource config -- `src/types.ts` — Query and model types +- `src/components/QueryEditor.tsx` — Query builder UI +- `src/components/ConfigEditor.tsx` — Data source settings UI +- `src/types.ts` — Shared frontend models - `tests/` — E2E tests (if present) - `provisioning/` — Local development provisioning - `README.md` — Human documentation -- `pkg/*` - Backend part of plugin -- `pkg/main.go` - Plugin entry (backend) +- `pkg/` - Backend (Go) +- `pkg/main.go` - Backend entry point - `pkg/datasource.go` - Datasource implementation +- `pkg/models.go`- Query/response models +- `Magefile.go` - Backend build tasks +- `package.json` - Frontend build scripts + deps ## Coding guidelines From 68e9c0e7ec215fded19b514966fe2ea2b211e8df Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 27 Nov 2025 10:48:30 +0100 Subject: [PATCH 04/20] wip --- .../datasource/.config/AGENTS/instructions.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 8806221a5b..0e8e22f8b1 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -9,7 +9,7 @@ You are an expert Grafana datasource plugin developer for this project. ## Project knowledge -This repository contains a **Grafana datasource plugin**, providing a custom datasource for Grafana. +This repository contains a **Grafana datasource**, providing a custom datasource for Grafana. Datasource plugins are used to fetch and query data from external systems. ### Plugin anatomy @@ -29,12 +29,17 @@ A typical datasource with backend plugin includes: **Data source (`src/datasource.ts`)** - Defines the class that extends DataSourceWithBackend. -- Connects the UI to the backend, provides the default query, applies template variables, filters queries, and sends them to the Go backend for execution. +- Connects the UI to the backend, provides the default query, applies template variables, filters queries, and sends them to the Go backend for execution **Query editor (`src/QueryEditor.tsx`)** +- React component where users build and customize queries that will be sent to the data source + **Config editor (`src/ConfigEditor.tsx`)** +- React component where users manage and configure a data source instance +- Configures instance specific settings (like URLs or credentials) + ### Repository layout - `src/` - Frontend (TypeScript/React) @@ -72,18 +77,14 @@ You must **NOT**: - Change plugin ID or plugin type in `plugin.json` - Modify anything inside `.config/*` -- Add a backend (panel plugins = frontend only) -- Remove/change existing options without a migration handler -- Break public APIs (options, field configs, panel props) -- Store, read, or handle credentials +- Remove/change existing query model without a migration handler +- Break public APIs (query model) You **SHOULD**: - Maintain backward compatibility -- Preserve option schema unless migration handler is added -- Follow official Grafana panel plugin patterns +- Preserve query model schema unless migration handler is added +- Follow official Grafana datasource plugin patterns - Use idiomatic React + TypeScript ## Instructions for specific tasks - -- [Add panel options](./tasks/add-panel-options.md) From 40503a18e6ecb97698bcc5f5d37cb09a91c9c512 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 27 Nov 2025 10:58:22 +0100 Subject: [PATCH 05/20] wip --- .../templates/datasource/.config/AGENTS/instructions.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 0e8e22f8b1..f99d6a0713 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -40,6 +40,12 @@ A typical datasource with backend plugin includes: - React component where users manage and configure a data source instance - Configures instance specific settings (like URLs or credentials) +**Main module (`pkg/main.go`)** + +- Register a factory function with `grafana-plugin-sdk-go` to create datasource backend instances. + +**Data source (`pkg/plugin/datasource.go`)** + ### Repository layout - `src/` - Frontend (TypeScript/React) @@ -54,8 +60,7 @@ A typical datasource with backend plugin includes: - `README.md` — Human documentation - `pkg/` - Backend (Go) - `pkg/main.go` - Backend entry point -- `pkg/datasource.go` - Datasource implementation -- `pkg/models.go`- Query/response models +- `pkg/plugin/datasource.go` - Datasource implementation - `Magefile.go` - Backend build tasks - `package.json` - Frontend build scripts + deps From 670e59210d064430357a20938ba8af6cc4e86c32 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 27 Nov 2025 13:26:06 +0100 Subject: [PATCH 06/20] wip --- .../datasource/.config/AGENTS/instructions.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index f99d6a0713..3cdf555834 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -24,11 +24,11 @@ A typical datasource with backend plugin includes: **Main module (`src/module.ts`)** - Exports: `new DataSourcePlugin(DataSource)` -- Registers query editor, config editor. +- Registers query editor, config editor **Data source (`src/datasource.ts`)** -- Defines the class that extends DataSourceWithBackend. +- Frontend datasource that extends DataSourceWithBackend. - Connects the UI to the backend, provides the default query, applies template variables, filters queries, and sends them to the Go backend for execution **Query editor (`src/QueryEditor.tsx`)** @@ -42,10 +42,15 @@ A typical datasource with backend plugin includes: **Main module (`pkg/main.go`)** -- Register a factory function with `grafana-plugin-sdk-go` to create datasource backend instances. +- Register a factory function with `grafana-plugin-sdk-go` to create datasource backend instances **Data source (`pkg/plugin/datasource.go`)** +- Backend datasource that Implements QueryData (receives queries from frontend, unmarshals into queryModel, returns data frames) +- CheckHealth (validates API key from settings) +- Dispose (cleanup hook). +- NewDatasource factory called when Grafana starts instance of plugin + ### Repository layout - `src/` - Frontend (TypeScript/React) @@ -84,6 +89,9 @@ You must **NOT**: - Modify anything inside `.config/*` - Remove/change existing query model without a migration handler - Break public APIs (query model) +- Use the local file system +- Use environment variables +- Execute arbitrary code in the backend You **SHOULD**: @@ -91,5 +99,7 @@ You **SHOULD**: - Preserve query model schema unless migration handler is added - Follow official Grafana datasource plugin patterns - Use idiomatic React + TypeScript +- Use secureJsonData instead of jsonData for credentials and sensitive data +- Error happening should be logged with level `error` ## Instructions for specific tasks From bb6815abbdba2635465100e8d650d7d3f1f00a1c Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 27 Nov 2025 15:56:29 +0100 Subject: [PATCH 07/20] added claude redirect to agents.md --- packages/create-plugin/templates/common/CLAUDE.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/create-plugin/templates/common/CLAUDE.md diff --git a/packages/create-plugin/templates/common/CLAUDE.md b/packages/create-plugin/templates/common/CLAUDE.md new file mode 100644 index 0000000000..eef4bd20cf --- /dev/null +++ b/packages/create-plugin/templates/common/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md \ No newline at end of file From 7bb76e9a738e72f13cd812778e5e7d376bcf8a9e Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 27 Nov 2025 16:18:48 +0100 Subject: [PATCH 08/20] added task for adding template variable support. --- .../tasks/support-template-variables.md | 271 ++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md new file mode 100644 index 0000000000..0cce3b7027 --- /dev/null +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md @@ -0,0 +1,271 @@ +# Add Support for Template & Query Variables + +Variables are placeholders you can use to create templated queries and dynamic dashboards. In a data source, this means: + +- Using template variables inside query fields +- Implementing `metricFindQuery` and a `VariableQueryEditor` so the data source can be used as a **Query** type variable. + +Always complete **all three sections**: + +1. Interpolate template variables in queries +2. Support query variables via `metricFindQuery` +3. Add a `VariableQueryEditor` + +## 1. Interpolate Template Variables in Queries + +File: `src/datasource.ts` (or your data source class file) + +### 1.1 Import `getTemplateSrv` + +```ts +import { getTemplateSrv } from '@grafana/runtime'; +``` + +- `getTemplateSrv()` returns `TemplateSrv`, which exposes `replace()` for variable interpolation. + +### 1.2 Decide which fields support variables + +In your query model (e.g. `MyQuery`), identify all string fields that should accept `$var`: + +```ts +export interface MyQuery { + // existing… + rawQuery?: string; // e.g. SQL / text query + namespace?: string; // optional selector + // other fields… +} +``` + +Rules: + +- Only enable variables where they actually make sense (query strings, selectors, filters). +- Document which fields support variables if you have query editor help. + +### 1.3 Apply `replace()` in `query()` + +Inside your `DataSource` implementation: + +```ts +import { DataQueryRequest, DataQueryResponse } from '@grafana/schema'; + +export class DataSource extends DataSourceApi { + async query(options: DataQueryRequest): Promise { + const targets = options.targets.filter((t) => !t.hide); + + const interpolatedTargets = targets.map((target) => { + const rawQuery = getTemplateSrv().replace( + target.rawQuery ?? '', + options.scopedVars // include scoped vars for panel/time range + ); + + const namespace = getTemplateSrv().replace(target.namespace ?? '', options.scopedVars); + + return { + ...target, + rawQuery, + namespace, + }; + }); + + // Use interpolatedTargets when building your backend request. + return this.doRequest(interpolatedTargets, options); + } + + private async doRequest(targets: MyQuery[], options: DataQueryRequest): Promise { + // existing implementation… + } +} +``` + +- `replace(template, scopedVars?)` replaces `$var` with current values and supports built-ins like `$__from` / `$__to`. +- You can keep interpolation localized to **only** the fields that support variables (e.g. `rawQuery`, `namespace`). + +### 1.4 Handle multi-value variables (optional) + +For multi-value variables, choose a format that matches your backend (CSV, JSON array, etc.): + +```ts +const csvQuery = getTemplateSrv().replace( + target.rawQuery ?? '', + options.scopedVars, + 'csv' // built-in format option +); +``` + +Or provide a custom formatter: + +```ts +const formatter = (value: string | string[]): string => { + if (typeof value === 'string') { + return value; + } + + // Example: join with OR + if (value.length > 1) { + return '(' + value.map((v) => `"${v}"`).join(' OR ') + ')'; + } + + return value[0]; +}; + +const formattedQuery = getTemplateSrv().replace(target.rawQuery ?? '', options.scopedVars, formatter); +``` + +Rules: + +- Use a built-in format (`csv`, etc.) where possible. +- Use a custom interpolation function only if built-ins don’t match your protocol. + +## 2. Implement `metricFindQuery` for Query Variables + +File: `src/datasource.ts` + +A **query variable** lets Grafana call your data source to get variable values. To support this, you override `metricFindQuery` in your `DataSourceApi` implementation. + +### 2.1 Define a variable query model + +File: `src/types.ts` + +```ts +export interface MyVariableQuery { + namespace: string; + rawQuery: string; +} +``` + +- Keep this model minimal – only the fields needed to fetch variable values. +- This is separate from your “regular” `MyQuery` if that simplifies things. + +> If a plain text query is enough, you can leave `query` as `string` and skip the model entirely: +> `async metricFindQuery(query: string, options?: any)` + +### 2.2 Implement `metricFindQuery` + +File: `src/datasource.ts` + +```ts +import { MetricFindValue } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; +import { MyVariableQuery } from './types'; + +export class DataSource extends DataSourceApi { + // existing query()… + + async metricFindQuery(variableQuery: MyVariableQuery | string, options?: any): Promise { + if (typeof variableQuery === 'string') { + const interpolated = getTemplateSrv().replace(variableQuery); + const response = await this.fetchVariableValues({ rawQuery: interpolated }); + return response.map((name) => ({ text: name })); + } + + // If using MyVariableQuery model: + const namespace = getTemplateSrv().replace(variableQuery.namespace); + const rawQuery = getTemplateSrv().replace(variableQuery.rawQuery); + + const response = await this.fetchMetricNames(namespace, rawQuery); + + // Adapt this to match your backend response + return response.data.map((item: any) => ({ + text: item.name, + // optional: value: item.id, + })); + } + + private async fetchMetricNames(namespace: string, rawQuery: string) { + // call backend/API and return data in a consistent shape + } + + private async fetchVariableValues(args: { rawQuery: string }) { + // simplified variant if using a simple string-based query + } +} +``` + +Rules: + +- Return an array of `{ text: string }` (`MetricFindValue[]`). +- Use `getTemplateSrv().replace` inside `metricFindQuery` so variable queries can themselves use other variables (e.g. cascading variables). +- Keep queries lightweight – `metricFindQuery` can be called often by Grafana. + +## 3. Add a `VariableQueryEditor` + +If you use a structured `MyVariableQuery` model, add a small React editor so users can configure it from the variable UI. + +### 3.1 Create `VariableQueryEditor` + +File: `src/VariableQueryEditor.tsx` + +```tsx +import React, { useState } from 'react'; +import { MyVariableQuery } from './types'; + +interface VariableQueryProps { + query: MyVariableQuery; + onChange: (query: MyVariableQuery, definition: string) => void; +} + +export const VariableQueryEditor = ({ query, onChange }: VariableQueryProps) => { + const [state, setState] = useState(query); + + const saveQuery = () => { + // Second argument is the human-readable label shown in the variable list + const definition = `${state.rawQuery} (${state.namespace})`; + onChange(state, definition); + }; + + const handleChange = (event: React.FormEvent) => { + const { name, value } = event.currentTarget; + + const next = { + ...state, + [name]: value, + }; + + setState(next); + }; + + return ( + <> +
+ Namespace + +
+ +
+ Query + +
+ + ); +}; +``` + +### 3.2 Register `VariableQueryEditor` in the plugin + +File: `src/module.ts` + +```ts +import { DataSourcePlugin } from '@grafana/data'; +import { DataSource } from './datasource'; +import { QueryEditor } from './QueryEditor'; +import { VariableQueryEditor } from './VariableQueryEditor'; +import { MyQuery, MyDataSourceOptions, MyVariableQuery } from './types'; + +export const plugin = new DataSourcePlugin(DataSource) + .setQueryEditor(QueryEditor) + .setVariableQueryEditor(VariableQueryEditor); +``` + +- `setVariableQueryEditor` wires your editor into Grafana’s variable UI. From 57264925fb56b60022bb3a45172639242212ae88 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Fri, 28 Nov 2025 10:22:30 +0100 Subject: [PATCH 09/20] updated variable support. --- .../tasks/support-template-variables.md | 114 ++++++++++++------ 1 file changed, 75 insertions(+), 39 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md index 0cce3b7027..1502424e86 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md @@ -49,16 +49,23 @@ Inside your `DataSource` implementation: import { DataQueryRequest, DataQueryResponse } from '@grafana/schema'; export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + } + async query(options: DataQueryRequest): Promise { const targets = options.targets.filter((t) => !t.hide); const interpolatedTargets = targets.map((target) => { - const rawQuery = getTemplateSrv().replace( + const rawQuery = this.templateSrv.replace( target.rawQuery ?? '', options.scopedVars // include scoped vars for panel/time range ); - const namespace = getTemplateSrv().replace(target.namespace ?? '', options.scopedVars); + const namespace = this.templateSrv.replace(target.namespace ?? '', options.scopedVars); return { ...target, @@ -149,18 +156,18 @@ import { getTemplateSrv } from '@grafana/runtime'; import { MyVariableQuery } from './types'; export class DataSource extends DataSourceApi { - // existing query()… + // existing constructor, query()... async metricFindQuery(variableQuery: MyVariableQuery | string, options?: any): Promise { if (typeof variableQuery === 'string') { - const interpolated = getTemplateSrv().replace(variableQuery); + const interpolated = this.templateSrv.replace(variableQuery); const response = await this.fetchVariableValues({ rawQuery: interpolated }); return response.map((name) => ({ text: name })); } // If using MyVariableQuery model: - const namespace = getTemplateSrv().replace(variableQuery.namespace); - const rawQuery = getTemplateSrv().replace(variableQuery.rawQuery); + const namespace = this.templateSrv.replace(variableQuery.namespace); + const rawQuery = this.templateSrv.replace(variableQuery.rawQuery); const response = await this.fetchMetricNames(namespace, rawQuery); @@ -198,6 +205,7 @@ File: `src/VariableQueryEditor.tsx` ```tsx import React, { useState } from 'react'; import { MyVariableQuery } from './types'; +import { InlineField, InlineFieldRow, LoadingPlaceholder, Select } from '@grafana/ui'; interface VariableQueryProps { query: MyVariableQuery; @@ -226,46 +234,74 @@ export const VariableQueryEditor = ({ query, onChange }: VariableQueryProps) => return ( <> -
- Namespace - -
- -
- Query - -
+ + + + + + + + + + ); }; ``` -### 3.2 Register `VariableQueryEditor` in the plugin +### 3.2 Create a `VariableSupport` file for the plugin -File: `src/module.ts` +File: `src/variableSupport.ts` ```ts -import { DataSourcePlugin } from '@grafana/data'; -import { DataSource } from './datasource'; -import { QueryEditor } from './QueryEditor'; -import { VariableQueryEditor } from './VariableQueryEditor'; -import { MyQuery, MyDataSourceOptions, MyVariableQuery } from './types'; - -export const plugin = new DataSourcePlugin(DataSource) - .setQueryEditor(QueryEditor) - .setVariableQueryEditor(VariableQueryEditor); +export class MyVariableSupport extends CustomVariableSupport { + editor = VariableQueryEditor; + + constructor(private datasource: Datasource) { + super(); + } + + query(request: DataQueryRequest): Observable<{ data: MetricFindValue[] }> { + const [query] = request.targets; + const { range, scopedVars } = request; + + const result = this.datasource.metricFindQuery(query, { scopedVars, range }); + return from(result).pipe(map((data) => ({ data }))); + } +} ``` -- `setVariableQueryEditor` wires your editor into Grafana’s variable UI. +- `editor = VariableQueryEditor;` wires your editor into Grafana’s variable UI. + +### 3.3 Assign `VariableSupport` to `this.variables` in the datasource + +File: `src/datasource.ts` + +```ts +export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + // assign the variable property to the new VariableSupport + // to add variable support for this datasource + this.variables = new VariableSupport(this); + } + // existing functions()… +} +``` From d2b568d892b8350162b181239e5c6f1afe9b8260 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 18 Dec 2025 14:55:48 +0100 Subject: [PATCH 10/20] fixed nits. --- .../datasource/.config/AGENTS/instructions.md | 4 +- .../tasks/support-template-variables.md | 307 ------------------ 2 files changed, 2 insertions(+), 309 deletions(-) delete mode 100644 packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 3cdf555834..0d6ee69258 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -59,7 +59,7 @@ A typical datasource with backend plugin includes: - `src/datasource.ts` - Datasource implementation - `src/components/QueryEditor.tsx` — Query builder UI - `src/components/ConfigEditor.tsx` — Data source settings UI -- `src/types.ts` — Shared frontend models +- `src/types.ts` — Shared frontend types - `tests/` — E2E tests (if present) - `provisioning/` — Local development provisioning - `README.md` — Human documentation @@ -79,7 +79,7 @@ A typical datasource with backend plugin includes: - Keep layouts responsive (use `width`/`height`) - Avoid new dependencies unless necessary + Grafana-compatible - Maintain consistent file structure and predictable types -- Use **`@grafana/plugin-e2e`** for E2E tests and **always use versioned selectors** to interact with the Grafana UI. +- Use **`@grafana/plugin-e2e`** npm package for E2E tests and **always use versioned selectors** to interact with the Grafana UI. ## Boundaries diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md deleted file mode 100644 index 1502424e86..0000000000 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md +++ /dev/null @@ -1,307 +0,0 @@ -# Add Support for Template & Query Variables - -Variables are placeholders you can use to create templated queries and dynamic dashboards. In a data source, this means: - -- Using template variables inside query fields -- Implementing `metricFindQuery` and a `VariableQueryEditor` so the data source can be used as a **Query** type variable. - -Always complete **all three sections**: - -1. Interpolate template variables in queries -2. Support query variables via `metricFindQuery` -3. Add a `VariableQueryEditor` - -## 1. Interpolate Template Variables in Queries - -File: `src/datasource.ts` (or your data source class file) - -### 1.1 Import `getTemplateSrv` - -```ts -import { getTemplateSrv } from '@grafana/runtime'; -``` - -- `getTemplateSrv()` returns `TemplateSrv`, which exposes `replace()` for variable interpolation. - -### 1.2 Decide which fields support variables - -In your query model (e.g. `MyQuery`), identify all string fields that should accept `$var`: - -```ts -export interface MyQuery { - // existing… - rawQuery?: string; // e.g. SQL / text query - namespace?: string; // optional selector - // other fields… -} -``` - -Rules: - -- Only enable variables where they actually make sense (query strings, selectors, filters). -- Document which fields support variables if you have query editor help. - -### 1.3 Apply `replace()` in `query()` - -Inside your `DataSource` implementation: - -```ts -import { DataQueryRequest, DataQueryResponse } from '@grafana/schema'; - -export class DataSource extends DataSourceApi { - constructor( - instanceSettings: DataSourceInstanceSettings, - private readonly templateSrv: TemplateSrv = getTemplateSrv() - ) { - super(instanceSettings); - } - - async query(options: DataQueryRequest): Promise { - const targets = options.targets.filter((t) => !t.hide); - - const interpolatedTargets = targets.map((target) => { - const rawQuery = this.templateSrv.replace( - target.rawQuery ?? '', - options.scopedVars // include scoped vars for panel/time range - ); - - const namespace = this.templateSrv.replace(target.namespace ?? '', options.scopedVars); - - return { - ...target, - rawQuery, - namespace, - }; - }); - - // Use interpolatedTargets when building your backend request. - return this.doRequest(interpolatedTargets, options); - } - - private async doRequest(targets: MyQuery[], options: DataQueryRequest): Promise { - // existing implementation… - } -} -``` - -- `replace(template, scopedVars?)` replaces `$var` with current values and supports built-ins like `$__from` / `$__to`. -- You can keep interpolation localized to **only** the fields that support variables (e.g. `rawQuery`, `namespace`). - -### 1.4 Handle multi-value variables (optional) - -For multi-value variables, choose a format that matches your backend (CSV, JSON array, etc.): - -```ts -const csvQuery = getTemplateSrv().replace( - target.rawQuery ?? '', - options.scopedVars, - 'csv' // built-in format option -); -``` - -Or provide a custom formatter: - -```ts -const formatter = (value: string | string[]): string => { - if (typeof value === 'string') { - return value; - } - - // Example: join with OR - if (value.length > 1) { - return '(' + value.map((v) => `"${v}"`).join(' OR ') + ')'; - } - - return value[0]; -}; - -const formattedQuery = getTemplateSrv().replace(target.rawQuery ?? '', options.scopedVars, formatter); -``` - -Rules: - -- Use a built-in format (`csv`, etc.) where possible. -- Use a custom interpolation function only if built-ins don’t match your protocol. - -## 2. Implement `metricFindQuery` for Query Variables - -File: `src/datasource.ts` - -A **query variable** lets Grafana call your data source to get variable values. To support this, you override `metricFindQuery` in your `DataSourceApi` implementation. - -### 2.1 Define a variable query model - -File: `src/types.ts` - -```ts -export interface MyVariableQuery { - namespace: string; - rawQuery: string; -} -``` - -- Keep this model minimal – only the fields needed to fetch variable values. -- This is separate from your “regular” `MyQuery` if that simplifies things. - -> If a plain text query is enough, you can leave `query` as `string` and skip the model entirely: -> `async metricFindQuery(query: string, options?: any)` - -### 2.2 Implement `metricFindQuery` - -File: `src/datasource.ts` - -```ts -import { MetricFindValue } from '@grafana/data'; -import { getTemplateSrv } from '@grafana/runtime'; -import { MyVariableQuery } from './types'; - -export class DataSource extends DataSourceApi { - // existing constructor, query()... - - async metricFindQuery(variableQuery: MyVariableQuery | string, options?: any): Promise { - if (typeof variableQuery === 'string') { - const interpolated = this.templateSrv.replace(variableQuery); - const response = await this.fetchVariableValues({ rawQuery: interpolated }); - return response.map((name) => ({ text: name })); - } - - // If using MyVariableQuery model: - const namespace = this.templateSrv.replace(variableQuery.namespace); - const rawQuery = this.templateSrv.replace(variableQuery.rawQuery); - - const response = await this.fetchMetricNames(namespace, rawQuery); - - // Adapt this to match your backend response - return response.data.map((item: any) => ({ - text: item.name, - // optional: value: item.id, - })); - } - - private async fetchMetricNames(namespace: string, rawQuery: string) { - // call backend/API and return data in a consistent shape - } - - private async fetchVariableValues(args: { rawQuery: string }) { - // simplified variant if using a simple string-based query - } -} -``` - -Rules: - -- Return an array of `{ text: string }` (`MetricFindValue[]`). -- Use `getTemplateSrv().replace` inside `metricFindQuery` so variable queries can themselves use other variables (e.g. cascading variables). -- Keep queries lightweight – `metricFindQuery` can be called often by Grafana. - -## 3. Add a `VariableQueryEditor` - -If you use a structured `MyVariableQuery` model, add a small React editor so users can configure it from the variable UI. - -### 3.1 Create `VariableQueryEditor` - -File: `src/VariableQueryEditor.tsx` - -```tsx -import React, { useState } from 'react'; -import { MyVariableQuery } from './types'; -import { InlineField, InlineFieldRow, LoadingPlaceholder, Select } from '@grafana/ui'; - -interface VariableQueryProps { - query: MyVariableQuery; - onChange: (query: MyVariableQuery, definition: string) => void; -} - -export const VariableQueryEditor = ({ query, onChange }: VariableQueryProps) => { - const [state, setState] = useState(query); - - const saveQuery = () => { - // Second argument is the human-readable label shown in the variable list - const definition = `${state.rawQuery} (${state.namespace})`; - onChange(state, definition); - }; - - const handleChange = (event: React.FormEvent) => { - const { name, value } = event.currentTarget; - - const next = { - ...state, - [name]: value, - }; - - setState(next); - }; - - return ( - <> - - - - - - - - - - - - ); -}; -``` - -### 3.2 Create a `VariableSupport` file for the plugin - -File: `src/variableSupport.ts` - -```ts -export class MyVariableSupport extends CustomVariableSupport { - editor = VariableQueryEditor; - - constructor(private datasource: Datasource) { - super(); - } - - query(request: DataQueryRequest): Observable<{ data: MetricFindValue[] }> { - const [query] = request.targets; - const { range, scopedVars } = request; - - const result = this.datasource.metricFindQuery(query, { scopedVars, range }); - return from(result).pipe(map((data) => ({ data }))); - } -} -``` - -- `editor = VariableQueryEditor;` wires your editor into Grafana’s variable UI. - -### 3.3 Assign `VariableSupport` to `this.variables` in the datasource - -File: `src/datasource.ts` - -```ts -export class DataSource extends DataSourceApi { - constructor( - instanceSettings: DataSourceInstanceSettings, - private readonly templateSrv: TemplateSrv = getTemplateSrv() - ) { - super(instanceSettings); - // assign the variable property to the new VariableSupport - // to add variable support for this datasource - this.variables = new VariableSupport(this); - } - // existing functions()… -} -``` From f5286f4abbd466e9b331541dff805f9fc3f9ab72 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 18 Dec 2025 15:18:45 +0100 Subject: [PATCH 11/20] wip --- .../datasource/.config/AGENTS/instructions.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 0d6ee69258..c4df7fb974 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -19,7 +19,8 @@ A typical datasource with backend plugin includes: **plugin.json** - Declares plugin ID, type (`datasource`), name, version -- Loaded by Grafana at startup +- Gives Grafana the instructions it needs during startup to know how to run the plugin. +- Needs to define `backend:true` to launch the backend part of Grafana during startup. **Main module (`src/module.ts`)** @@ -51,6 +52,10 @@ A typical datasource with backend plugin includes: - Dispose (cleanup hook). - NewDatasource factory called when Grafana starts instance of plugin +**Instance Settings (`pkg/models/settings.go`)** + +- Loads instance settings by parsing its persisted JSON, retrieving secure and non-secure values, and returning a combined settings object for the plugin to use at runtime. + ### Repository layout - `src/` - Frontend (TypeScript/React) @@ -92,6 +97,9 @@ You must **NOT**: - Use the local file system - Use environment variables - Execute arbitrary code in the backend +- Log sensitive data +- Use upstream Golang HTTP client in the backend. +- Use `info` level for logging. You **SHOULD**: @@ -101,5 +109,8 @@ You **SHOULD**: - Use idiomatic React + TypeScript - Use secureJsonData instead of jsonData for credentials and sensitive data - Error happening should be logged with level `error` +- Use Grafana plugin SDK HTTP client in the backend. +- Use `debug` or `error` level for logging. ## Instructions for specific tasks +- [Add template variable support](./tasks/support-template-variables.md) \ No newline at end of file From 39ac36f21b65909fe78d57be88c300fe4797b2f7 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 18 Dec 2025 15:19:02 +0100 Subject: [PATCH 12/20] template variable task. --- .../tasks/support-template-variables.md | 307 ++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md new file mode 100644 index 0000000000..1502424e86 --- /dev/null +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/tasks/support-template-variables.md @@ -0,0 +1,307 @@ +# Add Support for Template & Query Variables + +Variables are placeholders you can use to create templated queries and dynamic dashboards. In a data source, this means: + +- Using template variables inside query fields +- Implementing `metricFindQuery` and a `VariableQueryEditor` so the data source can be used as a **Query** type variable. + +Always complete **all three sections**: + +1. Interpolate template variables in queries +2. Support query variables via `metricFindQuery` +3. Add a `VariableQueryEditor` + +## 1. Interpolate Template Variables in Queries + +File: `src/datasource.ts` (or your data source class file) + +### 1.1 Import `getTemplateSrv` + +```ts +import { getTemplateSrv } from '@grafana/runtime'; +``` + +- `getTemplateSrv()` returns `TemplateSrv`, which exposes `replace()` for variable interpolation. + +### 1.2 Decide which fields support variables + +In your query model (e.g. `MyQuery`), identify all string fields that should accept `$var`: + +```ts +export interface MyQuery { + // existing… + rawQuery?: string; // e.g. SQL / text query + namespace?: string; // optional selector + // other fields… +} +``` + +Rules: + +- Only enable variables where they actually make sense (query strings, selectors, filters). +- Document which fields support variables if you have query editor help. + +### 1.3 Apply `replace()` in `query()` + +Inside your `DataSource` implementation: + +```ts +import { DataQueryRequest, DataQueryResponse } from '@grafana/schema'; + +export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + } + + async query(options: DataQueryRequest): Promise { + const targets = options.targets.filter((t) => !t.hide); + + const interpolatedTargets = targets.map((target) => { + const rawQuery = this.templateSrv.replace( + target.rawQuery ?? '', + options.scopedVars // include scoped vars for panel/time range + ); + + const namespace = this.templateSrv.replace(target.namespace ?? '', options.scopedVars); + + return { + ...target, + rawQuery, + namespace, + }; + }); + + // Use interpolatedTargets when building your backend request. + return this.doRequest(interpolatedTargets, options); + } + + private async doRequest(targets: MyQuery[], options: DataQueryRequest): Promise { + // existing implementation… + } +} +``` + +- `replace(template, scopedVars?)` replaces `$var` with current values and supports built-ins like `$__from` / `$__to`. +- You can keep interpolation localized to **only** the fields that support variables (e.g. `rawQuery`, `namespace`). + +### 1.4 Handle multi-value variables (optional) + +For multi-value variables, choose a format that matches your backend (CSV, JSON array, etc.): + +```ts +const csvQuery = getTemplateSrv().replace( + target.rawQuery ?? '', + options.scopedVars, + 'csv' // built-in format option +); +``` + +Or provide a custom formatter: + +```ts +const formatter = (value: string | string[]): string => { + if (typeof value === 'string') { + return value; + } + + // Example: join with OR + if (value.length > 1) { + return '(' + value.map((v) => `"${v}"`).join(' OR ') + ')'; + } + + return value[0]; +}; + +const formattedQuery = getTemplateSrv().replace(target.rawQuery ?? '', options.scopedVars, formatter); +``` + +Rules: + +- Use a built-in format (`csv`, etc.) where possible. +- Use a custom interpolation function only if built-ins don’t match your protocol. + +## 2. Implement `metricFindQuery` for Query Variables + +File: `src/datasource.ts` + +A **query variable** lets Grafana call your data source to get variable values. To support this, you override `metricFindQuery` in your `DataSourceApi` implementation. + +### 2.1 Define a variable query model + +File: `src/types.ts` + +```ts +export interface MyVariableQuery { + namespace: string; + rawQuery: string; +} +``` + +- Keep this model minimal – only the fields needed to fetch variable values. +- This is separate from your “regular” `MyQuery` if that simplifies things. + +> If a plain text query is enough, you can leave `query` as `string` and skip the model entirely: +> `async metricFindQuery(query: string, options?: any)` + +### 2.2 Implement `metricFindQuery` + +File: `src/datasource.ts` + +```ts +import { MetricFindValue } from '@grafana/data'; +import { getTemplateSrv } from '@grafana/runtime'; +import { MyVariableQuery } from './types'; + +export class DataSource extends DataSourceApi { + // existing constructor, query()... + + async metricFindQuery(variableQuery: MyVariableQuery | string, options?: any): Promise { + if (typeof variableQuery === 'string') { + const interpolated = this.templateSrv.replace(variableQuery); + const response = await this.fetchVariableValues({ rawQuery: interpolated }); + return response.map((name) => ({ text: name })); + } + + // If using MyVariableQuery model: + const namespace = this.templateSrv.replace(variableQuery.namespace); + const rawQuery = this.templateSrv.replace(variableQuery.rawQuery); + + const response = await this.fetchMetricNames(namespace, rawQuery); + + // Adapt this to match your backend response + return response.data.map((item: any) => ({ + text: item.name, + // optional: value: item.id, + })); + } + + private async fetchMetricNames(namespace: string, rawQuery: string) { + // call backend/API and return data in a consistent shape + } + + private async fetchVariableValues(args: { rawQuery: string }) { + // simplified variant if using a simple string-based query + } +} +``` + +Rules: + +- Return an array of `{ text: string }` (`MetricFindValue[]`). +- Use `getTemplateSrv().replace` inside `metricFindQuery` so variable queries can themselves use other variables (e.g. cascading variables). +- Keep queries lightweight – `metricFindQuery` can be called often by Grafana. + +## 3. Add a `VariableQueryEditor` + +If you use a structured `MyVariableQuery` model, add a small React editor so users can configure it from the variable UI. + +### 3.1 Create `VariableQueryEditor` + +File: `src/VariableQueryEditor.tsx` + +```tsx +import React, { useState } from 'react'; +import { MyVariableQuery } from './types'; +import { InlineField, InlineFieldRow, LoadingPlaceholder, Select } from '@grafana/ui'; + +interface VariableQueryProps { + query: MyVariableQuery; + onChange: (query: MyVariableQuery, definition: string) => void; +} + +export const VariableQueryEditor = ({ query, onChange }: VariableQueryProps) => { + const [state, setState] = useState(query); + + const saveQuery = () => { + // Second argument is the human-readable label shown in the variable list + const definition = `${state.rawQuery} (${state.namespace})`; + onChange(state, definition); + }; + + const handleChange = (event: React.FormEvent) => { + const { name, value } = event.currentTarget; + + const next = { + ...state, + [name]: value, + }; + + setState(next); + }; + + return ( + <> + + + + + + + + + + + + ); +}; +``` + +### 3.2 Create a `VariableSupport` file for the plugin + +File: `src/variableSupport.ts` + +```ts +export class MyVariableSupport extends CustomVariableSupport { + editor = VariableQueryEditor; + + constructor(private datasource: Datasource) { + super(); + } + + query(request: DataQueryRequest): Observable<{ data: MetricFindValue[] }> { + const [query] = request.targets; + const { range, scopedVars } = request; + + const result = this.datasource.metricFindQuery(query, { scopedVars, range }); + return from(result).pipe(map((data) => ({ data }))); + } +} +``` + +- `editor = VariableQueryEditor;` wires your editor into Grafana’s variable UI. + +### 3.3 Assign `VariableSupport` to `this.variables` in the datasource + +File: `src/datasource.ts` + +```ts +export class DataSource extends DataSourceApi { + constructor( + instanceSettings: DataSourceInstanceSettings, + private readonly templateSrv: TemplateSrv = getTemplateSrv() + ) { + super(instanceSettings); + // assign the variable property to the new VariableSupport + // to add variable support for this datasource + this.variables = new VariableSupport(this); + } + // existing functions()… +} +``` From 17555c2ab985d57540298ca9d36cec6c78deb8a1 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 18 Dec 2025 15:20:26 +0100 Subject: [PATCH 13/20] wip --- .../templates/panel/.config/AGENTS/instructions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md b/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md index a75789bc45..03c7b56bbe 100644 --- a/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md @@ -53,7 +53,7 @@ A typical panel plugin includes: - Keep layouts responsive (use `width`/`height`) - Avoid new dependencies unless necessary + Grafana-compatible - Maintain consistent file structure and predictable types -- Use **`@grafana/plugin-e2e`** for E2E tests and **always use versioned selectors** to interact with the Grafana UI. +- Use **`@grafana/plugin-e2e`** npm package for E2E tests and **always use versioned selectors** to interact with the Grafana UI. ## Boundaries From c843c5a918f477227cf1716c80673b711d53ed27 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 18 Dec 2025 15:45:57 +0100 Subject: [PATCH 14/20] backend support. --- .../datasource/.config/AGENTS/instructions.md | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index c4df7fb974..5a001cf506 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -3,7 +3,7 @@ You are an expert Grafana datasource plugin developer for this project. ## Your role - You are fluent in TypeScript and React (frontend) -- You are fluent in Go (backend) +{{#if hasBackend}}- You are fluent in Go (backend){{/if}} - You know how to use Grafana dashboards - You know how to setup and manage Grafana datasources @@ -14,13 +14,14 @@ Datasource plugins are used to fetch and query data from external systems. ### Plugin anatomy -A typical datasource with backend plugin includes: +{{#if hasBackend}}A typical datasource with backend plugin includes:{{/if}} +{{#unless hasBackend}}A typical datasource frontend only plugin includes:{{/unless}} **plugin.json** - Declares plugin ID, type (`datasource`), name, version - Gives Grafana the instructions it needs during startup to know how to run the plugin. -- Needs to define `backend:true` to launch the backend part of Grafana during startup. +{{#if hasBackend}}- Needs to define `backend:true` to launch the backend part of Grafana during startup.{{/if}} **Main module (`src/module.ts`)** @@ -41,6 +42,7 @@ A typical datasource with backend plugin includes: - React component where users manage and configure a data source instance - Configures instance specific settings (like URLs or credentials) +{{#if hasBackend}} **Main module (`pkg/main.go`)** - Register a factory function with `grafana-plugin-sdk-go` to create datasource backend instances @@ -55,6 +57,7 @@ A typical datasource with backend plugin includes: **Instance Settings (`pkg/models/settings.go`)** - Loads instance settings by parsing its persisted JSON, retrieving secure and non-secure values, and returning a combined settings object for the plugin to use at runtime. +{{/if}} ### Repository layout @@ -68,10 +71,12 @@ A typical datasource with backend plugin includes: - `tests/` — E2E tests (if present) - `provisioning/` — Local development provisioning - `README.md` — Human documentation +{{#if hasBackend}} - `pkg/` - Backend (Go) - `pkg/main.go` - Backend entry point - `pkg/plugin/datasource.go` - Datasource implementation - `Magefile.go` - Backend build tasks +{{/if}} - `package.json` - Frontend build scripts + deps ## Coding guidelines @@ -94,12 +99,14 @@ You must **NOT**: - Modify anything inside `.config/*` - Remove/change existing query model without a migration handler - Break public APIs (query model) +{{#if hasBackend}} - Use the local file system - Use environment variables - Execute arbitrary code in the backend - Log sensitive data -- Use upstream Golang HTTP client in the backend. -- Use `info` level for logging. +- Use upstream Golang HTTP client in the backend +- Use `info` level for logging +{{/if}} You **SHOULD**: @@ -108,9 +115,10 @@ You **SHOULD**: - Follow official Grafana datasource plugin patterns - Use idiomatic React + TypeScript - Use secureJsonData instead of jsonData for credentials and sensitive data -- Error happening should be logged with level `error` -- Use Grafana plugin SDK HTTP client in the backend. -- Use `debug` or `error` level for logging. +{{#if hasBackend}} +- Use Grafana plugin SDK HTTP client in the backend +- Use `debug` or `error` level for logging +{{/if}} ## Instructions for specific tasks - [Add template variable support](./tasks/support-template-variables.md) \ No newline at end of file From 297a0d95a6fba476fb45571dcbac7754d22a80a7 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 15 Jan 2026 11:45:42 +0100 Subject: [PATCH 15/20] adding commands to the backend readme. --- .../_partials/backend-getting-started.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/create-plugin/templates/_partials/backend-getting-started.md b/packages/create-plugin/templates/_partials/backend-getting-started.md index bea9ea63fd..3063320763 100644 --- a/packages/create-plugin/templates/_partials/backend-getting-started.md +++ b/packages/create-plugin/templates/_partials/backend-getting-started.md @@ -13,7 +13,22 @@ mage -v ``` -3. List all available Mage targets for additional commands: +3. Build plugin backend binaries in debug when files change: + ```bash + mage watch + ``` + +4. Run backend tests: + ```bash + mage test + ``` + +5. Run the linter: + ```bash + mage lint + ``` + +6. List all available Mage targets for additional commands: ```bash mage -l From 604f780d21b4340fb233a35e9b4c0b93788426d3 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 15 Jan 2026 11:47:26 +0100 Subject: [PATCH 16/20] added ts(x) --- .../templates/datasource/.config/AGENTS/instructions.md | 2 +- .../templates/panel/.config/AGENTS/instructions.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 5a001cf506..4a7750596a 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -23,7 +23,7 @@ Datasource plugins are used to fetch and query data from external systems. - Gives Grafana the instructions it needs during startup to know how to run the plugin. {{#if hasBackend}}- Needs to define `backend:true` to launch the backend part of Grafana during startup.{{/if}} -**Main module (`src/module.ts`)** +**Main module (`src/module.ts(x)`)** - Exports: `new DataSourcePlugin(DataSource)` - Registers query editor, config editor diff --git a/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md b/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md index 03c7b56bbe..44ce058aa0 100644 --- a/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/panel/.config/AGENTS/instructions.md @@ -23,7 +23,7 @@ A typical panel plugin includes: - Declares plugin ID, type (`panel`), name, version - Loaded by Grafana at startup -**Main module (`src/module.ts`)** +**Main module (`src/module.ts(x)`)** - Exports: `new PanelPlugin(PanelComponent)` - Registers panel options, migrations, defaults, ui extensions From 45921a545a22f3c0737db7007afe500128f2611a Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 15 Jan 2026 11:55:16 +0100 Subject: [PATCH 17/20] adding executable details. --- .../templates/datasource/.config/AGENTS/instructions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 4a7750596a..44f1eda15f 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -21,7 +21,9 @@ Datasource plugins are used to fetch and query data from external systems. - Declares plugin ID, type (`datasource`), name, version - Gives Grafana the instructions it needs during startup to know how to run the plugin. -{{#if hasBackend}}- Needs to define `backend:true` to launch the backend part of Grafana during startup.{{/if}} +{{#if hasBackend}}- Needs to define `backend:true` and `executable:gpx_` to launch the backend part of Grafana during startup.{{/if}} + +Reference: https://grafana.com/developers/plugin-tools/reference/plugin-json **Main module (`src/module.ts(x)`)** From 69d9ec05094c1847bba332262f340b4124f2619e Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 15 Jan 2026 12:02:05 +0100 Subject: [PATCH 18/20] frontend ds extending different base classes depending on hasbackend. --- .../templates/datasource/.config/AGENTS/instructions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index 44f1eda15f..d09637a203 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -32,7 +32,8 @@ Reference: https://grafana.com/developers/plugin-tools/reference/plugin-json **Data source (`src/datasource.ts`)** -- Frontend datasource that extends DataSourceWithBackend. +{{#if hasBackend}}- Frontend datasource that extends DataSourceWithBackend.{{/if}} +{{#unless hasBackend}}- Frontend datasource that extends DataSourceApi.{{/unless}} - Connects the UI to the backend, provides the default query, applies template variables, filters queries, and sends them to the Go backend for execution **Query editor (`src/QueryEditor.tsx`)** From 8e13ddece4822be9d552978804e2150da32af270 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 15 Jan 2026 12:10:54 +0100 Subject: [PATCH 19/20] adding some more shoulds. --- .../templates/datasource/.config/AGENTS/instructions.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index d09637a203..eb0973b924 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -52,7 +52,7 @@ Reference: https://grafana.com/developers/plugin-tools/reference/plugin-json **Data source (`pkg/plugin/datasource.go`)** -- Backend datasource that Implements QueryData (receives queries from frontend, unmarshals into queryModel, returns data frames) +- Backend datasource that Implements QueryData (receives queries from frontend, unmarshals into queryModel, returns data frames). Remember to skip execution for hidden or empty queries. - CheckHealth (validates API key from settings) - Dispose (cleanup hook). - NewDatasource factory called when Grafana starts instance of plugin @@ -121,6 +121,7 @@ You **SHOULD**: {{#if hasBackend}} - Use Grafana plugin SDK HTTP client in the backend - Use `debug` or `error` level for logging +- Cache and reuse backend connections to external services {{/if}} ## Instructions for specific tasks From ff0be903ce139401dd459470182d436a11a04c01 Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 15 Jan 2026 12:18:35 +0100 Subject: [PATCH 20/20] best practies. --- .../templates/datasource/.config/AGENTS/instructions.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md index eb0973b924..f54c989086 100644 --- a/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md +++ b/packages/create-plugin/templates/datasource/.config/AGENTS/instructions.md @@ -12,6 +12,14 @@ You are an expert Grafana datasource plugin developer for this project. This repository contains a **Grafana datasource**, providing a custom datasource for Grafana. Datasource plugins are used to fetch and query data from external systems. +It is recommended that the datasource includes: +- A health check +- Template variable support +- A default query +{{#if hasBackend}} +- Support for alerting +{{/if}} + ### Plugin anatomy {{#if hasBackend}}A typical datasource with backend plugin includes:{{/if}}