@grafana/create-plugin 7.15.1 → 7.16.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 +8 -0
- package/dist/codemods/additions/prompts/docs-content.md +1 -1
- package/dist/codemods/additions/scripts/experimental-app-sdk.js +40 -0
- package/package.json +2 -1
- package/src/codemods/additions/prompts/docs-content.md +1 -1
- package/src/codemods/additions/scripts/docs.test.ts +39 -3
- package/src/codemods/additions/scripts/experimental-app-sdk.test.ts +59 -2
- package/src/codemods/additions/scripts/experimental-app-sdk.ts +55 -0
- package/templates/docs/app/docs/AGENTS.md +2 -0
- package/templates/docs/app/docs/configure.md +4 -0
- package/templates/docs/app/docs/dashboards.md +4 -0
- package/templates/docs/app/docs/index.md +5 -1
- package/templates/docs/app/docs/troubleshooting.md +5 -1
- package/templates/docs/datasource/docs/AGENTS.md +2 -0
- package/templates/docs/datasource/docs/configure.md +4 -0
- package/templates/docs/datasource/docs/dashboards.md +4 -0
- package/templates/docs/datasource/docs/troubleshooting.md +5 -1
- package/templates/docs/panel/docs/AGENTS.md +2 -0
- package/templates/docs/panel/docs/data-formats.md +4 -0
- package/templates/docs/panel/docs/examples.md +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [7.16.0](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.15.1...@grafana/create-plugin@7.16.0) (2026-10-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **create-plugin:** Override the default Grafana version in the experimental-app-sdk addition ([#3014](https://github.com/grafana/plugin-tools/issues/3014)) ([8d35cd5](https://github.com/grafana/plugin-tools/commit/8d35cd55d2213b454f8e465ec7b9e01cc3039010))
|
|
9
|
+
* **create-plugin:** scaffold docs snippets with fallback text ([#3021](https://github.com/grafana/plugin-tools/issues/3021)) ([7ef65e4](https://github.com/grafana/plugin-tools/commit/7ef65e418a9ed5951d62184954511297196aa0ee))
|
|
10
|
+
|
|
3
11
|
## [7.15.1](https://github.com/grafana/plugin-tools/compare/@grafana/create-plugin@7.15.0...@grafana/create-plugin@7.15.1) (2026-10-21)
|
|
4
12
|
|
|
5
13
|
|
|
@@ -20,7 +20,7 @@ This usually runs once, after `add docs`. Running it again is safe, because it o
|
|
|
20
20
|
|
|
21
21
|
## Check first
|
|
22
22
|
|
|
23
|
-
Search the docs folder for `section-brief` comments. A section that still has its brief is yours to write. A section without one has been written by the author, so never rewrite it.
|
|
23
|
+
Search the docs folder for `section-brief` comments. A section that still has its brief is yours to write. A section without one has been written by the author, so never rewrite it. Text between `<!-- snippet:... -->` and `<!-- /snippet:... -->` markers is maintained by Grafana, so never edit inside a snippet.
|
|
24
24
|
|
|
25
25
|
If no briefs remain, the pages are already written. Do steps 1 and 3, adding any README or linked-docs content that isn't on the pages yet without rewriting what is there, then steps 7 to 10. If nothing is left to migrate, say so in the summary and change nothing.
|
|
26
26
|
|
|
@@ -36,6 +36,7 @@ function appSdk(context) {
|
|
|
36
36
|
referenceAgentInstructions(context, data);
|
|
37
37
|
addGenerateScript(context);
|
|
38
38
|
addFeatureToggle(context);
|
|
39
|
+
addGrafanaVersionOverride(context);
|
|
39
40
|
wireGoBackend(context, data);
|
|
40
41
|
if (Object.keys(context.listChanges()).length > changesBefore) {
|
|
41
42
|
context.setMessage(buildNextStepsMessage(hasGoBackend(context), data.packageManagerName));
|
|
@@ -155,6 +156,45 @@ function addFeatureToggle(context) {
|
|
|
155
156
|
composeData.setIn(["services", "grafana", "environment", "GF_FEATURE_TOGGLES_ENABLE"], toggles.join(","));
|
|
156
157
|
context.updateFile(composePath, stringify(composeData, { lineWidth: 120, singleQuote: true }));
|
|
157
158
|
}
|
|
159
|
+
const GRAFANA_BUILD_ARGS = {
|
|
160
|
+
grafana_image: "${GRAFANA_IMAGE:-grafana}",
|
|
161
|
+
grafana_version: "${GRAFANA_VERSION:-nightly}"
|
|
162
|
+
};
|
|
163
|
+
function addGrafanaVersionOverride(context) {
|
|
164
|
+
const composePath = "docker-compose.yaml";
|
|
165
|
+
const composeContent = context.getFile(composePath);
|
|
166
|
+
if (!composeContent) {
|
|
167
|
+
additionsDebug(`Could not find ${composePath}. Skipping the Grafana version override.`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const composeData = parseDocument(composeContent);
|
|
171
|
+
if (!(composeData.getIn(["services", "grafana"]) instanceof YAMLMap)) {
|
|
172
|
+
additionsDebug(`Could not find the grafana service in ${composePath}. Skipping the Grafana version override.`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const build = composeData.getIn(["services", "grafana", "build"]);
|
|
176
|
+
if (build !== void 0 && !(build instanceof YAMLMap)) {
|
|
177
|
+
additionsDebug(`services.grafana.build in ${composePath} is not a mapping. Skipping the Grafana version override.`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const args = composeData.getIn(["services", "grafana", "build", "args"]);
|
|
181
|
+
if (args !== void 0 && !(args instanceof YAMLMap)) {
|
|
182
|
+
additionsDebug(
|
|
183
|
+
`services.grafana.build.args in ${composePath} is not a mapping. Skipping the Grafana version override.`
|
|
184
|
+
);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
let changed = false;
|
|
188
|
+
for (const [name, value] of Object.entries(GRAFANA_BUILD_ARGS)) {
|
|
189
|
+
if (!composeData.hasIn(["services", "grafana", "build", "args", name])) {
|
|
190
|
+
composeData.setIn(["services", "grafana", "build", "args", name], value);
|
|
191
|
+
changed = true;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (changed) {
|
|
195
|
+
context.updateFile(composePath, stringify(composeData, { lineWidth: 120, singleQuote: true }));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
158
198
|
function wireGoBackend(context, data) {
|
|
159
199
|
if (!hasGoBackend(context)) {
|
|
160
200
|
additionsDebug("No Go backend found. Skipping main.go wiring.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grafana/create-plugin",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.16.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"directory": "packages/create-plugin",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"yaml": "2.9.1"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@grafana/plugin-docs-parser": "0.6.0",
|
|
46
47
|
"@libs/find-up": "1.0.0",
|
|
47
48
|
"@libs/output": "1.0.3",
|
|
48
49
|
"@libs/version": "1.0.2",
|
|
@@ -20,7 +20,7 @@ This usually runs once, after `add docs`. Running it again is safe, because it o
|
|
|
20
20
|
|
|
21
21
|
## Check first
|
|
22
22
|
|
|
23
|
-
Search the docs folder for `section-brief` comments. A section that still has its brief is yours to write. A section without one has been written by the author, so never rewrite it.
|
|
23
|
+
Search the docs folder for `section-brief` comments. A section that still has its brief is yours to write. A section without one has been written by the author, so never rewrite it. Text between `<!-- snippet:... -->` and `<!-- /snippet:... -->` markers is maintained by Grafana, so never edit inside a snippet.
|
|
24
24
|
|
|
25
25
|
If no briefs remain, the pages are already written. Do steps 1 and 3, adding any README or linked-docs content that isn't on the pages yet without rewriting what is there, then steps 7 to 10. If nothing is left to migrate, say so in the summary and change nothing.
|
|
26
26
|
|
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:
|
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { stripVTControlCharacters } from 'node:util';
|
|
5
|
+
import { readSnippets, renderSnippet } from '@grafana/plugin-docs-parser';
|
|
5
6
|
import * as v from 'valibot';
|
|
6
7
|
import { parse as parseYaml } from 'yaml';
|
|
7
8
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
@@ -77,6 +78,7 @@ const PAGES_BY_TYPE: Record<string, string[]> = {
|
|
|
77
78
|
app: ['index', 'configure', 'overview', 'reports', 'dashboards', 'permissions', 'troubleshooting'],
|
|
78
79
|
};
|
|
79
80
|
const PLUGIN_TYPES = Object.keys(PAGES_BY_TYPE);
|
|
81
|
+
const SNIPPET_MARKER_RE = /^<!-- \/?snippet:\S+ -->$/gm;
|
|
80
82
|
|
|
81
83
|
// scaffolds once and writes it to disk, so the re-run sees a plugin that is already fully set up
|
|
82
84
|
function scaffoldThenRerun(
|
|
@@ -749,7 +751,7 @@ describe('docs codemod', () => {
|
|
|
749
751
|
const content = context.getFile(`docs/${page}.md`) ?? '';
|
|
750
752
|
const briefs = content.match(/<!-- section-brief\n[\s\S]*?-->/g) ?? [];
|
|
751
753
|
expect(briefs.length, page).toBeGreaterThan(0);
|
|
752
|
-
expect(content.match(/<!--/g)?.length, page).toBe(briefs.length);
|
|
754
|
+
expect(content.replace(SNIPPET_MARKER_RE, '').match(/<!--/g)?.length, page).toBe(briefs.length);
|
|
753
755
|
expect(content, page).not.toContain('section-brief:');
|
|
754
756
|
}
|
|
755
757
|
});
|
|
@@ -829,7 +831,7 @@ describe('docs codemod', () => {
|
|
|
829
831
|
troubleshooting: [
|
|
830
832
|
'This page explains how to fix common issues when you configure or use the My Plugin data source. For setup instructions, refer to [Configure the My Plugin data source](./configure.md).',
|
|
831
833
|
'To see the raw request and response for a panel, open the panel editor and click **Query inspector**.',
|
|
832
|
-
'In self-managed Grafana,
|
|
834
|
+
'In self-managed Grafana, the Grafana server logs show more detail:',
|
|
833
835
|
"1. Set the log level to `debug` in the Grafana configuration file:\n\n ```ini\n [log]\n level = debug\n ```\n\n1. Restart Grafana and reproduce the error.\n1. Look for log lines with `logger=plugin.acme-test-datasource`.\n1. Set the log level back to `info` when you're done, to keep the logs small.",
|
|
834
836
|
"If this page doesn't solve your problem:",
|
|
835
837
|
'- Search the [Grafana community forums](https://community.grafana.com/).\n- If your Grafana Cloud or Grafana Enterprise plan includes support, contact Grafana Support.',
|
|
@@ -867,7 +869,7 @@ describe('docs codemod', () => {
|
|
|
867
869
|
troubleshooting: [
|
|
868
870
|
'This page explains how to fix common issues when you set up or use My Plugin. For setup instructions, refer to the [Introduction](./index.md).',
|
|
869
871
|
'For errors in the app, open the developer tools in your browser and check the console.',
|
|
870
|
-
'In self-managed Grafana,
|
|
872
|
+
'In self-managed Grafana, the Grafana server logs show more detail:',
|
|
871
873
|
"1. Set the log level to `debug` in the Grafana configuration file:\n\n ```ini\n [log]\n level = debug\n ```\n\n1. Restart Grafana and reproduce the error.\n1. Look for log lines with `logger=plugin.acme-test-app`.\n1. Set the log level back to `info` when you're done, to keep the logs small.",
|
|
872
874
|
"If this page doesn't solve your problem:",
|
|
873
875
|
'- Search the [Grafana community forums](https://community.grafana.com/).\n- If your Grafana Cloud or Grafana Enterprise plan includes support, contact Grafana Support.',
|
|
@@ -886,6 +888,7 @@ describe('docs codemod', () => {
|
|
|
886
888
|
let left = (context.getFile(`docs/${page}.md`) ?? '')
|
|
887
889
|
.replace(/^---\n[\s\S]*?\n---\n/, '')
|
|
888
890
|
.replace(/<!-- section-brief\n[\s\S]*?-->/g, '')
|
|
891
|
+
.replace(SNIPPET_MARKER_RE, '')
|
|
889
892
|
.replace(/^#{1,6} .*$/gm, '');
|
|
890
893
|
for (const text of FIXED_TEXT[type][page] ?? []) {
|
|
891
894
|
expect(left, page).toContain(text);
|
|
@@ -896,6 +899,39 @@ describe('docs codemod', () => {
|
|
|
896
899
|
}
|
|
897
900
|
);
|
|
898
901
|
|
|
902
|
+
// when the parser's text for a snippet changes, the fallback in the template must follow
|
|
903
|
+
it.each(PLUGIN_TYPES)('scaffolds %s snippets whose fallback is the parser current text', (type) => {
|
|
904
|
+
const pluginJson = FULL_PLUGIN_JSON[type];
|
|
905
|
+
const context = makeContext(pluginJson);
|
|
906
|
+
docs(context, { docsPath: 'docs' });
|
|
907
|
+
const values = { pluginName: String(pluginJson.name), pluginId: String(pluginJson.id) };
|
|
908
|
+
const collapse = (text: string) => text.replace(/\s+/g, ' ').trim();
|
|
909
|
+
const names: string[] = [];
|
|
910
|
+
|
|
911
|
+
for (const page of PAGES_BY_TYPE[type]) {
|
|
912
|
+
const content = context.getFile(`docs/${page}.md`) ?? '';
|
|
913
|
+
const { snippets, problems } = readSnippets(content);
|
|
914
|
+
expect(problems, page).toEqual([]);
|
|
915
|
+
const lines = content.split('\n');
|
|
916
|
+
for (const { name, startLine, endLine } of snippets) {
|
|
917
|
+
names.push(name);
|
|
918
|
+
expect(collapse(lines.slice(startLine, endLine - 1).join('\n')), `${page} ${name}`).toBe(
|
|
919
|
+
collapse(renderSnippet(name, values) ?? '')
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
expect(names.length).toBeGreaterThan(0);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
it.each(['datasource', 'app'])('leaves the server logs snippet out of a %s without a backend', (type) => {
|
|
928
|
+
const context = makeContext({ ...FULL_PLUGIN_JSON[type], backend: false });
|
|
929
|
+
docs(context, { docsPath: 'docs' });
|
|
930
|
+
const content = context.getFile('docs/troubleshooting.md') ?? '';
|
|
931
|
+
expect(content).not.toContain('snippet:server-debug-logs');
|
|
932
|
+
expect(readSnippets(content)).toEqual({ snippets: [], problems: [] });
|
|
933
|
+
});
|
|
934
|
+
|
|
899
935
|
it('interpolates pluginName into the index page', () => {
|
|
900
936
|
const context = makeContext({ type: 'panel', id: 'acme-test-panel', name: 'My Panel' });
|
|
901
937
|
docs(context, { docsPath: 'docs' });
|
|
@@ -245,6 +245,62 @@ describe('experimental-app-sdk addition', () => {
|
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
247
|
|
|
248
|
+
describe('grafana version override', () => {
|
|
249
|
+
it('adds the image and version build args', () => {
|
|
250
|
+
const result = appSdk(createAppContext());
|
|
251
|
+
|
|
252
|
+
const compose = result.getFile('docker-compose.yaml') ?? '';
|
|
253
|
+
expect(compose).toContain('grafana_image: ${GRAFANA_IMAGE:-grafana}');
|
|
254
|
+
expect(compose).toContain('grafana_version: ${GRAFANA_VERSION:-nightly}');
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('adds the build args even when the base compose file already enables the toggles', () => {
|
|
258
|
+
const context = createAppContext();
|
|
259
|
+
context.addFile(
|
|
260
|
+
'.config/docker-compose-base.yaml',
|
|
261
|
+
`services:
|
|
262
|
+
grafana:
|
|
263
|
+
environment:
|
|
264
|
+
GF_FEATURE_TOGGLES_ENABLE: appplugins.loadAppManifest,appplugins.registerAPIServer,grafana.useRouterMiddleware
|
|
265
|
+
`
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const result = appSdk(context);
|
|
269
|
+
|
|
270
|
+
expect(result.getFile('docker-compose.yaml')).toContain('grafana_version: ${GRAFANA_VERSION:-nightly}');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('keeps build args the user already set', () => {
|
|
274
|
+
const context = createAppContext({
|
|
275
|
+
compose: `services:
|
|
276
|
+
grafana:
|
|
277
|
+
extends:
|
|
278
|
+
file: .config/docker-compose-base.yaml
|
|
279
|
+
service: grafana
|
|
280
|
+
build:
|
|
281
|
+
args:
|
|
282
|
+
grafana_version: 12.0.0
|
|
283
|
+
`,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const result = appSdk(context);
|
|
287
|
+
|
|
288
|
+
const compose = result.getFile('docker-compose.yaml') ?? '';
|
|
289
|
+
expect(compose).toContain('grafana_version: 12.0.0');
|
|
290
|
+
expect(compose).not.toContain('GRAFANA_VERSION');
|
|
291
|
+
expect(compose).toContain('grafana_image: ${GRAFANA_IMAGE:-grafana}');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('is idempotent', () => {
|
|
295
|
+
const once = appSdk(createAppContext());
|
|
296
|
+
const compose = once.getFile('docker-compose.yaml');
|
|
297
|
+
|
|
298
|
+
const twice = appSdk(once);
|
|
299
|
+
|
|
300
|
+
expect(twice.getFile('docker-compose.yaml')).toBe(compose);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
248
304
|
describe('feature toggle', () => {
|
|
249
305
|
it('enables the app-sdk manifest toggle', () => {
|
|
250
306
|
const context = createAppContext();
|
|
@@ -328,7 +384,7 @@ describe('experimental-app-sdk addition', () => {
|
|
|
328
384
|
|
|
329
385
|
const result = appSdk(context);
|
|
330
386
|
|
|
331
|
-
expect(result.getFile('docker-compose.yaml')).
|
|
387
|
+
expect(result.getFile('docker-compose.yaml')).not.toContain('GF_FEATURE_TOGGLES_ENABLE');
|
|
332
388
|
});
|
|
333
389
|
|
|
334
390
|
it('leaves a list-style environment block alone', () => {
|
|
@@ -341,7 +397,8 @@ describe('experimental-app-sdk addition', () => {
|
|
|
341
397
|
|
|
342
398
|
const result = appSdk(context);
|
|
343
399
|
|
|
344
|
-
expect(result.getFile('docker-compose.yaml')).
|
|
400
|
+
expect(result.getFile('docker-compose.yaml')).toContain('- GF_FEATURE_TOGGLES_ENABLE=someOtherToggle');
|
|
401
|
+
expect(result.getFile('docker-compose.yaml')).not.toContain('appplugins.loadAppManifest');
|
|
345
402
|
// The rest of the addition still applies.
|
|
346
403
|
expect(result.doesFileExist('kinds/config.cue')).toBe(true);
|
|
347
404
|
});
|
|
@@ -59,6 +59,7 @@ export default function appSdk(context: Context): Context {
|
|
|
59
59
|
referenceAgentInstructions(context, data);
|
|
60
60
|
addGenerateScript(context);
|
|
61
61
|
addFeatureToggle(context);
|
|
62
|
+
addGrafanaVersionOverride(context);
|
|
62
63
|
wireGoBackend(context, data);
|
|
63
64
|
|
|
64
65
|
// Only guide the user when we actually scaffolded something; a re-run should stay quiet.
|
|
@@ -240,6 +241,60 @@ function addFeatureToggle(context: Context) {
|
|
|
240
241
|
context.updateFile(composePath, stringify(composeData, { lineWidth: 120, singleQuote: true }));
|
|
241
242
|
}
|
|
242
243
|
|
|
244
|
+
// The app-sdk needs a newer Grafana than the default the plugin was scaffolded with, so the root
|
|
245
|
+
// compose file overrides the build args, while still letting GRAFANA_IMAGE/GRAFANA_VERSION win.
|
|
246
|
+
const GRAFANA_BUILD_ARGS: Record<string, string> = {
|
|
247
|
+
grafana_image: '${GRAFANA_IMAGE:-grafana}',
|
|
248
|
+
grafana_version: '${GRAFANA_VERSION:-nightly}',
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Overrides the default Grafana image and version in the root docker-compose.yaml. Build args the
|
|
253
|
+
* user has already set are left alone.
|
|
254
|
+
*/
|
|
255
|
+
function addGrafanaVersionOverride(context: Context) {
|
|
256
|
+
const composePath = 'docker-compose.yaml';
|
|
257
|
+
const composeContent = context.getFile(composePath);
|
|
258
|
+
|
|
259
|
+
if (!composeContent) {
|
|
260
|
+
additionsDebug(`Could not find ${composePath}. Skipping the Grafana version override.`);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const composeData = parseDocument(composeContent);
|
|
265
|
+
|
|
266
|
+
if (!(composeData.getIn(['services', 'grafana']) instanceof YAMLMap)) {
|
|
267
|
+
additionsDebug(`Could not find the grafana service in ${composePath}. Skipping the Grafana version override.`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const build = composeData.getIn(['services', 'grafana', 'build']);
|
|
272
|
+
if (build !== undefined && !(build instanceof YAMLMap)) {
|
|
273
|
+
additionsDebug(`services.grafana.build in ${composePath} is not a mapping. Skipping the Grafana version override.`);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const args = composeData.getIn(['services', 'grafana', 'build', 'args']);
|
|
278
|
+
if (args !== undefined && !(args instanceof YAMLMap)) {
|
|
279
|
+
additionsDebug(
|
|
280
|
+
`services.grafana.build.args in ${composePath} is not a mapping. Skipping the Grafana version override.`
|
|
281
|
+
);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let changed = false;
|
|
286
|
+
for (const [name, value] of Object.entries(GRAFANA_BUILD_ARGS)) {
|
|
287
|
+
if (!composeData.hasIn(['services', 'grafana', 'build', 'args', name])) {
|
|
288
|
+
composeData.setIn(['services', 'grafana', 'build', 'args', name], value);
|
|
289
|
+
changed = true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (changed) {
|
|
294
|
+
context.updateFile(composePath, stringify(composeData, { lineWidth: 120, singleQuote: true }));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
243
298
|
/**
|
|
244
299
|
* Wires the generated kinds into the Go backend, for app plugins that have one. Go code generation
|
|
245
300
|
* itself is enabled when kinds/config.cue is first scaffolded, in addTemplateFiles.
|
|
@@ -108,6 +108,8 @@ To fill a section:
|
|
|
108
108
|
Briefs are not shown in the preview or the catalog, so a section you haven't written looks like a bare
|
|
109
109
|
heading. `docs:validate` lists every section that still has only its brief.
|
|
110
110
|
|
|
111
|
+
Text between `<!-- snippet:... -->` and `<!-- /snippet:... -->` markers is maintained by Grafana. Never edit inside it - refer to `snippets.md` in the guides.
|
|
112
|
+
|
|
111
113
|
## Validate
|
|
112
114
|
|
|
113
115
|
```bash
|
|
@@ -20,12 +20,16 @@ Before you configure the app, ensure you have:
|
|
|
20
20
|
|
|
21
21
|
## Open the configuration page
|
|
22
22
|
|
|
23
|
+
<!-- snippet:open-app-config -->
|
|
24
|
+
|
|
23
25
|
To open the {{{pluginName}}} configuration page:
|
|
24
26
|
|
|
25
27
|
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
26
28
|
1. Enter `{{{pluginName}}}` in the search bar.
|
|
27
29
|
1. Select **{{{pluginName}}}**.
|
|
28
30
|
|
|
31
|
+
<!-- /snippet:open-app-config -->
|
|
32
|
+
|
|
29
33
|
<!-- section-brief
|
|
30
34
|
Fill this in: After the list above, add one sentence that names the tab to click, using the `title` of the page passed to `addConfigPage` in `src/module.ts(x)`, such as "Then click the **Configuration** tab." Keep it a sentence rather than another step of the list. If the app adds more than one config page, name each tab and what it holds.
|
|
31
35
|
-->
|
|
@@ -12,6 +12,8 @@ sidebar_position: 6
|
|
|
12
12
|
|
|
13
13
|
## Import a dashboard
|
|
14
14
|
|
|
15
|
+
<!-- snippet:import-app-dashboard -->
|
|
16
|
+
|
|
15
17
|
To import a dashboard that ships with {{{pluginName}}}:
|
|
16
18
|
|
|
17
19
|
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
@@ -22,6 +24,8 @@ To import a dashboard that ships with {{{pluginName}}}:
|
|
|
22
24
|
|
|
23
25
|
Imported dashboards don't update when you upgrade the plugin. When a plugin upgrade ships a newer version of a dashboard, click **Update** next to it. This overwrites any changes you made to it.
|
|
24
26
|
|
|
27
|
+
<!-- /snippet:import-app-dashboard -->
|
|
28
|
+
|
|
25
29
|
## Included dashboards
|
|
26
30
|
|
|
27
31
|
<!-- section-brief
|
|
@@ -24,6 +24,8 @@ Fill this in: Keep the list above. Add a bullet in the same **Label:** form for
|
|
|
24
24
|
|
|
25
25
|
## Enable the app
|
|
26
26
|
|
|
27
|
+
<!-- snippet:enable-app -->
|
|
28
|
+
|
|
27
29
|
To enable {{{pluginName}}}:
|
|
28
30
|
|
|
29
31
|
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
@@ -31,8 +33,10 @@ To enable {{{pluginName}}}:
|
|
|
31
33
|
1. Select **{{{pluginName}}}**.
|
|
32
34
|
1. Click **Enable**.
|
|
33
35
|
|
|
36
|
+
<!-- /snippet:enable-app -->
|
|
37
|
+
|
|
34
38
|
<!-- section-brief
|
|
35
|
-
Fill this in: If `src/plugin.json` sets `"autoEnabled": true`, the app is enabled when it's installed, so remove this section. Otherwise keep the steps above and remove only this brief.
|
|
39
|
+
Fill this in: If `src/plugin.json` sets `"autoEnabled": true`, the app is enabled when it's installed, so remove this section, including the steps and their snippet markers. Otherwise keep the steps above and remove only this brief.
|
|
36
40
|
-->
|
|
37
41
|
{{#if hasConfigPage}}
|
|
38
42
|
|
|
@@ -32,7 +32,9 @@ Open each H2 with one sentence on when its errors occur. Add an H3 per symptom,
|
|
|
32
32
|
For errors in the app, open the developer tools in your browser and check the console.
|
|
33
33
|
{{#if hasBackend}}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
<!-- snippet:server-debug-logs -->
|
|
36
|
+
|
|
37
|
+
In self-managed Grafana, the Grafana server logs show more detail:
|
|
36
38
|
|
|
37
39
|
1. Set the log level to `debug` in the Grafana configuration file:
|
|
38
40
|
|
|
@@ -44,6 +46,8 @@ In self-managed Grafana, you can also find more detail in the Grafana server log
|
|
|
44
46
|
1. Restart Grafana and reproduce the error.
|
|
45
47
|
1. Look for log lines with `logger=plugin.{{{pluginId}}}`.
|
|
46
48
|
1. Set the log level back to `info` when you're done, to keep the logs small.
|
|
49
|
+
|
|
50
|
+
<!-- /snippet:server-debug-logs -->
|
|
47
51
|
{{/if}}
|
|
48
52
|
|
|
49
53
|
## Get additional help
|
|
@@ -102,6 +102,8 @@ To fill a section:
|
|
|
102
102
|
Briefs are not shown in the preview or the catalog, so a section you haven't written looks like a bare
|
|
103
103
|
heading. `docs:validate` lists every section that still has only its brief.
|
|
104
104
|
|
|
105
|
+
Text between `<!-- snippet:... -->` and `<!-- /snippet:... -->` markers is maintained by Grafana. Never edit inside it - refer to `snippets.md` in the guides.
|
|
106
|
+
|
|
105
107
|
## Validate
|
|
106
108
|
|
|
107
109
|
```bash
|
|
@@ -31,6 +31,8 @@ If the reader only needs credentials they already have, remove this section and
|
|
|
31
31
|
|
|
32
32
|
## Add the data source
|
|
33
33
|
|
|
34
|
+
<!-- snippet:add-data-source -->
|
|
35
|
+
|
|
34
36
|
To add the {{{pluginName}}} data source:
|
|
35
37
|
|
|
36
38
|
1. Click **Connections** in the left-side menu.
|
|
@@ -39,6 +41,8 @@ To add the {{{pluginName}}} data source:
|
|
|
39
41
|
1. Select **{{{pluginName}}}**.
|
|
40
42
|
1. Click **Add new data source**.
|
|
41
43
|
|
|
44
|
+
<!-- /snippet:add-data-source -->
|
|
45
|
+
|
|
42
46
|
## Configure settings
|
|
43
47
|
|
|
44
48
|
<!-- section-brief
|
|
@@ -12,6 +12,8 @@ sidebar_position: 7
|
|
|
12
12
|
|
|
13
13
|
## Import a dashboard
|
|
14
14
|
|
|
15
|
+
<!-- snippet:import-dashboard -->
|
|
16
|
+
|
|
15
17
|
To import a dashboard that ships with {{{pluginName}}}:
|
|
16
18
|
|
|
17
19
|
1. Click **Connections** > **Data sources** in the left-side menu.
|
|
@@ -21,6 +23,8 @@ To import a dashboard that ships with {{{pluginName}}}:
|
|
|
21
23
|
|
|
22
24
|
Imported dashboards don't update when you upgrade the plugin. When a plugin upgrade ships a newer version of a dashboard, click **Update** next to it. This overwrites any changes you made to it.
|
|
23
25
|
|
|
26
|
+
<!-- /snippet:import-dashboard -->
|
|
27
|
+
|
|
24
28
|
## Included dashboards
|
|
25
29
|
|
|
26
30
|
<!-- section-brief
|
|
@@ -40,7 +40,9 @@ Open each H2 with one sentence on when its errors occur. Add an H3 per symptom,
|
|
|
40
40
|
To see the raw request and response for a panel, open the panel editor and click **Query inspector**.
|
|
41
41
|
{{#if hasBackend}}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
<!-- snippet:server-debug-logs -->
|
|
44
|
+
|
|
45
|
+
In self-managed Grafana, the Grafana server logs show more detail:
|
|
44
46
|
|
|
45
47
|
1. Set the log level to `debug` in the Grafana configuration file:
|
|
46
48
|
|
|
@@ -52,6 +54,8 @@ In self-managed Grafana, you can also find more detail in the Grafana server log
|
|
|
52
54
|
1. Restart Grafana and reproduce the error.
|
|
53
55
|
1. Look for log lines with `logger=plugin.{{{pluginId}}}`.
|
|
54
56
|
1. Set the log level back to `info` when you're done, to keep the logs small.
|
|
57
|
+
|
|
58
|
+
<!-- /snippet:server-debug-logs -->
|
|
55
59
|
{{/if}}
|
|
56
60
|
|
|
57
61
|
## Get additional help
|
|
@@ -92,6 +92,8 @@ To fill a section:
|
|
|
92
92
|
Briefs are not shown in the preview or the catalog, so a section you haven't written looks like a bare
|
|
93
93
|
heading. `docs:validate` lists every section that still has only its brief.
|
|
94
94
|
|
|
95
|
+
Text between `<!-- snippet:... -->` and `<!-- /snippet:... -->` markers is maintained by Grafana. Never edit inside it - refer to `snippets.md` in the guides.
|
|
96
|
+
|
|
95
97
|
## Validate
|
|
96
98
|
|
|
97
99
|
```bash
|
|
@@ -24,4 +24,8 @@ To see the data the panel receives, open the panel editor, click **Query inspect
|
|
|
24
24
|
|
|
25
25
|
## Reshape data that doesn't match
|
|
26
26
|
|
|
27
|
+
<!-- snippet:reshape-with-transformations -->
|
|
28
|
+
|
|
27
29
|
If a query returns data in a different shape, you can reshape it without changing the query. Select the **Transform data** tab in the panel editor and add a transformation. For more information, refer to [Transform data](https://grafana.com/docs/grafana/latest/panels-visualizations/query-transform-data/transform-data/).
|
|
30
|
+
|
|
31
|
+
<!-- /snippet:reshape-with-transformations -->
|
|
@@ -4,8 +4,12 @@ description: "Worked examples of the {{{yamlEscape pluginName}}} panel."
|
|
|
4
4
|
sidebar_position: 4
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
<!-- snippet:use-example-json -->
|
|
8
|
+
|
|
7
9
|
To use an example, open your dashboard and go to **Dashboard settings** > **JSON Model**. Paste the excerpt into the `panels` array and click **Save changes**.
|
|
8
10
|
|
|
11
|
+
<!-- /snippet:use-example-json -->
|
|
12
|
+
|
|
9
13
|
<!-- section-brief
|
|
10
14
|
Fill this in: Show concrete examples of {{{pluginName}}} configured for real use cases. When the repo has panels of this plugin's type in `provisioning/dashboards/*.json`, take the examples from those before inventing any. Express each example as a `dashboard.json` panel excerpt: `type` and `options`, plus `fieldConfig` and `targets` when the example depends on them. Leave out `id`, `gridPos`, `pluginVersion`, datasource UIDs and timestamps - they are specific to one dashboard and hide the settings that matter. Pair each example with a screenshot of the rendered visualization. Only use a screenshot that the example's JSON produces: open the image and check that what it shows, such as the legend, the values and the number of series, matches the JSON. If no image matches, leave the example without one and flag it. Don't cut settings from an excerpt that its result depends on, such as some of the overrides behind a screenshot.
|
|
11
15
|
|