solid_apm 0.11.1 → 0.11.2

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: d1c1e67891c00e76ce0f09899c23eaef403dd3daceeef92f58795525ee6a91e5
4
- data.tar.gz: ed1e539e7e0d2d3764dcc45b7ad131a8e6c6fbfc513ed6f80c4869350cab7c17
3
+ metadata.gz: 5ae3dca7dc8bedd15f5e89ee1c533d5f7e95707c4afa8b550f679a799a3bed0e
4
+ data.tar.gz: 4b45583cb978b1b7515e4b3482f1bbc0fc9e67963228a5deaf74163abceeb333
5
5
  SHA512:
6
- metadata.gz: a803be213c9785533bf87cf6fcb240926d47b63c47c3eff28aa5366349c3906bb9b46e17d7bd2190af3e1a35be2addf8140098f02611ff28b2b414bc85114047
7
- data.tar.gz: fb982b73450b544990308dbf6de65917708d06826d5f11744b0fcf0123745dde19ee259effdd989273569072956cffe3ea363022b368fcf514a43b94e40e9b59
6
+ metadata.gz: 01cebd760d13efbfac07743552fc34a91b9230d60bee26ba061ee7aca5e2db536f3f57580b35442bbb7996ae991d6c9b824f40a5efd134f7e8f15ea0ced30f10
7
+ data.tar.gz: a48210bbf993f9da2c4a8200a3cbc01c2c661147bd5b6451ad5486029859fe0c6c29f1aa84f146c40943d535176d4ad8e388fb9cfc3d741e605099255e294e62
@@ -101,4 +101,223 @@
101
101
  </div>
102
102
  <% end %>
103
103
 
104
- <%= javascript_include_tag 'solid_apm/time_range_form' %>
104
+ <script>
105
+ class TimeRangeForm {
106
+ constructor() {
107
+ this.form = document.getElementById('time-range-form');
108
+ this.relativeTab = document.getElementById('relative-tab');
109
+ this.absoluteTab = document.getElementById('absolute-tab');
110
+ this.relativePanel = document.getElementById('relative-panel');
111
+ this.absolutePanel = document.getElementById('absolute-panel');
112
+ this.customFromControl = document.getElementById('custom-from-control');
113
+ this.customToControl = document.getElementById('custom-to-control');
114
+
115
+ // Timezone handling
116
+ this.browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
117
+ this.timezoneOffset = new Date().getTimezoneOffset();
118
+
119
+ this.init();
120
+ }
121
+
122
+ init() {
123
+ this.setupEventListeners();
124
+ this.initializeFormState();
125
+ this.addTimezoneToForm();
126
+ this.adjustAbsoluteTimes();
127
+ }
128
+
129
+ setupEventListeners() {
130
+ if (this.form) {
131
+ this.form.addEventListener('submit', (e) => this.handleFormSubmit(e));
132
+ }
133
+ }
134
+
135
+ switchToRelative(event) {
136
+ event.preventDefault();
137
+
138
+ this.relativeTab.classList.add('is-primary');
139
+ this.absoluteTab.classList.remove('is-primary');
140
+ this.relativePanel.classList.remove('is-hidden');
141
+ this.absolutePanel.classList.add('is-hidden');
142
+
143
+ this.removeFields(['from_timestamp', 'to_timestamp']);
144
+ this.cleanupUrlParams(['from_timestamp', 'to_timestamp', 'from_datetime', 'to_datetime']);
145
+ }
146
+
147
+ switchToAbsolute(event) {
148
+ event.preventDefault();
149
+
150
+ this.absoluteTab.classList.add('is-primary');
151
+ this.relativeTab.classList.remove('is-primary');
152
+ this.absolutePanel.classList.remove('is-hidden');
153
+ this.relativePanel.classList.add('is-hidden');
154
+
155
+ this.cleanupUrlParams(['quick_range', 'from_value', 'from_unit', 'to_value', 'to_unit', 'quick_range_apply']);
156
+ }
157
+
158
+ handleQuickRangeChange(select) {
159
+ const isCustom = select.value === 'custom';
160
+
161
+ this.toggleVisibility(this.customFromControl, isCustom);
162
+ this.toggleVisibility(this.customToControl, isCustom);
163
+
164
+ if (!isCustom) {
165
+ this.applyQuickRange();
166
+ }
167
+ }
168
+
169
+ applyQuickRange() {
170
+ const quickRangeSelect = this.form.querySelector('[name="quick_range"]');
171
+ if (!quickRangeSelect || quickRangeSelect.value === 'custom') return;
172
+
173
+ this.removeFields(['from_datetime', 'to_datetime', 'from_timestamp', 'to_timestamp']);
174
+ this.addHiddenField('quick_range_apply', quickRangeSelect.value);
175
+ this.form.submit();
176
+ }
177
+
178
+ handleFormSubmit(event) {
179
+ const isAbsoluteMode = !this.absolutePanel.classList.contains('is-hidden');
180
+
181
+ if (isAbsoluteMode) {
182
+ this.handleAbsoluteModeSubmit();
183
+ } else {
184
+ this.handleRelativeModeSubmit();
185
+ }
186
+ }
187
+
188
+ handleAbsoluteModeSubmit() {
189
+ const fromDatetime = this.form.querySelector('[name="from_datetime"]');
190
+ const toDatetime = this.form.querySelector('[name="to_datetime"]');
191
+
192
+ if (fromDatetime?.value && toDatetime?.value) {
193
+ const fromTimestamp = Math.floor(new Date(fromDatetime.value).getTime() / 1000);
194
+ const toTimestamp = Math.floor(new Date(toDatetime.value).getTime() / 1000);
195
+
196
+ fromDatetime.disabled = true;
197
+ toDatetime.disabled = true;
198
+
199
+ this.addHiddenField('from_timestamp', fromTimestamp);
200
+ this.addHiddenField('to_timestamp', toTimestamp);
201
+ this.addHiddenField('browser_timezone', this.browserTimezone);
202
+ this.removeFields(['quick_range', 'from_value', 'from_unit', 'to_value', 'to_unit', 'quick_range_apply']);
203
+ }
204
+ }
205
+
206
+ handleRelativeModeSubmit() {
207
+ const quickRangeSelect = this.form.querySelector('[name="quick_range"]');
208
+ const quickRangeValue = quickRangeSelect?.value;
209
+
210
+ if (quickRangeValue && quickRangeValue !== 'custom') {
211
+ this.removeFields(['from_value', 'from_unit', 'to_value', 'to_unit', 'quick_range_apply']);
212
+ } else if (quickRangeValue === 'custom') {
213
+ this.removeFields(['quick_range_apply']);
214
+ }
215
+
216
+ this.removeFields(['from_datetime', 'to_datetime', 'from_timestamp', 'to_timestamp']);
217
+ }
218
+
219
+ initializeFormState() {
220
+ const urlParams = new URLSearchParams(window.location.search);
221
+ const hasCustomParams = urlParams.has('from_value') && urlParams.has('from_unit');
222
+ const hasQuickRange = urlParams.has('quick_range') && urlParams.get('quick_range') !== 'custom';
223
+ const quickRangeSelect = this.form.querySelector('[name="quick_range"]');
224
+
225
+ if (hasQuickRange) {
226
+ this.toggleVisibility(this.customFromControl, false);
227
+ this.toggleVisibility(this.customToControl, false);
228
+ } else if (hasCustomParams || urlParams.get('quick_range') === 'custom') {
229
+ if (quickRangeSelect) quickRangeSelect.value = 'custom';
230
+ this.toggleVisibility(this.customFromControl, true);
231
+ this.toggleVisibility(this.customToControl, true);
232
+ } else {
233
+ // Default state - show quick range only
234
+ this.toggleVisibility(this.customFromControl, false);
235
+ this.toggleVisibility(this.customToControl, false);
236
+ }
237
+ }
238
+
239
+ // Utility methods
240
+ removeFields(fieldNames) {
241
+ fieldNames.forEach(name => {
242
+ this.form.querySelectorAll(`[name="${name}"]`).forEach(field => field.remove());
243
+ });
244
+ }
245
+
246
+ addHiddenField(name, value) {
247
+ const input = document.createElement('input');
248
+ input.type = 'hidden';
249
+ input.name = name;
250
+ input.value = value;
251
+ this.form.appendChild(input);
252
+ }
253
+
254
+ toggleVisibility(element, show) {
255
+ if (!element) return;
256
+ element.classList.toggle('is-hidden', !show);
257
+ }
258
+
259
+ cleanupUrlParams(params) {
260
+ const url = new URL(window.location);
261
+ params.forEach(param => url.searchParams.delete(param));
262
+ window.history.replaceState({}, '', url);
263
+ }
264
+
265
+ // Timezone-related methods
266
+ addTimezoneToForm() {
267
+ // Add timezone information to form for server processing
268
+ this.addHiddenField('browser_timezone', this.browserTimezone);
269
+ }
270
+
271
+ adjustAbsoluteTimes() {
272
+ // Convert timestamps from URL to browser timezone for datetime-local inputs
273
+ const urlParams = new URLSearchParams(window.location.search);
274
+ const fromTimestamp = urlParams.get('from_timestamp');
275
+ const toTimestamp = urlParams.get('to_timestamp');
276
+
277
+ if (fromTimestamp && toTimestamp) {
278
+ const fromDatetime = this.form.querySelector('[name="from_datetime"]');
279
+ const toDatetime = this.form.querySelector('[name="to_datetime"]');
280
+
281
+ if (fromDatetime && toDatetime) {
282
+ // Convert UTC timestamps to local datetime strings
283
+ const fromDate = new Date(parseInt(fromTimestamp) * 1000);
284
+ const toDate = new Date(parseInt(toTimestamp) * 1000);
285
+
286
+ fromDatetime.value = this.formatDatetimeLocal(fromDate);
287
+ toDatetime.value = this.formatDatetimeLocal(toDate);
288
+ }
289
+ }
290
+ }
291
+
292
+ formatDatetimeLocal(date) {
293
+ // Format date for datetime-local input (YYYY-MM-DDTHH:MM)
294
+ const year = date.getFullYear();
295
+ const month = String(date.getMonth() + 1).padStart(2, '0');
296
+ const day = String(date.getDate()).padStart(2, '0');
297
+ const hours = String(date.getHours()).padStart(2, '0');
298
+ const minutes = String(date.getMinutes()).padStart(2, '0');
299
+
300
+ return `${year}-${month}-${day}T${hours}:${minutes}`;
301
+ }
302
+ }
303
+
304
+ // Global functions for onclick handlers (maintaining backward compatibility)
305
+ let timeRangeFormInstance;
306
+
307
+ function switchToRelative(event) {
308
+ timeRangeFormInstance?.switchToRelative(event);
309
+ }
310
+
311
+ function switchToAbsolute(event) {
312
+ timeRangeFormInstance?.switchToAbsolute(event);
313
+ }
314
+
315
+ function handleQuickRangeChange(select) {
316
+ timeRangeFormInstance?.handleQuickRangeChange(select);
317
+ }
318
+
319
+ // Initialize when DOM is ready
320
+ document.addEventListener('DOMContentLoaded', function() {
321
+ timeRangeFormInstance = new TimeRangeForm();
322
+ });
323
+ </script>
@@ -6,32 +6,44 @@ module SolidApm
6
6
 
7
7
  config.app_middleware.use Middleware
8
8
 
9
- initializer "solid_apm.assets.precompile" do |app|
10
- app.config.assets.precompile += %w( application.css application.js )
9
+ initializer 'solid_apm.assets' do |app|
10
+ # Add engine's assets to the load path for both Propshaft and Sprockets
11
+ if app.config.respond_to?(:assets)
12
+ app.config.assets.paths << root.join('app/assets/stylesheets')
13
+ app.config.assets.paths << root.join('app/assets/javascripts')
14
+
15
+ # For Sprockets
16
+ unless defined?(Propshaft)
17
+ app.config.assets.precompile += %w[
18
+ solid_apm/application.css
19
+ solid_apm/application.js
20
+ ]
21
+ end
22
+ end
11
23
  end
12
24
 
13
25
  begin
14
26
  # Mount the MCP server only if the main app added the fast_mcp in is Gemfile.
15
27
  require 'fast_mcp'
16
- initializer "solid_apm.mount_mcp_server" do |app|
17
- mcp_server_config = SolidApm.mcp_server_config.reverse_merge(
18
- name: 'solid-apm-mcp',
19
- version: '1.0.0',
20
- path: '/solid_apm/mcp'
21
- )
28
+ initializer 'solid_apm.mount_mcp_server' do |app|
29
+ mcp_server_config = SolidApm.mcp_server_config.reverse_merge(
30
+ name: 'solid-apm-mcp',
31
+ version: '1.0.0',
32
+ path: '/solid_apm/mcp'
33
+ )
22
34
 
23
- FastMcp.mount_in_rails(
24
- app,
25
- **mcp_server_config
26
- ) do |server|
27
- app.config.after_initialize do
28
- require_relative 'mcp/spans_for_transaction_tool'
29
- require_relative 'mcp/impactful_transactions_resource'
30
- server.register_resources(SolidApm::Mcp::ImpactfulTransactionsResource)
31
- server.register_tools(SolidApm::Mcp::SpansForTransactionTool)
35
+ FastMcp.mount_in_rails(
36
+ app,
37
+ **mcp_server_config
38
+ ) do |server|
39
+ app.config.after_initialize do
40
+ require_relative 'mcp/spans_for_transaction_tool'
41
+ require_relative 'mcp/impactful_transactions_resource'
42
+ server.register_resources(SolidApm::Mcp::ImpactfulTransactionsResource)
43
+ server.register_tools(SolidApm::Mcp::SpansForTransactionTool)
44
+ end
32
45
  end
33
46
  end
34
- end
35
47
  rescue LoadError
36
48
  # Ignored
37
49
  end
@@ -1,3 +1,3 @@
1
1
  module SolidApm
2
- VERSION = "0.11.1"
2
+ VERSION = "0.11.2"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.1
4
+ version: 0.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean-Francis Bastien
@@ -119,7 +119,6 @@ files:
119
119
  - Rakefile
120
120
  - app/assets/config/solid_apm_manifest.js
121
121
  - app/assets/javascripts/solid_apm/application.js
122
- - app/assets/javascripts/solid_apm/time_range_form.js
123
122
  - app/assets/stylesheets/solid_apm/application.css
124
123
  - app/controllers/solid_apm/application_controller.rb
125
124
  - app/controllers/solid_apm/spans_controller.rb
@@ -1,219 +0,0 @@
1
- class TimeRangeForm {
2
- constructor() {
3
- this.form = document.getElementById('time-range-form');
4
- this.relativeTab = document.getElementById('relative-tab');
5
- this.absoluteTab = document.getElementById('absolute-tab');
6
- this.relativePanel = document.getElementById('relative-panel');
7
- this.absolutePanel = document.getElementById('absolute-panel');
8
- this.customFromControl = document.getElementById('custom-from-control');
9
- this.customToControl = document.getElementById('custom-to-control');
10
-
11
- // Timezone handling
12
- this.browserTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
13
- this.timezoneOffset = new Date().getTimezoneOffset();
14
-
15
- this.init();
16
- }
17
-
18
- init() {
19
- this.setupEventListeners();
20
- this.initializeFormState();
21
- this.addTimezoneToForm();
22
- this.adjustAbsoluteTimes();
23
- }
24
-
25
- setupEventListeners() {
26
- if (this.form) {
27
- this.form.addEventListener('submit', (e) => this.handleFormSubmit(e));
28
- }
29
- }
30
-
31
- switchToRelative(event) {
32
- event.preventDefault();
33
-
34
- this.relativeTab.classList.add('is-primary');
35
- this.absoluteTab.classList.remove('is-primary');
36
- this.relativePanel.classList.remove('is-hidden');
37
- this.absolutePanel.classList.add('is-hidden');
38
-
39
- this.removeFields(['from_timestamp', 'to_timestamp']);
40
- this.cleanupUrlParams(['from_timestamp', 'to_timestamp', 'from_datetime', 'to_datetime']);
41
- }
42
-
43
- switchToAbsolute(event) {
44
- event.preventDefault();
45
-
46
- this.absoluteTab.classList.add('is-primary');
47
- this.relativeTab.classList.remove('is-primary');
48
- this.absolutePanel.classList.remove('is-hidden');
49
- this.relativePanel.classList.add('is-hidden');
50
-
51
- this.cleanupUrlParams(['quick_range', 'from_value', 'from_unit', 'to_value', 'to_unit', 'quick_range_apply']);
52
- }
53
-
54
- handleQuickRangeChange(select) {
55
- const isCustom = select.value === 'custom';
56
-
57
- this.toggleVisibility(this.customFromControl, isCustom);
58
- this.toggleVisibility(this.customToControl, isCustom);
59
-
60
- if (!isCustom) {
61
- this.applyQuickRange();
62
- }
63
- }
64
-
65
- applyQuickRange() {
66
- const quickRangeSelect = this.form.querySelector('[name="quick_range"]');
67
- if (!quickRangeSelect || quickRangeSelect.value === 'custom') return;
68
-
69
- this.removeFields(['from_datetime', 'to_datetime', 'from_timestamp', 'to_timestamp']);
70
- this.addHiddenField('quick_range_apply', quickRangeSelect.value);
71
- this.form.submit();
72
- }
73
-
74
- handleFormSubmit(event) {
75
- const isAbsoluteMode = !this.absolutePanel.classList.contains('is-hidden');
76
-
77
- if (isAbsoluteMode) {
78
- this.handleAbsoluteModeSubmit();
79
- } else {
80
- this.handleRelativeModeSubmit();
81
- }
82
- }
83
-
84
- handleAbsoluteModeSubmit() {
85
- const fromDatetime = this.form.querySelector('[name="from_datetime"]');
86
- const toDatetime = this.form.querySelector('[name="to_datetime"]');
87
-
88
- if (fromDatetime?.value && toDatetime?.value) {
89
- const fromTimestamp = Math.floor(new Date(fromDatetime.value).getTime() / 1000);
90
- const toTimestamp = Math.floor(new Date(toDatetime.value).getTime() / 1000);
91
-
92
- fromDatetime.disabled = true;
93
- toDatetime.disabled = true;
94
-
95
- this.addHiddenField('from_timestamp', fromTimestamp);
96
- this.addHiddenField('to_timestamp', toTimestamp);
97
- this.addHiddenField('browser_timezone', this.browserTimezone);
98
- this.removeFields(['quick_range', 'from_value', 'from_unit', 'to_value', 'to_unit', 'quick_range_apply']);
99
- }
100
- }
101
-
102
- handleRelativeModeSubmit() {
103
- const quickRangeSelect = this.form.querySelector('[name="quick_range"]');
104
- const quickRangeValue = quickRangeSelect?.value;
105
-
106
- if (quickRangeValue && quickRangeValue !== 'custom') {
107
- this.removeFields(['from_value', 'from_unit', 'to_value', 'to_unit', 'quick_range_apply']);
108
- } else if (quickRangeValue === 'custom') {
109
- this.removeFields(['quick_range_apply']);
110
- }
111
-
112
- this.removeFields(['from_datetime', 'to_datetime', 'from_timestamp', 'to_timestamp']);
113
- }
114
-
115
- initializeFormState() {
116
- const urlParams = new URLSearchParams(window.location.search);
117
- const hasCustomParams = urlParams.has('from_value') && urlParams.has('from_unit');
118
- const hasQuickRange = urlParams.has('quick_range') && urlParams.get('quick_range') !== 'custom';
119
- const quickRangeSelect = this.form.querySelector('[name="quick_range"]');
120
-
121
- if (hasQuickRange) {
122
- this.toggleVisibility(this.customFromControl, false);
123
- this.toggleVisibility(this.customToControl, false);
124
- } else if (hasCustomParams || urlParams.get('quick_range') === 'custom') {
125
- if (quickRangeSelect) quickRangeSelect.value = 'custom';
126
- this.toggleVisibility(this.customFromControl, true);
127
- this.toggleVisibility(this.customToControl, true);
128
- } else {
129
- // Default state - show quick range only
130
- this.toggleVisibility(this.customFromControl, false);
131
- this.toggleVisibility(this.customToControl, false);
132
- }
133
- }
134
-
135
- // Utility methods
136
- removeFields(fieldNames) {
137
- fieldNames.forEach(name => {
138
- this.form.querySelectorAll(`[name="${name}"]`).forEach(field => field.remove());
139
- });
140
- }
141
-
142
- addHiddenField(name, value) {
143
- const input = document.createElement('input');
144
- input.type = 'hidden';
145
- input.name = name;
146
- input.value = value;
147
- this.form.appendChild(input);
148
- }
149
-
150
- toggleVisibility(element, show) {
151
- if (!element) return;
152
- element.classList.toggle('is-hidden', !show);
153
- }
154
-
155
- cleanupUrlParams(params) {
156
- const url = new URL(window.location);
157
- params.forEach(param => url.searchParams.delete(param));
158
- window.history.replaceState({}, '', url);
159
- }
160
-
161
- // Timezone-related methods
162
- addTimezoneToForm() {
163
- // Add timezone information to form for server processing
164
- this.addHiddenField('browser_timezone', this.browserTimezone);
165
- }
166
-
167
- adjustAbsoluteTimes() {
168
- // Convert timestamps from URL to browser timezone for datetime-local inputs
169
- const urlParams = new URLSearchParams(window.location.search);
170
- const fromTimestamp = urlParams.get('from_timestamp');
171
- const toTimestamp = urlParams.get('to_timestamp');
172
-
173
- if (fromTimestamp && toTimestamp) {
174
- const fromDatetime = this.form.querySelector('[name="from_datetime"]');
175
- const toDatetime = this.form.querySelector('[name="to_datetime"]');
176
-
177
- if (fromDatetime && toDatetime) {
178
- // Convert UTC timestamps to local datetime strings
179
- const fromDate = new Date(parseInt(fromTimestamp) * 1000);
180
- const toDate = new Date(parseInt(toTimestamp) * 1000);
181
-
182
- fromDatetime.value = this.formatDatetimeLocal(fromDate);
183
- toDatetime.value = this.formatDatetimeLocal(toDate);
184
- }
185
- }
186
- }
187
-
188
- formatDatetimeLocal(date) {
189
- // Format date for datetime-local input (YYYY-MM-DDTHH:MM)
190
- const year = date.getFullYear();
191
- const month = String(date.getMonth() + 1).padStart(2, '0');
192
- const day = String(date.getDate()).padStart(2, '0');
193
- const hours = String(date.getHours()).padStart(2, '0');
194
- const minutes = String(date.getMinutes()).padStart(2, '0');
195
-
196
- return `${year}-${month}-${day}T${hours}:${minutes}`;
197
- }
198
-
199
- }
200
-
201
- // Global functions for onclick handlers (maintaining backward compatibility)
202
- let timeRangeFormInstance;
203
-
204
- function switchToRelative(event) {
205
- timeRangeFormInstance?.switchToRelative(event);
206
- }
207
-
208
- function switchToAbsolute(event) {
209
- timeRangeFormInstance?.switchToAbsolute(event);
210
- }
211
-
212
- function handleQuickRangeChange(select) {
213
- timeRangeFormInstance?.handleQuickRangeChange(select);
214
- }
215
-
216
- // Initialize when DOM is ready
217
- document.addEventListener('DOMContentLoaded', function() {
218
- timeRangeFormInstance = new TimeRangeForm();
219
- });