You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
385 lines
13 KiB
385 lines
13 KiB
# Copyright (C) 2005-2024 Splunk Inc. All Rights Reserved.
|
|
from .utils import dict_merge, normalize_integer, normalize_color
|
|
from .vis.icon_collection import get_icons_collection
|
|
from .vis.drilldowns import transform_drilldowns, set_off_custom_drilldown_on
|
|
from .vis.thresholds import transform_thresholds_deprecated, transform_thresholds_singlevalue, \
|
|
transform_thresholds_singlevalueicon, transform_thresholds_line
|
|
|
|
from ui_utils.css_utils import (DEFAULT_TEXT_COLOR)
|
|
|
|
DEFAULT_FONT_SIZE = 12
|
|
DEFAULT_STROKE = 1
|
|
DEFAULT_STROKE_FOR_LINE_WITH_DECORATOR = 2
|
|
|
|
|
|
class VizConverter(object):
|
|
def __init__(self, session_key=None):
|
|
"""
|
|
Visualization converter
|
|
:param session_key: splunkd session key
|
|
"""
|
|
self.item = None
|
|
self.session_key = session_key
|
|
self.icon_collection = []
|
|
|
|
def convert(self, item, scale_factor):
|
|
"""
|
|
Takes an original widget data and produces a UDF vis object
|
|
:param item: original widget
|
|
:param scale_factor: scale factor applied to the classic GT
|
|
:return: UDF visualization object
|
|
"""
|
|
self.item = item
|
|
self.scale_factor = scale_factor
|
|
set_off_custom_drilldown_on(item)
|
|
|
|
name = item.get('name')
|
|
|
|
func_map = {
|
|
'Text': self.create_text,
|
|
'Rectangle': self.create_rectangle,
|
|
'Ellipse': self.create_ellipse,
|
|
'Connection': self.create_connection,
|
|
'Line': self.create_line,
|
|
'PolyIcon': self.create_icon,
|
|
'PolyImage': self.create_image,
|
|
'SingleValue': self.create_single_value,
|
|
'Sparkline': self.create_sparkline,
|
|
'SingleValueDelta': self.create_single_value_delta,
|
|
'Gauge': self.create_gauge,
|
|
'SquareWidget': self.upgrade_to_rectangle,
|
|
'CircularWidget': self.upgrade_to_ellipse
|
|
}
|
|
func = func_map.get(name)
|
|
if not func:
|
|
raise Exception('Unknown viz: type=%s name=%s' % (self.item.get('vizType'), self.item.get('name')))
|
|
return func()
|
|
|
|
def create_text(self):
|
|
bg_color = normalize_color(self.item.get('bgColor'))
|
|
font_color = normalize_color(self.item.get('fontColor'))
|
|
|
|
return {
|
|
'type': 'splunk.markdown',
|
|
'options': {
|
|
'markdown': self.item.get('text'),
|
|
'fontColor': font_color,
|
|
'backgroundColor': bg_color
|
|
}
|
|
}
|
|
|
|
def create_image(self):
|
|
return {
|
|
'type': 'splunk.image',
|
|
'options': {
|
|
'src': 'splunk-enterprise-kvstore://%s' % self.item.get('fileModel').get('_key')
|
|
}
|
|
}
|
|
|
|
def create_icon(self):
|
|
icon_id = self.item.get('iconId')
|
|
color = self.item.get('color')
|
|
bg_color = normalize_color(self.item.get('bgColor'))
|
|
color_calc = self.item.get('colorCalc')
|
|
svg_path = self.item.get('svgPath')
|
|
thresholds = None
|
|
|
|
if icon_id.endswith('Icon'):
|
|
# retrieve kvstore id for icons referred by name (old classic gt style)
|
|
if not self.icon_collection:
|
|
self.icon_collection = get_icons_collection(self.session_key)
|
|
for icon_obj in self.icon_collection:
|
|
if icon_obj.get('svg_path') == svg_path:
|
|
icon_id = icon_obj.get('_key')
|
|
break
|
|
|
|
options = {
|
|
'icon': 'splunk-enterprise-kvstore://' + icon_id,
|
|
'backgroundColor': bg_color,
|
|
'showValue': False
|
|
}
|
|
if color and (not color_calc or color_calc == 'static'):
|
|
options['iconColor'] = color
|
|
elif color_calc == 'adhoc' or color_calc == 'kpi':
|
|
thresholds = transform_thresholds_singlevalueicon(self.item)
|
|
|
|
res = {
|
|
'type': 'splunk.singlevalueicon',
|
|
'options': options
|
|
}
|
|
|
|
if thresholds:
|
|
dict_merge(res, thresholds)
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def create_single_value(self):
|
|
new_type = 'splunk.singlevalue'
|
|
unit = self.item.get('unit')
|
|
digit_precision = self.item.get('digitPrecision')
|
|
options = {
|
|
'trendDisplay': 'off',
|
|
'sparklineDisplay': 'off',
|
|
'showSparklineTooltip': True
|
|
}
|
|
if unit:
|
|
options['unit'] = unit
|
|
if digit_precision:
|
|
options['numberPrecision'] = digit_precision
|
|
|
|
res = {
|
|
'type': new_type,
|
|
'options': options
|
|
}
|
|
|
|
thresholds = transform_thresholds_singlevalue(self.item)
|
|
if thresholds:
|
|
dict_merge(res, thresholds)
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def create_sparkline(self):
|
|
new_type = 'splunk.singlevalue'
|
|
unit = self.item.get('unit')
|
|
digit_precision = self.item.get('digitPrecision')
|
|
options = {
|
|
'sparklineDisplay': 'before',
|
|
'trendDisplay': 'off',
|
|
'showSparklineTooltip': True
|
|
}
|
|
if unit:
|
|
options['unit'] = unit
|
|
if digit_precision:
|
|
options['numberPrecision'] = digit_precision
|
|
|
|
res = {
|
|
'type': new_type,
|
|
'options': options
|
|
}
|
|
|
|
thresholds = transform_thresholds_singlevalue(self.item)
|
|
if thresholds:
|
|
dict_merge(res, thresholds)
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def create_single_value_delta(self):
|
|
new_type = 'splunk.singlevalue'
|
|
unit = self.item.get('unit')
|
|
digit_precision = self.item.get('digitPrecision')
|
|
|
|
options = {
|
|
'trendDisplay': 'percent',
|
|
'sparklineDisplay': 'off'
|
|
}
|
|
if unit:
|
|
options['unit'] = unit
|
|
if digit_precision:
|
|
options['numberPrecision'] = digit_precision
|
|
|
|
res = {
|
|
'type': new_type,
|
|
'options': options
|
|
}
|
|
|
|
thresholds = transform_thresholds_singlevalue(self.item)
|
|
if thresholds:
|
|
dict_merge(res, thresholds)
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def create_gauge(self):
|
|
unit = self.item.get('unit')
|
|
digit_precision = self.item.get('digitPrecision')
|
|
|
|
options = {}
|
|
if unit:
|
|
options['unit'] = unit
|
|
if digit_precision:
|
|
options['numberPrecision'] = digit_precision
|
|
|
|
res = {
|
|
'type': 'splunk.singlevalueradial',
|
|
'options': options
|
|
}
|
|
thresholds = transform_thresholds_deprecated(self.item)
|
|
if thresholds:
|
|
dict_merge(res, thresholds)
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def create_rectangle(self):
|
|
color = normalize_color(self.item.get('color'))
|
|
bg_color = normalize_color(self.item.get('bgColor'))
|
|
stroke_width = normalize_integer(self.item.get('stroke', DEFAULT_STROKE), DEFAULT_STROKE)
|
|
|
|
res = {
|
|
'type': 'splunk.rectangle',
|
|
'options': {
|
|
'strokeColor': color,
|
|
'strokeWidth': int(stroke_width)
|
|
}
|
|
}
|
|
|
|
if self.item.get('statModel'):
|
|
# Indicating that the rectangle is associated with some search
|
|
thresholds = transform_thresholds_deprecated(self.item)
|
|
if thresholds:
|
|
if thresholds.get('options', {}).get('backgroundColor'):
|
|
# rectangle uses fill instead of backgroundColor
|
|
thresholds['options']['fill'] = thresholds['options'].pop('backgroundColor')
|
|
dict_merge(res, thresholds)
|
|
else:
|
|
res['options']['fill'] = bg_color
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def create_ellipse(self):
|
|
color = normalize_color(self.item.get('color'))
|
|
bg_color = normalize_color(self.item.get('bgColor'))
|
|
stroke_width = normalize_integer(self.item.get('stroke', DEFAULT_STROKE), DEFAULT_STROKE)
|
|
|
|
res = {
|
|
'type': 'splunk.ellipse',
|
|
'options': {
|
|
'strokeColor': color,
|
|
'strokeWidth': int(stroke_width)
|
|
}
|
|
}
|
|
|
|
if self.item.get('statModel'):
|
|
# Indicating that the ellipse is associated with some search
|
|
thresholds = transform_thresholds_deprecated(self.item)
|
|
if thresholds:
|
|
if thresholds.get('options', {}).get('backgroundColor'):
|
|
# ellipse uses fill instead of backgroundColor
|
|
thresholds['options']['fill'] = thresholds['options'].pop('backgroundColor')
|
|
dict_merge(res, thresholds)
|
|
else:
|
|
res['options']['fill'] = bg_color
|
|
|
|
drilldowns = transform_drilldowns(self.item, self.session_key)
|
|
if drilldowns:
|
|
dict_merge(res, drilldowns)
|
|
|
|
return res
|
|
|
|
def upgrade_to_rectangle(self):
|
|
self.item['stroke'] = 1
|
|
self.item['bgColor'] = '#FFFFFF'
|
|
self.item['color'] = DEFAULT_TEXT_COLOR
|
|
return self.create_rectangle()
|
|
|
|
def upgrade_to_ellipse(self):
|
|
self.item['stroke'] = 1
|
|
self.item['bgColor'] = '#FFFFFF'
|
|
self.item['color'] = DEFAULT_TEXT_COLOR
|
|
return self.create_ellipse()
|
|
|
|
def create_connection(self):
|
|
stroke_width = normalize_integer(self.item.get('stroke', DEFAULT_STROKE), DEFAULT_STROKE)
|
|
# UDF doesn't support transparent line so default to white
|
|
color = '#FFFFFF' if self.item.get('color') == 'none' else self.item.get('color')
|
|
options = {}
|
|
if stroke_width and stroke_width == DEFAULT_STROKE:
|
|
stroke_width = normalize_integer('', DEFAULT_STROKE_FOR_LINE_WITH_DECORATOR)
|
|
|
|
source_decorator = self.item.get('sourceDecorator')
|
|
target_decorator = self.item.get('targetDecorator')
|
|
options['fromArrow'] = True if source_decorator else False
|
|
options['toArrow'] = True if target_decorator else False
|
|
if color:
|
|
options['strokeColor'] = color
|
|
if stroke_width:
|
|
options['strokeWidth'] = int(stroke_width)
|
|
return {
|
|
'type': 'abslayout.line',
|
|
'options': options
|
|
}
|
|
|
|
def create_line(self):
|
|
stroke_width = normalize_integer(self.item.get('stroke', DEFAULT_STROKE), DEFAULT_STROKE)
|
|
# UDF doesn't support transparent line so default to white
|
|
color = '#FFFFFF' if self.item.get('color') == 'none' else self.item.get('color')
|
|
|
|
options = {}
|
|
|
|
classic_decorator_types = ['simple', 'triangle', 'diamond']
|
|
start_point_decorator_type = self.item.get('startPointDecoratorType')
|
|
end_point_decorator_type = self.item.get('endPointDecoratorType')
|
|
options['fromArrow'] = False
|
|
options['toArrow'] = False
|
|
if start_point_decorator_type in classic_decorator_types:
|
|
options['fromArrow'] = True
|
|
if end_point_decorator_type in classic_decorator_types:
|
|
options['toArrow'] = True
|
|
if options['fromArrow'] or options['toArrow']:
|
|
if stroke_width and stroke_width == DEFAULT_STROKE:
|
|
stroke_width = normalize_integer('', DEFAULT_STROKE_FOR_LINE_WITH_DECORATOR)
|
|
|
|
if stroke_width:
|
|
options['strokeWidth'] = int(stroke_width)
|
|
if color: # if static color
|
|
options['strokeColor'] = color
|
|
|
|
res = {
|
|
'type': 'abslayout.line',
|
|
'options': options
|
|
}
|
|
|
|
if self.item.get('statModel'):
|
|
thresholds = transform_thresholds_line(self.item)
|
|
if thresholds:
|
|
dict_merge(res, thresholds)
|
|
|
|
return res
|
|
|
|
def create_label(self, item):
|
|
label = item.get('label')
|
|
font_family = item.get('fontFamily') # pick first font from the list
|
|
font_family = [font.strip() for font in font_family.split(',')][0] if font_family else ''
|
|
bg_color = item.get('bgColor')
|
|
font_color = item.get('labelFontColor')
|
|
label_font_size = normalize_integer(self.item.get('labelFontSize', DEFAULT_FONT_SIZE), DEFAULT_FONT_SIZE)
|
|
label_font_size = int(label_font_size * self.scale_factor)
|
|
|
|
options = {
|
|
'content': label,
|
|
'fontSize': label_font_size
|
|
}
|
|
if font_family:
|
|
options['fontFamily'] = font_family
|
|
if bg_color:
|
|
options['backgroundColor'] = bg_color
|
|
if font_color:
|
|
options['textColor'] = font_color
|
|
|
|
return {
|
|
'type': 'splunk.markdown',
|
|
'options': options
|
|
}
|