diff --git a/atdb/taskdatabase/services/algorithms.py b/atdb/taskdatabase/services/algorithms.py index 054ad10b091c7bf3931a25874465a780968e5426..7baaa014d0599d4efdc2ca453207b966cfeaada7 100644 --- a/atdb/taskdatabase/services/algorithms.py +++ b/atdb/taskdatabase/services/algorithms.py @@ -4,7 +4,7 @@ Description: Business logic for ATDB. These functions are called from the views (views.py). """ -from django.db.models import Q,Sum +from django.db.models import Q, Sum import logging from .common import timeit from ..models import Task, LogEntry, Workflow @@ -16,6 +16,7 @@ DJANGO_TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" logger = logging.getLogger(__name__) + @timeit def get_size(status_list, type): """ @@ -24,7 +25,7 @@ def get_size(status_list, type): :return: summed sizes """ - logger.info("get_size("+str(status_list)+")") + logger.info("get_size(" + str(status_list) + ")") if type == 'processed': field = 'size_processed' @@ -49,7 +50,7 @@ def convert_logentries_to_html(log_entries): for log in log_entries: line = "<tr><td><b>" + str(log.service) + '</b></td>' line += "<td><b>" + str(log.step_name) + '</b></td>' - line +='<td class="' + log.status + '" >' + log.status + "</td>" + line += '<td class="' + log.status + '" >' + log.status + "</td>" try: line += "<td>" + str(log.timestamp.strftime("%m-%d-%Y, %H:%M:%S")) + "</td>" except: @@ -57,7 +58,7 @@ def convert_logentries_to_html(log_entries): line += "<td>" + str(log.cpu_cycles) + "</td>" line += "<td>" + str(log.wall_clock_time) + "</td>" - if log.url_to_log_file!=None: + if log.url_to_log_file != None: link = "<a href=" + '"' + str(log.url_to_log_file) + '" target="_blank">' + "logfile" + "</a>" else: link = "-" @@ -66,8 +67,8 @@ def convert_logentries_to_html(log_entries): results += "</tbody>" except Exception as err: - results = "<tr><td>"+str(err)+"</td></tr>" - #results = "<tr><td>no data</td></tr>" + results = "<tr><td>" + str(err) + "</td></tr>" + # results = "<tr><td>no data</td></tr>" return results @@ -85,10 +86,10 @@ def convert_list_of_dicts_to_html(my_blob): try: for my_dict in my_list: # iterate through the dict of key/values - for key,value in my_dict.items(): + for key, value in my_dict.items(): try: if "://" in value: - link = "<a href=" + '"' + value + '">' + key +"</a>" + link = "<a href=" + '"' + value + '">' + key + "</a>" value = link except: pass @@ -100,6 +101,48 @@ def convert_list_of_dicts_to_html(my_blob): return results +import xml.etree.ElementTree as ElementTree +from typing import Union, List, Dict + + +def _generate_html_from_json_tree(json_blob: Union[List, Dict], element: ElementTree.Element): + if isinstance(json_blob, list) or isinstance(json_blob, tuple): + + if element.tag != 'tbody': + sub_table = ElementTree.SubElement(element, 'table') + else: + sub_table = element + for item in json_blob: + row = ElementTree.SubElement(sub_table, 'tr') + element = ElementTree.SubElement(row, 'td') + _generate_html_from_json_tree(item, element) + + elif isinstance(json_blob, dict): + if element.tag != 'tbody': + sub_table = ElementTree.SubElement(element, 'table') + else: + sub_table = element + for key, value in json_blob.items(): + row = ElementTree.SubElement(sub_table, 'tr') + key_element = ElementTree.SubElement(row, 'td') + bold_key = ElementTree.SubElement(key_element, 'b') + bold_key.text = key + value_element = ElementTree.SubElement(row, 'td') + + _generate_html_from_json_tree(value, value_element) + + else: + value = ElementTree.SubElement(element, 'td', attrib={"style": "max-width:25rem"}) + value.text = str(json_blob) + + +def convert_json_to_nested_table(json_blob): + root_element = ElementTree.Element('tbody') + _generate_html_from_json_tree(json_blob, root_element) + + return ElementTree.tostring(root_element, method='xml').decode() + + def convert_config_to_html(querylist): results = "" try: @@ -111,12 +154,13 @@ def convert_config_to_html(querylist): try: if "://" in value: - link = "<a href=" + '"' + value + '">' + key +"</a>" + link = "<a href=" + '"' + value + '">' + key + "</a>" value = link except: pass - line = "<tr><td><b>" + str(filter) + "</b></td> <td><b>" + str(key) + "</b></td><td>" + str(value) + "</td></tr>" + line = "<tr><td><b>" + str(filter) + "</b></td> <td><b>" + str(key) + "</b></td><td>" + str( + value) + "</td></tr>" results = results + line except: results = "<tr><td>no data</td></tr>" @@ -126,7 +170,6 @@ def convert_config_to_html(querylist): # aggregate information from the tasks table per workflow per status def aggregate_resources_tasks(selection): - workflow_results = [] my_workflows = [] @@ -140,7 +183,7 @@ def aggregate_resources_tasks(selection): # construct the list of workflows (cheap) for w in active_workflows: try: - workflow = Workflow.objects.get(id = w['workflow']) + workflow = Workflow.objects.get(id=w['workflow']) my_workflows.append(workflow) except: pass @@ -209,7 +252,7 @@ def aggregate_resources_logs(selection): # construct the list of workflows (cheap) for w in active_workflows: try: - workflow = Workflow.objects.get(id = w['workflow']) + workflow = Workflow.objects.get(id=w['workflow']) my_workflows.append(workflow) except: pass @@ -226,8 +269,8 @@ def aggregate_resources_logs(selection): record = {} # aggregate logentries per step for all active statusses (expensive) - logs = LogEntry.objects.filter(status=status)\ - .filter(task__status__in=settings.ACTIVE_STATUSSES)\ + logs = LogEntry.objects.filter(status=status) \ + .filter(task__status__in=settings.ACTIVE_STATUSSES) \ .filter(task__workflow=workflow) sum_cpu_cycles = logs.aggregate(Sum('cpu_cycles')) @@ -242,13 +285,12 @@ def aggregate_resources_logs(selection): workflow_result['records_per_status'] = record_per_status workflow_results.append(workflow_result) - + return workflow_results # aggregate information from the logentries table per workflow per status def aggregate_resources_logs_version1(): - records = [] # get all active tasks @@ -263,18 +305,18 @@ def aggregate_resources_logs_version1(): workflow_result = {} # extract the workflow object (cheap) - workflow = Workflow.objects.get(id = w['workflow']) + workflow = Workflow.objects.get(id=w['workflow']) # aggregate logentries per step for all active statusses for status in settings.ACTIVE_STATUSSES: record = {} - record['name'] = str(workflow.id) +' - '+ workflow.workflow_uri + record['name'] = str(workflow.id) + ' - ' + workflow.workflow_uri # record['name'] = str(workflow.id) record['status'] = status # aggregate logentries per step for all active statusses (expensive) - logs = LogEntry.objects.filter(status=status)\ - .filter(task__status__in=settings.ACTIVE_STATUSSES)\ + logs = LogEntry.objects.filter(status=status) \ + .filter(task__status__in=settings.ACTIVE_STATUSSES) \ .filter(task__workflow=workflow) sum_cpu_cycles = logs.aggregate(Sum('cpu_cycles')) @@ -344,7 +386,6 @@ def human_readable(size_in_bytes): def highlight_value(values, value_to_highlight): - # find 'class' left of the value pos_value = values.find(str(value_to_highlight)) @@ -362,8 +403,8 @@ def highlight_value(values, value_to_highlight): return values -def construct_tasks_per_workflow_html(request, workflow_results): +def construct_tasks_per_workflow_html(request, workflow_results): # --- Progress of tasks per active workflow --- results_tasks = "<p>Progress of tasks per workflow</p>" @@ -390,7 +431,8 @@ def construct_tasks_per_workflow_html(request, workflow_results): percentage = round(int(workflow_result['size_processed']) / int(workflow_result['size_to_process']) * 100) except: percentage = 0 - values += "<td><b>size processed:</b> " + str(human_readable(workflow_result['size_processed'])) + " (<b>"+ str(percentage) + "%</b>) </td>" + values += "<td><b>size processed:</b> " + str( + human_readable(workflow_result['size_processed'])) + " (<b>" + str(percentage) + "%</b>) </td>" values += "<td><b>processing time:</b> " + str(workflow_result['total_processing_time']) + "</td>" values += "<td colspan='8'></td></tr><tr>" @@ -409,7 +451,7 @@ def construct_tasks_per_workflow_html(request, workflow_results): # distinguish active statusses style = "inactive" - if key in settings.ACTIVE_STATUSSES or key=='active': + if key in settings.ACTIVE_STATUSSES or key == 'active': style = "active" # bonus: add a query link @@ -417,15 +459,15 @@ def construct_tasks_per_workflow_html(request, workflow_results): values += "<td class=" + style + ">" + str(percentage) + "% (" + link + ")</td>" # add sizes -# values += "<td>" + str(human_readable(workflow_result['size_to_process'])) + "</td>" -# try: -# percentage = round(int(workflow_result['size_processed']) / int(workflow_result['size_to_process']) * 100) -# except: -# percentage = 0 -# values += "<td>" + str(human_readable(workflow_result['size_processed'])) + " ("+ str(percentage) + "%) </td>" -# values += "<td>" + str(workflow_result['total_processing_time']) + "</td>" - - if max>0: + # values += "<td>" + str(human_readable(workflow_result['size_to_process'])) + "</td>" + # try: + # percentage = round(int(workflow_result['size_processed']) / int(workflow_result['size_to_process']) * 100) + # except: + # percentage = 0 + # values += "<td>" + str(human_readable(workflow_result['size_processed'])) + " ("+ str(percentage) + "%) </td>" + # values += "<td>" + str(workflow_result['total_processing_time']) + "</td>" + + if max > 0: values = highlight_value(values, max) results_tasks += "</tr><tr>" + values + "</tr>" @@ -448,7 +490,7 @@ def construct_logs_per_workflow_html_version1(log_records): style = "active" line = "<tr><td><b>" + record['name'] + "</b></td>" \ - '<td class="' + style + '" >' + record['status'] + \ + '<td class="' + style + '" >' + record['status'] + \ "</td><td>" + str(record['cpu_cycles']) + \ "</td><td>" + str(record['wall_clock_time']) + "</td><tr>" @@ -475,25 +517,25 @@ def construct_logs_per_workflow_html(request, workflow_results): for status in records_per_status: - record = records_per_status[status] - # distinguish active statusses - style = "" - if status in settings.ACTIVE_STATUSSES or status=='active': + record = records_per_status[status] + # distinguish active statusses + style = "" + if status in settings.ACTIVE_STATUSSES or status == 'active': style = "active" - # show the values (done with a weird ternary operator) - if record['cpu_cycles']: - cpu_cycles = str(record['cpu_cycles']) - else: - cpu_cycles = '0' + # show the values (done with a weird ternary operator) + if record['cpu_cycles']: + cpu_cycles = str(record['cpu_cycles']) + else: + cpu_cycles = '0' - if record['wall_clock_time']: - wall_clock_time = str(record['wall_clock_time']) - else: - wall_clock_time = '0' + if record['wall_clock_time']: + wall_clock_time = str(record['wall_clock_time']) + else: + wall_clock_time = '0' - value = cpu_cycles + '/' + wall_clock_time - values += "<td class=" + style + ">" + value + "</td>" + value = cpu_cycles + '/' + wall_clock_time + values += "<td class=" + style + ">" + value + "</td>" results += "<tr>" + values + "</tr>" @@ -502,7 +544,6 @@ def construct_logs_per_workflow_html(request, workflow_results): def construct_dashboard_html(request, selection): - # gather and construct the dashboard based on the requested selection # --- Progress of tasks per active workflow --- @@ -515,5 +556,4 @@ def construct_dashboard_html(request, selection): log_records = aggregate_resources_logs(selection) results_logs = construct_logs_per_workflow_html(request, log_records) - return results_tasks,results_logs - + return results_tasks, results_logs diff --git a/atdb/taskdatabase/templates/taskdatabase/details/inputs.html b/atdb/taskdatabase/templates/taskdatabase/details/inputs.html index fa84403790f770fcc41086570b9aa6d4134062bf..3d6bb9935f8209171ea00bf59adab92d3b9d8abe 100644 --- a/atdb/taskdatabase/templates/taskdatabase/details/inputs.html +++ b/atdb/taskdatabase/templates/taskdatabase/details/inputs.html @@ -7,37 +7,10 @@ <div class="card-body"> <h3>Inputs </h3> <table class="table table-striped"> - <tbody id="inputs_table"> + <tbody> + {{ results | safe }} </tbody> </table> </div> </div> -<script> - var inputs_values = {{ resultsjson | safe }} - - function generate_tree(tree, div) { - if (Array.isArray(tree)) { - var array = $("<tbody></tbody>") - div.append(array) - for (let row_index in tree) { - var row = $('<tr></tr>') - generate_tree(tree[row_index], row) - array.append(row) - } - - } else if (typeof (tree) === 'object' && tree !== null) { - for (var att_name in tree) { - var row = $(`<tr><td><b>${att_name}</b></td></tr>`) - generate_tree(tree[att_name], row); - div.append( - row - ) - } - } else { - div.append(`<td>${tree}</td>`) - } - } - - generate_tree(inputs_values, $('#inputs_table')) -</script> {% endblock %} \ No newline at end of file diff --git a/atdb/taskdatabase/templates/taskdatabase/details/outputs.html b/atdb/taskdatabase/templates/taskdatabase/details/outputs.html index 91851d607e85fe9c255c468ddd69abafb4fd448d..9b31ef46489dcda8e6b305f102ae2bd95db78a67 100644 --- a/atdb/taskdatabase/templates/taskdatabase/details/outputs.html +++ b/atdb/taskdatabase/templates/taskdatabase/details/outputs.html @@ -6,39 +6,9 @@ <div class="card-body"> <h3>Outputs </h3> <table class="table table-striped"> - <tbody id="output_values"> - - </tbody> + {{ results | safe }} </table> </div> </div> - -<script> - var output_values = {{ resultsjson | safe }} - - function generate_tree(tree, div) { - if (Array.isArray(tree)) { - var array = $("<tbody></tbody>") - div.append(array) - for (let row_index in tree) { - var row = $('<tr></tr>') - generate_tree(tree[row_index], row) - array.append(row) - } - } else if (typeof (tree) === 'object' && tree !== null) { - for (var att_name in tree) { - var row = $(`<tr><td><b>${att_name}</b></td></tr>`) - generate_tree(tree[att_name], row); - div.append( - row - ) - } - } else { - div.append(`<td>${tree}</td>`) - } - } - - generate_tree(output_values, $('#output_values')) -</script> {% endblock %} \ No newline at end of file diff --git a/atdb/taskdatabase/views.py b/atdb/taskdatabase/views.py index 91714ac8f2d4bff1e73cd8b9eff67f3172505cce..57d6d022f4cbe4b3a975dd486729286c648ada04 100644 --- a/atdb/taskdatabase/views.py +++ b/atdb/taskdatabase/views.py @@ -266,16 +266,16 @@ def ShowInputs(request, id): task = Task.objects.get(id=id) # convert the json to a presentable piece of html for the output template - results = algorithms.convert_list_of_dicts_to_html(task.inputs) - return render(request, "taskdatabase/details/inputs.html", {'results': results, 'resultsjson': task.inputs}) + results = algorithms.convert_json_to_nested_table(task.inputs) + return render(request, "taskdatabase/details/inputs.html", {'results': results}) def ShowOutputs(request, id): task = Task.objects.get(id=id) - # convert the json to a presentable piece of html for the output template - results = algorithms.convert_list_of_dicts_to_html(task.outputs) - return render(request, "taskdatabase/details/outputs.html", {'results': results, 'resultsjson': task.outputs}) + results = algorithms.convert_json_to_nested_table(task.outputs) + + return render(request, "taskdatabase/details/outputs.html", {'results': results}) def ShowMetrics(request, id):