@@ -81,6 +81,60 @@ class ValidatorResultRecorder:
8181 validation_summary : Dict [str , List [str ]] = field (
8282 default_factory = lambda : {"valid" : [], "invalid" : []}
8383 )
84+ # Used to create unique error_tables in the log file if multiple tables are present.
85+ error_table_id = 0
86+ # Used styling to use the same theme as Robot Framework uses.
87+ style_and_filter_script = """
88+ <style>
89+ #table_block_0 {
90+ margin-bottom: -5em;
91+ }
92+ .dataframe th {
93+ background-color: var(--primary-color);
94+ padding: 0.2em 0.3em;
95+ }
96+ .dataframe th, .dataframe td {
97+ border-width: 1px;
98+ border-style: solid;
99+ border-color: var(--secondary-color);
100+ padding: 0.1em 0.3em;
101+ font-family: Helvetica, sans-serif;
102+ }
103+ input.filter {
104+ width: 25em;
105+ background-color: var(--background-color);
106+ border-color: var(--secondary-color);
107+ border-width: 2px;
108+ border-style: solid;
109+ border-radius: 2px;
110+ color: var(--text-color);
111+ margin-left: 0;
112+ font-family: Helvetica, sans-serif;
113+ }
114+ </style>
115+ <script>
116+ function filterTable(blockId) {
117+ const container = document.getElementById("table_block_" + blockId);
118+ const input = container.querySelector("input");
119+ const table = container.querySelector("table");
120+ const filter = input.value.toLowerCase();
121+ const trs = table.getElementsByTagName("tr");
122+
123+ for (let i = 1; i < trs.length; i++) {
124+ const tds = trs[i].getElementsByTagName("td");
125+ let rowVisible = false;
126+ for (let j = 0; j < tds.length; j++) {
127+ const td = tds[j];
128+ if (td && td.textContent.toLowerCase().indexOf(filter) > -1) {
129+ rowVisible = true;
130+ break;
131+ }
132+ }
133+ trs[i].style.display = rowVisible ? "" : "none";
134+ }
135+ }
136+ </script>
137+ """
84138
85139 def _get_summary (self ) -> Dict [str , int ]:
86140 """
@@ -340,6 +394,52 @@ def write_errors_to_csv(self,
340394 ) from e
341395 return str ( output_csv_path .resolve () )
342396
397+ def write_error_table_to_log (self , errors : List [ Dict [str , Any ] ]):
398+ """
399+ Writes a table of validation errors to the log file.
400+
401+ This method takes a list of error dictionaries and writes them
402+ to the log file in a table format. It also adds an input that
403+ can be used to filter through the errors and updates in real
404+ time.
405+
406+ Args:
407+
408+ - errors (List[Dict[str, Any]]):
409+ A list of dictionaries, where each dictionary contains details
410+ of a validation error. Each key in the dictionaries
411+ corresponds to a column in the output CSV.
412+
413+ Notes:
414+
415+ - If `errors` is an empty list, the method exits early and logs
416+ an informational message without creating a file.
417+ the error dictionaries.
418+ - The method uses `pandas` for CSV generation.
419+ """
420+ # Return if no errors were passed.
421+ if not errors :
422+ logger .info ("No errors to write to log file." )
423+ return
424+ # Convert the errors list to a DataFrame.
425+ df = pd .DataFrame (errors )
426+ # Convert the dataframe to HTML.
427+ df_table = df .to_html (index = False , border = 0 )
428+ # Get the table id and increment for the next one.
429+ error_table_id = self .error_table_id
430+ self .error_table_id += 1
431+ # Add the filter input to the df_table (includes the function call)
432+ full_html = f"""<div id="table_block_{ error_table_id } ">
433+ <input class="filter" type="text" onkeyup="filterTable('{ error_table_id } ')" placeholder="Search validation errors...">
434+ { df_table }
435+ </div>"""
436+ # Add the style and filter script if it is the first table
437+ if error_table_id == 0 :
438+ full_html = f"{ full_html } { self .style_and_filter_script } "
439+ # Actually print the table to the log file
440+ logger .info (full_html , html = True )
441+
442+
343443class ValidatorResult : # pylint: disable=R0903:too-few-public-methods
344444 """
345445 Encapsulates the result of an operation in a success-or-failure format.
0 commit comments