lsa_tdx_feedback 1.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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +328 -0
- data/INSTALLATION.md +244 -0
- data/LICENSE.txt +21 -0
- data/README.md +391 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/lsa_tdx_feedback.js +231 -0
- data/app/assets/stylesheets/lsa_tdx_feedback.css +279 -0
- data/app/controllers/lsa_tdx_feedback/feedback_controller.rb +71 -0
- data/app/views/lsa_tdx_feedback/shared/_feedback_modal.html.erb +95 -0
- data/config/routes.rb +3 -0
- data/example_application_tests.rb +0 -0
- data/lib/lsa_tdx_feedback/application_controller_extensions.rb +52 -0
- data/lib/lsa_tdx_feedback/configuration.rb +89 -0
- data/lib/lsa_tdx_feedback/engine.rb +65 -0
- data/lib/lsa_tdx_feedback/oauth_client.rb +81 -0
- data/lib/lsa_tdx_feedback/ticket_client.rb +106 -0
- data/lib/lsa_tdx_feedback/version.rb +3 -0
- data/lib/lsa_tdx_feedback/view_helpers.rb +37 -0
- data/lib/lsa_tdx_feedback.rb +31 -0
- metadata +223 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/* LsaTdxFeedback Styles - Self-contained and namespaced */
|
|
2
|
+
|
|
3
|
+
/* Trigger Button */
|
|
4
|
+
.lsa-tdx-feedback-trigger-btn {
|
|
5
|
+
position: fixed;
|
|
6
|
+
bottom: 20px;
|
|
7
|
+
right: 20px;
|
|
8
|
+
background: #007bff;
|
|
9
|
+
color: white;
|
|
10
|
+
border: none;
|
|
11
|
+
border-radius: 25px;
|
|
12
|
+
padding: 12px 20px;
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
font-weight: 600;
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
|
18
|
+
transition: all 0.2s ease;
|
|
19
|
+
z-index: 9998;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: 8px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.lsa-tdx-feedback-trigger-btn:hover {
|
|
26
|
+
background: #0056b3;
|
|
27
|
+
transform: translateY(-2px);
|
|
28
|
+
box-shadow: 0 6px 20px rgba(0, 123, 255, 0.4);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.lsa-tdx-feedback-trigger-btn:active {
|
|
32
|
+
transform: translateY(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.lsa-tdx-feedback-trigger-text {
|
|
36
|
+
display: inline;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (max-width: 768px) {
|
|
40
|
+
.lsa-tdx-feedback-trigger-btn {
|
|
41
|
+
bottom: 15px;
|
|
42
|
+
right: 15px;
|
|
43
|
+
padding: 10px 16px;
|
|
44
|
+
font-size: 13px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.lsa-tdx-feedback-trigger-text {
|
|
48
|
+
display: none;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Modal Styles */
|
|
53
|
+
.lsa-tdx-feedback-modal {
|
|
54
|
+
position: fixed;
|
|
55
|
+
top: 0;
|
|
56
|
+
left: 0;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
z-index: 9999;
|
|
60
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.lsa-tdx-feedback-modal-backdrop {
|
|
64
|
+
position: absolute;
|
|
65
|
+
top: 0;
|
|
66
|
+
left: 0;
|
|
67
|
+
width: 100%;
|
|
68
|
+
height: 100%;
|
|
69
|
+
background: rgba(0, 0, 0, 0.5);
|
|
70
|
+
backdrop-filter: blur(4px);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.lsa-tdx-feedback-modal-content {
|
|
74
|
+
position: relative;
|
|
75
|
+
background: white;
|
|
76
|
+
border-radius: 12px;
|
|
77
|
+
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
|
|
78
|
+
max-width: 500px;
|
|
79
|
+
width: 90%;
|
|
80
|
+
max-height: 90vh;
|
|
81
|
+
overflow-y: auto;
|
|
82
|
+
margin: 5vh auto;
|
|
83
|
+
animation: lsa-tdx-feedback-modal-enter 0.3s ease-out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keyframes lsa-tdx-feedback-modal-enter {
|
|
87
|
+
from {
|
|
88
|
+
opacity: 0;
|
|
89
|
+
transform: scale(0.9) translateY(20px);
|
|
90
|
+
}
|
|
91
|
+
to {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
transform: scale(1) translateY(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.lsa-tdx-feedback-modal-header {
|
|
98
|
+
padding: 24px 24px 16px;
|
|
99
|
+
border-bottom: 1px solid #e9ecef;
|
|
100
|
+
display: flex;
|
|
101
|
+
justify-content: space-between;
|
|
102
|
+
align-items: center;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.lsa-tdx-feedback-modal-header h2 {
|
|
106
|
+
margin: 0;
|
|
107
|
+
font-size: 20px;
|
|
108
|
+
font-weight: 600;
|
|
109
|
+
color: #212529;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.lsa-tdx-feedback-close-btn {
|
|
113
|
+
background: none;
|
|
114
|
+
border: none;
|
|
115
|
+
font-size: 24px;
|
|
116
|
+
color: #6c757d;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
padding: 0;
|
|
119
|
+
width: 32px;
|
|
120
|
+
height: 32px;
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: center;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
border-radius: 6px;
|
|
125
|
+
transition: all 0.2s ease;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.lsa-tdx-feedback-close-btn:hover {
|
|
129
|
+
background: #f8f9fa;
|
|
130
|
+
color: #495057;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.lsa-tdx-feedback-modal-body {
|
|
134
|
+
padding: 24px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.lsa-tdx-feedback-form-group {
|
|
138
|
+
margin-bottom: 20px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.lsa-tdx-feedback-form-group label {
|
|
142
|
+
display: block;
|
|
143
|
+
margin-bottom: 6px;
|
|
144
|
+
font-weight: 500;
|
|
145
|
+
color: #495057;
|
|
146
|
+
font-size: 14px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.lsa-tdx-feedback-form-group input,
|
|
150
|
+
.lsa-tdx-feedback-form-group select,
|
|
151
|
+
.lsa-tdx-feedback-form-group textarea {
|
|
152
|
+
width: 100%;
|
|
153
|
+
padding: 10px 12px;
|
|
154
|
+
border: 1px solid #ced4da;
|
|
155
|
+
border-radius: 6px;
|
|
156
|
+
font-size: 14px;
|
|
157
|
+
font-family: inherit;
|
|
158
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
159
|
+
box-sizing: border-box;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.lsa-tdx-feedback-form-group input:focus,
|
|
163
|
+
.lsa-tdx-feedback-form-group select:focus,
|
|
164
|
+
.lsa-tdx-feedback-form-group textarea:focus {
|
|
165
|
+
outline: none;
|
|
166
|
+
border-color: #007bff;
|
|
167
|
+
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.lsa-tdx-feedback-form-group textarea {
|
|
171
|
+
resize: vertical;
|
|
172
|
+
min-height: 80px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.lsa-tdx-feedback-help-text {
|
|
176
|
+
display: block;
|
|
177
|
+
margin-top: 4px;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
color: #6c757d;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.lsa-tdx-feedback-modal-footer {
|
|
183
|
+
padding: 16px 24px 24px;
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: 12px;
|
|
186
|
+
justify-content: flex-end;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.lsa-tdx-feedback-btn {
|
|
190
|
+
padding: 10px 20px;
|
|
191
|
+
border-radius: 6px;
|
|
192
|
+
font-size: 14px;
|
|
193
|
+
font-weight: 500;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
transition: all 0.2s ease;
|
|
196
|
+
border: 1px solid transparent;
|
|
197
|
+
display: inline-flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 6px;
|
|
200
|
+
font-family: inherit;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.lsa-tdx-feedback-btn-primary {
|
|
204
|
+
background: #007bff;
|
|
205
|
+
color: white;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.lsa-tdx-feedback-btn-primary:hover:not(:disabled) {
|
|
209
|
+
background: #0056b3;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.lsa-tdx-feedback-btn-primary:disabled {
|
|
213
|
+
background: #6c757d;
|
|
214
|
+
cursor: not-allowed;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.lsa-tdx-feedback-btn-secondary {
|
|
218
|
+
background: white;
|
|
219
|
+
color: #6c757d;
|
|
220
|
+
border-color: #ced4da;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.lsa-tdx-feedback-btn-secondary:hover {
|
|
224
|
+
background: #f8f9fa;
|
|
225
|
+
color: #495057;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.lsa-tdx-feedback-loading-spinner {
|
|
229
|
+
animation: lsa-tdx-feedback-spin 1s linear infinite;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@keyframes lsa-tdx-feedback-spin {
|
|
233
|
+
from { transform: rotate(0deg); }
|
|
234
|
+
to { transform: rotate(360deg); }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/* Messages */
|
|
238
|
+
.lsa-tdx-feedback-message {
|
|
239
|
+
margin: 16px 24px 0;
|
|
240
|
+
padding: 12px 16px;
|
|
241
|
+
border-radius: 6px;
|
|
242
|
+
font-size: 14px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.lsa-tdx-feedback-message.success {
|
|
246
|
+
background: #d4edda;
|
|
247
|
+
color: #155724;
|
|
248
|
+
border: 1px solid #c3e6cb;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.lsa-tdx-feedback-message.error {
|
|
252
|
+
background: #f8d7da;
|
|
253
|
+
color: #721c24;
|
|
254
|
+
border: 1px solid #f5c6cb;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Mobile Responsive */
|
|
258
|
+
@media (max-width: 768px) {
|
|
259
|
+
.lsa-tdx-feedback-modal-content {
|
|
260
|
+
margin: 2vh auto;
|
|
261
|
+
max-height: 96vh;
|
|
262
|
+
width: 95%;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.lsa-tdx-feedback-modal-header,
|
|
266
|
+
.lsa-tdx-feedback-modal-body {
|
|
267
|
+
padding: 20px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.lsa-tdx-feedback-modal-footer {
|
|
271
|
+
padding: 12px 20px 20px;
|
|
272
|
+
flex-direction: column;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.lsa-tdx-feedback-btn {
|
|
276
|
+
width: 100%;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module LsaTdxFeedback
|
|
2
|
+
class FeedbackController < ::ApplicationController
|
|
3
|
+
protect_from_forgery with: :exception
|
|
4
|
+
before_action :set_lsa_tdx_feedback_data
|
|
5
|
+
|
|
6
|
+
def create
|
|
7
|
+
ticket_client = TicketClient.new
|
|
8
|
+
|
|
9
|
+
# Build the title and include the app name if available
|
|
10
|
+
title = feedback_params[:title].presence || "User Feedback - #{feedback_params[:category]}"
|
|
11
|
+
|
|
12
|
+
if defined?(@lsa_tdx_feedback_app_name) && @lsa_tdx_feedback_app_name.present?
|
|
13
|
+
title = "[#{@lsa_tdx_feedback_app_name}]: #{title}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
feedback_data = {
|
|
17
|
+
title: title,
|
|
18
|
+
feedback: feedback_params[:feedback],
|
|
19
|
+
category: feedback_params[:category],
|
|
20
|
+
email: feedback_params[:email],
|
|
21
|
+
url: feedback_params[:url],
|
|
22
|
+
user_agent: feedback_params[:user_agent],
|
|
23
|
+
additional_info: feedback_params[:additional_info],
|
|
24
|
+
priority_id: priority_from_category(feedback_params[:category])
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
begin
|
|
28
|
+
ticket_response = ticket_client.create_feedback_ticket(feedback_data)
|
|
29
|
+
|
|
30
|
+
render json: {
|
|
31
|
+
success: true,
|
|
32
|
+
message: 'Thank you for your feedback! Your ticket has been created.',
|
|
33
|
+
ticket_id: ticket_response['ID']
|
|
34
|
+
}, status: :created
|
|
35
|
+
rescue LsaTdxFeedback::Error => e
|
|
36
|
+
Rails.logger.error "LsaTdxFeedback Error: #{e.message}"
|
|
37
|
+
|
|
38
|
+
render json: {
|
|
39
|
+
success: false,
|
|
40
|
+
message: 'Sorry, there was an error submitting your feedback. Please try again later.'
|
|
41
|
+
}, status: :unprocessable_entity
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
Rails.logger.error "Unexpected error in LsaTdxFeedback: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
44
|
+
|
|
45
|
+
render json: {
|
|
46
|
+
success: false,
|
|
47
|
+
message: 'Sorry, there was an unexpected error. Please try again later.'
|
|
48
|
+
}, status: :internal_server_error
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def feedback_params
|
|
55
|
+
params.require(:feedback).permit(:title, :feedback, :category, :email, :url, :user_agent, :additional_info)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def priority_from_category(category)
|
|
59
|
+
case category&.downcase
|
|
60
|
+
when 'bug', 'error', 'broken'
|
|
61
|
+
21 # High priority
|
|
62
|
+
when 'urgent', 'critical'
|
|
63
|
+
22 # Emergency priority
|
|
64
|
+
when 'suggestion', 'enhancement', 'feature'
|
|
65
|
+
19 # Low priority
|
|
66
|
+
else
|
|
67
|
+
20 # Medium priority (default)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<!-- LsaTdxFeedback Modal -->
|
|
2
|
+
<div id="lsa-tdx-feedback-modal" class="lsa-tdx-feedback-modal" style="display: none;">
|
|
3
|
+
<div class="lsa-tdx-feedback-modal-backdrop"></div>
|
|
4
|
+
<div class="lsa-tdx-feedback-modal-content">
|
|
5
|
+
<div class="lsa-tdx-feedback-modal-header">
|
|
6
|
+
<h2>
|
|
7
|
+
<%= "[#{@lsa_tdx_feedback_app_name}]: " if defined?(@lsa_tdx_feedback_app_name) && @lsa_tdx_feedback_app_name.present? %>Send Feedback
|
|
8
|
+
</h2>
|
|
9
|
+
<button type="button" class="lsa-tdx-feedback-close-btn" aria-label="Close">×</button>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div class="lsa-tdx-feedback-modal-body">
|
|
13
|
+
<form id="lsa-tdx-feedback-form">
|
|
14
|
+
<div class="lsa-tdx-feedback-form-group">
|
|
15
|
+
<label for="lsa-tdx-feedback-category">Category</label>
|
|
16
|
+
<select id="lsa-tdx-feedback-category" name="category" required>
|
|
17
|
+
<option value="">Select a category</option>
|
|
18
|
+
<option value="bug">Bug Report</option>
|
|
19
|
+
<option value="suggestion">Suggestion</option>
|
|
20
|
+
<option value="feature">Feature Request</option>
|
|
21
|
+
<option value="general">General Feedback</option>
|
|
22
|
+
<option value="other">Other</option>
|
|
23
|
+
</select>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="lsa-tdx-feedback-form-group">
|
|
27
|
+
<label for="lsa-tdx-feedback-feedback">Your Feedback *</label>
|
|
28
|
+
<textarea
|
|
29
|
+
id="lsa-tdx-feedback-feedback"
|
|
30
|
+
name="feedback"
|
|
31
|
+
required
|
|
32
|
+
rows="5"
|
|
33
|
+
placeholder="Please describe your feedback in detail..."
|
|
34
|
+
></textarea>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="lsa-tdx-feedback-form-group">
|
|
38
|
+
<label for="lsa-tdx-feedback-email">Your Email *</label>
|
|
39
|
+
<input
|
|
40
|
+
type="email"
|
|
41
|
+
id="lsa-tdx-feedback-email"
|
|
42
|
+
name="email"
|
|
43
|
+
placeholder="your.email@example.com"
|
|
44
|
+
required
|
|
45
|
+
value="<%= @lsa_tdx_feedback_user_email if defined?(@lsa_tdx_feedback_user_email) && @lsa_tdx_feedback_user_email.present? %>"
|
|
46
|
+
>
|
|
47
|
+
<small class="lsa-tdx-feedback-help-text">Required - We'll use this to follow up on your feedback</small>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="lsa-tdx-feedback-form-group">
|
|
51
|
+
<label for="lsa-tdx-feedback-additional-info">Additional Information</label>
|
|
52
|
+
<textarea
|
|
53
|
+
id="lsa-tdx-feedback-additional-info"
|
|
54
|
+
name="additional_info"
|
|
55
|
+
rows="3"
|
|
56
|
+
placeholder="Any additional context or information..."
|
|
57
|
+
></textarea>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<!-- Hidden fields for context -->
|
|
61
|
+
<input type="hidden" name="url" value="<%= @lsa_tdx_feedback_current_url if defined?(@lsa_tdx_feedback_current_url) %>">
|
|
62
|
+
<input type="hidden" name="user_agent" value="<%= @lsa_tdx_feedback_user_agent if defined?(@lsa_tdx_feedback_user_agent) %>">
|
|
63
|
+
</form>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="lsa-tdx-feedback-modal-footer">
|
|
67
|
+
<button type="button" class="lsa-tdx-feedback-btn lsa-tdx-feedback-btn-secondary lsa-tdx-feedback-cancel-btn">
|
|
68
|
+
Cancel
|
|
69
|
+
</button>
|
|
70
|
+
<button type="submit" form="lsa-tdx-feedback-form" class="lsa-tdx-feedback-btn lsa-tdx-feedback-btn-primary lsa-tdx-feedback-submit-btn">
|
|
71
|
+
<span class="lsa-tdx-feedback-btn-text">Send Feedback</span>
|
|
72
|
+
<span class="lsa-tdx-feedback-loading-spinner" style="display: none;">
|
|
73
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
|
|
74
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" opacity="0.25"/>
|
|
75
|
+
<path d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" fill="currentColor"/>
|
|
76
|
+
</svg>
|
|
77
|
+
Sending...
|
|
78
|
+
</span>
|
|
79
|
+
</button>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Success/Error Messages -->
|
|
83
|
+
<div id="lsa-tdx-feedback-message" class="lsa-tdx-feedback-message" style="display: none;">
|
|
84
|
+
<div class="lsa-tdx-feedback-message-content"></div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<!-- Feedback Button -->
|
|
90
|
+
<button id="lsa-tdx-feedback-trigger" class="lsa-tdx-feedback-trigger-btn" title="Send Feedback">
|
|
91
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
|
|
92
|
+
<path d="M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z"/>
|
|
93
|
+
</svg>
|
|
94
|
+
<span class="lsa-tdx-feedback-trigger-text">Feedback</span>
|
|
95
|
+
</button>
|
data/config/routes.rb
ADDED
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module LsaTdxFeedback
|
|
2
|
+
module ApplicationControllerExtensions
|
|
3
|
+
if defined?(ActiveSupport::Concern)
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do |base|
|
|
7
|
+
base.before_action :set_lsa_tdx_feedback_data
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def set_lsa_tdx_feedback_data
|
|
14
|
+
@lsa_tdx_feedback_current_url = request.original_url
|
|
15
|
+
@lsa_tdx_feedback_user_agent = request.user_agent
|
|
16
|
+
@lsa_tdx_feedback_app_name = begin
|
|
17
|
+
# Prefer Rails 6+ API, fallback for older Rails
|
|
18
|
+
if Rails.application.class.respond_to?(:module_parent_name)
|
|
19
|
+
Rails.application.class.module_parent_name
|
|
20
|
+
else
|
|
21
|
+
Rails.application.class.parent_name
|
|
22
|
+
end
|
|
23
|
+
rescue
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Logging
|
|
28
|
+
Rails.logger.info "set_lsa_tdx_feedback_data called"
|
|
29
|
+
Rails.logger.info " respond_to? current_user_email_for_feedback: #{respond_to?(:current_user_email_for_feedback, true)}"
|
|
30
|
+
Rails.logger.info " current_user available: #{respond_to?(:current_user, true)}"
|
|
31
|
+
Rails.logger.info " current_user: #{current_user&.email || 'nil'}"
|
|
32
|
+
|
|
33
|
+
if respond_to?(:current_user_email_for_feedback, true)
|
|
34
|
+
@lsa_tdx_feedback_user_email = current_user_email_for_feedback
|
|
35
|
+
Rails.logger.info " @lsa_tdx_feedback_user_email set to: #{@lsa_tdx_feedback_user_email}"
|
|
36
|
+
else
|
|
37
|
+
Rails.logger.info " current_user_email_for_feedback method not available"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Override this method in your ApplicationController to provide user email
|
|
42
|
+
def current_user_email_for_feedback
|
|
43
|
+
if defined?(current_user) && current_user.respond_to?(:email)
|
|
44
|
+
current_user.email
|
|
45
|
+
elsif defined?(current_user) && current_user.respond_to?(:email_address)
|
|
46
|
+
current_user.email_address
|
|
47
|
+
else
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module LsaTdxFeedback
|
|
2
|
+
class Configuration
|
|
3
|
+
attr_accessor :client_id, :client_secret, :oauth_url, :api_base_url, :app_id,
|
|
4
|
+
:default_type_id, :default_form_id, :default_classification,
|
|
5
|
+
:default_status_id, :default_priority_id, :default_responsible_group_id,
|
|
6
|
+
:default_service_id, :default_source_id, :service_offering_id, :account_id,
|
|
7
|
+
:cache_store, :cache_expiry
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
# All values must be configured by the application for security
|
|
11
|
+
@oauth_url = nil
|
|
12
|
+
@api_base_url = nil
|
|
13
|
+
@client_id = nil
|
|
14
|
+
@client_secret = nil
|
|
15
|
+
@app_id = nil
|
|
16
|
+
@default_type_id = nil
|
|
17
|
+
@default_form_id = nil
|
|
18
|
+
@default_classification = nil
|
|
19
|
+
@default_status_id = nil
|
|
20
|
+
@default_priority_id = nil
|
|
21
|
+
@default_source_id = nil
|
|
22
|
+
@default_responsible_group_id = nil
|
|
23
|
+
@default_service_id = nil
|
|
24
|
+
@service_offering_id = nil
|
|
25
|
+
@account_id = nil
|
|
26
|
+
|
|
27
|
+
# Cache configuration
|
|
28
|
+
@cache_store = :redis_cache_store
|
|
29
|
+
@cache_expiry = 3600 # 1 hour for OAuth tokens
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def oauth_scope
|
|
33
|
+
'https://gw-test.api.it.umich.edu/um/it tdxticket'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def grant_type
|
|
37
|
+
'client_credentials'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def valid?
|
|
41
|
+
!oauth_url.nil? && !oauth_url.empty? &&
|
|
42
|
+
!api_base_url.nil? && !api_base_url.empty? &&
|
|
43
|
+
!client_id.nil? && !client_id.empty? &&
|
|
44
|
+
!client_secret.nil? && !client_secret.empty? &&
|
|
45
|
+
!app_id.nil? &&
|
|
46
|
+
!default_type_id.nil? &&
|
|
47
|
+
!default_form_id.nil? &&
|
|
48
|
+
!default_classification.nil? && (default_classification.is_a?(String) ? !default_classification.empty? : true) &&
|
|
49
|
+
!default_status_id.nil? &&
|
|
50
|
+
!default_priority_id.nil? &&
|
|
51
|
+
!default_source_id.nil? &&
|
|
52
|
+
!default_responsible_group_id.nil? &&
|
|
53
|
+
!default_service_id.nil? &&
|
|
54
|
+
!service_offering_id.nil? &&
|
|
55
|
+
!account_id.nil?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def validate!
|
|
59
|
+
raise Error, 'oauth_url is required' unless oauth_url && !oauth_url.empty?
|
|
60
|
+
raise Error, 'api_base_url is required' unless api_base_url && !api_base_url.empty?
|
|
61
|
+
raise Error, 'client_id is required' unless client_id && !client_id.empty?
|
|
62
|
+
raise Error, 'client_secret is required' unless client_secret && !client_secret.empty?
|
|
63
|
+
raise Error, 'app_id is required' unless app_id
|
|
64
|
+
raise Error, 'default_type_id is required' unless default_type_id
|
|
65
|
+
raise Error, 'default_form_id is required' unless default_form_id
|
|
66
|
+
raise Error, 'default_classification is required' unless default_classification && (default_classification.is_a?(String) ? !default_classification.empty? : true)
|
|
67
|
+
raise Error, 'default_status_id is required' unless default_status_id
|
|
68
|
+
raise Error, 'default_priority_id is required' unless default_priority_id
|
|
69
|
+
raise Error, 'default_source_id is required' unless default_source_id
|
|
70
|
+
raise Error, 'default_responsible_group_id is required' unless default_responsible_group_id
|
|
71
|
+
raise Error, 'default_service_id is required' unless default_service_id
|
|
72
|
+
raise Error, 'service_offering_id is required' unless service_offering_id
|
|
73
|
+
raise Error, 'account_id is required' unless account_id
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def debug_info
|
|
77
|
+
{
|
|
78
|
+
oauth_url: oauth_url,
|
|
79
|
+
api_base_url: api_base_url,
|
|
80
|
+
client_id: client_id ? "#{client_id[0..4]}..." : nil,
|
|
81
|
+
client_secret: client_secret ? "#{client_secret[0..4]}..." : nil,
|
|
82
|
+
app_id: app_id,
|
|
83
|
+
grant_type: grant_type,
|
|
84
|
+
oauth_scope: oauth_scope,
|
|
85
|
+
valid: valid?
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module LsaTdxFeedback
|
|
2
|
+
class Engine < ::Rails::Engine
|
|
3
|
+
isolate_namespace LsaTdxFeedback
|
|
4
|
+
|
|
5
|
+
# Automatically add assets to the asset pipeline
|
|
6
|
+
initializer 'lsa_tdx_feedback.assets.precompile' do |app|
|
|
7
|
+
if app.config.respond_to?(:assets)
|
|
8
|
+
app.config.assets.precompile += %w[lsa_tdx_feedback.js lsa_tdx_feedback.css]
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Add view paths for the feedback modal
|
|
13
|
+
initializer 'lsa_tdx_feedback.view_paths' do |app|
|
|
14
|
+
ActiveSupport.on_load :action_controller do
|
|
15
|
+
append_view_path Engine.root.join('app', 'views')
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Add helper methods to ApplicationController
|
|
20
|
+
initializer 'lsa_tdx_feedback.action_controller' do
|
|
21
|
+
ActiveSupport.on_load :action_controller do
|
|
22
|
+
include LsaTdxFeedback::ApplicationControllerExtensions
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Add view helpers to ActionView
|
|
27
|
+
initializer 'lsa_tdx_feedback.action_view' do
|
|
28
|
+
ActiveSupport.on_load :action_view do
|
|
29
|
+
include LsaTdxFeedback::ViewHelpers
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Auto-configure if credentials are available
|
|
34
|
+
config.after_initialize do
|
|
35
|
+
credentials = Rails.application.credentials.lsa_tdx_feedback
|
|
36
|
+
|
|
37
|
+
if credentials&.dig(:client_id) && credentials&.dig(:client_secret) && credentials&.dig(:default_responsible_group_id)
|
|
38
|
+
LsaTdxFeedback.configure do |config|
|
|
39
|
+
# Load configuration from Rails credentials
|
|
40
|
+
|
|
41
|
+
# Required OAuth Configuration
|
|
42
|
+
config.oauth_url = credentials[:oauth_url]
|
|
43
|
+
config.api_base_url = credentials[:api_base_url]
|
|
44
|
+
config.client_id = credentials[:client_id]
|
|
45
|
+
config.client_secret = credentials[:client_secret]
|
|
46
|
+
|
|
47
|
+
# Required TDX Configuration
|
|
48
|
+
config.app_id = credentials[:app_id]
|
|
49
|
+
config.account_id = credentials[:account_id]
|
|
50
|
+
config.service_offering_id = credentials[:service_offering_id]
|
|
51
|
+
|
|
52
|
+
# Required Ticket Configuration
|
|
53
|
+
config.default_type_id = credentials[:default_type_id]
|
|
54
|
+
config.default_form_id = credentials[:default_form_id]
|
|
55
|
+
config.default_classification = credentials[:default_classification]
|
|
56
|
+
config.default_status_id = credentials[:default_status_id]
|
|
57
|
+
config.default_priority_id = credentials[:default_priority_id]
|
|
58
|
+
config.default_source_id = credentials[:default_source_id]
|
|
59
|
+
config.default_responsible_group_id = credentials[:default_responsible_group_id]
|
|
60
|
+
config.default_service_id = credentials[:default_service_id]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|