How to programmatically copy a dashboard's layout to another in Grafana?

152 views Asked by At

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:

enter image description here

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!

1

There are 1 answers

0
Vincent Tep On

@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:

# setup
import requests


myToken = '' # your token goes here
url = '' # your grafana instance's url goes here
headers = {
    "Authorization": "Bearer "+ myToken,
    "Accept": "application/json",
    "Content-Type": "application/json",
}

# Creating session
s = requests.Session()
s.headers = headers

# API call functions

# GET
api_search = url + "/api/search?query=&"
api_get_dashboard_by_uid = url + "/api/dashboards/uid/"
api_get_all_folders = url + "/api/folders"

# POST
api_create_dashboard = url + "/api/dashboards/db"
api_create_folder = url + "/api/folders"


# sample payload from https://grafana.com/docs/grafana/latest/developers/http_api/dashboard/#create--update-dashboard

payload_create_dashboard = {
  "dashboard": {
    "id": "",
    "uid": "",
    "title": "Replace this sample title with your own.",
    "tags": [ "automated" ],
    "timezone": "browser",
    "schemaVersion": 16,
    "refresh": "25s"
  },
  "folderUid": "",
  "message": "",
  "overwrite": False
}


# 1 - use sample payload to create first dashboard
first_dashboard = copy.deepcopy(payload_create_dashboard)

first_dashboard = json.dumps(first_dashboard)

r = s.post(api_create_dashboard, data=first_dashboard)
r.status_code #200

# 2 - go to Graphana's graphical user interface and make modifications to the dashboard you just created + copy its uid

# 3 - retrieve newly-created dashboard's json
targetDashboardUid = '' # uid of your first dashboard (you can find it in its url)
myRequest = s.get(api_get_dashboard_by_uid + targetDashboardUid)
targetDashboardLayout = myRequest.json()
targetDashboardLayout

# 4 - from this json, extract only the ['dashboard']['panels'] node and put it into a copy of the sample payload

first_dashboard_edited = copy.deepcopy(payload_create_dashboard)
first_dashboard_edited['dashboard']['panels'] = targetDashboardLayout['dashboard']['panels']
first_dashboard_edited['dashboard']['title'] = 'this new dashboard should contain 1 row & 1 panel.'
first_dashboard_edited

# 5 - create a new dashboard using this modified sample payload
first_dashboard_edited= json.dumps(first_dashboard_edited)
myRequest = s.post(api_create_dashboard, data=first_dashboard_edited)
myRequest.status_code # 200