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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed7edcaa26884623654d7ad0a2cd3eca12af0e9bb7053002a6df9f5da7f0951b
4
- data.tar.gz: e82079453e9055fb342d1e40672139643f7efe84e036dfac121f98a8e7c7981c
3
+ metadata.gz: cdf45a19f9379935c65e7ee18b70a4cc2ce0df00b71fe13586c89ffe15180524
4
+ data.tar.gz: 86c1c3fda7b28206020da469995a5bcc6215c61b9249e60e0b121307b31ab66d
5
5
  SHA512:
6
- metadata.gz: 8010a563e726d175637ef8d7312ba86ab375f260e09d8e8b7904554461a51f15c7d0b1eca9d0af05d7e7d8f8103375f414f2639e81f1f7b1bb45a0f502a8904d
7
- data.tar.gz: 5762e832bea8af62b7e14060ac553f17f2c9122a8e60bbe5481a60fc6cb61e5bc2462503e7c68369b264a88cf29dd3d7deead45edb94c791a286883abaad828c
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
- if (content.style.display === "none") {
12
- content.style.display = "block"
13
- icon.textContent = "visibility_off"
18
+
19
+ const isHidden = window.getComputedStyle(content).display === "none"
20
+
21
+ if (isHidden) {
22
+ this.showContent()
14
23
  } else {
15
- content.style.display = "none"
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
- content.style.display = "block"
15
- icon.textContent = "visibility_off"
22
+ this.showContent()
16
23
  } else {
17
- content.style.display = "none"
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
  }
@@ -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.js"
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", "sage/application", nonce: true %>
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
- <%= form_for @query, url: (@query.persisted? ? query_path(@query, params: @variable_params) : queries_path(params: @variable_params)), html: {autocomplete: "off", class: ""} do |f| %>
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
- <%= form_with url: (defined?(sage) && sage.respond_to?(:run_queries_path) ? sage.run_queries_path : run_queries_path), method: :post, local: false, style: "margin: 0;" do |f| %>
36
- <%= f.hidden_field :statement, id: 'run_query_statement' %>
37
- <%= f.hidden_field :query_id, value: @query.id %>
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
- <% elsif @only_chart %>
229
- <p class="text-muted">No rows</p>
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
- <pre id="code" data-sage--query-toggle-target="content"><code><%= @statement.try(:display_statement) || @query.statement %></code></pre>
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
@@ -1,3 +1,3 @@
1
1
  module Sage
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.4"
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.1.2
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