My goal is to programmatically duplicate a Grafana dashboard.
I couldn't find this feature in the API doc so I thought pasting dashboard1's JSON into dashboard2 would achieve the same results.
Here is what I'm doing:
Python 3, in a Jupyter Notebook:
1 - create my first dashboard:
first_dashboard = {
"dashboard": {
"id": "",
"uid": "",
"title": "this is a first dashboard",
"tags": [ "automated" ],
"timezone": "browser",
"schemaVersion": 16,
"refresh": "25s"
},
"folderUid": "",
"message": "",
"overwrite": False
}
first_dashboard = json.dumps(first_dashboard)
r = s.post(api_create_dashboard, data=first_dashboard)
r.status_code
The first dashboard is successfully created.
2 - from Grafana's GUI, I make a few changes to my first dashboard so it looks like this:
3 - from the dashboard's URL, get its uid, and use it to get the dashboard's JSON:
r1 = s.get(api_get_dashboard_by_uid + first_dashboard_uid)
first_dashboard_json = r1.json()
first_dashboard_json
The changes I've made from the GUI show in the returned dict:
{'meta': {'type': 'db',
'canSave': True,
'canEdit': True,
'canAdmin': True,
'canStar': True,
'canDelete': True,
'slug': 'this-is-a-first-dashboard',
'url': '/d/f26861d9-0ed9-44df-843c-578186f5d38f/this-is-a-first-dashboard',
'expires': '0001-01-01T00:00:00Z',
'created': '2023-11-22T08:41:09Z',
'updated': '2023-11-22T08:44:57Z',
'updatedBy': 'dashboard-manager',
'createdBy': 'Anonymous',
'version': 2,
'hasAcl': False,
'isFolder': False,
'folderId': 0,
'folderUid': '',
'folderTitle': 'General',
'folderUrl': '',
'provisioned': False,
'provisionedExternalId': '',
'annotationsPermissions': {'dashboard': {'canAdd': True,
'canEdit': True,
'canDelete': True},
'organization': {'canAdd': True, 'canEdit': True, 'canDelete': True}}},
'dashboard': {'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,
'id': 44,
'links': [],
'liveNow': False,
'panels': [{'datasource': {'type': 'mysql',
'uid': 'b204da61-c9be-4556-87ed-2c875554dfb3'},
'fieldConfig': {'defaults': {'color': {'mode': 'thresholds'},
'custom': {'align': 'auto',
'cellOptions': {'type': 'auto'},
'inspect': False},
'mappings': [],
'thresholds': {'mode': 'absolute',
'steps': [{'color': 'green', 'value': None},
{'color': 'red', 'value': 80}]}},
'overrides': []},
'gridPos': {'h': 8, 'w': 12, 'x': 0, 'y': 0},
'id': 2,
'options': {'cellHeight': 'sm',
'footer': {'countRows': False,
'fields': '',
'reducer': ['sum'],
'show': False},
'showHeader': True},
'pluginVersion': '10.2.0',
'targets': [{'dataset': 'poc_finops_db_rec',
'datasource': {'type': 'mysql',
'uid': 'b204da61-c9be-4556-87ed-2c875554dfb3'},
'editorMode': 'builder',
'format': 'table',
'rawSql': 'SELECT COUNT(Currency) FROM poc_finops_db_rec.`012021` LIMIT 50 ',
'refId': 'A',
'sql': {'columns': [{'name': 'COUNT',
'parameters': [{'name': 'Currency', 'type': 'functionParameter'}],
'type': 'function'}],
'groupBy': [{'property': {'type': 'string'}, 'type': 'groupBy'}],
'limit': 50},
'table': '`012021`'}],
'title': 'this is a new title',
'type': 'table'},
{'collapsed': True,
'gridPos': {'h': 1, 'w': 24, 'x': 0, 'y': 8},
'id': 1,
'panels': [],
'title': 'this is a first row',
'type': 'row'}],
'refresh': '25s',
'schemaVersion': 38,
'tags': ['automated'],
'templating': {'list': []},
'time': {'from': 'now-6h', 'to': 'now'},
'timepicker': {},
'timezone': 'browser',
'title': 'this is a first dashboard',
'uid': 'f26861d9-0ed9-44df-843c-578186f5d38f',
'version': 2,
'weekStart': ''}}
OK, now let's create a 2nd dashboard:
second_dashboard = {
"dashboard": {
"id": "",
"uid": "",
"title": "this is a second, empty dashboard",
"tags": [ "automated" ],
"timezone": "browser",
"schemaVersion": 16,
"refresh": "25s"
},
"folderUid": "",
"message": "",
"overwrite": False
}
second_dashboard = json.dumps(second_dashboard)
r = s.post(api_create_dashboard, data=second_dashboard)
r.status_code
Now, let's try to paste the first dashboard's JSON into the second dashboard's, so that the second dashboard becomes a "copy" of the first one. This is where I'm stuck. Here's what I'm doing:
# Get second dashboard's json
r2 = s.get(api_get_dashboard_by_uid + second_dashboard_uid)
second_dashboard_json = r2.json()
# copy the "dashboard" node - which contains the first dashboard's uid
second_dashboard_json['dashboard'] = first_dashboard_json['dashboard']
# replace uid with the second dashboard's uid
second_dashboard_json['uid'] = second_dashboard_uid
at this stage, second_dashboard_json looks like this:
{'meta': {'type': 'db',
'canSave': True,
'canEdit': True,
'canAdmin': True,
'canStar': True,
'canDelete': True,
'slug': 'this-is-a-second-empty-dashboard',
'url': '/d/a27120af-2f98-4775-8db6-6b746be64d32/this-is-a-second-empty-dashboard',
'expires': '0001-01-01T00:00:00Z',
'created': '2023-11-22T08:48:03Z',
'updated': '2023-11-22T08:48:03Z',
'updatedBy': 'Anonymous',
'createdBy': 'Anonymous',
'version': 1,
'hasAcl': False,
'isFolder': False,
'folderId': 0,
'folderUid': '',
'folderTitle': 'General',
'folderUrl': '',
'provisioned': False,
'provisionedExternalId': '',
'annotationsPermissions': {'dashboard': {'canAdd': True,
'canEdit': True,
'canDelete': True},
'organization': {'canAdd': True, 'canEdit': True, 'canDelete': True}}},
'dashboard': {'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,
'id': 44,
'links': [],
'liveNow': False,
'panels': [{'datasource': {'type': 'mysql',
'uid': 'b204da61-c9be-4556-87ed-2c875554dfb3'},
'fieldConfig': {'defaults': {'color': {'mode': 'thresholds'},
'custom': {'align': 'auto',
'cellOptions': {'type': 'auto'},
'inspect': False},
'mappings': [],
'thresholds': {'mode': 'absolute',
'steps': [{'color': 'green', 'value': None},
{'color': 'red', 'value': 80}]}},
'overrides': []},
'gridPos': {'h': 8, 'w': 12, 'x': 0, 'y': 0},
'id': 2,
'options': {'cellHeight': 'sm',
'footer': {'countRows': False,
'fields': '',
'reducer': ['sum'],
'show': False},
'showHeader': True},
'pluginVersion': '10.2.0',
'targets': [{'dataset': 'poc_finops_db_rec',
'datasource': {'type': 'mysql',
'uid': 'b204da61-c9be-4556-87ed-2c875554dfb3'},
'editorMode': 'builder',
'format': 'table',
'rawSql': 'SELECT COUNT(Currency) FROM poc_finops_db_rec.`012021` LIMIT 50 ',
'refId': 'A',
'sql': {'columns': [{'name': 'COUNT',
'parameters': [{'name': 'Currency', 'type': 'functionParameter'}],
'type': 'function'}],
'groupBy': [{'property': {'type': 'string'}, 'type': 'groupBy'}],
'limit': 50},
'table': '`012021`'}],
'title': 'this is a new title',
'type': 'table'},
{'collapsed': True,
'gridPos': {'h': 1, 'w': 24, 'x': 0, 'y': 8},
'id': 1,
'panels': [],
'title': 'this is a first row',
'type': 'row'}],
'refresh': '25s',
'schemaVersion': 38,
'tags': ['automated'],
'templating': {'list': []},
'time': {'from': 'now-6h', 'to': 'now'},
'timepicker': {},
'timezone': 'browser',
'title': 'this is a first dashboard',
'uid': 'a27120af-2f98-4775-8db6-6b746be64d32',
'version': 2,
'weekStart': ''}}
I then POST it:
# Now let's POST this updated data to our second dashboard
r3 = s.post(api_create_dashboard, data = json.dumps(second_dashboard_json))
r3.status_code
It returns 400. I've tried removing second_dashboard_json's "meta" node, but I am still getting 400.
What am I doing wrong? Any help would be much appreciated. Thanks!
@Sébastien Vercammen put me on the right track. I was able to create a brand new dashboard containing elements from a preexisting one by making a POST requests whose payload was the one in this example, to which I added the preexisting dashboard's
['dashboard']['panels']
node. Thanks Sébastien!Here is my code: