nquery 0.1.2 → 0.1.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 +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +54 -0
- data/app/assets/builds/nquery/application.css +22 -2
- data/app/assets/builds/nquery/application.js +48 -34
- data/app/controllers/concerns/nquery/breadcrumbs.rb +2 -13
- data/app/controllers/concerns/nquery/chart_actions.rb +4 -4
- data/app/controllers/concerns/nquery/chart_results.rb +18 -4
- data/app/controllers/concerns/nquery/sharing_page.rb +38 -0
- data/app/controllers/nquery/application_controller.rb +2 -0
- data/app/controllers/nquery/dashboards_controller.rb +8 -3
- data/app/controllers/nquery/embed/charts_controller.rb +15 -13
- data/app/controllers/nquery/embed/dashboards_controller.rb +18 -8
- data/app/controllers/nquery/public/charts_controller.rb +19 -0
- data/app/controllers/nquery/public/dashboards_controller.rb +20 -0
- data/app/controllers/nquery/public_resources_controller.rb +79 -0
- data/app/controllers/nquery/queries_controller.rb +5 -49
- data/app/controllers/nquery/sharing/base_controller.rb +89 -0
- data/app/controllers/nquery/sharing/embed_tokens_controller.rb +41 -0
- data/app/controllers/nquery/sharing/embeddings_controller.rb +17 -0
- data/app/controllers/nquery/sharing/public_links_controller.rb +21 -0
- data/app/helpers/nquery/charts_helper.rb +13 -0
- data/app/helpers/nquery/sharing_helper.rb +67 -0
- data/app/javascript/nquery/controllers/chart_preview_controller.js +19 -1
- data/app/javascript/nquery/controllers/clipboard_controller.js +23 -0
- data/app/models/concerns/nquery/shareable.rb +43 -0
- data/app/models/nquery/chart.rb +14 -2
- data/app/models/nquery/dashboard.rb +2 -0
- data/app/models/nquery/data_source.rb +1 -1
- data/app/models/nquery/query.rb +4 -0
- data/app/views/layouts/nquery/embed.html.erb +3 -1
- data/app/views/nquery/charts/_builder_form.html.erb +1 -6
- data/app/views/nquery/charts/embed.html.erb +5 -11
- data/app/views/nquery/charts/new.html.erb +2 -1
- data/app/views/nquery/dashboard/charts/new.html.erb +2 -1
- data/app/views/nquery/dashboards/embed.html.erb +9 -0
- data/app/views/nquery/dashboards/show.html.erb +4 -0
- data/app/views/nquery/embed/charts/show.html.erb +7 -8
- data/app/views/nquery/embed/dashboards/show.html.erb +12 -6
- data/app/views/nquery/embed/unavailable.html.erb +1 -0
- data/app/views/nquery/shared/_icon.html.erb +6 -0
- data/app/views/nquery/shared/_shared_chart_card.html.erb +20 -0
- data/app/views/nquery/shared/_sharing_panel.html.erb +94 -0
- data/config/routes.rb +20 -1
- data/db/migrate/20260907120000_add_sharing_to_nquery_charts_and_dashboards.rb +19 -0
- data/db/migrate/20260907130000_remove_embedding_params_from_nquery_charts_and_dashboards.rb +8 -0
- data/lib/nquery/boolean.rb +9 -0
- data/lib/nquery/configuration.rb +6 -0
- data/lib/nquery/embed_token_service.rb +10 -5
- data/lib/nquery/encryption.rb +1 -1
- data/lib/nquery/seeder.rb +20 -6
- data/lib/nquery/version.rb +1 -1
- data/lib/nquery.rb +1 -0
- metadata +20 -5
- data/app/javascript/nquery/controllers/query_editor_controller.js +0 -41
- data/app/views/nquery/queries/edit.html.erb +0 -40
- data/app/views/nquery/queries/new.html.erb +0 -37
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
<%= nq_icon(:chart) %>
|
|
11
11
|
<span>New chart</span>
|
|
12
12
|
<% end %>
|
|
13
|
+
<%= link_to dashboard_embed_path(@dashboard), class: "nq-btn" do %>
|
|
14
|
+
<%= nq_icon(:share) %>
|
|
15
|
+
<span>Embed</span>
|
|
16
|
+
<% end %>
|
|
13
17
|
<%= link_to edit_dashboard_path(@dashboard), class: "nq-btn" do %>
|
|
14
18
|
<%= nq_icon(:edit) %>
|
|
15
19
|
<span>Edit</span>
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
<% if embed_titled? %>
|
|
2
|
+
<h1><%= @chart.name %></h1>
|
|
3
|
+
<% end %>
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
data-chart-preview-data-value="<%= @result.to_json %>">
|
|
8
|
-
<canvas data-chart-preview-target="canvas" height="120"></canvas>
|
|
9
|
-
</div>
|
|
5
|
+
<%= render "nquery/shared/shared_chart_card",
|
|
6
|
+
chart: @chart,
|
|
7
|
+
result: @result,
|
|
8
|
+
show_title: false %>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
<% @dashboard.dashboard_cards.each do |card| %>
|
|
4
|
-
<section>
|
|
5
|
-
<h2><%= card.chart.name %></h2>
|
|
6
|
-
</section>
|
|
1
|
+
<% if embed_titled? %>
|
|
2
|
+
<h1><%= @dashboard.name %></h1>
|
|
7
3
|
<% end %>
|
|
4
|
+
|
|
5
|
+
<div class="nq-dashboard-grid nq-embed-grid">
|
|
6
|
+
<% @dashboard_cards.each do |card| %>
|
|
7
|
+
<%= render "nquery/shared/shared_chart_card",
|
|
8
|
+
chart: card.chart,
|
|
9
|
+
result: @card_results[card],
|
|
10
|
+
width: card.width,
|
|
11
|
+
height: card.height %>
|
|
12
|
+
<% end %>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p class="nq-chart-error"><%= @unavailable_message %></p>
|
|
@@ -41,6 +41,12 @@
|
|
|
41
41
|
<% when "arrow-right" %>
|
|
42
42
|
<path d="M5 12h14" />
|
|
43
43
|
<path d="m12 5 7 7-7 7" />
|
|
44
|
+
<% when "share" %>
|
|
45
|
+
<circle cx="18" cy="5" r="3" />
|
|
46
|
+
<circle cx="6" cy="12" r="3" />
|
|
47
|
+
<circle cx="18" cy="19" r="3" />
|
|
48
|
+
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49" />
|
|
49
|
+
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
|
|
44
50
|
<% when "format" %>
|
|
45
51
|
<path d="M21 10H3" />
|
|
46
52
|
<path d="M21 6H3" />
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<% width = local_assigns.fetch(:width, 12)
|
|
2
|
+
height = local_assigns.fetch(:height, 4)
|
|
3
|
+
show_title = local_assigns.fetch(:show_title, true)
|
|
4
|
+
titled = !respond_to?(:embed_titled?) || embed_titled?
|
|
5
|
+
%>
|
|
6
|
+
<div class="nq-dashboard-card"
|
|
7
|
+
style="grid-column: span <%= width %>; grid-row: span <%= height %>;">
|
|
8
|
+
<% if show_title && titled %>
|
|
9
|
+
<header class="nq-dashboard-card-header">
|
|
10
|
+
<h3 class="nq-dashboard-card-title"><%= chart.name %></h3>
|
|
11
|
+
</header>
|
|
12
|
+
<% end %>
|
|
13
|
+
<div class="nq-chart-preview" data-controller="chart-preview"
|
|
14
|
+
data-chart-preview-type-value="<%= chart.chart_type %>"
|
|
15
|
+
data-chart-preview-x-value="<%= chart.visualization.to_h["x"] %>"
|
|
16
|
+
data-chart-preview-y-value="<%= chart.visualization.to_h["y"] %>"
|
|
17
|
+
data-chart-preview-data-value="<%= result.to_json %>">
|
|
18
|
+
<canvas data-chart-preview-target="canvas" height="80"></canvas>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<% shareable = local_assigns.fetch(:shareable)
|
|
2
|
+
public_url = local_assigns[:public_url]
|
|
3
|
+
embed_url = local_assigns[:embed_url]
|
|
4
|
+
embed_token = local_assigns[:embed_token]
|
|
5
|
+
can_curate = can_curate_shareable?(shareable)
|
|
6
|
+
iframe_height = share_iframe_height(shareable)
|
|
7
|
+
public_sharing_enabled = Nquery.configuration.public_sharing_enabled
|
|
8
|
+
static_embedding_enabled = Nquery.configuration.static_embedding_enabled
|
|
9
|
+
%>
|
|
10
|
+
|
|
11
|
+
<section class="nq-card nq-sharing-section">
|
|
12
|
+
<h2>Public link</h2>
|
|
13
|
+
<% unless public_sharing_enabled %>
|
|
14
|
+
<p class="nq-muted">Public sharing is disabled. Set <code>config.public_sharing_enabled = true</code> in the host app initializer.</p>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
17
|
+
<% if public_url %>
|
|
18
|
+
<p class="nq-muted">Anyone with this link can view the visualization without signing in.</p>
|
|
19
|
+
<div class="nq-copy-field">
|
|
20
|
+
<pre class="nq-code" id="public-share-url"><%= public_url %></pre>
|
|
21
|
+
<button type="button" class="nq-btn" data-copy-button data-copy-target="#public-share-url">Copy</button>
|
|
22
|
+
</div>
|
|
23
|
+
<h3>Iframe snippet</h3>
|
|
24
|
+
<div class="nq-copy-field">
|
|
25
|
+
<pre class="nq-code" id="public-share-iframe"><%= share_iframe_snippet(public_url, height: iframe_height) %></pre>
|
|
26
|
+
<button type="button" class="nq-btn" data-copy-button data-copy-target="#public-share-iframe">Copy</button>
|
|
27
|
+
</div>
|
|
28
|
+
<% if can_curate %>
|
|
29
|
+
<div class="nq-actions">
|
|
30
|
+
<%= button_to "Remove public link", shareable_public_link_path(shareable), method: :delete, class: "nq-btn nq-btn-danger", form: { data: { turbo_confirm: "Remove the public link? The public URL will stop working. Embed tokens are revoked separately." } } %>
|
|
31
|
+
</div>
|
|
32
|
+
<% end %>
|
|
33
|
+
<% elsif can_curate && public_sharing_enabled %>
|
|
34
|
+
<p class="nq-muted">Create an unguessable public URL. Anyone with the link can view this visualization.</p>
|
|
35
|
+
<div class="nq-actions">
|
|
36
|
+
<%= button_to "Enable public link", shareable_public_link_path(shareable), method: :post, class: "nq-btn nq-btn-primary" %>
|
|
37
|
+
</div>
|
|
38
|
+
<% elsif can_curate %>
|
|
39
|
+
<p class="nq-muted">Enable public sharing in configuration, then return here to create a link.</p>
|
|
40
|
+
<% else %>
|
|
41
|
+
<p class="nq-muted">No public link yet.</p>
|
|
42
|
+
<% end %>
|
|
43
|
+
</section>
|
|
44
|
+
|
|
45
|
+
<section class="nq-card nq-sharing-section">
|
|
46
|
+
<h2>Static embed</h2>
|
|
47
|
+
<% unless static_embedding_enabled %>
|
|
48
|
+
<p class="nq-muted">Static embedding is disabled. Set <code>config.static_embedding_enabled = true</code> in the host app initializer.</p>
|
|
49
|
+
<% end %>
|
|
50
|
+
|
|
51
|
+
<% if shareable.enable_embedding? %>
|
|
52
|
+
<p class="nq-muted">Embedding is enabled for this visualization.</p>
|
|
53
|
+
<% if can_curate && static_embedding_enabled %>
|
|
54
|
+
<%= button_to "Disable embedding", shareable_embedding_path(shareable), method: :patch, params: { enable_embedding: false }, class: "nq-btn" %>
|
|
55
|
+
<% end %>
|
|
56
|
+
<% elsif can_curate && static_embedding_enabled %>
|
|
57
|
+
<p class="nq-muted">Enable embedding to publish a signed iframe URL.</p>
|
|
58
|
+
<%= button_to "Enable embedding", shareable_embedding_path(shareable), method: :patch, params: { enable_embedding: true }, class: "nq-btn nq-btn-primary" %>
|
|
59
|
+
<% end %>
|
|
60
|
+
|
|
61
|
+
<% if embed_token && embed_url %>
|
|
62
|
+
<p class="nq-muted">Signed embed token (expires <%= embed_token.expires_at&.to_fs(:long) || "never" %>)</p>
|
|
63
|
+
<div class="nq-copy-field">
|
|
64
|
+
<pre class="nq-code" id="static-embed-url"><%= embed_url %></pre>
|
|
65
|
+
<button type="button" class="nq-btn" data-copy-button data-copy-target="#static-embed-url">Copy</button>
|
|
66
|
+
</div>
|
|
67
|
+
<h3>Iframe snippet</h3>
|
|
68
|
+
<div class="nq-copy-field">
|
|
69
|
+
<pre class="nq-code" id="static-embed-iframe"><%= share_iframe_snippet(embed_url, height: iframe_height) %></pre>
|
|
70
|
+
<button type="button" class="nq-btn" data-copy-button data-copy-target="#static-embed-iframe">Copy</button>
|
|
71
|
+
</div>
|
|
72
|
+
<% if can_curate && static_embedding_enabled %>
|
|
73
|
+
<div class="nq-actions">
|
|
74
|
+
<%= button_to "Revoke token", shareable_embed_token_path(shareable, embed_token), method: :delete, class: "nq-btn nq-btn-danger", form: { data: { turbo_confirm: "Revoke this embed token?" } } %>
|
|
75
|
+
</div>
|
|
76
|
+
<% end %>
|
|
77
|
+
<% elsif can_curate && !static_embedding_enabled %>
|
|
78
|
+
<p class="nq-muted">Enable static embedding in configuration, then return here to generate a token.</p>
|
|
79
|
+
<% elsif !can_curate %>
|
|
80
|
+
<p class="nq-muted">No embed token yet.</p>
|
|
81
|
+
<% end %>
|
|
82
|
+
|
|
83
|
+
<% if can_curate && static_embedding_enabled %>
|
|
84
|
+
<%= form_with url: shareable_embed_tokens_path(shareable), method: :post, class: "nq-sharing-token-form" do %>
|
|
85
|
+
<label class="nq-form-row">
|
|
86
|
+
<span>Expires</span>
|
|
87
|
+
<%= select_tag :expires_in, options_for_select(Nquery::SharingHelper::EMBED_EXPIRY_OPTIONS, "1.hour"), class: "nq-input" %>
|
|
88
|
+
</label>
|
|
89
|
+
<div class="nq-actions">
|
|
90
|
+
<%= submit_tag embed_token ? "Regenerate" : "Generate token", class: "nq-btn nq-btn-primary" %>
|
|
91
|
+
</div>
|
|
92
|
+
<% end %>
|
|
93
|
+
<% end %>
|
|
94
|
+
</section>
|
data/config/routes.rb
CHANGED
|
@@ -23,16 +23,25 @@ Nquery::Engine.routes.draw do
|
|
|
23
23
|
resources :dashboards, only: %i[new create], module: :collection
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
resources :queries, only: %i[
|
|
26
|
+
resources :queries, only: %i[update] do
|
|
27
27
|
post :run, on: :collection
|
|
28
28
|
get :schema, on: :collection
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
concern :shareable do
|
|
32
|
+
scope module: :sharing do
|
|
33
|
+
resource :public_link, only: %i[create destroy]
|
|
34
|
+
resource :embedding, only: %i[update]
|
|
35
|
+
resources :embed_tokens, only: %i[create destroy]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
resources :charts, only: %i[new create show edit update destroy] do
|
|
32
40
|
member do
|
|
33
41
|
get :embed
|
|
34
42
|
patch :archive
|
|
35
43
|
end
|
|
44
|
+
concerns :shareable
|
|
36
45
|
end
|
|
37
46
|
|
|
38
47
|
resources :dashboards do
|
|
@@ -42,12 +51,17 @@ Nquery::Engine.routes.draw do
|
|
|
42
51
|
patch :archive
|
|
43
52
|
end
|
|
44
53
|
end
|
|
54
|
+
resources :charts, only: [] do
|
|
55
|
+
concerns :shareable
|
|
56
|
+
end
|
|
45
57
|
|
|
46
58
|
member do
|
|
59
|
+
get :embed
|
|
47
60
|
patch :update_layout
|
|
48
61
|
patch :archive
|
|
49
62
|
patch :unarchive
|
|
50
63
|
end
|
|
64
|
+
concerns :shareable
|
|
51
65
|
end
|
|
52
66
|
|
|
53
67
|
resources :imports, only: %i[new create]
|
|
@@ -93,5 +107,10 @@ Nquery::Engine.routes.draw do
|
|
|
93
107
|
get "dashboards/show", to: "dashboards#show", as: :public_dashboard
|
|
94
108
|
end
|
|
95
109
|
|
|
110
|
+
scope module: :public, path: "public", as: :public do
|
|
111
|
+
get "charts/:uuid", to: "charts#show", as: :chart
|
|
112
|
+
get "dashboards/:uuid", to: "dashboards#show", as: :dashboard
|
|
113
|
+
end
|
|
114
|
+
|
|
96
115
|
match "*unmatched", to: "errors#not_found", via: :all
|
|
97
116
|
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddSharingToNqueryChartsAndDashboards < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
add_sharing_columns :nquery_charts
|
|
6
|
+
add_sharing_columns :nquery_dashboards
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def add_sharing_columns(table)
|
|
12
|
+
add_column table, :public_uuid, :string
|
|
13
|
+
add_index table, :public_uuid, unique: true
|
|
14
|
+
add_reference table, :made_public_by, foreign_key: { to_table: :nquery_users }
|
|
15
|
+
add_column table, :public_shared_at, :datetime
|
|
16
|
+
add_column table, :enable_embedding, :boolean, null: false, default: false
|
|
17
|
+
add_column table, :embedding_params, :json, null: false, default: {}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class RemoveEmbeddingParamsFromNqueryChartsAndDashboards < ActiveRecord::Migration[8.1]
|
|
4
|
+
def change
|
|
5
|
+
remove_column :nquery_charts, :embedding_params, :json, null: false, default: {}
|
|
6
|
+
remove_column :nquery_dashboards, :embedding_params, :json, null: false, default: {}
|
|
7
|
+
end
|
|
8
|
+
end
|
data/lib/nquery/configuration.rb
CHANGED
|
@@ -10,6 +10,9 @@ module Nquery
|
|
|
10
10
|
attr_accessor :query_timeout
|
|
11
11
|
attr_accessor :query_row_limit
|
|
12
12
|
attr_accessor :embed_secret
|
|
13
|
+
attr_accessor :public_sharing_enabled
|
|
14
|
+
attr_accessor :static_embedding_enabled
|
|
15
|
+
attr_accessor :embed_frame_ancestors
|
|
13
16
|
|
|
14
17
|
def initialize
|
|
15
18
|
@data_sources = { main: { adapter: :rails, name: "Application database" } }
|
|
@@ -19,6 +22,9 @@ module Nquery
|
|
|
19
22
|
@query_timeout = 15
|
|
20
23
|
@query_row_limit = 10_000
|
|
21
24
|
@embed_secret = nil
|
|
25
|
+
@public_sharing_enabled = false
|
|
26
|
+
@static_embedding_enabled = false
|
|
27
|
+
@embed_frame_ancestors = nil
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def embed_signing_key
|
|
@@ -13,9 +13,9 @@ module Nquery
|
|
|
13
13
|
token: token,
|
|
14
14
|
resource_type: resource_type,
|
|
15
15
|
resource_id: resource_id,
|
|
16
|
-
params: params
|
|
17
|
-
exp: expires_at.to_i
|
|
16
|
+
params: params
|
|
18
17
|
}
|
|
18
|
+
payload[:exp] = expires_at.to_i if expires_at
|
|
19
19
|
data = Base64.urlsafe_encode64(payload.to_json)
|
|
20
20
|
|
|
21
21
|
signature = OpenSSL::HMAC.hexdigest("SHA256", signing_key, data)
|
|
@@ -44,7 +44,8 @@ module Nquery
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
payload = JSON.parse(Base64.urlsafe_decode64(data))
|
|
47
|
-
|
|
47
|
+
exp = payload["exp"]
|
|
48
|
+
raise Error, "Token expired" if exp.present? && exp.to_i <= Time.current.to_i
|
|
48
49
|
|
|
49
50
|
record = EmbedToken.active.find_by(token: payload["token"])
|
|
50
51
|
raise Error, "Token not found" unless record
|
|
@@ -64,14 +65,18 @@ module Nquery
|
|
|
64
65
|
token: record.token,
|
|
65
66
|
resource_type: record.resource_type,
|
|
66
67
|
resource_id: record.resource_id,
|
|
67
|
-
params: record.params || {}
|
|
68
|
-
exp: record.expires_at.to_i
|
|
68
|
+
params: record.params || {}
|
|
69
69
|
}
|
|
70
|
+
payload[:exp] = record.expires_at.to_i if record.expires_at
|
|
70
71
|
data = Base64.urlsafe_encode64(payload.to_json)
|
|
71
72
|
signature = OpenSSL::HMAC.hexdigest("SHA256", signing_key, data)
|
|
72
73
|
"#{data}.#{signature}"
|
|
73
74
|
end
|
|
74
75
|
|
|
76
|
+
def self.revoke!(record)
|
|
77
|
+
record.update!(active: false)
|
|
78
|
+
end
|
|
79
|
+
|
|
75
80
|
def self.generate_token
|
|
76
81
|
SecureRandom.urlsafe_base64(24)
|
|
77
82
|
end
|
data/lib/nquery/encryption.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Nquery
|
|
|
28
28
|
|
|
29
29
|
def support_unencrypted_data
|
|
30
30
|
if ENV.key?("NQUERY_SUPPORT_UNENCRYPTED_DATA")
|
|
31
|
-
return
|
|
31
|
+
return Nquery::Boolean.cast(ENV.fetch("NQUERY_SUPPORT_UNENCRYPTED_DATA"))
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
true
|
data/lib/nquery/seeder.rb
CHANGED
|
@@ -69,12 +69,11 @@ module Nquery
|
|
|
69
69
|
card.height = 4
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
) unless EmbedToken.exists?(resource_type: "Nquery::Chart", resource_id: chart.id)
|
|
72
|
+
chart.share_publicly!(user: admin)
|
|
73
|
+
ensure_embedding_enabled!(chart)
|
|
74
|
+
ensure_embedding_enabled!(dashboard)
|
|
75
|
+
dashboard.unshare_publicly!
|
|
76
|
+
ensure_active_embed_token!(chart, creator: admin)
|
|
78
77
|
|
|
79
78
|
organization.update!(onboarding_completed_at: Time.current) if organization.onboarding_completed_at.blank?
|
|
80
79
|
end
|
|
@@ -118,5 +117,20 @@ module Nquery
|
|
|
118
117
|
p.access_level = "no"
|
|
119
118
|
end
|
|
120
119
|
end
|
|
120
|
+
|
|
121
|
+
def ensure_embedding_enabled!(resource)
|
|
122
|
+
resource.update!(enable_embedding: true) unless resource.enable_embedding?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def ensure_active_embed_token!(resource, creator:)
|
|
126
|
+
return if resource.active_embed_token
|
|
127
|
+
|
|
128
|
+
EmbedTokenService.sign(
|
|
129
|
+
resource_type: resource.class.name,
|
|
130
|
+
resource_id: resource.id,
|
|
131
|
+
creator: creator,
|
|
132
|
+
expires_at: 1.year.from_now
|
|
133
|
+
)
|
|
134
|
+
end
|
|
121
135
|
end
|
|
122
136
|
end
|
data/lib/nquery/version.rb
CHANGED
data/lib/nquery.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nquery
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nquery Contributors
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -315,6 +315,7 @@ files:
|
|
|
315
315
|
- app/controllers/concerns/nquery/browsable.rb
|
|
316
316
|
- app/controllers/concerns/nquery/chart_actions.rb
|
|
317
317
|
- app/controllers/concerns/nquery/chart_results.rb
|
|
318
|
+
- app/controllers/concerns/nquery/sharing_page.rb
|
|
318
319
|
- app/controllers/nquery/admin/base_controller.rb
|
|
319
320
|
- app/controllers/nquery/admin/data_sources_controller.rb
|
|
320
321
|
- app/controllers/nquery/admin/groups_controller.rb
|
|
@@ -337,8 +338,15 @@ files:
|
|
|
337
338
|
- app/controllers/nquery/onboarding/companies_controller.rb
|
|
338
339
|
- app/controllers/nquery/onboarding/confirmations_controller.rb
|
|
339
340
|
- app/controllers/nquery/onboarding/congrats_controller.rb
|
|
341
|
+
- app/controllers/nquery/public/charts_controller.rb
|
|
342
|
+
- app/controllers/nquery/public/dashboards_controller.rb
|
|
343
|
+
- app/controllers/nquery/public_resources_controller.rb
|
|
340
344
|
- app/controllers/nquery/queries_controller.rb
|
|
341
345
|
- app/controllers/nquery/sessions_controller.rb
|
|
346
|
+
- app/controllers/nquery/sharing/base_controller.rb
|
|
347
|
+
- app/controllers/nquery/sharing/embed_tokens_controller.rb
|
|
348
|
+
- app/controllers/nquery/sharing/embeddings_controller.rb
|
|
349
|
+
- app/controllers/nquery/sharing/public_links_controller.rb
|
|
342
350
|
- app/helpers/nquery/charts_helper.rb
|
|
343
351
|
- app/helpers/nquery/collections_helper.rb
|
|
344
352
|
- app/helpers/nquery/components_helper.rb
|
|
@@ -347,13 +355,15 @@ files:
|
|
|
347
355
|
- app/helpers/nquery/home_helper.rb
|
|
348
356
|
- app/helpers/nquery/icon_helper.rb
|
|
349
357
|
- app/helpers/nquery/navigation_helper.rb
|
|
358
|
+
- app/helpers/nquery/sharing_helper.rb
|
|
350
359
|
- app/javascript/nquery/controllers/chart_preview_controller.js
|
|
360
|
+
- app/javascript/nquery/controllers/clipboard_controller.js
|
|
351
361
|
- app/javascript/nquery/controllers/csv_mapper_controller.js
|
|
352
362
|
- app/javascript/nquery/controllers/dashboard_grid_controller.js
|
|
353
363
|
- app/javascript/nquery/controllers/data_source_form_controller.js
|
|
354
|
-
- app/javascript/nquery/controllers/query_editor_controller.js
|
|
355
364
|
- app/mailers/nquery/application_mailer.rb
|
|
356
365
|
- app/mailers/nquery/devise_mailer.rb
|
|
366
|
+
- app/models/concerns/nquery/shareable.rb
|
|
357
367
|
- app/models/nquery/application_permission.rb
|
|
358
368
|
- app/models/nquery/application_record.rb
|
|
359
369
|
- app/models/nquery/audit.rb
|
|
@@ -411,6 +421,7 @@ files:
|
|
|
411
421
|
- app/views/nquery/dashboard/charts/new.html.erb
|
|
412
422
|
- app/views/nquery/dashboards/_chart_card.html.erb
|
|
413
423
|
- app/views/nquery/dashboards/edit.html.erb
|
|
424
|
+
- app/views/nquery/dashboards/embed.html.erb
|
|
414
425
|
- app/views/nquery/dashboards/index.html.erb
|
|
415
426
|
- app/views/nquery/dashboards/new.html.erb
|
|
416
427
|
- app/views/nquery/dashboards/show.html.erb
|
|
@@ -418,6 +429,7 @@ files:
|
|
|
418
429
|
- app/views/nquery/devise_mailer/confirmation_instructions.text.erb
|
|
419
430
|
- app/views/nquery/embed/charts/show.html.erb
|
|
420
431
|
- app/views/nquery/embed/dashboards/show.html.erb
|
|
432
|
+
- app/views/nquery/embed/unavailable.html.erb
|
|
421
433
|
- app/views/nquery/errors/internal_server_error.html.erb
|
|
422
434
|
- app/views/nquery/errors/not_found.html.erb
|
|
423
435
|
- app/views/nquery/home/index.html.erb
|
|
@@ -426,8 +438,6 @@ files:
|
|
|
426
438
|
- app/views/nquery/onboarding/companies/new.html.erb
|
|
427
439
|
- app/views/nquery/onboarding/confirmations/show.html.erb
|
|
428
440
|
- app/views/nquery/onboarding/congrats/show.html.erb
|
|
429
|
-
- app/views/nquery/queries/edit.html.erb
|
|
430
|
-
- app/views/nquery/queries/new.html.erb
|
|
431
441
|
- app/views/nquery/sessions/new.html.erb
|
|
432
442
|
- app/views/nquery/shared/_auth_shell.html.erb
|
|
433
443
|
- app/views/nquery/shared/_breadcrumbs.html.erb
|
|
@@ -435,6 +445,8 @@ files:
|
|
|
435
445
|
- app/views/nquery/shared/_chart_row_actions.html.erb
|
|
436
446
|
- app/views/nquery/shared/_flash.html.erb
|
|
437
447
|
- app/views/nquery/shared/_icon.html.erb
|
|
448
|
+
- app/views/nquery/shared/_shared_chart_card.html.erb
|
|
449
|
+
- app/views/nquery/shared/_sharing_panel.html.erb
|
|
438
450
|
- app/views/nquery/shared/_sidebar.html.erb
|
|
439
451
|
- app/views/nquery/shared/_topbar.html.erb
|
|
440
452
|
- config/importmap.rb
|
|
@@ -447,9 +459,12 @@ files:
|
|
|
447
459
|
- db/migrate/20260722000002_create_nquery_organizations.rb
|
|
448
460
|
- db/migrate/20260722000003_add_devise_to_nquery_users.rb
|
|
449
461
|
- db/migrate/20260722000004_add_onboarding_completed_at_to_nquery_organizations.rb
|
|
462
|
+
- db/migrate/20260907120000_add_sharing_to_nquery_charts_and_dashboards.rb
|
|
463
|
+
- db/migrate/20260907130000_remove_embedding_params_from_nquery_charts_and_dashboards.rb
|
|
450
464
|
- lib/generators/nquery/install_generator.rb
|
|
451
465
|
- lib/nquery.rb
|
|
452
466
|
- lib/nquery/authorizes_collection.rb
|
|
467
|
+
- lib/nquery/boolean.rb
|
|
453
468
|
- lib/nquery/configuration.rb
|
|
454
469
|
- lib/nquery/csv_importer.rb
|
|
455
470
|
- lib/nquery/data_sources/adapter.rb
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Controller } from "@hotwired/stimulus"
|
|
2
|
-
|
|
3
|
-
export default class extends Controller {
|
|
4
|
-
static targets = ["statement", "results", "dataSource", "schema"]
|
|
5
|
-
|
|
6
|
-
insertTable(event) {
|
|
7
|
-
const table = event.currentTarget.dataset.table
|
|
8
|
-
const textarea = this.statementTarget
|
|
9
|
-
const insert = `SELECT * FROM ${table} LIMIT 100`
|
|
10
|
-
textarea.value = insert
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async run() {
|
|
14
|
-
const statement = this.statementTarget.value
|
|
15
|
-
const dataSourceId = this.dataSourceTarget?.value
|
|
16
|
-
const csrf = document.querySelector('meta[name="csrf-token"]')?.content
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const response = await fetch("/queries/run", {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: { "Content-Type": "application/json", "X-CSRF-Token": csrf },
|
|
22
|
-
body: JSON.stringify({ statement, data_source_id: dataSourceId })
|
|
23
|
-
})
|
|
24
|
-
const data = await response.json()
|
|
25
|
-
this.resultsTarget.textContent = JSON.stringify(data, null, 2)
|
|
26
|
-
} catch (e) {
|
|
27
|
-
this.resultsTarget.textContent = e.message
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async loadSchema() {
|
|
32
|
-
if (!this.hasSchemaTarget) return
|
|
33
|
-
const dataSourceId = this.dataSourceTarget.value
|
|
34
|
-
const response = await fetch(`/queries/schema?data_source_id=${dataSourceId}`)
|
|
35
|
-
const data = await response.json()
|
|
36
|
-
this.schemaTarget.innerHTML = data.tables.map(t => {
|
|
37
|
-
const name = typeof t === "string" ? t : t.name
|
|
38
|
-
return `<li class="nq-tree-item" data-action="click->query-editor#insertTable" data-table="${name}">${name}</li>`
|
|
39
|
-
}).join("")
|
|
40
|
-
}
|
|
41
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
<div class="nq-page-header">
|
|
2
|
-
<h1>Edit query: <%= @query.name || "Untitled" %></h1>
|
|
3
|
-
</div>
|
|
4
|
-
|
|
5
|
-
<%= form_with model: @query, url: query_path(@query), method: :patch, class: "nq-form" do |f| %>
|
|
6
|
-
<div class="nq-grid nq-grid-editor" data-controller="query-editor">
|
|
7
|
-
<aside class="nq-card nq-schema-panel">
|
|
8
|
-
<h3>Schema</h3>
|
|
9
|
-
<ul class="nq-tree" data-query-editor-target="schema">
|
|
10
|
-
<% @schema_tables.each do |table| %>
|
|
11
|
-
<li class="nq-tree-item" data-action="click->query-editor#insertTable" data-table="<%= table %>"><%= table %></li>
|
|
12
|
-
<% end %>
|
|
13
|
-
</ul>
|
|
14
|
-
</aside>
|
|
15
|
-
|
|
16
|
-
<div class="nq-editor-panel">
|
|
17
|
-
<div class="nq-form-row">
|
|
18
|
-
<%= f.label :name %>
|
|
19
|
-
<%= f.text_field :name, class: "nq-input" %>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="nq-form-row">
|
|
22
|
-
<%= f.label :data_source_id, "Data source" %>
|
|
23
|
-
<%= f.collection_select :data_source_id, @data_sources, :id, :name, {}, class: "nq-input",
|
|
24
|
-
data: { query_editor_target: "dataSource" } %>
|
|
25
|
-
</div>
|
|
26
|
-
<div class="nq-form-row">
|
|
27
|
-
<%= f.label :statement, "SQL" %>
|
|
28
|
-
<%= f.text_area :statement, rows: 12, class: "nq-textarea", data: { query_editor_target: "statement" } %>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="nq-actions">
|
|
31
|
-
<button type="button" class="nq-btn" data-action="click->query-editor#run">Run query</button>
|
|
32
|
-
<%= f.submit "Save", class: "nq-btn nq-btn-primary" %>
|
|
33
|
-
<% if @query.chart %>
|
|
34
|
-
<%= link_to "View chart", chart_path(@query.chart), class: "nq-btn" %>
|
|
35
|
-
<% end %>
|
|
36
|
-
</div>
|
|
37
|
-
<pre class="nq-results" data-query-editor-target="results"></pre>
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
|
-
<% end %>
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
<div class="nq-page-header">
|
|
2
|
-
<h1>New SQL query</h1>
|
|
3
|
-
</div>
|
|
4
|
-
|
|
5
|
-
<%= form_with model: @query, url: queries_path, class: "nq-form" do |f| %>
|
|
6
|
-
<div class="nq-grid nq-grid-editor" data-controller="query-editor">
|
|
7
|
-
<aside class="nq-card nq-schema-panel">
|
|
8
|
-
<h3>Schema</h3>
|
|
9
|
-
<ul class="nq-tree">
|
|
10
|
-
<% @schema_tables.each do |table| %>
|
|
11
|
-
<li class="nq-tree-item" data-action="click->query-editor#insertTable" data-table="<%= table %>"><%= table %></li>
|
|
12
|
-
<% end %>
|
|
13
|
-
</ul>
|
|
14
|
-
</aside>
|
|
15
|
-
|
|
16
|
-
<div class="nq-editor-panel">
|
|
17
|
-
<div class="nq-form-row">
|
|
18
|
-
<%= f.label :name, "Query name" %>
|
|
19
|
-
<%= f.text_field :name, class: "nq-input" %>
|
|
20
|
-
</div>
|
|
21
|
-
<div class="nq-form-row">
|
|
22
|
-
<%= f.label :data_source_id, "Data source" %>
|
|
23
|
-
<%= f.collection_select :data_source_id, @data_sources, :id, :name, {}, class: "nq-input",
|
|
24
|
-
data: { query_editor_target: "dataSource", action: "change->query-editor#loadSchema" } %>
|
|
25
|
-
</div>
|
|
26
|
-
<div class="nq-form-row">
|
|
27
|
-
<%= f.label :statement, "SQL" %>
|
|
28
|
-
<%= f.text_area :statement, rows: 12, class: "nq-textarea", data: { query_editor_target: "statement" } %>
|
|
29
|
-
</div>
|
|
30
|
-
<div class="nq-actions">
|
|
31
|
-
<button type="button" class="nq-btn" data-action="click->query-editor#run">Run query</button>
|
|
32
|
-
<%= f.submit "Save", class: "nq-btn nq-btn-primary" %>
|
|
33
|
-
</div>
|
|
34
|
-
<pre class="nq-results" data-query-editor-target="results"></pre>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
<% end %>
|