LCORE-2631: Generate graphs#2003
Conversation
|
Warning Review limit reached
More reviews will be available in 56 minutes and 11 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughThe vulnerability report script now adds graphs for fix latency, vulnerable packages, and CVE dates, and ChangesVulnerability report graph generation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
9fd7604 to
a769671
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/vulnerability_report.py`:
- Around line 529-556: The graph helpers crash when their Counter inputs are
empty because generate_new_cve_dates_graph and the package bar-chart block both
unpack zip(*) results unconditionally. Update the CVE dates and packages graph
generation paths to detect empty data before calling zip/unpacking, and in that
case render the existing “No data” placeholder instead of continuing. Use the
generate_new_cve_dates_graph function and the packages graph block that reads
stat["dates"] and stat["packages"] as the locations to apply the guard.
- Around line 507-536: The two plotting helpers, generate_fixed_in_day_graph and
generate_vulnerable_packages_graph, are hard-capping the y-axis with
ax.set_ylim(top=100), which can truncate high-count data. Remove that fixed
limit from both functions so matplotlib can autoscale based on the actual
values, while keeping the rest of the chart setup unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: c566847d-0028-4438-9d8c-0a3a743a23f2
📒 Files selected for processing (1)
scripts/vulnerability_report.py
📜 Review details
⏰ Context from checks skipped due to timeout. (15)
- GitHub Check: Pylinter
- GitHub Check: unit_tests (3.12)
- GitHub Check: unit_tests (3.13)
- GitHub Check: build-pr
- GitHub Check: integration_tests (3.12)
- GitHub Check: integration_tests (3.13)
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
- GitHub Check: E2E: server mode / ci / group 3
- GitHub Check: E2E: library mode / ci / group 3
- GitHub Check: E2E: server mode / ci / group 1
- GitHub Check: E2E: library mode / ci / group 2
- GitHub Check: E2E: library mode / ci / group 1
- GitHub Check: E2E: server mode / ci / group 2
- GitHub Check: E2E Tests for Lightspeed Evaluation job
- GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-0-6-on-pull-request
⚠️ CI failures not shown inline (2)
GitHub Actions: OpenAPI (Spectral) / 0_spectral.txt: LCORE-2631: Generate graphs
Conclusion: failure
##[group]Run set -euo pipefail
�[36;1mset -euo pipefail�[0m
�[36;1muv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json�[0m
�[36;1mif ! diff -u docs/openapi.json /tmp/openapi-generated.json; then�[0m
�[36;1m echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"�[0m
GitHub Actions: OpenAPI (Spectral) / spectral: LCORE-2631: Generate graphs
Conclusion: failure
##[group]Run set -euo pipefail
�[36;1mset -euo pipefail�[0m
�[36;1muv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json�[0m
�[36;1mif ! diff -u docs/openapi.json /tmp/openapi-generated.json; then�[0m
�[36;1m echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"�[0m
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-06-24T13:45:37.249Z
Learnt from: Jdubrick
Repo: lightspeed-core/lightspeed-stack PR: 1971
File: src/utils/markdown_repair.py:31-36
Timestamp: 2026-06-24T13:45:37.249Z
Learning: In the lightspeed-stack repository, docstrings must use the section header name "Parameters:" (not "Args:") for function arguments, even if the project references Google Python docstring conventions. Ensure docstrings follow the project’s established "Parameters:" header format for any documented function parameters.
Applied to files:
scripts/vulnerability_report.py
| 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() |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major
Remove the fixed top=100 y-axis limit.
The hard-coded ax.set_ylim(top=100) in both graph functions truncates data when vulnerability counts exceed 100. Popular packages frequently accumulate hundreds of CVEs, and large repositories produce histogram bin counts well above this threshold. This arbitrary cap renders charts misleading for datasets where the visualization is most critical. Letting matplotlib autoscale ensures accuracy across all data sizes.
Proposed fix
def generate_vuln_for_days_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
(...)
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:
(...)
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()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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() | |
| fig, ax = plt.subplots() | |
| D = stat["days"]["days"] | |
| ax.hist(D, bins=30, edgecolor="black") | |
| 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_title("CVEs per package") | |
| ax.tick_params(axis="x", labelrotation=90) | |
| fig.tight_layout() |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/vulnerability_report.py` around lines 507 - 536, The two plotting
helpers, generate_fixed_in_day_graph and generate_vulnerable_packages_graph, are
hard-capping the y-axis with ax.set_ylim(top=100), which can truncate high-count
data. Remove that fixed limit from both functions so matplotlib can autoscale
based on the actual values, while keeping the rest of the chart setup unchanged.
| 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) |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major
Fix empty-counter crash in graph generation helpers.
Lines 531 and 555 attempt to unpack zip(*) results without checking if the input Counter is empty. Since Counter.most_common(10) and sorted(Counter.items()) return empty lists when the counter has no items, zip(*[]) produces an empty iterator. Unpacking this into names, counts raises ValueError: not enough values to unpack, causing the CLI to crash on valid empty Dependabot reports when --generate-graphs is enabled.
Guard against empty data explicitly before unpacking to emit a "No data" placeholder graph instead.
Proposed fix
def generate_vulnerable_packages_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
@@
fig, ax = plt.subplots()
D = stat["packages"]
+ if not D:
+ ax.set_title("CVEs per package")
+ ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes)
+ save_graph(fig, prefix, "packages", svg_output, png_output)
+ return
names, counts = zip(*D.most_common(10))
@@
def generate_new_cve_dates_graph(
stat: dict[str, Any], prefix: str, svg_output: bool, png_output: bool
) -> None:
@@
fig, ax = plt.subplots()
D = stat["dates"]
+ if not D:
+ ax.set_title("New CVEs timeline")
+ ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes)
+ save_graph(fig, prefix, "timeline", svg_output, png_output)
+ return
dates, counts = zip(*sorted(D.items(), key=lambda x: x[0]))📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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) | |
| fig, ax = plt.subplots() | |
| D = stat["packages"] | |
| if not D: | |
| ax.set_title("CVEs per package") | |
| ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes) | |
| save_graph(fig, prefix, "packages", svg_output, png_output) | |
| return | |
| 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"] | |
| if not D: | |
| ax.set_title("New CVEs timeline") | |
| ax.text(0.5, 0.5, "No data", ha="center", va="center", transform=ax.transAxes) | |
| save_graph(fig, prefix, "timeline", svg_output, png_output) | |
| return | |
| dates, counts = zip(*sorted(D.items(), key=lambda x: x[0])) | |
| ax.plot(dates, counts) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/vulnerability_report.py` around lines 529 - 556, The graph helpers
crash when their Counter inputs are empty because generate_new_cve_dates_graph
and the package bar-chart block both unpack zip(*) results unconditionally.
Update the CVE dates and packages graph generation paths to detect empty data
before calling zip/unpacking, and in that case render the existing “No data”
placeholder instead of continuing. Use the generate_new_cve_dates_graph function
and the packages graph block that reads stat["dates"] and stat["packages"] as
the locations to apply the guard.
Description
LCORE-2631: Generate graphs
Type of change
Tools used to create PR
Related Tickets & Documents
Summary by CodeRabbit
New Features
Bug Fixes