Skip to content

Commit

Permalink
create-plugin: Add provisioning scaffold (#529)
Browse files Browse the repository at this point in the history
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Joe Perez <joseph.perez@grafana.com>
Co-authored-by: David Harris <david.harris@grafana.com>
  • Loading branch information
3 people authored Dec 7, 2023
1 parent a3755f7 commit 2dfba91
Show file tree
Hide file tree
Showing 25 changed files with 273 additions and 14 deletions.
54 changes: 54 additions & 0 deletions docusaurus/docs/publish-a-plugin/provide-test-environment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
id: provide-test-environment
title: Provide a test environment
description: How to add provisioning to your plugin to speed up your plugin review process.
keywords:
- github
- provisioning
- provision
- review
sidebar_position: 5
---

# Provide a test environment

Developers often ask us how long it takes for a plugin to be reviewed for publishing to the Grafana [plugin catalog](https://grafana.com/plugins). Although we [can't provide estimates](https://grafana.com/developers/plugin-tools/publish-a-plugin/publish-a-plugin#how-long-does-it-take-to-review-a-submission), we are always looking for ways to reduce cycle times.

By far the most time consuming aspect of a plugin's review is the creation of a suitable test environment so we can verify its behavior. This step often involves a number of back-and-forth conversations between the plugin developer and the review team.

To drastically improve the review time, developers can provide this themselves by [_provisioning_](https://grafana.com/docs/grafana/latest/administration/provisioning/#provision-grafana) their plugin. Provisioning refers to the process of preparing and configuring a test environment within the plugin's [Docker development environment](https://grafana.com/developers/plugin-tools/get-started/set-up-development-environment).

## Why should plugin developers care about this?

There are several benefits to provisioning:

- **Much faster review times.** If you provision your plugin prior to submission, your wait for the review will be much shorter.
- **Easier contributions.** By having an out-of-the-box working example available, would-be contributors to your plugin can easily experiment with additions to the plugin and raise pull requests.
- **Easier set up for e2e tests.** By provisioning dashboards, e2e tests can run against specific scenarios across local development and in CI.
- **Improved clarity.** We have found that provisioned plugins make it easier for tech-savvy users to understand how the plugin works.

## Mechanism to provide a test environment

Grafana can be configured to have resources installed and enabled through a mechanism known as [provisioning](https://grafana.com/docs/grafana/latest/administration/provisioning/#provision-grafana), where resources are configured in a YAML file under a `/provisioning` directory.

Since create-plugin v2.8.0, we generate provisioning capabilities for all plugin types (apps, scenes-apps, datasources, panels), and to include a sample dashboard as part of create-plugin.

## What do plugin developers need to do?

:::note

Provisioning is not required; it's an optional part of the plugin submission process that will speed the review.

:::

1. When you run the create-plugin tool, it will generate a folder called `provisioning` with additional files based on the plugin type selected.
1. When you run the Docker development environment, these files are used to automatically install (and if applicable, _enable_) your plugin and a sample dashboard.
1. We recommended that you use and update the sample dashboard to continuously verify behavior as part of your development process. And, as appropriate, configure your plugin so that it can return data.

:::note

In the case where a plugin has been scaffolded with a previous version of create-plugin, a new command can be run to retrospectively add the provisioning files.

:::

---
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ keywords:
- plugin
- publish plugin
- update plugin
- provision
---

# Publish or update a plugin
Expand All @@ -23,10 +24,10 @@ In this guide you learn how to manage the lifecycle of a plugin in the catalog,
- [Package a plugin](./package-a-plugin.md) - Build the plugin and get it ready to share in the form of a ZIP archive.
- Refer to [plugin-examples](https://github.com/grafana/grafana-plugin-examples) to review best practices for building your plugin.

To speed up the time it takes to review your plugin:
**To speed up the time it takes to review your plugin:**

- Check that your plugin is ready for review using the [plugin validator](https://github.com/grafana/plugin-validator).
- Provide sample dashboards and test data with your repository so that the plugin's functionality can be verified.
- Provide sample dashboards and test data with your repository so that the plugin's functionality can be verified. Use the [provisioning](./provide-test-environment.md) process provided to simplify this step.

## Publish your plugin

Expand Down Expand Up @@ -78,7 +79,7 @@ For more information on plugin deprecation and how to request your plugin to be

### How long does it take to review a submission?

- We're not able to give an estimate at this time, though we're constantly working on improving the time it takes to review a plugin.
- We're not able to give an estimate at this time, though we're constantly working to improve the time it takes to review a plugin. Providing a [provisioned](./provide-test-environment.md) test environment can drastically speed up your review.

### Can I decide a date when my plugin will be published?

Expand Down
19 changes: 17 additions & 2 deletions packages/create-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,23 @@ For more information see [here](https://grafana.com/developers/plugin-tools/migr

## Customizing or extending the basic configs

You can read more about customizing or extending the basic configuration [here](https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/extend-configurations)
You can read more about customizing or extending the basic configuration in our [documentation](https://grafana.com/developers/plugin-tools/create-a-plugin/extend-a-plugin/extend-configurations).

## Add provisioning to your existing plugin

You can streamline the plugin review process by incorporating provisioning into your existing plugin, enabling reviewers to test your plugin more efficiently.

```bash
# Run this command from the root of your plugin
cd ./my-plugin

npx @grafana/create-plugin@latest provisioning
```

For more information see our [documentation](https://grafana.com/developers/plugin-tools/publish-a-plugin/provide-test-environment).

---

## Contributing

We are always grateful for contributions! See the [CONTRIBUTING.md](../CONTRIBUTING.md) for more information.
We are always grateful for contributions! See [CONTRIBUTING.md](../CONTRIBUTING.md) for more information.
3 changes: 2 additions & 1 deletion packages/create-plugin/src/bin/run.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node

import minimist from 'minimist';
import { generate, update, migrate, version } from '../commands';
import { generate, update, migrate, version, provisioning } from '../commands';
import { isUnsupportedPlatform } from '../utils/utils.os';

// Exit early if operating system isn't supported.
Expand All @@ -19,6 +19,7 @@ const commands: Record<string, (argv: minimist.ParsedArgs) => void> = {
generate,
update,
version,
provisioning,
};
const command = commands[argv._[0]] || commands.generate;

Expand Down
1 change: 1 addition & 0 deletions packages/create-plugin/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './generate.command';
export * from './update.command';
export * from './migrate.command';
export * from './version.command';
export * from './provisioning.command';
32 changes: 32 additions & 0 deletions packages/create-plugin/src/commands/provisioning.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import glob from 'glob';
import path from 'path';
import fs from 'fs';
import { TEMPLATE_PATHS, TEXT } from '../constants';
import { getPluginJson } from '../utils/utils.plugin';
import { compileProvisioningTemplateFile, getTemplateData } from '../utils/utils.templates';
import { confirmPrompt, printMessage, printSuccessMessage, printError } from '../utils/utils.console';

export const provisioning = async () => {
const { type } = getPluginJson();
const provisioningFolder = path.join(process.cwd(), 'provisioning');
try {
if (await confirmPrompt(TEXT.addProvisioning)) {
if (!fs.existsSync(provisioningFolder)) {
const provisioningSpecificFiles = glob.sync(`${TEMPLATE_PATHS[type]}/provisioning/**`, { dot: true });

provisioningSpecificFiles.forEach((file) => {
compileProvisioningTemplateFile(type, file, getTemplateData());
});
printSuccessMessage(TEXT.addProvisioningSuccess);
} else {
printMessage(`You plugin already has provisioning files located under ${provisioningFolder}, aborting.`);
process.exit(0);
}
} else {
printMessage(TEXT.addProvisioningAborted);
process.exit(1);
}
} catch (error) {
printError(error);
}
};
6 changes: 5 additions & 1 deletion packages/create-plugin/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ export const TEXT = {
updateNpmDependenciesSuccess: 'Successfully updated the NPM dependencies.',
updateNpmDependenciesAborted: 'No NPM dependencies have been updated.',

removeNpmDependenciesPrompt: '**Would you like to remove the following possibly unnecessary NPM dependencies?**',
addProvisioning: '**Do you want to add provisioning files?**',
addProvisioningSuccess: 'Successfully added provisioning.',
addProvisioningAborted: 'No provisioning has been added.',

removeNpmDependenciesPrompt: '**Do you want to remove the following possibly unnecessary NPM dependencies?**',
removeNpmDependenciesSuccess: 'Unnecessary NPM dependencies removed successfully.',
removeNpmDependenciesAborted: 'No NPM dependencies have been removed.',

Expand Down
4 changes: 4 additions & 0 deletions packages/create-plugin/src/utils/utils.console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export function printSuccessMessage(msg: string) {
console.log(displayAsMarkdown(`\n✔ ${msg}`));
}

export function printError(error: string) {
console.error(displayAsMarkdown(`\n❌ ${error}`));
}

export function confirmPrompt(message: string): Promise<boolean> {
const prompt = new Confirm({
name: 'question',
Expand Down
13 changes: 13 additions & 0 deletions packages/create-plugin/src/utils/utils.templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,19 @@ export function compileSingleTemplateFile(pluginType: string, templateFile: stri
fs.writeFileSync(exportPath, rendered);
}

export function compileProvisioningTemplateFile(pluginType: string, templateFile: string, data?: any) {
if (!isFile(templateFile)) {
return;
}

const rendered = renderTemplateFromFile(templateFile, data);
const relativeExportPath = templateFile.replace(TEMPLATE_PATHS[pluginType], '.');
const exportPath = path.join(EXPORT_PATH_PREFIX, path.dirname(relativeExportPath), getExportFileName(templateFile));

mkdirp.sync(path.dirname(exportPath));
fs.writeFileSync(exportPath, rendered);
}

export function renderTemplateFromFile(templateFile: string, data?: any) {
return renderHandlebarsTemplate(fs.readFileSync(templateFile).toString(), data);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
For more information see our documentation:

- [Provision Grafana](https://grafana.com/docs/grafana/latest/administration/provisioning/)
- [Provision a plugin](https://grafana.com/developers/plugin-tools/publish-a-plugin/provide-test-environment)
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,10 @@ apiVersion: 1

apps:
- type: '{{ normalize_id pluginName orgName 'app' }}'
org_id: 1
org_name: '{{ orgName }}'
disabled: false
jsonData:
apiUrl: http://default-url.com
secureJsonData:
apiKey: secret-key
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
For more information see [Provision dashboards and data sources](https://grafana.com/tutorials/provision-dashboards-and-data-sources/)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: 1

datasources:
- name: '{{ pluginName }}'
type: '{{ normalize_id pluginName orgName 'datasource' }}'
access: proxy
isDefault: false
orgId: 1
version: 1
editable: true
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
For more information see [Provision dashboards and data sources](https://grafana.com/tutorials/provision-dashboards-and-data-sources/)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [],
"liveNow": false,
"panels": [
{
"datasource": {
"type": "grafana",
"uid": "grafana"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"seriesCountSize": "sm",
"showSeriesCount": false,
"text": "Default value of text input option"
},
"targets": [
{
"datasource": {
"type": "datasource",
"uid": "grafana"
},
"queryType": "randomWalk",
"refId": "A"
}
],
"title": "Panel Title",
"type": "{{ pluginId }}"
}
],
"refresh": "",
"schemaVersion": 38,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Provisioned {{ pluginName }} dashboard",
"version": 0,
"weekStart": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: 1

providers:
- name: '{{ pluginName }}'
type: file
options:
path: /etc/grafana/provisioning/dashboards
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
For more information see our documentation:

- [Provision Grafana](https://grafana.com/docs/grafana/latest/administration/provisioning/)
- [Provision a plugin](https://grafana.com/developers/plugin-tools/publish-a-plugin/provide-test-environment)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
app_mode = development

[feature_toggles]


Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ apiVersion: 1

datasources:
- name: gdev-testdata
isDefault: true
isDefault: false
type: testdata
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: 1

apps:
- type: '{{ normalize_id pluginName orgName 'app' }}'
org_id: 1
org_name: '{{ orgName }}'
disabled: false
jsonData:
apiUrl: http://default-url.com
isApiKeySet: true
secureJsonData:
apiKey: secret-key

This file was deleted.

2 changes: 1 addition & 1 deletion packages/plugin-e2e/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export type PluginFixture = {
/**
* Isolated {@link PanelEditPage} instance for each test.
*
* Navigates to a new dashboard page, adds a new panel and moves to the panel edit page.
* Navigates to a new dashboard page, adds a new panel and moves to the panel edit page.
*
* Use {@link PanelEditPage.setVisualization} to change the visualization
* Use {@link PanelEditPage.datasource.set} to change the datasource
Expand Down
18 changes: 18 additions & 0 deletions packages/plugin-e2e/tests/selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as semver from 'semver';
import { test, expect } from '../src';

test('should resolve ds picker selector with test id for grafana 10 and later', async ({
grafanaVersion,
selectors,
}, testInfo) => {
testInfo.skip(semver.lt(grafanaVersion, '10.0.0'));
expect(selectors.components.DataSourcePicker.container).toBe('data-testid Data source picker select container');
});

test('should resolve ds picker selector without test id for grafana 10 and later', async ({
grafanaVersion,
selectors,
}, testInfo) => {
testInfo.skip(semver.gte(grafanaVersion, '10.0.0'));
expect(selectors.components.DataSourcePicker.container).toBe('Data source picker select container');
});

0 comments on commit 2dfba91

Please sign in to comment.