From e2439ad23e5043423bee84d27eb01c0d1ecba56a Mon Sep 17 00:00:00 2001 From: kedarnn Date: Wed, 11 Dec 2024 13:48:52 +0100 Subject: [PATCH] Feature/html report UI upgrade (#134) This feature adds the following updates to the HTML report. 1. Filter items by status (Ok, Missing, Partial, Warning, Justified) 2. Hide/Unhide Issues. 3. Search in issues. --- CHANGELOG.md | 29 ++-- lobster/html/htmldoc.py | 5 + .../core/html_report/assets/html_report.css | 139 ++++++++++++++++++ .../core/html_report/assets/html_report.js | 87 +++++++++++ lobster/tools/core/html_report/html_report.py | 55 ++++++- packages/lobster-core/setup.py | 3 + packages/lobster-monolithic/setup.py | 3 + 7 files changed, 305 insertions(+), 16 deletions(-) create mode 100644 lobster/tools/core/html_report/assets/html_report.css create mode 100644 lobster/tools/core/html_report/assets/html_report.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d7e3327..d1a66fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,23 @@ ### 0.9.21-dev +* `lobster-html-report` has the following updates. + * Filter items by status (Ok, Missing, Partial, Warning, Justified) + * Hide/Unhide Issues. + * Search in issues and detailed report. +* Add support to view version for lobster tools for following tools: + - `lobster-ci-report` + - `lobster-codebeamer` + - `lobster-cpp` + - `lobster-cpptest` + - `lobster-gtest` + - `lobster-html-report` + - `lobster-json` + - `lobster-online-report` + - `lobster-python` + - `lobster-report` + - `lobster-trlc` ### 0.9.20 @@ -24,19 +40,6 @@ * `lobster-codebeamer` * `lobster-report` -* Add support to view version for lobster tools for following tools: - - `lobster-ci-report` - - `lobster-codebeamer` - - `lobster-cpp` - - `lobster-cpptest` - - `lobster-gtest` - - `lobster-html-report` - - `lobster-json` - - `lobster-online-report` - - `lobster-python` - - `lobster-report` - - `lobster-trlc` - * `lobster-gtest` accepts XML nodes other than `testcase`, but ignores them. ### 0.9.19 diff --git a/lobster/html/htmldoc.py b/lobster/html/htmldoc.py index 071065f0..fbdea7dc 100644 --- a/lobster/html/htmldoc.py +++ b/lobster/html/htmldoc.py @@ -247,6 +247,7 @@ def __init__(self, title, subtitle): } self.scripts = [] self.body = [] + self.css_files = [] def add_line(self, line): assert isinstance(line, str) @@ -293,6 +294,10 @@ def render(self): rv.append(" %s: %s;" % (attr, value)) rv.append("}") rv.append("") + + # add css files that are appended to self.files + for css_file in self.css_files: + rv.append(f"") rv.append("") rv.append("") diff --git a/lobster/tools/core/html_report/assets/html_report.css b/lobster/tools/core/html_report/assets/html_report.css new file mode 100644 index 00000000..a505901f --- /dev/null +++ b/lobster/tools/core/html_report/assets/html_report.css @@ -0,0 +1,139 @@ +.button { + background-color: #818589; + border: none; + border-radius: 5px; + color: white; + padding: 12px 25px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 14px; + margin: 4px 2px; + cursor: pointer +} + +.button active:before { + content: ; + position: absolute; + left: 0; + top: 0; + display: inline-block; + width: 0; + height: 0; + border-style: solid; + border-width: 15px 15px 0 0; + border-color: #333 transparent transparent transparent +} + +.buttonActive.button { + text-decoration: none; + border: 5px solid #000000 +} + +.buttonOK { + background-color: #04AA6D; + color: white; + border: 2px solid #04AA6D; + border-radius: 5px +} + +.buttonOK:hover { + background-color: #026641; + color: white; + border: 2px solid #026641 +} + +.buttonActive.buttonOK { + text-decoration: none; + border: 5px solid #026641 +} + +.buttonPartial { + background-color: #17a2b8; + color: white; + border: 2px solid #17a2b8; + border-radius: 5px +} + +.buttonPartial:hover { + background-color: #0e616e; + color: white; + border: 2px solid #0e616e +} + +.buttonActive.buttonPartial { + text-decoration: none; + border: 5px solid #0e616e +} + +.buttonMissing { + background-color: #f44336; + color: white; + border: 2px solid #f44336; + border-radius: 5px +} + +.buttonMissing:hover { + background-color: #a91409; + color: white; + border: 2px solid #a91409 +} + +.buttonActive.buttonMissing { + text-decoration: none; + border: 5px solid #a91409 +} + +.buttonJustified { + background-color: #6c757d; + color: white; + border: 2px solid #6c757d; + border-radius: 5px +} + +.buttonJustified:hover { + background-color: #41464b; + color: white; + border: 2px solid #41464b +} + +.buttonActive.buttonJustified { + text-decoration: none; + border: 5px solid #41464b +} + +.buttonWarning { + background-color: #ffbf00; + color: white; + border: 2px solid #ffbf00; + border-radius: 5px +} + +.buttonWarning:hover { + background-color: #997300; + color: white; + border: 2px solid #997300 +} + +.buttonActive.buttonWarning { + text-decoration: none; + border: 5px solid #997300 +} + +.buttonBlue { + background-color: #0000ff; + color: white; + border: 2px solid #0000ff; + border-radius: 5px +} + +.buttonBlue:hover { + background-color: #000099; + color: white; + border: 2px solid #000099 +} + +.buttonActive.buttonBlue { + text-decoration: none; + border: 5px solid #000099 +} diff --git a/lobster/tools/core/html_report/assets/html_report.js b/lobster/tools/core/html_report/assets/html_report.js new file mode 100644 index 00000000..b2f5a874 --- /dev/null +++ b/lobster/tools/core/html_report/assets/html_report.js @@ -0,0 +1,87 @@ +function buttonFilter(filter) { + var elms = document.getElementsByTagName("div"); + var issue_elms = document.getElementsByClassName("issue"); + for (i = 0; i < elms.length; i++) { + if (elms[i].id.startsWith("item-")) { + console.log("elms[i].className ", elms[i].className) + if (filter == "all") { + elms[i].style.display = "block"; + } else if (elms[i].className == "item-" + filter) { + elms[i].style.display = "block"; + } else { + elms[i].style.display = "none"; + } + } + } + // filter the issues list based on the issue filter button clicked + for (i = 0; i < issue_elms.length; i++) { + console.log("log ", issue_elms[i].className) + if (filter == "all") { + issue_elms[i].style.display = "list-item"; + } else if (issue_elms[i].className == "issue issue-" + filter) { + issue_elms[i].style.display = "list-item"; + } else { + issue_elms[i].style.display = "none"; + } + } + activeButton(filter); + //call the search filering which could have been overwritten by the current filtering + searchItem(); +} + + +function activeButton(filter) { + var elms = document.getElementsByTagName("button"); + console.log("the click buitton is " + filter); + for (i = 0; i < elms.length; i++) { + if (elms[i].className.includes("buttonActive")) { + console.log("elem active found : " + elms[i].className); + elms[i].className = elms[i].className.replace("buttonActive", ""); + } else if (elms[i].className.toLowerCase().includes("button" + filter.toLowerCase())) { + console.log("elem to be activated found : " + elms[i].className); + elms[i].className = elms[i].className + " buttonActive"; + } + } +} + + +function ToggleIssues() { + var div_issue = document.getElementById("issues-section"); + if (div_issue.style.display == "block" || div_issue.style.display == "") { + div_issue.style.display = "none"; + document.getElementById("BtnToggleIssue").innerHTML = "Show Issues"; + document.getElementById("BtnToggleIssue").className = document.getElementById("BtnToggleIssue").className + " buttonActive"; + } else { + div_issue.style = 'display: block; flex-direction: column; height: 200px;' + + 'overflow:auto;'; + document.getElementById("BtnToggleIssue").innerHTML = "Hide Issues"; + document.getElementById("BtnToggleIssue").className = document.getElementById("BtnToggleIssue").className.replace("buttonActive", ""); + } +} + + +function searchItem() { + var input = document.getElementById('search').value + input = input.toLowerCase(); + + var divs = document.getElementsByClassName('item-name'); + for (i = 0; i < divs.length; i++) { + var title = divs[i].parentNode.getAttribute("title"); + // get requirement name: 2nd part when we cut the long string with /svg + var reqname = divs[i].innerHTML.toLowerCase().split("").pop(); + reqname = reqname.split(" ").pop(); + if (reqname.includes(input)) { + // the search pattern has been found, if this elem has the title "hidden-not-matching", put it back to diplayed + if (title) { + if (title.startsWith("hidden-not-matching")) { + divs[i].parentNode.style.display = "block"; + } + } + divs[i].parentNode.setAttribute("title", "matching-" + input) + } else { + // not maching, we hide + divs[i].parentNode.setAttribute("title", "hidden-not-matching") + divs[i].parentNode.style.display = "none"; + } + } +} diff --git a/lobster/tools/core/html_report/html_report.py b/lobster/tools/core/html_report/html_report.py index 2bf7c141..c40a9501 100755 --- a/lobster/tools/core/html_report/html_report.py +++ b/lobster/tools/core/html_report/html_report.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Affero General Public # License along with this program. If not, see # . - import os.path import argparse import html @@ -374,8 +373,43 @@ def write_html(fd, report, dot, high_contrast): print("> please install Graphviz (https://graphviz.org)") doc.add_line('') + ### Filtering + doc.add_heading(2, "Filtering", "filtering-options") + doc.add_heading(3, "Item Filters") + doc.add_line('
') + doc.add_line('') + + doc.add_line('') + + doc.add_line('') + + doc.add_line('') + + doc.add_line('') + + doc.add_line('') + doc.add_line("
") + + doc.add_heading(3, "Show Issues") + doc.add_line('
') + doc.add_line('') + doc.add_line('
') + + doc.add_heading(3, "Search", "search") + doc.add_line('') + doc.add_line('") ### Report file_heading = None @@ -447,6 +483,19 @@ def write_html(fd, report, dot, high_contrast): write_item_box_end(doc) else: doc.add_line("No items recorded at this level.") + # Closing tag for id #search-sec-id + doc.add_line("") + + # Add the css from assets + dir_path = os.path.dirname(os.path.abspath(__file__)) + file_path = dir_path + "/assets/html_report.css" + doc.css_files.append(file_path) + + # Add javascript from assets/html_report.js file + dir_path = os.path.dirname(os.path.abspath(__file__)) + file_path = dir_path + "/assets/html_report.js" + with open(file_path, "r", encoding="UTF-8") as scripts: + doc.scripts.append("".join(scripts.readlines())) ### STM # doc.add_heading(2, "Software traceability matrix", "matrix") diff --git a/packages/lobster-core/setup.py b/packages/lobster-core/setup.py index af0db774..9df68841 100644 --- a/packages/lobster-core/setup.py +++ b/packages/lobster-core/setup.py @@ -52,6 +52,9 @@ "lobster.tools.core.html_report", "lobster.tools.core.online_report", "lobster.tools.core.report"], + package_data={ + "lobster.tools.core.html_report":["assets/*"] + }, install_requires=[], python_requires=">=3.7, <4", classifiers=[ diff --git a/packages/lobster-monolithic/setup.py b/packages/lobster-monolithic/setup.py index 4960fead..2b161414 100644 --- a/packages/lobster-monolithic/setup.py +++ b/packages/lobster-monolithic/setup.py @@ -48,6 +48,9 @@ project_urls=project_urls, license="GNU Affero General Public License v3", packages=setuptools.find_packages(), + package_data={ + "lobster.tools.core.html_report":["assets/*"] + }, install_requires=[ "miss-hit>=0.9.42", "requests>=2.22",