nquery 0.1.0 → 0.1.1

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.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +263 -24
  4. data/app/assets/builds/nquery/application.css +61 -4
  5. data/app/assets/builds/nquery/application.js +107 -12
  6. data/app/controllers/concerns/nquery/breadcrumbs.rb +5 -3
  7. data/app/controllers/concerns/nquery/chart_actions.rb +1 -0
  8. data/app/controllers/concerns/nquery/chart_results.rb +17 -3
  9. data/app/controllers/nquery/admin/data_sources_controller.rb +49 -2
  10. data/app/controllers/nquery/admin/groups_controller.rb +8 -2
  11. data/app/controllers/nquery/admin/users_controller.rb +6 -1
  12. data/app/controllers/nquery/application_controller.rb +45 -28
  13. data/app/controllers/nquery/dashboards_controller.rb +30 -0
  14. data/app/controllers/nquery/embed/charts_controller.rb +1 -0
  15. data/app/controllers/nquery/embed/dashboards_controller.rb +1 -0
  16. data/app/controllers/nquery/errors_controller.rb +11 -0
  17. data/app/controllers/nquery/home_controller.rb +1 -0
  18. data/app/controllers/nquery/onboarding/admins_controller.rb +42 -0
  19. data/app/controllers/nquery/onboarding/base_controller.rb +27 -0
  20. data/app/controllers/nquery/onboarding/companies_controller.rb +30 -0
  21. data/app/controllers/nquery/onboarding/confirmations_controller.rb +48 -0
  22. data/app/controllers/nquery/onboarding/congrats_controller.rb +15 -0
  23. data/app/controllers/nquery/queries_controller.rb +0 -1
  24. data/app/controllers/nquery/sessions_controller.rb +14 -17
  25. data/app/helpers/nquery/data_sources_helper.rb +13 -0
  26. data/app/helpers/nquery/groups_helper.rb +19 -0
  27. data/app/javascript/nquery/controllers/data_source_form_controller.js +25 -0
  28. data/app/mailers/nquery/application_mailer.rb +7 -0
  29. data/app/mailers/nquery/devise_mailer.rb +9 -0
  30. data/app/models/nquery/audit.rb +2 -2
  31. data/app/models/nquery/collection.rb +1 -1
  32. data/app/models/nquery/dashboard.rb +1 -1
  33. data/app/models/nquery/data_source.rb +139 -4
  34. data/app/models/nquery/organization.rb +38 -0
  35. data/app/models/nquery/query.rb +8 -0
  36. data/app/models/nquery/user.rb +45 -10
  37. data/app/views/nquery/admin/data_sources/_form.html.erb +121 -0
  38. data/app/views/nquery/admin/data_sources/edit.html.erb +6 -8
  39. data/app/views/nquery/admin/data_sources/new.html.erb +6 -7
  40. data/app/views/nquery/admin/groups/_form.html.erb +47 -0
  41. data/app/views/nquery/admin/groups/edit.html.erb +15 -6
  42. data/app/views/nquery/admin/groups/index.html.erb +25 -20
  43. data/app/views/nquery/admin/groups/new.html.erb +6 -6
  44. data/app/views/nquery/admin/groups/show.html.erb +98 -19
  45. data/app/views/nquery/admin/logs/index.html.erb +26 -30
  46. data/app/views/nquery/charts/_builder_form.html.erb +6 -4
  47. data/app/views/nquery/charts/edit.html.erb +2 -1
  48. data/app/views/nquery/dashboards/index.html.erb +6 -0
  49. data/app/views/nquery/dashboards/new.html.erb +34 -0
  50. data/app/views/nquery/devise_mailer/confirmation_instructions.html.erb +5 -0
  51. data/app/views/nquery/devise_mailer/confirmation_instructions.text.erb +5 -0
  52. data/app/views/nquery/errors/internal_server_error.html.erb +10 -0
  53. data/app/views/nquery/errors/not_found.html.erb +10 -0
  54. data/app/views/nquery/home/index.html.erb +7 -15
  55. data/app/views/nquery/onboarding/admins/new.html.erb +21 -0
  56. data/app/views/nquery/onboarding/companies/new.html.erb +25 -0
  57. data/app/views/nquery/onboarding/confirmations/show.html.erb +17 -0
  58. data/app/views/nquery/onboarding/congrats/show.html.erb +11 -0
  59. data/app/views/nquery/sessions/new.html.erb +8 -9
  60. data/app/views/nquery/shared/_auth_shell.html.erb +1 -1
  61. data/app/views/nquery/shared/_topbar.html.erb +0 -4
  62. data/config/routes.rb +26 -7
  63. data/db/migrate/20260721000001_create_nquery_tables.rb +1 -1
  64. data/db/migrate/20260722000001_add_key_to_nquery_data_sources.rb +8 -0
  65. data/db/migrate/20260722000002_create_nquery_organizations.rb +11 -0
  66. data/db/migrate/20260722000003_add_devise_to_nquery_users.rb +35 -0
  67. data/db/migrate/20260722000004_add_onboarding_completed_at_to_nquery_organizations.rb +7 -0
  68. data/lib/generators/nquery/install_generator.rb +9 -17
  69. data/lib/nquery/configuration.rb +7 -23
  70. data/lib/nquery/csv_importer.rb +3 -3
  71. data/lib/nquery/data_sources/adapter.rb +4 -0
  72. data/lib/nquery/data_sources/postgresql_adapter.rb +15 -1
  73. data/lib/nquery/data_sources/rails_adapter.rb +5 -0
  74. data/lib/nquery/data_sources/syncer.rb +50 -0
  75. data/lib/nquery/encryption.rb +50 -0
  76. data/lib/nquery/engine.rb +42 -3
  77. data/lib/nquery/onboarding/admin_provisioner.rb +40 -0
  78. data/lib/nquery/onboarding/password_confirmation.rb +46 -0
  79. data/lib/nquery/onboarding.rb +40 -0
  80. data/lib/nquery/permissions/resolver.rb +14 -4
  81. data/lib/nquery/query_runner.rb +5 -9
  82. data/lib/nquery/readonly_sql.rb +25 -0
  83. data/lib/nquery/seeder.rb +25 -34
  84. data/lib/nquery/setup.rb +75 -0
  85. data/lib/nquery/version.rb +1 -1
  86. data/lib/nquery.rb +9 -0
  87. data/lib/tasks/nquery.rake +6 -0
  88. data/nquery.gemspec +9 -4
  89. metadata +131 -23
  90. data/app/controllers/nquery/registrations_controller.rb +0 -31
  91. data/app/views/nquery/registrations/new.html.erb +0 -34
@@ -1,24 +1,103 @@
1
- <div class="nq-page-header"><h1><%= @group.name %></h1></div>
2
- <div class="nq-grid nq-grid-2">
3
- <section class="nq-card">
4
- <h2>Members</h2>
5
- <ul class="nq-list">
6
- <% @group.users.each do |user| %>
7
- <li>
8
- <%= user.name %> (<%= user.email %>)
9
- <% unless @group.system_group == "all_users" %>
10
- <%= button_to "Remove", remove_member_admin_group_path(@group, user_id: user.id), method: :delete, class: "nq-btn nq-btn-ghost" %>
11
- <% end %>
12
- </li>
13
- <% end %>
14
- </ul>
1
+ <div class="nq-page-header">
2
+ <h1><%= @group.name %></h1>
3
+ <p class="nq-muted nq-resource-meta">
4
+ <span class="nq-badge <%= group_type_badge_class(@group) %>"><%= group_type_label(@group) %></span>
5
+ · <%= pluralize(@members.size, "member") %>
6
+ </p>
7
+ <div class="nq-actions">
8
+ <%= link_to edit_admin_group_path(@group), class: "nq-btn" do %>
9
+ <%= nq_icon(:edit) %>
10
+ <span>Edit</span>
11
+ <% end %>
12
+ <%= link_to by_group_admin_permissions_path(group_id: @group.id), class: "nq-btn" do %>
13
+ <span>Permissions</span>
14
+ <% end %>
15
+ </div>
16
+ </div>
17
+
18
+ <% if @group.description.present? %>
19
+ <div class="nq-card nq-group-description nq-mb">
20
+ <p><%= @group.description %></p>
21
+ </div>
22
+ <% end %>
23
+
24
+ <div class="nq-grid nq-grid-2 nq-group-layout">
25
+ <section class="nq-section">
26
+ <h2 class="nq-section-title">Members</h2>
27
+
28
+ <% if @members.any? %>
29
+ <div class="nq-card nq-card-table">
30
+ <table class="nq-table">
31
+ <thead>
32
+ <tr>
33
+ <th>Name</th>
34
+ <th>Email</th>
35
+ <th></th>
36
+ </tr>
37
+ </thead>
38
+ <tbody>
39
+ <% @members.each do |user| %>
40
+ <tr>
41
+ <td><%= link_to user.name, admin_user_path(user) %></td>
42
+ <td class="nq-muted"><%= user.email %></td>
43
+ <td>
44
+ <% unless @group.system_group == "all_users" %>
45
+ <div class="nq-actions">
46
+ <%= button_to "Remove",
47
+ remove_member_admin_group_path(@group),
48
+ method: :delete,
49
+ params: { user_id: user.id },
50
+ class: "nq-btn nq-btn-ghost",
51
+ form: { onsubmit: "return confirm('#{j("Remove #{user.name} from #{@group.name}?")}');" } %>
52
+ </div>
53
+ <% end %>
54
+ </td>
55
+ </tr>
56
+ <% end %>
57
+ </tbody>
58
+ </table>
59
+ </div>
60
+ <% else %>
61
+ <div class="nq-card nq-empty-state">
62
+ <h2>No members yet</h2>
63
+ <p class="nq-muted">Add users to this group to grant them shared permissions.</p>
64
+ </div>
65
+ <% end %>
15
66
  </section>
67
+
16
68
  <% if @available_users.any? %>
17
- <section class="nq-card">
18
- <h2>Add member</h2>
19
- <% @available_users.limit(10).each do |user| %>
20
- <%= button_to "Add #{user.email}", add_member_admin_group_path(@group, user_id: user.id), method: :post, class: "nq-btn" %>
21
- <% end %>
69
+ <section class="nq-section">
70
+ <h2 class="nq-section-title">Add member</h2>
71
+
72
+ <div class="nq-card">
73
+ <%= form_with url: add_member_admin_group_path(@group), method: :post, class: "nq-form nq-add-member-form" do %>
74
+ <div class="nq-form-row">
75
+ <%= label_tag :user_id, "User" %>
76
+ <%= select_tag :user_id,
77
+ options_from_collection_for_select(@available_users, :id, :name_with_email),
78
+ include_blank: "Select a user…",
79
+ class: "nq-input",
80
+ required: true %>
81
+ <p class="nq-field-hint">Only active users who are not already in this group are listed.</p>
82
+ </div>
83
+ <%= submit_tag "Add to group", class: "nq-btn nq-btn-primary" %>
84
+ <% end %>
85
+ </div>
86
+ </section>
87
+ <% elsif @group.system_group == "all_users" %>
88
+ <section class="nq-section">
89
+ <h2 class="nq-section-title">Add member</h2>
90
+ <div class="nq-card">
91
+ <%= nq_flash_card "Every active user is automatically added to this group.", type: :info, toast: false %>
92
+ </div>
93
+ </section>
94
+ <% else %>
95
+ <section class="nq-section">
96
+ <h2 class="nq-section-title">Add member</h2>
97
+ <div class="nq-card nq-empty-state">
98
+ <h2>Everyone is already here</h2>
99
+ <p class="nq-muted">All active users are already members of this group.</p>
100
+ </div>
22
101
  </section>
23
102
  <% end %>
24
103
  </div>
@@ -40,35 +40,31 @@
40
40
  <% end %>
41
41
  </section>
42
42
 
43
- <section class="nq-card nq-mt">
44
- <table class="nq-table">
45
- <thead>
43
+ <%= nq_card_table columns: ["User", "Query", "Collection", "Dashboard", "Status", "When"], class: "nq-mt nq-logs-table" do %>
44
+ <% if @audits.any? %>
45
+ <% @audits.each do |audit| %>
46
+ <%
47
+ statement = audit.statement.to_s
48
+ status_class = audit.status == "success" ? "nq-badge-success" : "nq-badge-error"
49
+ %>
46
50
  <tr>
47
- <th>User</th>
48
- <th>Query</th>
49
- <th>Collection</th>
50
- <th>Dashboard</th>
51
- <th>Status</th>
52
- <th>When</th>
51
+ <td data-label="User"><%= audit.user&.name || "—" %></td>
52
+ <td data-label="Query">
53
+ <code class="nq-logs-query" title="<%= statement %>"><%= truncate(statement, length: 80) %></code>
54
+ </td>
55
+ <td data-label="Collection"><%= audit.query&.collection&.name || "—" %></td>
56
+ <td data-label="Dashboard"><%= audit.query&.chart&.dashboard_cards&.first&.dashboard&.name || "—" %></td>
57
+ <td data-label="Status"><span class="nq-badge <%= status_class %>"><%= audit.status %></span></td>
58
+ <td data-label="When">
59
+ <time datetime="<%= audit.created_at.iso8601 %>" title="<%= l(audit.created_at, format: :long) %>">
60
+ <%= time_ago_in_words(audit.created_at) %> ago
61
+ </time>
62
+ </td>
53
63
  </tr>
54
- </thead>
55
- <tbody>
56
- <% if @audits.any? %>
57
- <% @audits.each do |audit| %>
58
- <tr>
59
- <td><%= audit.user&.name || "—" %></td>
60
- <td><%= truncate(audit.statement.to_s, length: 60) %></td>
61
- <td><%= audit.query&.collection&.name || "—" %></td>
62
- <td><%= audit.query&.chart&.dashboard_cards&.first&.dashboard&.name || "—" %></td>
63
- <td><span class="nq-badge"><%= audit.status %></span></td>
64
- <td><%= time_ago_in_words(audit.created_at) %> ago</td>
65
- </tr>
66
- <% end %>
67
- <% else %>
68
- <tr>
69
- <td colspan="6" class="nq-muted">No query logs found.</td>
70
- </tr>
71
- <% end %>
72
- </tbody>
73
- </table>
74
- </section>
64
+ <% end %>
65
+ <% else %>
66
+ <tr class="nq-logs-empty">
67
+ <td colspan="6" class="nq-muted">No query logs found.</td>
68
+ </tr>
69
+ <% end %>
70
+ <% end %>
@@ -1,11 +1,13 @@
1
1
  <% viz = chart.visualization.is_a?(Hash) ? chart.visualization : {} %>
2
2
  <% chart_type = viz["type"].presence || "table" %>
3
3
  <% active_chart_type = chart_builder_chart_types.include?(chart_type) ? chart_type : "bar" %>
4
+ <% builder_data = {
5
+ controller: "chart-builder",
6
+ query_save_url: chart.query&.persisted? ? query_path(chart.query) : nil
7
+ } %>
8
+ <% builder_data[:initial_result] = result if local_assigns[:result].present? %>
4
9
  <%= form_with model: chart, url: form_url, class: "nq-form nq-chart-builder-form", html: {
5
- data: {
6
- controller: "chart-builder",
7
- query_save_url: chart.query&.persisted? ? query_path(chart.query) : nil
8
- }
10
+ data: builder_data
9
11
  } do |f| %>
10
12
  <%= render "nquery/charts/new_header",
11
13
  f: f,
@@ -3,4 +3,5 @@
3
3
  dashboard: @dashboard,
4
4
  form_url: chart_form_path(@chart),
5
5
  data_sources: @data_sources,
6
- schema: @schema %>
6
+ schema: @schema,
7
+ result: @result %>
@@ -1,5 +1,11 @@
1
1
  <div class="nq-page-header">
2
2
  <h1>Dashboards</h1>
3
+ <% if @collections.any? %>
4
+ <%= link_to new_dashboard_path, class: "nq-btn nq-btn-primary" do %>
5
+ <%= nq_icon(:plus) %>
6
+ <span>New dashboard</span>
7
+ <% end %>
8
+ <% end %>
3
9
  </div>
4
10
 
5
11
  <div class="nq-card nq-card-table">
@@ -0,0 +1,34 @@
1
+ <div class="nq-page-header">
2
+ <h1>New dashboard</h1>
3
+ <p class="nq-muted nq-resource-meta">Choose a collection, then add charts after you create it.</p>
4
+ </div>
5
+
6
+ <%= form_with model: @dashboard, url: dashboards_path, class: "nq-form nq-card" do |f| %>
7
+ <% if @dashboard.errors.any? %>
8
+ <div class="nq-form-errors" role="alert">
9
+ <p><strong>Please fix the following:</strong></p>
10
+ <ul>
11
+ <% @dashboard.errors.full_messages.each do |message| %>
12
+ <li><%= message %></li>
13
+ <% end %>
14
+ </ul>
15
+ </div>
16
+ <% end %>
17
+
18
+ <div class="nq-form-row">
19
+ <%= f.label :name %>
20
+ <%= f.text_field :name, class: "nq-input", autofocus: true %>
21
+ </div>
22
+ <div class="nq-form-row">
23
+ <%= f.label :description %>
24
+ <%= f.text_area :description, class: "nq-textarea", rows: 2 %>
25
+ </div>
26
+ <div class="nq-form-row">
27
+ <%= f.label :collection_id, "Collection" %>
28
+ <%= f.collection_select :collection_id, @collections, :id, :name, { prompt: "Select a collection" }, class: "nq-input" %>
29
+ </div>
30
+ <div class="nq-form-actions">
31
+ <%= link_to "Cancel", dashboards_path, class: "nq-btn" %>
32
+ <%= f.submit "Create", class: "nq-btn nq-btn-primary" %>
33
+ </div>
34
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <p>Welcome <%= @email %>!</p>
2
+
3
+ <p>You can confirm your account email through the link below:</p>
4
+
5
+ <p><%= link_to "Confirm my account", onboarding_confirm_url(confirmation_token: @token) %></p>
@@ -0,0 +1,5 @@
1
+ Welcome <%= @email %>!
2
+
3
+ You can confirm your account email through the link below:
4
+
5
+ <%= onboarding_confirm_url(confirmation_token: @token) %>
@@ -0,0 +1,10 @@
1
+ <%= render layout: "nquery/shared/auth_shell",
2
+ locals: {
3
+ title: "Something went wrong",
4
+ subtitle: "We hit an unexpected error. Please try again in a moment.",
5
+ footer_text: "Head back to",
6
+ footer_label: "home",
7
+ footer_link: root_path
8
+ } do %>
9
+ <p class="nq-muted">Error 500</p>
10
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <%= render layout: "nquery/shared/auth_shell",
2
+ locals: {
3
+ title: "Page not found",
4
+ subtitle: "The page you were looking for doesn’t exist or is no longer available.",
5
+ footer_text: "Head back to",
6
+ footer_label: "home",
7
+ footer_link: root_path
8
+ } do %>
9
+ <p class="nq-muted">Error 404</p>
10
+ <% end %>
@@ -11,9 +11,11 @@
11
11
  <%= nq_icon(:chart) %>
12
12
  <span>New question</span>
13
13
  <% end %>
14
- <%= link_to new_collection_dashboard_path(@root_collection), class: "nq-btn nq-btn-primary nq-home-hero-cta" do %>
15
- <%= nq_icon(:plus) %>
16
- <span>New dashboard</span>
14
+ <% if @collections.any? %>
15
+ <%= link_to new_dashboard_path, class: "nq-btn nq-btn-primary nq-home-hero-cta" do %>
16
+ <%= nq_icon(:plus) %>
17
+ <span>New dashboard</span>
18
+ <% end %>
17
19
  <% end %>
18
20
  </div>
19
21
  <% end %>
@@ -35,16 +37,6 @@
35
37
  <span class="nq-home-action-label">Dashboards</span>
36
38
  <span class="nq-muted nq-home-action-desc">View all your dashboards</span>
37
39
  <% end %>
38
-
39
- <% if @root_collection %>
40
- <%= link_to collection_path(@root_collection), class: "nq-home-action-card" do %>
41
- <span class="nq-home-action-icon nq-home-action-icon-chart">
42
- <%= nq_icon(:chart) %>
43
- </span>
44
- <span class="nq-home-action-label">Analytics</span>
45
- <span class="nq-muted nq-home-action-desc">Open <%= @root_collection.name %></span>
46
- <% end %>
47
- <% end %>
48
40
  </section>
49
41
 
50
42
  <div class="nq-home-recents">
@@ -86,8 +78,8 @@
86
78
  <div class="nq-home-empty">
87
79
  <%= nq_icon(:dashboard, class: "nq-icon-lg") %>
88
80
  <p class="nq-muted">No dashboards yet</p>
89
- <% if @root_collection %>
90
- <%= link_to new_collection_dashboard_path(@root_collection), class: "nq-btn nq-btn-primary" do %>
81
+ <% if @collections.any? %>
82
+ <%= link_to new_dashboard_path, class: "nq-btn nq-btn-primary" do %>
91
83
  <%= nq_icon(:plus) %>
92
84
  <span>Create dashboard</span>
93
85
  <% end %>
@@ -0,0 +1,21 @@
1
+ <%= render layout: "nquery/shared/auth_shell",
2
+ locals: {
3
+ title: "Create your admin account",
4
+ subtitle: "We'll email you a link to set your password."
5
+ } do %>
6
+ <%= form_with model: @user, url: onboarding_admin_path, class: "nq-form nq-auth-form" do |form| %>
7
+ <div class="nq-form-row">
8
+ <%= form.label :first_name, "First name" %>
9
+ <%= form.text_field :first_name, class: "nq-input", required: true, autofocus: true %>
10
+ </div>
11
+ <div class="nq-form-row">
12
+ <%= form.label :last_name, "Last name" %>
13
+ <%= form.text_field :last_name, class: "nq-input", required: true %>
14
+ </div>
15
+ <div class="nq-form-row">
16
+ <%= form.label :email, "Email address" %>
17
+ <%= form.email_field :email, class: "nq-input", required: true, autocomplete: "email" %>
18
+ </div>
19
+ <button type="submit" class="nq-btn nq-btn-primary nq-btn-block nq-auth-submit">Send confirmation email</button>
20
+ <% end %>
21
+ <% end %>
@@ -0,0 +1,25 @@
1
+ <%= render layout: "nquery/shared/auth_shell",
2
+ locals: {
3
+ title: "Set up your company",
4
+ subtitle: "Tell us about your organization to get started."
5
+ } do %>
6
+ <%= form_with model: @organization, url: onboarding_company_path, class: "nq-form nq-auth-form" do |form| %>
7
+ <div class="nq-form-row">
8
+ <%= form.label :name, "Company name" %>
9
+ <%= form.text_field :name, class: "nq-input", required: true, autofocus: true %>
10
+ </div>
11
+ <div class="nq-form-row">
12
+ <%= form.label :website, "Website" %>
13
+ <%= form.url_field :website, class: "nq-input", placeholder: "https://example.com" %>
14
+ </div>
15
+ <div class="nq-form-row">
16
+ <%= form.label :logo, "Logo" %>
17
+ <%= form.file_field :logo, class: "nq-input" %>
18
+ </div>
19
+ <div class="nq-form-row">
20
+ <%= form.label :cover_image, "Cover image" %>
21
+ <%= form.file_field :cover_image, class: "nq-input" %>
22
+ </div>
23
+ <button type="submit" class="nq-btn nq-btn-primary nq-btn-block nq-auth-submit">Continue</button>
24
+ <% end %>
25
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <%= render layout: "nquery/shared/auth_shell",
2
+ locals: {
3
+ title: "Set your password",
4
+ subtitle: "Choose a password for your admin account."
5
+ } do %>
6
+ <%= form_with model: @user, url: onboarding_confirm_path(confirmation_token: params[:confirmation_token]), method: :patch, class: "nq-form nq-auth-form" do |form| %>
7
+ <div class="nq-form-row">
8
+ <%= form.label :password, "Password" %>
9
+ <%= form.password_field :password, class: "nq-input", required: true, autofocus: true, autocomplete: "new-password" %>
10
+ </div>
11
+ <div class="nq-form-row">
12
+ <%= form.label :password_confirmation, "Confirm password" %>
13
+ <%= form.password_field :password_confirmation, class: "nq-input", required: true, autocomplete: "new-password" %>
14
+ </div>
15
+ <button type="submit" class="nq-btn nq-btn-primary nq-btn-block nq-auth-submit">Confirm and sign in</button>
16
+ <% end %>
17
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <%= render layout: "nquery/shared/auth_shell",
2
+ locals: {
3
+ title: "Check your email",
4
+ subtitle: if @email.present?
5
+ "We sent a confirmation link to #{@email}. Follow it to set your password and sign in."
6
+ else
7
+ "We sent a confirmation link to your administrator email. Follow it to set your password and sign in."
8
+ end
9
+ } do %>
10
+ <p class="nq-muted">Didn't receive it? Check spam or contact your administrator.</p>
11
+ <% end %>
@@ -1,15 +1,14 @@
1
1
  <%= render layout: "nquery/shared/auth_shell",
2
2
  locals: {
3
3
  title: "Welcome back",
4
- subtitle: "Sign in to explore your charts and dashboards.",
5
- footer_text: "Don't have an account?",
6
- footer_link: signup_path,
7
- footer_label: "Create an account"
4
+ subtitle: "Sign in to explore your charts and dashboards."
8
5
  } do %>
9
- <%= form_with url: login_path, class: "nq-form nq-auth-form" do %>
6
+ <%= form_with url: nquery_user_session_path,
7
+ scope: :nquery_user,
8
+ html: { class: "nq-form nq-auth-form", id: "new_nquery_user" } do |form| %>
10
9
  <div class="nq-form-row">
11
- <%= label_tag :email, "Email address" %>
12
- <%= email_field_tag :email, nil,
10
+ <%= form.label :email, "Email address" %>
11
+ <%= form.email_field :email,
13
12
  class: "nq-input",
14
13
  required: true,
15
14
  autofocus: true,
@@ -17,8 +16,8 @@
17
16
  placeholder: "you@company.com" %>
18
17
  </div>
19
18
  <div class="nq-form-row">
20
- <%= label_tag :password, "Password" %>
21
- <%= password_field_tag :password, nil,
19
+ <%= form.label :password, "Password" %>
20
+ <%= form.password_field :password,
22
21
  class: "nq-input",
23
22
  required: true,
24
23
  autocomplete: "current-password",
@@ -12,7 +12,7 @@
12
12
 
13
13
  <%= yield %>
14
14
 
15
- <% if footer_link.present? %>
15
+ <% if local_assigns[:footer_link].present? %>
16
16
  <footer class="nq-auth-footer">
17
17
  <span class="nq-muted"><%= footer_text %></span>
18
18
  <%= link_to footer_label, footer_link, class: "nq-auth-link" %>
@@ -1,9 +1,5 @@
1
1
  <header class="nq-topbar">
2
- <div class="nq-search">
3
- <input type="search" placeholder="Search charts, dashboards..." class="nq-input" />
4
- </div>
5
2
  <div class="nq-topbar-actions">
6
- <span class="nq-badge"><%= Nquery::DataSource.active.first&.name || "Main DB" %></span>
7
3
  <% if current_nquery_user %>
8
4
  <span class="nq-user"><%= current_nquery_user.name %></span>
9
5
  <%= button_to "Sign out", logout_path, method: :delete, class: "nq-btn nq-btn-ghost" %>
data/config/routes.rb CHANGED
@@ -5,6 +5,14 @@ Nquery::Engine.routes.draw do
5
5
 
6
6
  get "browse", to: redirect("/collections")
7
7
 
8
+ namespace :onboarding do
9
+ resource :company, only: %i[new create], controller: "companies"
10
+ resource :admin, only: %i[new create], controller: "admins"
11
+ get "congrats", to: "congrats#show"
12
+ get "confirm", to: "confirmations#show", as: :confirm
13
+ patch "confirm", to: "confirmations#update"
14
+ end
15
+
8
16
  resources :collections do
9
17
  member do
10
18
  patch :archive
@@ -27,7 +35,7 @@ Nquery::Engine.routes.draw do
27
35
  end
28
36
  end
29
37
 
30
- resources :dashboards, except: %i[new create] do
38
+ resources :dashboards do
31
39
  resources :charts, module: :dashboard do
32
40
  member do
33
41
  get :embed
@@ -54,7 +62,9 @@ Nquery::Engine.routes.draw do
54
62
  post :add_member, on: :member
55
63
  delete :remove_member, on: :member
56
64
  end
57
- resources :data_sources, only: %i[index new create edit update]
65
+ resources :data_sources, only: %i[index new create edit update] do
66
+ post :test_connection, on: :collection
67
+ end
58
68
  resources :logs, only: %i[index]
59
69
  resources :permissions, only: %i[index] do
60
70
  collection do
@@ -65,14 +75,23 @@ Nquery::Engine.routes.draw do
65
75
  end
66
76
  end
67
77
 
68
- get "login", to: "sessions#new"
69
- post "login", to: "sessions#create"
70
- delete "logout", to: "sessions#destroy"
71
- get "signup", to: "registrations#new"
72
- post "signup", to: "registrations#create"
78
+ devise_for :nquery_users,
79
+ class_name: "Nquery::User",
80
+ path: "",
81
+ path_names: { sign_in: "login", sign_out: "logout" },
82
+ controllers: { sessions: "nquery/sessions" },
83
+ router_name: :nquery,
84
+ sign_out_via: :delete,
85
+ skip: %i[registrations passwords confirmations unlocks]
86
+
87
+ devise_scope :nquery_user do
88
+ get "users/confirmation", to: "onboarding/confirmations#show", as: :nquery_user_confirmation
89
+ end
73
90
 
74
91
  namespace :embed do
75
92
  get "charts/show", to: "charts#show", as: :public_chart
76
93
  get "dashboards/show", to: "dashboards#show", as: :public_dashboard
77
94
  end
95
+
96
+ match "*unmatched", to: "errors#not_found", via: :all
78
97
  end
@@ -95,7 +95,7 @@ class CreateNqueryTables < ActiveRecord::Migration[8.0]
95
95
  create_table :nquery_dashboards do |t|
96
96
  t.string :name, null: false
97
97
  t.text :description
98
- t.references :collection, foreign_key: { to_table: :nquery_collections }
98
+ t.references :collection, null: false, foreign_key: { to_table: :nquery_collections }
99
99
  t.references :creator, foreign_key: { to_table: :nquery_users }
100
100
  t.json :settings, null: false, default: {}
101
101
  t.timestamps
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddKeyToNqueryDataSources < ActiveRecord::Migration[8.0]
4
+ def change
5
+ add_column :nquery_data_sources, :key, :string
6
+ add_index :nquery_data_sources, :key, unique: true
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateNqueryOrganizations < ActiveRecord::Migration[8.0]
4
+ def change
5
+ create_table :nquery_organizations do |t|
6
+ t.string :name, null: false
7
+ t.string :website
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddDeviseToNqueryUsers < ActiveRecord::Migration[8.0]
4
+ def change
5
+ change_table :nquery_users, bulk: true do |t|
6
+ t.string :encrypted_password, limit: 128
7
+ t.string :reset_password_token
8
+ t.datetime :reset_password_sent_at
9
+ t.datetime :remember_created_at
10
+ t.string :confirmation_token
11
+ t.datetime :confirmed_at
12
+ t.datetime :confirmation_sent_at
13
+ t.string :unconfirmed_email
14
+ end
15
+
16
+ add_index :nquery_users, :reset_password_token, unique: true
17
+ add_index :nquery_users, :confirmation_token, unique: true
18
+
19
+ reversible do |dir|
20
+ dir.up do
21
+ execute <<~SQL.squish
22
+ UPDATE nquery_users
23
+ SET encrypted_password = password_digest
24
+ WHERE encrypted_password IS NULL AND password_digest IS NOT NULL
25
+ SQL
26
+
27
+ execute <<~SQL.squish
28
+ UPDATE nquery_users
29
+ SET confirmed_at = CURRENT_TIMESTAMP
30
+ WHERE confirmed_at IS NULL AND encrypted_password IS NOT NULL
31
+ SQL
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddOnboardingCompletedAtToNqueryOrganizations < ActiveRecord::Migration[8.0]
4
+ def change
5
+ add_column :nquery_organizations, :onboarding_completed_at, :datetime
6
+ end
7
+ end