sage-rails 0.1.3 → 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 -7
- 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/controllers/query_toggle_controller.js +48 -6
- 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/lib/sage/version.rb +1 -1
- metadata +1 -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
@@ -34,13 +34,6 @@ Run bundle install:
|
|
34
34
|
$ bundle install
|
35
35
|
```
|
36
36
|
|
37
|
-
## Sprockets
|
38
|
-
|
39
|
-
If using sprockets, add this line to your app/javascript/application.js file:
|
40
|
-
```js
|
41
|
-
import "sage/applicaiton";
|
42
|
-
```
|
43
|
-
|
44
37
|
## Getting Started
|
45
38
|
|
46
39
|
Run the install generator to set up Sage in your Rails application:
|
@@ -60,6 +53,14 @@ After installation, run the migrations:
|
|
60
53
|
$ rails db:migrate
|
61
54
|
```
|
62
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
|
+
|
63
64
|
## LLM Configuration
|
64
65
|
|
65
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)
|
@@ -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
|
}
|
@@ -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/lib/sage/version.rb
CHANGED