@grafana/create-plugin 6.3.0-canary.2314.19540598852.0 → 6.3.0-canary.2320.19631436862.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/codemods/additions/additions.js +5 -0
- package/dist/codemods/additions/scripts/i18n/code-generation.js +122 -0
- package/dist/codemods/additions/scripts/i18n/config-updates.js +113 -0
- package/dist/codemods/additions/scripts/i18n/index.js +49 -0
- package/dist/codemods/additions/scripts/i18n/tooling.js +119 -0
- package/dist/codemods/additions/scripts/i18n/utils.js +50 -0
- package/dist/codemods/utils.js +2 -2
- package/dist/constants.js +4 -4
- package/dist/libs/version/src/index.js +2 -2
- package/package.json +3 -3
- package/src/codemods/AGENTS.md +25 -0
- package/src/codemods/additions/additions.ts +5 -0
- package/src/codemods/additions/scripts/i18n/README.md +157 -0
- package/src/codemods/additions/scripts/i18n/code-generation.ts +156 -0
- package/src/codemods/additions/scripts/i18n/config-updates.ts +139 -0
- package/src/codemods/additions/scripts/i18n/index.test.ts +770 -0
- package/src/codemods/additions/scripts/i18n/index.ts +81 -0
- package/src/codemods/additions/scripts/i18n/tooling.ts +146 -0
- package/src/codemods/additions/scripts/i18n/utils.ts +60 -0
- package/templates/panel/.config/AGENTS/instructions.md +0 -83
- package/templates/panel/.config/AGENTS/tasks/add-panel-options.md +0 -132
- package/templates/panel/AGENTS.md +0 -10
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
|
|
3
|
+
import type { Context } from '../../../context.js';
|
|
4
|
+
import { additionsDebug } from '../../../utils.js';
|
|
5
|
+
import { updateDockerCompose, updatePluginJson, createI18nextConfig } from './config-updates.js';
|
|
6
|
+
import { addI18nInitialization, createLoadResourcesFile } from './code-generation.js';
|
|
7
|
+
import { updateEslintConfig, addI18nDependency, addSemverDependency, addI18nextCli } from './tooling.js';
|
|
8
|
+
import { checkNeedsBackwardCompatibility, createLocaleFiles } from './utils.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* I18n addition schema using Valibot
|
|
12
|
+
* Adds internationalization support to a plugin
|
|
13
|
+
*/
|
|
14
|
+
export const schema = v.object(
|
|
15
|
+
{
|
|
16
|
+
locales: v.pipe(
|
|
17
|
+
v.union([v.string(), v.array(v.string())]),
|
|
18
|
+
v.transform((input) => {
|
|
19
|
+
// Handle both string (from CLI) and array (from tests)
|
|
20
|
+
return typeof input === 'string' ? input.split(',').map((s) => s.trim()) : input;
|
|
21
|
+
}),
|
|
22
|
+
v.array(
|
|
23
|
+
v.pipe(
|
|
24
|
+
v.string(),
|
|
25
|
+
v.regex(/^[a-z]{2}-[A-Z]{2}$/, 'Locale must be in format xx-XX (e.g., en-US, es-ES, sv-SE)')
|
|
26
|
+
),
|
|
27
|
+
'Please provide a comma-separated list of all supported locales, e.g., "en-US,es-ES,sv-SE"'
|
|
28
|
+
),
|
|
29
|
+
v.minLength(1, 'Please provide a comma-separated list of all supported locales, e.g., "en-US,es-ES,sv-SE"')
|
|
30
|
+
),
|
|
31
|
+
},
|
|
32
|
+
'Please provide a comma-separated list of all supported locales, e.g., "en-US,es-ES,sv-SE"'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
type I18nOptions = v.InferOutput<typeof schema>;
|
|
36
|
+
|
|
37
|
+
export default function i18nAddition(context: Context, options: I18nOptions): Context {
|
|
38
|
+
const { locales } = options;
|
|
39
|
+
|
|
40
|
+
additionsDebug('Adding i18n support with locales:', locales);
|
|
41
|
+
|
|
42
|
+
// Determine if we need backward compatibility (Grafana < 12.1.0)
|
|
43
|
+
const needsBackwardCompatibility = checkNeedsBackwardCompatibility(context);
|
|
44
|
+
additionsDebug('Needs backward compatibility:', needsBackwardCompatibility);
|
|
45
|
+
|
|
46
|
+
// 1. Update docker-compose.yaml with feature toggle
|
|
47
|
+
updateDockerCompose(context);
|
|
48
|
+
|
|
49
|
+
// 2. Update plugin.json with languages and grafanaDependency
|
|
50
|
+
updatePluginJson(context, locales, needsBackwardCompatibility);
|
|
51
|
+
|
|
52
|
+
// 3. Create locale folders and files
|
|
53
|
+
createLocaleFiles(context, locales);
|
|
54
|
+
|
|
55
|
+
// 4. Add @grafana/i18n dependency
|
|
56
|
+
addI18nDependency(context);
|
|
57
|
+
|
|
58
|
+
// 5. Add semver dependency for backward compatibility
|
|
59
|
+
if (needsBackwardCompatibility) {
|
|
60
|
+
addSemverDependency(context);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 6. Update eslint.config.mjs if needed
|
|
64
|
+
updateEslintConfig(context);
|
|
65
|
+
|
|
66
|
+
// 7. Add i18n initialization to module file
|
|
67
|
+
addI18nInitialization(context, needsBackwardCompatibility);
|
|
68
|
+
|
|
69
|
+
// 8. Create loadResources.ts for backward compatibility
|
|
70
|
+
if (needsBackwardCompatibility) {
|
|
71
|
+
createLoadResourcesFile(context);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 9. Add i18next-cli as dev dependency and add script
|
|
75
|
+
addI18nextCli(context);
|
|
76
|
+
|
|
77
|
+
// 10. Create i18next.config.ts
|
|
78
|
+
createI18nextConfig(context);
|
|
79
|
+
|
|
80
|
+
return context;
|
|
81
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as recast from 'recast';
|
|
2
|
+
import * as babelParser from 'recast/parsers/babel-ts.js';
|
|
3
|
+
|
|
4
|
+
import type { Context } from '../../../context.js';
|
|
5
|
+
import { addDependenciesToPackageJson, additionsDebug } from '../../../utils.js';
|
|
6
|
+
|
|
7
|
+
const { builders } = recast.types;
|
|
8
|
+
|
|
9
|
+
export function addI18nDependency(context: Context): void {
|
|
10
|
+
addDependenciesToPackageJson(context, { '@grafana/i18n': '12.2.2' }, {});
|
|
11
|
+
additionsDebug('Added @grafana/i18n dependency version 12.2.2');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function addSemverDependency(context: Context): void {
|
|
15
|
+
// Add semver as regular dependency and @types/semver as dev dependency
|
|
16
|
+
addDependenciesToPackageJson(context, { semver: '^7.6.0' }, { '@types/semver': '^7.5.0' });
|
|
17
|
+
additionsDebug('Added semver dependency');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function addI18nextCli(context: Context): void {
|
|
21
|
+
// Add i18next-cli as dev dependency
|
|
22
|
+
addDependenciesToPackageJson(context, {}, { 'i18next-cli': '^1.1.1' });
|
|
23
|
+
|
|
24
|
+
// Add i18n-extract script to package.json
|
|
25
|
+
const packageJsonRaw = context.getFile('package.json');
|
|
26
|
+
if (!packageJsonRaw) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const packageJson = JSON.parse(packageJsonRaw);
|
|
32
|
+
|
|
33
|
+
if (!packageJson.scripts) {
|
|
34
|
+
packageJson.scripts = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Defensive: only add if not already present
|
|
38
|
+
if (!packageJson.scripts['i18n-extract']) {
|
|
39
|
+
packageJson.scripts['i18n-extract'] = 'i18next-cli extract --sync-primary';
|
|
40
|
+
context.updateFile('package.json', JSON.stringify(packageJson, null, 2));
|
|
41
|
+
additionsDebug('Added i18n-extract script to package.json');
|
|
42
|
+
} else {
|
|
43
|
+
additionsDebug('i18n-extract script already exists, skipping');
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
additionsDebug('Error adding i18n-extract script:', error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function updateEslintConfig(context: Context): void {
|
|
51
|
+
if (!context.doesFileExist('eslint.config.mjs')) {
|
|
52
|
+
additionsDebug('eslint.config.mjs not found, skipping');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const eslintConfigRaw = context.getFile('eslint.config.mjs');
|
|
57
|
+
if (!eslintConfigRaw) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Defensive: check if @grafana/i18n eslint plugin is already configured
|
|
62
|
+
if (eslintConfigRaw.includes('@grafana/i18n/eslint-plugin')) {
|
|
63
|
+
additionsDebug('ESLint i18n rule already configured');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const ast = recast.parse(eslintConfigRaw, {
|
|
69
|
+
parser: babelParser,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Find the import section and add the plugin import
|
|
73
|
+
const imports = ast.program.body.filter((node: any) => node.type === 'ImportDeclaration');
|
|
74
|
+
const lastImport = imports[imports.length - 1];
|
|
75
|
+
|
|
76
|
+
if (lastImport) {
|
|
77
|
+
const pluginImport = builders.importDeclaration(
|
|
78
|
+
[builders.importDefaultSpecifier(builders.identifier('grafanaI18nPlugin'))],
|
|
79
|
+
builders.literal('@grafana/i18n/eslint-plugin')
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const lastImportIndex = ast.program.body.indexOf(lastImport);
|
|
83
|
+
ast.program.body.splice(lastImportIndex + 1, 0, pluginImport);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Find the defineConfig array and add the plugin config
|
|
87
|
+
recast.visit(ast, {
|
|
88
|
+
visitCallExpression(path: any) {
|
|
89
|
+
if (path.node.callee.name === 'defineConfig' && path.node.arguments[0]?.type === 'ArrayExpression') {
|
|
90
|
+
const configArray = path.node.arguments[0];
|
|
91
|
+
|
|
92
|
+
// Add the grafana i18n config object
|
|
93
|
+
const i18nConfig = builders.objectExpression([
|
|
94
|
+
builders.property('init', builders.identifier('name'), builders.literal('grafana/i18n-rules')),
|
|
95
|
+
builders.property(
|
|
96
|
+
'init',
|
|
97
|
+
builders.identifier('plugins'),
|
|
98
|
+
builders.objectExpression([
|
|
99
|
+
builders.property('init', builders.literal('@grafana/i18n'), builders.identifier('grafanaI18nPlugin')),
|
|
100
|
+
])
|
|
101
|
+
),
|
|
102
|
+
builders.property(
|
|
103
|
+
'init',
|
|
104
|
+
builders.identifier('rules'),
|
|
105
|
+
builders.objectExpression([
|
|
106
|
+
builders.property(
|
|
107
|
+
'init',
|
|
108
|
+
builders.literal('@grafana/i18n/no-untranslated-strings'),
|
|
109
|
+
builders.arrayExpression([
|
|
110
|
+
builders.literal('error'),
|
|
111
|
+
builders.objectExpression([
|
|
112
|
+
builders.property(
|
|
113
|
+
'init',
|
|
114
|
+
builders.identifier('calleesToIgnore'),
|
|
115
|
+
builders.arrayExpression([builders.literal('^css$'), builders.literal('use[A-Z].*')])
|
|
116
|
+
),
|
|
117
|
+
]),
|
|
118
|
+
])
|
|
119
|
+
),
|
|
120
|
+
builders.property(
|
|
121
|
+
'init',
|
|
122
|
+
builders.literal('@grafana/i18n/no-translation-top-level'),
|
|
123
|
+
builders.literal('error')
|
|
124
|
+
),
|
|
125
|
+
])
|
|
126
|
+
),
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
configArray.elements.push(i18nConfig);
|
|
130
|
+
}
|
|
131
|
+
this.traverse(path);
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const output = recast.print(ast, {
|
|
136
|
+
tabWidth: 2,
|
|
137
|
+
trailingComma: true,
|
|
138
|
+
lineTerminator: '\n',
|
|
139
|
+
}).code;
|
|
140
|
+
|
|
141
|
+
context.updateFile('eslint.config.mjs', output);
|
|
142
|
+
additionsDebug('Updated eslint.config.mjs with i18n linting rules');
|
|
143
|
+
} catch (error) {
|
|
144
|
+
additionsDebug('Error updating eslint.config.mjs:', error);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { coerce, gte } from 'semver';
|
|
2
|
+
|
|
3
|
+
import type { Context } from '../../../context.js';
|
|
4
|
+
import { additionsDebug } from '../../../utils.js';
|
|
5
|
+
|
|
6
|
+
export function checkNeedsBackwardCompatibility(context: Context): boolean {
|
|
7
|
+
const pluginJsonRaw = context.getFile('src/plugin.json');
|
|
8
|
+
if (!pluginJsonRaw) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const pluginJson = JSON.parse(pluginJsonRaw);
|
|
14
|
+
const currentGrafanaDep = pluginJson.dependencies?.grafanaDependency || '>=11.0.0';
|
|
15
|
+
const minVersion = coerce('12.1.0');
|
|
16
|
+
const currentVersion = coerce(currentGrafanaDep.replace(/^[><=]+/, ''));
|
|
17
|
+
|
|
18
|
+
// If current version is less than 12.1.0, we need backward compatibility
|
|
19
|
+
if (currentVersion && minVersion && gte(currentVersion, minVersion)) {
|
|
20
|
+
return false; // Already >= 12.1.0, no backward compat needed
|
|
21
|
+
}
|
|
22
|
+
return true; // < 12.1.0, needs backward compat
|
|
23
|
+
} catch (error) {
|
|
24
|
+
additionsDebug('Error checking backward compatibility:', error);
|
|
25
|
+
return true; // Default to backward compat on error
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function createLocaleFiles(context: Context, locales: string[]): void {
|
|
30
|
+
// Get plugin ID from plugin.json
|
|
31
|
+
const pluginJsonRaw = context.getFile('src/plugin.json');
|
|
32
|
+
if (!pluginJsonRaw) {
|
|
33
|
+
additionsDebug('Cannot create locale files without plugin.json');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const pluginJson = JSON.parse(pluginJsonRaw);
|
|
39
|
+
const pluginId = pluginJson.id;
|
|
40
|
+
|
|
41
|
+
if (!pluginId) {
|
|
42
|
+
additionsDebug('No plugin ID found in plugin.json');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create locale files for each locale (defensive: only if not already present)
|
|
47
|
+
for (const locale of locales) {
|
|
48
|
+
const localePath = `src/locales/${locale}/${pluginId}.json`;
|
|
49
|
+
|
|
50
|
+
if (!context.doesFileExist(localePath)) {
|
|
51
|
+
context.addFile(localePath, JSON.stringify({}, null, 2));
|
|
52
|
+
additionsDebug(`Created ${localePath}`);
|
|
53
|
+
} else {
|
|
54
|
+
additionsDebug(`${localePath} already exists, skipping`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
additionsDebug('Error creating locale files:', error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: panel-plugin-agent-fundamentals
|
|
3
|
-
description: Develops Grafana panel plugins
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
You are an expert Grafana panel plugin developer for this project.
|
|
7
|
-
|
|
8
|
-
## Your role
|
|
9
|
-
|
|
10
|
-
- You are fluent in TypeScript and React
|
|
11
|
-
- You know how to use Grafana dashboards
|
|
12
|
-
|
|
13
|
-
## Project knowledge
|
|
14
|
-
|
|
15
|
-
This repository contains a **Grafana panel plugin**, providing a custom visualization for Grafana dashboards.
|
|
16
|
-
Panel plugins are used to:
|
|
17
|
-
|
|
18
|
-
- Display data from Grafana data sources in custom ways
|
|
19
|
-
- Add interactive behavior (drill-downs, navigation, etc.)
|
|
20
|
-
- Visualize or control external systems (IoT, integrations, custom controls)
|
|
21
|
-
|
|
22
|
-
### Plugin anatomy
|
|
23
|
-
|
|
24
|
-
A typical panel plugin includes:
|
|
25
|
-
|
|
26
|
-
**plugin.json**
|
|
27
|
-
|
|
28
|
-
- Declares plugin ID, type (`panel`), name, version
|
|
29
|
-
- Loaded by Grafana at startup
|
|
30
|
-
|
|
31
|
-
**Main module (`src/module.ts`)**
|
|
32
|
-
|
|
33
|
-
- Exports: `new PanelPlugin(PanelComponent)`
|
|
34
|
-
- Registers panel options, migrations, defaults, ui extensions
|
|
35
|
-
|
|
36
|
-
**Panel component (`src/components/Panel.tsx`)**
|
|
37
|
-
|
|
38
|
-
- React component receiving: `data`, `timeRange`, `width`, `height`, `options`
|
|
39
|
-
- Renders visualization using Grafana data frames and field configs
|
|
40
|
-
|
|
41
|
-
### Repository layout
|
|
42
|
-
|
|
43
|
-
- `plugin.json` — Panel plugin manifest
|
|
44
|
-
- `src/module.ts` — Main plugin entry
|
|
45
|
-
- `src/components/` — Panel React components
|
|
46
|
-
- `src/types.ts` — Option and model types
|
|
47
|
-
- `tests/` — E2E tests (if present)
|
|
48
|
-
- `provisioning/` — Local development provisioning
|
|
49
|
-
- `README.md` — Human documentation
|
|
50
|
-
|
|
51
|
-
## Coding guidelines
|
|
52
|
-
|
|
53
|
-
- Use **TypeScript**, functional React components, and idiomatic patterns
|
|
54
|
-
- Use **@grafana/ui**, **@grafana/data**, **@grafana/runtime**
|
|
55
|
-
- Use **`useTheme2()`** for all colors, spacing, typography
|
|
56
|
-
- **Never hardcode** colors, spacing, padding, or font sizes
|
|
57
|
-
- Use **Emotion** + `useStyles2()` + theme tokens for styling
|
|
58
|
-
- Keep layouts responsive (use `width`/`height`)
|
|
59
|
-
- Avoid new dependencies unless necessary + Grafana-compatible
|
|
60
|
-
- Maintain consistent file structure and predictable types
|
|
61
|
-
- Use **`@grafana/plugin-e2e`** for E2E tests and **always use versioned selectors** to interact with the Grafana UI.
|
|
62
|
-
|
|
63
|
-
## Boundaries
|
|
64
|
-
|
|
65
|
-
You must **NOT**:
|
|
66
|
-
|
|
67
|
-
- Change plugin ID or plugin type in `plugin.json`
|
|
68
|
-
- Modify anything inside `.config/*`
|
|
69
|
-
- Add a backend (panel plugins = frontend only)
|
|
70
|
-
- Remove/change existing options without a migration handler
|
|
71
|
-
- Break public APIs (options, field configs, panel props)
|
|
72
|
-
- Store, read, or handle credentials
|
|
73
|
-
|
|
74
|
-
You **SHOULD**:
|
|
75
|
-
|
|
76
|
-
- Maintain backward compatibility
|
|
77
|
-
- Preserve option schema unless migration handler is added
|
|
78
|
-
- Follow official Grafana panel plugin patterns
|
|
79
|
-
- Use idiomatic React + TypeScript
|
|
80
|
-
|
|
81
|
-
## Instructions for specific tasks
|
|
82
|
-
|
|
83
|
-
- [Add panel options](./tasks/add-panel-options.md)
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
# Adding a New Panel Option
|
|
2
|
-
|
|
3
|
-
Panel options are settings the user configures to change panel behavior.
|
|
4
|
-
|
|
5
|
-
Always complete **all three steps**:
|
|
6
|
-
|
|
7
|
-
### **1. Extend the options type**
|
|
8
|
-
|
|
9
|
-
File: `src/types.ts`
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
export interface SimpleOptions {
|
|
13
|
-
// existing...
|
|
14
|
-
myOptionName: MyOptionType;
|
|
15
|
-
}
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
- Property name must match the builder `path`.
|
|
19
|
-
- Type must match expected editor output.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
### **2. Register the option in `setPanelOptions`**
|
|
24
|
-
|
|
25
|
-
File: `src/module.ts` inside `setPanelOptions((builder) => { ... })`
|
|
26
|
-
|
|
27
|
-
Use the correct builder method:
|
|
28
|
-
|
|
29
|
-
| Type | Builder |
|
|
30
|
-
| ------------------ | -------------------- |
|
|
31
|
-
| boolean | `addBooleanSwitch` |
|
|
32
|
-
| number | `addNumberInput` |
|
|
33
|
-
| string | `addTextInput` |
|
|
34
|
-
| select | `addSelect` |
|
|
35
|
-
| radio | `addRadio` |
|
|
36
|
-
| radio group | `addRadio` |
|
|
37
|
-
| slider | `addSliderInput` |
|
|
38
|
-
| color picker | `addColorPicker` |
|
|
39
|
-
| unit picker | `addUnitPicker` |
|
|
40
|
-
| field namer picker | `addFieldNamePicker` |
|
|
41
|
-
|
|
42
|
-
Template:
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
builder.addXxx({
|
|
46
|
-
path: 'myOptionName', // must match type property
|
|
47
|
-
name: 'My option',
|
|
48
|
-
defaultValue: <default>,
|
|
49
|
-
description: '',
|
|
50
|
-
settings: { /* optional */ },
|
|
51
|
-
// Optional visibility rule:
|
|
52
|
-
// showIf: (opts) => opts.someOtherOption,
|
|
53
|
-
});
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Rules:
|
|
57
|
-
|
|
58
|
-
- Every option **must** have a `defaultValue`.
|
|
59
|
-
- Put numeric constraints in `settings` (`min`, `max`, `step`).
|
|
60
|
-
- Use `showIf` for conditional display.
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
### **3. Use the option in the panel component**
|
|
65
|
-
|
|
66
|
-
File: `src/Panel.tsx` (or equivalent)
|
|
67
|
-
|
|
68
|
-
```tsx
|
|
69
|
-
export const Panel = ({ options }) => {
|
|
70
|
-
const { myOptionName } = options;
|
|
71
|
-
// apply it in rendering/logic
|
|
72
|
-
};
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
No option may remain unused.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
# Quick Editor Recipes
|
|
80
|
-
|
|
81
|
-
### **Boolean**
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
.addBooleanSwitch({ path: 'flag', name: 'Flag', defaultValue: false })
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### **Number**
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
.addNumberInput({
|
|
91
|
-
path: 'max',
|
|
92
|
-
name: 'Max value',
|
|
93
|
-
defaultValue: 10,
|
|
94
|
-
settings: { min: 1, max: 100, step: 1 },
|
|
95
|
-
})
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### **String**
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
.addTextInput({ path: 'label', name: 'Label', defaultValue: '' })
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### **Select**
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
.addSelect({
|
|
108
|
-
path: 'mode',
|
|
109
|
-
name: 'Mode',
|
|
110
|
-
defaultValue: 'auto',
|
|
111
|
-
settings: { options: [
|
|
112
|
-
{ value: 'a', label: 'A' },
|
|
113
|
-
{ value: 'b', label: 'B' },
|
|
114
|
-
]},
|
|
115
|
-
})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### **Radio**
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
.addRadio({
|
|
122
|
-
path: 'size',
|
|
123
|
-
name: 'Size',
|
|
124
|
-
defaultValue: 'md',
|
|
125
|
-
settings: { options: [
|
|
126
|
-
{ value: 'sm', label: 'Small' },
|
|
127
|
-
{ value: 'md', label: 'Medium' },
|
|
128
|
-
{ value: 'lg', label: 'Large' },
|
|
129
|
-
]},
|
|
130
|
-
showIf: (o) => o.showSize,
|
|
131
|
-
})
|
|
132
|
-
```
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: panel-plugin-agent
|
|
3
|
-
description: Develops Grafana panel plugins
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Project knowledge
|
|
7
|
-
|
|
8
|
-
This repository contains a **Grafana panel plugin**. Follow the [instructions](./.config/AGENTS/instructions.md) before doing any changes.
|
|
9
|
-
|
|
10
|
-
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.
|