sage-rails 0.0.6 → 0.0.8
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/CONTRIBUTING.md +42 -0
- data/README.md +21 -22
- data/app/assets/javascripts/sage/application.js +27 -1
- data/app/assets/javascripts/sage/controllers/clipboard_controller.js +26 -0
- data/app/assets/javascripts/sage/controllers/dashboard_controller.js +132 -0
- data/app/assets/javascripts/sage/controllers/reverse_infinite_scroll_controller.js +146 -0
- data/app/assets/javascripts/sage/controllers/search_controller.js +47 -0
- data/app/assets/javascripts/sage/controllers/select_controller.js +215 -0
- data/app/assets/javascripts/sage/controllers/variables_controller.js +122 -0
- data/app/assets/javascripts/sage.js +21 -0
- data/app/controllers/sage/checks_controller.rb +3 -3
- data/app/controllers/sage/dashboards_controller.rb +8 -9
- data/app/controllers/sage/queries_controller.rb +1 -1
- data/app/helpers/sage/application_helper.rb +1 -1
- data/app/helpers/sage/queries_helper.rb +2 -2
- data/app/javascript/sage/controllers/variables_controller.js +122 -0
- data/app/javascript/sage.js +3 -1
- data/app/views/sage/_variables.html.erb +168 -0
- data/app/views/sage/dashboards/show.html.erb +1 -1
- data/app/views/sage/queries/show.html.erb +2 -2
- data/config/initializers/ransack.rb +19 -19
- data/config/routes.rb +1 -1
- data/lib/generators/sage/install/install_generator.rb +11 -33
- data/lib/sage/engine.rb +2 -2
- data/lib/sage/model_scopes_context.rb +19 -19
- data/lib/sage/version.rb +1 -1
- metadata +11 -1
@@ -0,0 +1,168 @@
|
|
1
|
+
<% if @bind_vars.any? %>
|
2
|
+
<% var_params = request.query_parameters %>
|
3
|
+
<%= javascript_tag nonce: true do %>
|
4
|
+
<%= blazer_js_var "timeZone", Blazer.time_zone.tzinfo.name %>
|
5
|
+
var now = moment.tz(timeZone)
|
6
|
+
var format = "YYYY-MM-DD"
|
7
|
+
|
8
|
+
function toDate(time) {
|
9
|
+
return moment.tz(time.format(format), timeZone)
|
10
|
+
}
|
11
|
+
<% end %>
|
12
|
+
<div data-controller="sage--variables" data-sage--variables-form-target="form">
|
13
|
+
<form id="bind" method="get" action="<%= action %>" class="form-inline" style="margin-bottom: 15px;">
|
14
|
+
<% date_vars = ["start_time", "end_time"] %>
|
15
|
+
<% if (date_vars - @bind_vars).empty? %>
|
16
|
+
<% @bind_vars = @bind_vars - date_vars %>
|
17
|
+
<% else %>
|
18
|
+
<% date_vars = nil %>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% @bind_vars.each_with_index do |var, i| %>
|
22
|
+
<div style="margin-bottom: 10px;">
|
23
|
+
<div style="margin-bottom: 3px;">
|
24
|
+
<small class="text-muted"><code>{{<%= var %>}}</code></small>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
<% if (data = @smart_vars[var]) %>
|
28
|
+
<%= select_tag var, options_for_select([[nil, nil]] + data, selected: var_params[var]), style: "margin-right: 20px; width: 200px; display: none;", data: { "sage--variables-target": "variable" } %>
|
29
|
+
<%= javascript_tag nonce: true do %>
|
30
|
+
$("#<%= var %>").selectize({
|
31
|
+
create: true
|
32
|
+
});
|
33
|
+
<% end %>
|
34
|
+
<% elsif var.end_with?("_at") || var == "start_time" || var == "end_time" %>
|
35
|
+
<%= hidden_field_tag var, var_params[var], data: { "sage--variables-target": "variable" } %>
|
36
|
+
|
37
|
+
<div class="selectize-control single" style="width: 200px;">
|
38
|
+
<div id="<%= var %>-select" class="selectize-input" style="display: inline-block;">
|
39
|
+
<span>Select a date</span>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<%= javascript_tag nonce: true do %>
|
44
|
+
(function() {
|
45
|
+
var input = $("#<%= var %>")
|
46
|
+
var datePicker = $("#<%= var %>-select")
|
47
|
+
datePicker.daterangepicker({
|
48
|
+
singleDatePicker: true,
|
49
|
+
locale: {format: format},
|
50
|
+
autoUpdateInput: false,
|
51
|
+
autoApply: true,
|
52
|
+
startDate: input.val().length > 0 ? moment.tz(input.val(), timeZone) : now
|
53
|
+
})
|
54
|
+
// hack to start with empty date
|
55
|
+
datePicker.on("apply.daterangepicker", function(ev, picker) {
|
56
|
+
datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
|
57
|
+
input.val(toDate(picker.startDate).utc().format())
|
58
|
+
|
59
|
+
// Trigger our custom variable controller instead of submitIfCompleted
|
60
|
+
var controller = document.querySelector('[data-controller*="sage--variables"]')
|
61
|
+
if (controller && window.Stimulus) {
|
62
|
+
var controllerInstance = window.Stimulus.getControllerForElementAndIdentifier(controller, 'sage--variables')
|
63
|
+
if (controllerInstance) {
|
64
|
+
controllerInstance.handleDateRangeChange(input[0], picker)
|
65
|
+
}
|
66
|
+
}
|
67
|
+
})
|
68
|
+
if (input.val().length > 0) {
|
69
|
+
var picker = datePicker.data("daterangepicker")
|
70
|
+
datePicker.find("span").html(toDate(picker.startDate).format("MMMM D, YYYY"))
|
71
|
+
}
|
72
|
+
})()
|
73
|
+
<% end %>
|
74
|
+
<% else %>
|
75
|
+
<%= text_field_tag var, var_params[var], style: "width: 120px; margin-right: 20px;", autofocus: i == 0 && !var.end_with?("_at") && !var_params[var], class: "form-control", data: { "sage--variables-target": "variable" } %>
|
76
|
+
<% end %>
|
77
|
+
<% end %>
|
78
|
+
|
79
|
+
<% if date_vars %>
|
80
|
+
<% date_vars.each do |var| %>
|
81
|
+
<%= hidden_field_tag var, var_params[var], data: { "sage--variables-target": "variable" } %>
|
82
|
+
<% end %>
|
83
|
+
|
84
|
+
<div style="margin-bottom: 5px; text-align: left;">
|
85
|
+
<small class="text-muted"><code>{{<%= date_vars.join('}}, {{') %>}}</code></small>
|
86
|
+
</div>
|
87
|
+
<div class="selectize-control single" style="width: 300px;">
|
88
|
+
<div id="reportrange" class="selectize-input" style="display: inline-block;">
|
89
|
+
<span>Select a time range</span>
|
90
|
+
</div>
|
91
|
+
</div>
|
92
|
+
|
93
|
+
<%= javascript_tag nonce: true do %>
|
94
|
+
function dateStr(daysAgo) {
|
95
|
+
return now.clone().subtract(daysAgo || 0, "days").format(format)
|
96
|
+
}
|
97
|
+
|
98
|
+
function setTimeInputs(start, end) {
|
99
|
+
$("#start_time").val(toDate(start).utc().format())
|
100
|
+
$("#end_time").val(toDate(end).endOf("day").utc().format())
|
101
|
+
}
|
102
|
+
|
103
|
+
$("#reportrange").daterangepicker(
|
104
|
+
{
|
105
|
+
ranges: {
|
106
|
+
"Today": [dateStr(), dateStr()],
|
107
|
+
"Last 7 Days": [dateStr(6), dateStr()],
|
108
|
+
"Last 30 Days": [dateStr(29), dateStr()]
|
109
|
+
},
|
110
|
+
locale: {
|
111
|
+
format: format
|
112
|
+
},
|
113
|
+
startDate: dateStr(29),
|
114
|
+
endDate: dateStr(),
|
115
|
+
opens: "right",
|
116
|
+
alwaysShowCalendars: true
|
117
|
+
},
|
118
|
+
function(start, end) {
|
119
|
+
setTimeInputs(start, end)
|
120
|
+
|
121
|
+
// Trigger our custom variable controller instead of submitIfCompleted
|
122
|
+
var controller = document.querySelector('[data-controller*="sage--variables"]')
|
123
|
+
if (controller && window.Stimulus) {
|
124
|
+
var controllerInstance = window.Stimulus.getControllerForElementAndIdentifier(controller, 'sage--variables')
|
125
|
+
if (controllerInstance) {
|
126
|
+
controllerInstance.triggerSubmit()
|
127
|
+
}
|
128
|
+
}
|
129
|
+
}
|
130
|
+
).on("apply.daterangepicker", function(ev, picker) {
|
131
|
+
setTimeInputs(picker.startDate, picker.endDate)
|
132
|
+
$("#reportrange span").html(toDate(picker.startDate).format("MMMM D, YYYY") + " - " + toDate(picker.endDate).format("MMMM D, YYYY"))
|
133
|
+
|
134
|
+
// Trigger our custom variable controller
|
135
|
+
var controller = document.querySelector('[data-controller*="sage--variables"]')
|
136
|
+
if (controller && window.Stimulus) {
|
137
|
+
var controllerInstance = window.Stimulus.getControllerForElementAndIdentifier(controller, 'sage--variables')
|
138
|
+
if (controllerInstance) {
|
139
|
+
controllerInstance.handleDateRangeChange(document.getElementById('start_time'), picker)
|
140
|
+
}
|
141
|
+
}
|
142
|
+
})
|
143
|
+
|
144
|
+
if ($("#start_time").val().length > 0) {
|
145
|
+
var picker = $("#reportrange").data("daterangepicker")
|
146
|
+
picker.setStartDate(moment.tz($("#start_time").val(), timeZone))
|
147
|
+
picker.setEndDate(moment.tz($("#end_time").val(), timeZone))
|
148
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
149
|
+
} else {
|
150
|
+
var picker = $("#reportrange").data("daterangepicker")
|
151
|
+
$("#reportrange").trigger("apply.daterangepicker", picker)
|
152
|
+
|
153
|
+
// Initial trigger for our custom controller
|
154
|
+
var controller = document.querySelector('[data-controller*="sage--variables"]')
|
155
|
+
if (controller && window.Stimulus) {
|
156
|
+
var controllerInstance = window.Stimulus.getControllerForElementAndIdentifier(controller, 'sage--variables')
|
157
|
+
if (controllerInstance) {
|
158
|
+
controllerInstance.triggerSubmit()
|
159
|
+
}
|
160
|
+
}
|
161
|
+
}
|
162
|
+
<% end %>
|
163
|
+
<% end %>
|
164
|
+
|
165
|
+
<button type='submit' class='btn btn-primary'>Run</button>
|
166
|
+
</form>
|
167
|
+
</div>
|
168
|
+
<% end %>
|
@@ -22,7 +22,7 @@
|
|
22
22
|
<% end %>
|
23
23
|
|
24
24
|
<% if @bind_vars.any? %>
|
25
|
-
<%= render partial: "
|
25
|
+
<%= render partial: "sage/variables", locals: {action: dashboard_path(@dashboard)} %>
|
26
26
|
<% else %>
|
27
27
|
<div style="padding-bottom: 15px;"></div>
|
28
28
|
<% end %>
|
@@ -28,12 +28,12 @@
|
|
28
28
|
<p style="white-space: pre-line;"><%= @query.description %></p>
|
29
29
|
<% end %>
|
30
30
|
|
31
|
-
<%= render partial: "
|
31
|
+
<%= render partial: "sage/variables", locals: { action: query_path(@query) } %>
|
32
32
|
|
33
33
|
<pre id="code"><code><%= @statement.display_statement %></code></pre>
|
34
34
|
|
35
35
|
<% if @success %>
|
36
|
-
<%= turbo_frame_tag dom_id(@query, 'results'), src: run_query_path(@query.id, from_show: true) do %>
|
36
|
+
<%= turbo_frame_tag dom_id(@query, 'results'), src: run_query_path(@query.id, from_show: true, variables: variable_params(@query)) do %>
|
37
37
|
loading...
|
38
38
|
<% end %>
|
39
39
|
<% end %>
|
@@ -1,17 +1,17 @@
|
|
1
|
-
require
|
1
|
+
require "ransack"
|
2
2
|
|
3
3
|
# Configure Ransack for Blazer models
|
4
4
|
Rails.application.config.after_initialize do
|
5
5
|
# Ensure Blazer is loaded first
|
6
|
-
require
|
7
|
-
|
6
|
+
require "blazer" if defined?(Blazer)
|
7
|
+
|
8
8
|
# Extend Blazer::Query with Ransack capabilities
|
9
9
|
if defined?(Blazer::Query)
|
10
10
|
# First, ensure Ransack is included in the model
|
11
11
|
unless Blazer::Query.respond_to?(:ransack)
|
12
12
|
Blazer::Query.send(:extend, Ransack::Adapters::ActiveRecord::Base)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
Blazer::Query.class_eval do
|
16
16
|
# Define which attributes can be searched
|
17
17
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -21,7 +21,7 @@ Rails.application.config.after_initialize do
|
|
21
21
|
# Define which associations can be searched
|
22
22
|
def self.ransackable_associations(auth_object = nil)
|
23
23
|
associations = %w[checks audits dashboard_queries dashboards]
|
24
|
-
associations <<
|
24
|
+
associations << "creator" if Blazer.user_class
|
25
25
|
associations
|
26
26
|
end
|
27
27
|
|
@@ -37,14 +37,14 @@ Rails.application.config.after_initialize do
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
# Extend Blazer::Dashboard with Ransack capabilities
|
42
42
|
if defined?(Blazer::Dashboard)
|
43
43
|
# First, ensure Ransack is included in the model
|
44
44
|
unless Blazer::Dashboard.respond_to?(:ransack)
|
45
45
|
Blazer::Dashboard.send(:extend, Ransack::Adapters::ActiveRecord::Base)
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
Blazer::Dashboard.class_eval do
|
49
49
|
# Define which attributes can be searched
|
50
50
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -54,7 +54,7 @@ Rails.application.config.after_initialize do
|
|
54
54
|
# Define which associations can be searched
|
55
55
|
def self.ransackable_associations(auth_object = nil)
|
56
56
|
associations = %w[dashboard_queries queries]
|
57
|
-
associations <<
|
57
|
+
associations << "creator" if Blazer.user_class
|
58
58
|
associations
|
59
59
|
end
|
60
60
|
end
|
@@ -68,7 +68,7 @@ Rails.application.config.to_prepare do
|
|
68
68
|
unless Blazer::Query.respond_to?(:ransack)
|
69
69
|
Blazer::Query.send(:extend, Ransack::Adapters::ActiveRecord::Base)
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
unless Blazer::Query.respond_to?(:ransackable_attributes)
|
73
73
|
Blazer::Query.class_eval do
|
74
74
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -85,13 +85,13 @@ Rails.application.config.to_prepare do
|
|
85
85
|
end
|
86
86
|
end
|
87
87
|
end
|
88
|
-
|
88
|
+
|
89
89
|
if defined?(Blazer::Dashboard)
|
90
90
|
# Ensure Ransack is included
|
91
91
|
unless Blazer::Dashboard.respond_to?(:ransack)
|
92
92
|
Blazer::Dashboard.send(:extend, Ransack::Adapters::ActiveRecord::Base)
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
unless Blazer::Dashboard.respond_to?(:ransackable_attributes)
|
96
96
|
Blazer::Dashboard.class_eval do
|
97
97
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -100,19 +100,19 @@ Rails.application.config.to_prepare do
|
|
100
100
|
|
101
101
|
def self.ransackable_associations(auth_object = nil)
|
102
102
|
associations = %w[dashboard_queries queries]
|
103
|
-
associations <<
|
103
|
+
associations << "creator" if Blazer.user_class
|
104
104
|
associations
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
if defined?(Blazer::Check)
|
111
111
|
# Ensure Ransack is included
|
112
112
|
unless Blazer::Check.respond_to?(:ransack)
|
113
113
|
Blazer::Check.send(:extend, Ransack::Adapters::ActiveRecord::Base)
|
114
114
|
end
|
115
|
-
|
115
|
+
|
116
116
|
unless Blazer::Check.respond_to?(:ransackable_attributes)
|
117
117
|
Blazer::Check.class_eval do
|
118
118
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -121,20 +121,20 @@ Rails.application.config.to_prepare do
|
|
121
121
|
|
122
122
|
def self.ransackable_associations(auth_object = nil)
|
123
123
|
associations = %w[query]
|
124
|
-
associations <<
|
124
|
+
associations << "creator" if Blazer.user_class
|
125
125
|
associations
|
126
126
|
end
|
127
127
|
end
|
128
128
|
end
|
129
129
|
end
|
130
|
-
|
130
|
+
|
131
131
|
# Extend Blazer::Check with Ransack capabilities
|
132
132
|
if defined?(Blazer::Check)
|
133
133
|
# First, ensure Ransack is included in the model
|
134
134
|
unless Blazer::Check.respond_to?(:ransack)
|
135
135
|
Blazer::Check.send(:extend, Ransack::Adapters::ActiveRecord::Base)
|
136
136
|
end
|
137
|
-
|
137
|
+
|
138
138
|
Blazer::Check.class_eval do
|
139
139
|
# Define which attributes can be searched
|
140
140
|
def self.ransackable_attributes(auth_object = nil)
|
@@ -144,9 +144,9 @@ Rails.application.config.to_prepare do
|
|
144
144
|
# Define which associations can be searched
|
145
145
|
def self.ransackable_associations(auth_object = nil)
|
146
146
|
associations = %w[query]
|
147
|
-
associations <<
|
147
|
+
associations << "creator" if Blazer.user_class
|
148
148
|
associations
|
149
149
|
end
|
150
150
|
end
|
151
151
|
end
|
152
|
-
end
|
152
|
+
end
|
data/config/routes.rb
CHANGED
@@ -5,7 +5,7 @@ Sage::Engine.routes.draw do
|
|
5
5
|
post :refresh, on: :member
|
6
6
|
get :run, on: :member
|
7
7
|
|
8
|
-
resources :messages, only: [:index, :create], controller:
|
8
|
+
resources :messages, only: [ :index, :create ], controller: "queries/messages"
|
9
9
|
|
10
10
|
collection do
|
11
11
|
post :run
|
@@ -20,7 +20,6 @@ module Sage
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def add_routes
|
23
|
-
# Remove existing Blazer route if present
|
24
23
|
routes_file = "config/routes.rb"
|
25
24
|
if File.exist?(routes_file)
|
26
25
|
routes_content = File.read(routes_file)
|
@@ -29,15 +28,19 @@ module Sage
|
|
29
28
|
blazer_route_pattern = /^\s*mount\s+Blazer::Engine\s*,\s*at:\s*['"]blazer['"]\s*$/
|
30
29
|
|
31
30
|
if routes_content.match?(blazer_route_pattern)
|
32
|
-
#
|
33
|
-
gsub_file routes_file, blazer_route_pattern, ""
|
34
|
-
say "
|
31
|
+
# Replace the Blazer route with Sage route
|
32
|
+
gsub_file routes_file, blazer_route_pattern, ' mount Sage::Engine => "/sage"'
|
33
|
+
say "Replaced Blazer route with Sage route at /sage", :green
|
34
|
+
else
|
35
|
+
# No existing Blazer route, add Sage route
|
36
|
+
route 'mount Sage::Engine => "/sage"'
|
37
|
+
say "Mounted Sage at /sage", :green
|
35
38
|
end
|
39
|
+
else
|
40
|
+
# No routes file, add Sage route
|
41
|
+
route 'mount Sage::Engine => "/sage"'
|
42
|
+
say "Mounted Sage at /sage", :green
|
36
43
|
end
|
37
|
-
|
38
|
-
# Mount Sage (which includes Blazer functionality)
|
39
|
-
route 'mount Sage::Engine => "/sage"'
|
40
|
-
say "Mounted Sage at /sage", :green
|
41
44
|
end
|
42
45
|
|
43
46
|
def create_initializer
|
@@ -72,31 +75,6 @@ module Sage
|
|
72
75
|
say "Created migration for sage_messages table", :green
|
73
76
|
end
|
74
77
|
|
75
|
-
def add_javascript_integration
|
76
|
-
say "Configuring JavaScript integration...", :green
|
77
|
-
|
78
|
-
# Update controllers/index.js to register Sage controllers
|
79
|
-
controllers_index_path = "app/javascript/controllers/index.js"
|
80
|
-
if File.exist?(controllers_index_path)
|
81
|
-
controllers_content = File.read(controllers_index_path)
|
82
|
-
unless controllers_content.include?("sage")
|
83
|
-
append_to_file controllers_index_path do
|
84
|
-
<<~JS
|
85
|
-
|
86
|
-
// Import and register Sage controllers
|
87
|
-
import { registerControllers } from "sage"
|
88
|
-
registerControllers(application)
|
89
|
-
JS
|
90
|
-
end
|
91
|
-
say "Updated controllers/index.js to register Sage controllers", :green
|
92
|
-
else
|
93
|
-
say "Sage controllers already registered in controllers/index.js", :yellow
|
94
|
-
end
|
95
|
-
else
|
96
|
-
say "Could not find app/javascript/controllers/index.js - you'll need to manually import Sage controllers", :yellow
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
78
|
def add_stylesheets
|
101
79
|
# Stylesheets are served directly from the engine via the asset pipeline
|
102
80
|
# No need to copy or require them - they're automatically available
|
data/lib/sage/engine.rb
CHANGED
@@ -141,8 +141,8 @@ module Sage
|
|
141
141
|
end
|
142
142
|
|
143
143
|
initializer "sage.assets" do |app|
|
144
|
-
# Add JavaScript
|
145
|
-
if
|
144
|
+
# Add JavaScript paths based on asset pipeline
|
145
|
+
if app.config.respond_to?(:assets)
|
146
146
|
app.config.assets.paths << Engine.root.join("app/javascript")
|
147
147
|
app.config.assets.paths << Engine.root.join("app/javascript/sage")
|
148
148
|
end
|
@@ -43,20 +43,20 @@ module Sage
|
|
43
43
|
|
44
44
|
def collect_models_with_scopes
|
45
45
|
models_with_scopes = []
|
46
|
-
|
46
|
+
|
47
47
|
# Find all model files in the app/models directory
|
48
48
|
model_files = Dir.glob(Rails.root.join("app/models/**/*.rb"))
|
49
|
-
|
49
|
+
|
50
50
|
model_files.each do |file_path|
|
51
51
|
# Skip concern files and other non-model files
|
52
52
|
next if file_path.include?("/concerns/")
|
53
|
-
|
53
|
+
|
54
54
|
# Read the file content
|
55
55
|
file_content = File.read(file_path)
|
56
|
-
|
56
|
+
|
57
57
|
# Extract model name from file path
|
58
58
|
model_name = File.basename(file_path, ".rb").camelize
|
59
|
-
|
59
|
+
|
60
60
|
# Find all scope definitions using regex
|
61
61
|
# Match various scope patterns:
|
62
62
|
# scope :active, -> { where(active: true) }
|
@@ -70,7 +70,7 @@ module Sage
|
|
70
70
|
# Pattern 3: Simple one-liner scopes
|
71
71
|
/scope\s+:(\w+)\s*,\s*(.+?)$/
|
72
72
|
]
|
73
|
-
|
73
|
+
|
74
74
|
scope_matches = []
|
75
75
|
scope_patterns.each do |pattern|
|
76
76
|
matches = file_content.scan(pattern)
|
@@ -79,11 +79,11 @@ module Sage
|
|
79
79
|
scope_body = match[1] || ""
|
80
80
|
# Avoid duplicate entries
|
81
81
|
unless scope_matches.any? { |s| s[0] == scope_name }
|
82
|
-
scope_matches << [scope_name, scope_body]
|
82
|
+
scope_matches << [ scope_name, scope_body ]
|
83
83
|
end
|
84
84
|
end
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
if scope_matches.any?
|
88
88
|
# Try to get the actual model class and table name
|
89
89
|
begin
|
@@ -93,17 +93,17 @@ module Sage
|
|
93
93
|
table_name = model_name.tableize
|
94
94
|
model_class = nil
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
model_info = {
|
98
98
|
name: model_name,
|
99
99
|
table: table_name,
|
100
100
|
scopes: []
|
101
101
|
}
|
102
|
-
|
102
|
+
|
103
103
|
scope_matches.each do |match|
|
104
104
|
scope_name = match[0]
|
105
105
|
scope_body = match[1]
|
106
|
-
|
106
|
+
|
107
107
|
if scope_body
|
108
108
|
# Try to extract SQL-like patterns from the scope body
|
109
109
|
# Look for where conditions, joins, etc.
|
@@ -114,20 +114,20 @@ module Sage
|
|
114
114
|
model_info[:scopes] << " • `#{scope_name}` → (check model file for implementation)"
|
115
115
|
end
|
116
116
|
end
|
117
|
-
|
117
|
+
|
118
118
|
models_with_scopes << model_info if model_info[:scopes].any?
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
122
122
|
models_with_scopes
|
123
123
|
end
|
124
|
-
|
124
|
+
|
125
125
|
def extract_sql_from_scope_body(scope_body)
|
126
126
|
# Clean up the scope body
|
127
127
|
cleaned = scope_body.strip
|
128
|
-
|
128
|
+
|
129
129
|
sql_parts = []
|
130
|
-
|
130
|
+
|
131
131
|
# Extract WHERE conditions
|
132
132
|
if cleaned =~ /where\s*\(["']([^"']+)["'](?:,\s*(.+?))?\)/
|
133
133
|
# String SQL with potential parameters
|
@@ -146,14 +146,14 @@ module Sage
|
|
146
146
|
not_conditions = not_conditions.gsub(/(\w+):\s*/, '\1 != ')
|
147
147
|
sql_parts << "WHERE NOT (#{not_conditions})"
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
# Extract JOINs
|
151
151
|
if cleaned =~ /joins?\s*\(:?(\w+)\)/
|
152
152
|
sql_parts << "JOIN #{$1}"
|
153
153
|
elsif cleaned =~ /includes?\s*\(:?(\w+)\)/
|
154
154
|
sql_parts << "LEFT JOIN #{$1}"
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
157
|
# Extract ORDER
|
158
158
|
if cleaned =~ /order\s*\(["']([^"']+)["']\)/
|
159
159
|
sql_parts << "ORDER BY #{$1}"
|
@@ -162,12 +162,12 @@ module Sage
|
|
162
162
|
order_clause = order_clause.gsub(/(\w+):\s*:?(asc|desc)/i, '\1 \2')
|
163
163
|
sql_parts << "ORDER BY #{order_clause}"
|
164
164
|
end
|
165
|
-
|
165
|
+
|
166
166
|
# Extract LIMIT
|
167
167
|
if cleaned =~ /limit\s*\((\d+)\)/
|
168
168
|
sql_parts << "LIMIT #{$1}"
|
169
169
|
end
|
170
|
-
|
170
|
+
|
171
171
|
# If we found SQL parts, join them
|
172
172
|
if sql_parts.any?
|
173
173
|
sql_parts.join(" ")
|
data/lib/sage/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sage-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Jones
|
@@ -140,6 +140,7 @@ executables: []
|
|
140
140
|
extensions: []
|
141
141
|
extra_rdoc_files: []
|
142
142
|
files:
|
143
|
+
- CONTRIBUTING.md
|
143
144
|
- README.md
|
144
145
|
- app/assets/images/chevron-down-zinc-500.svg
|
145
146
|
- app/assets/images/chevron-right.svg
|
@@ -147,7 +148,14 @@ files:
|
|
147
148
|
- app/assets/images/sage/chevron-down-zinc-500.svg
|
148
149
|
- app/assets/images/sage/chevron-right.svg
|
149
150
|
- app/assets/images/sage/loading.svg
|
151
|
+
- app/assets/javascripts/sage.js
|
150
152
|
- app/assets/javascripts/sage/application.js
|
153
|
+
- app/assets/javascripts/sage/controllers/clipboard_controller.js
|
154
|
+
- app/assets/javascripts/sage/controllers/dashboard_controller.js
|
155
|
+
- app/assets/javascripts/sage/controllers/reverse_infinite_scroll_controller.js
|
156
|
+
- app/assets/javascripts/sage/controllers/search_controller.js
|
157
|
+
- app/assets/javascripts/sage/controllers/select_controller.js
|
158
|
+
- app/assets/javascripts/sage/controllers/variables_controller.js
|
151
159
|
- app/assets/stylesheets/sage/application.css
|
152
160
|
- app/controllers/sage/actions_controller.rb
|
153
161
|
- app/controllers/sage/application_controller.rb
|
@@ -165,6 +173,7 @@ files:
|
|
165
173
|
- app/javascript/sage/controllers/reverse_infinite_scroll_controller.js
|
166
174
|
- app/javascript/sage/controllers/search_controller.js
|
167
175
|
- app/javascript/sage/controllers/select_controller.js
|
176
|
+
- app/javascript/sage/controllers/variables_controller.js
|
168
177
|
- app/jobs/sage/application_job.rb
|
169
178
|
- app/jobs/sage/process_report_job.rb
|
170
179
|
- app/mailers/sage/application_mailer.rb
|
@@ -173,6 +182,7 @@ files:
|
|
173
182
|
- app/schemas/sage/report_response_schema.rb
|
174
183
|
- app/views/layouts/application.html.erb
|
175
184
|
- app/views/layouts/sage/application.html.erb
|
185
|
+
- app/views/sage/_variables.html.erb
|
176
186
|
- app/views/sage/checks/_form.html.erb
|
177
187
|
- app/views/sage/checks/_search.html.erb
|
178
188
|
- app/views/sage/checks/edit.html.erb
|