Skip to content

Commit

Permalink
Retreive salesforce data (#130)
Browse files Browse the repository at this point in the history
* add salesforce library

* add soql field
  • Loading branch information
vswamidass-sfdc authored Feb 20, 2025
1 parent 6661111 commit 61371de
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 7 deletions.
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,7 @@ gem 'reverse_markdown'
gem 'pager_duty-connection'

gem 'slack-ruby-client'

gem 'savon'

gem 'restforce'
41 changes: 41 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ GEM
acts_as_votable (0.14.0)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
akami (1.3.3)
base64
gyoku (>= 0.4.0)
nokogiri
ast (2.4.2)
base64 (0.2.0)
bcrypt (3.1.20)
Expand Down Expand Up @@ -139,6 +143,8 @@ GEM
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-mashify (0.1.1)
faraday (~> 2.0)
hashie
Expand All @@ -150,11 +156,19 @@ GEM
ostruct
globalid (1.2.1)
activesupport (>= 6.1)
gyoku (1.4.0)
builder (>= 2.1.2)
rexml (~> 3.0)
hashie (5.0.0)
httparty (0.22.0)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
httpi (4.0.4)
base64
mutex_m
nkf
rack (>= 2.0, < 4)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
importmap-rails (2.0.1)
Expand All @@ -169,6 +183,8 @@ GEM
actionview (>= 5.0.0)
activesupport (>= 5.0.0)
json (2.7.2)
jwt (2.10.1)
base64
kaminari (1.2.2)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2)
Expand Down Expand Up @@ -214,12 +230,15 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nkf (0.2.0)
nokogiri (1.18.3-arm64-darwin)
racc (~> 1.4)
nokogiri (1.18.3-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.18.3-x86_64-linux-gnu)
racc (~> 1.4)
nori (2.7.1)
bigdecimal
optparse (0.5.0)
ostruct (0.6.1)
pager_duty-connection (3.0.0)
Expand Down Expand Up @@ -293,6 +312,13 @@ GEM
regexp_parser (2.9.2)
reline (0.5.8)
io-console (~> 0.5)
restforce (8.0.0)
faraday (>= 1.1.0, < 3.0.0)
faraday-follow_redirects (<= 0.3.0, < 1.0.0)
faraday-multipart (>= 1.0.0, < 2.0.0)
faraday-net_http (< 4.0.0)
hashie (>= 1.2.0, < 6.0)
jwt (>= 1.5.6)
reverse_markdown (2.1.1)
nokogiri
rexml (3.3.9)
Expand Down Expand Up @@ -331,6 +357,15 @@ GEM
nokogiri (>= 1.13.10)
rexml
rubyzip (2.3.2)
savon (2.15.1)
akami (~> 1.2)
builder (>= 2.1.2)
gyoku (~> 1.2)
httpi (>= 4, < 5)
mail (~> 2.5)
nokogiri (>= 1.8.1)
nori (~> 2.4)
wasabi (>= 3.7, < 6)
securerandom (0.4.1)
selenium-webdriver (4.10.0)
rexml (~> 3.2, >= 3.2.5)
Expand Down Expand Up @@ -373,6 +408,10 @@ GEM
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
uri (1.0.2)
wasabi (5.1.0)
addressable
faraday (>= 1.9, < 3)
nokogiri (>= 1.13.9)
web-console (4.2.1)
actionview (>= 6.0.0)
activemodel (>= 6.0.0)
Expand Down Expand Up @@ -429,10 +468,12 @@ DEPENDENCIES
rails-controller-testing
redcarpet
redis (~> 4.0)
restforce
reverse_markdown
rspec-rails (~> 6.1)
rubocop
ruby-saml
savon
selenium-webdriver
shoulda-matchers (~> 4.0)
slack-ruby-client
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/base_assistants_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,6 @@ def set_assistant
# Only allow a list of trusted parameters through.
def assistant_params
params.require(:assistant).permit(:libraries, :library_search_text, :name, :input, :output, :context, :instructions, :description, :status, :quip_url, :confluence_spaces, :user_id,
:slack_channel_name, :approval_keywords, :create_doc_on_approval, :disable_nonbot_chat, :library_id)
:slack_channel_name, :approval_keywords, :create_doc_on_approval, :disable_nonbot_chat, :library_id, :soql)
end
end
9 changes: 9 additions & 0 deletions app/jobs/generate_message_response_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,15 @@ def perform(_message_id)
prompt += '</CONFLUENCE_DOCUMENTS>'
end

if assistant.soql.present?
prompt += "<SOQL>\n\n"
salesforce_client = Salesforce::Client.new

salesforce_results = salesforce_client.query(assistant.soql)
prompt += JSON.pretty_generate(salesforce_results.map(&:to_h))
prompt += '</SOQL>'
end

prompt += "\n\n<RECENT_SLACK_MESSAGES>"

slack_service = SlackService.new
Expand Down
9 changes: 4 additions & 5 deletions app/views/assistants/_assistant.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
<strong class="block text-lg font-semibold text-gray-800 mb-2">Description</strong>
<textarea class="w-full p-3 border border-gray-300 rounded-lg bg-gray-100 text-gray-700" rows="4" disabled><%= assistant.description %></textarea>
</div>

<!-- Data Sources -->
<div class="my-5">
<strong class="block text-lg font-semibold text-gray-800 mb-2">Quip URL</strong>
Expand All @@ -30,7 +29,10 @@
<strong class="block text-lg font-semibold text-gray-800 mb-2">Libraries</strong>
<p class="text-gray-700"><%= assistant.libraries %></p>
</div>

<div class="my-5">
<strong class="block text-lg font-semibold text-gray-800 mb-2">Soql</strong>
<p class="text-gray-700"><%= assistant.soql %></p>
</div>
<!-- Prompts & Settings -->
<div class="my-5">
<strong class="block text-lg font-semibold text-gray-800 mb-2">Input</strong>
Expand All @@ -48,7 +50,6 @@
<strong class="block text-lg font-semibold text-gray-800 mb-2">Context</strong>
<textarea class="w-full p-3 border border-gray-300 rounded-lg bg-gray-100 text-gray-700" rows="4" disabled><%= assistant.context %></textarea>
</div>

<!-- Slack Configuration -->
<div class="my-5">
<strong class="block text-lg font-semibold text-gray-800 mb-2">Slack Channel</strong>
Expand All @@ -58,7 +59,6 @@
<strong class="block text-lg font-semibold text-gray-800 mb-2">Disable Non-Bot Chat</strong>
<p class="text-gray-700"><%= assistant.disable_nonbot_chat ? 'Yes' : 'No' %></p>
</div>

<!-- Document Creation -->
<div class="my-5">
<strong class="block text-lg font-semibold text-gray-800 mb-2">Create Document on Approval</strong>
Expand All @@ -72,7 +72,6 @@
<strong class="block text-lg font-semibold text-gray-800 mb-2">Library</strong>
<p class="text-gray-700"><%= assistant.library.name if assistant.library_id %></p>
</div>

<!-- JSON Representation -->
<div class="my-5">
<strong class="block text-lg font-semibold text-gray-800 mb-2">JSON</strong>
Expand Down
4 changes: 4 additions & 0 deletions app/views/assistants/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
<%= form.label "Library Search Text" %>
<%= form.text_field :library_search_text, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
</div>
<div class="my-5">
<%= form.label "Salesforce SOQL" %>
<%= form.text_area :soql, class: "block shadow rounded-md border border-gray-400 outline-none px-3 py-2 mt-2 w-full" %>
</div>
<% end %>
<%= render "shared/form_section", title: "Prompts and Settings" do %>
<div class="my-5">
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20250219233304_add_soql_to_assistant.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddSoqlToAssistant < ActiveRecord::Migration[7.1]
def change
add_column :assistants, :soql, :text
end
end
3 changes: 2 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

90 changes: 90 additions & 0 deletions lib/salesforce/client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require 'savon'
require 'restforce'
require 'logger'

module Salesforce
class Client
SF_LOGIN_URL = ENV.fetch('SF_LOGIN_URL', nil)

attr_reader :session_id, :instance_url, :restforce_client

def initialize(username: ENV.fetch('SF_USERNAME', nil), password: ENV.fetch('SF_PASSWORD', nil))
@username = username
@password = password

raise 'Salesforce credentials missing' if @username.nil? || @password.nil?

setup_logger
authenticate
end

private

def setup_logger
@logger = Logger.new(STDOUT)
@logger.level = Logger::ERROR
end

def authenticate
savon_client = Savon.client(
endpoint: SF_LOGIN_URL + '/services/Soap/u/63.0',
namespace: 'urn:partner.soap.sforce.com',
ssl_verify_mode: :none
)

response = savon_client.call(:login, xml: soap_request_body)

login_result = response.body[:login_response][:result]
@session_id = login_result[:session_id]
@instance_url = extract_instance_url(login_result[:server_url])

@logger.info '🎉 Salesforce login successful!'
@logger.info "Instance URL: #{@instance_url}"

initialize_restforce
end

def soap_request_body
<<~XML
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<login xmlns="urn:partner.soap.sforce.com">
<username>#{@username}</username>
<password>#{@password}</password>
</login>
</soapenv:Body>
</soapenv:Envelope>
XML
end

def extract_instance_url(server_url)
server_url.match(%r{https://([^/]+)})[0]
end

def initialize_restforce
@restforce_client = Restforce.new(
instance_url: @instance_url,
oauth_token: @session_id,
authentication_middleware: Restforce::Middleware::Authentication::Token
)
end

public

def fetch_user_info
@restforce_client.get('/services/data/v63.0/chatter/users/me').body
rescue Restforce::UnauthorizedError => e
@logger.error "Salesforce authentication error: #{e.message}"
nil
end

def query(soql)
@restforce_client.query(soql)
rescue StandardError => e
@logger.error "Salesforce query error: #{e.message}"
nil
end
end
end

0 comments on commit 61371de

Please sign in to comment.