Unverified Commit 7c3f92b4 authored by thibaut severac's avatar thibaut severac
Browse files

init grafana plugin

parent 6ca275a1
name: CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.2
with:
node-version: "14.x"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache yarn cache
uses: actions/cache@v2
id: cache-yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-nodemodules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.node-version }}-nodemodules-
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build and test frontend
run: yarn build
- name: Check for backend
id: check-for-backend
run: |
if [ -f "Magefile.go" ]
then
echo "::set-output name=has-backend::true"
fi
- name: Setup Go environment
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: actions/setup-go@v2
with:
go-version: "1.15"
- name: Test backend
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: magefile/mage-action@v1
with:
version: latest
args: coverage
- name: Build backend
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: magefile/mage-action@v1
with:
version: latest
args: buildAll
name: Release
on:
push:
tags:
- "v*.*.*" # Run workflow on version tags, e.g. v1.0.0.
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js environment
uses: actions/setup-node@v2.1.2
with:
node-version: "14.x"
- name: Setup Go environment
uses: actions/setup-go@v2
with:
go-version: "1.15"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache yarn cache
uses: actions/cache@v2
id: cache-yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v2
with:
path: node_modules
key: ${{ runner.os }}-${{ matrix.node-version }}-nodemodules-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.node-version }}-nodemodules-
- name: Install dependencies
run: yarn install --frozen-lockfile;
if: |
steps.cache-yarn-cache.outputs.cache-hit != 'true' ||
steps.cache-node-modules.outputs.cache-hit != 'true'
- name: Build and test frontend
run: yarn build
- name: Check for backend
id: check-for-backend
run: |
if [ -f "Magefile.go" ]
then
echo "::set-output name=has-backend::true"
fi
- name: Test backend
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: magefile/mage-action@v1
with:
version: latest
args: coverage
- name: Build backend
if: steps.check-for-backend.outputs.has-backend == 'true'
uses: magefile/mage-action@v1
with:
version: latest
args: buildAll
- name: Sign plugin
run: yarn sign
env:
GRAFANA_API_KEY: ${{ secrets.GRAFANA_API_KEY }} # Requires a Grafana API key from Grafana.com.
- name: Get plugin metadata
id: metadata
run: |
sudo apt-get install jq
export GRAFANA_PLUGIN_ID=$(cat dist/plugin.json | jq -r .id)
export GRAFANA_PLUGIN_VERSION=$(cat dist/plugin.json | jq -r .info.version)
export GRAFANA_PLUGIN_TYPE=$(cat dist/plugin.json | jq -r .type)
export GRAFANA_PLUGIN_ARTIFACT=${GRAFANA_PLUGIN_ID}-${GRAFANA_PLUGIN_VERSION}.zip
export GRAFANA_PLUGIN_ARTIFACT_CHECKSUM=${GRAFANA_PLUGIN_ARTIFACT}.md5
echo "::set-output name=plugin-id::${GRAFANA_PLUGIN_ID}"
echo "::set-output name=plugin-version::${GRAFANA_PLUGIN_VERSION}"
echo "::set-output name=plugin-type::${GRAFANA_PLUGIN_TYPE}"
echo "::set-output name=archive::${GRAFANA_PLUGIN_ARTIFACT}"
echo "::set-output name=archive-checksum::${GRAFANA_PLUGIN_ARTIFACT_CHECKSUM}"
echo ::set-output name=github-tag::${GITHUB_REF#refs/*/}
- name: Read changelog
id: changelog
run: |
awk '/^## / {s++} s == 1 {print}' CHANGELOG.md > release_notes.md
echo "::set-output name=path::release_notes.md"
- name: Check package version
run: if [ "v${{ steps.metadata.outputs.plugin-version }}" != "${{ steps.metadata.outputs.github-tag }}" ]; then printf "\033[0;31mPlugin version doesn't match tag name\033[0m\n"; exit 1; fi
- name: Package plugin
id: package-plugin
run: |
mv dist ${{ steps.metadata.outputs.plugin-id }}
zip ${{ steps.metadata.outputs.archive }} ${{ steps.metadata.outputs.plugin-id }} -r
md5sum ${{ steps.metadata.outputs.archive }} > ${{ steps.metadata.outputs.archive-checksum }}
echo "::set-output name=checksum::$(cat ./${{ steps.metadata.outputs.archive-checksum }} | cut -d' ' -f1)"
- name: Lint plugin
run: |
git clone https://github.com/grafana/plugin-validator
pushd ./plugin-validator/cmd/plugincheck
go install
popd
plugincheck ${{ steps.metadata.outputs.archive }}
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body_path: ${{ steps.changelog.outputs.path }}
draft: true
- name: Add plugin to release
id: upload-plugin-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.metadata.outputs.archive }}
asset_name: ${{ steps.metadata.outputs.archive }}
asset_content_type: application/zip
- name: Add checksum to release
id: upload-checksum-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ steps.metadata.outputs.archive-checksum }}
asset_name: ${{ steps.metadata.outputs.archive-checksum }}
asset_content_type: text/plain
- name: Publish to Grafana.com
run: |
echo A draft release has been created for your plugin. Please review and publish it. Then submit your plugin to grafana.com/plugins by opening a PR to https://github.com/grafana/grafana-plugin-repository with the following entry:
echo
echo '{ "id": "${{ steps.metadata.outputs.plugin-id }}", "type": "${{ steps.metadata.outputs.plugin-type }}", "url": "https://github.com/${{ github.repository }}", "versions": [ { "version": "${{ steps.metadata.outputs.plugin-version }}", "commit": "${{ github.sha }}", "url": "https://github.com/${{ github.repository }}", "download": { "any": { "url": "https://github.com/${{ github.repository }}/releases/download/v${{ steps.metadata.outputs.plugin-version }}/${{ steps.metadata.outputs.archive }}", "md5": "${{ steps.package-plugin.outputs.checksum }}" } } } ] }' | jq .
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
node_modules/
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Compiled binary addons (https://nodejs.org/api/addons.html)
dist/
artifacts/
work/
ci/
e2e-results/
# Editor
.idea
module.exports = {
...require("./node_modules/@grafana/toolkit/src/config/prettier.plugin.config.json"),
};
# Changelog
## 1.0.0 (Unreleased)
Initial release.
# Grafana Data Source Plugin Template
[![Build](https://github.com/grafana/grafana-starter-datasource/workflows/CI/badge.svg)](https://github.com/grafana/grafana-starter-datasource/actions?query=workflow%3A%22CI%22)
This template is a starting point for building Grafana Data Source Plugins
## What is Grafana Data Source Plugin?
Grafana supports a wide range of data sources, including Prometheus, MySQL, and even Datadog. There’s a good chance you can already visualize metrics from the systems you have set up. In some cases, though, you already have an in-house metrics solution that you’d like to add to your Grafana dashboards. Grafana Data Source Plugins enables integrating such solutions with Grafana.
## Getting started
1. Install dependencies
```bash
yarn install
```
2. Build plugin in development mode or run in watch mode
```bash
yarn dev
```
or
```bash
yarn watch
```
3. Build plugin in production mode
```bash
yarn build
```
## Learn more
- [Build a data source plugin tutorial](https://grafana.com/tutorials/build-a-data-source-plugin)
- [Grafana documentation](https://grafana.com/docs/)
- [Grafana Tutorials](https://grafana.com/tutorials/) - Grafana Tutorials are step-by-step guides that help you make the most of Grafana
- [Grafana UI Library](https://developers.grafana.com/ui) - UI components to help you build interfaces using Grafana Design System
version: '3.7'
services:
grafana:
image: grafana/grafana:latest
environment:
GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS: centreon-datasource
volumes:
- ./dist:/var/lib/grafana/plugins/centreon-datasource
- grafana_data:/var/lib/grafana
ports:
- "3000:3000"
volumes:
grafana_data:
\ No newline at end of file
// This file is needed because it is used by vscode and other tools that
// call `jest` directly. However, unless you are doing anything special
// do not edit this file
const standard = require('@grafana/toolkit/src/config/jest.plugin.config');
// This process will use the same config that `yarn test` is using
module.exports = standard.jestConfig();
{
"name": "centreon-grafana-datasource",
"version": "1.0.0",
"description": "",
"scripts": {
"build": "grafana-toolkit plugin:build",
"test": "grafana-toolkit plugin:test",
"dev": "grafana-toolkit plugin:dev",
"watch": "grafana-toolkit plugin:dev --watch",
"sign": "grafana-toolkit plugin:sign",
"start": "yarn watch"
},
"author": "Centreon",
"license": "Apache-2.0",
"devDependencies": {
"@grafana/data": "latest",
"@grafana/toolkit": "latest",
"@grafana/ui": "latest",
"@types/lodash": "latest",
"@testing-library/jest-dom": "5.4.0",
"@testing-library/react": "^10.0.2"
},
"engines": {
"node": ">=14"
}
}
\ No newline at end of file
sonar.projectKey={PROJECT_TITLE}
sonar.projectName={PROJECT_NAME}
sonar.projectVersion={PROJECT_VERSION}
sonar.sources=.
sonar.tsql.file.suffixes=sql,tsql
sonar.plsql.file.suffixes=pks,pkb
# mandatory to not fail the builds until build-wrapper is installed and sources are compiled
sonar.c.file.suffixes=-
sonar.cpp.file.suffixes=-
sonar.objc.file.suffixes=-
#sonar.language=js
sonar.typescript.file.suffixes=.ts
sonar.sources=src
sonar.sourceEncoding=UTF-8
sonar.exclusions=**/*.test.*,node_modules/**,coverage/**, logs/**, build/**, lib/**, buildTests/**, front/**, cache/**, tests/**, src/test.ts
sonar.tests=tests
sonar.testExecutionReportPaths=coverage/test-report.xml
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.eslint.reportPaths=coverage/eslint-report.json
import React, { ChangeEvent, PureComponent } from 'react';
import {Field, LegacyForms, Select} from '@grafana/ui';
import { DataSourcePluginOptionsEditorProps, SelectableValue } from '@grafana/data';
import { MyDataSourceOptions, MySecureJsonData } from './types';
import { ActionMeta } from '@grafana/ui/components/Select/types';
const { SecretFormField, FormField } = LegacyForms;
interface Props extends DataSourcePluginOptionsEditorProps<MyDataSourceOptions> {}
interface State {}
export class ConfigEditor extends PureComponent<Props, State> {
onURLChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onOptionsChange, options } = this.props;
const jsonData = {
...options.jsonData,
path: event.target.value,
};
onOptionsChange({ ...options, jsonData });
};
onProxyChange = (value: SelectableValue<string>, actionMeta: ActionMeta): {} | void => {
// const { onOptionsChange, options } = this.props;
// const jsonData = {
// ...options.jsonData,
// path: event.target.value,
// };
// onOptionsChange({ ...options, jsonData });
console.log(value);
};
// Secure field (only sent to the backend)
onAPIKeyChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onOptionsChange, options } = this.props;
onOptionsChange({
...options,
secureJsonData: {
apiKey: event.target.value,
},
});
};
onResetAPIKey = () => {
const { onOptionsChange, options } = this.props;
onOptionsChange({
...options,
secureJsonFields: {
...options.secureJsonFields,
apiKey: false,
},
secureJsonData: {
...options.secureJsonData,
apiKey: '',
},
});
};
render() {
const { options } = this.props;
const { jsonData, secureJsonFields } = options;
const secureJsonData = (options.secureJsonData || {}) as MySecureJsonData;
const selectOptions = [
{ label: 'Basic option', value: 'proxy' },
{ label: 'Option with description', value: 'access', description: 'this is a description' },
];
const selectedOption = selectOptions.filter((option) => options.jsonData.access === option.value);
return (
<div className="gf-form-group">
<h2>HTTP Address</h2>
<div className="gf-form">
<FormField
label="URL"
labelWidth={6}
inputWidth={20}
onChange={this.onURLChange}
value={jsonData.url || ''}
placeholder="url of your centreon instance"
/>
</div>
<div className="gf-form">
<Field>
<Select options={selectOptions} value={selectedOption} onChange={this.onProxyChange} />
</Field>
</div>
<div className="gf-form-inline">
<div className="gf-form">
<SecretFormField
isConfigured={secureJsonFields.apiKey}
value={secureJsonData.password || ''}
label="API Key"
placeholder="secure json field (backend only)"
labelWidth={6}
inputWidth={20}
onReset={this.onResetAPIKey}
onChange={this.onAPIKeyChange}
/>
</div>
</div>
</div>
);
}
}
import defaults from 'lodash/defaults';
import React, { ChangeEvent, PureComponent } from 'react';
import { LegacyForms } from '@grafana/ui';
import { QueryEditorProps } from '@grafana/data';
import { DataSource } from './datasource';
import { defaultQuery, MyDataSourceOptions, MyQuery } from './types';
const { FormField } = LegacyForms;
type Props = QueryEditorProps<DataSource, MyQuery, MyDataSourceOptions>;
export class QueryEditor extends PureComponent<Props> {
onQueryTextChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onChange, query } = this.props;
onChange({ ...query, queryText: event.target.value });
};
onConstantChange = (event: ChangeEvent<HTMLInputElement>) => {
const { onChange, query, onRunQuery } = this.props;
onChange({ ...query, constant: parseFloat(event.target.value) });
// executes the query
onRunQuery();
};
render() {
const query = defaults(this.props.query, defaultQuery);
const { queryText, constant } = query;
return (
<div className="gf-form">
<FormField
width={4}
value={constant}
onChange={this.onConstantChange}
label="Constant"
type="number"
step="0.1"
/>
<FormField
labelWidth={8}
value={queryText || ''}
onChange={this.onQueryTextChange}
label="Query Text"
tooltip="Not used yet"
/>
</div>
);
}
}
import defaults from 'lodash/defaults';
import {
DataQueryRequest,
DataQueryResponse,
DataSourceApi,
DataSourceInstanceSettings,
MutableDataFrame,
FieldType,
} from '@grafana/data';
import { MyQuery, MyDataSourceOptions, defaultQuery } from './types';
export class DataSource extends DataSourceApi<MyQuery, MyDataSourceOptions> {
constructor(instanceSettings: DataSourceInstanceSettings<MyDataSourceOptions>) {
super(instanceSettings);
}
async query(options: DataQueryRequest<MyQuery>): Promise<DataQueryResponse> {
const { range } = options;
const from = range!.from.valueOf();
const to = range!.to.valueOf();
// Return a constant for each query.
const data = options.targets.map(target => {
const query = defaults(target, defaultQuery);
return new MutableDataFrame({
refId: query.refId,
fields: [
{ name: 'Time', values: [from, to], type: FieldType.time },
{ name: 'Value', values: [query.constant, query.constant], type: FieldType.number },
],
});
});
return { data };
}
async testDatasource() {
// Implement a health check for your data source.
return {
status: 'success',
message: 'Success',
};
}
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 81.9 71.52"><defs><style>.cls-1{fill:#84aff1;}.cls-2{fill:#3865ab;}.cls-3{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" x1="42.95" y1="16.88" x2="81.9" y2="16.88" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2cc0c"/><stop offset="1" stop-color="#ff9830"/></linearGradient></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M55.46,62.43A2,2,0,0,1,54.07,59l4.72-4.54a2,2,0,0,1,2.2-.39l3.65,1.63,3.68-3.64a2,2,0,1,1,2.81,2.84l-4.64,4.6a2,2,0,0,1-2.22.41L60.6,58.26l-3.76,3.61A2,2,0,0,1,55.46,62.43Z"/><path class="cls-2" d="M37,0H2A2,2,0,0,0,0,2V31.76a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V2A2,2,0,0,0,37,0ZM4,29.76V8.84H35V29.76Z"/><path class="cls-3" d="M79.9,0H45a2,2,0,0,0-2,2V31.76a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V2A2,2,0,0,0,79.9,0ZM47,29.76V8.84h31V29.76Z"/><path class="cls-2" d="M37,37.76H2a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2H37a2,2,0,0,0,2-2V39.76A2,2,0,0,0,37,37.76ZM4,67.52V46.6H35V67.52Z"/><path class="cls-2" d="M79.9,37.76H45a2,2,0,0,0-2,2V69.52a2,2,0,0,0,2,2h35a2,2,0,0,0,2-2V39.76A2,2,0,0,0,79.9,37.76ZM47,67.52V46.6h31V67.52Z"/><rect class="cls-1" x="10.48" y="56.95" width="4" height="5.79"/><rect class="cls-1" x="17.43" y="53.95" width="4" height="8.79"/><rect class="cls-1" x="24.47" y="50.95" width="4" height="11.79"/><path class="cls-1" d="M19.47,25.8a6.93,6.93,0,1,1,6.93-6.92A6.93,6.93,0,0,1,19.47,25.8Zm0-9.85a2.93,2.93,0,1,0,2.93,2.93A2.93,2.93,0,0,0,19.47,16Z"/></g></g></svg>
\ No newline at end of file
import { DataSourcePlugin } from '@grafana/data';