solidstats 1.1.0 → 3.0.0.beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +17 -0
  3. data/README.md +27 -0
  4. data/Rakefile +3 -3
  5. data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
  6. data/app/controllers/solidstats/dashboard_controller.rb +82 -60
  7. data/app/controllers/solidstats/logs_controller.rb +72 -0
  8. data/app/controllers/solidstats/performance_controller.rb +25 -0
  9. data/app/controllers/solidstats/productivity_controller.rb +39 -0
  10. data/app/controllers/solidstats/quality_controller.rb +152 -0
  11. data/app/controllers/solidstats/securities_controller.rb +30 -0
  12. data/app/helpers/solidstats/application_helper.rb +155 -0
  13. data/app/helpers/solidstats/performance_helper.rb +87 -0
  14. data/app/helpers/solidstats/productivity_helper.rb +38 -0
  15. data/app/services/solidstats/bundler_audit_service.rb +206 -0
  16. data/app/services/solidstats/coverage_compass_service.rb +335 -0
  17. data/app/services/solidstats/load_lens_service.rb +454 -0
  18. data/app/services/solidstats/log_size_monitor_service.rb +205 -74
  19. data/app/services/solidstats/my_todo_service.rb +242 -0
  20. data/app/services/solidstats/style_patrol_service.rb +319 -0
  21. data/app/views/layouts/solidstats/application.html.erb +9 -2
  22. data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
  23. data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
  24. data/app/views/solidstats/logs/logs_size.html.erb +409 -0
  25. data/app/views/solidstats/performance/load_lens.html.erb +158 -0
  26. data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
  27. data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
  28. data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
  29. data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
  30. data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
  31. data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
  32. data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
  33. data/config/routes.rb +32 -4
  34. data/lib/generators/solidstats/install/install_generator.rb +28 -2
  35. data/lib/generators/solidstats/install/templates/README +7 -0
  36. data/lib/solidstats/version.rb +1 -1
  37. data/lib/tasks/solidstats_performance.rake +84 -0
  38. metadata +43 -19
  39. data/app/services/solidstats/audit_service.rb +0 -56
  40. data/app/services/solidstats/data_collector_service.rb +0 -83
  41. data/app/services/solidstats/todo_service.rb +0 -114
  42. data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
  43. data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
  44. data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
  45. data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
  46. data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
  47. data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
  48. data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
  49. data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
  50. data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
  51. data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
  52. data/app/views/solidstats/dashboard/index.html.erb +0 -1351
  53. data/lib/tasks/solidstats_tasks.rake +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1e51da79f65a50793bc1bfc777ed19df5d81795739ceda8558d3261745b53ae
4
- data.tar.gz: e58f0018fd96135789f192372b2b85f7165cf51a6921fa7e2a56057bebdb14b5
3
+ metadata.gz: dd313b23c968d18619f31b5157b96b2a9c75435398121e800deaf6c2e7d4bfd4
4
+ data.tar.gz: 1f7407373917b47d1ef56ebe7e03138cd36ef111517a930e0c8b6354bc2e389f
5
5
  SHA512:
6
- metadata.gz: 3fc113aaaf952e1d9a320b55618b72d4b6025ec81d3bc274f29a090ddbc6a0f500bbd50aa2873d8e02d850dac992ef28214e5709f0160bc2ce0e88e348182f0a
7
- data.tar.gz: 976d3989eeffc2781c1040846af1435147be33a407b6e5a2bae1db8dfba091f9678635b1863c3dcc36916adf5d2b334cc0f0a7d523225066bb4fdac074ebf270
6
+ metadata.gz: adcd27c59ddc3e68c8843583dd274d96312959155c3a0491f06540cd1eb474681bcb7de407b6d286bc1a9e97dd03b4e94099b3e40f5232a37929b3c2565e1cd2
7
+ data.tar.gz: 515a1510e1911e65f8b7784bf746341112f469de7800170923f3f3f716c537825fe742afa266ecb5fc091ad9c1e3c7aebfbef328eef964da69960a7b0d63506b
data/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.0.0] - 2025-06-11
9
+
10
+ ### Added
11
+ - **LoadLens Performance Monitor**: A new development performance monitoring feature that parses development logs to track Rails application performance. It includes metrics for response times, database performance, view rendering, and error rates.
12
+ - **Style Patrol**: A new code quality analysis feature to enforce style guidelines and improve code health.
13
+ - **My TODOs Feature**: A new service to manage and track TODO items within the codebase, with a dedicated UI and refresh functionality.
14
+ - **Bundler Audit Security**: Added security auditing for Bundler, with a dedicated UI to display vulnerability details and remediation suggestions.
15
+
16
+ ### Changed
17
+ - **Dashboard Redesign**: The Solidstats dashboard has been completely redesigned with DaisyUI, providing a modern, consistent, and themeable user interface.
18
+ - **Refactored Services**: The `LogSizeMonitorService` has been refactored for enhanced log management and caching.
19
+ - **Improved Installation**: The installation process has been enhanced with automatic directory creation and `.gitignore` updates.
20
+
21
+ ### Fixed
22
+ - Updated route aliases and paths for better consistency across the application.
23
+ - Replaced static dashboard titles with dynamic links for improved navigation.
24
+
8
25
  ## [1.1.0] - 2025-05-23
9
26
 
10
27
  ### Added
data/README.md CHANGED
@@ -9,6 +9,7 @@ Solidstats is a local-only Rails engine that shows your project's health at `/so
9
9
  - Gem impact analysis showing affected gems by severity
10
10
  - Log Size Monitor for tracking and managing application log files
11
11
  - Log file truncation tool for individual or all log files
12
+ - LoadLens - Development performance monitoring with request tracking
12
13
  - Rubocop offense count and quality metrics
13
14
  - TODO/FIXME tracker with file hotspots
14
15
  - Test coverage summary
@@ -77,6 +78,32 @@ You can refresh the dashboard data at any time by clicking the "Refresh" button
77
78
  ### Code Quality
78
79
  Displays code quality metrics, test coverage, and code health indicators.
79
80
 
81
+ ### LoadLens Performance
82
+ Monitors your Rails app's performance in development by parsing `log/development.log`:
83
+ - **Response Times**: Average response time across all requests
84
+ - **Database Performance**: ActiveRecord query timing analysis
85
+ - **View Rendering**: Template rendering performance metrics
86
+ - **Error Tracking**: HTTP status code analysis and error rates
87
+ - **Slow Request Detection**: Identifies requests taking >1000ms
88
+ - **Request History**: Recent request details with timing breakdown
89
+
90
+ LoadLens automatically tracks performance data and provides manual refresh capability. Data is stored in daily rotating JSON files and cleaned up after 7 days.
91
+
92
+ **CLI Commands:**
93
+ ```bash
94
+ # Parse development logs manually
95
+ rake solidstats:load_lens:parse_logs
96
+
97
+ # Refresh performance data
98
+ rake solidstats:load_lens:refresh
99
+
100
+ # View performance summary
101
+ rake solidstats:load_lens:summary
102
+
103
+ # Clean old data files
104
+ rake solidstats:load_lens:clean_old_data
105
+ ```
106
+
80
107
  ### Tasks
81
108
  Shows a breakdown of TODO, FIXME, and HACK annotations found in your codebase, with file hotspots and expandable details.
82
109
 
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
1
  require "bundler/setup"
2
-
3
- load "rails/tasks/statistics.rake"
4
-
5
2
  require "bundler/gem_tasks"
3
+
4
+ # Load solidstats-specific tasks
5
+ Dir[File.join(File.dirname(__FILE__), 'lib', 'tasks', '*.rake')].each { |f| load f }
@@ -0,0 +1,48 @@
1
+ /* Solidstats Dashboard with DaisyUI */
2
+
3
+ /* Custom animations for dashboard */
4
+ .dashboard-enter {
5
+ animation: fadeInUp 0.6s ease-out;
6
+ }
7
+
8
+ @keyframes fadeInUp {
9
+ from {
10
+ opacity: 0;
11
+ transform: translateY(30px);
12
+ }
13
+ to {
14
+ opacity: 1;
15
+ transform: translateY(0);
16
+ }
17
+ }
18
+
19
+ /* Gradient text for brand */
20
+ .gradient-text {
21
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
22
+ -webkit-background-clip: text;
23
+ -webkit-text-fill-color: transparent;
24
+ background-clip: text;
25
+ }
26
+
27
+ /* Light mode improvements */
28
+ [data-theme="light"] .card {
29
+ background-color: #ffffff;
30
+ border: 1px solid #e5e7eb;
31
+ }
32
+
33
+ [data-theme="light"] .card:hover {
34
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
35
+ }
36
+
37
+ [data-theme="light"] .stat-value {
38
+ color: #1f2937;
39
+ }
40
+
41
+ [data-theme="light"] .card-title {
42
+ color: #1f2937;
43
+ }
44
+
45
+ [data-theme="light"] .navbar {
46
+ background-color: #ffffff;
47
+ border-bottom: 1px solid #e5e7eb;
48
+ }
@@ -1,77 +1,99 @@
1
1
  module Solidstats
2
2
  class DashboardController < ApplicationController
3
+ layout 'solidstats/dashboard'
4
+
3
5
  TODO_CACHE_FILE = Rails.root.join("tmp", "solidstats_todos.json")
4
6
  AUDIT_CACHE_HOURS = 12 # Configure how many hours before refreshing
5
7
 
6
- def index
7
- # Use new services for data collection
8
- audit_service = AuditService.new
9
- todo_service = TodoService.new
10
- log_monitor_service = LogSizeMonitorService.new
11
-
12
- # Get full data for detailed views
13
- @audit_output = audit_service.fetch
14
- @todo_items = todo_service.fetch
15
- @log_data = log_monitor_service.collect_data
16
8
 
17
- # Get summary data for dashboard cards
18
- @audit_summary = audit_service.summary
19
- @todo_summary = todo_service.summary
20
-
21
- # TODO: Refactor these to use services as well
22
- @rubocop_output = "JSON.parse(`rubocop --format json`)"
23
- @coverage = "100"
9
+ def dashboard
10
+ # Load dashboard cards from JSON file
11
+ @dashboard_cards = load_dashboard_cards
12
+ @quick_actions = quick_actions_data
13
+ render 'dashboard'
24
14
  end
25
15
 
26
- # Force refresh all dashboard data
27
16
  def refresh
28
- # Create services
29
- audit_service = AuditService.new
30
- todo_service = TodoService.new
31
- log_monitor_service = LogSizeMonitorService.new
32
-
33
- # Force refresh of data
34
- audit_output = audit_service.fetch(true) # Force refresh
35
- todo_items = todo_service.fetch(true) # Force refresh
36
- log_data = log_monitor_service.collect_data
37
-
38
- # Get updated summaries
39
- audit_summary = audit_service.summary
40
- todo_summary = todo_service.summary
17
+ # Refresh all services
18
+ Solidstats::LogSizeMonitorService.scan_and_cache
19
+ Solidstats::BundlerAuditService.scan_and_cache
20
+ Solidstats::MyTodoService.collect_todos
21
+ Solidstats::StylePatrolService.refresh_cache
22
+ Solidstats::CoverageCompassService.refresh_cache
23
+ Solidstats::LoadLensService.scan_and_cache
24
+
25
+ redirect_to solidstats_dashboard_path, notice: 'Dashboard data refreshed successfully!'
26
+ end
41
27
 
42
- # Get current time for last updated display
43
- last_updated = Time.now.strftime("%B %d, %Y at %H:%M")
28
+ private
44
29
 
45
- # Return JSON response with refreshed data
46
- render json: {
47
- audit_output: audit_output,
48
- todo_items: todo_items,
49
- audit_summary: audit_summary,
50
- todo_summary: todo_summary,
51
- log_data: log_data,
52
- last_updated: last_updated,
53
- status: "success"
54
- }
55
- rescue StandardError => e
56
- # Return error information
57
- render json: {
58
- status: "error",
59
- message: "Failed to refresh data: #{e.message}"
60
- }, status: :internal_server_error
30
+ def quick_actions_data
31
+ [
32
+ {
33
+ icon: 'refresh-cw',
34
+ label: 'Refresh Data',
35
+ color: 'blue',
36
+ action: 'refresh_dashboard'
37
+ },
38
+ {
39
+ icon: 'settings',
40
+ label: 'Configure',
41
+ color: 'purple',
42
+ action: 'open_settings'
43
+ },
44
+ {
45
+ icon: 'download',
46
+ label: 'Export',
47
+ color: 'green',
48
+ action: 'export_data'
49
+ },
50
+ {
51
+ icon: 'bell',
52
+ label: 'Alerts',
53
+ color: 'orange',
54
+ action: 'view_alerts'
55
+ }
56
+ ]
61
57
  end
62
-
63
- def truncate_log
64
- log_monitor_service = LogSizeMonitorService.new
65
- filename = params[:filename]
58
+
59
+ def load_dashboard_cards
60
+ json_file_path = Rails.root.join("solidstats", "summary.json")
66
61
 
67
- # Add .log extension if not included in the filename
68
- if filename.present? && !filename.end_with?('.log')
69
- filename = "#{filename}.log"
62
+ begin
63
+ # Read and parse the JSON file
64
+ json_data = JSON.parse(File.read(json_file_path))
65
+
66
+ # Transform the JSON data into the format expected by the view
67
+ json_data.map do |name, data|
68
+ {
69
+ name: name,
70
+ icon: data["icon"],
71
+ status: data["status"],
72
+ value: data["value"],
73
+ last_updated: Time.parse(data["last_updated"]),
74
+ url: data["url"],
75
+ badges: data["badges"] || []
76
+ }
77
+ end
78
+ rescue Errno::ENOENT
79
+ Rails.logger.warn("Summary JSON file not found, generating initial data...")
80
+ generate_initial_data
81
+ retry
82
+ rescue JSON::ParserError => e
83
+ Rails.logger.error("Error parsing summary JSON: #{e.message}")
84
+ # Fallback to empty array if JSON is invalid
85
+ []
70
86
  end
71
-
72
- result = log_monitor_service.truncate_log(filename)
73
-
74
- render json: result
87
+ end
88
+
89
+ def generate_initial_data
90
+ # Force a scan to create initial data if missing
91
+ Solidstats::LogSizeMonitorService.scan_and_cache
92
+ Solidstats::BundlerAuditService.scan_and_cache
93
+ Solidstats::MyTodoService.collect_todos
94
+ Solidstats::StylePatrolService.refresh_cache
95
+ Solidstats::CoverageCompassService.refresh_cache
96
+ Solidstats::DevLogParserService.scan_and_cache
75
97
  end
76
98
  end
77
99
  end
@@ -0,0 +1,72 @@
1
+ module Solidstats
2
+ class LogsController < ApplicationController
3
+ layout 'solidstats/dashboard'
4
+
5
+ def logs_size
6
+ @logs_data = Solidstats::LogSizeMonitorService.get_logs_data
7
+ end
8
+
9
+ def truncate
10
+ filename = params[:filename]
11
+
12
+ # Validate filename presence
13
+ if filename.blank?
14
+ return render json: {
15
+ status: "error",
16
+ message: "Filename is required"
17
+ }, status: :bad_request
18
+ end
19
+
20
+ # Remove any path traversal attempts for security
21
+ filename = File.basename(filename)
22
+
23
+ # Ensure it's a .log file
24
+ unless filename.end_with?('.log')
25
+ filename = "#{filename}.log" if filename.present?
26
+ end
27
+
28
+ # Additional security check
29
+ unless filename.end_with?('.log')
30
+ return render json: {
31
+ status: "error",
32
+ message: "Invalid file type. Only .log files can be truncated."
33
+ }, status: :bad_request
34
+ end
35
+
36
+ result = Solidstats::LogSizeMonitorService.truncate_log(filename)
37
+
38
+ if result[:status] == "success"
39
+ render json: {
40
+ status: "success",
41
+ message: result[:message] || "Log file truncated successfully",
42
+ filename: filename,
43
+ original_size: result[:original_size]
44
+ }
45
+ else
46
+ render json: result, status: :unprocessable_entity
47
+ end
48
+ rescue StandardError => e
49
+ Rails.logger.error("Failed to truncate log #{filename}: #{e.message}")
50
+ render json: {
51
+ status: "error",
52
+ message: "Failed to truncate log: #{e.message}"
53
+ }, status: :internal_server_error
54
+ end
55
+
56
+ def refresh
57
+ # Force refresh of log monitoring data
58
+ result = Solidstats::LogSizeMonitorService.scan_and_cache
59
+
60
+ render json: {
61
+ status: "success",
62
+ message: "Log data refreshed",
63
+ data: result
64
+ }
65
+ rescue StandardError => e
66
+ render json: {
67
+ status: "error",
68
+ message: "Failed to refresh log data: #{e.message}"
69
+ }, status: :internal_server_error
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solidstats
4
+ class PerformanceController < ApplicationController
5
+ layout 'solidstats/dashboard'
6
+
7
+ def load_lens
8
+ @performance_data = LoadLensService.get_performance_data
9
+ @metrics = @performance_data[:summary] || {}
10
+ @recent_requests = @performance_data[:recent_requests] || []
11
+ end
12
+
13
+ def refresh
14
+ # Parse new log entries and refresh cache
15
+ parse_result = LoadLensService.parse_log_and_save
16
+ LoadLensService.refresh_data
17
+
18
+ if parse_result[:success]
19
+ redirect_to load_lens_performance_index_path, notice: "LoadLens data refreshed! Parsed #{parse_result[:processed]} new requests."
20
+ else
21
+ redirect_to load_lens_performance_index_path, alert: "Failed to parse logs: #{parse_result[:error]}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solidstats
4
+ class ProductivityController < ApplicationController
5
+ layout 'solidstats/dashboard'
6
+
7
+ def my_todos
8
+ @todos = MyTodoService.collect_todos
9
+ @summary = MyTodoService.get_summary
10
+
11
+ # Group todos by type for display
12
+ @todos_by_type = @todos.group_by { |todo| todo[:type] }
13
+
14
+ # Recent todos (first 10 for quick view)
15
+ @recent_todos = @todos.first(10)
16
+
17
+ # Stats for dashboard
18
+ @stats = {
19
+ total: @todos.length,
20
+ by_type: @todos_by_type.transform_values(&:count),
21
+ files_with_todos: @todos.map { |t| t[:file] }.uniq.length
22
+ }
23
+
24
+ respond_to do |format|
25
+ format.html
26
+ format.json { render json: { todos: @todos, summary: @summary, stats: @stats } }
27
+ end
28
+ end
29
+
30
+ def refresh_todos
31
+ @todos = MyTodoService.collect_todos(force_refresh: true)
32
+
33
+ respond_to do |format|
34
+ format.html { redirect_to my_todos_productivity_index_path, notice: 'TODOs refreshed successfully!' }
35
+ format.json { render json: { status: 'success', message: 'TODOs refreshed', count: @todos.length } }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solidstats
4
+ class QualityController < ApplicationController
5
+ layout 'solidstats/dashboard'
6
+
7
+ # Display Standard gem code quality analysis
8
+ def style_patrol
9
+ @analysis_data = Solidstats::StylePatrolService.collect_data
10
+ @summary = Solidstats::StylePatrolService.get_summary
11
+
12
+ # Group issues by file for better display
13
+ if @analysis_data[:issues].present?
14
+ @issues_by_file = @analysis_data[:issues].group_by { |issue| issue[:file] }
15
+ @issues_by_severity = @analysis_data[:issues].group_by { |issue| issue[:severity] }
16
+ else
17
+ @issues_by_file = {}
18
+ @issues_by_severity = {}
19
+ end
20
+
21
+ render 'style_patrol'
22
+ end
23
+
24
+ # Force refresh of style patrol data
25
+ def refresh_style_patrol
26
+ @analysis_data = Solidstats::StylePatrolService.refresh_cache
27
+ redirect_to quality_style_patrol_path, notice: 'Code quality analysis refreshed successfully.'
28
+ rescue => e
29
+ redirect_to quality_style_patrol_path, alert: "Error refreshing analysis: #{e.message}"
30
+ end
31
+
32
+ # Display SimpleCov code coverage analysis
33
+ def coverage_compass
34
+ # Get coverage data using the correct method
35
+ @analysis_data = Solidstats::CoverageCompassService.collect_data
36
+
37
+ # Handle different response types from service
38
+ if @analysis_data
39
+ if @analysis_data[:setup_required]
40
+ @setup_instructions = @analysis_data[:instructions]
41
+ @show_setup = true
42
+ @analysis_data[:status] = 'setup_required'
43
+ elsif @analysis_data[:stale_data]
44
+ @stale_warning = true
45
+ @data_age_hours = @analysis_data[:data_age_hours]
46
+ @suggestions = @analysis_data[:suggestions]
47
+ @analysis_data = @analysis_data[:last_coverage] || @analysis_data
48
+ @analysis_data[:status] = 'stale'
49
+ elsif @analysis_data[:error]
50
+ @error_message = @analysis_data[:error]
51
+ @analysis_data[:status] = 'error'
52
+ else
53
+ @analysis_data[:status] = 'success'
54
+ end
55
+
56
+ # Organize file coverage data for better display
57
+ organize_coverage_data
58
+ else
59
+ set_error_state
60
+ @analysis_data = { status: 'error' }
61
+ end
62
+
63
+ render 'coverage_compass'
64
+ end
65
+
66
+ # Force refresh of coverage compass data
67
+ def refresh_coverage_compass
68
+ Solidstats::CoverageCompassService.refresh_cache
69
+ redirect_to quality_coverage_compass_path, notice: 'Code coverage analysis refreshed successfully.'
70
+ rescue => e
71
+ redirect_to quality_coverage_compass_path, alert: "Error refreshing coverage analysis: #{e.message}"
72
+ end
73
+
74
+ private
75
+
76
+ def organize_coverage_data
77
+ file_coverage_data = @analysis_data[:file_coverage]
78
+ if file_coverage_data.present?
79
+ @file_coverage = file_coverage_data.map do |path, data|
80
+ {
81
+ file_path: path,
82
+ coverage_percent: data[:coverage_percentage] || 0,
83
+ total_lines: data[:total_lines] || 0,
84
+ covered_lines: data[:covered_lines] || 0,
85
+ missed_lines: data[:missed_lines] || []
86
+ }
87
+ end
88
+
89
+ # Group files by coverage ranges for better visualization
90
+ @coverage_ranges = {
91
+ excellent: @file_coverage.select { |f| f[:coverage_percent] >= 90 },
92
+ good: @file_coverage.select { |f| f[:coverage_percent] >= 70 && f[:coverage_percent] < 90 },
93
+ needs_improvement: @file_coverage.select { |f| f[:coverage_percent] >= 50 && f[:coverage_percent] < 70 },
94
+ poor: @file_coverage.select { |f| f[:coverage_percent] < 50 }
95
+ }
96
+ else
97
+ @file_coverage = []
98
+ @coverage_ranges = { excellent: [], good: [], needs_improvement: [], poor: [] }
99
+ end
100
+
101
+ # Set summary data
102
+ @summary = {
103
+ overall_coverage: @analysis_data[:overall_coverage] || 0,
104
+ coverage_percent: @analysis_data[:overall_coverage] || 0,
105
+ total_lines: @analysis_data[:total_lines] || 0,
106
+ covered_lines: @analysis_data[:covered_lines] || 0,
107
+ missed_lines: (@analysis_data[:total_lines] || 0) - (@analysis_data[:covered_lines] || 0),
108
+ total_files: @file_coverage.size,
109
+ health_score: calculate_health_score(@analysis_data[:overall_coverage] || 0),
110
+ coverage_grade: determine_coverage_grade(@analysis_data[:overall_coverage] || 0)
111
+ }
112
+ end
113
+
114
+ def set_error_state
115
+ @error_message = "Failed to retrieve coverage data"
116
+ @file_coverage = []
117
+ @coverage_ranges = { excellent: [], good: [], needs_improvement: [], poor: [] }
118
+ @summary = {
119
+ overall_coverage: 0,
120
+ coverage_percent: 0,
121
+ total_lines: 0,
122
+ covered_lines: 0,
123
+ missed_lines: 0,
124
+ total_files: 0,
125
+ health_score: 0,
126
+ coverage_grade: 'F'
127
+ }
128
+ end
129
+
130
+ def calculate_health_score(coverage_percent)
131
+ case coverage_percent
132
+ when 90..100 then 95
133
+ when 80..89 then 85
134
+ when 70..79 then 75
135
+ when 60..69 then 65
136
+ when 50..59 then 55
137
+ else 25
138
+ end
139
+ end
140
+
141
+ def determine_coverage_grade(coverage_percent)
142
+ case coverage_percent
143
+ when 90..100 then 'A+'
144
+ when 80..89 then 'A'
145
+ when 70..79 then 'B'
146
+ when 60..69 then 'C'
147
+ when 50..59 then 'D'
148
+ else 'F'
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solidstats
4
+ class SecuritiesController < ApplicationController
5
+ layout 'solidstats/dashboard'
6
+
7
+ # Display bundler audit security vulnerabilities
8
+ def bundler_audit
9
+ @vulnerabilities_data = Solidstats::BundlerAuditService.fetch_vulnerabilities
10
+ @vulnerabilities = @vulnerabilities_data.dig("output", "results") || []
11
+ @summary = Solidstats::BundlerAuditService.summary
12
+ @last_updated = @vulnerabilities_data.dig("output", "created_at")
13
+
14
+ # Group vulnerabilities by severity for better display
15
+ @vulnerabilities_by_severity = @vulnerabilities.group_by do |vuln|
16
+ vuln.dig("advisory", "criticality") || "unknown"
17
+ end
18
+
19
+ render 'bundler_audit'
20
+ end
21
+
22
+ # Force refresh of bundler audit data
23
+ def refresh_bundler_audit
24
+ @vulnerabilities_data = Solidstats::BundlerAuditService.scan_and_cache
25
+ redirect_to securities_bundler_audit_path, notice: 'Security vulnerabilities refreshed successfully.'
26
+ rescue => e
27
+ redirect_to securities_bundler_audit_path, alert: "Error refreshing vulnerabilities: #{e.message}"
28
+ end
29
+ end
30
+ end