# 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 }