nquery 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +52 -0
- data/Rakefile +9 -0
- data/app/assets/builds/nquery/application.css +339 -0
- data/app/assets/builds/nquery/application.js +989 -0
- data/app/assets/config/nquery_manifest.js +2 -0
- data/app/components/nquery/_card_component.html.erb +6 -0
- data/app/components/nquery/_card_table_component.html.erb +14 -0
- data/app/components/nquery/_flash_card_component.html.erb +32 -0
- data/app/components/nquery/_flash_cards_component.html.erb +3 -0
- data/app/components/nquery/_infinite_scroll_component.html.erb +11 -0
- data/app/components/nquery/card_component.rb +18 -0
- data/app/components/nquery/card_table_component.rb +19 -0
- data/app/components/nquery/component.rb +26 -0
- data/app/components/nquery/flash_card_component.rb +51 -0
- data/app/components/nquery/flash_cards_component.rb +20 -0
- data/app/components/nquery/infinite_scroll_component.rb +19 -0
- data/app/controllers/concerns/nquery/breadcrumbs.rb +238 -0
- data/app/controllers/concerns/nquery/browsable.rb +56 -0
- data/app/controllers/concerns/nquery/chart_actions.rb +104 -0
- data/app/controllers/concerns/nquery/chart_results.rb +30 -0
- data/app/controllers/nquery/admin/base_controller.rb +9 -0
- data/app/controllers/nquery/admin/data_sources_controller.rb +47 -0
- data/app/controllers/nquery/admin/groups_controller.rb +63 -0
- data/app/controllers/nquery/admin/logs_controller.rb +28 -0
- data/app/controllers/nquery/admin/permissions_controller.rb +53 -0
- data/app/controllers/nquery/admin/users_controller.rb +80 -0
- data/app/controllers/nquery/application_controller.rb +70 -0
- data/app/controllers/nquery/charts_controller.rb +51 -0
- data/app/controllers/nquery/collection/dashboards_controller.rb +38 -0
- data/app/controllers/nquery/collections_controller.rb +117 -0
- data/app/controllers/nquery/dashboard/charts_controller.rb +62 -0
- data/app/controllers/nquery/dashboards_controller.rb +90 -0
- data/app/controllers/nquery/embed/charts_controller.rb +24 -0
- data/app/controllers/nquery/embed/dashboards_controller.rb +17 -0
- data/app/controllers/nquery/home_controller.rb +16 -0
- data/app/controllers/nquery/imports_controller.rb +41 -0
- data/app/controllers/nquery/queries_controller.rb +105 -0
- data/app/controllers/nquery/registrations_controller.rb +31 -0
- data/app/controllers/nquery/sessions_controller.rb +28 -0
- data/app/helpers/nquery/charts_helper.rb +33 -0
- data/app/helpers/nquery/collections_helper.rb +13 -0
- data/app/helpers/nquery/components_helper.rb +29 -0
- data/app/helpers/nquery/home_helper.rb +23 -0
- data/app/helpers/nquery/icon_helper.rb +9 -0
- data/app/helpers/nquery/navigation_helper.rb +10 -0
- data/app/javascript/nquery/controllers/chart_preview_controller.js +125 -0
- data/app/javascript/nquery/controllers/csv_mapper_controller.js +23 -0
- data/app/javascript/nquery/controllers/dashboard_grid_controller.js +25 -0
- data/app/javascript/nquery/controllers/query_editor_controller.js +41 -0
- data/app/models/nquery/application_permission.rb +12 -0
- data/app/models/nquery/application_record.rb +7 -0
- data/app/models/nquery/audit.rb +38 -0
- data/app/models/nquery/chart.rb +37 -0
- data/app/models/nquery/collection.rb +35 -0
- data/app/models/nquery/collection_permission.rb +12 -0
- data/app/models/nquery/csv_upload.rb +11 -0
- data/app/models/nquery/dashboard.rb +27 -0
- data/app/models/nquery/dashboard_card.rb +8 -0
- data/app/models/nquery/data_permission.rb +13 -0
- data/app/models/nquery/data_source.rb +27 -0
- data/app/models/nquery/embed_token.rb +21 -0
- data/app/models/nquery/group.rb +27 -0
- data/app/models/nquery/group_membership.rb +10 -0
- data/app/models/nquery/query.rb +19 -0
- data/app/models/nquery/user.rb +58 -0
- data/app/views/layouts/nquery/application.html.erb +32 -0
- data/app/views/layouts/nquery/auth.html.erb +16 -0
- data/app/views/layouts/nquery/embed.html.erb +12 -0
- data/app/views/nquery/admin/data_sources/edit.html.erb +8 -0
- data/app/views/nquery/admin/data_sources/index.html.erb +21 -0
- data/app/views/nquery/admin/data_sources/new.html.erb +7 -0
- data/app/views/nquery/admin/groups/edit.html.erb +6 -0
- data/app/views/nquery/admin/groups/index.html.erb +21 -0
- data/app/views/nquery/admin/groups/new.html.erb +6 -0
- data/app/views/nquery/admin/groups/show.html.erb +24 -0
- data/app/views/nquery/admin/logs/index.html.erb +74 -0
- data/app/views/nquery/admin/permissions/_collection.html.erb +25 -0
- data/app/views/nquery/admin/permissions/_data_source.html.erb +27 -0
- data/app/views/nquery/admin/permissions/_group.html.erb +42 -0
- data/app/views/nquery/admin/permissions/by_collection.html.erb +10 -0
- data/app/views/nquery/admin/permissions/by_data_source.html.erb +7 -0
- data/app/views/nquery/admin/permissions/by_group.html.erb +12 -0
- data/app/views/nquery/admin/users/edit.html.erb +17 -0
- data/app/views/nquery/admin/users/index.html.erb +34 -0
- data/app/views/nquery/admin/users/new.html.erb +14 -0
- data/app/views/nquery/admin/users/show.html.erb +6 -0
- data/app/views/nquery/charts/_builder_form.html.erb +185 -0
- data/app/views/nquery/charts/_new_header.html.erb +31 -0
- data/app/views/nquery/charts/_schema_tree.html.erb +25 -0
- data/app/views/nquery/charts/edit.html.erb +6 -0
- data/app/views/nquery/charts/embed.html.erb +15 -0
- data/app/views/nquery/charts/new.html.erb +5 -0
- data/app/views/nquery/charts/show.html.erb +15 -0
- data/app/views/nquery/charts/update.turbo_stream.erb +1 -0
- data/app/views/nquery/collection/dashboards/new.html.erb +13 -0
- data/app/views/nquery/collections/_dashboard_card.html.erb +57 -0
- data/app/views/nquery/collections/edit.html.erb +15 -0
- data/app/views/nquery/collections/index.html.erb +41 -0
- data/app/views/nquery/collections/new.html.erb +15 -0
- data/app/views/nquery/collections/show.html.erb +100 -0
- data/app/views/nquery/dashboard/charts/new.html.erb +6 -0
- data/app/views/nquery/dashboards/_chart_card.html.erb +16 -0
- data/app/views/nquery/dashboards/edit.html.erb +34 -0
- data/app/views/nquery/dashboards/index.html.erb +35 -0
- data/app/views/nquery/dashboards/show.html.erb +46 -0
- data/app/views/nquery/embed/charts/show.html.erb +9 -0
- data/app/views/nquery/embed/dashboards/show.html.erb +7 -0
- data/app/views/nquery/home/index.html.erb +143 -0
- data/app/views/nquery/imports/new.html.erb +20 -0
- data/app/views/nquery/queries/edit.html.erb +40 -0
- data/app/views/nquery/queries/new.html.erb +37 -0
- data/app/views/nquery/registrations/new.html.erb +34 -0
- data/app/views/nquery/sessions/new.html.erb +29 -0
- data/app/views/nquery/shared/_auth_shell.html.erb +22 -0
- data/app/views/nquery/shared/_breadcrumbs.html.erb +15 -0
- data/app/views/nquery/shared/_chart_actions_menu.html.erb +27 -0
- data/app/views/nquery/shared/_chart_row_actions.html.erb +16 -0
- data/app/views/nquery/shared/_flash.html.erb +1 -0
- data/app/views/nquery/shared/_icon.html.erb +50 -0
- data/app/views/nquery/shared/_sidebar.html.erb +16 -0
- data/app/views/nquery/shared/_topbar.html.erb +12 -0
- data/config/importmap.rb +9 -0
- data/config/routes.rb +78 -0
- data/db/migrate/20260721000001_create_nquery_tables.rb +146 -0
- data/db/migrate/20260721180000_add_archived_at_to_nquery_dashboards.rb +8 -0
- data/db/migrate/20260721190000_add_archived_at_to_nquery_charts.rb +8 -0
- data/db/migrate/20260721200000_add_archived_at_to_nquery_collections.rb +8 -0
- data/lib/generators/nquery/install_generator.rb +38 -0
- data/lib/nquery/authorizes_collection.rb +50 -0
- data/lib/nquery/configuration.rb +44 -0
- data/lib/nquery/csv_importer.rb +68 -0
- data/lib/nquery/data_sources/adapter.rb +34 -0
- data/lib/nquery/data_sources/mysql_adapter.rb +24 -0
- data/lib/nquery/data_sources/postgresql_adapter.rb +46 -0
- data/lib/nquery/data_sources/rails_adapter.rb +54 -0
- data/lib/nquery/data_sources/sqlite_adapter.rb +8 -0
- data/lib/nquery/embed_token_service.rb +83 -0
- data/lib/nquery/engine.rb +55 -0
- data/lib/nquery/permissions/resolver.rb +83 -0
- data/lib/nquery/query_runner.rb +69 -0
- data/lib/nquery/sample_data/ecommerce.rb +157 -0
- data/lib/nquery/schema_explorer.rb +28 -0
- data/lib/nquery/seeder.rb +131 -0
- data/lib/nquery/version.rb +5 -0
- data/lib/nquery.rb +36 -0
- data/lib/tasks/nquery.rake +9 -0
- data/nquery.gemspec +40 -0
- metadata +395 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
module Admin
|
|
5
|
+
class DataSourcesController < BaseController
|
|
6
|
+
before_action :set_data_source, only: %i[edit update]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@data_sources = DataSource.order(:name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new
|
|
13
|
+
@data_source = DataSource.new(adapter: "postgresql")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@data_source = DataSource.new(data_source_params)
|
|
18
|
+
if @data_source.save
|
|
19
|
+
redirect_to admin_data_sources_path, notice: "Data source created."
|
|
20
|
+
else
|
|
21
|
+
render :new, status: :unprocessable_content
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def edit
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update
|
|
29
|
+
if @data_source.update(data_source_params)
|
|
30
|
+
redirect_to admin_data_sources_path, notice: "Data source updated."
|
|
31
|
+
else
|
|
32
|
+
render :edit, status: :unprocessable_content
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def set_data_source
|
|
39
|
+
@data_source = DataSource.find(params[:id])
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def data_source_params
|
|
43
|
+
params.require(:data_source).permit(:name, :adapter, :active, :connection_config)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
module Admin
|
|
5
|
+
class GroupsController < BaseController
|
|
6
|
+
before_action :set_group, only: %i[show edit update add_member remove_member]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@groups = Group.includes(:users).order(:name)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@available_users = User.active.where.not(id: @group.user_ids).order(:email)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def new
|
|
17
|
+
@group = Group.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create
|
|
21
|
+
@group = Group.new(group_params.merge(system_group: "custom"))
|
|
22
|
+
if @group.save
|
|
23
|
+
redirect_to admin_groups_path, notice: "Group created."
|
|
24
|
+
else
|
|
25
|
+
render :new, status: :unprocessable_content
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def edit
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def update
|
|
33
|
+
if @group.update(group_params)
|
|
34
|
+
redirect_to admin_groups_path, notice: "Group updated."
|
|
35
|
+
else
|
|
36
|
+
render :edit, status: :unprocessable_content
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def add_member
|
|
41
|
+
user = User.find(params[:user_id])
|
|
42
|
+
@group.group_memberships.find_or_create_by!(user: user)
|
|
43
|
+
redirect_to admin_group_path(@group), notice: "#{user.name} added to group."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def remove_member
|
|
47
|
+
membership = @group.group_memberships.find_by!(user_id: params[:user_id])
|
|
48
|
+
membership.destroy unless @group.system_group == "all_users"
|
|
49
|
+
redirect_to admin_group_path(@group), notice: "Member removed."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def set_group
|
|
55
|
+
@group = Group.includes(:users).find(params[:id])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def group_params
|
|
59
|
+
params.require(:group).permit(:name, :description)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
module Admin
|
|
5
|
+
class LogsController < BaseController
|
|
6
|
+
def index
|
|
7
|
+
@collections = Collection.order(:name)
|
|
8
|
+
@dashboards = Dashboard.order(:name)
|
|
9
|
+
@audits = filtered_audits
|
|
10
|
+
.includes(:user, query: [:collection, { chart: { dashboard_cards: :dashboard } }])
|
|
11
|
+
.recent
|
|
12
|
+
.limit(100)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def filtered_audits
|
|
18
|
+
scope = Audit.all
|
|
19
|
+
scope = scope.for_user(params[:user]) if params[:user].present?
|
|
20
|
+
scope = scope.for_collection(params[:collection]) if params[:collection].present?
|
|
21
|
+
scope = scope.for_dashboard(params[:dashboard]) if params[:dashboard].present?
|
|
22
|
+
scope = scope.since(params[:from]) if params[:from].present?
|
|
23
|
+
scope = scope.until_date(params[:to]) if params[:to].present?
|
|
24
|
+
scope
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
module Admin
|
|
5
|
+
class PermissionsController < BaseController
|
|
6
|
+
def index
|
|
7
|
+
redirect_to by_group_admin_permissions_path
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def by_group
|
|
11
|
+
@view = "group"
|
|
12
|
+
@groups = Group.order(:name)
|
|
13
|
+
@selected_group = Group.find_by(id: params[:group_id]) || @groups.first
|
|
14
|
+
@collections = Collection.shared.order(:name)
|
|
15
|
+
@data_sources = DataSource.order(:name)
|
|
16
|
+
@warnings = permission_warnings(@selected_group)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def by_data_source
|
|
20
|
+
@view = "data_source"
|
|
21
|
+
@data_sources = DataSource.order(:name)
|
|
22
|
+
@selected_data_source = DataSource.find_by(id: params[:data_source_id]) || @data_sources.first
|
|
23
|
+
@groups = Group.order(:name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def by_collection
|
|
27
|
+
@view = "collection"
|
|
28
|
+
@collections = Collection.shared.order(:name)
|
|
29
|
+
@selected_collection = Collection.find_by(id: params[:collection_id]) || @collections.first
|
|
30
|
+
@groups = Group.order(:name)
|
|
31
|
+
@warnings = @groups.filter_map do |group|
|
|
32
|
+
"All Users is more permissive than #{group.name}" if permission_resolver.all_users_more_permissive?(group, collection: @selected_collection)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def permission_warnings(group)
|
|
39
|
+
return [] unless group
|
|
40
|
+
|
|
41
|
+
Collection.shared.filter_map do |collection|
|
|
42
|
+
next unless permission_resolver.all_users_more_permissive?(group, collection: collection)
|
|
43
|
+
|
|
44
|
+
"All Users has broader access than #{group.name} on #{collection.name}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def permission_resolver
|
|
49
|
+
@permission_resolver ||= Permissions::Resolver.new(current_nquery_user)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
module Admin
|
|
5
|
+
class UsersController < BaseController
|
|
6
|
+
before_action :set_user, only: %i[show edit update deactivate destroy]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@users = User.active.includes(:groups).order(:email)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def new
|
|
16
|
+
@user = User.new
|
|
17
|
+
@groups = Group.order(:name)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create
|
|
21
|
+
@user = User.new(user_params)
|
|
22
|
+
if @user.save
|
|
23
|
+
assign_groups(@user)
|
|
24
|
+
@user.ensure_all_users_membership!
|
|
25
|
+
@user.ensure_personal_collection!
|
|
26
|
+
redirect_to admin_users_path, notice: "User created."
|
|
27
|
+
else
|
|
28
|
+
@groups = Group.order(:name)
|
|
29
|
+
render :new, status: :unprocessable_content
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def edit
|
|
34
|
+
@groups = Group.order(:name)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def update
|
|
38
|
+
if @user.update(user_params)
|
|
39
|
+
assign_groups(@user)
|
|
40
|
+
redirect_to admin_users_path, notice: "User updated."
|
|
41
|
+
else
|
|
42
|
+
@groups = Group.order(:name)
|
|
43
|
+
render :edit, status: :unprocessable_content
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def deactivate
|
|
48
|
+
return redirect_to admin_users_path, alert: "You cannot disable your own account." if @user == current_nquery_user
|
|
49
|
+
|
|
50
|
+
@user.deactivate!
|
|
51
|
+
redirect_to admin_users_path, notice: "#{@user.name} has been disabled."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def destroy
|
|
55
|
+
return redirect_to admin_users_path, alert: "You cannot remove your own account." if @user == current_nquery_user
|
|
56
|
+
|
|
57
|
+
name = @user.name
|
|
58
|
+
@user.destroy!
|
|
59
|
+
redirect_to admin_users_path, notice: "#{name} has been removed."
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def set_user
|
|
65
|
+
@user = User.find(params[:id])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def user_params
|
|
69
|
+
params.require(:user).permit(:email, :first_name, :last_name, :password, :password_confirmation)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def assign_groups(user)
|
|
73
|
+
group_ids = params[:group_ids]&.reject(&:blank?) || []
|
|
74
|
+
user.group_memberships.where.not(group_id: group_ids).destroy_all
|
|
75
|
+
group_ids.each { |gid| user.group_memberships.find_or_create_by!(group_id: gid) }
|
|
76
|
+
user.ensure_all_users_membership!
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
class ApplicationController < ActionController::Base
|
|
5
|
+
include Nquery::AuthorizesCollection
|
|
6
|
+
include Nquery::Breadcrumbs
|
|
7
|
+
include Nquery::Engine.routes.url_helpers
|
|
8
|
+
|
|
9
|
+
protect_from_forgery with: :exception
|
|
10
|
+
layout "nquery/application"
|
|
11
|
+
|
|
12
|
+
helper_method :current_nquery_user, :permission_resolver
|
|
13
|
+
|
|
14
|
+
before_action :_authenticate!
|
|
15
|
+
before_action :set_current_user_context
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def _authenticate!
|
|
20
|
+
if Nquery.configuration.authenticate
|
|
21
|
+
instance_eval(&Nquery.configuration.authenticate)
|
|
22
|
+
else
|
|
23
|
+
default_authenticate!
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def default_authenticate!
|
|
28
|
+
return if current_nquery_user
|
|
29
|
+
return if auth_exempt?
|
|
30
|
+
|
|
31
|
+
redirect_to login_path, alert: "Please sign in to continue."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def auth_exempt?
|
|
35
|
+
controller_path.in?(%w[nquery/sessions nquery/registrations nquery/embed/charts nquery/embed/dashboards])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def current_nquery_user
|
|
39
|
+
@current_nquery_user ||= resolve_current_user
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def resolve_current_user
|
|
43
|
+
if session[:nquery_user_id]
|
|
44
|
+
return User.active.find_by(id: session[:nquery_user_id])
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if Nquery.configuration.authentication_mode.in?(%i[sso hybrid])
|
|
48
|
+
host_method = Nquery.configuration.current_user_method
|
|
49
|
+
host_user = host_method.is_a?(Symbol) ? send(host_method) : instance_eval(&host_method)
|
|
50
|
+
if host_user && Nquery.configuration.resolve_user
|
|
51
|
+
return instance_eval { Nquery.configuration.resolve_user.call(host_user) }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def set_current_user_context
|
|
59
|
+
@permission_resolver = Permissions::Resolver.new(current_nquery_user) if current_nquery_user
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def permission_resolver
|
|
63
|
+
@permission_resolver ||= Permissions::Resolver.new(current_nquery_user)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def require_admin!
|
|
67
|
+
redirect_to root_path, alert: "Admin access required." unless permission_resolver.admin?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
class ChartsController < ApplicationController
|
|
5
|
+
include ChartActions
|
|
6
|
+
|
|
7
|
+
before_action :set_root_collection, only: %i[new create]
|
|
8
|
+
before_action :authorize_chart_create!, only: %i[new create]
|
|
9
|
+
|
|
10
|
+
def new
|
|
11
|
+
@chart = Chart.new
|
|
12
|
+
@chart.build_query(statement: "SELECT 1 AS example")
|
|
13
|
+
load_chart_builder_assigns
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@chart = Chart.new(
|
|
18
|
+
chart_params.merge(
|
|
19
|
+
creator: current_nquery_user,
|
|
20
|
+
collection: @root_collection
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
@chart.name = @chart.name.presence || @chart.query&.name
|
|
24
|
+
@chart.visualization = { "type" => "table" } if @chart.visualization.blank?
|
|
25
|
+
@chart.query&.creator = current_nquery_user
|
|
26
|
+
@chart.query&.collection = @root_collection
|
|
27
|
+
|
|
28
|
+
if @chart.save
|
|
29
|
+
redirect_to edit_chart_path(@chart), notice: "Chart created."
|
|
30
|
+
else
|
|
31
|
+
load_chart_builder_assigns
|
|
32
|
+
render :new, status: :unprocessable_content
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def set_root_collection
|
|
39
|
+
@root_collection = Collection.roots.first
|
|
40
|
+
redirect_to root_path, alert: "No collection available." unless @root_collection
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def authorize_chart_create!
|
|
44
|
+
authorize_collection_access!(@root_collection, required: :curate)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def set_chart
|
|
48
|
+
@chart = Chart.find(params[:id])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
class Collection::DashboardsController < ApplicationController
|
|
5
|
+
include Browsable
|
|
6
|
+
|
|
7
|
+
before_action :set_collection
|
|
8
|
+
before_action :authorize_collection_curate!
|
|
9
|
+
|
|
10
|
+
def new
|
|
11
|
+
@dashboard = Dashboard.new(collection: @collection)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create
|
|
15
|
+
@dashboard = Dashboard.new(dashboard_params.merge(collection: @collection, creator: current_nquery_user))
|
|
16
|
+
|
|
17
|
+
if @dashboard.save
|
|
18
|
+
redirect_to dashboard_path(@dashboard), notice: "Dashboard created."
|
|
19
|
+
else
|
|
20
|
+
render :new, status: :unprocessable_content
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def set_collection
|
|
27
|
+
@collection = Collection.find(params[:collection_id])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def authorize_collection_curate!
|
|
31
|
+
authorize_collection_access!(@collection, required: :curate)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dashboard_params
|
|
35
|
+
params.require(:dashboard).permit(:name, :description, settings: {})
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
class CollectionsController < ApplicationController
|
|
5
|
+
include Browsable
|
|
6
|
+
|
|
7
|
+
before_action :set_collection, only: %i[show edit update destroy archive unarchive]
|
|
8
|
+
before_action :set_parent_collection, only: %i[new create]
|
|
9
|
+
before_action :authorize_collection_view!, only: %i[show]
|
|
10
|
+
before_action :authorize_collection_curate!, only: %i[edit update destroy archive unarchive]
|
|
11
|
+
before_action :authorize_parent_curate!, only: %i[new create]
|
|
12
|
+
|
|
13
|
+
before_action :prevent_root_deletion, only: %i[destroy]
|
|
14
|
+
before_action :prevent_root_archival, only: %i[archive]
|
|
15
|
+
|
|
16
|
+
def index
|
|
17
|
+
@collections = viewable_collections
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def show
|
|
21
|
+
load_collection_contents(@collection)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def new
|
|
25
|
+
@collection = Collection.new(kind: "standard", parent: @parent_collection)
|
|
26
|
+
@parent_collections = assignable_collections
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def create
|
|
30
|
+
@collection = Collection.new(collection_params.merge(kind: "standard", parent: @parent_collection))
|
|
31
|
+
@parent_collections = assignable_collections
|
|
32
|
+
|
|
33
|
+
if @collection.save
|
|
34
|
+
redirect_to collection_path(@collection), notice: "Collection created."
|
|
35
|
+
else
|
|
36
|
+
render :new, status: :unprocessable_content
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def edit
|
|
41
|
+
@parent_collections = assignable_collections.reject { |c| c.id == @collection.id }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def update
|
|
45
|
+
@parent_collections = assignable_collections.reject { |c| c.id == @collection.id }
|
|
46
|
+
if collection_params[:parent_id].present?
|
|
47
|
+
parent = Collection.find_by(id: collection_params[:parent_id])
|
|
48
|
+
authorize_collection_access!(parent, required: :curate) if parent
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
if @collection.update(collection_params)
|
|
52
|
+
redirect_to collection_path(@collection), notice: "Collection updated."
|
|
53
|
+
else
|
|
54
|
+
render :edit, status: :unprocessable_content
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def destroy
|
|
59
|
+
@collection.destroy
|
|
60
|
+
redirect_to collections_path, notice: "Collection deleted."
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def archive
|
|
64
|
+
@collection.archive!
|
|
65
|
+
redirect_to collections_path, notice: "Collection archived."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def unarchive
|
|
69
|
+
@collection.unarchive!
|
|
70
|
+
redirect_to collection_path(@collection), notice: "Collection unarchived."
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def set_collection
|
|
76
|
+
@collection = Collection.find(params[:id])
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def set_parent_collection
|
|
80
|
+
return if params[:collection_id].blank?
|
|
81
|
+
|
|
82
|
+
@parent_collection = Collection.find(params[:collection_id])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def authorize_parent_curate!
|
|
86
|
+
parent_id = params.dig(:collection, :parent_id)
|
|
87
|
+
parent = @parent_collection || Collection.find_by(id: parent_id)
|
|
88
|
+
return if parent.nil?
|
|
89
|
+
|
|
90
|
+
authorize_collection_access!(parent, required: :curate)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def authorize_collection_view!
|
|
94
|
+
authorize_collection_access!(@collection, required: :view)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def authorize_collection_curate!
|
|
98
|
+
authorize_collection_access!(@collection, required: :curate)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def prevent_root_deletion
|
|
102
|
+
return unless @collection.kind == "root"
|
|
103
|
+
|
|
104
|
+
redirect_to collections_path, alert: "Root collections cannot be deleted."
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def prevent_root_archival
|
|
108
|
+
return unless @collection.kind == "root"
|
|
109
|
+
|
|
110
|
+
redirect_to collections_path, alert: "Root collections cannot be archived."
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def collection_params
|
|
114
|
+
params.require(:collection).permit(:name, :parent_id)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
class Dashboard::ChartsController < ApplicationController
|
|
5
|
+
include ChartActions
|
|
6
|
+
|
|
7
|
+
prepend_before_action :set_dashboard
|
|
8
|
+
before_action :authorize_dashboard_view!, only: %i[show embed]
|
|
9
|
+
before_action :authorize_dashboard_curate!, except: %i[show embed]
|
|
10
|
+
|
|
11
|
+
def new
|
|
12
|
+
@chart = Chart.new
|
|
13
|
+
@chart.build_query(statement: "SELECT 1 AS example")
|
|
14
|
+
load_chart_builder_assigns
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create
|
|
18
|
+
@chart = Chart.new(
|
|
19
|
+
chart_params.merge(
|
|
20
|
+
creator: current_nquery_user,
|
|
21
|
+
collection: @dashboard.collection
|
|
22
|
+
)
|
|
23
|
+
)
|
|
24
|
+
@chart.name = @chart.name.presence || @chart.query&.name
|
|
25
|
+
@chart.visualization = { "type" => "table" } if @chart.visualization.blank?
|
|
26
|
+
@chart.query&.creator = current_nquery_user
|
|
27
|
+
@chart.query&.collection = @dashboard.collection
|
|
28
|
+
|
|
29
|
+
if @chart.save
|
|
30
|
+
@dashboard.dashboard_cards.create!(
|
|
31
|
+
chart: @chart,
|
|
32
|
+
pos_x: 0,
|
|
33
|
+
pos_y: 0,
|
|
34
|
+
width: 6,
|
|
35
|
+
height: 4
|
|
36
|
+
)
|
|
37
|
+
redirect_to edit_dashboard_chart_path(@dashboard, @chart), notice: "Chart created."
|
|
38
|
+
else
|
|
39
|
+
load_chart_builder_assigns
|
|
40
|
+
render :new, status: :unprocessable_content
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def set_dashboard
|
|
47
|
+
@dashboard = Dashboard.find(params[:dashboard_id])
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def set_chart
|
|
51
|
+
@chart = @dashboard.charts.find(params[:id])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def authorize_dashboard_view!
|
|
55
|
+
authorize_collection_access!(@dashboard.collection, required: :view)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def authorize_dashboard_curate!
|
|
59
|
+
authorize_collection_access!(@dashboard.collection, required: :curate)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nquery
|
|
4
|
+
class DashboardsController < ApplicationController
|
|
5
|
+
include ChartResults
|
|
6
|
+
include Browsable
|
|
7
|
+
|
|
8
|
+
before_action :set_dashboard, only: %i[show edit update destroy archive unarchive update_layout]
|
|
9
|
+
before_action :authorize_dashboard_view!, only: %i[show]
|
|
10
|
+
before_action :authorize_dashboard_curate!, only: %i[edit update destroy archive unarchive update_layout]
|
|
11
|
+
|
|
12
|
+
def index
|
|
13
|
+
@dashboards = filter_viewable_dashboards(Dashboard.active.includes(:collection).order(:name))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def show
|
|
17
|
+
@dashboard_cards = @dashboard.dashboard_cards
|
|
18
|
+
.joins(:chart)
|
|
19
|
+
.merge(Chart.active)
|
|
20
|
+
.includes(:chart)
|
|
21
|
+
@card_results = @dashboard_cards.index_with { |card| chart_result(card.chart) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def edit
|
|
25
|
+
@collections = assignable_collections
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def update
|
|
29
|
+
@collections = assignable_collections
|
|
30
|
+
authorize_collection_access!(Collection.find(dashboard_params[:collection_id]), required: :curate) if dashboard_params[:collection_id].present?
|
|
31
|
+
|
|
32
|
+
if @dashboard.update(dashboard_params)
|
|
33
|
+
redirect_to dashboard_path(@dashboard), notice: "Dashboard updated."
|
|
34
|
+
else
|
|
35
|
+
render :edit, status: :unprocessable_content
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def destroy
|
|
40
|
+
@dashboard.destroy
|
|
41
|
+
redirect_to dashboards_path, notice: "Dashboard removed."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def archive
|
|
45
|
+
@dashboard.archive!
|
|
46
|
+
redirect_to dashboards_path, notice: "Dashboard archived."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def unarchive
|
|
50
|
+
@dashboard.unarchive!
|
|
51
|
+
redirect_to dashboard_path(@dashboard), notice: "Dashboard unarchived."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update_layout
|
|
55
|
+
card_layouts.each do |card_id, layout|
|
|
56
|
+
card = @dashboard.dashboard_cards.find(card_id)
|
|
57
|
+
card.update(pos_x: layout[:x], pos_y: layout[:y], width: layout[:w], height: layout[:h])
|
|
58
|
+
end
|
|
59
|
+
head :ok
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def set_dashboard
|
|
65
|
+
@dashboard = if action_name.in?(%w[show edit])
|
|
66
|
+
Dashboard.includes(:creator, dashboard_cards: :chart).find(params[:id])
|
|
67
|
+
else
|
|
68
|
+
Dashboard.find(params[:id])
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def authorize_dashboard_view!
|
|
73
|
+
authorize_collection_access!(@dashboard.collection, required: :view)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def authorize_dashboard_curate!
|
|
77
|
+
authorize_collection_access!(@dashboard.collection, required: :curate)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def card_layouts
|
|
81
|
+
params.permit(cards: {}).fetch(:cards, {}).transform_values do |layout|
|
|
82
|
+
layout.permit(:x, :y, :w, :h).to_h.symbolize_keys
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def dashboard_params
|
|
87
|
+
params.require(:dashboard).permit(:name, :description, :collection_id, settings: {})
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|