Skip to content

feature: add error_table in log file #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/xmlvalidator/XmlValidator.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ def __init__(
xsd_path: str | Path | None = None,
base_url: str | None = None,
error_facets: List[str] | None = None,
fail_on_errors: bool = True
fail_on_errors: bool = True,
) -> None:
"""
**Library Scope**
Expand Down Expand Up @@ -1460,7 +1460,8 @@ def validate_xml_files( # pylint: disable=R0913:too-many-arguments disable=R0914
write_to_csv: Optional[bool] = True,
timestamped: Optional[bool] = True,
reset_errors: bool = True,
fail_on_errors: Optional[bool] = None
fail_on_errors: Optional[bool] = None,
error_table: Optional[bool] = True
) -> Tuple[
List[ Dict[str, Any] ],
str | None
Expand Down Expand Up @@ -1591,6 +1592,11 @@ def validate_xml_files( # pylint: disable=R0913:too-many-arguments disable=R0914
XML files, one or more errors have been reported. Error
reporting and exporting will not change.

``error_table``

If True, writes all collected errors to a filterable table in
the log file. Defaults to True.

**Returns**

A tuple, holding:
Expand Down Expand Up @@ -1670,6 +1676,11 @@ def validate_xml_files( # pylint: disable=R0913:too-many-arguments disable=R0914
)
else:
csv_path = None
# Write errors to the log file as a table if requested.
if error_table and self.validator_results.errors_by_file:
self.validator_results.write_error_table_to_log(
self.validator_results.errors_by_file,
)
# Log a summary of the test run.
self.validator_results.log_summary()
if fail_on_errors and self.validator_results.errors_by_file:
Expand Down
100 changes: 100 additions & 0 deletions src/xmlvalidator/xml_validator_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,60 @@ class ValidatorResultRecorder:
validation_summary: Dict[str, List[str]] = field(
default_factory=lambda: {"valid": [], "invalid": []}
)
# Used to create unique error_tables in the log file if multiple tables are present.
error_table_id = 0
# Used styling to use the same theme as Robot Framework uses.
style_and_filter_script = """
<style>
#table_block_0 {
margin-bottom: -5em;
}
.dataframe th {
background-color: var(--primary-color);
padding: 0.2em 0.3em;
}
.dataframe th, .dataframe td {
border-width: 1px;
border-style: solid;
border-color: var(--secondary-color);
padding: 0.1em 0.3em;
font-family: Helvetica, sans-serif;
}
input.filter {
width: 25em;
background-color: var(--background-color);
border-color: var(--secondary-color);
border-width: 2px;
border-style: solid;
border-radius: 2px;
color: var(--text-color);
margin-left: 0;
font-family: Helvetica, sans-serif;
}
</style>
<script>
function filterTable(blockId) {
const container = document.getElementById("table_block_" + blockId);
const input = container.querySelector("input");
const table = container.querySelector("table");
const filter = input.value.toLowerCase();
const trs = table.getElementsByTagName("tr");

for (let i = 1; i < trs.length; i++) {
const tds = trs[i].getElementsByTagName("td");
let rowVisible = false;
for (let j = 0; j < tds.length; j++) {
const td = tds[j];
if (td && td.textContent.toLowerCase().indexOf(filter) > -1) {
rowVisible = true;
break;
}
}
trs[i].style.display = rowVisible ? "" : "none";
}
}
</script>
"""

def _get_summary(self) -> Dict[str, int]:
"""
Expand Down Expand Up @@ -340,6 +394,52 @@ def write_errors_to_csv(self,
) from e
return str( output_csv_path.resolve() )

def write_error_table_to_log(self, errors: List[ Dict[str, Any] ]):
"""
Writes a table of validation errors to the log file.

This method takes a list of error dictionaries and writes them
to the log file in a table format. It also adds an input that
can be used to filter through the errors and updates in real
time.

Args:

- errors (List[Dict[str, Any]]):
A list of dictionaries, where each dictionary contains details
of a validation error. Each key in the dictionaries
corresponds to a column in the output CSV.

Notes:

- If `errors` is an empty list, the method exits early and logs
an informational message without creating a file.
the error dictionaries.
- The method uses `pandas` for CSV generation.
"""
# Return if no errors were passed.
if not errors:
logger.info("No errors to write to log file.")
return
# Convert the errors list to a DataFrame.
df = pd.DataFrame(errors)
# Convert the dataframe to HTML.
df_table = df.to_html(index=False, border=0)
# Get the table id and increment for the next one.
error_table_id = self.error_table_id
self.error_table_id += 1
# Add the filter input to the df_table (includes the function call)
full_html = f"""<div id="table_block_{error_table_id}">
<input class="filter" type="text" onkeyup="filterTable('{error_table_id}')" placeholder="Search validation errors...">
{df_table}
</div>"""
# Add the style and filter script if it is the first table
if error_table_id == 0:
full_html = f"{full_html}{self.style_and_filter_script}"
# Actually print the table to the log file
logger.info(full_html, html=True)


class ValidatorResult: # pylint: disable=R0903:too-few-public-methods
"""
Encapsulates the result of an operation in a success-or-failure format.
Expand Down
Empty file.
Empty file.