@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 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.15.1",
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, you can also find more detail in the Grafana server logs:',
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, you can also find more detail in the Grafana server logs:',
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')).toBe(STOCK_COMPOSE);
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')).toBe(listCompose);
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
- In self-managed Grafana, you can also find more detail in the Grafana server logs:
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
- In self-managed Grafana, you can also find more detail in the Grafana server logs:
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