Merge "Add import of json files"

This commit is contained in:
Zuul 2021-05-07 01:31:49 +00:00 committed by Gerrit Code Review
commit 39d8bd1d84
6 changed files with 182 additions and 17 deletions

View File

@ -52,6 +52,11 @@ For example, here is a minimal dashboard specification
environments. Users can specify their dashboards via a normal review environments. Users can specify their dashboards via a normal review
process and tests can validate their correctness. process and tests can validate their correctness.
The tool can also take JSON manually exported from the Grafana
interface and load it as a dashboard. This allows keeping dashboards
that have been edited with the inbuilt editor externally version
controlled.
A large number of examples are available in the OpenStack A large number of examples are available in the OpenStack
`project-config `project-config
<https://git.openstack.org/cgit/openstack-infra/project-config/tree/grafana>`__ <https://git.openstack.org/cgit/openstack-infra/project-config/tree/grafana>`__

View File

@ -38,7 +38,8 @@ Update Command
``grafana-dashboard`` [options] update <path> ``grafana-dashboard`` [options] update <path>
Updates each specified dashboard to the lastest layout from parsed yaml files. Updates each specified dashboard to the lastest layout from parsed
yaml or json files.
FILES FILES
===== =====

View File

@ -51,7 +51,8 @@ class Builder(object):
files_to_process.extend([os.path.join(path, f) files_to_process.extend([os.path.join(path, f)
for f in os.listdir(path) for f in os.listdir(path)
if (f.endswith('.yaml') if (f.endswith('.yaml')
or f.endswith('.yml'))]) or f.endswith('.yml')
or f.endswith('.json'))])
else: else:
files_to_process.append(path) files_to_process.append(path)

View File

@ -50,21 +50,29 @@ class YamlParser(object):
def parse_fp(self, fp): def parse_fp(self, fp):
data = yaml.safe_load(fp) data = yaml.safe_load(fp)
result = self.validate(data) # Since a json file is valid YAML, we just pass through
for item in result.items(): # any JSON files
group = self.data.get(item[0], {}) if fp.name.endswith('.json'):
# Create slug to make it easier to find dashboards. slug = slugify(data['title'])
if item[0] == 'dashboard': if not self.data.get('dashboard'):
name = item[1]['title'] self.data['dashboard'] = {}
else: self.data['dashboard'][slug] = data
name = item[1]['name'] else:
slug = slugify(name) result = self.validate(data)
if slug in group: for item in result.items():
raise Exception( group = self.data.get(item[0], {})
"Duplicate {0} found in '{1}: '{2}' " # Create slug to make it easier to find dashboards.
"already defined".format(item[0], fp.name, name)) if item[0] == 'dashboard':
group[slug] = item[1] name = item[1]['title']
self.data[item[0]] = group else:
name = item[1]['name']
slug = slugify(name)
if slug in group:
raise Exception(
"Duplicate {0} found in '{1}: '{2}' "
"already defined".format(item[0], fp.name, name))
group[slug] = item[1]
self.data[item[0]] = group
def validate(self, data): def validate(self, data):
schema = Schema() schema = Schema()

View File

@ -0,0 +1,141 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": 38,
"links": [],
"panels": [
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": null,
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 9,
"w": 12,
"x": 0,
"y": 0
},
"hiddenSeries": false,
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"dataLinks": []
},
"percentage": false,
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"refId": "A",
"target": "stats.haproxy.balance_git_http.gitea01.opendev.org.bout"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Fresh update",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
}
],
"schemaVersion": 25,
"style": "dark",
"tags": [],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "",
"title": "test json",
"uid": "M-GEcyWMk",
"version": 1
}

View File

@ -100,3 +100,12 @@ class TestCaseParser(TestCase):
def _get_empty_dashboard(self, name): def _get_empty_dashboard(self, name):
res, md5 = self.parser.get_dashboard(name) res, md5 = self.parser.get_dashboard(name)
self.assertEqual(res, None) self.assertEqual(res, None)
def test_parse_json(self):
path = os.path.join(
os.path.dirname(__file__),
'fixtures/parser/json-dashboard-0001.json')
self.parser.parse(path)
# Get parsed dashboard
res, md5 = self.parser.get_dashboard('test-json')
self.assertEqual(res['title'], 'test json')