# panel_base.py
# V0.5.0 LDO 19/10/2022: initial version
# V0.5.1 LDO 12/11/2022: refactor modules
# V0.6.1 LDO 17/12/2022: release candidate 1
'''
grafanacode:
base class for generating panels
'''
#******************************************************************************
# EXTERNAL MODULE REFERENCES
#******************************************************************************
from attrs import define, field, Factory
from attr.validators import in_, instance_of, optional, deep_iterable
import grafanacode.c_units as UNITS
import grafanacode.c_colors as COLORS
from grafanacode import funcs
#******************************************************************************
# Panel Property classes: store panel property configuration parts
#******************************************************************************
[docs]@define(slots=False)
class PropColor:
'''
Dataclass encapsulating the panel colors attribute
'''
# logging level of this item
loglevel : int = 20
# attributes
color = field( default='SL_BLUE',
validator=optional(instance_of((str, COLORS.RGB, COLORS.RGBA))))
ashex = field( default=False,
validator=instance_of(bool))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
if dashboard is not None and dashboard.style == 'light':
htheme = 'light'
else:
htheme = 'dark'
hjson = COLORS.getColor(self.color, htheme, self.ashex)
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropGridPos:
'''
Dataclass encapsulating the panel gridposition attribute
'''
# logging level of this item
loglevel : int = 20
# attributes
h = field( default=0,
validator=instance_of(int))
w = field( default=0,
validator=instance_of(int))
x = field( default=0,
validator=instance_of(int))
y = field( default=0,
validator=instance_of(int))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
hjson = {'h': self.h, 'w': self.w, 'x': self.x, 'y': self.y}
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropLinksItem:
'''
Dataclass encapsulating one panel linkitem
'''
# logging level of this item
loglevel : int = 21
# attributes
title = field( default=None,
validator=instance_of(str))
url = field( default=None,
validator=instance_of(str))
newtab = field( default=None,
validator=instance_of(bool))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
hjson = {
'title' : self.title,
'url' : self.url,
'targetBlank' : self.newtab,
}
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropLinks:
'''
Dataclass encapsulating the panel links list attribute consisting of PropLinksItem
'''
# logging level of this item
loglevel : int = 20
# attributes
links = field( default=Factory(list),
validator=deep_iterable(
member_validator=instance_of(PropLinksItem),
iterable_validator=instance_of(list)))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
hjson = self.links
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropRepeat:
'''
Dataclass encapsulating the panel repeat attribute
'''
# logging level of this item
loglevel : int = 20
# attributes
variable = field( default=None,
validator=instance_of(str))
direction = field( default=None,
validator=in_(('h', 'v')))
maxperrow = field( default=None,
validator=optional(instance_of(int)))
@maxperrow.validator
# pylint: disable=invalid-name
def _check_maxperrow(self, attribute, value):
if ((value is not None) and not isinstance(value, int)):
raise ValueError(f'{attribute} should either be None or an integer')
# def getJson(self, dashboard=None, panel=None): # handled in panelbase
#**********************************************************
# Statusmap, Heatmap
#******************************************************************************
# PanelBaseExtCommon1 Property classes: store panel property configuration parts
#******************************************************************************
[docs]@define(slots=False)
class PropMappingsItem:
'''
Dataclass encapsulating one panel mappingsitem
'''
# logging level of this item
loglevel : int = 21
# attributes
mvalue = field( default=None,
validator=optional(instance_of((int, float, str))))
mfrom = field( default=None,
validator=optional(instance_of((int, float))))
mto = field( default=None,
validator=optional(instance_of((int, float))))
mregex = field( default=None,
validator=optional(instance_of(str)))
mspecial = field( default=None,
validator=optional(in_(['null', 'nan', 'null+nan', 'empty', 'true', 'false'])))
mtext = field( default=None,
validator=optional(instance_of(str)))
mcolor = field( default=None,
validator=optional(instance_of((str, PropColor, COLORS.RGB, COLORS.RGBA))))
mindex = field( init=False,
default=0) # no input, calculated afterwards in PropMappings.getJson
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
if type(self.mcolor)==str:
self.mcolor = PropColor(self.mcolor)
hresult = {'index': self.mindex}
if self.mtext is not None:
hresult['text'] = self.mtext
if self.mcolor is not None:
hresult['color'] = self.mcolor
hoptions = {}
if self.mvalue is not None:
htype = 'value'
hoptions[str(self.mvalue)] = hresult
elif self.mfrom is not None and self.mto is not None:
htype = 'range'
hoptions['from'] = self.mfrom
hoptions['to'] = self.mto
hoptions['result'] = hresult
elif self.mregex is not None:
htype = 'regex'
hoptions['pattern'] = self.mregex
hoptions['result'] = hresult
elif self.mspecial is not None:
htype = 'special'
hoptions['match'] = self.mspecial
hoptions['result'] = hresult
else:
raise ValueError('PropMappingsItem has no mvalue, mfrom/mto; mregex or mspecial')
hjson = {
'type' : htype,
'options' : hoptions,
}
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropMappings:
'''
Dataclass encapsulating the panel mappings list attribute consisting of PropMappingsItem
'''
# logging level of this item
loglevel : int = 20
# attributes
items = field( default=Factory(list),
validator=deep_iterable(
member_validator=instance_of(PropMappingsItem),
iterable_validator=instance_of(list)))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument, not-an-iterable
# set mappings indexes
if self.items:
i = 0
for item in self.items:
item.mindex = i
i += 1
return self.items
#**********************************************************
[docs]@define(slots=False)
class PropOverridesItem:
'''
Dataclass encapsulating one panel overrideitem
'''
# logging level of this item
loglevel : int = 21
# attributes
oname = field( default=None,
validator=optional(instance_of(str)))
oregex = field( default=None,
validator=optional(instance_of(str)))
otype = field( default=None,
validator=optional(instance_of(str)))
oquery = field( default=None,
validator=optional(instance_of(str)))
prop = field( default=None,
validator=instance_of(list))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
if self.oname is not None:
hmatcher = {'id': 'byName', 'options': self.oname}
elif self.oregex is not None:
hmatcher = {'id': 'byRegexp', 'options': self.oregex}
elif self.otype is not None:
hmatcher = {'id': 'byType', 'options': self.otype}
elif self.oquery is not None:
hmatcher = {'id': 'byFrameRefID', 'options': self.oquery}
else:
raise ValueError('PropOverridesItem has no oname, oregex; otype or oquery')
hjson = {
'matcher' : hmatcher,
'properties' : self.prop,
}
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropOverrides:
'''
Dataclass encapsulating the panel overrides list attribute consisting of PropOverridesItem
'''
# logging level of this item
loglevel : int = 20
# attributes
items = field( default=Factory(list),
validator=deep_iterable(
member_validator=instance_of(PropOverridesItem),
iterable_validator=instance_of(list)))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
return self.items
#**********************************************************
# Threshold items must be specified (numerical order)
[docs]@define(slots=False)
class PropThresholdsItem:
'''
Dataclass encapsulating one panel threshold item
'''
# logging level of this item
loglevel : int = 21
# attributes
color = field( default=PropColor('super-light-blue'),
validator=optional(instance_of((str, PropColor, COLORS.RGB, COLORS.RGBA))))
value = field( default=None,
validator=optional(instance_of((int, float))))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
if type(self.color)==str:
self.color = PropColor(self.color)
hjson = {
'color': self.color,
'value': None if self.value is None else self.value,
}
return hjson
#**********************************************************
[docs]@define(slots=False)
class PropThresholds:
'''
Dataclass encapsulating the panel thresholds list attribute consisting of PropThresholdsItem
'''
# logging level of this item
loglevel : int = 20
# attributes
items = field( default=Factory(list),
validator=deep_iterable(
member_validator=instance_of(PropThresholdsItem),
iterable_validator=instance_of(list)))
[docs] def getJ(self, fcolor):
'''
The fcolor attribute maps to the key <0> of the thresholds attribute.
Parameters:
fcolor (string): forecolor of the panel
Returns:
dict: items attribute
'''
# pylint: disable=no-member
if fcolor is not None:
self.items.insert(0, PropThresholdsItem(fcolor, None))
return self.items
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
return self.items
#******************************************************************************
# PanelBaseExtOpt2 Property classes: store panel property configuration parts
#******************************************************************************
[docs]@define(slots=False)
class PropLegend:
'''
Dataclass encapsulating the panel legend attribute
'''
# logging level of this item
loglevel : int = 20
# attributes
showlegend = field( default=False,
validator=instance_of(bool))
displaymode = field( default='table',
validator=optional(in_(('hidden', 'list', 'table'))))
placement = field( default='bottom',
validator=optional(in_(('bottom', 'right'))))
calcs = field( default=['lastNotNull', 'min', 'max', 'mean'],
validator=deep_iterable(
member_validator=in_(('lastNotNull','min','mean','max','last','firstNotNull','first','sum','count','range','delta','step',
'diff','logmin','allIsZero','allIsNull','changeCount','distinctCount','diffperc','allValues')),
iterable_validator=instance_of(list)))
values = field( default=[],
validator=deep_iterable(
member_validator=in_(('value','percent')),
iterable_validator=instance_of(list)))
values = field( default=Factory(list),
validator=instance_of(list))
width = field( default=None,
validator=optional(instance_of(int)))
[docs] def getJson(self, dashboard=None, panel=None):
'''
Create the json to pack this item.
Parameters:
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
hjson = {
'showLegend' : self.showlegend,
'displayMode' : self.displaymode,
'placement' : self.placement,
'calcs' : self.calcs,
'values' : self.values,
'width' : self.width,
}
return hjson
#******************************************************************************
# Panel Base Class
#******************************************************************************
[docs]@define(slots=False)
class PanelBase:
'''
Base Panel dataclass. This class encapsulates all properties of a single Grafana panel.
It creates the final panel JSON by means of its getJson method.
This class will not be called directly, but subclassed to specify a specific type of panel.
'''
# logging level of this item
loglevel : int = 10
logstr : str = ''
# attributes
#cachetimeout = field( default=None) # wasda?????
#editable = field( default=True,
# validator=instance_of(bool))
datasource = field( default=None,
validator=optional(instance_of(dict)))
gridpos = field( default=Factory(PropGridPos),
validator=instance_of(PropGridPos))
hidetimeoverride = field( default=None,
validator=optional(instance_of(bool))) # in Grafana GUI in query options
id = field( default=None,
validator=optional(instance_of(int)))
interval = field( default=None,
validator=optional(instance_of(int))) # in Grafana GUI in query options
links = field( default=Factory(PropLinks),
validator=instance_of(PropLinks))
maxdatapoints = field( default=None,
validator=optional(instance_of(int))) # in Grafana GUI in query options
repeat = field( default=None,
validator=optional(instance_of(PropRepeat)))
targets = field( default=None,
validator=optional(instance_of(list)))
timefrom = field( default=None,
validator=optional(instance_of(str))) # in Grafana GUI in query options
timeregions = field( default=None,
validator=optional(instance_of(list)))
timezone = field( default=None,
validator=optional(instance_of(str)))
timeshift = field( default=None,
validator=optional(instance_of(str))) # in Grafana GUI in query options
title = field( default='',
validator=instance_of(str))
@title.validator
# pylint: disable=invalid-name
def _check_title(self, attribute, value):
self.logstr = f'Panel name: {value}'
transformations = field( default=None,
validator=optional(instance_of(list)))
transparent = field( default=False,
validator=instance_of(bool))
# shortcuts and extra's
extraprops = field( default=None,
validator=optional(instance_of(dict)))
# message
def __attrs_post_init__(self):
'''
Attrs post init, just print a message.
'''
print (f' > Init Panel: {self.title} - PanelBase')
[docs] def getJson(self, customjson={}, dashboard=None, panel=None):
'''
Create the json to pack this panel.
Parameters:
customjson (dict): custom or child class json code to add
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
self.logstr = self.title
hjson = {
'gridPos' : self.gridpos,
'id' : self.id,
'links' : self.links,
'title' : self.title,
'transparent' : self.transparent,
}
# optional JSON
#if self.cachetimeout is not None:
# hjson['cacheTimeout'] = self.cachetimeout
#if self.editable is not None:
# hjson['editable'] = self.editable
if self.targets is not None:
hjson['targets'] = self.targets
if self.datasource is not None:
hjson['datasource'] = self.datasource
if self.interval is not None:
hjson['interval'] = self.interval
if self.hidetimeoverride is not None:
hjson['hideTimeOverride'] = self.hidetimeoverride
if self.maxdatapoints is not None:
hjson['maxDataPoints'] = self.maxdatapoints
if self.repeat is not None:
hjson['repeat'] = self.repeat.variable
hjson['repeatDirection'] = self.repeat.direction
hjson['maxPerRow'] = self.repeat.maxperrow
if self.timeregions is not None:
hjson['timeRegions'] = self.timeregions
if self.timezone is not None:
hjson['timezone'] = self.timezone
if self.timefrom is not None:
hjson['timeFrom'] = self.timefrom
if self.timeshift is not None:
hjson['timeShift'] = self.timeshift
if self.transformations is not None:
hjson['transformations'] = self.transformations
# merge child class JSON and extra prop
funcs.deepMerge(hjson, customjson)
funcs.deepMerge(hjson, self.extraprops)
return hjson
#******************************************************************************
# Base Class with some extra common properties
[docs]@define(slots=False)
class PanelBaseExtCommon1(PanelBase):
'''
Panel subclass with some additional attributes.
This class will not be called directly, but subclassed to specify a specific type of panel.
'''
# logging level of this item
loglevel : int = 11
# attributes
colorscheme = field( default='thresholds',
validator=in_(('value', 'fixed', 'thresholds', 'palette-classic', 'continuous-GrYlRd', 'continuous-RdYlGr', 'continuous-BlYlRd')))
decimals = field( default=None,
validator=optional(instance_of(int)))
displayname = field( default=None,
validator=optional(instance_of(str)))
fcolor = field( default=PropColor('super-light-blue'),
validator=optional(instance_of((str, PropColor, COLORS.RGB, COLORS.RGBA))))
mappings = field( default=Factory(PropMappings),
validator=instance_of(PropMappings))
max = field( default=None,
validator=optional(instance_of((int, float))))
min = field( default=None,
validator=optional(instance_of((int, float))))
novalue = field( default=None,
validator=optional(instance_of(str)))
overrides = field( default=Factory(PropOverrides),
validator=instance_of(PropOverrides))
thresholdmode = field( default='absolute',
validator=in_(('absolute', 'percentage')))
thresholds = field( default=Factory(PropThresholds),
validator=optional(instance_of(PropThresholds)))
units = field( default=None,
validator=optional(instance_of(str)))
# message
def __attrs_post_init__(self):
'''
Attrs post init, just print a message.
'''
print (f' > Init Panel: {self.title} - type: PanelBaseExtCommon1 ')
[docs] def getJson(self, customjson={}, dashboard=None, panel=None):
'''
Create the json to pack this panel.
Parameters:
customjson (dict): custom or child class json code to add
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
if type(self.fcolor)==str:
self.fcolor = PropColor(self.fcolor)
hjson = super().getJson({
'fieldConfig' : {
'defaults' : {
'mappings' : self.mappings,
'thresholds' : {
'mode' : self.thresholdmode,
},
},
'overrides' : self.overrides,
},
})
# optional JSON
if self.colorscheme is not None:
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'color', 'mode'], self.colorscheme)
if self.decimals is not None:
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'decimals'], self.decimals)
if self.decimals is not None:
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'decimals'], self.decimals)
if self.min is not None:
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'min'], self.min)
if self.max is not None:
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'max'], self.max)
if self.units is not None:
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'unit'], self.units)
# thresholds
funcs.addJsonItem(hjson, ['fieldConfig', 'defaults', 'thresholds', 'steps'], self.thresholds.getJ(self.fcolor))
# merge child class JSON
funcs.deepMerge(hjson, customjson)
return hjson
#******************************************************************************
# Base Class with some extra properties
[docs]@define(slots=False)
class PanelBaseExtOpt1(PanelBaseExtCommon1):
'''
Panel subclass with some additional attributes.
This class will not be called directly, but subclassed to specify a specific type of panel.
'''
# logging level of this item
loglevel : int = 12
# attributes
calcs = field( default=['lastNotNull'],
validator=deep_iterable(
member_validator=in_(('lastNotNull','min','mean','max','last','firstNotNull','first','sum','count','range','delta','step',
'diff','logmin','allIsZero','allIsNull','changeCount','distinctCount','diffperc','allValues')),
iterable_validator=instance_of(list)))
fields = field( default='',
validator=instance_of(str))
orientation = field( default='auto',
validator=optional(in_(['auto', 'horizontal', 'vertical'])))
reducelimit = field( default=25,
validator=instance_of(int)) # Max number of rows to display when reducevalues = AllValues=True
reducevalues = field( default=False,
validator=instance_of(bool)) # Calculate=False or AllValues=True
titlesize = field( default=None,
validator=optional(instance_of(int)))
valuesize = field( default=None,
validator=optional(instance_of(int)))
# message
def __attrs_post_init__(self):
'''
Attrs post init, just print a message.
'''
print (f' > Init Panel: {self.title} - type: PanelBaseExtOpt1 ')
[docs] def getJson(self, customjson={}, dashboard=None, panel=None):
'''
Create the json to pack this panel.
Parameters:
customjson (dict): custom or child class json code to add
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
hjson = super().getJson({
'options' : {
'orientation' : self.orientation,
'reduceOptions' : {
'calcs' : self.calcs,
'fields' : self.fields,
'values' : self.reducevalues,
'limit' : self.reducelimit,
},
},
})
# optional JSON
if self.titlesize is not None:
funcs.addJsonItem(hjson, ['options', 'text', 'titleSize'], self.titlesize)
if self.valuesize is not None:
funcs.addJsonItem(hjson, ['options', 'text', 'valueSize'], self.valuesize)
# merge child class JSON
funcs.deepMerge(hjson, customjson)
return hjson
#******************************************************************************
# Base Class with some extra properties
[docs]@define(slots=False)
class PanelBaseExtOpt2(PanelBaseExtCommon1):
'''
Panel subclass with some additional attributes.
This class will not be called directly, but subclassed to specify a specific type of panel.
'''
# logging level of this item
loglevel : int = 12
# attributes
datalinks = field( default=Factory(PropLinks),
validator=instance_of(PropLinks))
fillopacity = field( default=0,
validator=instance_of(int))
gradientmode = field( default='none',
validator=optional(in_(['none', 'opacity', 'hue', 'scheme'])))
hidefromgraph = field( default=False,
validator=instance_of(bool))
hidefromlegend = field( default=False,
validator=instance_of(bool))
hidefromtooltip = field( default=False,
validator=instance_of(bool))
hidefromviz = field( default=False,
validator=instance_of(bool))
legend = field( default=Factory(PropLegend),
validator=instance_of(PropLegend))
linewidth = field( default=1,
validator=instance_of(int))
tooltipmode = field( default='single',
validator=optional(in_(('single', 'multi', 'none'))))
tooltipsort = field( default='none',
validator=optional(in_(['none', 'asc', 'desc'])))
# message
def __attrs_post_init__(self):
'''
Attrs post init, just print a message.
'''
print (f' > Init Panel: {self.title} - type: PanelBaseExtOpt2 ')
[docs] def getJson(self, customjson={}, dashboard=None, panel=None):
'''
Create the json to pack this panel.
Parameters:
customjson (dict): custom or child class json code to add
dashboard (Dashboard): Dashboard object we are in
panel (Panel_base): Panel object we are in
Returns:
dict: panel json code
'''
# pylint: disable=unused-argument
hjson = super().getJson({
'fieldConfig' : {
'defaults' : {
'custom' : {
'gradientMode' : self.gradientmode,
'hideFrom' : {
'tooltip' : self.hidefromtooltip,
'viz' : self.hidefromviz,
'legend' : self.hidefromlegend,
'graph' : self.hidefromgraph,
},
'lineWidth' : self.linewidth,
'fillOpacity' : self.fillopacity,
},
'links' : self.datalinks,
},
},
'options' : {
'legend' : self.legend,
'tooltip' : {
'mode' : self.tooltipmode,
'sort' : self.tooltipsort,
}
},
})
# merge child class JSON
funcs.deepMerge(hjson, customjson)
return hjson
#******************************************************************************
# EXTRACT FUNCTIONS : EXTRACT A CREATION SCRIPT FROM A JSON
#******************************************************************************
#**********************************************************
# some common extracts grouped
#**********************************************************
#**********************************************************