soql_dashboard 0.1.1 → 0.3.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 +4 -4
- data/README.md +7 -0
- data/app/assets/javascripts/soql_dashboard/application.js +32 -0
- data/app/controllers/soql_dashboard/reports_controller.rb +42 -3
- data/app/lib/soql_dashboard/salesforce_api_client.rb +58 -9
- data/app/models/soql_dashboard/audit.rb +18 -0
- data/app/services/soql_dashboard/run_statement.rb +80 -0
- data/app/services/soql_dashboard/soql_executor.rb +1 -1
- data/app/views/layouts/soql_dashboard/application.html.erb +1 -0
- data/app/views/soql_dashboard/reports/index.html.erb +25 -3
- data/db/migrate/20250919113356_create_soql_dashboard_audits.rb +16 -0
- data/lib/generators/soql_dashboard/install_generator.rb +15 -0
- data/lib/soql_dashboard/engine.rb +1 -0
- data/lib/soql_dashboard/version.rb +1 -1
- data/lib/soql_dashboard.rb +27 -2
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bed67fd1eebb62ccb69de1e3339c57111880529f390e60df26cad66891cf9450
|
|
4
|
+
data.tar.gz: dd40bc1e03c1f0f942a4235aef591b1665e88155fda7b1ec50883f5be46771b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 71b08f78a91658a4ed3733fc021d3c80d98cd27c40a623e9280bd7790ebb508454ee9c5015f09355e02e55a83479983e7ae8b0a3f892bf7f465c9889474a826c
|
|
7
|
+
data.tar.gz: e46e0a99a3339eb9c57579e995de9324f92dc8b2e0af29ebdb0f348e7971c6194ef33e2341beb6da62b6dde7346eb0f85d144cbc9851cb89da59e5e2fd20042c
|
data/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A Rails engine that provides a dashboard interface for querying Salesforce using
|
|
|
6
6
|
|
|
7
7
|
- Query Salesforce objects using SOQL
|
|
8
8
|
- View results in a user-friendly dashboard
|
|
9
|
+
- Audit trail for all SOQL queries with user tracking
|
|
9
10
|
- Easily mountable in any Rails application
|
|
10
11
|
|
|
11
12
|
## Installation
|
|
@@ -20,8 +21,14 @@ Then run:
|
|
|
20
21
|
|
|
21
22
|
```sh
|
|
22
23
|
bundle install
|
|
24
|
+
rails generate soql_dashboard:install
|
|
25
|
+
rails soql_dashboard:install:migrations
|
|
26
|
+
rails db:migrate
|
|
23
27
|
```
|
|
24
28
|
|
|
29
|
+
This will create the necessary database table:
|
|
30
|
+
- `soql_dashboard_audits` - tracks all query executions for audit purposes
|
|
31
|
+
|
|
25
32
|
## Mounting the Engine
|
|
26
33
|
|
|
27
34
|
In your application's `config/routes.rb`:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
2
|
+
try {
|
|
3
|
+
var integrationSelect = document.querySelector('#integration-selector-form select.integration-dropdown');
|
|
4
|
+
if (integrationSelect && integrationSelect.form) {
|
|
5
|
+
integrationSelect.addEventListener('change', function () {
|
|
6
|
+
try { integrationSelect.form.submit(); } catch (e) { /* no-op */ }
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function insertSelectedObject() {
|
|
11
|
+
try {
|
|
12
|
+
var select = document.getElementById('available-objects');
|
|
13
|
+
var value = select && select.value;
|
|
14
|
+
if (!value) { return; }
|
|
15
|
+
var textarea = document.getElementById('soql_textarea') || document.querySelector('textarea[name="soql_query"]');
|
|
16
|
+
var snippet = 'SELECT FIELDS(ALL) FROM ' + value + ' LIMIT 200';
|
|
17
|
+
if (textarea) {
|
|
18
|
+
textarea.value = snippet;
|
|
19
|
+
textarea.focus();
|
|
20
|
+
}
|
|
21
|
+
} catch (e) { /* no-op */ }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var objectsSelect = document.getElementById('available-objects');
|
|
25
|
+
if (objectsSelect) {
|
|
26
|
+
objectsSelect.addEventListener('change', insertSelectedObject);
|
|
27
|
+
}
|
|
28
|
+
} catch (e) {
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
|
|
@@ -4,9 +4,12 @@ module SoqlDashboard
|
|
|
4
4
|
class ReportsController < ApplicationController
|
|
5
5
|
before_action :set_integration, only: %i[index execute_query]
|
|
6
6
|
|
|
7
|
+
FALLBACK_OBJECTS = %w[Account Contact Opportunity Lead Case User].freeze
|
|
8
|
+
|
|
7
9
|
def index
|
|
8
10
|
@integrations = available_integrations
|
|
9
11
|
@selected_integration = @integration
|
|
12
|
+
@objects = integration_objects
|
|
10
13
|
@query_result = nil
|
|
11
14
|
end
|
|
12
15
|
|
|
@@ -15,6 +18,7 @@ module SoqlDashboard
|
|
|
15
18
|
|
|
16
19
|
@integrations = available_integrations
|
|
17
20
|
@selected_integration = @integration
|
|
21
|
+
@objects = integration_objects
|
|
18
22
|
@soql_query = params[:soql_query]
|
|
19
23
|
query_execution if @soql_query.present?
|
|
20
24
|
render :index
|
|
@@ -29,11 +33,20 @@ module SoqlDashboard
|
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def query_execution
|
|
32
|
-
|
|
36
|
+
service = SoqlDashboard::RunStatement.new
|
|
37
|
+
result = service.call(@soql_query, {
|
|
38
|
+
user: current_user_for_audit,
|
|
39
|
+
salesforce_integration: @integration,
|
|
40
|
+
})
|
|
41
|
+
|
|
33
42
|
if result[:error]
|
|
34
43
|
@error = result[:error]
|
|
35
44
|
else
|
|
36
|
-
@query_result =
|
|
45
|
+
@query_result = {
|
|
46
|
+
results: result[:result][:records] || [],
|
|
47
|
+
total_size: result[:result][:total_size] || 0,
|
|
48
|
+
duration: result[:duration],
|
|
49
|
+
}
|
|
37
50
|
end
|
|
38
51
|
rescue StandardError => e
|
|
39
52
|
@error = e.message
|
|
@@ -67,8 +80,34 @@ module SoqlDashboard
|
|
|
67
80
|
{
|
|
68
81
|
query:,
|
|
69
82
|
results: result[:records] || [],
|
|
70
|
-
total_size: result[:
|
|
83
|
+
total_size: result[:total_size] || 0,
|
|
71
84
|
}
|
|
72
85
|
end
|
|
86
|
+
|
|
87
|
+
def integration_objects
|
|
88
|
+
return [] unless @integration
|
|
89
|
+
|
|
90
|
+
client = SoqlDashboard::SalesforceApiClient.new(@integration.send(config_method_name))
|
|
91
|
+
result = client.list_all_queryable_objects
|
|
92
|
+
|
|
93
|
+
Rails.logger.debug { "integration_objects: #{result.to_json}" }
|
|
94
|
+
if result[:error]
|
|
95
|
+
Rails.logger.error "Failed to fetch objects: #{result[:error]}"
|
|
96
|
+
# Fallback to common Salesforce objects
|
|
97
|
+
FALLBACK_OBJECTS
|
|
98
|
+
else
|
|
99
|
+
records = result[:records]&.pluck("QualifiedApiName")
|
|
100
|
+
records&.any? ? records : FALLBACK_OBJECTS
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def current_user_for_audit
|
|
105
|
+
return nil unless SoqlDashboard.user_class
|
|
106
|
+
|
|
107
|
+
method_name = SoqlDashboard.user_method
|
|
108
|
+
respond_to?(method_name, true) ? send(method_name) : nil
|
|
109
|
+
rescue StandardError
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
73
112
|
end
|
|
74
113
|
end
|
|
@@ -6,7 +6,7 @@ require "json"
|
|
|
6
6
|
require "timeout"
|
|
7
7
|
|
|
8
8
|
module SoqlDashboard
|
|
9
|
-
class SalesforceApiClient
|
|
9
|
+
class SalesforceApiClient # rubocop:disable Metrics/ClassLength
|
|
10
10
|
def initialize(config)
|
|
11
11
|
@config = config
|
|
12
12
|
end
|
|
@@ -18,14 +18,24 @@ module SoqlDashboard
|
|
|
18
18
|
response = http.request(request)
|
|
19
19
|
|
|
20
20
|
parse_response(response)
|
|
21
|
-
rescue Timeout::Error
|
|
22
|
-
{ error: "Request timed out. Please try again." }
|
|
23
|
-
rescue Net::HTTPError => e
|
|
24
|
-
{ error: "Network error: #{e.message}" }
|
|
25
|
-
rescue JSON::ParserError
|
|
26
|
-
{ error: "Invalid response from Salesforce API" }
|
|
27
21
|
rescue StandardError => e
|
|
28
|
-
|
|
22
|
+
handle_error(e)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list_all_queryable_objects
|
|
26
|
+
# Use describe_global to get all available objects
|
|
27
|
+
describe_result = describe_global
|
|
28
|
+
return describe_result if describe_result[:error]
|
|
29
|
+
|
|
30
|
+
# Filter to only queryable objects
|
|
31
|
+
queryable_objects = describe_result[:sobjects]&.select do |sobject|
|
|
32
|
+
sobject["queryable"] == true
|
|
33
|
+
end&.pluck("name") || []
|
|
34
|
+
|
|
35
|
+
{
|
|
36
|
+
total_size: queryable_objects.length,
|
|
37
|
+
records: queryable_objects.map { |name| { "QualifiedApiName" => name } },
|
|
38
|
+
}
|
|
29
39
|
end
|
|
30
40
|
|
|
31
41
|
private
|
|
@@ -72,7 +82,7 @@ module SoqlDashboard
|
|
|
72
82
|
result = JSON.parse(response.body)
|
|
73
83
|
|
|
74
84
|
{
|
|
75
|
-
|
|
85
|
+
total_size: result["totalSize"],
|
|
76
86
|
records: result["records"] || [],
|
|
77
87
|
}
|
|
78
88
|
end
|
|
@@ -82,5 +92,44 @@ module SoqlDashboard
|
|
|
82
92
|
error_message = error_data.first["message"] || "Bad Request"
|
|
83
93
|
{ error: "SOQL Error: #{error_message}" }
|
|
84
94
|
end
|
|
95
|
+
|
|
96
|
+
def handle_error(error)
|
|
97
|
+
case error
|
|
98
|
+
when Timeout::Error
|
|
99
|
+
{ error: "Request timed out. Please try again." }
|
|
100
|
+
when Net::HTTPError
|
|
101
|
+
{ error: "Network error: #{error.message}" }
|
|
102
|
+
when JSON::ParserError
|
|
103
|
+
{ error: "Invalid response from Salesforce API" }
|
|
104
|
+
else
|
|
105
|
+
{ error: "Unexpected error: #{error.message}" }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def describe_global
|
|
110
|
+
uri = build_describe_uri
|
|
111
|
+
http = setup_http_connection(uri)
|
|
112
|
+
request = create_request(uri)
|
|
113
|
+
response = http.request(request)
|
|
114
|
+
|
|
115
|
+
parse_describe_response(response)
|
|
116
|
+
rescue StandardError => e
|
|
117
|
+
handle_error(e)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def build_describe_uri
|
|
121
|
+
URI("#{config['instance_url']}/services/data/v58.0/sobjects/")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def parse_describe_response(response)
|
|
125
|
+
case response.code.to_i
|
|
126
|
+
when 200
|
|
127
|
+
result = JSON.parse(response.body)
|
|
128
|
+
{ sobjects: result["sobjects"] || [] }
|
|
129
|
+
else
|
|
130
|
+
# Reuse the error handling from parse_response for common HTTP errors
|
|
131
|
+
parse_response(response)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
85
134
|
end
|
|
86
135
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SoqlDashboard
|
|
4
|
+
class Audit < ApplicationRecord
|
|
5
|
+
self.table_name = "soql_dashboard_audits"
|
|
6
|
+
|
|
7
|
+
belongs_to :user, optional: true, class_name: SoqlDashboard.user_class.to_s if SoqlDashboard.user_class
|
|
8
|
+
if SoqlDashboard.salesforce_integration_model
|
|
9
|
+
belongs_to :salesforce_integration, optional: true,
|
|
10
|
+
class_name: SoqlDashboard.salesforce_integration_model.to_s
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
validates :statement, presence: true
|
|
14
|
+
|
|
15
|
+
scope :recent, -> { order(created_at: :desc) }
|
|
16
|
+
scope :for_integration, ->(integration_id) { where(salesforce_integration_id: integration_id) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SoqlDashboard
|
|
4
|
+
class RunStatement
|
|
5
|
+
def call(query_text, options = {})
|
|
6
|
+
audit = create_audit_record(query_text, options) if audit_enabled?
|
|
7
|
+
|
|
8
|
+
execution_result = execute_with_timing(query_text, options)
|
|
9
|
+
update_audit_record(audit, execution_result) if audit
|
|
10
|
+
|
|
11
|
+
build_response(execution_result)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def audit_enabled?
|
|
17
|
+
SoqlDashboard.audit
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create_audit_record(query_text, options)
|
|
21
|
+
audit = SoqlDashboard::Audit.new(statement: query_text)
|
|
22
|
+
|
|
23
|
+
assign_audit_associations(audit, options)
|
|
24
|
+
audit.save!
|
|
25
|
+
audit
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def assign_audit_associations(audit, options)
|
|
29
|
+
audit.user = options[:user] if options[:user] && audit.respond_to?(:user=)
|
|
30
|
+
|
|
31
|
+
return unless options[:salesforce_integration] && audit.respond_to?(:salesforce_integration=)
|
|
32
|
+
|
|
33
|
+
audit.salesforce_integration = options[:salesforce_integration]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def execute_with_timing(query_text, options)
|
|
37
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
result = execute_soql_query(query_text, options)
|
|
41
|
+
{ success: true, result:, error: nil }
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
{ success: false, result: nil, error: e.message }
|
|
44
|
+
ensure
|
|
45
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
46
|
+
@duration = duration # Keep this for build_response method
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def execute_soql_query(query_text, options)
|
|
51
|
+
service = SoqlDashboard::SoqlExecutor.new(options[:salesforce_integration])
|
|
52
|
+
result = service.call(query_text)
|
|
53
|
+
|
|
54
|
+
raise result[:error] if result[:error]
|
|
55
|
+
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def update_audit_record(audit, execution_result)
|
|
60
|
+
return unless audit&.persisted?
|
|
61
|
+
|
|
62
|
+
audit.duration = duration if audit.respond_to?(:duration=)
|
|
63
|
+
audit.error = execution_result[:error] if audit.respond_to?(:error=)
|
|
64
|
+
|
|
65
|
+
audit.save! if audit.changed?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_response(execution_result)
|
|
69
|
+
{
|
|
70
|
+
result: execution_result[:result],
|
|
71
|
+
error: execution_result[:error],
|
|
72
|
+
duration:,
|
|
73
|
+
}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def duration
|
|
77
|
+
@duration || 0.0
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -32,7 +32,7 @@ module SoqlDashboard
|
|
|
32
32
|
query_lower = query.downcase
|
|
33
33
|
|
|
34
34
|
forbidden_keywords.each do |keyword|
|
|
35
|
-
raise ArgumentError, "#{keyword.upcase} statements are not allowed" if query_lower.
|
|
35
|
+
raise ArgumentError, "#{keyword.upcase} statements are not allowed" if query_lower.match?(/\b#{keyword}\b/)
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
),
|
|
15
15
|
{ prompt: "Select a Salesforce Integration..." },
|
|
16
16
|
{
|
|
17
|
-
class: "integration-dropdown"
|
|
18
|
-
onchange: "this.form.submit();"
|
|
17
|
+
class: "integration-dropdown"
|
|
19
18
|
}
|
|
20
19
|
%>
|
|
21
20
|
<% end %>
|
|
@@ -38,6 +37,7 @@
|
|
|
38
37
|
value: @soql_query,
|
|
39
38
|
placeholder: "SELECT Id, Name FROM Opportunity LIMIT 10",
|
|
40
39
|
rows: 5,
|
|
40
|
+
id: "soql_textarea",
|
|
41
41
|
class: "soql-textarea" %>
|
|
42
42
|
</div>
|
|
43
43
|
|
|
@@ -46,6 +46,27 @@
|
|
|
46
46
|
</div>
|
|
47
47
|
<% end %>
|
|
48
48
|
</div>
|
|
49
|
+
<% if @objects.is_a?(Array) && @objects.any? %>
|
|
50
|
+
<div class="field-selector" style="margin: 12px 0; padding: 12px; background: #ffffff; border: 1px solid #e5e7eb; border-radius: 6px;">
|
|
51
|
+
<div style="display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;">
|
|
52
|
+
<div>
|
|
53
|
+
<div style="font-weight: 600; margin-bottom: 6px; color: #374151;">
|
|
54
|
+
Available Salesforce objects
|
|
55
|
+
</div>
|
|
56
|
+
<select
|
|
57
|
+
id="available-objects"
|
|
58
|
+
style="min-width: 360px; width: 100%; max-width: 680px;"
|
|
59
|
+
class="integration-dropdown"
|
|
60
|
+
>
|
|
61
|
+
<option disabled selected value="">Select an object...</option>
|
|
62
|
+
<% (@objects || []).sort.each do |obj_name| %>
|
|
63
|
+
<option value="<%= obj_name %>"><%= obj_name %></option>
|
|
64
|
+
<% end %>
|
|
65
|
+
</select>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
<% end %>
|
|
49
70
|
|
|
50
71
|
<% if @error %>
|
|
51
72
|
<div class="error-section">
|
|
@@ -60,8 +81,9 @@
|
|
|
60
81
|
|
|
61
82
|
<div class="results-meta">
|
|
62
83
|
<p>
|
|
63
|
-
<strong>Query:</strong> <%= @
|
|
84
|
+
<strong>Query:</strong> <%= @soql_query %><br>
|
|
64
85
|
<strong>Total Records:</strong> <%= @query_result[:total_size] %><br>
|
|
86
|
+
<strong>Execution Time:</strong> <%= number_with_precision(@query_result[:duration], precision: 3) %>s<br>
|
|
65
87
|
</p>
|
|
66
88
|
</div>
|
|
67
89
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateSoqlDashboardAudits < ActiveRecord::Migration[7.0]
|
|
4
|
+
def change
|
|
5
|
+
create_table :soql_dashboard_audits do |t|
|
|
6
|
+
t.references :user, null: true, index: true
|
|
7
|
+
t.references :salesforce_integration, null: true, index: true
|
|
8
|
+
t.text :statement
|
|
9
|
+
t.float :duration
|
|
10
|
+
t.text :error
|
|
11
|
+
t.datetime :created_at
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
add_index :soql_dashboard_audits, :created_at
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators/active_record"
|
|
4
|
+
|
|
5
|
+
module SoqlDashboard
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.join(__dir__, "install", "templates")
|
|
9
|
+
|
|
10
|
+
def copy_config
|
|
11
|
+
template "soql_dashboard.rb", "config/initializers/soql_dashboard.rb"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/soql_dashboard.rb
CHANGED
|
@@ -5,21 +5,46 @@ require "soql_dashboard/engine"
|
|
|
5
5
|
|
|
6
6
|
module SoqlDashboard
|
|
7
7
|
class << self
|
|
8
|
-
attr_accessor :configuration
|
|
8
|
+
attr_accessor :configuration, :audit
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
self.audit = true
|
|
12
|
+
|
|
11
13
|
def self.configure
|
|
12
14
|
self.configuration ||= Configuration.new
|
|
13
15
|
yield(configuration) if block_given?
|
|
14
16
|
end
|
|
15
17
|
|
|
18
|
+
def self.user_class
|
|
19
|
+
@user_class ||= configuration&.user_class || begin
|
|
20
|
+
User.name
|
|
21
|
+
rescue StandardError
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.user_method
|
|
27
|
+
@user_method ||= configuration&.user_method || (
|
|
28
|
+
if user_class
|
|
29
|
+
:"current_#{user_class.to_s.downcase.underscore}"
|
|
30
|
+
else
|
|
31
|
+
:current_user
|
|
32
|
+
end
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.salesforce_integration_model
|
|
37
|
+
@salesforce_integration_model ||= configuration&.salesforce_integration_model
|
|
38
|
+
end
|
|
39
|
+
|
|
16
40
|
class Configuration
|
|
17
|
-
attr_accessor :salesforce_integration_model, :user_class, :user_method
|
|
41
|
+
attr_accessor :salesforce_integration_model, :user_class, :user_method, :audit
|
|
18
42
|
|
|
19
43
|
def initialize
|
|
20
44
|
@salesforce_integration_model = nil
|
|
21
45
|
@user_class = nil
|
|
22
46
|
@user_method = :current_user
|
|
47
|
+
@audit = true
|
|
23
48
|
end
|
|
24
49
|
end
|
|
25
50
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: soql_dashboard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fred Moura
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-10-06 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pg
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- README.md
|
|
51
51
|
- Rakefile
|
|
52
52
|
- app/assets/config/soql_dashboard_manifest.js
|
|
53
|
+
- app/assets/javascripts/soql_dashboard/application.js
|
|
53
54
|
- app/assets/stylesheets/soql_dashboard/application.css
|
|
54
55
|
- app/controllers/soql_dashboard/application_controller.rb
|
|
55
56
|
- app/controllers/soql_dashboard/reports_controller.rb
|
|
@@ -58,11 +59,15 @@ files:
|
|
|
58
59
|
- app/lib/soql_dashboard/salesforce_api_client.rb
|
|
59
60
|
- app/mailers/soql_dashboard/application_mailer.rb
|
|
60
61
|
- app/models/soql_dashboard/application_record.rb
|
|
62
|
+
- app/models/soql_dashboard/audit.rb
|
|
63
|
+
- app/services/soql_dashboard/run_statement.rb
|
|
61
64
|
- app/services/soql_dashboard/soql_executor.rb
|
|
62
65
|
- app/views/layouts/soql_dashboard/application.html.erb
|
|
63
66
|
- app/views/soql_dashboard/reports/index.html.erb
|
|
64
67
|
- config/routes.rb
|
|
68
|
+
- db/migrate/20250919113356_create_soql_dashboard_audits.rb
|
|
65
69
|
- lib/generators/soql_dashboard/install/templates/soql_dashboard.rb
|
|
70
|
+
- lib/generators/soql_dashboard/install_generator.rb
|
|
66
71
|
- lib/soql_dashboard.rb
|
|
67
72
|
- lib/soql_dashboard/engine.rb
|
|
68
73
|
- lib/soql_dashboard/version.rb
|