Skip to content

Conversation

@andrewm4894
Copy link
Owner

@andrewm4894 andrewm4894 commented Aug 7, 2025

Migrate Dashboard Charts from Plotly to Monster UI ApexCharts

🎯 Summary

Complete migration of the anomstack dashboard from Plotly.js to Monster UI's native ApexChart components, maintaining all existing functionality while improving code organization and user experience.

✨ Key Changes

Charts Migration

  • Replaced Plotly with ApexCharts - Complete rewrite of chart generation system
  • Dual Y-Axes - Maintained value axis (left) + score axis (right)
  • Percentage Formatting - Score axis now displays as 0%-100% instead of decimals
  • Alert Markers - Red/orange dots at 100% line for anomaly detection alerts
  • Visual Consistency - Metric value markers now match blue line color
  • Responsive Design - Charts properly sized and responsive across devices

Technical Improvements

  • Native Integration - Uses Monster UI ApexChart components instead of raw HTML
  • Better Data Handling - Fixed JSON serialization issues with pandas data types
  • Clean Architecture - Removed circular imports and improved code organization
  • Performance - Eliminated debug statements and optimized chart initialization
  • Maintainability - Simplified codebase with better separation of concerns

Preserved Features

  • All Chart Types - Regular, expanded, and sparkline charts working
  • Interactivity - Zoom, pan, tooltips, and toolbar functionality
  • Themes - Dark/light mode support maintained
  • Legends - Chart legends with proper series identification
  • Settings - All dashboard customization options preserved

📁 Files Modified

  • dashboard/app.py - Added ApexCharts CDN, JavaScript initialization, and styling
  • dashboard/charts.py - Complete rewrite of ChartManager class for ApexCharts
  • dashboard/routes/batch.py - Updated chart rendering and imports

🧪 Testing

  • All chart types render correctly
  • Dual y-axes working with percentage formatting
  • Alert markers visible and properly positioned
  • Expanded view modal functionality working
  • Theme switching preserved
  • Responsive design across screen sizes
  • No console errors or warnings

📸 Visual Changes

  • Score axis now shows percentages (0%-100%) instead of decimals (0.0-1.0)
  • Alert markers are smaller and more visually appealing
  • Metric value dots match the primary line color for consistency
  • Charts maintain exact same layout and positioning

🚀 Benefits

  • Better Integration: Native Monster UI components instead of external HTML injection
  • Improved UX: Cleaner percentage display and consistent visual styling
  • Code Quality: Reduced complexity and better maintainability
  • Performance: Optimized chart initialization and rendering
  • Framework Consistency: Fully aligned with Monster UI design system

🔄 Migration Notes

This is a complete drop-in replacement - no breaking changes to the dashboard API or user experience. All existing functionality is preserved while improving the underlying implementation.

Summary by CodeRabbit

  • New Features

    • Switched chart rendering from Plotly to ApexCharts, offering improved interactivity and updated visual style.
    • Added dark mode support and enhanced chart customization options including markers, line width, and legend visibility.
    • Improved anomaly and alert visualization with distinct markers and dual y-axis support.
    • Dynamic chart initialization on page load and content updates for seamless user experience.
    • Updated anomaly list to use a responsive grid layout with improved pagination and on-demand expanded chart loading.
    • Enhanced feedback button styling and interaction in anomaly lists and modals.
  • Bug Fixes

    • Enhanced reliability of chart rendering during dynamic content updates.
  • Style

    • Updated chart styling for consistent sizing and appearance across the dashboard.
    • Refined icon sizes and layout in anomaly list controls.

Complete migration of anomstack dashboard from Plotly.js to Monster UI's native ApexChart components while maintaining all existing functionality and improving user experience.

Key changes:
- Replace Plotly with ApexCharts across all chart types (regular, expanded, sparklines)
- Implement dual y-axes with percentage-formatted score axis (0%-100%)
- Add properly sized alert markers at 100% line for anomaly detection
- Ensure metric value dots match blue line color for visual consistency
- Fix JSON serialization issues with pandas data types
- Add client-side percentage formatting via JavaScript
- Improve chart dimensions and responsive design
- Remove debug statements and optimize chart initialization
- Maintain all interactive features (zoom, tooltips, themes, legends)

Technical improvements:
- Use native Monster UI ApexChart components instead of raw HTML injection
- Eliminate circular imports and improve code organization
- Reduce codebase complexity while preserving full functionality
- Better integration with Monster UI design system

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The codebase transitions from using Plotly.js to ApexCharts for all dashboard chart rendering. This involves updating the dashboard app's JavaScript and CSS, refactoring chart creation logic to build ApexCharts configurations, and modifying routes to handle new chart parameters and output XML via fastcore.xml. No changes are made to public API signatures.

Changes

Cohort / File(s) Change Summary
Dashboard App Integration
dashboard/app.py
Replaces Plotly.js with ApexCharts by updating script sources, injects new CSS for chart display, adds inline JavaScript to initialize ApexCharts on custom <uk-chart> elements, handles lazy and immediate initialization including after HTMX swaps, and adds error logging. Imports Style for CSS injection.
Chart Rendering Refactor
dashboard/charts.py
Fully migrates chart rendering from Plotly to ApexCharts. Removes Plotly imports and figure building, adds ApexCharts option builders, updates chart creation methods and signatures to support ApexCharts features, preserves theming and marker logic, and converts timestamps to milliseconds.
Route and Serialization Updates
dashboard/routes/batch.py
Updates chart rendering endpoints to serialize ApexChart components to XML using fastcore.xml.to_xml, wraps output with Safe, passes new chart configuration parameters (e.g., dark_mode, small_charts, show_markers, line_width, show_legend) from app state, reduces anomaly list page size default, refactors anomaly list layout to CSS grid, and adjusts dynamic loading and styling of expanded charts and feedback buttons.
Test Adjustments
tests/test_dashboard.py
Modifies test assertions to match new ApexCharts configuration keys and structure, replacing Plotly-specific keys with ApexCharts chart and theme keys, and updates related checks accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant App (FastAPI)
    participant ChartManager
    participant ApexCharts

    User->>Browser: Requests dashboard page
    Browser->>App (FastAPI): HTTP request for charts
    App (FastAPI)->>ChartManager: create_chart(...) with config
    ChartManager->>App (FastAPI): Returns ApexChart config as XML
    App (FastAPI)->>Browser: Sends HTML with <uk-chart> & config
    Browser->>ApexCharts: Initializes charts on <uk-chart> via JS
    ApexCharts-->>Browser: Renders chart in DOM
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

🐇✨
Goodbye Plotly, hello Apex flair,
With XML charts floating in the air.
New colors, markers, themes delight,
The dashboard’s future’s looking bright!
From code to canvas, swift and smart—
A rabbit’s hop to a brand new chart!
📊🥕

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/monster-charts-migration

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@andrewm4894 andrewm4894 requested a review from Copilot August 7, 2025 13:12
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR completely migrates the anomstack dashboard's charting system from Plotly.js to Monster UI's native ApexCharts components, maintaining all functionality while improving integration and visual consistency.

Key Changes:

  • Replaced all Plotly chart generation with ApexCharts implementation in ChartManager class
  • Added ApexCharts CDN and JavaScript initialization logic for proper chart rendering
  • Updated chart rendering calls to pass configuration parameters and use to_xml() for serialization

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
dashboard/app.py Added ApexCharts CDN, custom styling for chart containers, and JavaScript initialization logic
dashboard/charts.py Complete rewrite of ChartManager class methods to generate ApexChart components instead of Plotly HTML
dashboard/routes/batch.py Updated chart method calls to pass configuration parameters and serialize charts with to_xml()

Comment on lines +61 to +67
uk-chart {
display: block !important;
width: 100% !important;
min-height: 300px !important;
height: auto !important;
}
uk-chart .apexcharts-canvas {
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CSS selector 'uk-chart' targets a custom element but lacks specificity. Consider using a more specific selector like '.uk-chart' or 'uk-chart[data-chart]' to avoid potential conflicts with other elements.

Suggested change
uk-chart {
display: block !important;
width: 100% !important;
min-height: 300px !important;
height: auto !important;
}
uk-chart .apexcharts-canvas {
uk-chart[data-chart-initialized] {
display: block !important;
width: 100% !important;
min-height: 300px !important;
height: auto !important;
}
uk-chart[data-chart-initialized] .apexcharts-canvas {

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +81
# Convert timestamps to milliseconds for ApexCharts
timestamps = [int(pd.to_datetime(ts).timestamp() * 1000) for ts in df_metric["metric_timestamp"]]

# Prepare main metric data (ensure native Python types for JSON serialization)
value_data = [[int(ts), float(val)] for ts, val in zip(timestamps, df_metric["metric_value"])]
score_data = [[int(ts), float(val)] for ts, val in zip(timestamps, df_metric["metric_score"])]
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested list comprehensions for data preparation could be optimized by using vectorized pandas operations or numpy arrays, especially for large datasets. Consider using df_metric[['metric_timestamp', 'metric_value']].values.tolist() after proper timestamp conversion.

Suggested change
# Convert timestamps to milliseconds for ApexCharts
timestamps = [int(pd.to_datetime(ts).timestamp() * 1000) for ts in df_metric["metric_timestamp"]]
# Prepare main metric data (ensure native Python types for JSON serialization)
value_data = [[int(ts), float(val)] for ts, val in zip(timestamps, df_metric["metric_value"])]
score_data = [[int(ts), float(val)] for ts, val in zip(timestamps, df_metric["metric_score"])]
# Convert timestamps to milliseconds for ApexCharts (vectorized)
df_metric = df_metric.copy()
df_metric["timestamp_ms"] = pd.to_datetime(df_metric["metric_timestamp"]).astype("int64") // 10**6
# Prepare main metric data (ensure native Python types for JSON serialization, vectorized)
value_data = df_metric[["timestamp_ms", "metric_value"]].astype({"timestamp_ms": int, "metric_value": float}).values.tolist()
score_data = df_metric[["timestamp_ms", "metric_score"]].astype({"timestamp_ms": int, "metric_score": float}).values.tolist()

Copilot uses AI. Check for mistakes.
Comment on lines +194 to +203
"width": [line_width, line_width, 0, 0], # Line widths for each series
"dashArray": [0, 5, 0, 0] # Dash patterns (score line dashed)
},
"markers": {
"size": [4 if show_markers else 0, 0, 4, 4], # Much smaller alert markers
"colors": [colors["primary"], "undefined", colors["alert"], colors["llmalert"]], # Value markers same as blue line
"strokeWidth": [1, 0, 1, 1],
"strokeColors": ["#ffffff", "#ffffff", "#ffffff", "#ffffff"],
"hover": {
"size": [6 if show_markers else 0, 0, 6, 6] # Smaller hover too
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded marker sizes array assumes a specific number of series. If the number of series changes (e.g., when alerts are added), this could cause index mismatches. Consider dynamically building this array based on the actual series count.

Suggested change
"width": [line_width, line_width, 0, 0], # Line widths for each series
"dashArray": [0, 5, 0, 0] # Dash patterns (score line dashed)
},
"markers": {
"size": [4 if show_markers else 0, 0, 4, 4], # Much smaller alert markers
"colors": [colors["primary"], "undefined", colors["alert"], colors["llmalert"]], # Value markers same as blue line
"strokeWidth": [1, 0, 1, 1],
"strokeColors": ["#ffffff", "#ffffff", "#ffffff", "#ffffff"],
"hover": {
"size": [6 if show_markers else 0, 0, 6, 6] # Smaller hover too
"width": stroke_widths, # Line widths for each series
"dashArray": dash_arrays # Dash patterns (score line dashed)
},
"markers": {
"size": marker_sizes, # Much smaller alert markers
"colors": marker_colors, # Value markers same as blue line
"strokeWidth": marker_stroke_widths,
"strokeColors": marker_stroke_colors,
"hover": {
"size": marker_hover_sizes # Smaller hover too

Copilot uses AI. Check for mistakes.
"height": 600,
"scale": 1
"stroke": {
"width": [line_width, line_width, 0, 0], # Line widths for each series
Copy link

Copilot AI Aug 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to marker sizes, the stroke width array is hardcoded for 4 series. This creates a brittle dependency on the exact number and order of series. Consider building this array dynamically based on the series configuration.

Suggested change
"width": [line_width, line_width, 0, 0], # Line widths for each series
"width": [
line_width if s.get("name", "").lower() in ["value", "score"] else 0
for s in series
], # Line widths for each series

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (3)
dashboard/routes/batch.py (2)

852-852: Fix incorrect button state logic

The positive button class should be secondary when feedback is negative, but the current condition will always evaluate to secondary.

-                    cls=(ButtonT.secondary if current_feedback == "negative" else ButtonT.secondary) + " p-2",
+                    cls=(ButtonT.primary if current_feedback == "positive" else ButtonT.secondary) + " p-2",

876-876: Fix duplicate button state logic error

Same issue as above - the positive button in the table row update has incorrect conditional logic.

-                    cls=(ButtonT.secondary if current_feedback == "negative" else ButtonT.secondary) + " sm:p-2 p-1",
+                    cls=(ButtonT.primary if current_feedback == "positive" else ButtonT.secondary) + " sm:p-2 p-1",
dashboard/charts.py (1)

512-525: Remove unused get_common_styling method

The get_common_styling helper is defined in dashboard/charts.py (lines 512–525) but isn’t referenced anywhere else in the codebase. It appears to be leftover from the old Plotly setup and can be safely removed to reduce dead code.

• File: dashboard/charts.py
• Lines: 512–525

Suggested diff:

--- a/dashboard/charts.py
+++ b/dashboard/charts.py
@@ -510,20 +510,6 @@ class ChartStyle:
     @staticmethod
-    def get_common_styling(colors: dict) -> tuple:
-        """Return common styling configurations."""
-        common_font = dict(size=10, color=colors["text"])
-        common_title_font = dict(size=12, color=colors["text"])
-        common_grid = dict(
-            showgrid=True,
-            gridwidth=1,
-            gridcolor=colors["grid"],
-            zeroline=False,
-            tickfont=common_font,
-            title_font=common_title_font,
-        )
-        return common_font, common_title_font, common_grid
🧹 Nitpick comments (4)
dashboard/app.py (1)

60-71: Clean up whitespace in CSS block

The CSS block contains trailing whitespace that should be removed for consistency.

         Style("""
 uk-chart {
     display: block !important;
     width: 100% !important;
     min-height: 300px !important;
     height: auto !important;
 }
 uk-chart .apexcharts-canvas {
     width: 100% !important;
     height: 100% !important;
 }
         """),
dashboard/routes/batch.py (2)

141-149: Remove trailing whitespace

Clean up formatting issues identified by static analysis.

         fig = ChartManager.create_chart(
-            df_metric, 
+            df_metric,
             chart_index,
             small_charts=app.state.small_charts,
             dark_mode=app.state.dark_mode,
             show_markers=app.state.show_markers,
             line_width=app.state.line_width,
             show_legend=app.state.show_legend
         )

300-306: Remove trailing whitespace in expanded chart creation

Clean up formatting inconsistencies.

     expanded_fig = ChartManager.create_expanded_chart(
-        df_metric, 
+        df_metric,
         chart_index,
         dark_mode=app.state.dark_mode,
         show_markers=app.state.show_markers,
         line_width=app.state.line_width
     )
dashboard/charts.py (1)

72-226: Clean up whitespace throughout the method

This method has numerous trailing whitespace and blank line formatting issues that should be cleaned up for consistency.

Remove trailing whitespace from lines 96, 136, 179 and clean up blank lines containing whitespace on lines 65, 75, 78, 82, 106, 127, 148, 224.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 772afb9 and 3347334.

📒 Files selected for processing (3)
  • dashboard/app.py (2 hunks)
  • dashboard/charts.py (3 hunks)
  • dashboard/routes/batch.py (7 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
dashboard/app.py (2)
dashboard/routes/toggles.py (1)
  • post (58-64)
dashboard/routes/index.py (1)
  • index (164-198)
dashboard/routes/batch.py (1)
dashboard/charts.py (5)
  • ChartManager (17-492)
  • create_chart (53-69)
  • create_expanded_chart (228-249)
  • create_sparkline (280-391)
  • create_single_anomaly_expanded_chart (440-492)
🪛 Ruff (0.12.2)
dashboard/app.py

75-75: Blank line contains whitespace

Remove whitespace from blank line

(W293)


81-81: Blank line contains whitespace

Remove whitespace from blank line

(W293)


89-89: Blank line contains whitespace

Remove whitespace from blank line

(W293)


94-94: Blank line contains whitespace

Remove whitespace from blank line

(W293)

dashboard/routes/batch.py

142-142: Trailing whitespace

Remove trailing whitespace

(W291)


301-301: Trailing whitespace

Remove trailing whitespace

(W291)


307-307: Blank line contains whitespace

Remove whitespace from blank line

(W293)


954-954: Blank line contains whitespace

Remove whitespace from blank line

(W293)

dashboard/charts.py

13-13: json imported but unused

Remove unused import: json

(F401)


14-14: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)


65-65: Blank line contains whitespace

Remove whitespace from blank line

(W293)


75-75: Blank line contains whitespace

Remove whitespace from blank line

(W293)


78-78: Blank line contains whitespace

Remove whitespace from blank line

(W293)


82-82: Blank line contains whitespace

Remove whitespace from blank line

(W293)


96-96: Trailing whitespace

Remove trailing whitespace

(W291)


106-106: Blank line contains whitespace

Remove whitespace from blank line

(W293)


127-127: Blank line contains whitespace

Remove whitespace from blank line

(W293)


136-136: Trailing whitespace

Remove trailing whitespace

(W291)


148-148: Blank line contains whitespace

Remove whitespace from blank line

(W293)


179-179: Trailing whitespace

Remove trailing whitespace

(W291)


224-224: Blank line contains whitespace

Remove whitespace from blank line

(W293)


240-240: Blank line contains whitespace

Remove whitespace from blank line

(W293)


245-245: Blank line contains whitespace

Remove whitespace from blank line

(W293)


292-292: Blank line contains whitespace

Remove whitespace from blank line

(W293)


295-295: Blank line contains whitespace

Remove whitespace from blank line

(W293)


299-299: Blank line contains whitespace

Remove whitespace from blank line

(W293)


310-310: Trailing whitespace

Remove trailing whitespace

(W291)


316-316: Blank line contains whitespace

Remove whitespace from blank line

(W293)


334-334: Blank line contains whitespace

Remove whitespace from blank line

(W293)


390-390: Blank line contains whitespace

Remove whitespace from blank line

(W293)


413-413: Blank line contains whitespace

Remove whitespace from blank line

(W293)


421-421: Blank line contains whitespace

Remove whitespace from blank line

(W293)


436-436: Blank line contains whitespace

Remove whitespace from blank line

(W293)


452-452: Blank line contains whitespace

Remove whitespace from blank line

(W293)


461-461: Blank line contains whitespace

Remove whitespace from blank line

(W293)


469-469: Blank line contains whitespace

Remove whitespace from blank line

(W293)


480-480: Blank line contains whitespace

Remove whitespace from blank line

(W293)


491-491: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🔇 Additional comments (1)
dashboard/charts.py (1)

77-81: Good handling of pandas data type serialization

The explicit conversion to native Python types (int/float) properly addresses the JSON serialization issues with pandas data types mentioned in the PR objectives.

hdrs=(
Theme.blue.headers(),
Script(src="https://cdn.plot.ly/plotly-2.32.0.min.js"),
Script(src="https://cdn.jsdelivr.net/npm/apexcharts@latest"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a specific version for the ApexCharts CDN

Using @latest in the CDN URL could lead to unexpected breaking changes in production. Pin to a specific version for stability.

-        Script(src="https://cdn.jsdelivr.net/npm/apexcharts@latest"),
+        Script(src="https://cdn.jsdelivr.net/npm/apexcharts@3.45.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.

Suggested change
Script(src="https://cdn.jsdelivr.net/npm/apexcharts@latest"),
Script(src="https://cdn.jsdelivr.net/npm/apexcharts@3.45.0"),
🤖 Prompt for AI Agents
In dashboard/app.py at line 59, the ApexCharts CDN URL uses '@latest', which can
cause instability due to unexpected breaking changes. Replace '@latest' with a
specific version number to ensure consistent and stable behavior in production.

Comment on lines 72 to 126
Script("""
function initializeCharts() {
const elements = document.querySelectorAll('uk-chart:not([data-chart-initialized])');
elements.forEach(function(element) {
const script = element.querySelector('script[type="application/json"]');
if (script) {
try {
const config = JSON.parse(script.textContent);
// Add percentage formatting to score axis (second y-axis)
if (config.yaxis && config.yaxis[1]) {
config.yaxis[1].labels = config.yaxis[1].labels || {};
config.yaxis[1].labels.formatter = function(val) {
return Math.round(val * 100) + '%';
};
}
// Ensure chart has dimensions
element.style.minHeight = '300px';
element.style.width = '100%';
element.style.display = 'block';
const chart = new ApexCharts(element, config);
chart.render().then(() => {
element.setAttribute('data-chart-initialized', 'true');
});
} catch (e) {
console.error('Error initializing chart:', e, element);
}
}
});
}
// Wait for both DOM and ApexCharts to be ready
document.addEventListener('DOMContentLoaded', function() {
// Check if ApexCharts is loaded
if (typeof ApexCharts !== 'undefined') {
initializeCharts();
} else {
// Wait a bit more for ApexCharts to load
setTimeout(() => {
if (typeof ApexCharts !== 'undefined') {
initializeCharts();
} else {
console.error('ApexCharts library not loaded');
}
}, 1000);
}
});
// Re-initialize charts after HTMX requests
document.addEventListener('htmx:afterSwap', initializeCharts);
document.addEventListener('htmx:afterSettle', initializeCharts);
"""),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add chart cleanup to prevent memory leaks

Charts are re-initialized after HTMX swaps without destroying previous instances, which could cause memory leaks. Also, the percentage formatter should handle edge cases.

 function initializeCharts() {
     const elements = document.querySelectorAll('uk-chart:not([data-chart-initialized])');
     
     elements.forEach(function(element) {
+        // Destroy existing chart if present
+        if (element._chart) {
+            element._chart.destroy();
+            element._chart = null;
+        }
+        
         const script = element.querySelector('script[type="application/json"]');
         if (script) {
             try {
                 const config = JSON.parse(script.textContent);
                 
                 // Add percentage formatting to score axis (second y-axis)
                 if (config.yaxis && config.yaxis[1]) {
                     config.yaxis[1].labels = config.yaxis[1].labels || {};
                     config.yaxis[1].labels.formatter = function(val) {
-                        return Math.round(val * 100) + '%';
+                        // Handle edge cases and ensure valid percentage
+                        const percentage = Math.round((val || 0) * 100);
+                        return Math.min(100, Math.max(0, percentage)) + '%';
                     };
                 }
                 
                 // Ensure chart has dimensions
                 element.style.minHeight = '300px';
                 element.style.width = '100%';
                 element.style.display = 'block';
                 
                 const chart = new ApexCharts(element, config);
                 chart.render().then(() => {
                     element.setAttribute('data-chart-initialized', 'true');
+                    // Store chart instance for cleanup
+                    element._chart = chart;
                 });
             } catch (e) {
                 console.error('Error initializing chart:', e, element);
             }
         }
     });
 }

+// Clean up charts before page unload
+window.addEventListener('beforeunload', function() {
+    document.querySelectorAll('uk-chart[data-chart-initialized]').forEach(function(element) {
+        if (element._chart) {
+            element._chart.destroy();
+        }
+    });
+});
📝 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.

Suggested change
Script("""
function initializeCharts() {
const elements = document.querySelectorAll('uk-chart:not([data-chart-initialized])');
elements.forEach(function(element) {
const script = element.querySelector('script[type="application/json"]');
if (script) {
try {
const config = JSON.parse(script.textContent);
// Add percentage formatting to score axis (second y-axis)
if (config.yaxis && config.yaxis[1]) {
config.yaxis[1].labels = config.yaxis[1].labels || {};
config.yaxis[1].labels.formatter = function(val) {
return Math.round(val * 100) + '%';
};
}
// Ensure chart has dimensions
element.style.minHeight = '300px';
element.style.width = '100%';
element.style.display = 'block';
const chart = new ApexCharts(element, config);
chart.render().then(() => {
element.setAttribute('data-chart-initialized', 'true');
});
} catch (e) {
console.error('Error initializing chart:', e, element);
}
}
});
}
// Wait for both DOM and ApexCharts to be ready
document.addEventListener('DOMContentLoaded', function() {
// Check if ApexCharts is loaded
if (typeof ApexCharts !== 'undefined') {
initializeCharts();
} else {
// Wait a bit more for ApexCharts to load
setTimeout(() => {
if (typeof ApexCharts !== 'undefined') {
initializeCharts();
} else {
console.error('ApexCharts library not loaded');
}
}, 1000);
}
});
// Re-initialize charts after HTMX requests
document.addEventListener('htmx:afterSwap', initializeCharts);
document.addEventListener('htmx:afterSettle', initializeCharts);
"""),
Script("""
function initializeCharts() {
const elements = document.querySelectorAll('uk-chart:not([data-chart-initialized])');
elements.forEach(function(element) {
// Destroy existing chart if present
if (element._chart) {
element._chart.destroy();
element._chart = null;
}
const script = element.querySelector('script[type="application/json"]');
if (script) {
try {
const config = JSON.parse(script.textContent);
// Add percentage formatting to score axis (second y-axis)
if (config.yaxis && config.yaxis[1]) {
config.yaxis[1].labels = config.yaxis[1].labels || {};
config.yaxis[1].labels.formatter = function(val) {
// Handle edge cases and ensure valid percentage
const percentage = Math.round((val || 0) * 100);
return Math.min(100, Math.max(0, percentage)) + '%';
};
}
// Ensure chart has dimensions
element.style.minHeight = '300px';
element.style.width = '100%';
element.style.display = 'block';
const chart = new ApexCharts(element, config);
chart.render().then(() => {
element.setAttribute('data-chart-initialized', 'true');
// Store chart instance for cleanup
element._chart = chart;
});
} catch (e) {
console.error('Error initializing chart:', e, element);
}
}
});
}
// Clean up charts before page unload
window.addEventListener('beforeunload', function() {
document.querySelectorAll('uk-chart[data-chart-initialized]').forEach(function(element) {
if (element._chart) {
element._chart.destroy();
}
});
});
// Wait for both DOM and ApexCharts to be ready
document.addEventListener('DOMContentLoaded', function() {
// Check if ApexCharts is loaded
if (typeof ApexCharts !== 'undefined') {
initializeCharts();
} else {
// Wait a bit more for ApexCharts to load
setTimeout(() => {
if (typeof ApexCharts !== 'undefined') {
initializeCharts();
} else {
console.error('ApexCharts library not loaded');
}
}, 1000);
}
});
// Re-initialize charts after HTMX requests
document.addEventListener('htmx:afterSwap', initializeCharts);
document.addEventListener('htmx:afterSettle', initializeCharts);
"""),
🧰 Tools
🪛 Ruff (0.12.2)

75-75: Blank line contains whitespace

Remove whitespace from blank line

(W293)


81-81: Blank line contains whitespace

Remove whitespace from blank line

(W293)


89-89: Blank line contains whitespace

Remove whitespace from blank line

(W293)


94-94: Blank line contains whitespace

Remove whitespace from blank line

(W293)

🤖 Prompt for AI Agents
In dashboard/app.py around lines 72 to 126, the chart initialization code
re-creates ApexCharts instances on HTMX events without destroying previous
charts, causing potential memory leaks. Modify the code to keep track of created
chart instances per element and call their destroy method before
re-initializing. Also, update the percentage formatter to safely handle
non-numeric or null values by checking the input before formatting.

Comment on lines +13 to +14
import json
from datetime import datetime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove unused imports

The json and datetime imports are not used in the code.

 from monsterui.all import Card, DivLAligned, Loading, LoadingT, TextPresets, ApexChart
 import pandas as pd
-import json
-from datetime import datetime
📝 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.

Suggested change
import json
from datetime import datetime
from monsterui.all import Card, DivLAligned, Loading, LoadingT, TextPresets, ApexChart
import pandas as pd
🧰 Tools
🪛 Ruff (0.12.2)

13-13: json imported but unused

Remove unused import: json

(F401)


14-14: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)

🤖 Prompt for AI Agents
In dashboard/charts.py around lines 13 to 14, the imports for json and datetime
are not used anywhere in the code. Remove both import statements to clean up the
code and avoid unnecessary dependencies.

Update test_chart_manager_config_and_colors to verify ApexCharts configuration keys
instead of Plotly configuration keys after migration.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Aug 7, 2025

📊 Test Coverage Report

Coverage: 56% (yellow)

✅ Coverage maintained or improved!

💡 See detailed coverage report in the tests README

@github-actions
Copy link

github-actions bot commented Aug 8, 2025

📊 Test Coverage Report

Coverage: 56% (yellow)

✅ Coverage maintained or improved!

💡 See detailed coverage report in the tests README

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
dashboard/app.py (3)

60-89: Selector still too generic.

Past feedback about uk-chart specificity hasn’t been addressed; consider [data-chart] or a BEM-style class to avoid style clashes.


59-60: Pin the ApexCharts version instead of using @latest.

Unpinned CDN can introduce breaking changes at runtime. Stick to a known good version (e.g. @3.45.0).


170-210: Destroy previously created charts to avoid memory leaks.

initializeCharts() may run multiple times after HTMX swaps but there’s no call to chart.destroy().
Add a _chart handle on the element and destroy() it before re-rendering.

@@
-        const chart = new ApexCharts(el, config);
-        chart.render().then(() => el.setAttribute('data-chart-initialized', 'true'));
+        if (el._chart) {
+            el._chart.destroy();
+        }
+        const chart = new ApexCharts(el, config);
+        chart.render().then(() => {
+            el._chart = chart;
+            el.setAttribute('data-chart-initialized', 'true');
+        });
dashboard/charts.py (2)

13-14: Remove unused imports json and datetime.


194-205: Hard-coded stroke/marker arrays break if series count changes.

Adding or omitting alert series will desynchronise these arrays from series.length, leading to wrong styling.

- "width": [line_width, line_width, 0, 0],
+ "width": Array(series.length).fill(0).map((_, i) =>
+     i < 2 ? line_width : 0),

Apply the same pattern for dashArray, markers.size, etc.

🧹 Nitpick comments (6)
dashboard/app.py (2)

102-105: DRY up the percentage-formatter block.

The same four lines are duplicated; wrap them in a helper to avoid drift.

Also applies to: 139-143


211-230: Remove verbose console logging or gate it behind a debug flag.

Frequent console.log in production harms performance and pollutes logs.

dashboard/routes/batch.py (2)

10-12: Unused HTML-table imports can be dropped.

Table, Td, Th, Tr are no longer referenced after the grid refactor.


142-149: Clean up trailing whitespace flagged by Ruff.

Running ruff --fix will remove the W291/W293 offences.

Also applies to: 301-307, 953-956

dashboard/charts.py (2)

386-395: Duplicate issue in sparkline config – static arrays assume five series.

Build arrays dynamically to match series.length as above.


292-299: Ruff: strip blank-line whitespace.

Multiple W293 warnings—run ruff --fix.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3404fc8 and 55f3939.

📒 Files selected for processing (3)
  • dashboard/app.py (2 hunks)
  • dashboard/charts.py (3 hunks)
  • dashboard/routes/batch.py (11 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
dashboard/app.py (2)
dashboard/routes/toggles.py (1)
  • post (58-64)
dashboard/routes/index.py (1)
  • index (164-198)
🪛 Ruff (0.12.2)
dashboard/charts.py

13-13: json imported but unused

Remove unused import: json

(F401)


14-14: datetime.datetime imported but unused

Remove unused import: datetime.datetime

(F401)


65-65: Blank line contains whitespace

Remove whitespace from blank line

(W293)


75-75: Blank line contains whitespace

Remove whitespace from blank line

(W293)


78-78: Blank line contains whitespace

Remove whitespace from blank line

(W293)


82-82: Blank line contains whitespace

Remove whitespace from blank line

(W293)


96-96: Trailing whitespace

Remove trailing whitespace

(W291)


106-106: Blank line contains whitespace

Remove whitespace from blank line

(W293)


127-127: Blank line contains whitespace

Remove whitespace from blank line

(W293)


136-136: Trailing whitespace

Remove trailing whitespace

(W291)


148-148: Blank line contains whitespace

Remove whitespace from blank line

(W293)


179-179: Trailing whitespace

Remove trailing whitespace

(W291)


224-224: Blank line contains whitespace

Remove whitespace from blank line

(W293)


240-240: Blank line contains whitespace

Remove whitespace from blank line

(W293)


245-245: Blank line contains whitespace

Remove whitespace from blank line

(W293)


292-292: Blank line contains whitespace

Remove whitespace from blank line

(W293)


295-295: Blank line contains whitespace

Remove whitespace from blank line

(W293)


299-299: Blank line contains whitespace

Remove whitespace from blank line

(W293)


310-310: Trailing whitespace

Remove trailing whitespace

(W291)


317-317: Blank line contains whitespace

Remove whitespace from blank line

(W293)


330-330: Blank line contains whitespace

Remove whitespace from blank line

(W293)


338-338: Trailing whitespace

Remove trailing whitespace

(W291)


343-343: Blank line contains whitespace

Remove whitespace from blank line

(W293)


350-350: Blank line contains whitespace

Remove whitespace from blank line

(W293)


353-353: Trailing whitespace

Remove trailing whitespace

(W291)


354-354: Trailing whitespace

Remove trailing whitespace

(W291)


357-357: Blank line contains whitespace

Remove whitespace from blank line

(W293)


365-365: Blank line contains whitespace

Remove whitespace from blank line

(W293)


408-408: Blank line contains whitespace

Remove whitespace from blank line

(W293)


442-442: Blank line contains whitespace

Remove whitespace from blank line

(W293)


450-450: Blank line contains whitespace

Remove whitespace from blank line

(W293)


465-465: Blank line contains whitespace

Remove whitespace from blank line

(W293)


481-481: Blank line contains whitespace

Remove whitespace from blank line

(W293)


490-490: Blank line contains whitespace

Remove whitespace from blank line

(W293)


498-498: Blank line contains whitespace

Remove whitespace from blank line

(W293)


509-509: Blank line contains whitespace

Remove whitespace from blank line

(W293)


520-520: Blank line contains whitespace

Remove whitespace from blank line

(W293)

dashboard/routes/batch.py

142-142: Trailing whitespace

Remove trailing whitespace

(W291)


301-301: Trailing whitespace

Remove trailing whitespace

(W291)


307-307: Blank line contains whitespace

Remove whitespace from blank line

(W293)


953-953: Blank line contains whitespace

Remove whitespace from blank line

(W293)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (1)
dashboard/charts.py (1)

90-104: yAxisIndex and per-series marker/fill are not recognised by ApexCharts.

ApexCharts maps series to y-axes by array position; unsupported keys are silently ignored.
Double-check that the right axis is actually used or switch to top-level yaxis[index].seriesName mapping.

Comment on lines +378 to 388
for row_idx, (_, row) in enumerate(df_page.iterrows(), start=1):
metric_name = row["metric_name"]
timestamp = row["metric_timestamp"]

# Get the metric data for this anomaly
df_metric = df[df["metric_name"] == metric_name].copy()
df_metric = df_metric.sort_values("metric_timestamp")
fig = ChartManager.create_sparkline(df_metric, anomaly_timestamp=timestamp)
log.info(f"Creating sparkline for {metric_name} with {len(df_metric)} data points")
fig = ChartManager.create_sparkline(df_metric, anomaly_timestamp=timestamp, dark_mode=app.state.dark_mode)
log.info(f"Sparkline created: {type(fig)}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential performance hit: full sparkline generation inside tight loop.

create_sparkline() is called for every anomaly row with the entire metric history; this can dominate render time when pages contain many anomalies.
Cache per-metric sparklines or pre-slice to the last n points before passing into the chart helper.

🤖 Prompt for AI Agents
In dashboard/routes/batch.py around lines 378 to 388, the create_sparkline
function is called inside a loop for each anomaly row using the full metric
history, causing performance issues. To fix this, implement caching of
sparklines per metric to avoid redundant processing, or pre-slice df_metric to
include only the last n data points before calling create_sparkline. This
reduces the data size and avoids repeated full history processing for each
anomaly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants