solidstats 2.0.0 → 3.0.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -63
  3. data/README.md +27 -0
  4. data/Rakefile +3 -3
  5. data/app/assets/javascripts/solidstats/dashboard.js +0 -46
  6. data/app/assets/stylesheets/solidstats/dashboard.css +48 -0
  7. data/app/controllers/solidstats/dashboard_controller.rb +73 -61
  8. data/app/controllers/solidstats/logs_controller.rb +72 -0
  9. data/app/controllers/solidstats/performance_controller.rb +25 -0
  10. data/app/controllers/solidstats/productivity_controller.rb +39 -0
  11. data/app/controllers/solidstats/quality_controller.rb +152 -0
  12. data/app/controllers/solidstats/securities_controller.rb +30 -0
  13. data/app/helpers/solidstats/application_helper.rb +124 -11
  14. data/app/helpers/solidstats/performance_helper.rb +87 -0
  15. data/app/helpers/solidstats/productivity_helper.rb +38 -0
  16. data/app/services/solidstats/bundler_audit_service.rb +206 -0
  17. data/app/services/solidstats/coverage_compass_service.rb +335 -0
  18. data/app/services/solidstats/load_lens_service.rb +474 -0
  19. data/app/services/solidstats/log_size_monitor_service.rb +197 -66
  20. data/app/services/solidstats/my_todo_service.rb +242 -0
  21. data/app/services/solidstats/style_patrol_service.rb +319 -0
  22. data/app/views/layouts/solidstats/application.html.erb +8 -2
  23. data/app/views/layouts/solidstats/dashboard.html.erb +84 -0
  24. data/app/views/solidstats/dashboard/dashboard.html.erb +39 -0
  25. data/app/views/solidstats/logs/logs_size.html.erb +409 -0
  26. data/app/views/solidstats/performance/load_lens.html.erb +158 -0
  27. data/app/views/solidstats/productivity/_todo_list.html.erb +49 -0
  28. data/app/views/solidstats/productivity/my_todos.html.erb +84 -0
  29. data/app/views/solidstats/quality/coverage_compass.html.erb +420 -0
  30. data/app/views/solidstats/quality/style_patrol.html.erb +463 -0
  31. data/app/views/solidstats/securities/bundler_audit.html.erb +345 -0
  32. data/app/views/solidstats/shared/_dashboard_card.html.erb +160 -0
  33. data/app/views/solidstats/shared/_quick_actions.html.erb +26 -0
  34. data/config/routes.rb +31 -6
  35. data/lib/generators/solidstats/clean/clean_generator.rb +24 -0
  36. data/lib/generators/solidstats/clean/templates/README +8 -0
  37. data/lib/generators/solidstats/install/install_generator.rb +51 -10
  38. data/lib/generators/solidstats/install/templates/README +7 -0
  39. data/lib/solidstats/engine.rb +6 -71
  40. data/lib/solidstats/version.rb +1 -1
  41. data/lib/solidstats.rb +19 -303
  42. data/lib/tasks/solidstats.rake +67 -0
  43. data/lib/tasks/solidstats_performance.rake +61 -0
  44. data/lib/tasks/solidstats_tasks.rake +16 -4
  45. metadata +33 -95
  46. data/app/assets/javascripts/solidstats/gem_metadata.js +0 -554
  47. data/app/assets/stylesheets/solidstats/components/action_button.css +0 -99
  48. data/app/assets/stylesheets/solidstats/components/dashboard.css +0 -151
  49. data/app/assets/stylesheets/solidstats/components/dashboard_header.css +0 -93
  50. data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +0 -97
  51. data/app/assets/stylesheets/solidstats/components/gem_metadata.css +0 -1403
  52. data/app/assets/stylesheets/solidstats/components/navigation.css +0 -80
  53. data/app/assets/stylesheets/solidstats/components/quick_navigation.css +0 -54
  54. data/app/assets/stylesheets/solidstats/components/security.css +0 -332
  55. data/app/assets/stylesheets/solidstats/components/status_badge.css +0 -58
  56. data/app/assets/stylesheets/solidstats/components/summary_card.css +0 -66
  57. data/app/assets/stylesheets/solidstats/components/tab_navigation.css +0 -95
  58. data/app/components/solidstats/base_component.rb +0 -88
  59. data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
  60. data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
  61. data/app/components/solidstats/code_quality/section_component.html.erb +0 -45
  62. data/app/components/solidstats/code_quality/section_component.rb +0 -34
  63. data/app/components/solidstats/dashboard_header_component.html.erb +0 -39
  64. data/app/components/solidstats/dashboard_header_component.rb +0 -33
  65. data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +0 -6
  66. data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +0 -6
  67. data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +0 -6
  68. data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +0 -6
  69. data/app/components/solidstats/previews/action_button_component_preview.rb +0 -64
  70. data/app/components/solidstats/previews/navigation_component_preview.rb +0 -74
  71. data/app/components/solidstats/previews/stats_overview_component_preview.rb +0 -100
  72. data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +0 -6
  73. data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +0 -6
  74. data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +0 -6
  75. data/app/components/solidstats/previews/status_badge_component_preview.rb +0 -49
  76. data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +0 -9
  77. data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +0 -9
  78. data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +0 -6
  79. data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +0 -6
  80. data/app/components/solidstats/previews/summary_card_component_preview.rb +0 -67
  81. data/app/components/solidstats/quick_navigation_component.html.erb +0 -8
  82. data/app/components/solidstats/quick_navigation_component.rb +0 -21
  83. data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +0 -44
  84. data/app/components/solidstats/security/gem_impact_analysis_component.rb +0 -45
  85. data/app/components/solidstats/security/overview_component.html.erb +0 -21
  86. data/app/components/solidstats/security/overview_component.rb +0 -104
  87. data/app/components/solidstats/security/section_component.html.erb +0 -26
  88. data/app/components/solidstats/security/section_component.rb +0 -52
  89. data/app/components/solidstats/security/timeline_component.html.erb +0 -39
  90. data/app/components/solidstats/security/timeline_component.rb +0 -43
  91. data/app/components/solidstats/tasks_section_component.html.erb +0 -17
  92. data/app/components/solidstats/tasks_section_component.rb +0 -22
  93. data/app/components/solidstats/ui/action_button_component.html.erb +0 -6
  94. data/app/components/solidstats/ui/action_button_component.rb +0 -71
  95. data/app/components/solidstats/ui/dashboard_layout_component.html.erb +0 -19
  96. data/app/components/solidstats/ui/dashboard_layout_component.rb +0 -85
  97. data/app/components/solidstats/ui/navigation_component.html.erb +0 -34
  98. data/app/components/solidstats/ui/navigation_component.rb +0 -72
  99. data/app/components/solidstats/ui/stats_overview_component.html.erb +0 -14
  100. data/app/components/solidstats/ui/stats_overview_component.rb +0 -78
  101. data/app/components/solidstats/ui/status_badge_component.html.erb +0 -6
  102. data/app/components/solidstats/ui/status_badge_component.rb +0 -42
  103. data/app/components/solidstats/ui/summary_card_component.html.erb +0 -12
  104. data/app/components/solidstats/ui/summary_card_component.rb +0 -63
  105. data/app/components/solidstats/ui/tab_navigation_component.html.erb +0 -22
  106. data/app/components/solidstats/ui/tab_navigation_component.rb +0 -79
  107. data/app/services/solidstats/audit_service.rb +0 -56
  108. data/app/services/solidstats/data_collector_service.rb +0 -83
  109. data/app/services/solidstats/gem_metadata/fetcher_service.rb +0 -136
  110. data/app/services/solidstats/todo_service.rb +0 -114
  111. data/app/views/solidstats/dashboard/_log_monitor.html.erb +0 -759
  112. data/app/views/solidstats/dashboard/_todos.html.erb +0 -151
  113. data/app/views/solidstats/dashboard/audit/_additional_styles.css +0 -22
  114. data/app/views/solidstats/dashboard/audit/_audit_badge.html.erb +0 -5
  115. data/app/views/solidstats/dashboard/audit/_audit_details.html.erb +0 -495
  116. data/app/views/solidstats/dashboard/audit/_audit_summary.html.erb +0 -26
  117. data/app/views/solidstats/dashboard/audit/_no_vulnerabilities.html.erb +0 -3
  118. data/app/views/solidstats/dashboard/audit/_security_audit.html.erb +0 -14
  119. data/app/views/solidstats/dashboard/audit/_vulnerabilities_table.html.erb +0 -1120
  120. data/app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb +0 -63
  121. data/app/views/solidstats/dashboard/index.html.erb +0 -81
  122. data/app/views/solidstats/gem_metadata/_panel.html.erb +0 -419
  123. data/lib/generators/solidstats/feature/feature_generator.rb +0 -170
  124. data/lib/generators/solidstats/feature/templates/component.html.erb +0 -84
  125. data/lib/generators/solidstats/feature/templates/component.rb.erb +0 -103
  126. data/lib/generators/solidstats/feature/templates/component.scss +0 -243
  127. data/lib/generators/solidstats/feature/templates/component_test.rb.erb +0 -183
  128. data/lib/generators/solidstats/feature/templates/controller.rb.erb +0 -44
  129. data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +0 -111
  130. data/lib/generators/solidstats/feature/templates/detail_view.html.erb +0 -755
  131. data/lib/generators/solidstats/feature/templates/preview.rb.erb +0 -107
  132. data/lib/generators/solidstats/feature/templates/service.rb.erb +0 -132
  133. data/lib/generators/solidstats/feature/templates/service_test.rb.erb +0 -109
  134. data/lib/generators/solidstats/install_generator.rb +0 -109
  135. data/lib/tasks/solidstats_install.rake +0 -133
@@ -0,0 +1,335 @@
1
+ require "json"
2
+ require "fileutils"
3
+
4
+ module Solidstats
5
+ class CoverageCompassService
6
+ class << self
7
+ def collect_data(force_refresh: false)
8
+ # Check if cached data exists and is fresh
9
+ if !force_refresh && cached_data_exists? && cache_fresh?
10
+ Rails.logger.info "CoverageCompass: Using cached data"
11
+ return load_cached_data
12
+ end
13
+
14
+ Rails.logger.info "CoverageCompass: Collecting fresh coverage data"
15
+
16
+ # Collect fresh coverage data
17
+ coverage_data = collect_coverage_data
18
+
19
+ # Cache the data if it's valid coverage information
20
+ if coverage_data && !coverage_data[:setup_required] && !coverage_data[:error]
21
+ cache_coverage_data(coverage_data)
22
+ Rails.logger.info "CoverageCompass: Data cached successfully"
23
+ end
24
+
25
+ # Always update summary file
26
+ update_summary_file(coverage_data) if coverage_data
27
+
28
+ coverage_data
29
+ rescue => e
30
+ Rails.logger.error "Coverage collection failed: #{e.message}"
31
+ { error: e.message, collected_at: Time.current, analyzed_at: Time.current }
32
+ end
33
+
34
+ def refresh_cache
35
+ File.delete(cache_file_path) if File.exist?(cache_file_path)
36
+ collect_data(force_refresh: true)
37
+ end
38
+
39
+ private
40
+
41
+ def get_resultset_file_path
42
+ # Only check in the coverage directory
43
+ coverage_file = File.join(Rails.root, "coverage", ".resultset.json")
44
+
45
+ Rails.logger.info "CoverageCompass: Looking for coverage file at: #{coverage_file}"
46
+
47
+ if File.exist?(coverage_file)
48
+ Rails.logger.info "CoverageCompass: Coverage file found - Size: #{File.size(coverage_file)} bytes"
49
+ coverage_file
50
+ else
51
+ Rails.logger.warn "CoverageCompass: Coverage file not found"
52
+ nil
53
+ end
54
+ end
55
+
56
+ def cached_data_exists?
57
+ File.exist?(cache_file_path)
58
+ end
59
+
60
+ def cache_fresh?
61
+ return false unless cached_data_exists?
62
+
63
+ resultset_path = get_resultset_file_path
64
+ return false unless resultset_path
65
+
66
+ cache_time = File.mtime(cache_file_path)
67
+ resultset_time = File.mtime(resultset_path)
68
+
69
+ # Cache is fresh if it's newer than the resultset file
70
+ cache_time >= resultset_time
71
+ end
72
+
73
+ def load_cached_data
74
+ cached_data = JSON.parse(File.read(cache_file_path)).deep_symbolize_keys
75
+
76
+ # Convert string timestamps back to Time objects
77
+ [ :collected_at, :analyzed_at, :data_timestamp ].each do |time_field|
78
+ if cached_data[time_field].is_a?(String)
79
+ cached_data[time_field] = Time.parse(cached_data[time_field])
80
+ end
81
+ end
82
+
83
+ cached_data
84
+ rescue => e
85
+ Rails.logger.error "Failed to load cached coverage data: #{e.message}"
86
+ nil
87
+ end
88
+
89
+ def collect_coverage_data
90
+ resultset_path = get_resultset_file_path
91
+
92
+ unless resultset_path
93
+ return setup_instructions_data
94
+ end
95
+
96
+ # Check if coverage data is stale (older than 24 hours)
97
+ if File.mtime(resultset_path) < 24.hours.ago
98
+ return stale_coverage_data(resultset_path)
99
+ end
100
+
101
+ parse_resultset_file(resultset_path)
102
+ end
103
+
104
+ def parse_resultset_file(file_path)
105
+ Rails.logger.info "CoverageCompass: Parsing file #{file_path}"
106
+ resultset_data = JSON.parse(File.read(file_path)).deep_symbolize_keys
107
+
108
+ Rails.logger.info "CoverageCompass: Found test suites: #{resultset_data.keys}"
109
+
110
+ # Extract coverage data from SimpleCov resultset format
111
+ coverage_results = {}
112
+ total_lines = 0
113
+ covered_lines = 0
114
+
115
+ resultset_data.each do |test_suite, suite_data|
116
+ Rails.logger.info "CoverageCompass: Processing suite '#{test_suite}'"
117
+
118
+ unless suite_data[:coverage]
119
+ Rails.logger.warn "CoverageCompass: No coverage data in suite '#{test_suite}'"
120
+ next
121
+ end
122
+
123
+ Rails.logger.info "CoverageCompass: Found #{suite_data[:coverage].keys.size} files in '#{test_suite}'"
124
+
125
+ suite_data[:coverage].each do |file_path, file_coverage|
126
+ # Convert file_path to string in case it's a symbol
127
+ file_path_str = file_path.to_s
128
+ next if file_path_str.include?("vendor/") || file_path_str.include?("spec/") || file_path_str.include?("test/")
129
+
130
+ # Handle different SimpleCov formats - newer versions use "lines" key
131
+ line_coverage = file_coverage.is_a?(Hash) ? file_coverage[:lines] : file_coverage
132
+
133
+ unless line_coverage.is_a?(Array)
134
+ Rails.logger.warn "CoverageCompass: Invalid line coverage format for #{file_path_str}"
135
+ next
136
+ end
137
+
138
+ relative_path = file_path_str.sub("#{Rails.root}/", "")
139
+ line_count = line_coverage.compact.size
140
+ covered_count = line_coverage.compact.count { |hits| hits > 0 }
141
+
142
+ # Find uncovered lines (lines that are trackable but have 0 hits)
143
+ missed_lines = []
144
+ line_coverage.each_with_index do |hits, index|
145
+ next if hits.nil? # Skip non-trackable lines
146
+ missed_lines << (index + 1) if hits == 0 # Line numbers are 1-based
147
+ end
148
+
149
+ coverage_results[relative_path] = {
150
+ total_lines: line_count,
151
+ covered_lines: covered_count,
152
+ missed_lines: missed_lines,
153
+ coverage_percentage: line_count > 0 ? (covered_count.to_f / line_count * 100).round(2) : 0
154
+ }
155
+
156
+ total_lines += line_count
157
+ covered_lines += covered_count
158
+ end
159
+ end
160
+
161
+ overall_percentage = total_lines > 0 ? (covered_lines.to_f / total_lines * 100).round(2) : 0
162
+
163
+ Rails.logger.info "CoverageCompass: Processed #{coverage_results.size} files, #{overall_percentage}% coverage"
164
+
165
+ {
166
+ overall_coverage: overall_percentage,
167
+ file_coverage: coverage_results,
168
+ total_lines: total_lines,
169
+ covered_lines: covered_lines,
170
+ collected_at: Time.current,
171
+ analyzed_at: Time.current,
172
+ data_timestamp: File.mtime(file_path)
173
+ }
174
+ rescue => e
175
+ Rails.logger.error "Failed to parse coverage resultset: #{e.message}"
176
+ Rails.logger.error e.backtrace.join("\n")
177
+ { error: "Failed to parse coverage data: #{e.message}", collected_at: Time.current, analyzed_at: Time.current }
178
+ end
179
+
180
+ def setup_instructions_data
181
+ {
182
+ setup_required: true,
183
+ message: "No coverage data found. Please set up code coverage in your project.",
184
+ instructions: {
185
+ rspec: {
186
+ title: "For RSpec projects:",
187
+ steps: [
188
+ "Add 'simplecov' to your Gemfile",
189
+ "Add the following to spec/spec_helper.rb:",
190
+ "require 'simplecov'\nSimpleCov.start 'rails'",
191
+ "Run your test suite: bundle exec rspec"
192
+ ]
193
+ },
194
+ minitest: {
195
+ title: "For Minitest projects:",
196
+ steps: [
197
+ "Add 'simplecov' to your Gemfile",
198
+ "Add the following to test/test_helper.rb:",
199
+ "require 'simplecov'\nSimpleCov.start 'rails'",
200
+ "Run your test suite: bundle exec rails test"
201
+ ]
202
+ },
203
+ custom: {
204
+ title: "For other test frameworks:",
205
+ steps: [
206
+ "Install SimpleCov gem",
207
+ "Configure SimpleCov at the beginning of your test helper",
208
+ "Run your test suite to generate coverage data"
209
+ ]
210
+ }
211
+ },
212
+ collected_at: Time.current,
213
+ analyzed_at: Time.current
214
+ }
215
+ end
216
+
217
+ def stale_coverage_data(resultset_path)
218
+ data_age = Time.current - File.mtime(resultset_path)
219
+ hours_old = (data_age / 1.hour).round(1)
220
+
221
+ # Get the last coverage data even if it's stale
222
+ last_coverage_data = parse_resultset_file(resultset_path)
223
+
224
+ {
225
+ stale_data: true,
226
+ message: "Coverage data is #{hours_old} hours old. Consider running tests to get fresh data.",
227
+ suggestions: [
228
+ "Run your test suite: bundle exec rspec (or rails test)",
229
+ "Check if your CI/CD pipeline is running tests",
230
+ "Verify SimpleCov is properly configured"
231
+ ],
232
+ last_coverage: last_coverage_data,
233
+ data_age_hours: hours_old,
234
+ collected_at: Time.current,
235
+ analyzed_at: Time.current
236
+ }
237
+ end
238
+
239
+ def cache_coverage_data(data)
240
+ ensure_solidstats_directory
241
+ File.write(cache_file_path, JSON.pretty_generate(data))
242
+ Rails.logger.info "Coverage data cached to #{cache_file_path}"
243
+ rescue => e
244
+ Rails.logger.error "Failed to cache coverage data: #{e.message}"
245
+ end
246
+
247
+ def update_summary_file(coverage_data)
248
+ summary_file_path = Rails.root.join("solidstats", "summary.json")
249
+
250
+ # Ensure directory exists
251
+ FileUtils.mkdir_p(File.dirname(summary_file_path))
252
+
253
+ # Read existing summary or create new one
254
+ begin
255
+ existing_summary = File.exist?(summary_file_path) ? JSON.parse(File.read(summary_file_path)) : {}
256
+ rescue JSON::ParserError
257
+ existing_summary = {}
258
+ end
259
+
260
+ # Calculate coverage metrics
261
+ overall_coverage = coverage_data[:overall_coverage] || 0
262
+ total_files = coverage_data[:file_coverage]&.size || 0
263
+ status = determine_status(coverage_data)
264
+
265
+ # Create badges based on coverage metrics
266
+ badges = []
267
+ badges << { "text" => "#{overall_coverage.round(1)}% Coverage", "color" => coverage_badge_color(overall_coverage) }
268
+ badges << { "text" => "#{total_files} Files", "color" => "info" }
269
+
270
+ # Update the Coverage Compass entry
271
+ existing_summary["Coverage Compass"] = {
272
+ "icon" => "target",
273
+ "status" => status,
274
+ "value" => generate_coverage_message(coverage_data),
275
+ "last_updated" => Time.current.iso8601,
276
+ "url" => "/solidstats/quality/coverage_compass",
277
+ "badges" => badges
278
+ }
279
+
280
+ # Write updated summary
281
+ File.write(summary_file_path, JSON.pretty_generate(existing_summary))
282
+ Rails.logger.info("Updated summary.json with Coverage Compass data")
283
+ rescue => e
284
+ Rails.logger.error("Failed to update summary.json: #{e.message}")
285
+ end
286
+
287
+ def determine_status(coverage_data)
288
+ return "danger" if coverage_data[:setup_required] || coverage_data[:error]
289
+ return "warning" if coverage_data[:stale_data]
290
+
291
+ coverage = coverage_data[:overall_coverage] || 0
292
+ case coverage
293
+ when 0...50 then "danger"
294
+ when 50...80 then "warning"
295
+ when 80...95 then "info"
296
+ else "success"
297
+ end
298
+ end
299
+
300
+ def generate_coverage_message(coverage_data)
301
+ return "Setup required" if coverage_data[:setup_required]
302
+ return "Analysis error" if coverage_data[:error]
303
+ return "Stale data" if coverage_data[:stale_data]
304
+
305
+ coverage = coverage_data[:overall_coverage] || 0
306
+ "#{coverage.round(1)}% coverage"
307
+ end
308
+
309
+ def coverage_badge_color(coverage)
310
+ case coverage
311
+ when 0...50 then "error"
312
+ when 50...80 then "warning"
313
+ when 80...95 then "info"
314
+ else "success"
315
+ end
316
+ end
317
+
318
+ def cache_file_path
319
+ File.join(solidstats_directory, "coverage_cache.json")
320
+ end
321
+
322
+ def summary_file_path
323
+ File.join(solidstats_directory, "summary.json")
324
+ end
325
+
326
+ def solidstats_directory
327
+ File.join(Rails.root, "solidstats")
328
+ end
329
+
330
+ def ensure_solidstats_directory
331
+ FileUtils.mkdir_p(solidstats_directory) unless File.directory?(solidstats_directory)
332
+ end
333
+ end
334
+ end
335
+ end