diff --git a/scripts/vulnerability_report.py b/scripts/vulnerability_report.py index e90d578ff..3cdc399ad 100644 --- a/scripts/vulnerability_report.py +++ b/scripts/vulnerability_report.py @@ -409,7 +409,8 @@ def process_dependabot_file(dependabot_file: str) -> dict[str, Any]: Returns: dict[str, Any]: A dictionary with keys: "count", "state", "severity", "severities", - "days", "packages", and "dates", each containing the corresponding computed metrics. + "days", "packages", and "dates", each containing the + corresponding computed metrics. """ source_data = load_dependabot_file(dependabot_file) @@ -491,6 +492,98 @@ def generate_severity_graph( save_graph(fig, prefix, "severity", svg_output, png_output) +def generate_vuln_for_days_graph( + stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool +) -> None: + """ + Generate a histogram of the distribution of days required to fix vulnerabilities. + + Parameters: + stat (dict[str, Any]): Statistics dictionary + prefix (str): Prefix for the output file name. + svg_output (bool): If true, save the graph as SVG. + png_output (bool): If true, save the graph as PNG. + """ + fig, ax = plt.subplots() + D = stat["days"]["days"] + ax.hist(D, bins=30, edgecolor="black") + ax.set_ylim(top=100) + ax.set_xlabel("Days") + ax.set_title("Fixed in day(s)") + save_graph(fig, prefix, "days", svg_output, png_output) + + +def generate_vulnerable_packages_graph( + stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool +) -> None: + """ + Generate a bar chart showing the top 10 packages with the most CVEs. + + Parameters: + stat (dict[str, Any]): Statistics dictionary containing vulnerability + data, with "packages" key holding package frequency counts. + prefix (str): Prefix for the output file name. + svg_output (bool): If true, save the graph as SVG. + png_output (bool): If true, save the graph as PNG. + """ + fig, ax = plt.subplots() + D = stat["packages"] + names, counts = zip(*D.most_common(10)) + ax.bar(names, counts, edgecolor="black") + ax.set_ylim(top=100) + ax.set_title("CVEs per package") + ax.tick_params(axis="x", labelrotation=90) + fig.tight_layout() + save_graph(fig, prefix, "packages", svg_output, png_output) + + +def generate_new_cve_dates_graph( + stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool +) -> None: + """ + Create a line chart showing new CVE detection dates over time. + + Parameters: + stat (dict[str, Any]): Statistics dictionary with "dates" key + containing a Counter of datetime objects mapped to occurrence counts. + prefix (str): Base filename prefix for output files. + svg_output (bool): Whether to save the graph as SVG. + png_output (bool): Whether to save the graph as PNG. + """ + fig, ax = plt.subplots() + D = stat["dates"] + dates, counts = zip(*sorted(D.items(), key=lambda x: x[0])) + ax.plot(dates, counts) + ax.set_title("New CVEs timeline") + save_graph(fig, prefix, "timeline", svg_output, png_output) + + +def generate_graphs( + stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool +) -> None: + """ + Generate vulnerability visualization graphs. + """ + generate_overal_state_graph(stat, prefix, svg_output, png_output) + generate_severity_graph(stat, prefix, svg_output, png_output) + generate_vuln_for_days_graph(stat, prefix, svg_output, png_output) + generate_vulnerable_packages_graph(stat, prefix, svg_output, png_output) + generate_new_cve_dates_graph(stat, prefix, svg_output, png_output) + + +def generate_page( + stat: dict[str, Any], prefix: str, organization: str, repository: str +) -> None: + """ + Generate HTML page containing visualization graphs. + """ + filename = f"vulnerabilities_{prefix}.html" + output = f""" +""" + with open(filename, "w", encoding="utf-8") as fout: + fout.write(output) + + def main() -> int: """ CLI entry point that retrieves Dependabot issues and produces Vulnerability report. @@ -514,8 +607,12 @@ def main() -> int: dependabot_file = dependabot_file_name(args) prefix = args.repository stat = process_dependabot_file(dependabot_file) - print(stat) - print(prefix) + + if args.generate_graphs: + generate_graphs(stat, prefix, args.svg_output, args.png_output) + + if args.generate_page: + generate_page(stat, prefix, args.organization, args.repository) return 0