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.
@@ -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: "blazer/variables", locals: {action: dashboard_path(@dashboard)} %>
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: "blazer/variables", locals: { action: query_path(@query) } %>
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 'ransack'
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 'blazer' if defined?(Blazer)
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 << 'creator' if Blazer.user_class
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 << 'creator' if Blazer.user_class
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 << 'creator' if Blazer.user_class
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 << 'creator' if Blazer.user_class
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 << 'creator' if Blazer.user_class
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: 'queries/messages'
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
- # Remove the Blazer route
33
- gsub_file routes_file, blazer_route_pattern, ""
34
- say "Removed existing Blazer route", :yellow
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 path for Propshaft
145
- if defined?(Propshaft::Railtie)
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
@@ -1,3 +1,3 @@
1
1
  module Sage
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.8"
3
3
  end
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.6
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