solidstats 1.0.0 → 2.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +85 -0
  3. data/README.md +35 -0
  4. data/app/assets/javascripts/solidstats/application.js +257 -0
  5. data/app/assets/javascripts/solidstats/dashboard.js +225 -0
  6. data/app/assets/javascripts/solidstats/gem_metadata.js +554 -0
  7. data/app/assets/stylesheets/solidstats/application.css +6 -1
  8. data/app/assets/stylesheets/solidstats/components/action_button.css +99 -0
  9. data/app/assets/stylesheets/solidstats/components/dashboard.css +151 -0
  10. data/app/assets/stylesheets/solidstats/components/dashboard_header.css +93 -0
  11. data/app/assets/stylesheets/solidstats/components/dashboard_layout.css +97 -0
  12. data/app/assets/stylesheets/solidstats/components/gem_metadata.css +1403 -0
  13. data/app/assets/stylesheets/solidstats/components/navigation.css +80 -0
  14. data/app/assets/stylesheets/solidstats/components/quick_navigation.css +54 -0
  15. data/app/assets/stylesheets/solidstats/components/security.css +332 -0
  16. data/app/assets/stylesheets/solidstats/components/status_badge.css +58 -0
  17. data/app/assets/stylesheets/solidstats/components/summary_card.css +66 -0
  18. data/app/assets/stylesheets/solidstats/components/tab_navigation.css +95 -0
  19. data/app/components/solidstats/base_component.rb +88 -0
  20. data/app/components/solidstats/code_quality/code_quality_section_component.html.erb +0 -0
  21. data/app/components/solidstats/code_quality/code_quality_section_component.rb +0 -0
  22. data/app/components/solidstats/code_quality/section_component.html.erb +45 -0
  23. data/app/components/solidstats/code_quality/section_component.rb +34 -0
  24. data/app/components/solidstats/dashboard_header_component.html.erb +39 -0
  25. data/app/components/solidstats/dashboard_header_component.rb +33 -0
  26. data/app/components/solidstats/previews/action_button_component_preview/button_vs_link.html.erb +6 -0
  27. data/app/components/solidstats/previews/action_button_component_preview/sizes.html.erb +6 -0
  28. data/app/components/solidstats/previews/action_button_component_preview/variants.html.erb +6 -0
  29. data/app/components/solidstats/previews/action_button_component_preview/with_icons.html.erb +6 -0
  30. data/app/components/solidstats/previews/action_button_component_preview.rb +64 -0
  31. data/app/components/solidstats/previews/navigation_component_preview.rb +74 -0
  32. data/app/components/solidstats/previews/stats_overview_component_preview.rb +100 -0
  33. data/app/components/solidstats/previews/status_badge_component_preview/sizes.html.erb +6 -0
  34. data/app/components/solidstats/previews/status_badge_component_preview/statuses.html.erb +6 -0
  35. data/app/components/solidstats/previews/status_badge_component_preview/with_icons.html.erb +6 -0
  36. data/app/components/solidstats/previews/status_badge_component_preview.rb +49 -0
  37. data/app/components/solidstats/previews/summary_card_component_preview/clickable.html.erb +9 -0
  38. data/app/components/solidstats/previews/summary_card_component_preview/dashboard_layout.html.erb +9 -0
  39. data/app/components/solidstats/previews/summary_card_component_preview/statuses.html.erb +6 -0
  40. data/app/components/solidstats/previews/summary_card_component_preview/value_formats.html.erb +6 -0
  41. data/app/components/solidstats/previews/summary_card_component_preview.rb +67 -0
  42. data/app/components/solidstats/quick_navigation_component.html.erb +8 -0
  43. data/app/components/solidstats/quick_navigation_component.rb +21 -0
  44. data/app/components/solidstats/security/gem_impact_analysis_component.html.erb +44 -0
  45. data/app/components/solidstats/security/gem_impact_analysis_component.rb +45 -0
  46. data/app/components/solidstats/security/overview_component.html.erb +21 -0
  47. data/app/components/solidstats/security/overview_component.rb +104 -0
  48. data/app/components/solidstats/security/section_component.html.erb +26 -0
  49. data/app/components/solidstats/security/section_component.rb +52 -0
  50. data/app/components/solidstats/security/timeline_component.html.erb +39 -0
  51. data/app/components/solidstats/security/timeline_component.rb +43 -0
  52. data/app/components/solidstats/tasks_section_component.html.erb +17 -0
  53. data/app/components/solidstats/tasks_section_component.rb +22 -0
  54. data/app/components/solidstats/ui/action_button_component.html.erb +6 -0
  55. data/app/components/solidstats/ui/action_button_component.rb +71 -0
  56. data/app/components/solidstats/ui/dashboard_layout_component.html.erb +19 -0
  57. data/app/components/solidstats/ui/dashboard_layout_component.rb +85 -0
  58. data/app/components/solidstats/ui/navigation_component.html.erb +34 -0
  59. data/app/components/solidstats/ui/navigation_component.rb +72 -0
  60. data/app/components/solidstats/ui/stats_overview_component.html.erb +14 -0
  61. data/app/components/solidstats/ui/stats_overview_component.rb +78 -0
  62. data/app/components/solidstats/ui/status_badge_component.html.erb +6 -0
  63. data/app/components/solidstats/ui/status_badge_component.rb +42 -0
  64. data/app/components/solidstats/ui/summary_card_component.html.erb +12 -0
  65. data/app/components/solidstats/ui/summary_card_component.rb +63 -0
  66. data/app/components/solidstats/ui/tab_navigation_component.html.erb +22 -0
  67. data/app/components/solidstats/ui/tab_navigation_component.rb +79 -0
  68. data/app/controllers/solidstats/dashboard_controller.rb +22 -0
  69. data/app/controllers/solidstats/gem_metadata_controller.rb +12 -0
  70. data/app/helpers/solidstats/application_helper.rb +42 -0
  71. data/app/services/solidstats/gem_metadata/fetcher_service.rb +136 -0
  72. data/app/services/solidstats/log_size_monitor_service.rb +94 -0
  73. data/app/views/layouts/solidstats/application.html.erb +2 -1
  74. data/app/views/solidstats/dashboard/_log_monitor.html.erb +759 -0
  75. data/app/views/solidstats/dashboard/index.html.erb +67 -1323
  76. data/app/views/solidstats/gem_metadata/_panel.html.erb +419 -0
  77. data/config/routes.rb +7 -0
  78. data/lib/generators/solidstats/feature/feature_generator.rb +170 -0
  79. data/lib/generators/solidstats/feature/templates/component.html.erb +84 -0
  80. data/lib/generators/solidstats/feature/templates/component.rb.erb +103 -0
  81. data/lib/generators/solidstats/feature/templates/component.scss +243 -0
  82. data/lib/generators/solidstats/feature/templates/component_test.rb.erb +183 -0
  83. data/lib/generators/solidstats/feature/templates/controller.rb.erb +44 -0
  84. data/lib/generators/solidstats/feature/templates/controller_test.rb.erb +111 -0
  85. data/lib/generators/solidstats/feature/templates/detail_view.html.erb +755 -0
  86. data/lib/generators/solidstats/feature/templates/preview.rb.erb +107 -0
  87. data/lib/generators/solidstats/feature/templates/service.rb.erb +132 -0
  88. data/lib/generators/solidstats/feature/templates/service_test.rb.erb +109 -0
  89. data/lib/generators/solidstats/install_generator.rb +109 -0
  90. data/lib/generators/solidstats/templates/initializer.rb +112 -0
  91. data/lib/solidstats/asset_compatibility.rb +238 -0
  92. data/lib/solidstats/asset_manifest.rb +205 -0
  93. data/lib/solidstats/engine.rb +114 -9
  94. data/lib/solidstats/version.rb +1 -1
  95. data/lib/solidstats.rb +299 -2
  96. data/lib/tasks/solidstats_install.rake +122 -2
  97. metadata +99 -2
@@ -0,0 +1,84 @@
1
+ <%# <%= human_name %> Component Partial %>
2
+ <%
3
+ # Initialize data with defaults
4
+ data ||= { value: 0, items: [], status: :ok, last_updated: Time.current }
5
+
6
+ # Include helper module methods
7
+ extend Solidstats::<%= class_name %>Helper
8
+ %>
9
+
10
+ <div class="<%= file_name.dasherize %>-component">
11
+ <div class="component-header">
12
+ <div class="header-main">
13
+ <h3 class="component-title">
14
+ <span class="component-icon"><%= options[:icon] %></span>
15
+ <%= human_name %>
16
+ </h3>
17
+ <div class="component-status <%%= <%= file_name %>_status_class(data) %>">
18
+ <span class="status-icon"><%%= <%= file_name %>_status_icon(data) %></span>
19
+ <span class="status-text"><%%= <%= file_name %>_status_text(data) %></span>
20
+ </div>
21
+ </div>
22
+ <div class="header-meta">
23
+ <span class="last-updated">Last updated: <%%= <%= file_name %>_last_updated(data) %></span>
24
+ </div>
25
+ </div>
26
+
27
+ <div class="component-content">
28
+ <div class="metric-overview">
29
+ <div class="metric-card primary">
30
+ <div class="metric-icon"><%= options[:icon] %></div>
31
+ <div class="metric-data">
32
+ <div class="metric-value"><%%= <%= file_name %>_value_display(data) %></div>
33
+ <div class="metric-label">Total <%= human_name %></div>
34
+ </div>
35
+ </div>
36
+
37
+ <%% if <%= file_name %>_item_count(data) > 0 %>
38
+ <div class="metric-card secondary">
39
+ <div class="metric-icon">📊</div>
40
+ <div class="metric-data">
41
+ <div class="metric-value"><%%= <%= file_name %>_item_count(data) %></div>
42
+ <div class="metric-label">Items Found</div>
43
+ </div>
44
+ </div>
45
+ <%% end %>
46
+ </div>
47
+
48
+ <%% if <%= file_name %>_has_items?(data) %>
49
+ <div class="items-section">
50
+ <h4 class="section-title">Details</h4>
51
+ <div class="items-grid">
52
+ <%% data[:items].first(5).each do |item| %>
53
+ <div class="item-card">
54
+ <div class="item-header">
55
+ <span class="item-name"><%%= item[:name] %></span>
56
+ <span class="item-value"><%%= item[:value] %></span>
57
+ </div>
58
+ <%% if item[:category] %>
59
+ <div class="item-meta">
60
+ <span class="item-category"><%%= item[:category].humanize %></span>
61
+ </div>
62
+ <%% end %>
63
+ </div>
64
+ <%% end %>
65
+ </div>
66
+
67
+ <%% if data[:items].size > 5 %>
68
+ <div class="view-all-link">
69
+ <a href="<%%= solidstats.<%= file_name %>_path %>" class="action-button">
70
+ <span class="action-icon">đŸ‘ī¸</span>
71
+ View All (<%%= data[:items].size %> total)
72
+ </a>
73
+ </div>
74
+ <%% end %>
75
+ </div>
76
+ <%% else %>
77
+ <div class="empty-state">
78
+ <div class="empty-icon">🎉</div>
79
+ <div class="empty-message">No <%= file_name.humanize.downcase %> items found</div>
80
+ <div class="empty-description">This is good news! Everything looks clean.</div>
81
+ </div>
82
+ <%% end %>
83
+ </div>
84
+ </div>
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Helper module for <%= human_name %> component rendering
4
+ # This provides data processing and helper methods for the <%= file_name %> partial
5
+ module Solidstats
6
+ module <%= class_name %>Helper
7
+ def <%= file_name %>_status_class(data)
8
+ "status-#{data[:status] || 'ok'}"
9
+ end
10
+
11
+ def <%= file_name %>_status_icon(data)
12
+ case data[:status]&.to_sym
13
+ when :good then "✅"
14
+ when :warning then "âš ī¸"
15
+ when :critical then "❌"
16
+ when :info then "â„šī¸"
17
+ else "❓"
18
+ end
19
+ end
20
+
21
+ def <%= file_name %>_status_text(data)
22
+ case data[:status]&.to_sym
23
+ when :good then "Good"
24
+ when :warning then "Warning"
25
+ when :critical then "Critical"
26
+ when :info then "Info"
27
+ else "Unknown"
28
+ end
29
+ end
30
+
31
+ def <%= file_name %>_value_display(data)
32
+ value = data[:value]
33
+ return "0" unless value
34
+
35
+ case value
36
+ when String then value
37
+ when Numeric
38
+ # Format large numbers with commas
39
+ if value >= 1000
40
+ number_with_delimiter(value)
41
+ else
42
+ value.to_s
43
+ end
44
+ else
45
+ value.to_s
46
+ end
47
+ end
48
+
49
+ def <%= file_name %>_last_updated(data)
50
+ return "Never" unless data[:last_updated]
51
+
52
+ time = data[:last_updated]
53
+ time = Time.parse(time) if time.is_a?(String)
54
+
55
+ distance_of_time_in_words(time, Time.current) + " ago"
56
+ end
57
+
58
+ def <%= file_name %>_has_items?(data)
59
+ data[:items].present? && data[:items].any?
60
+ end
61
+
62
+ def <%= file_name %>_item_count(data)
63
+ return 0 unless data[:items].present?
64
+ data[:items].length
65
+ end
66
+
67
+ def <%= file_name %>_summary_metrics(data)
68
+ {
69
+ value: <%= file_name %>_value_display(data),
70
+ status: <%= file_name %>_status_text(data),
71
+ last_updated: <%= file_name %>_last_updated(data),
72
+ item_count: <%= file_name %>_item_count(data)
73
+ }
74
+ end
75
+
76
+ def <%= file_name %>_trend_indicator(data)
77
+ return "" unless data[:trend]
78
+
79
+ case data[:trend]&.to_sym
80
+ when :up then "📈"
81
+ when :down then "📉"
82
+ when :stable then "âžĄī¸"
83
+ else ""
84
+ end
85
+ end
86
+
87
+ def <%= file_name %>_priority_class(item)
88
+ return "" unless item.is_a?(Hash) && item[:priority]
89
+
90
+ "priority-#{item[:priority]}"
91
+ end
92
+
93
+ def <%= file_name %>_format_item_value(item)
94
+ return "" unless item.is_a?(Hash)
95
+
96
+ if item[:value].is_a?(Numeric) && item[:value] >= 1000
97
+ number_with_delimiter(item[:value])
98
+ else
99
+ item[:value].to_s
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,243 @@
1
+ .<%= file_name.dasherize %>-component {
2
+ background-color: #fff;
3
+ border-radius: 12px;
4
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
5
+ overflow: hidden;
6
+ margin-bottom: 1.5rem;
7
+ border: 1px solid rgba(0, 0, 0, 0.05);
8
+ }
9
+
10
+ .component-header {
11
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
12
+ color: white;
13
+ padding: 1.5rem;
14
+
15
+ .header-main {
16
+ display: flex;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ margin-bottom: 0.5rem;
20
+ }
21
+
22
+ .component-title {
23
+ font-size: 1.25rem;
24
+ font-weight: 600;
25
+ margin: 0;
26
+ display: flex;
27
+ align-items: center;
28
+ gap: 0.5rem;
29
+ }
30
+
31
+ .component-status {
32
+ display: flex;
33
+ align-items: center;
34
+ gap: 0.5rem;
35
+ padding: 0.25rem 0.75rem;
36
+ border-radius: 12px;
37
+ background: rgba(255, 255, 255, 0.2);
38
+ font-size: 0.875rem;
39
+
40
+ &.status-ok {
41
+ background: rgba(40, 167, 69, 0.2);
42
+ }
43
+
44
+ &.status-warning {
45
+ background: rgba(255, 193, 7, 0.2);
46
+ }
47
+
48
+ &.status-danger {
49
+ background: rgba(220, 53, 69, 0.2);
50
+ }
51
+
52
+ &.status-error {
53
+ background: rgba(108, 117, 125, 0.2);
54
+ }
55
+ }
56
+
57
+ .header-meta {
58
+ opacity: 0.9;
59
+ font-size: 0.875rem;
60
+ }
61
+ }
62
+
63
+ .component-content {
64
+ padding: 1.5rem;
65
+ }
66
+
67
+ .metric-overview {
68
+ display: grid;
69
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
70
+ gap: 1rem;
71
+ margin-bottom: 2rem;
72
+ }
73
+
74
+ .metric-card {
75
+ background: white;
76
+ border-radius: 8px;
77
+ padding: 1.25rem;
78
+ border: 1px solid #e5e7eb;
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 1rem;
82
+ transition: all 0.3s ease;
83
+
84
+ &:hover {
85
+ transform: translateY(-2px);
86
+ box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.1);
87
+ }
88
+
89
+ &.primary {
90
+ border-left: 4px solid #3b82f6;
91
+ }
92
+
93
+ &.secondary {
94
+ border-left: 4px solid #10b981;
95
+ }
96
+
97
+ .metric-icon {
98
+ font-size: 1.5rem;
99
+ opacity: 0.8;
100
+ }
101
+
102
+ .metric-data {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 0.25rem;
106
+ }
107
+
108
+ .metric-value {
109
+ font-size: 1.75rem;
110
+ font-weight: 700;
111
+ color: #1f2937;
112
+ line-height: 1;
113
+ }
114
+
115
+ .metric-label {
116
+ font-size: 0.875rem;
117
+ color: #6b7280;
118
+ font-weight: 500;
119
+ }
120
+ }
121
+
122
+ .items-section {
123
+ .section-title {
124
+ font-size: 1.125rem;
125
+ font-weight: 600;
126
+ margin: 0 0 1rem 0;
127
+ color: #1f2937;
128
+ }
129
+ }
130
+
131
+ .items-grid {
132
+ display: grid;
133
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
134
+ gap: 1rem;
135
+ margin-bottom: 1.5rem;
136
+ }
137
+
138
+ .item-card {
139
+ background: #f9fafb;
140
+ border: 1px solid #e5e7eb;
141
+ border-radius: 8px;
142
+ padding: 1rem;
143
+ transition: all 0.2s ease;
144
+
145
+ &:hover {
146
+ background: #f3f4f6;
147
+ border-color: #d1d5db;
148
+ }
149
+
150
+ .item-header {
151
+ display: flex;
152
+ justify-content: space-between;
153
+ align-items: center;
154
+ margin-bottom: 0.5rem;
155
+ }
156
+
157
+ .item-name {
158
+ font-weight: 500;
159
+ color: #1f2937;
160
+ }
161
+
162
+ .item-value {
163
+ font-weight: 600;
164
+ color: #3b82f6;
165
+ }
166
+
167
+ .item-meta {
168
+ font-size: 0.875rem;
169
+ color: #6b7280;
170
+ }
171
+
172
+ .item-category {
173
+ background: #e5e7eb;
174
+ padding: 0.125rem 0.5rem;
175
+ border-radius: 4px;
176
+ font-size: 0.75rem;
177
+ }
178
+ }
179
+
180
+ .view-all-link {
181
+ text-align: center;
182
+
183
+ .action-button {
184
+ display: inline-flex;
185
+ align-items: center;
186
+ gap: 0.5rem;
187
+ padding: 0.75rem 1.5rem;
188
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
189
+ color: white;
190
+ text-decoration: none;
191
+ border-radius: 8px;
192
+ font-weight: 500;
193
+ transition: all 0.2s ease;
194
+
195
+ &:hover {
196
+ transform: translateY(-1px);
197
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
198
+ }
199
+ }
200
+ }
201
+
202
+ .empty-state {
203
+ text-align: center;
204
+ padding: 3rem 1rem;
205
+ color: #6b7280;
206
+
207
+ .empty-icon {
208
+ font-size: 3rem;
209
+ margin-bottom: 1rem;
210
+ opacity: 0.7;
211
+ }
212
+
213
+ .empty-message {
214
+ font-size: 1.25rem;
215
+ font-weight: 600;
216
+ color: #374151;
217
+ margin-bottom: 0.5rem;
218
+ }
219
+
220
+ .empty-description {
221
+ max-width: 400px;
222
+ margin: 0 auto;
223
+ }
224
+ }
225
+
226
+ // Responsive design
227
+ @media (max-width: 768px) {
228
+ .component-header {
229
+ .header-main {
230
+ flex-direction: column;
231
+ gap: 0.75rem;
232
+ align-items: flex-start;
233
+ }
234
+ }
235
+
236
+ .metric-overview {
237
+ grid-template-columns: 1fr;
238
+ }
239
+
240
+ .items-grid {
241
+ grid-template-columns: 1fr;
242
+ }
243
+ }
@@ -0,0 +1,183 @@
1
+ require "test_helper"
2
+
3
+ module Solidstats
4
+ class <%= class_name %>HelperTest < ActionView::TestCase
5
+ include Solidstats::<%= class_name %>Helper
6
+
7
+ def setup
8
+ @sample_data = {
9
+ value: 42,
10
+ count: 8,
11
+ status: :good,
12
+ last_updated: 30.minutes.ago,
13
+ items: [
14
+ {
15
+ name: "Test Item 1",
16
+ value: 15,
17
+ category: "important",
18
+ description: "Test description"
19
+ },
20
+ {
21
+ name: "Test Item 2",
22
+ value: 8,
23
+ category: "normal"
24
+ }
25
+ ]
26
+ }
27
+ end
28
+
29
+ test "returns correct status class" do
30
+ assert_equal "status-good", <%= file_name %>_status_class(@sample_data)
31
+
32
+ warning_data = @sample_data.merge(status: :warning)
33
+ assert_equal "status-warning", <%= file_name %>_status_class(warning_data)
34
+
35
+ critical_data = @sample_data.merge(status: :critical)
36
+ assert_equal "status-critical", <%= file_name %>_status_class(critical_data)
37
+ end
38
+
39
+ test "returns correct status icon" do
40
+ assert_equal "✅", <%= file_name %>_status_icon(@sample_data)
41
+
42
+ warning_data = @sample_data.merge(status: :warning)
43
+ assert_equal "âš ī¸", <%= file_name %>_status_icon(warning_data)
44
+
45
+ critical_data = @sample_data.merge(status: :critical)
46
+ assert_equal "❌", <%= file_name %>_status_icon(critical_data)
47
+ end
48
+
49
+ test "returns correct status text" do
50
+ assert_equal "Good", <%= file_name %>_status_text(@sample_data)
51
+
52
+ critical_data = @sample_data.merge(status: :critical)
53
+ assert_equal "Critical", <%= file_name %>_status_text(critical_data)
54
+ end
55
+
56
+ test "displays value correctly" do
57
+ assert_equal "42", <%= file_name %>_value_display(@sample_data)
58
+
59
+ large_value_data = @sample_data.merge(value: 1234567)
60
+ assert_equal "1,234,567", <%= file_name %>_value_display(large_value_data)
61
+
62
+ string_value_data = @sample_data.merge(value: "Custom Value")
63
+ assert_equal "Custom Value", <%= file_name %>_value_display(string_value_data)
64
+ end
65
+
66
+ test "returns last updated time" do
67
+ result = <%= file_name %>_last_updated(@sample_data)
68
+ assert_includes result, "ago"
69
+
70
+ no_update_data = @sample_data.except(:last_updated)
71
+ assert_equal "Never", <%= file_name %>_last_updated(no_update_data)
72
+ end
73
+
74
+ test "detects items presence" do
75
+ assert <%= file_name %>_has_items?(@sample_data)
76
+
77
+ no_items_data = @sample_data.merge(items: [])
78
+ refute <%= file_name %>_has_items?(no_items_data)
79
+
80
+ nil_items_data = @sample_data.merge(items: nil)
81
+ refute <%= file_name %>_has_items?(nil_items_data)
82
+ end
83
+
84
+ test "counts items correctly" do
85
+ assert_equal 2, <%= file_name %>_item_count(@sample_data)
86
+
87
+ no_items_data = @sample_data.merge(items: [])
88
+ assert_equal 0, <%= file_name %>_item_count(no_items_data)
89
+
90
+ nil_items_data = @sample_data.merge(items: nil)
91
+ assert_equal 0, <%= file_name %>_item_count(nil_items_data)
92
+ end
93
+
94
+ test "returns summary metrics" do
95
+ metrics = <%= file_name %>_summary_metrics(@sample_data)
96
+
97
+ assert_equal "42", metrics[:value]
98
+ assert_equal "Good", metrics[:status]
99
+ assert_includes metrics[:last_updated], "ago"
100
+ assert_equal 2, metrics[:item_count]
101
+ end
102
+
103
+ test "returns trend indicators" do
104
+ up_data = @sample_data.merge(trend: :up)
105
+ assert_equal "📈", <%= file_name %>_trend_indicator(up_data)
106
+
107
+ down_data = @sample_data.merge(trend: :down)
108
+ assert_equal "📉", <%= file_name %>_trend_indicator(down_data)
109
+
110
+ stable_data = @sample_data.merge(trend: :stable)
111
+ assert_equal "âžĄī¸", <%= file_name %>_trend_indicator(stable_data)
112
+
113
+ no_trend_data = @sample_data.except(:trend)
114
+ assert_equal "", <%= file_name %>_trend_indicator(no_trend_data)
115
+ end
116
+
117
+ test "returns priority class for items" do
118
+ high_priority_item = { priority: "high" }
119
+ assert_equal "priority-high", <%= file_name %>_priority_class(high_priority_item)
120
+
121
+ low_priority_item = { priority: "low" }
122
+ assert_equal "priority-low", <%= file_name %>_priority_class(low_priority_item)
123
+
124
+ no_priority_item = { name: "test" }
125
+ assert_equal "", <%= file_name %>_priority_class(no_priority_item)
126
+ end
127
+
128
+ test "formats item values correctly" do
129
+ large_value_item = { value: 1500 }
130
+ assert_equal "1,500", <%= file_name %>_format_item_value(large_value_item)
131
+
132
+ small_value_item = { value: 42 }
133
+ assert_equal "42", <%= file_name %>_format_item_value(small_value_item)
134
+
135
+ string_value_item = { value: "test" }
136
+ assert_equal "test", <%= file_name %>_format_item_value(string_value_item)
137
+ end
138
+
139
+ test "handles edge cases" do
140
+ # Test with nil data
141
+ nil_data = nil
142
+ assert_equal "", <%= file_name %>_priority_class(nil_data)
143
+ assert_equal "", <%= file_name %>_format_item_value(nil_data)
144
+
145
+ # Test with empty hash
146
+ empty_hash = {}
147
+ assert_equal "status-ok", <%= file_name %>_status_class(empty_hash)
148
+ assert_equal "❓", <%= file_name %>_status_icon(empty_hash)
149
+ assert_equal "Unknown", <%= file_name %>_status_text(empty_hash)
150
+
151
+ # Test with missing items
152
+ no_items = { value: 10 }
153
+ refute <%= file_name %>_has_items?(no_items)
154
+ assert_equal 0, <%= file_name %>_item_count(no_items)
155
+ end
156
+
157
+ test "handles different status values" do
158
+ statuses = [:good, :warning, :critical, :info, :unknown]
159
+
160
+ statuses.each do |status|
161
+ test_data = @sample_data.merge(status: status)
162
+
163
+ case status
164
+ when :good
165
+ assert_equal "✅", <%= file_name %>_status_icon(test_data)
166
+ assert_equal "Good", <%= file_name %>_status_text(test_data)
167
+ when :warning
168
+ assert_equal "âš ī¸", <%= file_name %>_status_icon(test_data)
169
+ assert_equal "Warning", <%= file_name %>_status_text(test_data)
170
+ when :critical
171
+ assert_equal "❌", <%= file_name %>_status_icon(test_data)
172
+ assert_equal "Critical", <%= file_name %>_status_text(test_data)
173
+ when :info
174
+ assert_equal "â„šī¸", <%= file_name %>_status_icon(test_data)
175
+ assert_equal "Info", <%= file_name %>_status_text(test_data)
176
+ else
177
+ assert_equal "❓", <%= file_name %>_status_icon(test_data)
178
+ assert_equal "Unknown", <%= file_name %>_status_text(test_data)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,44 @@
1
+ module Solidstats
2
+ class <%= class_name %>Controller < ApplicationController
3
+ before_action :development_only
4
+ before_action :load_service
5
+
6
+ def index
7
+ @data = @service.detailed_data
8
+
9
+ respond_to do |format|
10
+ format.html
11
+ format.json { render json: @data }
12
+ end
13
+ end
14
+
15
+ def refresh
16
+ @data = @service.fetch(true)
17
+
18
+ respond_to do |format|
19
+ format.html { redirect_to solidstats.<%= file_name %>_path, notice: '<%= human_name %> data refreshed successfully.' }
20
+ format.json { render json: @data }
21
+ end
22
+ end
23
+
24
+ def summary
25
+ summary_data = @service.summary
26
+
27
+ respond_to do |format|
28
+ format.json { render json: summary_data }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def load_service
35
+ @service = <%= class_name %>Service.new
36
+ end
37
+
38
+ def development_only
39
+ unless Rails.env.development?
40
+ head :not_found
41
+ end
42
+ end
43
+ end
44
+ end