solidstats 0.0.2 → 0.0.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 362ed85e9b95dda956d51647061a65b632eba0d997ee5183d7970e343e3780ce
4
- data.tar.gz: 9d2c4b5c9ba1bf77a79504c093864eda80fe3f9f0da0657982d6c075b8349521
3
+ metadata.gz: f752538540fc6711f9cb874ee24b1a5fd64986c7fc306b6019994567993a2213
4
+ data.tar.gz: db591225a673fd6d863b31402d2e212b6669f93ff90e7daea9016dd9f2729f1d
5
5
  SHA512:
6
- metadata.gz: '0128ae70285f226a7914c33b70d7294040238a4ffa36daab1dcf76dd3eacb9d99b68120e4ec99ada8f3710f28219ba76c762231cf5738b347a7ace6b3a5788f3'
7
- data.tar.gz: 57175f6bb38a4ea43b65a2769a40ad798ba09139471bfcef44676f425ff1b58ce632ed0025b20efc10b60a7f54537b66ec5b323d592bec2076234a8040fd0361
6
+ metadata.gz: 324af0252248b56a47b09ac4211c33c8547d28dec98d9458077cccf736742c31ff09b5bff70fcb2d1754e7ff5a0e6c00c12a832e9406031af7fc0f5665557f8d
7
+ data.tar.gz: b49c025af721bb4f2bd6ddc15857f6f6a46827e223bb8b23d1f6a9641e5ba7848a75b6778854636f2008f2c38f0d7bb94c1adbc6248a7cb0117a81b6d9134708
data/CHANGELOG.md ADDED
@@ -0,0 +1,35 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.0.3] - 2025-05-20
9
+
10
+ ### Added
11
+ - Install generator (`rails g solidstats:install`) to automatically mount routes
12
+ - Rake task (`rake solidstats:install`) as an alias for the generator
13
+ - Improved documentation for installation process
14
+
15
+ ## [0.0.2] - 2025-05-19
16
+
17
+ ### Added
18
+ - Asset precompilation configuration for solidstats/application.css in development environment
19
+
20
+ ## [0.0.1] - 2025-05-19
21
+
22
+ ### Added
23
+ - Initial release of the Solidstats gem
24
+ - Basic engine structure
25
+ - Development mode restriction
26
+ - Dashboard controller with initial views
27
+ - Security audit components and views
28
+
29
+ ## [Unreleased]
30
+
31
+ - Improved TODO dashboard partial:
32
+ - Ensured correct handling of nil keys in the hotspots hash.
33
+ - Displayed TODO, FIXME, and HACK counts with color-coded badges.
34
+ - Added expandable details for files with most TODOs and all TODO items.
35
+ - Enhanced UI with better grouping and error handling for TODO data.
data/README.md CHANGED
@@ -12,13 +12,26 @@ Solidstats is a local-only Rails engine that shows your project's health at `/so
12
12
  ```ruby
13
13
  # Gemfile (dev only)
14
14
  group :development do
15
- gem 'solidstats', path: '../solidstats'
15
+ gem 'solidstats'
16
16
  end
17
17
  ```
18
18
 
19
- Then mount in `config/routes.rb`:
19
+ After bundle install, you can run the installer:
20
+
21
+ ```bash
22
+ bundle exec rails g solidstats:install
23
+ ```
24
+
25
+ Or using the provided rake task:
26
+
27
+ ```bash
28
+ bundle exec rake solidstats:install
29
+ ```
30
+
31
+ The installer will automatically mount the engine in your routes:
20
32
 
21
33
  ```ruby
34
+ # config/routes.rb
22
35
  mount Solidstats::Engine => '/solidstats' if Rails.env.development?
23
36
  ```
24
37
 
@@ -1,12 +1,14 @@
1
1
  module Solidstats
2
2
  class DashboardController < ApplicationController
3
3
  AUDIT_CACHE_FILE = Rails.root.join("tmp", "solidstats_audit.json")
4
+ TODO_CACHE_FILE = Rails.root.join("tmp", "solidstats_todos.json")
4
5
  AUDIT_CACHE_HOURS = 12 # Configure how many hours before refreshing
5
6
 
6
7
  def index
7
8
  @audit_output = fetch_audit_output
8
9
  @rubocop_output = "JSON.parse(`rubocop --format json`)"
9
- @todo_count = `grep -r -E \"TODO|FIXME|HACK\" app lib | wc -l`.to_i
10
+ @todo_items = fetch_todo_items
11
+ @todo_stats = calculate_todo_stats(@todo_items)
10
12
  @coverage = "read_coverage_percent"
11
13
  end
12
14
 
@@ -44,6 +46,87 @@ module Solidstats
44
46
  audit_output
45
47
  end
46
48
 
49
+ def fetch_todo_items
50
+ # Check if cache file exists and is recent enough
51
+ if File.exist?(TODO_CACHE_FILE)
52
+ cached_data = JSON.parse(File.read(TODO_CACHE_FILE))
53
+ last_run_time = Time.parse(cached_data["timestamp"])
54
+
55
+ # Use cached data if it's less than AUDIT_CACHE_HOURS old
56
+ if Time.now - last_run_time < AUDIT_CACHE_HOURS.hours
57
+ return cached_data["output"]
58
+ end
59
+ end
60
+
61
+ todos = []
62
+ # Updated grep pattern to match only all-uppercase or all-lowercase variants
63
+ raw_output = `grep -r -n -E "(TODO|FIXME|HACK|todo|fixme|hack)" app lib`.split("\n")
64
+
65
+ raw_output.each do |line|
66
+ if line =~ /^(.+):(\d+):(.+)$/
67
+ file = $1
68
+ line_num = $2
69
+ content = $3
70
+
71
+ # Match only exact lowercase or uppercase variants
72
+ type_match = content.match(/(TODO|FIXME|HACK|todo|fixme|hack)/)
73
+ if type_match
74
+ # Convert to uppercase for consistency
75
+ type = type_match.to_s.upcase
76
+
77
+ todos << {
78
+ file: file,
79
+ line: line_num.to_i,
80
+ content: content.strip,
81
+ type: type
82
+ }
83
+ end
84
+ end
85
+ end
86
+
87
+ # Save to cache file with timestamp
88
+ cache_data = {
89
+ "output" => todos,
90
+ "timestamp" => Time.now.iso8601
91
+ }
92
+
93
+ # Ensure the tmp directory exists
94
+ FileUtils.mkdir_p(File.dirname(TODO_CACHE_FILE))
95
+
96
+ # Write the cache file
97
+ File.write(TODO_CACHE_FILE, JSON.generate(cache_data))
98
+
99
+ todos
100
+ end
101
+
102
+ def calculate_todo_stats(todos)
103
+ return {} if todos.nil? || todos.empty?
104
+
105
+ stats = {
106
+ total: todos.count,
107
+ by_type: {
108
+ "TODO" => todos.count { |t| t[:type] == "TODO" },
109
+ "FIXME" => todos.count { |t| t[:type] == "FIXME" },
110
+ "HACK" => todos.count { |t| t[:type] == "HACK" }
111
+ },
112
+ by_file: {}
113
+ }
114
+
115
+ # Group by file path
116
+ todos.each do |todo|
117
+ file_path = todo[:file]
118
+ stats[:by_file][file_path] ||= 0
119
+ stats[:by_file][file_path] += 1
120
+ end
121
+
122
+ # Find files with most TODOs (top 5)
123
+ stats[:hotspots] = stats[:by_file].sort_by { |_, count| -count }
124
+ .first(5)
125
+ .to_h
126
+
127
+ stats
128
+ end
129
+
47
130
  def read_coverage_percent
48
131
  file = Rails.root.join("coverage", ".last_run.json")
49
132
  return 0 unless File.exist?(file)
@@ -0,0 +1,172 @@
1
+ <div class="stat-card todo-card">
2
+ <h2><span class="icon">📝</span> TODO Items</h2>
3
+ <div class="card-content">
4
+ <% if @todo_items.nil? %>
5
+ <div class="status-badge danger">Error loading TODO data</div>
6
+ <% elsif @todo_items.empty? %>
7
+ <div class="status-badge success">All Clear</div>
8
+ <% else %>
9
+ <div class="status-badge warning"><%= @todo_items.count %> <%= "Item".pluralize(@todo_items.count) %> Found</div>
10
+ <% end %>
11
+
12
+ <% if @todo_items.present? && @todo_stats.present? %>
13
+ <%
14
+ # Ensure we have proper counts by type - recalculate if needed
15
+ todo_count = @todo_items.count { |item| (item[:type] || item["type"])&.upcase == "TODO" }
16
+ fixme_count = @todo_items.count { |item| (item[:type] || item["type"])&.upcase == "FIXME" }
17
+ hack_count = @todo_items.count { |item| (item[:type] || item["type"])&.upcase == "HACK" }
18
+ %>
19
+ <div class="metrics-group">
20
+ <div class="metric">
21
+ <span class="metric-label">TODO:</span>
22
+ <span class="metric-value"><%= todo_count %></span>
23
+ </div>
24
+ <div class="metric">
25
+ <span class="metric-label">FIXME:</span>
26
+ <span class="metric-value text-danger"><%= fixme_count %></span>
27
+ </div>
28
+ <div class="metric">
29
+ <span class="metric-label">HACK:</span>
30
+ <span class="metric-value text-warning"><%= hack_count %></span>
31
+ </div>
32
+ </div>
33
+
34
+ <a href="#" class="toggle-details" data-target="todo-details">Show Details</a>
35
+ <% end %>
36
+ </div>
37
+
38
+ <% if @todo_items.present? && @todo_stats.present? %>
39
+ <div id="todo-details" class="details-panel hidden">
40
+ <h3 class="details-section-title">Files with Most TODOs</h3>
41
+ <div class="table-responsive mb-4">
42
+ <table class="table">
43
+ <thead>
44
+ <tr>
45
+ <th>File</th>
46
+ <th class="text-end">Count</th>
47
+ </tr>
48
+ </thead>
49
+ <tbody>
50
+ <%
51
+ # If hotspots has nil keys, reconstruct it from @todo_items
52
+ hotspots = @todo_stats[:hotspots]
53
+ if hotspots.is_a?(Hash) && hotspots.keys.any?(&:nil?)
54
+ # Rebuild hotspots from todo_items
55
+ hotspots = {}
56
+ @todo_items.each do |item|
57
+ item_hash = item.respond_to?(:symbolize_keys) ? item.symbolize_keys : item
58
+ file = item_hash[:file] || item["file"]
59
+ next unless file.present?
60
+ hotspots[file] ||= 0
61
+ hotspots[file] += 1
62
+ end
63
+ end
64
+ %>
65
+
66
+ <% if hotspots.present? %>
67
+ <% hotspots.each do |file, count| %>
68
+ <tr>
69
+ <td><code><%= file.nil? ? "Unknown File" : file.to_s %></code></td>
70
+ <td class="text-end"><%= count.to_i %></td>
71
+ </tr>
72
+ <% end %>
73
+ <% else %>
74
+ <tr>
75
+ <td colspan="2">No hotspot data available</td>
76
+ </tr>
77
+ <% end %>
78
+ </tbody>
79
+ </table>
80
+ </div>
81
+
82
+ <h3 class="details-section-title">All TODO Items</h3>
83
+ <div class="accordion" id="todoAccordion">
84
+ <% @todo_items.each_with_index do |item, index| %>
85
+ <%
86
+ # Convert item to a hash with symbol keys if it's a hash with string keys
87
+ item = item.symbolize_keys if item.respond_to?(:symbolize_keys)
88
+ %>
89
+ <div class="accordion-item">
90
+ <h4 class="accordion-header">
91
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#todo-<%= index %>">
92
+ <span class="badge <%= item[:type] == 'FIXME' ? 'bg-danger' : (item[:type] == 'HACK' ? 'bg-warning' : 'bg-primary') %> me-2"><%= item[:type] || "TODO" %></span>
93
+ <%= File.basename(item[:file].to_s) %>:<%= item[:line] || '?' %>
94
+ </button>
95
+ </h4>
96
+ <div id="todo-<%= index %>" class="accordion-collapse collapse" data-bs-parent="#todoAccordion">
97
+ <div class="accordion-body">
98
+ <p><code class="d-block"><%= item[:content].to_s %></code></p>
99
+ <small class="text-muted">File: <%= item[:file].to_s %></small>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ <% end %>
104
+ </div>
105
+ </div>
106
+ <% end %>
107
+ </div>
108
+
109
+ <style>
110
+ /* Additional styles for todo card */
111
+ .todo-card.expanded {
112
+ grid-column: 1 / -1;
113
+ width: 100%;
114
+ }
115
+
116
+ .details-section-title {
117
+ font-size: 1.1rem;
118
+ font-weight: 600;
119
+ margin: 1.5rem 0 1rem 0;
120
+ }
121
+
122
+ .details-section-title:first-child {
123
+ margin-top: 0;
124
+ }
125
+
126
+ /* Fix for accordion within the card */
127
+ .todo-card .accordion {
128
+ margin-top: 1rem;
129
+ }
130
+
131
+ .todo-card .accordion-item {
132
+ border: 1px solid rgba(0, 0, 0, 0.125);
133
+ margin-bottom: 0.5rem;
134
+ border-radius: 4px;
135
+ overflow: hidden;
136
+ }
137
+
138
+ .todo-card .accordion-button {
139
+ padding: 0.75rem 1rem;
140
+ font-size: 0.9rem;
141
+ background-color: #f8f9fa;
142
+ }
143
+
144
+ .todo-card .accordion-button:not(.collapsed) {
145
+ background-color: #e7f1ff;
146
+ }
147
+
148
+ .todo-card .accordion-body {
149
+ padding: 1rem;
150
+ background-color: #fff;
151
+ }
152
+
153
+ .todo-card code {
154
+ font-family: SFMono-Regular, Menlo, Monaco, Consolas, monospace;
155
+ font-size: 0.85rem;
156
+ padding: 0.2rem 0.4rem;
157
+ background: #f6f8fa;
158
+ border-radius: 3px;
159
+ color: #24292e;
160
+ }
161
+
162
+ .todo-card code.d-block {
163
+ display: block;
164
+ padding: 0.75rem;
165
+ margin-bottom: 0.5rem;
166
+ white-space: pre-wrap;
167
+ word-break: break-word;
168
+ background: #f6f8fa;
169
+ border-radius: 4px;
170
+ border: 1px solid #e1e4e8;
171
+ }
172
+ </style>
@@ -7,6 +7,8 @@
7
7
  <div class="stats-cards">
8
8
  <%= render 'solidstats/dashboard/audit/security_audit' %>
9
9
 
10
+ <%= render partial: 'todos' %>
11
+
10
12
  <div class="stat-card">
11
13
  <h2><span class="icon">🧹</span> Code Quality</h2>
12
14
  <div class="card-content">
@@ -0,0 +1,32 @@
1
+ require "rails/generators/base"
2
+
3
+ module Solidstats
4
+ module Generators
5
+ # This generator installs Solidstats routes in the host application
6
+ #
7
+ # @example
8
+ # $ rails generate solidstats:install
9
+ #
10
+ # This will:
11
+ # 1. Add the Solidstats routes to the host application's config/routes.rb
12
+ # 2. Show a helpful README with next steps
13
+ class InstallGenerator < Rails::Generators::Base
14
+ source_root File.expand_path("templates", __dir__)
15
+ desc "Adds Solidstats routes to your development application"
16
+
17
+ def add_routes
18
+ route_code = <<-RUBY
19
+ # Solidstats Routes (development only)
20
+ mount Solidstats::Engine => "/solidstats" if Rails.env.development?
21
+ RUBY
22
+
23
+ route route_code
24
+ say_status :routes, "Mounting Solidstats engine at /solidstats (development environment only)", :green
25
+ end
26
+
27
+ def show_readme
28
+ readme "README" if behavior == :invoke
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ ===============================================================================
2
+
3
+ Solidstats has been successfully installed for development!
4
+
5
+ Your Solidstats dashboard is now available at: /solidstats
6
+ (Only in development environment)
7
+
8
+ To access your stats:
9
+ 1. Start your Rails server: bundle exec rails s
10
+ 2. Visit: http://localhost:3000/solidstats
11
+
12
+ Solidstats will show you:
13
+ - TODO/FIXME items in your codebase
14
+ - Security audit information
15
+ - Code quality metrics
16
+ - And more!
17
+
18
+ ===============================================================================
@@ -10,8 +10,13 @@ module Solidstats
10
10
 
11
11
  initializer "solidstats.assets.precompile" do |app|
12
12
  if Rails.env.development?
13
- app.config.assets.precompile += %w( solidstats/application.css )
13
+ app.config.assets.precompile += %w[ solidstats/application.css ]
14
14
  end
15
15
  end
16
+
17
+ # Load the solidstats rake tasks
18
+ rake_tasks do
19
+ Dir[File.join(File.dirname(__FILE__), "../tasks/**/*.rake")].each { |f| load f }
20
+ end
16
21
  end
17
22
  end
@@ -1,3 +1,3 @@
1
1
  module Solidstats
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
data/lib/solidstats.rb CHANGED
@@ -2,5 +2,17 @@ require "solidstats/version"
2
2
  require "solidstats/engine"
3
3
 
4
4
  module Solidstats
5
- # Your code goes here...
5
+ class << self
6
+ # Returns the absolute path to this gem's root directory
7
+ # @return [String] Gem root path
8
+ def root
9
+ File.dirname(__dir__)
10
+ end
11
+
12
+ # Returns version string
13
+ # @return [String] Version
14
+ def version
15
+ VERSION
16
+ end
17
+ end
6
18
  end
@@ -0,0 +1,13 @@
1
+
2
+ namespace :solidstats do
3
+ desc "Install Solidstats into your Rails application"
4
+ task :install do
5
+ if system "rails g solidstats:install"
6
+ puts "\n✅ Solidstats installation completed successfully!"
7
+ puts " Start your Rails server and visit http://localhost:3000/solidstats\n\n"
8
+ else
9
+ puts "\n❌ Solidstats installation failed. Please try running manually:"
10
+ puts " bundle exec rails generate solidstats:install\n\n"
11
+ end
12
+ end
13
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidstats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - MezbahAlam
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-19 00:00:00.000000000 Z
11
+ date: 2025-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -45,6 +45,7 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - CHANGELOG.md
48
49
  - MIT-LICENSE
49
50
  - README.md
50
51
  - Rakefile
@@ -56,6 +57,7 @@ files:
56
57
  - app/mailers/solidstats/application_mailer.rb
57
58
  - app/models/solidstats/application_record.rb
58
59
  - app/views/layouts/solidstats/application.html.erb
60
+ - app/views/solidstats/dashboard/_todos.html.erb
59
61
  - app/views/solidstats/dashboard/audit/_audit_badge.html.erb
60
62
  - app/views/solidstats/dashboard/audit/_audit_details.html.erb
61
63
  - app/views/solidstats/dashboard/audit/_audit_filters.html.erb
@@ -66,9 +68,12 @@ files:
66
68
  - app/views/solidstats/dashboard/audit/_vulnerability_details.html.erb
67
69
  - app/views/solidstats/dashboard/index.html.erb
68
70
  - config/routes.rb
71
+ - lib/generators/solidstats/install/install_generator.rb
72
+ - lib/generators/solidstats/install/templates/README
69
73
  - lib/solidstats.rb
70
74
  - lib/solidstats/engine.rb
71
75
  - lib/solidstats/version.rb
76
+ - lib/tasks/solidstats_install.rake
72
77
  - lib/tasks/solidstats_tasks.rake
73
78
  homepage: https://solidstats.infolily.com
74
79
  licenses: