0

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!

4
  • Your code sample doesn't include where first_dashboard_json is being set, and contains no debug values (e.g. the output of first_dashboard_json before and after editing it in the UI). Can you include the complete code and some values of the variables? Commented Nov 21, 2023 at 22:24
  • 1
    Hi Sébastien, thank you for your comment, I've just added all this. Commented Nov 22, 2023 at 9:06
  • second_dashboard_json most likely contains fields that aren't allowed when creating a new dashboard (e.g. url comes to mind, which can't ever be the same as the first dashboard). You will likely have to create a clean JSON body for the create API and use only the allowed fields. I expect you may only need a few fields, like panels. Commented Nov 22, 2023 at 14:35
  • Thanks. How do I know which fields are allowed, and which aren't? Commented Nov 22, 2023 at 20:42

1 Answer 1

1

@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 

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.