Generate markdown table (#1738)
* script for markdown table * refactor * refactor * allow kwargs * tests * refactor tests * sanitize markdown * Update spiffworkflow-backend/src/spiffworkflow_backend/scripts/generate_markdown_table.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update spiffworkflow-backend/src/spiffworkflow_backend/scripts/generate_markdown_table.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * lint * allow turning off sanitization for a column w/ burnettk --------- Co-authored-by: burnettk <burnettk@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
d1dffb95de
commit
991759deee
|
@ -0,0 +1,69 @@
|
|||
from typing import Any
|
||||
|
||||
from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext
|
||||
from spiffworkflow_backend.scripts.script import Script
|
||||
from spiffworkflow_backend.services.jinja_service import JinjaHelpers
|
||||
|
||||
|
||||
class GenerateMarkdownTable(Script):
|
||||
@staticmethod
|
||||
def requires_privileged_permissions() -> bool:
|
||||
"""We have deemed this function safe to run without elevated permissions."""
|
||||
return False
|
||||
|
||||
def get_description(self) -> str:
|
||||
return """Given column info and data, returns a string suitable for use in markdown to show a table."""
|
||||
|
||||
def normalize_table_headers_to_dicts(self, columns: list) -> list[dict]:
|
||||
return [
|
||||
{"property": col, "label": col.replace("_", " ").capitalize()} if not isinstance(col, dict) else col
|
||||
for col in columns
|
||||
]
|
||||
|
||||
def generate_table_rows(self, table_headers: list[dict], data: list) -> str:
|
||||
table = ""
|
||||
for item in data:
|
||||
row = []
|
||||
for column in table_headers:
|
||||
property_name = column["property"]
|
||||
value = str(item.get(property_name, ""))
|
||||
if "formatter" in column and column["formatter"] == "convert_seconds_to_date_time_for_display":
|
||||
value = f"SPIFF_FORMAT:::convert_seconds_to_date_time_for_display({value})"
|
||||
elif "sanitize" not in column or column["sanitize"] is True:
|
||||
value = JinjaHelpers.sanitize_for_md(value)
|
||||
row.append(value)
|
||||
table += "| " + " | ".join(row) + " |\n"
|
||||
return table
|
||||
|
||||
def run(
|
||||
self,
|
||||
script_attributes_context: ScriptAttributesContext,
|
||||
*args: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""
|
||||
Generates a markdown table from columns and data.
|
||||
|
||||
:param columns: List of column definitions. Each column can be a string or a dictionary
|
||||
with keys 'property', 'label', 'sanitize', and 'formatter'.
|
||||
:param data: List of dictionaries containing the data.
|
||||
:return: A string containing the markdown table.
|
||||
"""
|
||||
columns: list = kwargs.get("columns", args[0] if len(args) > 0 else None)
|
||||
data: list = kwargs.get("data", args[1] if len(args) > 1 else None)
|
||||
|
||||
if columns is None:
|
||||
raise ValueError(
|
||||
"Missing required argument: 'columns'. Ensure that 'columns' is passed as a positional or keyword argument."
|
||||
)
|
||||
if data is None:
|
||||
raise ValueError(
|
||||
"Missing required argument: 'data'. Ensure that 'data' is passed as a positional or keyword argument."
|
||||
)
|
||||
table_headers = self.normalize_table_headers_to_dicts(columns)
|
||||
|
||||
header_labels = [header["label"] for header in table_headers]
|
||||
table = "| " + " | ".join(header_labels) + " |\n"
|
||||
table += "| " + " | ".join(["----"] * len(header_labels)) + " |\n"
|
||||
table += self.generate_table_rows(table_headers, data)
|
||||
return table
|
|
@ -0,0 +1,102 @@
|
|||
from flask import Flask
|
||||
from spiffworkflow_backend.models.script_attributes_context import ScriptAttributesContext
|
||||
from spiffworkflow_backend.scripts.generate_markdown_table import GenerateMarkdownTable
|
||||
|
||||
from tests.spiffworkflow_backend.helpers.base_test import BaseTest
|
||||
|
||||
|
||||
class TestGenerateMarkdownTable(BaseTest):
|
||||
def setup_script_attributes_context(self) -> ScriptAttributesContext:
|
||||
return ScriptAttributesContext(
|
||||
task=None,
|
||||
environment_identifier="unit_testing",
|
||||
process_instance_id=1,
|
||||
process_model_identifier="test_process_model",
|
||||
)
|
||||
|
||||
def run_generate_markdown_table_test(self, columns: list) -> None:
|
||||
data = [
|
||||
{"name": "Alice", "age": 30, "created_at": 1609459200},
|
||||
{"name": "Bob", "age": 25, "created_at": 1609545600},
|
||||
]
|
||||
script_attributes_context = self.setup_script_attributes_context()
|
||||
result = GenerateMarkdownTable().run(
|
||||
script_attributes_context,
|
||||
columns=columns,
|
||||
data=data,
|
||||
)
|
||||
expected_result = (
|
||||
"| Name | Age | Created At |\n"
|
||||
"| ---- | ---- | ---- |\n"
|
||||
"| Alice | 30 | SPIFF_FORMAT:::convert_seconds_to_date_time_for_display(1609459200) |\n"
|
||||
"| Bob | 25 | SPIFF_FORMAT:::convert_seconds_to_date_time_for_display(1609545600) |\n"
|
||||
)
|
||||
assert result == expected_result
|
||||
|
||||
def test_generate_markdown_table_script(self, app: Flask, with_db_and_bpmn_file_cleanup: None) -> None:
|
||||
columns = [
|
||||
{"property": "name", "label": "Name"},
|
||||
{"property": "age", "label": "Age"},
|
||||
{"property": "created_at", "label": "Created At", "formatter": "convert_seconds_to_date_time_for_display"},
|
||||
]
|
||||
self.run_generate_markdown_table_test(columns)
|
||||
|
||||
def test_generate_markdown_table_script_handles_vertical_bars_in_data(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
columns = [
|
||||
{"property": "name", "label": "Name"},
|
||||
{"property": "description", "label": "Description"},
|
||||
]
|
||||
data = [
|
||||
{"name": "Alice", "description": "Alice's description | with a vertical bar"},
|
||||
{"name": "Bob", "description": "Bob's description | with another vertical bar"},
|
||||
]
|
||||
script_attributes_context = self.setup_script_attributes_context()
|
||||
result = GenerateMarkdownTable().run(
|
||||
script_attributes_context,
|
||||
columns=columns,
|
||||
data=data,
|
||||
)
|
||||
expected_result = (
|
||||
"| Name | Description |\n"
|
||||
"| ---- | ---- |\n"
|
||||
"| Alice | Alice's description \\| with a vertical bar |\n"
|
||||
"| Bob | Bob's description \\| with another vertical bar |\n"
|
||||
)
|
||||
assert result == expected_result
|
||||
|
||||
def test_generate_markdown_table_script_supports_string_columns_for_ease_of_use(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
columns = [
|
||||
"name",
|
||||
{"property": "age", "label": "Age"},
|
||||
{"property": "created_at", "label": "Created At", "formatter": "convert_seconds_to_date_time_for_display"},
|
||||
]
|
||||
self.run_generate_markdown_table_test(columns)
|
||||
|
||||
def test_generate_markdown_table_script_skips_sanitization_if_desired(
|
||||
self, app: Flask, with_db_and_bpmn_file_cleanup: None
|
||||
) -> None:
|
||||
columns = [
|
||||
{"property": "name", "label": "Name"},
|
||||
{"property": "description", "label": "Description", "sanitize": False},
|
||||
]
|
||||
data = [
|
||||
{"name": "Alice", "description": "Alice's `description | with a vertical` bar"},
|
||||
{"name": "Bob", "description": "Bob's `description | with another vertical` bar"},
|
||||
]
|
||||
script_attributes_context = self.setup_script_attributes_context()
|
||||
result = GenerateMarkdownTable().run(
|
||||
script_attributes_context,
|
||||
columns=columns,
|
||||
data=data,
|
||||
)
|
||||
expected_result = (
|
||||
"| Name | Description |\n"
|
||||
"| ---- | ---- |\n"
|
||||
"| Alice | Alice's `description | with a vertical` bar |\n"
|
||||
"| Bob | Bob's `description | with another vertical` bar |\n"
|
||||
)
|
||||
assert result == expected_result
|
Loading…
Reference in New Issue