sage-rails 0.1.2 → 0.1.4
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/README.md +8 -0
- data/app/assets/javascripts/sage/controllers/query_toggle_controller.js +50 -6
- data/app/controllers/sage/queries_controller.rb +2 -1
- data/app/javascript/sage/application.js +16 -0
- data/app/javascript/sage/controllers/query_toggle_controller.js +48 -6
- data/app/javascript/sage.js +1 -1
- data/app/views/layouts/sage/application.html.erb +1 -1
- data/app/views/sage/queries/_new_form.html.erb +15 -13
- data/app/views/sage/queries/_run.html.erb +4 -4
- data/app/views/sage/queries/show.html.erb +14 -3
- data/config/importmap.rb +3 -0
- data/lib/sage/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cdf45a19f9379935c65e7ee18b70a4cc2ce0df00b71fe13586c89ffe15180524
|
|
4
|
+
data.tar.gz: 86c1c3fda7b28206020da469995a5bcc6215c61b9249e60e0b121307b31ab66d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 717576a4f348569e8610e9675132456b8d213156e666b930c1600e55c513583a05086e4d1082d4efc90ba7a7b3bccd76fbbde81b4e883c318fc94b3fe14a248c
|
|
7
|
+
data.tar.gz: c3c47a9212d72c6360a3cadcf2e4f20db07d16ad5fe9864faad84a95011ca164d251b0ad2df36e0c0e097e57d6549e9025f25061d000e3dcfa1974b659b9c6f6
|
data/README.md
CHANGED
|
@@ -53,6 +53,14 @@ After installation, run the migrations:
|
|
|
53
53
|
$ rails db:migrate
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
### Sprockets
|
|
57
|
+
|
|
58
|
+
If using sprockets, add this line to your app/javascript/application.js file:
|
|
59
|
+
```js
|
|
60
|
+
import "sage/application";
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
|
|
56
64
|
## LLM Configuration
|
|
57
65
|
|
|
58
66
|
Sage supports both Anthropic Claude and OpenAI models for SQL generation. Configure your preferred AI service in `config/initializers/sage.rb`:
|
|
@@ -2,21 +2,65 @@ import { Controller } from "@hotwired/stimulus";
|
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
static targets = ["content", "icon"]
|
|
5
|
+
static values = {
|
|
6
|
+
storageKey: { type: String, default: "sage_query_sql_visibility" }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
// Load saved visibility state from localStorage
|
|
11
|
+
this.loadVisibilityState()
|
|
12
|
+
}
|
|
5
13
|
|
|
6
14
|
toggle() {
|
|
7
15
|
try {
|
|
8
16
|
const content = this.contentTarget
|
|
9
17
|
const icon = this.iconTarget
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
|
|
19
|
+
const isHidden = window.getComputedStyle(content).display === "none"
|
|
20
|
+
|
|
21
|
+
if (isHidden) {
|
|
22
|
+
this.showContent()
|
|
14
23
|
} else {
|
|
15
|
-
|
|
16
|
-
icon.textContent = "visibility"
|
|
24
|
+
this.hideContent()
|
|
17
25
|
}
|
|
18
26
|
} catch (error) {
|
|
19
27
|
console.error("Error in toggle:", error)
|
|
20
28
|
}
|
|
21
29
|
}
|
|
30
|
+
|
|
31
|
+
showContent() {
|
|
32
|
+
this.contentTarget.style.display = "block"
|
|
33
|
+
this.iconTarget.textContent = "visibility_off"
|
|
34
|
+
this.saveVisibilityState(true)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
hideContent() {
|
|
38
|
+
this.contentTarget.style.display = "none"
|
|
39
|
+
this.iconTarget.textContent = "visibility"
|
|
40
|
+
this.saveVisibilityState(false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
saveVisibilityState(isVisible) {
|
|
44
|
+
try {
|
|
45
|
+
localStorage.setItem(this.storageKeyValue, JSON.stringify(isVisible))
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("Error saving visibility state:", error)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
loadVisibilityState() {
|
|
52
|
+
try {
|
|
53
|
+
const savedState = localStorage.getItem(this.storageKeyValue)
|
|
54
|
+
if (savedState !== null) {
|
|
55
|
+
const isVisible = JSON.parse(savedState)
|
|
56
|
+
if (isVisible) {
|
|
57
|
+
this.showContent()
|
|
58
|
+
} else {
|
|
59
|
+
this.hideContent()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("Error loading visibility state:", error)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
22
66
|
}
|
|
@@ -140,8 +140,9 @@ module Sage
|
|
|
140
140
|
@query ||= Blazer::Query.find_by(id: params[:query_id]) if params[:query_id]
|
|
141
141
|
|
|
142
142
|
# use query data source when present
|
|
143
|
-
data_source = @query.data_source if @query && @query.data_source
|
|
143
|
+
data_source = @query.data_source if @query && @query.data_source.present?
|
|
144
144
|
data_source ||= params[:data_source]
|
|
145
|
+
data_source ||= "main" # Fallback to main data source
|
|
145
146
|
@data_source = Blazer.data_sources[data_source]
|
|
146
147
|
|
|
147
148
|
# Prefer params statement over query's saved statement (for live editing)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Sage Stimulus controllers initialization
|
|
2
|
+
// This file is loaded via importmap and registers all Sage controllers with the host app's Stimulus instance
|
|
3
|
+
|
|
4
|
+
import { registerControllers } from "sage"
|
|
5
|
+
|
|
6
|
+
// Wait for the host app's Stimulus to be available
|
|
7
|
+
if (window.Stimulus) {
|
|
8
|
+
registerControllers(window.Stimulus)
|
|
9
|
+
} else {
|
|
10
|
+
// If Stimulus isn't ready yet, wait for it
|
|
11
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
12
|
+
if (window.Stimulus) {
|
|
13
|
+
registerControllers(window.Stimulus)
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
}
|
|
@@ -2,23 +2,65 @@ import { Controller } from "@hotwired/stimulus";
|
|
|
2
2
|
|
|
3
3
|
export default class extends Controller {
|
|
4
4
|
static targets = ["content", "icon"]
|
|
5
|
+
static values = {
|
|
6
|
+
storageKey: { type: String, default: "sage_query_sql_visibility" }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
connect() {
|
|
10
|
+
// Load saved visibility state from localStorage
|
|
11
|
+
this.loadVisibilityState()
|
|
12
|
+
}
|
|
5
13
|
|
|
6
14
|
toggle() {
|
|
7
15
|
try {
|
|
8
16
|
const content = this.contentTarget
|
|
9
17
|
const icon = this.iconTarget
|
|
10
|
-
|
|
18
|
+
|
|
11
19
|
const isHidden = window.getComputedStyle(content).display === "none"
|
|
12
|
-
|
|
20
|
+
|
|
13
21
|
if (isHidden) {
|
|
14
|
-
|
|
15
|
-
icon.textContent = "visibility_off"
|
|
22
|
+
this.showContent()
|
|
16
23
|
} else {
|
|
17
|
-
|
|
18
|
-
icon.textContent = "visibility"
|
|
24
|
+
this.hideContent()
|
|
19
25
|
}
|
|
20
26
|
} catch (error) {
|
|
21
27
|
console.error("Error in toggle:", error)
|
|
22
28
|
}
|
|
23
29
|
}
|
|
30
|
+
|
|
31
|
+
showContent() {
|
|
32
|
+
this.contentTarget.style.display = "block"
|
|
33
|
+
this.iconTarget.textContent = "visibility_off"
|
|
34
|
+
this.saveVisibilityState(true)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
hideContent() {
|
|
38
|
+
this.contentTarget.style.display = "none"
|
|
39
|
+
this.iconTarget.textContent = "visibility"
|
|
40
|
+
this.saveVisibilityState(false)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
saveVisibilityState(isVisible) {
|
|
44
|
+
try {
|
|
45
|
+
localStorage.setItem(this.storageKeyValue, JSON.stringify(isVisible))
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("Error saving visibility state:", error)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
loadVisibilityState() {
|
|
52
|
+
try {
|
|
53
|
+
const savedState = localStorage.getItem(this.storageKeyValue)
|
|
54
|
+
if (savedState !== null) {
|
|
55
|
+
const isVisible = JSON.parse(savedState)
|
|
56
|
+
if (isVisible) {
|
|
57
|
+
this.showContent()
|
|
58
|
+
} else {
|
|
59
|
+
this.hideContent()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error("Error loading visibility state:", error)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
24
66
|
}
|
data/app/javascript/sage.js
CHANGED
|
@@ -5,7 +5,7 @@ import SelectController from "sage/controllers/select_controller"
|
|
|
5
5
|
import DashboardController from "sage/controllers/dashboard_controller"
|
|
6
6
|
import ReverseInfiniteScrollController from "sage/controllers/reverse_infinite_scroll_controller"
|
|
7
7
|
import VariablesController from "sage/controllers/variables_controller"
|
|
8
|
-
import QueryToggleController from "sage/controllers/query_toggle_controller
|
|
8
|
+
import QueryToggleController from "sage/controllers/query_toggle_controller"
|
|
9
9
|
|
|
10
10
|
// Export all Sage controllers for manual registration
|
|
11
11
|
export { SearchController, ClipboardController, SelectController, DashboardController, ReverseInfiniteScrollController, VariablesController, QueryToggleController }
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<%= javascript_include_tag "blazer/jquery", "blazer/rails-ujs", "blazer/stupidtable", "blazer/stupidtable-custom-settings", "blazer/jquery.stickytableheaders", "blazer/selectize", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/chart.umd", "blazer/chartjs-adapter-date-fns.bundle", "blazer/chartkick", "blazer/mapkick.bundle", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/vue.global.prod", "blazer/routes", "blazer/queries", "blazer/fuzzysearch", nonce: true %>
|
|
14
14
|
<% elsif defined?(Sprockets) %>
|
|
15
15
|
<%= stylesheet_link_tag "sage/application", "blazer/selectize", "blazer/daterangepicker" %>
|
|
16
|
-
<%= javascript_include_tag "blazer/jquery", "blazer/rails-ujs", "blazer/stupidtable", "blazer/stupidtable-custom-settings", "blazer/jquery.stickytableheaders", "blazer/selectize", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/chart.umd", "blazer/chartjs-adapter-date-fns.bundle", "blazer/chartkick", "blazer/mapkick.bundle", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/vue.global.prod", "blazer/routes", "blazer/queries", "blazer/fuzzysearch",
|
|
16
|
+
<%= javascript_include_tag "blazer/jquery", "blazer/rails-ujs", "blazer/stupidtable", "blazer/stupidtable-custom-settings", "blazer/jquery.stickytableheaders", "blazer/selectize", "blazer/highlight.min", "blazer/moment", "blazer/moment-timezone-with-data", "blazer/daterangepicker", "blazer/chart.umd", "blazer/chartjs-adapter-date-fns.bundle", "blazer/chartkick", "blazer/mapkick.bundle", "blazer/ace/ace", "blazer/ace/ext-language_tools", "blazer/ace/theme-twilight", "blazer/ace/mode-sql", "blazer/ace/snippets/text", "blazer/ace/snippets/sql", "blazer/Sortable", "blazer/vue.global.prod", "blazer/routes", "blazer/queries", "blazer/fuzzysearch", nonce: true %>
|
|
17
17
|
<% else %>
|
|
18
18
|
<%= stylesheet_link_tag "sage/application" %>
|
|
19
19
|
<%= javascript_importmap_tags %>
|
|
@@ -10,36 +10,38 @@
|
|
|
10
10
|
|
|
11
11
|
<% @variable_params = @query.persisted? ? variable_params(@query) : nested_variable_params(@query) %>
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
<!-- Hidden Run button form outside the main form -->
|
|
14
|
+
<%= form_with url: (defined?(sage) && sage.respond_to?(:run_queries_path) ? sage.run_queries_path : run_queries_path), method: :post, local: false, html: {id: 'run-query-form', style: "display: none;"} do |f| %>
|
|
15
|
+
<%= f.hidden_field :statement, id: 'run_query_statement' %>
|
|
16
|
+
<%= f.hidden_field :query_id, value: @query.id %>
|
|
17
|
+
<%= f.hidden_field :data_source, value: @query.data_source || Blazer.data_sources.keys.first %>
|
|
18
|
+
<% end %>
|
|
19
|
+
|
|
20
|
+
<%= form_for @query, url: (@query.persisted? ? query_path(@query, params: @variable_params) : queries_path(params: @variable_params)), html: {autocomplete: "off", class: "", id: 'query-form'} do |f| %>
|
|
14
21
|
<div class="grid" style='margin-top: 0px'>
|
|
15
22
|
<div class="s10">
|
|
16
23
|
<div class="field label border">
|
|
17
24
|
<%= f.text_field :name %>
|
|
18
25
|
<%= f.label :name, class: (@query.name.present? ? "active" : "") %>
|
|
19
26
|
</div>
|
|
20
|
-
|
|
27
|
+
|
|
21
28
|
<div class="field textarea label border">
|
|
22
29
|
<%= f.text_area :description, style: "height: 145px; resize: vertical;" %>
|
|
23
30
|
<%= f.label :description, "Description (Optional)", class: (@query.description.present? ? "active" : "") %>
|
|
24
31
|
</div>
|
|
25
|
-
|
|
32
|
+
|
|
26
33
|
<%= f.hidden_field :statement, id: 'query_statement_form' %>
|
|
27
34
|
</div>
|
|
28
|
-
|
|
35
|
+
|
|
29
36
|
<div class="s2">
|
|
30
37
|
<nav style="display: flex; flex-direction: column; gap: 12px; padding-left: 8px;">
|
|
31
38
|
<button type='button' onclick="formatSQL()" class="button secondary" title="Format SQL">
|
|
32
39
|
<i>auto_fix_high</i>
|
|
33
40
|
</button>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<%= f.hidden_field :data_source, value: @query.data_source || Blazer.data_sources.keys.first %>
|
|
39
|
-
<button type='submit' class="button primary" title="Run Query">
|
|
40
|
-
<i>play_arrow</i>
|
|
41
|
-
</button>
|
|
42
|
-
<% end %>
|
|
41
|
+
|
|
42
|
+
<button type='button' onclick="document.getElementById('run-query-form').requestSubmit()" class="button primary" title="Run Query">
|
|
43
|
+
<i>play_arrow</i>
|
|
44
|
+
</button>
|
|
43
45
|
|
|
44
46
|
<button type="submit" name="commit" value="<%= @query.persisted? ? "Update" : "Create" %>" class="button primary" title="<%= @query.persisted? ? "Update" : "Create" %>">
|
|
45
47
|
<i><%= @query.persisted? ? "save" : "add" %></i>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<% else %>
|
|
34
34
|
<% unless @only_chart %>
|
|
35
35
|
<%= render partial: "caching" %>
|
|
36
|
-
<p class="text-muted" style="margin-bottom: 10px;">
|
|
36
|
+
<p class="text-muted" style="margin-top: <%= @rows.any? ? '0' : '40px' %>; margin-bottom: 10px;">
|
|
37
37
|
<% if @row_limit && @rows.size > @row_limit %>
|
|
38
38
|
First
|
|
39
39
|
<% @rows = @rows.first(@row_limit) %>
|
|
@@ -225,8 +225,8 @@
|
|
|
225
225
|
<% end %>
|
|
226
226
|
</div>
|
|
227
227
|
<% end %>
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
<% elsif @only_chart %>
|
|
229
|
+
<p class="text-muted">No rows</p>
|
|
230
|
+
<% end %>
|
|
230
231
|
<% end %>
|
|
231
232
|
<% end %>
|
|
232
|
-
<% end %>
|
|
@@ -35,14 +35,25 @@
|
|
|
35
35
|
<span>Statement</span>
|
|
36
36
|
<i class="material-icons" data-sage--query-toggle-target="icon" data-action="click->sage--query-toggle#toggle" style="cursor: pointer; font-size: 18px; opacity: 0.6;">visibility_off</i>
|
|
37
37
|
</div>
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
|
|
39
|
+
<% statement_display = begin
|
|
40
|
+
@statement.try(:display_statement) if @statement&.statement.present?
|
|
41
|
+
rescue => e
|
|
42
|
+
Rails.logger.error("Error displaying statement: #{e.message}")
|
|
43
|
+
nil
|
|
44
|
+
end %>
|
|
45
|
+
|
|
46
|
+
<pre id="code" data-sage--query-toggle-target="content"><code><%= statement_display || @query.statement || "-- No SQL statement defined --" %></code></pre>
|
|
40
47
|
</div>
|
|
41
48
|
|
|
42
|
-
<% if @success %>
|
|
49
|
+
<% if @success && @query.statement.present? && @query.data_source.present? %>
|
|
43
50
|
<%= turbo_frame_tag dom_id(@query, 'results'), src: run_query_path(@query.id, from_show: true, variables: variable_params(@query)) do %>
|
|
44
51
|
loading...
|
|
45
52
|
<% end %>
|
|
53
|
+
<% elsif @query.statement.blank? %>
|
|
54
|
+
<div class="padding center-align">
|
|
55
|
+
<p class="secondary-text">No SQL statement to execute. <%= link_to "Edit this query", edit_query_path(@query) %> to add a statement.</p>
|
|
56
|
+
</div>
|
|
46
57
|
<% end %>
|
|
47
58
|
|
|
48
59
|
<%= javascript_tag nonce: true do %>
|
data/config/importmap.rb
CHANGED
|
@@ -8,6 +8,9 @@ pin "sage/controllers/reverse_infinite_scroll_controller", to: "sage/controllers
|
|
|
8
8
|
pin "sage/controllers/variables_controller", to: "sage/controllers/variables_controller.js"
|
|
9
9
|
pin "sage/controllers/query_toggle_controller", to: "sage/controllers/query_toggle_controller.js"
|
|
10
10
|
|
|
11
|
+
# External dependencies
|
|
12
|
+
pin "debounce", to: "https://ga.jspm.io/npm:debounce@2.0.0/index.js"
|
|
13
|
+
|
|
11
14
|
# Don't pin common libraries - let the host app handle them
|
|
12
15
|
# pin "@hotwired/stimulus", to: "stimulus.min.js"
|
|
13
16
|
# pin "@hotwired/turbo-rails", to: "turbo.min.js"
|
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.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nathan Jones
|
|
@@ -169,6 +169,7 @@ files:
|
|
|
169
169
|
- app/helpers/sage/queries_helper.rb
|
|
170
170
|
- app/javascript/controllers/element_removal_controller.js
|
|
171
171
|
- app/javascript/sage.js
|
|
172
|
+
- app/javascript/sage/application.js
|
|
172
173
|
- app/javascript/sage/controllers/clipboard_controller.js
|
|
173
174
|
- app/javascript/sage/controllers/dashboard_controller.js
|
|
174
175
|
- app/javascript/sage/controllers/query_toggle_controller.js
|