code_healer 0.1.12 → 0.1.15
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 +4 -4
- data/CHANGELOG.md +81 -111
- data/DASHBOARD_README.md +157 -0
- data/config/routes.rb +16 -0
- data/lib/code_healer/controllers/dashboard_controller.rb +89 -0
- data/lib/code_healer/engine.rb +31 -0
- data/lib/code_healer/installer.rb +51 -0
- data/lib/code_healer/models/healing_metric.rb +116 -0
- data/lib/code_healer/routes.rb +20 -0
- data/lib/code_healer/services/metrics_collector.rb +135 -0
- data/lib/code_healer/version.rb +1 -1
- data/lib/code_healer/views/dashboard/healing_details.html.erb +356 -0
- data/lib/code_healer/views/dashboard/index.html.erb +408 -0
- data/lib/code_healer.rb +14 -0
- data/lib/generators/code_healer/install_generator.rb +48 -0
- data/lib/generators/code_healer/templates/create_healing_metrics.rb +56 -0
- metadata +13 -1
@@ -0,0 +1,116 @@
|
|
1
|
+
module CodeHealer
|
2
|
+
class HealingMetric < ActiveRecord::Base
|
3
|
+
self.table_name = 'healing_metrics'
|
4
|
+
|
5
|
+
# Validations
|
6
|
+
validates :healing_id, presence: true, uniqueness: true
|
7
|
+
validates :class_name, presence: true
|
8
|
+
validates :method_name, presence: true
|
9
|
+
validates :error_class, presence: true
|
10
|
+
|
11
|
+
# Scopes for common queries
|
12
|
+
scope :successful, -> { where(healing_successful: true) }
|
13
|
+
scope :failed, -> { where(healing_successful: false) }
|
14
|
+
scope :recent, ->(days = 30) { where('created_at >= ?', days.days.ago) }
|
15
|
+
scope :by_evolution_method, ->(method) { where(evolution_method: method) }
|
16
|
+
scope :by_ai_provider, ->(provider) { where(ai_provider: provider) }
|
17
|
+
scope :by_class, ->(class_name) { where(class_name: class_name) }
|
18
|
+
|
19
|
+
# Class methods for dashboard metrics
|
20
|
+
class << self
|
21
|
+
def total_healings
|
22
|
+
count
|
23
|
+
end
|
24
|
+
|
25
|
+
def success_rate
|
26
|
+
return 0 if count.zero?
|
27
|
+
(successful.count.to_f / count * 100).round(2)
|
28
|
+
end
|
29
|
+
|
30
|
+
def healings_today
|
31
|
+
where('created_at >= ?', Date.current.beginning_of_day).count
|
32
|
+
end
|
33
|
+
|
34
|
+
def healings_this_week
|
35
|
+
where('created_at >= ?', Date.current.beginning_of_week).count
|
36
|
+
end
|
37
|
+
|
38
|
+
def healings_this_month
|
39
|
+
where('created_at >= ?', Date.current.beginning_of_month).count
|
40
|
+
end
|
41
|
+
|
42
|
+
def average_resolution_time
|
43
|
+
successful.where.not(total_duration_ms: nil).average(:total_duration_ms)&.round(2)
|
44
|
+
end
|
45
|
+
|
46
|
+
def evolution_method_distribution
|
47
|
+
group(:evolution_method).count
|
48
|
+
end
|
49
|
+
|
50
|
+
def ai_provider_distribution
|
51
|
+
group(:ai_provider).count
|
52
|
+
end
|
53
|
+
|
54
|
+
def top_error_classes(limit = 10)
|
55
|
+
group(:error_class).order('count_all DESC').limit(limit).count
|
56
|
+
end
|
57
|
+
|
58
|
+
def top_classes_healed(limit = 10)
|
59
|
+
group(:class_name).order('count_all DESC').limit(limit).count
|
60
|
+
end
|
61
|
+
|
62
|
+
def daily_healing_trend(days = 30)
|
63
|
+
# Use Rails date methods instead of raw SQL
|
64
|
+
start_date = days.days.ago.to_date
|
65
|
+
end_date = Date.current
|
66
|
+
|
67
|
+
trend_data = {}
|
68
|
+
(start_date..end_date).each do |date|
|
69
|
+
count = where('DATE(created_at) = ?', date).count
|
70
|
+
trend_data[date.strftime('%Y-%m-%d')] = count
|
71
|
+
end
|
72
|
+
trend_data
|
73
|
+
end
|
74
|
+
|
75
|
+
def hourly_healing_distribution
|
76
|
+
# Use Rails to group by hour without raw SQL
|
77
|
+
distribution = {}
|
78
|
+
(0..23).each do |hour|
|
79
|
+
start_time = Time.current.beginning_of_day + hour.hours
|
80
|
+
end_time = start_time + 1.hour
|
81
|
+
count = where(created_at: start_time..end_time).count
|
82
|
+
distribution[hour.to_s.rjust(2, '0')] = count
|
83
|
+
end
|
84
|
+
distribution
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Instance methods
|
89
|
+
def duration_seconds
|
90
|
+
return nil unless total_duration_ms
|
91
|
+
(total_duration_ms / 1000.0).round(2)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ai_processing_seconds
|
95
|
+
return nil unless ai_processing_time_ms
|
96
|
+
(ai_processing_time_ms / 1000.0).round(2)
|
97
|
+
end
|
98
|
+
|
99
|
+
def git_operations_seconds
|
100
|
+
return nil unless git_operations_time_ms
|
101
|
+
(git_operations_time_ms / 1000.0).round(2)
|
102
|
+
end
|
103
|
+
|
104
|
+
def success_status
|
105
|
+
healing_successful ? '✅ Success' : '❌ Failed'
|
106
|
+
end
|
107
|
+
|
108
|
+
def evolution_method_display
|
109
|
+
evolution_method&.titleize || 'Unknown'
|
110
|
+
end
|
111
|
+
|
112
|
+
def ai_provider_display
|
113
|
+
ai_provider&.titleize || 'Unknown'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# CodeHealer Dashboard Routes
|
2
|
+
Rails.application.routes.draw do
|
3
|
+
namespace :code_healer do
|
4
|
+
# Dashboard
|
5
|
+
get '/dashboard', to: 'dashboard#index'
|
6
|
+
get '/dashboard/metrics', to: 'dashboard#metrics'
|
7
|
+
get '/dashboard/trends', to: 'dashboard#trends'
|
8
|
+
get '/dashboard/performance', to: 'dashboard#performance'
|
9
|
+
get '/dashboard/healing/:healing_id', to: 'dashboard#healing_details'
|
10
|
+
|
11
|
+
# API endpoints (JSON only)
|
12
|
+
namespace :api do
|
13
|
+
get '/dashboard/summary', to: 'dashboard#summary'
|
14
|
+
get '/dashboard/metrics', to: 'dashboard#metrics'
|
15
|
+
get '/dashboard/trends', to: 'dashboard#trends'
|
16
|
+
get '/dashboard/performance', to: 'dashboard#performance'
|
17
|
+
get '/dashboard/healing/:healing_id', to: 'dashboard#healing_details'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module CodeHealer
|
2
|
+
class MetricsCollector
|
3
|
+
def self.track_healing_start(healing_id, class_name, method_name, error_class, error_message, file_path)
|
4
|
+
metric = HealingMetric.find_or_initialize_by(healing_id: healing_id)
|
5
|
+
|
6
|
+
metric.assign_attributes(
|
7
|
+
class_name: class_name,
|
8
|
+
method_name: method_name,
|
9
|
+
error_class: error_class,
|
10
|
+
error_message: error_message,
|
11
|
+
file_path: file_path,
|
12
|
+
healing_started_at: Time.current
|
13
|
+
)
|
14
|
+
|
15
|
+
metric.save!
|
16
|
+
metric
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.track_ai_processing(healing_id, evolution_method, ai_provider, ai_response, tokens_used = nil, cost = nil)
|
20
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
21
|
+
return unless metric
|
22
|
+
|
23
|
+
metric.update!(
|
24
|
+
evolution_method: evolution_method,
|
25
|
+
ai_provider: ai_provider,
|
26
|
+
ai_response: ai_response,
|
27
|
+
ai_tokens_used: tokens_used,
|
28
|
+
ai_cost: cost,
|
29
|
+
ai_success: true
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.track_ai_failure(healing_id, evolution_method, ai_provider, failure_reason)
|
34
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
35
|
+
return unless metric
|
36
|
+
|
37
|
+
metric.update!(
|
38
|
+
evolution_method: evolution_method,
|
39
|
+
ai_provider: ai_provider,
|
40
|
+
ai_success: false,
|
41
|
+
failure_reason: failure_reason
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.track_workspace_creation(healing_id, workspace_path)
|
46
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
47
|
+
return unless metric
|
48
|
+
|
49
|
+
metric.update!(workspace_path: workspace_path)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.track_git_operations(healing_id, healing_branch, pull_request_url, pr_created)
|
53
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
54
|
+
return unless metric
|
55
|
+
|
56
|
+
metric.update!(
|
57
|
+
healing_branch: healing_branch,
|
58
|
+
pull_request_url: pull_request_url,
|
59
|
+
pr_created: pr_created
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.track_healing_completion(healing_id, success, tests_passed, syntax_valid, failure_reason = nil)
|
64
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
65
|
+
return unless metric
|
66
|
+
|
67
|
+
# Calculate timing
|
68
|
+
total_duration = if metric.healing_started_at
|
69
|
+
((Time.current - metric.healing_started_at) * 1000).round
|
70
|
+
else
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
metric.update!(
|
75
|
+
healing_completed_at: Time.current,
|
76
|
+
total_duration_ms: total_duration,
|
77
|
+
healing_successful: success,
|
78
|
+
tests_passed: tests_passed,
|
79
|
+
syntax_valid: syntax_valid,
|
80
|
+
failure_reason: failure_reason
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.track_timing(healing_id, ai_processing_time_ms, git_operations_time_ms)
|
85
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
86
|
+
return unless metric
|
87
|
+
|
88
|
+
metric.update!(
|
89
|
+
ai_processing_time_ms: ai_processing_time_ms,
|
90
|
+
git_operations_time_ms: git_operations_time_ms
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.track_business_context(healing_id, business_context, jira_issue_id = nil, confluence_page_id = nil)
|
95
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
96
|
+
return unless metric
|
97
|
+
|
98
|
+
metric.update!(
|
99
|
+
business_context_used: business_context,
|
100
|
+
jira_issue_id: jira_issue_id,
|
101
|
+
confluence_page_id: confluence_page_id
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.track_error_occurrence(healing_id, error_occurred_at)
|
106
|
+
metric = HealingMetric.find_by(healing_id: healing_id)
|
107
|
+
return unless metric
|
108
|
+
|
109
|
+
metric.update!(error_occurred_at: error_occurred_at)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Generate unique healing ID
|
113
|
+
def self.generate_healing_id
|
114
|
+
"healing_#{Time.current.to_i}_#{SecureRandom.hex(8)}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Get dashboard summary
|
118
|
+
def self.dashboard_summary
|
119
|
+
{
|
120
|
+
total_healings: HealingMetric.total_healings,
|
121
|
+
success_rate: HealingMetric.success_rate,
|
122
|
+
healings_today: HealingMetric.healings_today,
|
123
|
+
healings_this_week: HealingMetric.healings_this_week,
|
124
|
+
healings_this_month: HealingMetric.healings_this_month,
|
125
|
+
average_resolution_time: HealingMetric.average_resolution_time,
|
126
|
+
evolution_methods: HealingMetric.evolution_method_distribution,
|
127
|
+
ai_providers: HealingMetric.ai_provider_distribution,
|
128
|
+
top_error_classes: HealingMetric.top_error_classes(5),
|
129
|
+
top_classes_healed: HealingMetric.top_classes_healed(5),
|
130
|
+
daily_trend: HealingMetric.daily_healing_trend(7),
|
131
|
+
hourly_distribution: HealingMetric.hourly_healing_distribution
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/code_healer/version.rb
CHANGED
@@ -0,0 +1,356 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Healing Details - CodeHealer Dashboard</title>
|
7
|
+
<style>
|
8
|
+
* {
|
9
|
+
margin: 0;
|
10
|
+
padding: 0;
|
11
|
+
box-sizing: border-box;
|
12
|
+
}
|
13
|
+
|
14
|
+
body {
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
16
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
17
|
+
min-height: 100vh;
|
18
|
+
color: #333;
|
19
|
+
}
|
20
|
+
|
21
|
+
.container {
|
22
|
+
max-width: 1200px;
|
23
|
+
margin: 0 auto;
|
24
|
+
padding: 20px;
|
25
|
+
}
|
26
|
+
|
27
|
+
.header {
|
28
|
+
text-align: center;
|
29
|
+
margin-bottom: 40px;
|
30
|
+
color: white;
|
31
|
+
}
|
32
|
+
|
33
|
+
.header h1 {
|
34
|
+
font-size: 2.5rem;
|
35
|
+
font-weight: 700;
|
36
|
+
margin-bottom: 10px;
|
37
|
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
38
|
+
}
|
39
|
+
|
40
|
+
.back-link {
|
41
|
+
color: white;
|
42
|
+
text-decoration: none;
|
43
|
+
font-size: 1.1rem;
|
44
|
+
opacity: 0.9;
|
45
|
+
}
|
46
|
+
|
47
|
+
.back-link:hover {
|
48
|
+
opacity: 1;
|
49
|
+
}
|
50
|
+
|
51
|
+
.healing-card {
|
52
|
+
background: white;
|
53
|
+
border-radius: 16px;
|
54
|
+
padding: 30px;
|
55
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
56
|
+
margin-bottom: 30px;
|
57
|
+
}
|
58
|
+
|
59
|
+
.card-title {
|
60
|
+
font-size: 1.5rem;
|
61
|
+
font-weight: 600;
|
62
|
+
margin-bottom: 20px;
|
63
|
+
color: #333;
|
64
|
+
border-bottom: 2px solid #667eea;
|
65
|
+
padding-bottom: 10px;
|
66
|
+
}
|
67
|
+
|
68
|
+
.info-grid {
|
69
|
+
display: grid;
|
70
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
71
|
+
gap: 20px;
|
72
|
+
margin-bottom: 30px;
|
73
|
+
}
|
74
|
+
|
75
|
+
.info-section {
|
76
|
+
background: #f8f9fa;
|
77
|
+
padding: 20px;
|
78
|
+
border-radius: 12px;
|
79
|
+
border-left: 4px solid #667eea;
|
80
|
+
}
|
81
|
+
|
82
|
+
.info-section h3 {
|
83
|
+
font-size: 1.1rem;
|
84
|
+
font-weight: 600;
|
85
|
+
margin-bottom: 15px;
|
86
|
+
color: #333;
|
87
|
+
}
|
88
|
+
|
89
|
+
.info-item {
|
90
|
+
display: flex;
|
91
|
+
justify-content: space-between;
|
92
|
+
margin-bottom: 10px;
|
93
|
+
padding: 8px 0;
|
94
|
+
border-bottom: 1px solid #e9ecef;
|
95
|
+
}
|
96
|
+
|
97
|
+
.info-item:last-child {
|
98
|
+
border-bottom: none;
|
99
|
+
}
|
100
|
+
|
101
|
+
.info-label {
|
102
|
+
font-weight: 500;
|
103
|
+
color: #666;
|
104
|
+
}
|
105
|
+
|
106
|
+
.info-value {
|
107
|
+
font-weight: 600;
|
108
|
+
color: #333;
|
109
|
+
}
|
110
|
+
|
111
|
+
.status-badge {
|
112
|
+
padding: 6px 12px;
|
113
|
+
border-radius: 20px;
|
114
|
+
font-size: 0.8rem;
|
115
|
+
font-weight: 500;
|
116
|
+
text-transform: uppercase;
|
117
|
+
}
|
118
|
+
|
119
|
+
.status-success {
|
120
|
+
background-color: #d4edda;
|
121
|
+
color: #155724;
|
122
|
+
}
|
123
|
+
|
124
|
+
.status-failed {
|
125
|
+
background-color: #f8d7da;
|
126
|
+
color: #721c24;
|
127
|
+
}
|
128
|
+
|
129
|
+
.timing-chart {
|
130
|
+
background: #f8f9fa;
|
131
|
+
padding: 20px;
|
132
|
+
border-radius: 12px;
|
133
|
+
margin-top: 20px;
|
134
|
+
}
|
135
|
+
|
136
|
+
.timing-bar {
|
137
|
+
display: flex;
|
138
|
+
align-items: center;
|
139
|
+
margin-bottom: 15px;
|
140
|
+
}
|
141
|
+
|
142
|
+
.timing-label {
|
143
|
+
width: 120px;
|
144
|
+
font-weight: 500;
|
145
|
+
color: #666;
|
146
|
+
}
|
147
|
+
|
148
|
+
.timing-progress {
|
149
|
+
flex: 1;
|
150
|
+
height: 20px;
|
151
|
+
background: #e9ecef;
|
152
|
+
border-radius: 10px;
|
153
|
+
overflow: hidden;
|
154
|
+
margin: 0 15px;
|
155
|
+
}
|
156
|
+
|
157
|
+
.timing-fill {
|
158
|
+
height: 100%;
|
159
|
+
background: linear-gradient(90deg, #667eea, #764ba2);
|
160
|
+
transition: width 0.3s ease;
|
161
|
+
}
|
162
|
+
|
163
|
+
.timing-value {
|
164
|
+
width: 80px;
|
165
|
+
text-align: right;
|
166
|
+
font-weight: 600;
|
167
|
+
color: #333;
|
168
|
+
}
|
169
|
+
|
170
|
+
.error-details {
|
171
|
+
background: #fff5f5;
|
172
|
+
border: 1px solid #fed7d7;
|
173
|
+
border-radius: 12px;
|
174
|
+
padding: 20px;
|
175
|
+
margin-top: 20px;
|
176
|
+
}
|
177
|
+
|
178
|
+
.error-details h3 {
|
179
|
+
color: #c53030;
|
180
|
+
margin-bottom: 15px;
|
181
|
+
}
|
182
|
+
|
183
|
+
.code-block {
|
184
|
+
background: #2d3748;
|
185
|
+
color: #e2e8f0;
|
186
|
+
padding: 15px;
|
187
|
+
border-radius: 8px;
|
188
|
+
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
189
|
+
font-size: 0.9rem;
|
190
|
+
overflow-x: auto;
|
191
|
+
margin-top: 10px;
|
192
|
+
}
|
193
|
+
|
194
|
+
@media (max-width: 768px) {
|
195
|
+
.info-grid {
|
196
|
+
grid-template-columns: 1fr;
|
197
|
+
}
|
198
|
+
|
199
|
+
.header h1 {
|
200
|
+
font-size: 2rem;
|
201
|
+
}
|
202
|
+
}
|
203
|
+
</style>
|
204
|
+
</head>
|
205
|
+
<body>
|
206
|
+
<div class="container">
|
207
|
+
<div class="header">
|
208
|
+
<h1>🏥 Healing Details</h1>
|
209
|
+
<a href="/code_healer/dashboard" class="back-link">← Back to Dashboard</a>
|
210
|
+
</div>
|
211
|
+
|
212
|
+
<% if @healing %>
|
213
|
+
<div class="healing-card">
|
214
|
+
<div class="card-title">Healing Information</div>
|
215
|
+
|
216
|
+
<div class="info-grid">
|
217
|
+
<div class="info-section">
|
218
|
+
<h3>Basic Details</h3>
|
219
|
+
<div class="info-item">
|
220
|
+
<span class="info-label">Healing ID:</span>
|
221
|
+
<span class="info-value"><%= @healing.healing_id %></span>
|
222
|
+
</div>
|
223
|
+
<div class="info-item">
|
224
|
+
<span class="info-label">Class:</span>
|
225
|
+
<span class="info-value"><%= @healing.class_name %></span>
|
226
|
+
</div>
|
227
|
+
<div class="info-item">
|
228
|
+
<span class="info-label">Method:</span>
|
229
|
+
<span class="info-value"><%= @healing.method_name %></span>
|
230
|
+
</div>
|
231
|
+
<div class="info-item">
|
232
|
+
<span class="info-label">Status:</span>
|
233
|
+
<span class="status-badge status-<%= @healing.healing_successful ? 'success' : 'failed' %>">
|
234
|
+
<%= @healing.healing_successful ? '✅ Success' : '❌ Failed' %>
|
235
|
+
</span>
|
236
|
+
</div>
|
237
|
+
</div>
|
238
|
+
|
239
|
+
<div class="info-section">
|
240
|
+
<h3>AI & Evolution</h3>
|
241
|
+
<div class="info-item">
|
242
|
+
<span class="info-label">Evolution Method:</span>
|
243
|
+
<span class="info-value"><%= @healing.evolution_method_display %></span>
|
244
|
+
</div>
|
245
|
+
<div class="info-item">
|
246
|
+
<span class="info-label">AI Provider:</span>
|
247
|
+
<span class="info-value"><%= @healing.ai_provider_display %></span>
|
248
|
+
</div>
|
249
|
+
<div class="info-item">
|
250
|
+
<span class="info-label">AI Success:</span>
|
251
|
+
<span class="info-value"><%= @healing.ai_success ? '✅ Yes' : '❌ No' %></span>
|
252
|
+
</div>
|
253
|
+
<div class="info-item">
|
254
|
+
<span class="info-label">Tests Passed:</span>
|
255
|
+
<span class="info-value"><%= @healing.tests_passed ? '✅ Yes' : '❌ No' %></span>
|
256
|
+
</div>
|
257
|
+
</div>
|
258
|
+
|
259
|
+
<div class="info-section">
|
260
|
+
<h3>Timing & Performance</h3>
|
261
|
+
<div class="info-item">
|
262
|
+
<span class="info-label">Started:</span>
|
263
|
+
<span class="info-value"><%= @healing.healing_started_at&.strftime("%Y-%m-%d %H:%M:%S") || 'N/A' %></span>
|
264
|
+
</div>
|
265
|
+
<div class="info-item">
|
266
|
+
<span class="info-label">Completed:</span>
|
267
|
+
<span class="info-value"><%= @healing.healing_completed_at&.strftime("%Y-%m-%d %H:%M:%S") || 'N/A' %></span>
|
268
|
+
</div>
|
269
|
+
<div class="info-item">
|
270
|
+
<span class="info-label">Total Duration:</span>
|
271
|
+
<span class="info-value"><%= @healing.duration_seconds ? "#{@healing.duration_seconds}s" : 'N/A' %></span>
|
272
|
+
</div>
|
273
|
+
<div class="info-item">
|
274
|
+
<span class="info-label">Syntax Valid:</span>
|
275
|
+
<span class="info-value"><%= @healing.syntax_valid ? '✅ Yes' : '❌ No' %></span>
|
276
|
+
</div>
|
277
|
+
</div>
|
278
|
+
</div>
|
279
|
+
|
280
|
+
<div class="timing-chart">
|
281
|
+
<h3>Processing Breakdown</h3>
|
282
|
+
<div class="timing-bar">
|
283
|
+
<span class="timing-label">AI Processing:</span>
|
284
|
+
<div class="timing-progress">
|
285
|
+
<div class="timing-fill" style="width: <%= @healing.ai_processing_seconds ? (@healing.ai_processing_seconds / @healing.duration_seconds.to_f * 100).round(1) : 0 %>%"></div>
|
286
|
+
</div>
|
287
|
+
<span class="timing-value"><%= @healing.ai_processing_seconds ? "#{@healing.ai_processing_seconds}s" : 'N/A' %></span>
|
288
|
+
</div>
|
289
|
+
<div class="timing-bar">
|
290
|
+
<span class="timing-label">Git Operations:</span>
|
291
|
+
<div class="timing-progress">
|
292
|
+
<div class="timing-fill" style="width: <%= @healing.git_operations_seconds ? (@healing.git_operations_seconds / @healing.duration_seconds.to_f * 100).round(1) : 0 %>%"></div>
|
293
|
+
</div>
|
294
|
+
<span class="timing-value"><%= @healing.git_operations_seconds ? "#{@healing.git_operations_seconds}s" : 'N/A' %></span>
|
295
|
+
</div>
|
296
|
+
</div>
|
297
|
+
|
298
|
+
<% if @healing.error_class.present? %>
|
299
|
+
<div class="error-details">
|
300
|
+
<h3>Error Information</h3>
|
301
|
+
<div class="info-item">
|
302
|
+
<span class="info-label">Error Class:</span>
|
303
|
+
<span class="info-value"><%= @healing.error_class %></span>
|
304
|
+
</div>
|
305
|
+
<div class="info-item">
|
306
|
+
<span class="info-label">Error Message:</span>
|
307
|
+
<span class="info-value"><%= @healing.error_message %></span>
|
308
|
+
</div>
|
309
|
+
<div class="info-item">
|
310
|
+
<span class="info-label">File Path:</span>
|
311
|
+
<span class="info-value"><%= @healing.file_path %></span>
|
312
|
+
</div>
|
313
|
+
<% if @healing.failure_reason.present? %>
|
314
|
+
<div class="info-item">
|
315
|
+
<span class="info-label">Failure Reason:</span>
|
316
|
+
<span class="info-value"><%= @healing.failure_reason %></span>
|
317
|
+
</div>
|
318
|
+
<% end %>
|
319
|
+
</div>
|
320
|
+
<% end %>
|
321
|
+
|
322
|
+
<% if @healing.healing_branch.present? %>
|
323
|
+
<div class="info-section">
|
324
|
+
<h3>Git Operations</h3>
|
325
|
+
<div class="info-item">
|
326
|
+
<span class="info-label">Healing Branch:</span>
|
327
|
+
<span class="info-value"><%= @healing.healing_branch %></span>
|
328
|
+
</div>
|
329
|
+
<% if @healing.pull_request_url.present? %>
|
330
|
+
<div class="info-item">
|
331
|
+
<span class="info-label">Pull Request:</span>
|
332
|
+
<span class="info-value">
|
333
|
+
<a href="<%= @healing.pull_request_url %>" target="_blank" style="color: #667eea; text-decoration: none;">
|
334
|
+
🔗 View PR
|
335
|
+
</a>
|
336
|
+
</span>
|
337
|
+
</div>
|
338
|
+
<% end %>
|
339
|
+
<div class="info-item">
|
340
|
+
<span class="info-label">PR Created:</span>
|
341
|
+
<span class="info-value"><%= @healing.pr_created ? '✅ Yes' : '❌ No' %></span>
|
342
|
+
</div>
|
343
|
+
</div>
|
344
|
+
<% end %>
|
345
|
+
</div>
|
346
|
+
<% else %>
|
347
|
+
<div class="healing-card">
|
348
|
+
<div class="error-details">
|
349
|
+
<h3>Healing Not Found</h3>
|
350
|
+
<p>The requested healing operation could not be found.</p>
|
351
|
+
</div>
|
352
|
+
</div>
|
353
|
+
<% end %>
|
354
|
+
</div>
|
355
|
+
</body>
|
356
|
+
</html>
|