@@ -70,7 +70,9 @@ def fetch_dashboards(self):
7070 def fetch_dashboard_details (self , uid ):
7171 try :
7272 url = '{}/api/dashboards/uid/{}' .format (self .__host , uid )
73+ print (url )
7374 response = requests .get (url , headers = self .headers , verify = self .__ssl_verify )
75+ print (response .text )
7476 if response and response .status_code == 200 :
7577 return response .json ()
7678 except Exception as e :
@@ -139,6 +141,212 @@ def fetch_dashboard_variable_label_values(self, promql_datasource_uid, label_nam
139141 logger .error (f"Exception occurred while fetching promql metric labels for { label_name } with error: { e } " )
140142 return []
141143
144+ def get_datasource_by_uid (self , ds_uid ):
145+ """Fetches datasource details by its UID."""
146+ try :
147+ url = f'{ self .__host } /api/datasources/uid/{ ds_uid } '
148+ response = requests .get (url , headers = self .headers , verify = self .__ssl_verify )
149+ if response and response .status_code == 200 :
150+ return response .json ()
151+ logger .error (f"Failed to get datasource for uid { ds_uid } . Status: { response .status_code } , Body: { response .text } " )
152+ return None
153+ except Exception as e :
154+ logger .error (f"Exception fetching datasource { ds_uid } : { e } " )
155+ return None
156+
157+ def get_default_datasource_by_type (self , ds_type ):
158+ """Fetches the default datasource of a given type."""
159+ try :
160+ datasources = self .fetch_data_sources ()
161+ if not datasources :
162+ return None
163+
164+ # Find the default datasource of the specified type
165+ for ds in datasources :
166+ if ds .get ('type' ) == ds_type and ds .get ('isDefault' , False ):
167+ return ds
168+ # If no default found, return the first one of that type
169+ for ds in datasources :
170+ if ds .get ('type' ) == ds_type :
171+ return ds
172+ return None
173+ except Exception as e :
174+ logger .error (f"Exception fetching default datasource of type { ds_type } : { e } " )
175+ return None
176+
177+ def get_dashboard_variables (self , dashboard_uid ):
178+ """
179+ Fetches and resolves all variables for a given Grafana dashboard.
180+ Handles dependencies between variables and supports multiple variable types.
181+ """
182+ import re
183+
184+ try :
185+ dashboard_data = self .fetch_dashboard_details (dashboard_uid )
186+
187+ if not dashboard_data or 'dashboard' not in dashboard_data :
188+ logger .error ("Could not fetch or parse dashboard data." )
189+ return {}
190+
191+ dashboard_json = dashboard_data ['dashboard' ]
192+ variables = dashboard_json .get ('templating' , {}).get ('list' , [])
193+
194+ resolved_variables = {}
195+
196+ for var in variables :
197+ var_name = var .get ('name' )
198+ var_type = var .get ('type' )
199+
200+ if not var_name or not var_type :
201+ continue
202+
203+ values = []
204+ if var_type == 'query' :
205+ values = self ._resolve_query_variable (var , resolved_variables )
206+ elif var_type == 'datasource' :
207+ values = self ._resolve_datasource_variable (var )
208+ elif var_type == 'custom' :
209+ query = self ._substitute_variables (var .get ('query' , '' ), resolved_variables )
210+ values = [v .strip () for v in query .split (',' )]
211+ elif var_type == 'constant' :
212+ values = [self ._substitute_variables (var .get ('query' , '' ), resolved_variables )]
213+ elif var_type == 'textbox' :
214+ current_val = var .get ('current' , {}).get ('value' )
215+ query_val = self ._substitute_variables (var .get ('query' , '' ), resolved_variables )
216+ values = [current_val or query_val ]
217+ elif var_type == 'interval' :
218+ query = self ._substitute_variables (var .get ('query' , '' ), resolved_variables )
219+ values = [v .strip () for v in query .split (',' )]
220+
221+ if values :
222+ resolved_variables [var_name ] = values
223+
224+ logger .info (f"For dashboard '{ dashboard_json .get ('title' )} ', fetched variable values: { resolved_variables } " )
225+ return {
226+ 'dashboard_title' : dashboard_json .get ('title' ),
227+ 'dashboard_uid' : dashboard_uid ,
228+ 'variables' : resolved_variables
229+ }
230+ except Exception as e :
231+ logger .error (f"Exception occurred while fetching dashboard variables for { dashboard_uid } : { e } " )
232+ return {}
233+
234+ def _substitute_variables (self , query_string , resolved_variables ):
235+ """Substitutes variables in query strings."""
236+ import re
237+
238+ for name , value in resolved_variables .items ():
239+ sub_value = value [0 ] if isinstance (value , list ) and value else (value if isinstance (value , str ) else "" )
240+ query_string = re .sub (r'\$' + re .escape (name ) + r'\b' , sub_value , query_string )
241+ query_string = re .sub (r'\$\{' + re .escape (name ) + r'\}' , sub_value , query_string )
242+ return query_string
243+
244+ def _resolve_datasource_variable (self , var ):
245+ """Resolves a 'datasource' type variable."""
246+ ds_type = var .get ('query' )
247+ if not ds_type :
248+ return []
249+
250+ try :
251+ datasources = self .fetch_data_sources ()
252+ if not datasources :
253+ return []
254+
255+ # Get all datasources of this type
256+ matching_datasources = [ds ['uid' ] for ds in datasources if ds .get ('type' ) == ds_type ]
257+
258+ # If the current value is 'default', we should return the UID of the default datasource
259+ current_value = var .get ('current' , {}).get ('value' )
260+ if current_value == 'default' :
261+ default_ds = self .get_default_datasource_by_type (ds_type )
262+ if default_ds :
263+ return [default_ds ['uid' ]]
264+
265+ return matching_datasources
266+ except Exception as e :
267+ logger .error (f"Exception fetching datasources: { e } " )
268+ return []
269+
270+ def _resolve_query_variable (self , var , resolved_variables ):
271+ """
272+ Resolves a 'query' type variable.
273+ Currently supports Prometheus datasources.
274+ """
275+ import re
276+
277+ datasource = var .get ('datasource' )
278+ query = var .get ('query' )
279+
280+ if not datasource or not query :
281+ return []
282+
283+ ds_uid = datasource .get ('uid' ) if isinstance (datasource , dict ) else datasource
284+ ds_uid = self ._substitute_variables (ds_uid , resolved_variables )
285+
286+ # Handle the case where ds_uid is "default" - need to resolve it to actual datasource
287+ if ds_uid == 'default' :
288+ ds_type = datasource .get ('type' ) if isinstance (datasource , dict ) else 'prometheus' # assume prometheus if not specified
289+ datasource_details = self .get_default_datasource_by_type (ds_type )
290+ if datasource_details :
291+ ds_uid = datasource_details ['uid' ]
292+ else :
293+ logger .warning (f"Could not find default datasource of type '{ ds_type } ' for query variable '{ var .get ('name' )} '." )
294+ return []
295+ else :
296+ datasource_details = self .get_datasource_by_uid (ds_uid )
297+
298+ if not datasource_details or datasource_details .get ('type' ) != 'prometheus' :
299+ logger .warning (f"Unsupported or unknown datasource type for query variable '{ var .get ('name' )} '." )
300+ return []
301+
302+ query = self ._substitute_variables (str (query ), resolved_variables )
303+
304+ # Prometheus query handling
305+ # Case 1: label_values(label) or label_values(metric, label)
306+ label_values_match = re .search (r'label_values\((?:.*\s*,\s*)?(\w+)\)' , query )
307+ if label_values_match :
308+ label = label_values_match .group (1 )
309+ return self .fetch_dashboard_variable_label_values (ds_uid , label )
310+
311+ # Case 2: metrics(pattern) -> label_values(__name__)
312+ if re .match (r'metrics\(.*\)' , query ):
313+ return self .fetch_dashboard_variable_label_values (ds_uid , '__name__' )
314+
315+ # Case 3: Generic PromQL query (including query_result(query))
316+ try :
317+ if query .startswith ('query_result(' ) and query .endswith (')' ):
318+ query = query [len ('query_result(' ):- 1 ]
319+
320+ url = f'{ self .__host } /api/datasources/proxy/uid/{ ds_uid } /api/v1/query'
321+ params = {'query' : query }
322+ response = requests .get (url , headers = self .headers , params = params , verify = self .__ssl_verify )
323+ if response and response .status_code == 200 :
324+ results = response .json ().get ('data' , {}).get ('result' , [])
325+ values = []
326+ for res in results :
327+ metric_labels = res .get ('metric' , {})
328+ metric_str = "{" + ", " .join ([f'{ k } ="{ v } "' for k ,v in metric_labels .items ()]) + "}"
329+
330+ if 'regex' in var and var ['regex' ]:
331+ match = re .search (var ['regex' ], metric_str )
332+ if match :
333+ values .append (match .group (1 ) if len (match .groups ()) > 0 else match .group (0 ))
334+ else :
335+ # Default behavior: extract value of a label if there is one other than __name__
336+ # otherwise, the __name__
337+ non_name_labels = {k : v for k , v in metric_labels .items () if k != '__name__' }
338+ if len (non_name_labels ) == 1 :
339+ values .append (list (non_name_labels .values ())[0 ])
340+ else :
341+ values .append (metric_labels .get ('__name__' , metric_str ))
342+ return sorted (list (set (values )))
343+ else :
344+ logger .error (f"Query failed for '{ query } '. Status: { response .status_code } , Body: { response .text } " )
345+ return []
346+ except Exception as e :
347+ logger .error (f"Exception during generic query execution for '{ query } ': { e } " )
348+ return []
349+
142350 def panel_query_datasource_api (self , tr : TimeRange , queries , interval_ms = 300000 ):
143351 try :
144352 if not queries or len (queries ) == 0 :
0 commit comments