import copy import logging from .chart import Chart, get_am, io_task logger = logging.getLogger(__name__) class LineChart(Chart): def __init__(self, chart_conf): super().__init__(chart_conf) self._data_retrieval_task = {} self.am = {} self.max_value = 0 def prepare_data(self, recalculate=False, use_cache=True): """Fires off data retrieval in another thread""" if not self._data_retrieval_task: for lid, _ in enumerate(self.conf["lines"]): self._data_retrieval_task[lid] = self._get_analytics_data( lid, recalculate=recalculate, use_cache=use_cache ) return self @io_task def _get_analytics_data(self, lid, recalculate=False, use_cache=True, **kwargs): am = get_am(self.conf["lines"][lid]) self.am[lid] = am return am.get_result(recalculate=recalculate, use_cache=use_cache) def fill_json_template(self): """Filling json template with info from self.conf and data from retrieval_task""" return { "id": self.conf["id"], "type": self.conf["type"], "label": self.conf["label"], "options": self.conf["options"] if "options" in self.conf else {}, "data": self.map_multi_line(), "curve": self.conf["curve"] if "curve" in self.conf else "basis", "maxValue": self.max_value, } def map_multi_line(self): return [ { "id": lid, "label": line_conf["label"], "graph": self.map_module_result(lid), } for lid, line_conf in enumerate(self.conf["lines"]) ] def map_module_result(self, line_id): """Format the query result and make the proper labels for records""" am_data = self._data_retrieval_task[line_id].wait_for_result() am_map = self.am[line_id].get_output_map() data_map = { d: am_map[s].idx for d, s in self.conf["lines"][line_id]["output_maps"]["data"].items() } items = [] for data in am_data: item = {dest: data[source] for dest, source in data_map.items()} items.append(item) item_value = item["value"] if item_value > self.max_value: self.max_value = item_value return items def wait_for_done(self): no_errors = True if self._data_retrieval_task: for task in self._data_retrieval_task.values(): try: task.wait_for_result() except Exception as e: no_errors = False logger.exception(e) return no_errors @classmethod def get_series(cls, chart_conf, available_fields_list, schema, kwargs): if "lines" in chart_conf: return chart_conf["lines"] elif "dynamic" in chart_conf: return cls.generate_lines_conf( chart_conf, available_fields_list, schema, kwargs ) else: return [] @classmethod def generate_lines_conf(cls, chart_conf, available_fields_list, schema, kwargs): if "dynamic" in chart_conf: am_conf = chart_conf["dynamic"]["values"] attribute_id = available_fields_list.get( am_conf["inputs"]["field_name"], None ) if attribute_id is not None: am_conf["inputs"].update( {"schema": schema, "attribute_id": attribute_id} ) am_conf["inputs"].update(kwargs) values_am = get_am(am_conf) values = values_am.get_result(use_cache=False) out_map = am_conf["output_map"] am_map = values_am.get_output_map() lines = [] for value in values: val_dict = { val: value[am_map[val].idx] for val, idx in out_map.items() } template = copy.deepcopy(chart_conf["dynamic"]["template"]) template = cls.fill_template(template, val_dict) lines.append(template) chart_conf["lines"] = lines return chart_conf["lines"] return [] @classmethod def fill_template(cls, templ, val_dict): for key, val in templ.items(): if isinstance(val, dict): templ[key] = cls.fill_template(val, val_dict) elif isinstance(val, str): templ[key] = val.format_map(val_dict) else: raise RuntimeError( "Please only use strings and dicts in dynamic multiline template." ) return templ