sage-rails 0.0.3
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/README.md +202 -0
- data/app/assets/images/chevron-down-zinc-500.svg +1 -0
- data/app/assets/images/chevron-right.svg +1 -0
- data/app/assets/images/loading.svg +4 -0
- data/app/assets/images/sage/chevron-down-zinc-500.svg +1 -0
- data/app/assets/images/sage/chevron-right.svg +1 -0
- data/app/assets/images/sage/loading.svg +4 -0
- data/app/assets/javascripts/sage/application.js +18 -0
- data/app/assets/stylesheets/sage/application.css +308 -0
- data/app/controllers/sage/actions_controller.rb +5 -0
- data/app/controllers/sage/application_controller.rb +4 -0
- data/app/controllers/sage/base_controller.rb +10 -0
- data/app/controllers/sage/checks_controller.rb +65 -0
- data/app/controllers/sage/dashboards_controller.rb +130 -0
- data/app/controllers/sage/queries/messages_controller.rb +62 -0
- data/app/controllers/sage/queries_controller.rb +596 -0
- data/app/helpers/sage/application_helper.rb +30 -0
- data/app/helpers/sage/queries_helper.rb +23 -0
- data/app/javascript/controllers/element_removal_controller.js +7 -0
- data/app/javascript/sage/controllers/clipboard_controller.js +26 -0
- data/app/javascript/sage/controllers/dashboard_controller.js +132 -0
- data/app/javascript/sage/controllers/reverse_infinite_scroll_controller.js +146 -0
- data/app/javascript/sage/controllers/search_controller.js +47 -0
- data/app/javascript/sage/controllers/select_controller.js +215 -0
- data/app/javascript/sage.js +19 -0
- data/app/jobs/sage/application_job.rb +4 -0
- data/app/jobs/sage/process_report_job.rb +80 -0
- data/app/mailers/sage/application_mailer.rb +6 -0
- data/app/models/sage/application_record.rb +5 -0
- data/app/models/sage/message.rb +8 -0
- data/app/schemas/sage/report_response_schema.rb +8 -0
- data/app/views/layouts/application.html.erb +34 -0
- data/app/views/layouts/sage/application.html.erb +94 -0
- data/app/views/sage/checks/_form.html.erb +81 -0
- data/app/views/sage/checks/_search.html.erb +8 -0
- data/app/views/sage/checks/edit.html.erb +10 -0
- data/app/views/sage/checks/index.html.erb +58 -0
- data/app/views/sage/checks/new.html.erb +8 -0
- data/app/views/sage/dashboards/_form.html.erb +50 -0
- data/app/views/sage/dashboards/_search.html.erb +8 -0
- data/app/views/sage/dashboards/index.html.erb +58 -0
- data/app/views/sage/dashboards/new.html.erb +8 -0
- data/app/views/sage/dashboards/show.html.erb +58 -0
- data/app/views/sage/messages/_form.html.erb +14 -0
- data/app/views/sage/queries/_caching.html.erb +17 -0
- data/app/views/sage/queries/_form.html.erb +72 -0
- data/app/views/sage/queries/_input.html.erb +17 -0
- data/app/views/sage/queries/_message.html.erb +25 -0
- data/app/views/sage/queries/_message.turbo_stream.erb +10 -0
- data/app/views/sage/queries/_new_form.html.erb +43 -0
- data/app/views/sage/queries/_run.html.erb +232 -0
- data/app/views/sage/queries/_search.html.erb +8 -0
- data/app/views/sage/queries/_statement_box.html.erb +241 -0
- data/app/views/sage/queries/_streaming_message.html.erb +14 -0
- data/app/views/sage/queries/create.turbo_stream.erb +114 -0
- data/app/views/sage/queries/edit.html.erb +48 -0
- data/app/views/sage/queries/index.html.erb +59 -0
- data/app/views/sage/queries/messages/create.turbo_stream.erb +22 -0
- data/app/views/sage/queries/messages/index.html.erb +44 -0
- data/app/views/sage/queries/messages/index.turbo_stream.erb +15 -0
- data/app/views/sage/queries/new.html.erb +195 -0
- data/app/views/sage/queries/run.html.erb +1 -0
- data/app/views/sage/queries/run.turbo_stream.erb +3 -0
- data/app/views/sage/queries/show.html.erb +49 -0
- data/app/views/sage/queries/table_schema.html.erb +77 -0
- data/app/views/sage/shared/_navigation.html.erb +26 -0
- data/app/views/sage/shared/_overlay.html.erb +11 -0
- data/config/importmap.rb +11 -0
- data/config/initializers/pagy.rb +2 -0
- data/config/initializers/ransack.rb +152 -0
- data/config/routes.rb +31 -0
- data/lib/generators/sage/USAGE +13 -0
- data/lib/generators/sage/install/install_generator.rb +128 -0
- data/lib/generators/sage/install/templates/sage.rb +22 -0
- data/lib/sage/database_schema_context.rb +56 -0
- data/lib/sage/engine.rb +260 -0
- data/lib/sage/model_scopes_context.rb +185 -0
- data/lib/sage/report_processor.rb +263 -0
- data/lib/sage/version.rb +3 -0
- data/lib/sage.rb +25 -0
- data/lib/tasks/sage_tasks.rake +4 -0
- metadata +245 -0
@@ -0,0 +1,130 @@
|
|
1
|
+
module Sage
|
2
|
+
class DashboardsController < BaseController
|
3
|
+
before_action :set_dashboard, only: [:show, :edit, :update, :destroy, :refresh]
|
4
|
+
|
5
|
+
def index
|
6
|
+
@q = Blazer::Dashboard.ransack(params[:q])
|
7
|
+
@dashboards = @q.result
|
8
|
+
|
9
|
+
# Only include creator if Blazer.user_class is configured
|
10
|
+
@dashboards = @dashboards.includes(:creator) if Blazer.user_class
|
11
|
+
|
12
|
+
@dashboards = @dashboards.order(:name)
|
13
|
+
|
14
|
+
# Apply additional filters if needed
|
15
|
+
if blazer_user && params[:filter] == "mine"
|
16
|
+
@dashboards = @dashboards.where(creator_id: blazer_user.id).reorder(updated_at: :desc)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Apply pagination with Pagy
|
20
|
+
@pagy, @dashboards = pagy(@dashboards)
|
21
|
+
end
|
22
|
+
|
23
|
+
def new
|
24
|
+
@dashboard = Blazer::Dashboard.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def create
|
28
|
+
@dashboard = Blazer::Dashboard.new
|
29
|
+
# use creator_id instead of creator
|
30
|
+
# since we setup association without checking if column exists
|
31
|
+
@dashboard.creator = blazer_user if @dashboard.respond_to?(:creator_id=) && blazer_user
|
32
|
+
|
33
|
+
if update_dashboard(@dashboard)
|
34
|
+
redirect_to dashboard_path(@dashboard)
|
35
|
+
else
|
36
|
+
render_errors @dashboard
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def show
|
41
|
+
@queries = @dashboard.dashboard_queries.order(:position).preload(:query).map(&:query)
|
42
|
+
@query_errors = {}
|
43
|
+
|
44
|
+
@queries.each do |query|
|
45
|
+
# Check if the query has a valid data source
|
46
|
+
if query.data_source.blank? || !Blazer.data_sources.key?(query.data_source)
|
47
|
+
@query_errors[query.id] = "Invalid or missing data source: #{query.data_source.inspect}"
|
48
|
+
else
|
49
|
+
@success = process_vars(query.statement_object)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
@bind_vars ||= []
|
53
|
+
|
54
|
+
@smart_vars = {}
|
55
|
+
@sql_errors = []
|
56
|
+
@data_sources = @queries.map { |q|
|
57
|
+
# Use the query's data source if specified, otherwise use the default
|
58
|
+
source = q.data_source.presence || Blazer.data_sources.keys.first
|
59
|
+
Blazer.data_sources[source]
|
60
|
+
}.compact.uniq
|
61
|
+
|
62
|
+
@bind_vars.each do |var|
|
63
|
+
@data_sources.each do |data_source|
|
64
|
+
smart_var, error = parse_smart_variables(var, data_source)
|
65
|
+
((@smart_vars[var] ||= []).concat(smart_var)).uniq! if smart_var
|
66
|
+
@sql_errors << error if error
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
add_cohort_analysis_vars if @queries.any?(&:cohort_analysis?)
|
71
|
+
end
|
72
|
+
|
73
|
+
def edit
|
74
|
+
end
|
75
|
+
|
76
|
+
def update
|
77
|
+
if update_dashboard(@dashboard)
|
78
|
+
redirect_to dashboard_path(@dashboard, params: variable_params(@dashboard))
|
79
|
+
else
|
80
|
+
render_errors @dashboard
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def destroy
|
85
|
+
@dashboard.destroy
|
86
|
+
redirect_to root_path
|
87
|
+
end
|
88
|
+
|
89
|
+
def refresh
|
90
|
+
@dashboard.queries.each do |query|
|
91
|
+
refresh_query(query)
|
92
|
+
end
|
93
|
+
redirect_to dashboard_path(@dashboard, params: variable_params(@dashboard))
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def dashboard_params
|
99
|
+
params.require(:dashboard).permit(:name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_dashboard
|
103
|
+
@dashboard = Blazer::Dashboard.find(params[:id])
|
104
|
+
end
|
105
|
+
|
106
|
+
def update_dashboard(dashboard)
|
107
|
+
dashboard.assign_attributes(dashboard_params)
|
108
|
+
Blazer::Dashboard.transaction do
|
109
|
+
if params[:query_ids].is_a?(Array)
|
110
|
+
query_ids = params[:query_ids].map(&:to_i)
|
111
|
+
@queries = Blazer::Query.find(query_ids).sort_by { |q| query_ids.index(q.id) }
|
112
|
+
end
|
113
|
+
if dashboard.save
|
114
|
+
if @queries
|
115
|
+
@queries.each_with_index do |query, i|
|
116
|
+
dashboard_query = dashboard.dashboard_queries.where(query_id: query.id).first_or_initialize
|
117
|
+
dashboard_query.position = i
|
118
|
+
dashboard_query.save!
|
119
|
+
end
|
120
|
+
if dashboard.persisted?
|
121
|
+
dashboard.dashboard_queries.where.not(query_id: query_ids).destroy_all
|
122
|
+
end
|
123
|
+
end
|
124
|
+
true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
module Sage
|
3
|
+
module Queries
|
4
|
+
class MessagesController < BaseController
|
5
|
+
before_action :set_query
|
6
|
+
|
7
|
+
def index
|
8
|
+
page = params[:page] || 1
|
9
|
+
|
10
|
+
# For reverse infinite scroll, we want:
|
11
|
+
# Page 1: Most recent messages (to show at bottom initially)
|
12
|
+
# Page 2: Older messages (to prepend when scrolling up)
|
13
|
+
# Page 3: Even older messages, etc.
|
14
|
+
|
15
|
+
base_query = @query.messages.order(created_at: :desc)
|
16
|
+
@pagy, messages = pagy(base_query, page: page, overflow: :last_page)
|
17
|
+
|
18
|
+
# For page 1 (initial load), show newest messages in chronological order (oldest first)
|
19
|
+
# For page 2+, keep reverse chronological order for prepending
|
20
|
+
if page.to_i == 1
|
21
|
+
@messages = messages.reverse # Show oldest first for initial display
|
22
|
+
else
|
23
|
+
@messages = messages # Keep newest first for prepending
|
24
|
+
end
|
25
|
+
|
26
|
+
respond_to do |format|
|
27
|
+
format.html # For turbo_frame initial load
|
28
|
+
format.turbo_stream # For infinite scroll pagination
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create
|
33
|
+
@message = @query.messages.create!(body: params[:statement])
|
34
|
+
creator_id = blazer_user.present? ? blazer_user.id : 0
|
35
|
+
|
36
|
+
@message.creator_id = creator_id
|
37
|
+
@message.save!
|
38
|
+
@message.reload
|
39
|
+
|
40
|
+
stream_target_id = SecureRandom.hex(8)
|
41
|
+
|
42
|
+
Sage::ProcessReportJob.perform_later(
|
43
|
+
@message.body,
|
44
|
+
query_id: @message.blazer_query.id,
|
45
|
+
stream_target_id:
|
46
|
+
)
|
47
|
+
|
48
|
+
# Check if we need a new day separator
|
49
|
+
@need_day_separator = @query.messages.where(
|
50
|
+
"DATE(created_at) = ?",
|
51
|
+
@message.created_at.to_date
|
52
|
+
).count == 1
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def set_query
|
58
|
+
@query = Blazer::Query.find(params[:query_id])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|