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:
Kevin Burnett 2024-06-17 20:33:53 +00:00 committed by GitHub
parent d1dffb95de
commit 991759deee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 171 additions and 0 deletions

View File

@ -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

View File

@ -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