@grafana/create-plugin 7.13.0 → 7.15.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 +24 -0
- package/dist/codemods/additions/additions.js +12 -0
- package/dist/codemods/additions/prompts/docs-content.md +131 -0
- package/dist/codemods/additions/scripts/docs-content.js +30 -0
- package/dist/codemods/additions/scripts/docs.js +468 -0
- package/dist/codemods/additions/scripts/experimental-app-sdk.js +15 -14
- package/dist/codemods/utils.agents.js +64 -0
- package/dist/codemods/utils.js +40 -7
- package/dist/codemods/utils.workflows.js +94 -0
- package/dist/utils/utils.cli.js +3 -2
- package/dist/utils/utils.handlebars.js +9 -2
- package/package.json +1 -1
- package/src/codemods/additions/additions.ts +12 -0
- package/src/codemods/additions/prompts/docs-content.md +131 -0
- package/src/codemods/additions/scripts/docs-content.test.ts +103 -0
- package/src/codemods/additions/scripts/docs-content.ts +32 -0
- package/src/codemods/additions/scripts/docs.test.ts +1766 -0
- package/src/codemods/additions/scripts/docs.ts +697 -0
- package/src/codemods/additions/scripts/experimental-app-sdk.test.ts +4 -3
- package/src/codemods/additions/scripts/experimental-app-sdk.ts +15 -15
- package/src/codemods/migrations/scripts/004-eslint9-flat-config.ts +1 -3
- package/src/codemods/utils.agents.test.ts +140 -0
- package/src/codemods/utils.agents.ts +81 -0
- package/src/codemods/utils.test.ts +23 -0
- package/src/codemods/utils.ts +29 -7
- package/src/codemods/utils.workflows.test.ts +153 -0
- package/src/codemods/utils.workflows.ts +140 -0
- package/src/utils/utils.cli.ts +2 -1
- package/src/utils/utils.handlebars.ts +9 -0
- package/templates/app-sdk/.config/app-sdk/generate-kinds.mjs +1 -1
- package/templates/docs/app/docs/AGENTS.md +127 -0
- package/templates/docs/app/docs/CLAUDE.md +1 -0
- package/templates/docs/app/docs/README.md +20 -0
- package/templates/docs/app/docs/configure.md +65 -0
- package/templates/docs/app/docs/dashboards.md +29 -0
- package/templates/docs/app/docs/index.md +70 -0
- package/templates/docs/app/docs/permissions.md +45 -0
- package/templates/docs/app/docs/troubleshooting.md +65 -0
- package/templates/docs/app/feature-page.md +26 -0
- package/templates/docs/app/features-index.md +12 -0
- package/templates/docs/common/workflows/validate-docs.yml +52 -0
- package/templates/docs/datasource/docs/AGENTS.md +123 -0
- package/templates/docs/datasource/docs/CLAUDE.md +1 -0
- package/templates/docs/datasource/docs/README.md +20 -0
- package/templates/docs/datasource/docs/alerting.md +56 -0
- package/templates/docs/datasource/docs/annotations.md +68 -0
- package/templates/docs/datasource/docs/configure.md +114 -0
- package/templates/docs/datasource/docs/dashboards.md +28 -0
- package/templates/docs/datasource/docs/index.md +40 -0
- package/templates/docs/datasource/docs/query-editor.md +72 -0
- package/templates/docs/datasource/docs/template-variables.md +69 -0
- package/templates/docs/datasource/docs/troubleshooting.md +73 -0
- package/templates/docs/panel/docs/AGENTS.md +113 -0
- package/templates/docs/panel/docs/CLAUDE.md +1 -0
- package/templates/docs/panel/docs/README.md +20 -0
- package/templates/docs/panel/docs/data-formats.md +27 -0
- package/templates/docs/panel/docs/examples.md +35 -0
- package/templates/docs/panel/docs/index.md +26 -0
- package/templates/docs/panel/docs/options.md +53 -0
- package/templates/docs/panel/docs/troubleshooting.md +35 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { isScalar, parseDocument, visit } from 'yaml';
|
|
2
|
+
import type { Context } from './context.js';
|
|
3
|
+
import { codemodsDebug } from './utils.js';
|
|
4
|
+
|
|
5
|
+
export interface BumpActionRefsResult {
|
|
6
|
+
/** The actions used in the workflow. */
|
|
7
|
+
found: string[];
|
|
8
|
+
/** The actions pinned to something other than a version tag, such as a branch or a commit SHA. */
|
|
9
|
+
unrecognized: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Raises each `uses: <action>@<ref>` in a workflow to at least the given minimum ref, the way
|
|
14
|
+
* addDependenciesToPackageJson raises dependency versions. A ref is never lowered.
|
|
15
|
+
*
|
|
16
|
+
* Refs are compared as `vX[.Y[.Z]]` tags, optionally prefixed with the action's own name as monorepo
|
|
17
|
+
* tags are (`build-plugin/v1.2.0`). A shorter tag floats, so `v1` already runs the latest 1.x and is
|
|
18
|
+
* left alone, and a floating tag that is too low is raised at its own precision (`v1.1` to `v1.2`).
|
|
19
|
+
* Other refs are left as-is and reported, except `main`, which always runs the latest release.
|
|
20
|
+
*
|
|
21
|
+
* The parser finds each `uses` value, then only that value's source text is replaced, so comments and
|
|
22
|
+
* formatting elsewhere stay byte-for-byte the same.
|
|
23
|
+
*/
|
|
24
|
+
export function bumpActionRefs(
|
|
25
|
+
context: Context,
|
|
26
|
+
workflowPath: string,
|
|
27
|
+
minimumRefs: Record<string, string>
|
|
28
|
+
): BumpActionRefsResult {
|
|
29
|
+
const minimums = new Map(
|
|
30
|
+
Object.entries(minimumRefs).map(([action, minimumRef]) => {
|
|
31
|
+
const minimum = parseTag(action, minimumRef);
|
|
32
|
+
if (!minimum) {
|
|
33
|
+
throw new Error(`The minimum ref '${minimumRef}' for ${action} is not a version tag.`);
|
|
34
|
+
}
|
|
35
|
+
return [action, minimum];
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const content = context.getFile(workflowPath);
|
|
40
|
+
if (!content) {
|
|
41
|
+
codemodsDebug(`Could not find ${workflowPath}. Skipping action bumps.`);
|
|
42
|
+
return { found: [], unrecognized: [] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const doc = parseDocument(content);
|
|
46
|
+
if (doc.errors.length > 0) {
|
|
47
|
+
codemodsDebug(`Failed to parse ${workflowPath}: ${doc.errors[0].message}. Skipping action bumps.`);
|
|
48
|
+
return { found: [], unrecognized: [] };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const found = new Set<string>();
|
|
52
|
+
const unrecognized = new Set<string>();
|
|
53
|
+
const edits: Array<{ start: number; end: number; text: string }> = [];
|
|
54
|
+
|
|
55
|
+
visit(doc, {
|
|
56
|
+
Pair: (_, pair) => {
|
|
57
|
+
if (!isScalar(pair.key) || pair.key.value !== 'uses' || !isScalar(pair.value)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const uses = pair.value;
|
|
61
|
+
if (typeof uses.value !== 'string' || !uses.value.includes('@')) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const separator = uses.value.lastIndexOf('@');
|
|
65
|
+
const action = uses.value.slice(0, separator);
|
|
66
|
+
const ref = uses.value.slice(separator + 1);
|
|
67
|
+
const minimum = minimums.get(action);
|
|
68
|
+
if (!minimum) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
found.add(action);
|
|
73
|
+
if (ref === 'main') {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const tag = parseTag(action, ref);
|
|
77
|
+
if (!tag) {
|
|
78
|
+
codemodsDebug(`${workflowPath} pins ${action}@${ref}, which is not a version tag. Leaving it as-is.`);
|
|
79
|
+
unrecognized.add(action);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (meetsMinimum(tag.version, minimum.version)) {
|
|
83
|
+
codemodsDebug(`${workflowPath} already pins ${action}@${ref}. Skipping.`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!uses.range) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const [start, end] = uses.range;
|
|
90
|
+
edits.push({ start, end, text: content.slice(start, end).replace(`@${ref}`, `@${raisedRef(tag, minimum)}`) });
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (edits.length > 0) {
|
|
95
|
+
// apply from the end so earlier offsets stay valid
|
|
96
|
+
const updated = edits
|
|
97
|
+
.sort((a, b) => b.start - a.start)
|
|
98
|
+
.reduce((acc, { start, end, text }) => `${acc.slice(0, start)}${text}${acc.slice(end)}`, content);
|
|
99
|
+
context.updateFile(workflowPath, updated);
|
|
100
|
+
}
|
|
101
|
+
return { found: [...found], unrecognized: [...unrecognized] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface Tag {
|
|
105
|
+
/** The text before `v`, such as `build-plugin/`, or empty for a bare tag. */
|
|
106
|
+
prefix: string;
|
|
107
|
+
/** One to three numbers, fewer for a floating tag. */
|
|
108
|
+
version: number[];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// reads `vX[.Y[.Z]]`, or `<name>/vX[.Y[.Z]]` where name is the action's last path segment
|
|
112
|
+
function parseTag(action: string, ref: string): Tag | undefined {
|
|
113
|
+
const match = /^(?:([^/]+)\/)?v(\d+)(?:\.(\d+))?(?:\.(\d+))?$/.exec(ref);
|
|
114
|
+
if (!match || (match[1] !== undefined && match[1] !== action.split('/').at(-1))) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
prefix: match[1] === undefined ? '' : `${match[1]}/`,
|
|
119
|
+
version: match
|
|
120
|
+
.slice(2)
|
|
121
|
+
.filter((part) => part !== undefined)
|
|
122
|
+
.map(Number),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// compares only the numbers the tag has, since a floating tag runs the latest release below it
|
|
127
|
+
function meetsMinimum(version: number[], minimum: number[]): boolean {
|
|
128
|
+
for (const [i, part] of version.entries()) {
|
|
129
|
+
const minimumPart = minimum[i] ?? 0;
|
|
130
|
+
if (part !== minimumPart) {
|
|
131
|
+
return part > minimumPart;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function raisedRef(tag: Tag, minimum: Tag): string {
|
|
138
|
+
const version = Array.from({ length: tag.version.length }, (_, i) => minimum.version[i] ?? 0);
|
|
139
|
+
return `${minimum.prefix}v${version.join('.')}`;
|
|
140
|
+
}
|
package/src/utils/utils.cli.ts
CHANGED
|
@@ -13,6 +13,7 @@ export const argv = minimist(args, {
|
|
|
13
13
|
hasBackend: 'backend',
|
|
14
14
|
pluginName: 'plugin-name',
|
|
15
15
|
orgName: 'org-name',
|
|
16
|
+
docsPath: 'docs-path',
|
|
16
17
|
},
|
|
17
18
|
// tell minimist to parse boolean flags as true/false to prevent minimist from interpreting
|
|
18
19
|
// them as the command name if passed first. e.g. `create-plugin --force update`
|
|
@@ -20,7 +21,7 @@ export const argv = minimist(args, {
|
|
|
20
21
|
// --agent is tri-state: absent, bare (pick an agent), or a value (an agent id or a path to a binary).
|
|
21
22
|
// parsing it as a string keeps bare --agent as '' rather than true, so all three are distinguishable.
|
|
22
23
|
// --no-agent still arrives as false.
|
|
23
|
-
string: ['agent'],
|
|
24
|
+
string: ['agent', 'docsPath'],
|
|
24
25
|
});
|
|
25
26
|
|
|
26
27
|
export const commandName = argv._[0] || 'generate';
|
|
@@ -41,6 +41,14 @@ export const kebabToPascalKebab = (str: string) => {
|
|
|
41
41
|
.join('-');
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
// for text inside a double-quoted YAML scalar, e.g. `title: "{{{yamlEscape pluginName}}}"`
|
|
45
|
+
export const yamlEscape = (str: string) => {
|
|
46
|
+
if (typeof str !== 'string') {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
49
|
+
return str.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
|
|
50
|
+
};
|
|
51
|
+
|
|
44
52
|
// Register our helpers and partials with handlebars.
|
|
45
53
|
registerHandlebarsHelpers();
|
|
46
54
|
registerHandlebarsPartials();
|
|
@@ -63,6 +71,7 @@ function registerHandlebarsHelpers() {
|
|
|
63
71
|
properCase: pascalCase,
|
|
64
72
|
pascalCase: pascalCase,
|
|
65
73
|
if_eq: ifEq,
|
|
74
|
+
yamlEscape: yamlEscape,
|
|
66
75
|
};
|
|
67
76
|
|
|
68
77
|
Object.keys(helpers).forEach((helperName) =>
|
|
@@ -21,7 +21,7 @@ import { chmodSync, existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs
|
|
|
21
21
|
import { join, resolve } from 'node:path';
|
|
22
22
|
import { arch, platform, tmpdir } from 'node:os';
|
|
23
23
|
|
|
24
|
-
const VERSION = 'v0.60.
|
|
24
|
+
const VERSION = 'v0.60.16';
|
|
25
25
|
const REPO = 'grafana/grafana-app-sdk';
|
|
26
26
|
const BIN = 'grafana-app-sdk';
|
|
27
27
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Plugin docs
|
|
2
|
+
|
|
3
|
+
This folder is the documentation for **{{{pluginName}}}**, published at
|
|
4
|
+
`grafana.com/grafana/plugins/{{pluginId}}/docs/`.
|
|
5
|
+
|
|
6
|
+
## Guides
|
|
7
|
+
|
|
8
|
+
Read these before you write or change any page. This docs format is new and your training
|
|
9
|
+
data does not cover it, so do not rely on what you know about other docs tools.
|
|
10
|
+
|
|
11
|
+
Run `{{packageManagerName}} run docs:validate` and open the guide on its last line, `Docs guides:`. It's
|
|
12
|
+
the guides' `index.md`, which says which guide to read for what, and the other guides sit next to it.
|
|
13
|
+
The guides match the version of the validator this plugin uses. The line is a local path, usually
|
|
14
|
+
`node_modules/@grafana/plugin-docs-cli/docs/index.md`, or a URL when the package isn't on disk, such as
|
|
15
|
+
with Yarn Plug'n'Play.
|
|
16
|
+
|
|
17
|
+
The guides name scripts such as `docs:validate`. This plugin uses **{{packageManagerName}}**, so run them
|
|
18
|
+
with `{{packageManagerName}} run`, as in the commands below.
|
|
19
|
+
|
|
20
|
+
## Pages and their source
|
|
21
|
+
|
|
22
|
+
`configure.md`, the app pages, `dashboards.md` and `permissions.md` exist only when the app has that feature,
|
|
23
|
+
as described under [Optional pages](#optional-pages). An app with up to three pages has one file for each at
|
|
24
|
+
the root. With more, the pages move into a `features/` folder.
|
|
25
|
+
|
|
26
|
+
Files may have moved since the plugin was scaffolded, so trace them from `src/module.ts(x)`.
|
|
27
|
+
`new AppPlugin()` names the root page component in `setRootPage` and each config page component in
|
|
28
|
+
`addConfigPage`. The root page component routes each path in `src/plugin.json` to the component that
|
|
29
|
+
renders it. `src/plugin.json` only lists the pages in the app's navigation, so look for tabs and drilldowns
|
|
30
|
+
in those components too.
|
|
31
|
+
|
|
32
|
+
| Page | Source of truth |
|
|
33
|
+
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `index.md` | `dependencies` in `src/plugin.json`, the setup sections of the README and `provisioning/`. Update the links when pages are added or removed. |
|
|
35
|
+
| `configure.md` | The components passed to `addConfigPage` and the settings type, including `jsonData` and `secureJsonData`, or the in-app page named Configuration when the app has no `addConfigPage`. With a backend, also how it reads them |
|
|
36
|
+
| `features/index.md` | Nothing - it is a curated router for the pages in `features/`, if the folder exists. Update it when pages are added or removed. |
|
|
37
|
+
| App pages | The component the root page routes the page's path to. Screenshots from `info.screenshots` in `src/plugin.json` whose name matches the page |
|
|
38
|
+
| `dashboards.md` | `includes` entries of type `dashboard` in `src/plugin.json` and their JSON files |
|
|
39
|
+
| `permissions.md` | `roles`, `iam` and the `role` and `action` of each `includes` entry in `src/plugin.json` |
|
|
40
|
+
| `troubleshooting.md` | Error and empty-state messages in source, known issues in the README or other docs and reported issues. Never guess failures |
|
|
41
|
+
|
|
42
|
+
## Optional pages
|
|
43
|
+
|
|
44
|
+
Add one of these pages when the app has the matching feature. Use the filename given here, give the page
|
|
45
|
+
a `sidebar_position` that puts it where it belongs and link it from `index.md`.
|
|
46
|
+
|
|
47
|
+
| Trigger | Page | Scope |
|
|
48
|
+
| ---------------------------------------------------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
49
|
+
| `addConfigPage` in `src/module.ts(x)` | `configure.md` | Opening the config page, every setting and provisioning the app. |
|
|
50
|
+
| An app page named Configuration, when there is no `addConfigPage` | `configure.md` | What the page is for and how to use it, as for any app page. |
|
|
51
|
+
| An `includes` entry of type `page` with a path under `/a/{{pluginId}}` | App page | What the page is for and how to use it. Copy an existing app page, and link it from `index.md`. With a fourth page, move them all into `features/` and list them in `features/index.md`. |
|
|
52
|
+
| An `includes` entry of type `dashboard` | `dashboards.md` | What each bundled dashboard shows and how to import it. |
|
|
53
|
+
| `roles` or `iam` in `src/plugin.json` | `permissions.md` | What each role grants, default assignments and what the app can access. |
|
|
54
|
+
|
|
55
|
+
When an app page grows too long, turn it into a folder of the same name with an `index.md` and
|
|
56
|
+
split it into pages there. Keep every page within three folder levels of this one.
|
|
57
|
+
|
|
58
|
+
## What not to write
|
|
59
|
+
|
|
60
|
+
Never write installation instructions, such as installing the plugin with `grafana-cli`, from a zip file or
|
|
61
|
+
in Docker. The Installation tab on the plugin page covers them, and these docs start from a plugin that is
|
|
62
|
+
already installed. When the README has an installation section, leave it out of the pages.
|
|
63
|
+
|
|
64
|
+
## Section briefs
|
|
65
|
+
|
|
66
|
+
Each section of a scaffolded page has a brief telling you what belongs there. A brief is an HTML comment
|
|
67
|
+
that starts with `section-brief`. Search the docs folder for `section-brief` to find what is left to write:
|
|
68
|
+
|
|
69
|
+
```markdown
|
|
70
|
+
## Connect your data
|
|
71
|
+
|
|
72
|
+
<!-- section-brief
|
|
73
|
+
Fill this in: The steps to get the data the app shows into Grafana.
|
|
74
|
+
|
|
75
|
+
If the app needs no data of its own, remove this section entirely.
|
|
76
|
+
-->
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Some sections also have fixed text, with a brief that builds on it:
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
## Open the configuration page
|
|
83
|
+
|
|
84
|
+
To open the {{{pluginName}}} configuration page:
|
|
85
|
+
|
|
86
|
+
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
87
|
+
|
|
88
|
+
<!-- section-brief
|
|
89
|
+
Fill this in: After the list above, add a sentence that names the tab to click.
|
|
90
|
+
-->
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Text outside a brief is written for every app and is part of the scaffold. Keep it, and change it
|
|
94
|
+
only where it's wrong for this plugin, such as a menu path or a step that differs.
|
|
95
|
+
|
|
96
|
+
Everything inside a brief is the instruction. A brief covers the section it sits under, unless it says
|
|
97
|
+
otherwise: it may tell you to extend the fixed text above it, to replace it with several sections or to
|
|
98
|
+
remove the page. When a brief says to remove a section, remove its heading and fixed text too.
|
|
99
|
+
|
|
100
|
+
To fill a section:
|
|
101
|
+
|
|
102
|
+
1. Read the brief.
|
|
103
|
+
2. Read the source the page documents, from the table above, then write the section.
|
|
104
|
+
3. Delete the whole comment, from `<!-- section-brief` to `-->`.
|
|
105
|
+
|
|
106
|
+
Briefs are not shown in the preview or the catalog, so a section you haven't written looks like a bare
|
|
107
|
+
heading. `docs:validate` lists every section that still has only its brief.
|
|
108
|
+
|
|
109
|
+
## Validate
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
{{packageManagerName}} run docs:validate
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Run `docs:validate` after every page you write or change and fix what it reports. A docs task is done when
|
|
116
|
+
it reports no warnings and no suggestions about section briefs. In particular, an `empty-section` or
|
|
117
|
+
`unfilled-section-brief` suggestion means a section is still unwritten or a brief is still in place. Neither
|
|
118
|
+
fails validation, so a passing exit code alone does not mean the docs are done.
|
|
119
|
+
|
|
120
|
+
## Preview
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
{{packageManagerName}} run docs:serve # local preview at http://localhost:3001, reloads on save
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`docs:serve` keeps running until you stop it. Start it in the background if you need to look at the
|
|
127
|
+
rendered pages, and stop it when you are done.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# {{{pluginName}}} documentation
|
|
2
|
+
|
|
3
|
+
This folder is the documentation for **{{{pluginName}}}**. It's published on the Documentation tab of
|
|
4
|
+
`grafana.com/grafana/plugins/{{pluginId}}/` with each plugin release. This README isn't published.
|
|
5
|
+
|
|
6
|
+
To learn how plugin docs work and how to write them, read the
|
|
7
|
+
[Add documentation to your plugin](https://grafana.com/developers/plugin-tools/how-to-guides/plugin-docs) guide.
|
|
8
|
+
|
|
9
|
+
## Next steps
|
|
10
|
+
|
|
11
|
+
1. Write the pages. Each section has a note that says what belongs there. **Or** let an AI agent draft them from your source code and README: commit your changes, then run `{{createPluginCmd}} add docs-content`. Review its work either way.
|
|
12
|
+
2. Add screenshots, videos and figures where they help readers. Save them inside this folder, for example in `img/`, and link to them with a relative path. `docs:validate` checks their formats and sizes.
|
|
13
|
+
3. Run `{{packageManagerName}} run docs:serve` to preview the docs. It also lists every section left to fill.
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
{{packageManagerName}} run docs:serve # local preview at http://localhost:3001
|
|
19
|
+
{{packageManagerName}} run docs:validate # check your docs, including sections left to fill
|
|
20
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Configure
|
|
3
|
+
description: "Change the {{{yamlEscape pluginName}}} settings in Grafana or provision them as code."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- app
|
|
7
|
+
- configuration
|
|
8
|
+
- provisioning
|
|
9
|
+
sidebar_position: 2
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
This document explains how to configure {{{pluginName}}} after you enable it.
|
|
13
|
+
|
|
14
|
+
## Before you begin
|
|
15
|
+
|
|
16
|
+
Before you configure the app, ensure you have:
|
|
17
|
+
|
|
18
|
+
- **Grafana permissions:** The `Organization administrator` role. Only organization administrators can configure apps.
|
|
19
|
+
- **The app enabled:** Follow [Enable the app](./index.md#enable-the-app).
|
|
20
|
+
|
|
21
|
+
## Open the configuration page
|
|
22
|
+
|
|
23
|
+
To open the {{{pluginName}}} configuration page:
|
|
24
|
+
|
|
25
|
+
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
26
|
+
1. Enter `{{{pluginName}}}` in the search bar.
|
|
27
|
+
1. Select **{{{pluginName}}}**.
|
|
28
|
+
|
|
29
|
+
<!-- section-brief
|
|
30
|
+
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
|
+
-->
|
|
32
|
+
|
|
33
|
+
## Configure settings
|
|
34
|
+
|
|
35
|
+
<!-- section-brief
|
|
36
|
+
Fill this in: An H3 for each section of the config page, named and ordered as the page shows them, such as Connection, Authentication or Features. Under each H3, a table with this shape:
|
|
37
|
+
|
|
38
|
+
| Setting | Description | Required |
|
|
39
|
+
| ------- | ----------- | -------- |
|
|
40
|
+
|
|
41
|
+
Use the field labels from the config page, in bold. If the config page has no settings of its own, such as when it only turns the app on or off, say so in one sentence and document what its buttons do instead of a table. Find the settings in the components passed to `addConfigPage` and the settings type, including `jsonData` and `secureJsonData`. In a description, say when a field only appears for some choice and when it affects another setting. Use a `> [!NOTE]` callout for a setting that's only available in Grafana Cloud or Grafana Enterprise.
|
|
42
|
+
|
|
43
|
+
End with the button that saves the settings and what the reader sees when it works.
|
|
44
|
+
-->
|
|
45
|
+
|
|
46
|
+
## Provision the app
|
|
47
|
+
|
|
48
|
+
You can provision {{{pluginName}}} as code with a Grafana provisioning file. For more information, refer to [Provision Grafana](https://grafana.com/docs/grafana/latest/administration/provisioning/#plugins).
|
|
49
|
+
|
|
50
|
+
<!-- section-brief
|
|
51
|
+
Fill this in: A YAML example with this shape, using the real `jsonData` and `secureJsonData` keys from the settings type and placeholders for every value the reader must supply, such as `<API_URL>`:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
apiVersion: 1
|
|
55
|
+
|
|
56
|
+
apps:
|
|
57
|
+
- type: {{pluginId}}
|
|
58
|
+
org_id: 1
|
|
59
|
+
disabled: false
|
|
60
|
+
jsonData: {}
|
|
61
|
+
secureJsonData: {}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Follow it with a table of the keys it uses and what each one sets, and list each placeholder with what to replace it with. If `provisioning/plugins/` has a file for the app, base the example on it.
|
|
65
|
+
-->
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Dashboards
|
|
3
|
+
description: "Import and use the dashboards that ship with {{{yamlEscape pluginName}}}."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- app
|
|
7
|
+
- dashboards
|
|
8
|
+
sidebar_position: 6
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
{{{pluginName}}} ships with ready-made dashboards that you can import and use as they are, or as a starting point for your own. For general information about dashboards, refer to [Dashboards](https://grafana.com/docs/grafana/latest/dashboards/).
|
|
12
|
+
|
|
13
|
+
## Import a dashboard
|
|
14
|
+
|
|
15
|
+
To import a dashboard that ships with {{{pluginName}}}:
|
|
16
|
+
|
|
17
|
+
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
18
|
+
1. Enter `{{{pluginName}}}` in the search bar.
|
|
19
|
+
1. Select **{{{pluginName}}}**.
|
|
20
|
+
1. Click the **Dashboards** tab.
|
|
21
|
+
1. Click **Import** next to the dashboard.
|
|
22
|
+
|
|
23
|
+
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
|
+
|
|
25
|
+
## Included dashboards
|
|
26
|
+
|
|
27
|
+
<!-- section-brief
|
|
28
|
+
Fill this in: One H3 per dashboard in the `includes` list of `src/plugin.json` whose `type` is `dashboard`, using its `name`. For each one, describe what it shows and what data or settings it needs to work, based on the panels and queries in its JSON file. When the app ships many dashboards, group related ones under a shared H3 and list them, rather than repeating the same description.
|
|
29
|
+
-->
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Introduction
|
|
3
|
+
description: "Set up {{{yamlEscape pluginName}}} and find what to read next in the docs."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- app
|
|
7
|
+
- setup
|
|
8
|
+
sidebar_position: 1
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<!-- section-brief
|
|
12
|
+
Fill this in: Write a short orientation for someone who already has {{{pluginName}}} installed and landed here from the Documentation tab. One or two sentences on what this section covers and how it's organized. Do not restate what the app does or its feature list - that's the README's job on the Overview tab, and this reader has already seen it there. Do not list every page in this folder either - the sidebar already does that; use prose to say what to read first and why.
|
|
13
|
+
-->
|
|
14
|
+
|
|
15
|
+
## Before you begin
|
|
16
|
+
|
|
17
|
+
Before you set up the app, ensure you have:
|
|
18
|
+
|
|
19
|
+
- **Grafana permissions:** The `Organization administrator` role. Only organization administrators can enable and configure apps.
|
|
20
|
+
|
|
21
|
+
<!-- section-brief
|
|
22
|
+
Fill this in: Keep the list above. Add a bullet in the same **Label:** form for each other thing the reader needs: the Grafana version from `dependencies.grafanaDependency` in `src/plugin.json`, each plugin in `dependencies.plugins` linked to its catalog page at `https://grafana.com/grafana/plugins/<id>/`, and anything `src/plugin.json` doesn't declare, such as an external service, a telemetry pipeline or a license. Never cover installing the plugin itself, because the Installation tab on the plugin page owns it.
|
|
23
|
+
-->
|
|
24
|
+
|
|
25
|
+
## Enable the app
|
|
26
|
+
|
|
27
|
+
To enable {{{pluginName}}}:
|
|
28
|
+
|
|
29
|
+
1. Click **Administration** > **Plugins and data** > **Plugins** in the left-side menu.
|
|
30
|
+
1. Enter `{{{pluginName}}}` in the search bar.
|
|
31
|
+
1. Select **{{{pluginName}}}**.
|
|
32
|
+
1. Click **Enable**.
|
|
33
|
+
|
|
34
|
+
<!-- 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.
|
|
36
|
+
-->
|
|
37
|
+
{{#if hasConfigPage}}
|
|
38
|
+
|
|
39
|
+
After you enable the app, refer to [Configure](./configure.md) for its settings.
|
|
40
|
+
{{/if}}
|
|
41
|
+
|
|
42
|
+
## Connect your data
|
|
43
|
+
|
|
44
|
+
<!-- section-brief
|
|
45
|
+
Fill this in: The steps to get the data the app shows into Grafana, such as adding a data source, setting up an agent, setting environment variables or configuring a telemetry exporter. Take them from the README's setup sections and from what the app queries in source. Give each step the commands or settings to copy, and say how the reader can tell the data has arrived. Change the heading if necessary to name the task, such as "Add a data source". Never include steps to install the plugin itself, because the Installation tab on the plugin page owns them.
|
|
46
|
+
|
|
47
|
+
If the app works without any setup of its own, remove this section entirely.
|
|
48
|
+
-->
|
|
49
|
+
|
|
50
|
+
## Common tasks
|
|
51
|
+
|
|
52
|
+
<!-- section-brief
|
|
53
|
+
Fill this in: List the jobs someone lands here to do, phrased as tasks rather than page titles, each linked to the page that answers it. For a typical app this might look like:
|
|
54
|
+
|
|
55
|
+
{{#if hasConfigPage}}
|
|
56
|
+
- [Change the app settings](./configure.md)
|
|
57
|
+
{{/if}}
|
|
58
|
+
{{#if defaultFeaturePage}}
|
|
59
|
+
- [Find your way around {{{defaultFeaturePage.name}}}](./{{defaultFeaturePage.file}}.md)
|
|
60
|
+
{{/if}}
|
|
61
|
+
{{#if hasDashboards}}
|
|
62
|
+
- [Import the dashboards that ship with the app](./dashboards.md)
|
|
63
|
+
{{/if}}
|
|
64
|
+
{{#if hasPermissions}}
|
|
65
|
+
- [Give users access to the app](./permissions.md)
|
|
66
|
+
{{/if}}
|
|
67
|
+
- [Fix setup and data errors](./troubleshooting.md)
|
|
68
|
+
|
|
69
|
+
Adapt this to {{{pluginName}}}'s actual entry points - add, remove or reword tasks so each one matches a real job a user comes here to do. This is a curated set of jobs, not a mechanical list of every file in this folder.
|
|
70
|
+
-->
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Permissions
|
|
3
|
+
description: "The roles {{{yamlEscape pluginName}}} adds and the access it needs."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- app
|
|
7
|
+
- permissions
|
|
8
|
+
- RBAC
|
|
9
|
+
sidebar_position: 7
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
This document explains who can use each part of {{{pluginName}}} and what the app can access. For general information, refer to [Role-based access control for app plugins](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/rbac-for-app-plugins/).
|
|
13
|
+
|
|
14
|
+
## Roles
|
|
15
|
+
|
|
16
|
+
<!-- section-brief
|
|
17
|
+
Fill this in: A table with one row per entry in `roles` in `src/plugin.json`:
|
|
18
|
+
|
|
19
|
+
| Role | Description | Granted by default to |
|
|
20
|
+
| ---- | ----------- | --------------------- |
|
|
21
|
+
|
|
22
|
+
Use the role's display name, or its name if it has none, and take the defaults from `grants`. After the table, say what each role lets a user do in the app, in terms of its pages and actions rather than the raw permission names.
|
|
23
|
+
|
|
24
|
+
If `src/plugin.json` has no `roles`, remove this section and Assign a role below.
|
|
25
|
+
-->
|
|
26
|
+
|
|
27
|
+
## Page access
|
|
28
|
+
|
|
29
|
+
<!-- section-brief
|
|
30
|
+
Fill this in: A table of the app pages that need more than the `Viewer` role, with the role or permission each one needs, from the `role` and `action` of each `includes` entry in `src/plugin.json`. Link each page to its docs page.
|
|
31
|
+
|
|
32
|
+
If no `includes` entry sets `role` or `action`, remove this section.
|
|
33
|
+
-->
|
|
34
|
+
|
|
35
|
+
## What the app can access
|
|
36
|
+
|
|
37
|
+
<!-- section-brief
|
|
38
|
+
Fill this in: A table of the permissions in `iam.permissions` in `src/plugin.json`, with the action, the scope and why the app needs it, based on what the backend does with that access. Administrators read this before they install the app, so be precise.
|
|
39
|
+
|
|
40
|
+
If `src/plugin.json` has no `iam`, remove this section.
|
|
41
|
+
-->
|
|
42
|
+
|
|
43
|
+
## Assign a role
|
|
44
|
+
|
|
45
|
+
To assign a role to a user, team or service account, refer to [Assign RBAC roles](https://grafana.com/docs/grafana/latest/administration/roles-and-permissions/access-control/assign-rbac-roles/).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Troubleshooting
|
|
3
|
+
description: "Troubleshoot common {{{yamlEscape pluginName}}} setup, data and access errors."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- app
|
|
7
|
+
- troubleshooting
|
|
8
|
+
sidebar_position: 8
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
This page explains how to fix common issues when you set up or use {{{pluginName}}}. For setup instructions, refer to the [Introduction](./index.md).
|
|
12
|
+
|
|
13
|
+
<!-- section-brief
|
|
14
|
+
Fill this in: Never guess failures. Each symptom must come from an error or empty-state message in source, a known issue in the README or other docs or a reported issue. When source holds only a few real failures, a short page is right, so don't pad it.
|
|
15
|
+
|
|
16
|
+
Replace this brief with one H2 per category that has real failures, in this order:
|
|
17
|
+
|
|
18
|
+
- `## Setup errors`: the app won't enable, or its settings don't save or don't connect.
|
|
19
|
+
- `## Missing data`: pages that load but show no data, linking to the setup steps in the [Introduction](./index.md).
|
|
20
|
+
{{#if hasPermissions}}
|
|
21
|
+
- `## Permission errors`: users who can't see a page or an action, linking to [Permissions](./permissions.md).
|
|
22
|
+
{{/if}}
|
|
23
|
+
- `## Performance issues`: slow pages, timeouts or rate limits, and the settings that help.
|
|
24
|
+
|
|
25
|
+
Add an H2 for another part of the app with real failures, named after that part.
|
|
26
|
+
|
|
27
|
+
Open each H2 with one sentence on when its errors occur. Add an H3 per symptom, named after the error message in quotes, such as `### "Failed to load services"`, then **Symptoms:** and a **Possible causes and solutions:** table, or a numbered **Solutions:** list for a single cause.
|
|
28
|
+
-->
|
|
29
|
+
|
|
30
|
+
## Get more detail about an error
|
|
31
|
+
|
|
32
|
+
For errors in the app, open the developer tools in your browser and check the console.
|
|
33
|
+
{{#if hasBackend}}
|
|
34
|
+
|
|
35
|
+
In self-managed Grafana, you can also find more detail in the Grafana server logs:
|
|
36
|
+
|
|
37
|
+
1. Set the log level to `debug` in the Grafana configuration file:
|
|
38
|
+
|
|
39
|
+
```ini
|
|
40
|
+
[log]
|
|
41
|
+
level = debug
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
1. Restart Grafana and reproduce the error.
|
|
45
|
+
1. Look for log lines with `logger=plugin.{{{pluginId}}}`.
|
|
46
|
+
1. Set the log level back to `info` when you're done, to keep the logs small.
|
|
47
|
+
{{/if}}
|
|
48
|
+
|
|
49
|
+
## Get additional help
|
|
50
|
+
|
|
51
|
+
If this page doesn't solve your problem:
|
|
52
|
+
|
|
53
|
+
- Search the [Grafana community forums](https://community.grafana.com/).
|
|
54
|
+
- If your Grafana Cloud or Grafana Enterprise plan includes support, contact Grafana Support.
|
|
55
|
+
|
|
56
|
+
<!-- section-brief
|
|
57
|
+
Fill this in: Add links to the plugin's issue tracker and the docs of any service the app depends on to the list above.
|
|
58
|
+
-->
|
|
59
|
+
|
|
60
|
+
When you report an issue, include:
|
|
61
|
+
|
|
62
|
+
- The Grafana and plugin versions.
|
|
63
|
+
- The error message, with sensitive information removed.
|
|
64
|
+
- The steps to reproduce the issue.
|
|
65
|
+
- The app page where it happens and the relevant app settings, with credentials removed.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "{{{yamlEscape page.name}}}"
|
|
3
|
+
description: "Use the {{{yamlEscape page.name}}} page in {{{yamlEscape pluginName}}}."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- "{{{yamlEscape page.name}}}"
|
|
7
|
+
sidebar_position: {{page.position}}
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!-- section-brief
|
|
11
|
+
Fill this in: One or two sentences on what the {{{page.name}}} page is for and when someone opens it. The app shows it at `{{{page.path}}}`. Pair it with a screenshot of the page if `info.screenshots` in `src/plugin.json` has one.
|
|
12
|
+
-->
|
|
13
|
+
{{#if page.role}}
|
|
14
|
+
|
|
15
|
+
## Before you begin
|
|
16
|
+
|
|
17
|
+
- **Grafana permissions:** At least the `{{page.role}}` role.
|
|
18
|
+
|
|
19
|
+
<!-- section-brief
|
|
20
|
+
Fill this in: Keep the bullet above. Add a bullet in the same **Label:** form only for something the reader needs before they can use this page and wouldn't guess, such as a feature flag that must be on or data that must exist. Never repeat setup from the Introduction or from another page, and don't link back to them. If this section would only point at another page, it adds nothing, so remove it, including its heading and the bullet above.
|
|
21
|
+
-->
|
|
22
|
+
{{/if}}
|
|
23
|
+
|
|
24
|
+
<!-- section-brief
|
|
25
|
+
Fill this in: Replace this brief with one H2 per task someone does on the {{{page.name}}} page, named after the task, such as "Filter by service" or "Compare two time ranges". Under each, give the steps with the UI labels in bold, and say what each part of the page shows when it helps the task. Find the tasks in the component that renders `{{{page.path}}}`, including its tabs and drilldowns, which `src/plugin.json` doesn't list. Describe UI that only appears behind a feature flag or a condition as "when X is enabled".
|
|
26
|
+
-->
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Features
|
|
3
|
+
description: "What each part of {{{yamlEscape pluginName}}} is for."
|
|
4
|
+
keywords:
|
|
5
|
+
- "{{{yamlEscape pluginName}}}"
|
|
6
|
+
- app
|
|
7
|
+
sidebar_position: {{position}}
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!-- section-brief
|
|
11
|
+
Fill this in: One line per page in this folder, in the order the app's navigation shows them, each linked to its page and saying what the reader does there. This is a router, so keep the detail on the pages themselves.
|
|
12
|
+
-->
|