watchdog-dashboard 0.1.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 (32) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +123 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/watchdog_dashboard_manifest.js +1 -0
  6. data/app/assets/stylesheets/watchdog/dashboard/application.css +15 -0
  7. data/app/controllers/concerns/flaky_stats.rb +217 -0
  8. data/app/controllers/concerns/metric_stats.rb +217 -0
  9. data/app/controllers/watchdog/dashboard/application_controller.rb +6 -0
  10. data/app/controllers/watchdog/dashboard/dashboard_controller.rb +71 -0
  11. data/app/controllers/watchdog/dashboard/metrics_controller.rb +37 -0
  12. data/app/helpers/watchdog/dashboard/application_helper.rb +6 -0
  13. data/app/helpers/watchdog/dashboard/dashboard_helper.rb +4 -0
  14. data/app/helpers/watchdog/dashboard/metrics_helper.rb +4 -0
  15. data/app/jobs/watchdog/dashboard/application_job.rb +6 -0
  16. data/app/mailers/watchdog/dashboard/application_mailer.rb +8 -0
  17. data/app/models/watchdog/dashboard/application_record.rb +7 -0
  18. data/app/models/watchdog/dashboard/metric.rb +5 -0
  19. data/app/views/layouts/watchdog/dashboard/application.html.erb +28 -0
  20. data/app/views/watchdog/dashboard/dashboard/_sidebar.html.erb +83 -0
  21. data/app/views/watchdog/dashboard/dashboard/flakies.html.erb +263 -0
  22. data/app/views/watchdog/dashboard/dashboard/historic.html.erb +495 -0
  23. data/app/views/watchdog/dashboard/dashboard/index.html.erb +219 -0
  24. data/app/views/watchdog/dashboard/dashboard/metrics.html.erb +263 -0
  25. data/config/initializers/assets.rb +1 -0
  26. data/config/routes.rb +10 -0
  27. data/db/migrate/20250331204354_create_watchdog_dashboard_metrics.rb +14 -0
  28. data/lib/tasks/watchdog/dashboard_tasks.rake +4 -0
  29. data/lib/watchdog/dashboard/engine.rb +7 -0
  30. data/lib/watchdog/dashboard/version.rb +5 -0
  31. data/lib/watchdog/dashboard.rb +19 -0
  32. metadata +92 -0
@@ -0,0 +1,71 @@
1
+ module Watchdog::Dashboard
2
+ class DashboardController < ApplicationController
3
+ include MetricStats
4
+ include FlakyStats
5
+
6
+ def index
7
+ end
8
+
9
+ def historic
10
+ @metrics = Metric.all
11
+
12
+ @run_time_distribution = run_time_distribution
13
+ @performance_trend = performance_trend
14
+ @test_count_trend = test_count_trend
15
+ @longest_tests_by_day = longest_tests_by_day
16
+ @total_execution_time_by_day = total_execution_time_by_day
17
+ @tests_exceeding_time_threshold = tests_exceeding_time_threshold
18
+ @failed_tests_trend_by_file = failed_tests_trend_by_file
19
+ @avg_execution_time_by_file = avg_execution_time_by_file
20
+ @stability_trend = stability_trend
21
+ end
22
+
23
+ def metrics
24
+ return redirect_to root_path if Metric.count.zero?
25
+
26
+ @metrics = Metric.all
27
+ @average_time = average_time
28
+ @fastest_test = fastest_test
29
+ @slowest_test = slowest_test
30
+ @percentiles = percentiles
31
+ @failed_tests = failed_tests
32
+ @tests_grouped_by_file = tests_grouped_by_file
33
+ @tests_that_took_longer_than = tests_that_took_longer_than(0.5)
34
+ @time_distribution_analysis = time_distribution_analysis
35
+
36
+ @test_stability_analysis = test_stability_analysis
37
+ @total_tests = @test_stability_analysis[:total_tests]
38
+ @passed_percentage = @test_stability_analysis[:passed_percentage]
39
+ @failed_percentage = @test_stability_analysis[:failed_percentage]
40
+ @pending_percentage = @test_stability_analysis[:pending_percentage]
41
+
42
+ @execution_time_variance = execution_time_variance
43
+ @temporal_complexity_analysis = temporal_complexity_analysis
44
+ @test_dependency_analysis = test_dependency_analysis
45
+ end
46
+
47
+ def flakies
48
+ return redirect_to root_path if Metric.count.zero?
49
+
50
+ @metrics = Metric.all
51
+ @average_time = flaky_average_time
52
+ @fastest_test = flaky_fastest_test
53
+ @slowest_test = flaky_slowest_test
54
+ @percentiles = flaky_percentiles
55
+ @failed_tests = flaky_failed_tests
56
+ @tests_grouped_by_file = flaky_tests_grouped_by_file
57
+ @tests_that_took_longer_than = flaky_tests_that_took_longer_than(0.5)
58
+ @time_distribution_analysis = flaky_time_distribution_analysis
59
+
60
+ @test_stability_analysis = flaky_test_stability_analysis
61
+ @total_tests = @test_stability_analysis[:total_tests]
62
+ @passed_percentage = @test_stability_analysis[:passed_percentage]
63
+ @failed_percentage = @test_stability_analysis[:failed_percentage]
64
+ @pending_percentage = @test_stability_analysis[:pending_percentage]
65
+
66
+ @execution_time_variance = flaky_execution_time_variance
67
+ @temporal_complexity_analysis = flaky_temporal_complexity_analysis
68
+ @test_dependency_analysis = flaky_test_dependency_analysis
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,37 @@
1
+ module Watchdog::Dashboard
2
+ class MetricsController < ApplicationController
3
+ skip_forgery_protection
4
+ before_action :authenticate_token
5
+
6
+ def analytics
7
+ batch = params[:metrics]
8
+
9
+ metrics_to_insert = batch.map do |metric|
10
+ Metric.create!(
11
+ description: metric[:description],
12
+ file_path: metric[:file_path],
13
+ location: metric[:location],
14
+ run_time: metric[:run_time],
15
+ status: metric[:status],
16
+ error_message: metric[:error_message],
17
+ flaky: metric[:flaky]
18
+ )
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def metric_params
25
+ params.require(:metric).permit(:description, :file_path, :location, :run_time, :status, :error_message, :flaky)
26
+ end
27
+
28
+ def authenticate_token
29
+ expected_token = Watchdog::Dashboard.config.watchdog_api_token
30
+ request_token = request.headers["Authorization"]
31
+
32
+ unless request_token.present? && ActiveSupport::SecurityUtils.secure_compare(request_token, expected_token)
33
+ render json: { error: "Unauthorized" }, status: :unauthorized
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ module Watchdog
2
+ module Dashboard
3
+ module ApplicationHelper
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,4 @@
1
+ module Watchdog::Dashboard
2
+ module DashboardHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Watchdog::Dashboard
2
+ module MetricsHelper
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Watchdog
2
+ module Dashboard
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ module Watchdog
2
+ module Dashboard
3
+ class ApplicationMailer < ActionMailer::Base
4
+ default from: "from@example.com"
5
+ layout "mailer"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Watchdog
2
+ module Dashboard
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Watchdog::Dashboard
2
+ class Metric < ApplicationRecord
3
+ scope :flaky, -> { where(flaky: true) }
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Watchdog Dashboard</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "watchdog/dashboard/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <style>
15
+ body {
16
+ font-family: monospace;
17
+ margin: 0;
18
+ background-image: linear-gradient(to bottom right, rgb(17, 24, 39), rgb(12, 74, 110));
19
+ color: rgb(229, 231, 235);
20
+ display: flex;
21
+ }
22
+ </style>
23
+
24
+ <%= render "sidebar" %>
25
+ <%= yield %>
26
+
27
+ </body>
28
+ </html>
@@ -0,0 +1,83 @@
1
+ <div class="sidebar">
2
+ <div class="sidebar-header">
3
+ <h2>📊 RSpec Dashboard</h2>
4
+ </div>
5
+ <ul class="sidebar-menu">
6
+ <li class="<%= 'active' if controller_name == 'dashboard' && action_name == 'index' %>">
7
+ <%= link_to "📌 Summary", root_path, class: "active" %>
8
+ </li>
9
+ <li class="<%= 'active' if controller_name == 'dashboard' && action_name == 'metrics' %>">
10
+ <%= link_to "📈 Metrics", metrics_path %>
11
+ </li>
12
+ <li class="<%= 'active' if controller_name == 'dashboard' && action_name == 'flakies' %>">
13
+ <%= link_to "🔥 Flaky Tests", flakies_path %>
14
+ </li>
15
+ <li class="<%= 'active' if controller_name == 'dashboard' && action_name == 'historic' %>">
16
+ <%= link_to "📜 Historic", historic_path %>
17
+ </li>
18
+ </ul>
19
+ </div>
20
+ <style>
21
+ /* Estilos generales */
22
+
23
+
24
+ /* Sidebar */
25
+ .sidebar {
26
+ width: 260px;
27
+ height: 100vh;
28
+ background: #1e293b;
29
+ color: white;
30
+ padding: 20px;
31
+ flex-shrink: 0;
32
+ display: flex;
33
+ flex-direction: column;
34
+ box-shadow: 3px 0 10px rgba(0, 0, 0, 0.1);
35
+ }
36
+
37
+ /* Header del Sidebar */
38
+ .sidebar-header {
39
+ text-align: center;
40
+ font-size: 18px;
41
+ font-weight: bold;
42
+ padding: 10px;
43
+ border-bottom: 1px solid rgba(255, 255, 255, 0.2);
44
+ }
45
+
46
+ /* Menú del Sidebar */
47
+ .sidebar-menu {
48
+ list-style: none;
49
+ padding: 0;
50
+ margin-top: 20px;
51
+ }
52
+
53
+ .sidebar-menu li {
54
+ margin: 12px 0;
55
+ }
56
+
57
+ .sidebar-menu li a {
58
+ display: block;
59
+ padding: 12px 16px;
60
+ color: white;
61
+ text-decoration: none;
62
+ border-radius: 8px;
63
+ transition: all 0.3s ease;
64
+ font-size: 16px;
65
+ }
66
+
67
+ .sidebar-menu li a:hover {
68
+ background: rgba(255, 255, 255, 0.2);
69
+ }
70
+
71
+ /* Estilo para la opción activa */
72
+ .sidebar-menu .active a {
73
+ background: #3b82f6;
74
+ font-weight: bold;
75
+ }
76
+
77
+ /* Contenido del Dashboard */
78
+ .dashboard-content {
79
+ flex: 1;
80
+ padding: 20px;
81
+ margin-left: 260px;
82
+ }
83
+ </style>
@@ -0,0 +1,263 @@
1
+ <style>
2
+ .container {
3
+ max-width: 1200px;
4
+ margin: 0 auto;
5
+ padding: 32px;
6
+ }
7
+ .header {
8
+ display: flex;
9
+ align-items: center;
10
+ justify-content: space-between;
11
+ margin-bottom: 1.5rem;
12
+ }
13
+ .title {
14
+ font-size: 1.875rem;
15
+ color: rgb(74, 222, 128);
16
+ }
17
+ .version {
18
+ font-size: 0.875rem;
19
+ color: rgb(156, 163, 175);
20
+ margin-left: 0.5rem;
21
+ }
22
+ .last-scan {
23
+ background-color: rgba(31, 41, 55, 0.7);
24
+ padding: 0.25rem 0.75rem;
25
+ border-radius: 0.375rem;
26
+ border-left: 2px solid rgb(239, 68, 68);
27
+ font-size: 0.75rem;
28
+ }
29
+ .last-scan-time {
30
+ color: rgb(250, 204, 21);
31
+ }
32
+ .summary-grid {
33
+ display: grid;
34
+ grid-template-columns: repeat(4, 1fr);
35
+ gap: 1.5rem;
36
+ margin-bottom: 2rem;
37
+ }
38
+ .summary-card {
39
+ background-color: rgba(31, 41, 55, 0.8);
40
+ padding: 1rem;
41
+ border-radius: 0.5rem;
42
+ border: 1px solid rgb(55, 65, 81);
43
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
44
+ transition: border-color 0.3s;
45
+ text-align: center;
46
+ }
47
+ .summary-card:hover {
48
+ border-color: rgb(239, 68, 68);
49
+ }
50
+ .summary-card h3 {
51
+ font-size: 1.25rem;
52
+ margin-bottom: 0.5rem;
53
+ color: rgb(248, 113, 113);
54
+ }
55
+ .summary-card .value {
56
+ font-size: 2.25rem;
57
+ font-weight: 700;
58
+ margin-bottom: 0.5rem;
59
+ }
60
+ .summary-card .trend {
61
+ font-size: 0.875rem;
62
+ color: rgb(156, 163, 175);
63
+ }
64
+ .content-grid {
65
+ display: grid;
66
+ grid-template-columns: 1fr 1fr;
67
+ gap: 1.5rem;
68
+ margin-bottom: 2rem;
69
+ }
70
+ .section {
71
+ background-color: rgba(31, 41, 55, 0.8);
72
+ padding: 1rem;
73
+ border-radius: 0.5rem;
74
+ border: 1px solid rgb(55, 65, 81);
75
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
76
+ }
77
+ .section h3 {
78
+ font-size: 1.25rem;
79
+ margin-bottom: 1rem;
80
+ color: rgb(250, 204, 21);
81
+ text-align: center;
82
+ }
83
+ .section ul {
84
+ list-style-type: none;
85
+ padding: 0;
86
+ }
87
+ .section ul li {
88
+ border-bottom: 1px solid rgb(55, 65, 81);
89
+ padding: 0.5rem 0;
90
+ }
91
+ .section ul li:last-child {
92
+ border-bottom: none;
93
+ }
94
+ table {
95
+ width: 100%;
96
+ border-collapse: collapse;
97
+ font-size: 0.875rem;
98
+ }
99
+ table thead tr {
100
+ border-bottom: 1px solid rgb(55, 65, 81);
101
+ color: rgb(156, 163, 175);
102
+ }
103
+ table tbody tr {
104
+ border-bottom: 1px solid rgb(55, 65, 81);
105
+ transition: background-color 0.2s;
106
+ }
107
+ table tbody tr:hover {
108
+ background-color: rgba(127, 29, 29, 0.2);
109
+ }
110
+ table th, table td {
111
+ border: 1px solid rgb(55, 65, 81);
112
+ padding: 0.5rem 1rem;
113
+ text-align: left;
114
+ }
115
+ .footer {
116
+ margin-top: 2rem;
117
+ font-size: 0.75rem;
118
+ color: rgb(107, 114, 128);
119
+ text-align: center;
120
+ }
121
+ .footer a {
122
+ color: rgb(96, 165, 250);
123
+ text-decoration: none;
124
+ transition: color 0.2s;
125
+ }
126
+ .footer a:hover {
127
+ color: rgb(248, 113, 113);
128
+ }
129
+ .warning {
130
+ text-align: center;
131
+ color: #e74c3c;
132
+ margin: 30px 0;
133
+ font-size: 1.25rem;
134
+ }
135
+ .percentile-list li {
136
+ display: flex;
137
+ justify-content: space-between;
138
+ padding: 0.25rem 0;
139
+ }
140
+ </style>
141
+ </head>
142
+ <body>
143
+ <div class="container">
144
+ <div class="header">
145
+ <div>
146
+ <span class="title">🔍 Rspec Watchdog Dashboard</span>
147
+ <span class="version">v<%= Watchdog::Dashboard::VERSION %>-alpha</span>
148
+ </div>
149
+ <div class="last-scan">
150
+ Last scan: <span class="last-scan-time"><%= Time.current.strftime('%Y-%m-%d %H:%M:%S UTC') %></span>
151
+ </div>
152
+ </div>
153
+
154
+ <div class="summary-grid">
155
+ <div class="summary-card">
156
+ <h3>📊 Total Tests</h3>
157
+ <div class="value"><%= @total_tests %></div>
158
+ <div class="trend">All Tests</div>
159
+ </div>
160
+ <div class="summary-card">
161
+ <h3>✅ Passed Tests</h3>
162
+ <div class="value"><%= @passed_percentage %>%</div>
163
+ <div class="trend">Passing Rate</div>
164
+ </div>
165
+ <div class="summary-card">
166
+ <h3>🕒 Avg Execution</h3>
167
+ <div class="value"><%= sprintf('%.4f', @average_time) %>s</div>
168
+ <div class="trend">Test Duration</div>
169
+ </div>
170
+ <div class="summary-card">
171
+ <h3>❌ Failed Tests</h3>
172
+ <div class="value"><%= @failed_percentage %>%</div>
173
+ <div class="trend">Failure Rate</div>
174
+ </div>
175
+ </div>
176
+
177
+ <% if @failed_tests.any? %>
178
+ <div class="warning">❌ Failed Tests Detected ❌</div>
179
+ <% end %>
180
+
181
+ <div class="content-grid">
182
+ <div class="section">
183
+ <h3>⚡ Fastest vs 🐢 Slowest Test</h3>
184
+ <ul>
185
+ <li>
186
+ <strong>Fastest Test:</strong>
187
+ <%= @fastest_test.description %>
188
+ (Time: <%= sprintf('%.4f', @fastest_test.run_time) %> s)
189
+ </li>
190
+ <li>
191
+ <strong>Slowest Test:</strong>
192
+ <%= @slowest_test.description %>
193
+ (Time: <%= sprintf('%.4f', @slowest_test.run_time) %> s)
194
+ </li>
195
+ </ul>
196
+ </div>
197
+ <div class="section">
198
+ <h3>📊 Time Distribution</h3>
199
+ <ul>
200
+ <% @time_distribution_analysis.each do |category, count| %>
201
+ <li><%= category %>: <%= count %> tests</li>
202
+ <% end %>
203
+ </ul>
204
+ </div>
205
+ </div>
206
+
207
+ <div class="content-grid">
208
+ <div class="section percentile-list">
209
+ <h3>📈 Execution Time Percentiles</h3>
210
+ <ul>
211
+ <% @percentiles.each do |percentile| %>
212
+ <li>
213
+ <span><%= percentile[:percentile] %>% Percentile</span>
214
+ <span><%= sprintf('%.4f', percentile[:run_time]) %> s</span>
215
+ </li>
216
+ <% end %>
217
+ </ul>
218
+ </div>
219
+ <div class="section">
220
+ <h3>❌ Failed Tests Details</h3>
221
+ <ul>
222
+ <% @failed_tests.each do |test| %>
223
+ <li>
224
+ <%= test.description %>
225
+ (File: <%= test.file_path %>,
226
+ Location: <%= test.location %>)
227
+ </li>
228
+ <% end %>
229
+ </ul>
230
+ </div>
231
+ </div>
232
+
233
+ <div class="section">
234
+ <h3>📊 Detailed Test Metrics</h3>
235
+ <table>
236
+ <thead>
237
+ <tr>
238
+ <th>Description</th>
239
+ <th>File</th>
240
+ <th>Location</th>
241
+ <th>Execution Time (s)</th>
242
+ </tr>
243
+ </thead>
244
+ <tbody>
245
+ <% @metrics.each do |metric| %>
246
+ <tr>
247
+ <td><%= metric.description %></td>
248
+ <td><%= metric.file_path %></td>
249
+ <td><%= metric.location %></td>
250
+ <td><%= sprintf('%.4f', metric.run_time) %></td>
251
+ </tr>
252
+ <% end %>
253
+ </tbody>
254
+ </table>
255
+ </div>
256
+
257
+ <div class="footer">
258
+ <p>Dashboard <span class="version">v<%= Watchdog::Dashboard::VERSION %>-alpha</span> | Data refreshed every 6 hours |
259
+ <a href="#">Configure alerts</a> |
260
+ <a href="#">Export data</a>
261
+ </p>
262
+ </div>
263
+ </div>