soql_dashboard 0.1.0.pre.test.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d081ecb90e2e5a9f2507e6f0347b734c5cdd8e5da27e0fe3efbe30ab4486f9d4
4
+ data.tar.gz: e6f8cebd896f7e8cdf854df8cc6be89e834a5bce48fa073ab55f2a54a0ed4434
5
+ SHA512:
6
+ metadata.gz: 70e996f4a9b28a828a4758bdbb9eca19ebaba92fd0d7041cf2b87134647cc3d792f34690a540e373c9f6a38f39f45224c7ff735589b43477283851faef914eca
7
+ data.tar.gz: ac851129e8ba96aed3fbd90ca69ab028fe1cec34ab1e1e1c64ffaaa374c30b035e289b017771fcaeb0f64e811cf4deeb0f5ae32bcb7f09f0ec114637d6dac227
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 TrustedIQ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # SOQL Dashboard Engine
2
+
3
+ A Rails engine that provides a dashboard interface for querying Salesforce using SOQL.
4
+
5
+ ## Features
6
+
7
+ - Query Salesforce objects using SOQL
8
+ - View results in a user-friendly dashboard
9
+ - Easily mountable in any Rails application
10
+
11
+ ## Installation
12
+
13
+ Add the engine to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'soql_dashboard'
17
+ ```
18
+
19
+ Then run:
20
+
21
+ ```sh
22
+ bundle install
23
+ ```
24
+
25
+ ## Mounting the Engine
26
+
27
+ In your application's `config/routes.rb`:
28
+
29
+ ```ruby
30
+ mount SoqlDashboard::Engine => '/soql_dashboard'
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ - Visit `/soql_dashboard` in your browser to access the dashboard.
36
+ - Select a Salesforce model and run SOQL queries.
37
+
38
+ ## Development
39
+
40
+ To work on the engine:
41
+
42
+ ```sh
43
+ cd engines/soql_dashboard
44
+ ```
45
+
46
+ Run tests:
47
+
48
+ ```sh
49
+ bundle exec rspec
50
+ ```
51
+
52
+ ## Contributing
53
+
54
+ Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
55
+
56
+ ## License
57
+
58
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+
5
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
6
+ load "rails/tasks/engine.rake"
7
+
8
+ load "rails/tasks/statistics.rake"
9
+
10
+ require "bundler/gem_tasks"
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/soql_dashboard .css
@@ -0,0 +1,276 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
17
+ .soql-dashboard {
18
+ max-width: 1200px;
19
+ margin: 0 auto;
20
+ padding: 20px;
21
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
22
+ }
23
+
24
+ .soql-dashboard h1 {
25
+ color: #333;
26
+ border-bottom: 3px solid #0066cc;
27
+ padding-bottom: 10px;
28
+ margin-bottom: 30px;
29
+ }
30
+
31
+ .integration-selector {
32
+ background: #f8f9fa;
33
+ padding: 20px;
34
+ border-radius: 8px;
35
+ margin-bottom: 30px;
36
+ }
37
+
38
+ .integration-selector h3 {
39
+ margin-top: 0;
40
+ color: #495057;
41
+ }
42
+
43
+ .integrations-list {
44
+ display: grid;
45
+ grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
46
+ gap: 15px;
47
+ margin-top: 15px;
48
+ }
49
+
50
+ .integrations-dropdown {
51
+ margin-top: 15px;
52
+ }
53
+
54
+ .integration-dropdown {
55
+ width: 100%;
56
+ max-width: 400px;
57
+ padding: 12px 16px;
58
+ font-size: 16px;
59
+ border: 2px solid #dee2e6;
60
+ border-radius: 6px;
61
+ background: white;
62
+ color: #333;
63
+ cursor: pointer;
64
+ transition: all 0.2s ease;
65
+ }
66
+
67
+ .integration-dropdown:hover {
68
+ border-color: #0066cc;
69
+ }
70
+
71
+ .integration-dropdown:focus {
72
+ outline: none;
73
+ border-color: #0066cc;
74
+ box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
75
+ }
76
+
77
+ .integration-item {
78
+ background: white;
79
+ border: 2px solid #dee2e6;
80
+ border-radius: 6px;
81
+ transition: all 0.2s ease;
82
+ }
83
+
84
+ .integration-item:hover {
85
+ border-color: #0066cc;
86
+ box-shadow: 0 2px 8px rgba(0, 102, 204, 0.1);
87
+ }
88
+
89
+ .integration-item.selected {
90
+ border-color: #0066cc;
91
+ background: #e3f2fd;
92
+ }
93
+
94
+ .integration-link {
95
+ display: block;
96
+ padding: 15px;
97
+ text-decoration: none;
98
+ color: #333;
99
+ }
100
+
101
+ .integration-link:hover {
102
+ text-decoration: none;
103
+ color: #333;
104
+ }
105
+
106
+ .integration-link strong {
107
+ display: block;
108
+ font-size: 16px;
109
+ margin-bottom: 5px;
110
+ }
111
+
112
+ .integration-link small {
113
+ color: #6c757d;
114
+ font-size: 12px;
115
+ }
116
+
117
+ .no-integrations {
118
+ color: #dc3545;
119
+ font-style: italic;
120
+ text-align: center;
121
+ padding: 20px;
122
+ }
123
+
124
+ .query-section {
125
+ background: white;
126
+ border: 1px solid #dee2e6;
127
+ border-radius: 8px;
128
+ padding: 25px;
129
+ margin-bottom: 30px;
130
+ }
131
+
132
+ .query-section h3 {
133
+ margin-top: 0;
134
+ color: #495057;
135
+ }
136
+
137
+ .query-input {
138
+ margin-bottom: 20px;
139
+ }
140
+
141
+ .query-input label {
142
+ display: block;
143
+ font-weight: 600;
144
+ margin-bottom: 8px;
145
+ color: #495057;
146
+ }
147
+
148
+ .soql-textarea {
149
+ width: 100%;
150
+ min-height: 120px;
151
+ padding: 12px;
152
+ border: 1px solid #ced4da;
153
+ border-radius: 4px;
154
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
155
+ font-size: 14px;
156
+ line-height: 1.4;
157
+ resize: vertical;
158
+ }
159
+
160
+ .soql-textarea:focus {
161
+ outline: none;
162
+ border-color: #0066cc;
163
+ box-shadow: 0 0 0 2px rgba(0, 102, 204, 0.2);
164
+ }
165
+
166
+ .query-actions {
167
+ text-align: right;
168
+ }
169
+
170
+ .btn {
171
+ padding: 10px 20px;
172
+ border: none;
173
+ border-radius: 4px;
174
+ font-size: 14px;
175
+ font-weight: 600;
176
+ cursor: pointer;
177
+ text-decoration: none;
178
+ display: inline-block;
179
+ transition: all 0.2s ease;
180
+ }
181
+
182
+ .btn-primary {
183
+ background: #0066cc;
184
+ color: white;
185
+ }
186
+
187
+ .btn-primary:hover {
188
+ background: #0056b3;
189
+ }
190
+
191
+ .error-section {
192
+ background: #f8d7da;
193
+ border: 1px solid #f5c6cb;
194
+ border-radius: 8px;
195
+ padding: 20px;
196
+ margin-bottom: 30px;
197
+ }
198
+
199
+ .error-section h4 {
200
+ color: #721c24;
201
+ margin-top: 0;
202
+ }
203
+
204
+ .error-message {
205
+ color: #721c24;
206
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
207
+ font-size: 14px;
208
+ background: rgba(114, 28, 36, 0.1);
209
+ padding: 10px;
210
+ border-radius: 4px;
211
+ }
212
+
213
+ .results-section {
214
+ background: white;
215
+ border: 1px solid #dee2e6;
216
+ border-radius: 8px;
217
+ padding: 25px;
218
+ }
219
+
220
+ .results-section h4 {
221
+ margin-top: 0;
222
+ color: #495057;
223
+ }
224
+
225
+ .results-meta {
226
+ background: #f8f9fa;
227
+ padding: 15px;
228
+ border-radius: 4px;
229
+ margin-bottom: 20px;
230
+ }
231
+
232
+ .results-meta p {
233
+ margin: 0;
234
+ font-size: 14px;
235
+ line-height: 1.6;
236
+ }
237
+
238
+ .results-table {
239
+ overflow-x: auto;
240
+ }
241
+
242
+ .results-table table {
243
+ width: 100%;
244
+ border-collapse: collapse;
245
+ font-size: 14px;
246
+ }
247
+
248
+ .results-table th,
249
+ .results-table td {
250
+ padding: 12px;
251
+ text-align: left;
252
+ border-bottom: 1px solid #dee2e6;
253
+ }
254
+
255
+ .results-table th {
256
+ background: #f8f9fa;
257
+ font-weight: 600;
258
+ color: #495057;
259
+ }
260
+
261
+ .results-table tr:hover {
262
+ background: #f8f9fa;
263
+ }
264
+
265
+ .no-selection {
266
+ text-align: center;
267
+ padding: 40px;
268
+ color: #6c757d;
269
+ }
270
+
271
+ .no-selection code {
272
+ background: #f8f9fa;
273
+ padding: 2px 6px;
274
+ border-radius: 3px;
275
+ font-size: 13px;
276
+ }
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class ApplicationController < ActionController::Base
5
+ protect_from_forgery with: :exception
6
+
7
+ before_action :authenticate_user!
8
+
9
+ private
10
+
11
+ def authenticate_user!
12
+ return if send(current_user_model).present?
13
+
14
+ respond_to do |format|
15
+ format.html { redirect_to main_app.root_path, alert: "You must be logged in to access the SOQL Dashboard." }
16
+ format.json { render json: { error: "Authentication required" }, status: :unauthorized }
17
+ end
18
+ end
19
+
20
+ def current_user_model
21
+ return nil unless SoqlDashboard.configuration&.user_method
22
+
23
+ SoqlDashboard.configuration.user_method
24
+ end
25
+
26
+ def salesforce_integration_model
27
+ return nil unless SoqlDashboard.configuration&.salesforce_integration_model
28
+
29
+ SoqlDashboard.configuration.salesforce_integration_model.constantize
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class ReportsController < ApplicationController
5
+ before_action :set_integration, only: %i[index execute_query]
6
+
7
+ def index
8
+ @integrations = available_integrations
9
+ @selected_integration = @integration
10
+ @query_result = nil
11
+ end
12
+
13
+ def execute_query
14
+ return redirect_to root_path unless @integration
15
+
16
+ @integrations = available_integrations
17
+ @selected_integration = @integration
18
+ @soql_query = params[:soql_query]
19
+
20
+ if @soql_query.present?
21
+ begin
22
+ @query_result = execute_soql_query(@soql_query)
23
+ rescue StandardError => e
24
+ @error = e.message
25
+ end
26
+ end
27
+
28
+ render :index
29
+ end
30
+
31
+ private
32
+
33
+ def set_integration
34
+ return unless params[:integration_id].present? && salesforce_integration_model
35
+
36
+ @integration = salesforce_integration_model.find_by(id: params[:integration_id])
37
+ end
38
+
39
+ def available_integrations
40
+ return [] unless salesforce_integration_model
41
+
42
+ salesforce_integration_model.all.map do |integration|
43
+ config = integration.send(config_method_name)
44
+ {
45
+ id: integration.id,
46
+ name: config["name"] || "Integration ##{integration.id}",
47
+ config:,
48
+ }
49
+ end
50
+ end
51
+
52
+ def config_method_name
53
+ "#{SoqlDashboard::Engine.engine_name}_config"
54
+ end
55
+
56
+ def execute_soql_query(query)
57
+ return { error: "No integration selected" } unless @integration
58
+
59
+ executor = SoqlDashboard::SoqlExecutor.new(@integration)
60
+ result = executor.execute(query)
61
+
62
+ if result[:error]
63
+ { error: result[:error] }
64
+ else
65
+ {
66
+ query:,
67
+ results: result[:records] || [],
68
+ total_size: result[:totalSize] || 0,
69
+ done: result[:done] || true,
70
+ }
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ module ApplicationHelper
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class ApplicationJob < ActiveJob::Base
5
+ end
6
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "timeout"
7
+
8
+ module SoqlDashboard
9
+ class SalesforceApiClient
10
+ def initialize(config)
11
+ @config = config
12
+ end
13
+
14
+ def execute_query(query)
15
+ uri = build_query_uri(query)
16
+ http = setup_http_connection(uri)
17
+ request = create_request(uri)
18
+ response = http.request(request)
19
+
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
+ rescue StandardError => e
28
+ { error: "Unexpected error: #{e.message}" }
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :config
34
+
35
+ def build_query_uri(query)
36
+ escaped_query = URI.encode_www_form_component(query)
37
+ URI("#{config['instance_url']}/services/data/v58.0/query/?q=#{escaped_query}")
38
+ end
39
+
40
+ def setup_http_connection(uri)
41
+ http = Net::HTTP.new(uri.host, uri.port)
42
+ http.use_ssl = true
43
+ http.read_timeout = 30
44
+ http
45
+ end
46
+
47
+ def create_request(uri)
48
+ request = Net::HTTP::Get.new(uri)
49
+ request["Authorization"] = "Bearer #{config['oauth_token']}"
50
+ request["Accept"] = "application/json"
51
+ request
52
+ end
53
+
54
+ def parse_response(response)
55
+ case response.code.to_i
56
+ when 200
57
+ parse_success_response(response)
58
+ when 400
59
+ parse_bad_request_response(response)
60
+ when 401
61
+ { error: "Authentication failed. Please check your OAuth token." }
62
+ when 403
63
+ { error: "Access denied. Insufficient permissions." }
64
+ when 404
65
+ { error: "Salesforce API endpoint not found." }
66
+ else
67
+ { error: "Salesforce API error: #{response.code} - #{response.message}" }
68
+ end
69
+ end
70
+
71
+ def parse_success_response(response)
72
+ result = JSON.parse(response.body)
73
+ {
74
+ totalSize: result["totalSize"],
75
+ done: result["done"],
76
+ records: result["records"] || [],
77
+ }
78
+ end
79
+
80
+ def parse_bad_request_response(response)
81
+ error_data = JSON.parse(response.body)
82
+ error_message = error_data["message"] || "Bad Request"
83
+ { error: "SOQL Error: #{error_message}" }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class ApplicationMailer < ActionMailer::Base
5
+ default from: "TrustedIQ <no-reply@#{ENV.fetch('EMAIL_DOMAIN', 'local.trustediq.com')}>"
6
+ layout "mailer"
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class SoqlExecutor
5
+ def initialize(integration)
6
+ @integration = integration
7
+ @config = integration.send(config_method_name)
8
+ end
9
+
10
+ def execute(query)
11
+ validate_query(query)
12
+
13
+ api_client = SoqlDashboard::SalesforceApiClient.new(config)
14
+ api_client.execute_query(query)
15
+ rescue StandardError => e
16
+ { error: e.message }
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :integration, :config
22
+
23
+ def config_method_name
24
+ "#{SoqlDashboard::Engine.engine_name}_config"
25
+ end
26
+
27
+ def validate_query(query)
28
+ raise ArgumentError, "Query cannot be blank" if query.blank?
29
+ raise ArgumentError, "Query must be a SELECT statement" unless query.strip.downcase.start_with?("select")
30
+
31
+ forbidden_keywords = %w[delete update insert upsert merge]
32
+ query_lower = query.downcase
33
+
34
+ forbidden_keywords.each do |keyword|
35
+ raise ArgumentError, "#{keyword.upcase} statements are not allowed" if query_lower.include?(keyword)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Soql dashboard</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "soql_dashboard/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -0,0 +1,99 @@
1
+ <div class="soql-dashboard">
2
+ <h1>SOQL Dashboard</h1>
3
+
4
+ <div class="integration-selector">
5
+ <h3>Available Salesforce Integrations</h3>
6
+
7
+ <% if @integrations.any? %>
8
+ <div class="integrations-dropdown">
9
+ <%= form_with url: root_path, method: :get, local: true, id: "integration-selector-form" do |form| %>
10
+ <%= form.select :integration_id,
11
+ options_for_select(
12
+ @integrations.map { |integration| ["#{integration[:name]} (ID: #{integration[:id]})", integration[:id]] },
13
+ @selected_integration&.id
14
+ ),
15
+ { prompt: "Select a Salesforce Integration..." },
16
+ {
17
+ class: "integration-dropdown",
18
+ onchange: "this.form.submit();"
19
+ } %>
20
+ <% end %>
21
+ </div>
22
+ <% else %>
23
+ <p class="no-integrations">No Salesforce integrations found. Please configure your integrations first.</p>
24
+ <% end %>
25
+ </div>
26
+
27
+ <% if @selected_integration %>
28
+ <div class="query-section">
29
+ <h3>SOQL Query for <%= @integrations.find { |i| i[:id] == @selected_integration.id }[:name] %></h3>
30
+
31
+ <%= form_with url: execute_query_reports_path, method: :post do |form| %>
32
+ <%= hidden_field_tag :integration_id, @selected_integration.id %>
33
+
34
+ <div class="query-input">
35
+ <%= form.label :soql_query, "Enter your SOQL query:" %>
36
+ <%= form.text_area :soql_query,
37
+ value: @soql_query,
38
+ placeholder: "SELECT Id, Name FROM Opportunity LIMIT 10",
39
+ rows: 5,
40
+ class: "soql-textarea" %>
41
+ </div>
42
+
43
+ <div class="query-actions">
44
+ <%= form.submit "Execute Query", class: "btn btn-primary" %>
45
+ </div>
46
+ <% end %>
47
+ </div>
48
+
49
+ <% if @error %>
50
+ <div class="error-section">
51
+ <h4>Error</h4>
52
+ <div class="error-message"><%= @error %></div>
53
+ </div>
54
+ <% end %>
55
+
56
+ <% if @query_result %>
57
+ <div class="results-section">
58
+ <h4>Query Results</h4>
59
+
60
+ <div class="results-meta">
61
+ <p>
62
+ <strong>Query:</strong> <%= @query_result[:query] %><br>
63
+ <strong>Total Records:</strong> <%= @query_result[:total_size] %><br>
64
+ <strong>Status:</strong> <%= @query_result[:done] ? 'Complete' : 'Partial' %>
65
+ </p>
66
+ </div>
67
+
68
+ <% if @query_result[:results]&.any? %>
69
+ <div class="results-table">
70
+ <table>
71
+ <thead>
72
+ <tr>
73
+ <% @query_result[:results].first.keys.reject { |k| k == "attributes" }.each do |column| %>
74
+ <th><%= column %></th>
75
+ <% end %>
76
+ </tr>
77
+ </thead>
78
+ <tbody>
79
+ <% @query_result[:results].each do |record| %>
80
+ <tr>
81
+ <% record.reject { |k, v| k == "attributes" }.values.each do |value| %>
82
+ <td><%= value %></td>
83
+ <% end %>
84
+ </tr>
85
+ <% end %>
86
+ </tbody>
87
+ </table>
88
+ </div>
89
+ <% else %>
90
+ <p>No results found.</p>
91
+ <% end %>
92
+ </div>
93
+ <% end %>
94
+ <% else %>
95
+ <div class="no-selection">
96
+ <p>Please select a Salesforce integration above to start querying, or add <code>?integration_id=X</code> to the URL.</p>
97
+ </div>
98
+ <% end %>
99
+ </div>
data/config/routes.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ SoqlDashboard::Engine.routes.draw do
4
+ root "reports#index"
5
+
6
+ resources :reports, only: [:index] do
7
+ collection do
8
+ post :execute_query
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ SoqlDashboard.configure do |config|
4
+ # The model class that represents Salesforce integrations
5
+ # Example: config.salesforce_integration_model = 'Integrations::SalesforceIntegration'
6
+ config.salesforce_integration_model = "CHANGE_ME"
7
+
8
+ # The user class for authentication
9
+ # Example: config.user_class = 'AdminUser'
10
+ config.user_class = "CHANGE_ME"
11
+
12
+ # The method to get the current user (should be available in controllers)
13
+ # Example: config.user_method = :current_admin_user
14
+ config.user_method = :CHANGE_ME
15
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace SoqlDashboard
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SoqlDashboard
4
+ VERSION = "0.1.0-test.2"
5
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "soql_dashboard/version"
4
+ require "soql_dashboard/engine"
5
+
6
+ module SoqlDashboard
7
+ class << self
8
+ attr_accessor :configuration
9
+ end
10
+
11
+ def self.configure
12
+ self.configuration ||= Configuration.new
13
+ yield(configuration) if block_given?
14
+ end
15
+
16
+ class Configuration
17
+ attr_accessor :salesforce_integration_model, :user_class, :user_method
18
+
19
+ def initialize
20
+ @salesforce_integration_model = nil
21
+ @user_class = nil
22
+ @user_method = :current_user
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :soql_dashboard do
5
+ # # Task goes here
6
+ # end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: soql_dashboard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.test.2
5
+ platform: ruby
6
+ authors:
7
+ - Fred Moura
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: pg
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 7.2.2.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 7.2.2.2
41
+ description: A Rails engine providing a dashboard interface for querying Salesforce
42
+ using SOQL
43
+ email:
44
+ - fred@trustediq.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - MIT-LICENSE
50
+ - README.md
51
+ - Rakefile
52
+ - app/assets/config/soql_dashboard_manifest.js
53
+ - app/assets/stylesheets/soql_dashboard/application.css
54
+ - app/controllers/soql_dashboard/application_controller.rb
55
+ - app/controllers/soql_dashboard/reports_controller.rb
56
+ - app/helpers/soql_dashboard/application_helper.rb
57
+ - app/jobs/soql_dashboard/application_job.rb
58
+ - app/lib/soql_dashboard/salesforce_api_client.rb
59
+ - app/mailers/soql_dashboard/application_mailer.rb
60
+ - app/models/soql_dashboard/application_record.rb
61
+ - app/services/soql_dashboard/soql_executor.rb
62
+ - app/views/layouts/soql_dashboard/application.html.erb
63
+ - app/views/soql_dashboard/reports/index.html.erb
64
+ - config/routes.rb
65
+ - lib/generators/soql_dashboard/install/templates/soql_dashboard.rb
66
+ - lib/soql_dashboard.rb
67
+ - lib/soql_dashboard/engine.rb
68
+ - lib/soql_dashboard/version.rb
69
+ - lib/tasks/soql_dashboard_tasks.rake
70
+ homepage: https://github.com/TrustedIQ/trustediq
71
+ licenses:
72
+ - MIT
73
+ metadata:
74
+ homepage_uri: https://github.com/TrustedIQ/trustediq
75
+ source_code_uri: https://github.com/TrustedIQ/trustediq
76
+ changelog_uri: https://github.com/TrustedIQ/trustediq/blob/develop/CHANGELOG.md
77
+ rubygems_mfa_required: 'true'
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: 3.2.7
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">"
90
+ - !ruby/object:Gem::Version
91
+ version: 1.3.1
92
+ requirements: []
93
+ rubygems_version: 3.4.19
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: SOQL Dashboard for TrustedIQ Salesforce integrations
97
+ test_files: []