Rails/Ruby Parse AWS SNS Topic Notification Data - ruby-on-rails

There is this rails app which I created to accept SNS topic notification data. In summery SNS notification is generated on completion of a elastic transcoder job. So the rails app simply accept the incoming post request from AWS SNS service and All I need to parse this data. What doesn't work for me is, I'm trying to retrieve the Message => jobId but I can't figure out how. Appreciate any help.
def sns_transcode_completed
amz_message_type = request.headers['x-amz-sns-message-type']
amz_sns_topic = request.headers['x-amz-sns-topic-arn']
if !amz_sns_topic.nil? &&
amz_sns_topic.to_s.downcase == 'arn:aws:sns:us-east-1:XXXXXXXXXX:MyApp_transcode_completed'
request_body = JSON.parse(request.body.read, {:symbolize_names => true})
notification = Hashie::Mash.new(request_body)
if amz_message_type.to_s.downcase == 'subscriptionconfirmation'
subscribe_url = request_body['SubscribeURL']
if !subscribe_url.to_s.empty? && !subscribe_url.nil?
subscribe_confirm = HTTParty.get subscribe_url
puts subscribe_confirm
puts subscribe_url
end
end
if amz_message_type.to_s.downcase == 'notification'
puts "--------------------------"
puts notification.inspect # See output 1
puts "--------------------------"
puts notification.MessageId # This works I can get the MessageId
puts "--------------------------"
puts notification.Message => # From here I need to get the jobId, but it comes as a String? See output 2
puts "--------------------------"
end
end
render :nothing => true, :status => 200, :content_type => 'text/html'
end
Output 1
#<Hashie::Mash Message="{\n \"state\" : \"COMPLETED\",\n \"version\" : \"2012-09-25\",\n \"jobId\" : \"1440122777052-XXXXXX\",\n \"pipelineId\" :
\"1432361831290-XXXXX\",\n \"input\" : {\n \"key\" : \"web-b796ab20-297c-0133-4ccf-378cf690e3b1.mp3\"\n },\n \"outputKeyPrefix\" :
\"hlsv4/246-21632840-29d7-0133-1d8e-XXXXXXXX/\",\n \"outputs\" : [ {\n \"id\" : \"1\",\n \"presetId\" : \"1351620000001-200071\",\n \"key\" : \"hls_64k\",\n
\"segmentDuration\" : 10.0,\n \"status\" : \"Complete\",\n \"duration\" : 60\n } ],\n \"playlists\" : [ {\n \"name\" : \"index\",\n \"format\" : \"HLSv4\",\n
\"outputKeys\" : [ \"hls_64k\" ],\n \"status\" : \"Complete\"\n } ]\n}" MessageId="2d26f6d0-1715-5edb-af39-b8809eff521f" Signature="XXXXXX" SignatureVersion="1"
SigningCertURL="https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11fXXXXXXX.pem" Subject="Amazon Elastic Transcoder has
finished transcoding job 1440122777052-XXXXXX." Timestamp="2015-08-21T02:06:34.523Z" TopicArn="arn:aws:sns:us-east-1:005550634774:MyApp_transcode_completed"
Type="Notification"
UnsubscribeURL="https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:005550634774:MyApp_transcode_completed:8c0e48fe-c34
9-4185- bb94-XXXXXXXXXX">
Output 2
{
"state": "COMPLETED",
"version": "2012-09-25",
"jobId": "1440122777052-XXXXX",
"pipelineId": "1432361831290-XXXXXX",
"input": {
"key": "web-b796ab20-297c-0133-4ccf-378cf690e3b1.mp3"
},
"outputKeyPrefix": "hlsv4/246-21632840-29d7-0133-1d8e-35ebbdecd855/",
"outputs": [
{
"id": "1",
"presetId": "1351620000001-200071",
"key": "hls_64k",
"segmentDuration": 10,
"status": "Complete",
"duration": 60
}
],
"playlists": [
{
"name": "index",
"format": "HLSv4",
"outputKeys": [
"hls_64k"
],
"status": "Complete"
}
]
}

I was having the same issue and I ended up ditching Hashie.(It's really not needed aside from being able to use dot notation in this case.) I did still have to double parse (due to the string returned when you dig out the message). Once I grabbed that, the object I wanted to get at was in an Array so I used the dig method to pull out the object and then access the data I needed.
def sns_endpoint
notification = JSON.parse(request.raw_post, {:symbolize_names => true})
case notification[:Type]
when "SubscriptionConfirmation"
confirm(notification[:TopicArn], notification[:Token])
when "Notification"
message_data = JSON.parse(notification[:Message])
thing_you_are_trying_to_get = message_data["Records"].dig(0)["s3"]...etc...(access the data you need here.)
else
Rails.logger.error "Unknown notification type #{notification[:Type]}"
end
render body: nil
end

I got this working parsing the notification.Message value again. I wish if there is a better way than this.
parsed_message = JSON.parse(notification.Message, {:symbolize_names => true})
puts parsed_message.inspect
puts "--------------------------"
puts parsed_message[:jobId]

Related

Send response to Dialogflow webhook

I'm trying to implement Dialogflow bot. I've set all intents, the response defined for the intent and webhook receiver controller. It works well but based on Fulfillment docs after Dialogflow request to my webhook service I need to send response to Dialogflow. How to do that?
Here is my webhook service:
class Api::V1::WebhooksController < ActionController::API
include ActionController::HttpAuthentication::Basic::ControllerMethods
include ActionController::HttpAuthentication::Token::ControllerMethods
http_basic_authenticate_with name: Rails.application.credentials.webhook_auth_name, password: Rails.application.credentials.webhook_auth_password
def create
action_name = params[:webhook][:queryResult][:intent][:displayName]
case action_name
when 'get-calendar'
render json: 'Show calendar', status: :ok
when 'get-room'
render json: 'Show available rooms', status: :ok
end
end
end
Which gives me that message inside Raw interaction log :
{
"queryText": "Calendar",
"parameters": {},
"fulfillmentText": "<happy>This is your calendar</happy>",
"fulfillmentMessages": [
{
"text": {
"text": [
"<happy>This is your calendar</happy>"
]
},
"lang": "en"
}
],
"intent": {
"id": "1234",
"displayName": "get-calendar",
"priority": 500000,
"webhookState": "WEBHOOK_STATE_ENABLED",
"messages": [
{
"text": {
"text": [
"<happy>This is your calendar</happy>"
]
},
"lang": "en"
}
]
},
"intentDetectionConfidence": 1234,
"diagnosticInfo": {
"webhook_latency_ms": 1234
},
"languageCode": "en",
"slotfillingMetadata": {
"allRequiredParamsPresent": true
},
"id": "1234",
"sessionId": "1234",
"timestamp": "2020-09-25T14:38:31.92Z",
"source": "agent",
"webhookStatus": {
"webhookEnabledForAgent": true,
"webhookStatus": {
"code": 3,
"message": "Webhook call failed. Error: Failed to parse webhook JSON response: Expect message object but got: \"Show\"."
}
},
"agentEnvironmentId": {
"agentId": "1324",
"cloudProjectId": "test-project"
}
}
I don't think this part is what I expected:
"message": "Webhook call failed. Error: Failed to parse webhook JSON response: Expect message object but got: \"Show\"."
DialogFlow calls your WebHook when processing an Intent, you need to ensure your code send back a JSON object which DialogFlow understands and can process. It looks like you are sending back just a text message (Show Calendar), or at least a JSON starting with `Show..'
You are using DialogFlow API v1 (this is deprecated, better move to V2) so your JSON should look like this. Normally there are SDK (Java, NodeJS, I guess Ruby too?) which provides the model and objects to work with, so you don't need to create the JSON manually but just use the API.

How to take keep parts of an array and form a new array?

I am building a Rails 5 app.
In this app I have connected to the Google Calendar API.
The connection works fine and I get a list of calendars back.
What I need to do is to get the Id and Summary of this JSON object that I get back from Google.
This is what I get
[{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
},
{
"kind": "calendar#calendarListEntry",
"etag": "\"1483552200690000\"",
"id": "xxx.com_asae#group.calendar.google.com",
"summary": "My office calendar",
"description": "For office meeting",
"location": "344 Common st",
"colorId": "8",
"backgroundColor": "#16a765",
"foregroundColor": "#000000",
"accessRole": "owner",
"defaultReminders": [],
"conferenceProperties": {
"allowedConferenceSolutionTypes": [
"hangoutsMeet"
]
}
}]
This is what I want to end up with
[{
"id": "xxx.com_asae#group.calendar.google.com",
"title": "My office calendar",
}]
The purpose of this is that I want to populate a selectbox using Selectize plugin
Another way to achieve removing of certain keys in your hash is by using Hash#reject method:
response = { your_json_response }
expected = [response[0].reject {|k| k != :id && k != :summary}]
The original response remains unchanged while a mutated copy of the original response is returned.
You can filter the desierd keys with the select method:
responde = {your_json_response}
expected = [response[0].select{|k,v| ['id','title'].include?(k)}]
response[0] retrieves the hash, and the select compares each key with the ones you want and returns a hash with only those key: value pairs.
EDIT: I missed that you don't have a "title" key on the original response, I would do this then:
response = {your_json_response}
h = response[0]
expected = [{'id' => h['id'], 'title' => h['summary']}]
EDIT 2: Sorry, the first example was not clear that there would be multiple hashes
expected = response.map{|h| {'id' => h['id'], 'title' => h['summary']}}
map iterates over each element of response and returns the result of the block applied for each iteration as an array, so the blocks is apllied to each h and it generates a new hash from it
I suggest this approach.
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }
It returns just the required pairs:
# => [{:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}, {:id=>"xxx.com_asae#group.calendar.google.com", :summary=>"My office calendar"}]
To remove duplicates, just do expected.uniq
If you need to change the key name :summary to :title do:
expected = expected.each { |h| h[:title] = h.delete(:summary) }
One liner
expected = response.each { |h| h.keep_if { |k, _| k == :id || k == :summary } }.each { |h| h[:title] = h.delete(:summary) }.uniq
Of course, maybe it is better to move .uniq as first method expected = response.uniq.each { .....

Docusign integration with rails 4

I am using the docusign_rest gem for DocuSign REST API, and following are my DocuSign configuration.
# config/initializers/docusign_rest.rb
require 'docusign_rest'
DocusignRest.configure do |config|
config.username = 'myemail#email.com'
config.password = 'MyPassword'
config.integrator_key = 'My-key'
config.account_id = 'account_id'
config.endpoint = 'https://www.docusign.net/restapi'
config.api_version = 'v1'
end
When I try to connect and get account_id, I get nil as a response.
client = DocusignRest::Client.new
puts client.get_account_id # Returns nil.
I am using rails-4.1.4 and ruby-2.2.2
What did I miss? Please suggest.
Not sure if you figured this out or not quite yet. Here is another solution that wasn't too difficult using httparty. If you're trying to create a document for a template for example, your request might look like so:
baseUrl = "https://demo.docusign.net/restapi/v2/accounts/acct_number/envelopes"
#lease = Lease.find(lease.id)
#unit = #lease.unit
#application = #lease.application
#manager = #lease.property_manager
#application.applicants.each do |renter|
req = HTTParty.post(baseUrl,
body: {
"emailSubject": "DocuSign API call - Request Signature - Boom",
"templateId": "id of your template",
"templateRoles": [{
"name": "#{renter.background.legal_name}",
"email": "#{renter.email}",
"recipientId": "1",
"roleName": "Lessee",
"tabs": {
"texttabs": [{
"tablabel": "Rent",
"value": "#{#lease.rent}"
},{
"tablabel": "Address",
"value": "987 apple lane"
}]
}
},{
"email": "#{#manager.email}",
"name": "#{#manager.name}",
"roleName": "Lessor",
"tabs": {
"texttabs": [{
"tablabel": "Any",
"value": "#{#lease.labels}"
},{
"tablabel": "Address",
"value": "987 hoser lane"
}]
}
}],
"status": "sent"
}.to_json,
headers: {
"Content-Type" => "application/json",
'Accept' => 'application/json',
'X-DocuSign-Authentication' => '{
"Username" : "place your",
"Password" : "credentials",
"IntegratorKey" : "here"
}'
}, :debug_output => $stdout )
debug output on the final line is to allow you to debug the api request, it can be removed at any time.
This was a bug in docusign_rest 0.1.1; that method always returned nil. That bug has been fixed and the latest gem version includes that fix.

Successful consumption of a Azure Machine Learning API?

Does anyone have good documentation of a successful implementation of the Azure ML studio API in a web app that's not ASP.net? I'd like to run on it with ruby on rails, but I guess I have to figure it out on my own.
It is simply a rest API call. Look at this...
data = {
"Inputs": {
"input1":
{
"ColumnNames": ["YearBuild", "City", "State", "HomeType", "TaxAssesmentYear", "LotSize", "HomeSize", "NumBedrooms"],
"Values": [ [ "0", "Anchorage", "AK ", "Apartment", "0", "0", "0", "0" ], [ "0", "Anchorage", "AK ", "Apartment", "0", "0", "0", "0" ], ]
}, },
"GlobalParameters": {
}
}
body = str.encode(json.dumps(data))
url = 'https://ussouthcentral.services.azureml.net/workspaces/45aeb4d8283d4be6ae211592f5366af5/services/07ffeeb6fcb84f16bc62cdcf67fd95b3/execute?api-version=2.0&details=true'
api_key = 'abc123' # Replace this with the API key for the web service
headers = {'Content-Type':'application/json', 'Authorization':('Bearer '+ api_key)}
req = urllib2.Request(url, body, headers)
Give a shot at trying with the postman app in chrome first. Setting your headers, just as above, your data goes in the post payload in the json format.
Here you'll find Ruby code (not python)
data = {
'Inputs' => {
'input1' => [
{
'weekday' => 1,
'hour' => 2,
'events' => 0
}
]
},
'GlobalParameters' => {}
}
body = data.to_json
url = 'https://asiasoutheast.services.azureml.net/subscriptions/[tour stuff...]execute?api-version=2.0&format=swagger'
api_key = '[your api key]'
headers = {'Content-Type': 'application/json', 'Authorization': ('Bearer '+ api_key)}
RestClient::Request.execute(method: :post, url: url, payload: body, headers: headers)

Parse JSON with an array in Rails

I have the following JSON string returned by a remote server:
{
"users": [
{
"user_id": 1,
"name": "Chris Rivers",
},
{
"user_id": 3,
"name": "Peter Curley",
}
]
}
I'd like to iterate the users.
ActiveSupport::JSON.decode(response.body)["users"].each do |user|
puts user["name"]
end
As far as I understand, the problem is: ruby doesn't recognize ActiveSupport::JSON.decode(response.body)["users"] as an array, and thus puts returns me only the first user.
How do I solve that problem?
What you have pasted is not valid JSON. The trailing comma after on each "name" is a problem
"name": "Chris Rivers",
You'll get a LoadError trying to decode this with ActiveSupport::JSON.decode
MultiJson::LoadError: 399: unexpected token at '{"user_id": 1,"name": "Chris Rivers",},{"user_id": 3,"name": "Peter Curley",}]}'
If we clean up the JSON, turning it into something ActiveSupport::JSON.decode can understand
"{\"users\": [{\"user_id\": 1,\"name\": \"Chris Rivers\"},{\"user_id\": 3,\"name\": \"Peter Curley\"}]}"
you'll see there is no issue iterating over each object in "users" (x below is the above JSON string)
[8] pry(main)> ActiveSupport::JSON.decode(x)["users"].map { |user| user["name"] }
=> ["Chris Rivers", "Peter Curley"]
Does your source data actually have the trailing commas after each user's name? I get a parse error for that, but your code works as you want it to if I remove them:
json = '{ "users": [ { "user_id": 1, "name": "Chris Rivers" }, { "user_id": 3, "name": "Peter Curley" } ]}'
ActiveSupport::JSON.decode(json)["users"].each do |user|
puts user["name"]
end
The problem isn't not recognizing the array, it's the trailing commas after the "name" elements.
Removing those allows JSON parsing to proceed normally:
pry(main)> ActiveSupport::JSON.decode(s)["users"]
=> [{"user_id" => 1, "name" => "Chris Rivers"},
{"user_id" => 3, "name" => "Peter Curley"}]

Resources