Source code for rubin_nights.scriptqueue_formatting

import html

import numpy as np
import pandas as pd
import yaml

from .ts_xml_enums import CategoryIndexExtended

__all__ = ["get_name_and_color_from_category_index", "format_html"]


# Define name and colours from salIndex
def get_name_and_color_from_category_index(
    category_index: int, unknown_color: str = "#f9f9f9"
) -> tuple[str, str]:
    # Colors from https://medialab.github.io/iwanthue/
    return {
        CategoryIndexExtended.NARRATIVE_LOG_OTHER.value: ("Narrative log", "#cf7ddc"),
        CategoryIndexExtended.NARRATIVE_LOG_SIMONYI.value: ("Narrative log MT", "#cf7ddc"),
        CategoryIndexExtended.NARRATIVE_LOG_AUX.value: ("Narrative log AT", "#cf7ddc"),
        CategoryIndexExtended.OBSERVATORY_STATUS_SIMONYI.value: ("Observatory Status MT", "#cf7ddc"),
        CategoryIndexExtended.MAIN_TEL.value: ("MTQueue", "#b4c546"),
        CategoryIndexExtended.AUX_TEL.value: ("ATQueue", "#bab980"),
        CategoryIndexExtended.OCS.value: ("OCSQueue", "#b2baad"),
        CategoryIndexExtended.ERRORS_OTHER.value: ("Error", "#9cb5d5"),
        CategoryIndexExtended.ERRORS_SIMONYI.value: ("Error MT", "#9cb5d5"),
        CategoryIndexExtended.ERRORS_AUX.value: ("Error AT", "#9cb5d5"),
        CategoryIndexExtended.EXP_SIMONYI.value: ("Simonyi Exposure", "#b6ecf5"),
        CategoryIndexExtended.EXP_AUX.value: ("Auxtel Exposure", "#d8f1f5"),
        CategoryIndexExtended.AUTOLOG_OTHER.value: ("Autolog", "#e78bb8"),
        CategoryIndexExtended.AUTOLOG_SIMONYI.value: ("Autolog MT", "#e78bb8"),
        CategoryIndexExtended.AUTOLOG_AUX.value: ("Autolog AT", "#e78bb8"),
    }.get(category_index, ("??", unknown_color))


# Custom formatter to handle YAML-like strings with dynamic background colors
def format_config_as_yaml_with_colors(row: pd.Series) -> str:
    config_value = row["config"]
    category_index = row["category_index"]
    script_salindex = row["script_salIndex"]

    # Define background colors based on salIndex
    background_color = get_name_and_color_from_category_index(category_index)[1]

    # are the script_salIndex and sal_index possibly containing yaml?
    might_be_yaml = (script_salindex > 0) and (category_index in [1, 2, 3])
    # Is the config_value a string?
    might_be_yaml = might_be_yaml and isinstance(config_value, str) and len(config_value) > 0
    # And is the config_value not a Traceback
    might_be_yaml = might_be_yaml and not config_value.startswith("Traceback")

    if might_be_yaml:
        try:
            # Parse the YAML-like string
            parsed_yaml = yaml.safe_load(config_value)
            # Format back to YAML with proper indentation
            formatted_yaml = yaml.dump(parsed_yaml, default_flow_style=False)
            return (
                f"<pre style='background: {background_color}; "
                f"padding: 10px; border: 1px solid #ddd; margin: 0;'>"
                f"{formatted_yaml}</pre>"
            )
        except yaml.YAMLError:
            # If parsing fails, return as plain text in a styled <pre> block
            return (
                f"<pre style='background: {background_color}; "
                f"padding: 10px; border: 1px solid #ddd; margin: 0;'>"
                f"{config_value}</pre>"
            )
    elif config_value.startswith("Traceback"):
        return f"<pre style='background: {background_color}'>{config_value}</pre>"
    else:
        return config_value  # Return as-is if salIndex is 0 or invalid type


def format_description(row: pd.Series) -> str:
    if row.description.startswith("<a href"):
        return row.description
    else:
        # Escape these in particular because of tracebacks
        return html.escape(row.description)


[docs] def format_html( efd_and_messages: pd.DataFrame, cols: list, time_order: str, show_category_index: list[int] | None = None, ) -> str: """Format large report generated by scriptqueue.get_consolidated_messages into a color-coded, yaml-formatted HTML table. Parameters ---------- efd_and_messages The output of scriptqueue.get_consolidated_messages, generally. Could be any dataframe matching the general expected format. cols The columns to show from efd_and_messages. Generally will be the columns returned as the second return value from scriptqueue.get_consolidated_messages. time_order Show messages in `newest first` or not. show_category_index Which category_indexes to show in the formatted html. Returns ------- html : `str` An HTML table with the formatted scriptqueue + messages values. """ if show_category_index is not None: keep = np.zeros(len(efd_and_messages), dtype=bool) for si in show_category_index: keep |= efd_and_messages.category_index == si efd_and_messages = efd_and_messages[keep] def highlight_salindex(s: pd.Series) -> list[str]: return [f"background-color: {get_name_and_color_from_category_index(s.category_index)[1]}"] * len(s) msg = ["Color coding by "] for i in np.sort(efd_and_messages.category_index.unique()): what, color = get_name_and_color_from_category_index(i) msg.append(f" <font style='background-color: {color[0:]};'>{what}</font> ") html_values = " ".join(msg) if time_order == "newest first": efd_and_messages = efd_and_messages[::-1] # Apply yaml-like formatting conditionally efd_and_messages["config"] = efd_and_messages.apply(format_config_as_yaml_with_colors, axis=1) efd_and_messages["description"] = efd_and_messages.apply(format_description, axis=1) # Adjust the display call to include the formatted column styled_table = ( efd_and_messages[cols] .style.apply(highlight_salindex, axis=1) .set_table_styles([dict(selector="th", props=[("text-align", "left")])]) .set_properties(**{"text-align": "left"}) ) # Render with HTML html_values += styled_table.format().to_html(render_links=True) return html_values