Implementing notification/alert popup on job completion in Ruby on Rails - javascript

I am implementing background processing jobs in Rails using 'Sidekiq' gem which are run when a user clicks on a button. Since the jobs are run asynchronously, rails replies back instantly.
I want to add in a functionality or a callback to trigger a JavaScript popup to display that the job processing has finished.
Controller Snippet:
def exec_job
JobWorker.perform_async(#job_id,#user_id)
respond_to do |wants|
wants.html { }
wants.js { render 'summary.js.haml' }
end
end
Edit 1:
I am storing the 'user_id' to keep a track of the user who triggered the job. So that I can relate the popup to this user.
Edit 2:
The 'perform' method of Sidekiq does some database manipulation(Update mostly) and log creation, which takes time.
Currently, the user gets to know about the status when he refreshes the page later.
Edit 3(Solution Attempt 1):
I tried implementing 'Faye Push Notification' by using subscribe after successful login of user(with channel as user_id).
On the server side, when the job completes execution, I created another client to publish a message to the same channel (Using faye reference documents).
It is working fine on my desktop, i.e., I can see an alert popup prompting that the job has completed. But when I try testing using another machine on my local network, the alert is not prompted.
Client Side Script:
(function() {
var faye = new Faye.Client('http://Server_IP:9292/faye');
var public_subscription = faye.subscribe("/users/#{current_user.id}", function(data) {
alert(data);
});
});
Server Side Code:
EM.run {
client = Faye::Client.new('http://localhost:9292/faye')
publication = client.publish("/users/#{user.id}", 'Execution completed!')
publication.callback do
logger.info("Message sent to channel '/users/#{user.id}'")
end
publication.errback do |error|
logger.info('There was a problem: ' + error.message)
end
}

Rails 4 introduced the ActionController::Live module. This allows for pushing SSEs to the browser. If you want to implement this based on a database update you will need to look into configuring Postgresql to LISTEN and NOTIFY.
class MyController < ActionController::Base
include ActionController::Live
def stream
response.headers['Content-Type'] = 'text/event-stream'
100.times {
response.stream.write "hello world\n"
sleep 1
}
ensure
response.stream.close
end
end
Here is a good article on it: http://ngauthier.com/2013/02/rails-4-sse-notify-listen.html

Thanks fmendez.
I looked at the suggestions given by other users and finally, I have implemented a 'Faye' based push notification by:
Subscribing the user on successful login to a channel created using 'user_id'
Replying to this channel from server side after job completion (By fetching user_id)
For better understanding(so that it may be helpful for others), check the edit to the question.

Related

Better way to schedule cron jobs based on job orders from php script

So I wrote simple video creator script in NodeJS.
It's running on scheduled cron job.
I have a panel written in PHP, user enter details and clicks "Submit new Video Job" Button.
This new job is saving to DB with details, jobId and status="waiting" data.
PHP API is responsible for returning 1 status at a time, checks status="waiting" limits query to 1 then returns data with jobID when asked
Video Creation Script requests every x seconds to that API asks for new job is available.
It has 5 tasks.
available=true.
Check if new job order available (With GET Request in every 20 seconds), if has new job;
available=false
Get details (name, picture url, etc.)
Create video with details.
Upload Video to FTP
Post data to API to update details. And Mark that job as "done"
available=true;
These tasks are async so everytask has to be wait previous task to be done.
Right now, get or post requesting api if new job available in every 20 seconds (Time doesnt mattter) seems bad way to me.
So any way / package / system to accomplish this behavior?
Code Example:
const cron = require('node-cron');
let available=true;
var scheduler = cron.schedule(
'*/20 * * * * *',
() => {
if (available) {
makevideo();
}
},
{
scheduled: false,
timezone: 'Europe/Istanbul',
}
);
let makevideo = async () => {
available = false;
let {data} = await axios.get(
'https://api/checkJob'
);
if (data == 0) {
console.log('No Job');
available = true;
} else {
let jobid = data.id;
await createvideo();
await sendToFTP();
await axios.post('https://api/saveJob', {
id: jobid,
videoPath: 'somevideopath',
});
available = true;
}
};
scheduler.start();
RabbitMQ is also a good queueing system.
Why ?
It's really well documented (examples for many languages including javascript & php).
Tutorials are simple while they're exposing real use cases.
It has a REST API.
It ships with a monitoring UI.
How to use it to solve your problem ?
On the job producer side : send messages (jobs) to a queue by following tutorial 1
To consume jobs with your nodejs process : see RabbitMQ's tutorial 2
Other suggestions :
Use a prefetch value of 1 and publisher confirms so you can ensure that an instance of consumer will not receive messages while there's a job running.
Roadmap for a quick prototype : tutorial 1... then tutorial 2 x). After sending and receiving messages you can explore the options you can set on queues and messages
Nodejs package : http://www.squaremobius.net/amqp.node/
PHP package : https://github.com/php-amqplib/php-amqplib
While it is possible to use the database as a queue, it is commonly known as an anti-pattern (next to using the database for logging), and as you are looking for:
So any way / package / system to accomplish this behavior?
I use the free-form of your question thanks to the placed bounty to suggest: Beanstalk.
Beanstalk is a simple, fast work queue.
Its interface is generic, but was originally designed for reducing the latency of page views in high-volume web applications by running time-consuming tasks asynchronously.
It has client libraries in the languages you mention in your question (and many more), is easy to develop with and to run in production.
What you are doing in a very standard system design paradigm, done with Apache Kafka or any queue based implementation(ex, RabbitMQ). You can check out about Kafka/rabbitmq but basically Not going into details:
There is a central Queue.
When user submits a job the job gets added to the Queue.
The video processor runs indefinitely subscribing to the queue.
You can go ahead and look up : https://www.gentlydownthe.stream/ and you will recognize the similarities on what you are doing.
Here you don't need to poll yourself, you need to subscribe to an event and the other things will be managed by the respective queues.

How to allow JavaScript to receive a message sent to a specific user (or self) using Signal R?

I previously asked this question but it was closed for duplication owing to this thread (SignalR - Sending a message to a specific user using (IUserIdProvider) *NEW 2.0.0*) - but this doesn't show the JavaScript as made clear in my title.
I have a WebForm application in ASP.Net that uses SignalR to push live data to the user logged in. The setup works perfectly, but realised I am broadcasting messages to all clients, which whilst it doesn't cause the wrong data to displayed to the logged in user, does cause the JavaScript function to get called for all users when just one has a data push.
I have amended the Hub code to broadcast to a specific user (User) and provided the User ID, and I have also tried Client with a Connection ID. Both fire off fine in the codebehind, but the javascript will not update the front end.
I believe it's because the JavaScript has not been modified to listen for a message sent to the user, but I'm not sure how I need to adapt the code to allow the message to be received.
The 2 tried lines in Hub here:
context.Clients.User(Me.Context.User.Identity.GetUserId()).ReceiveNotification(notifCount)
context.Clients.Client(Me.Context.ConnectionId).ReceiveNotification(notifCount)
JavaScript/jQuery function for the SignalR message here:
$(function () {
var nf = $.connection.notificationHub;
nf.client.receiveNotification = function (notifCount) {
// Update data
}
$.connection.hub.start().done(function () {
nf.server.sendNotifications();
}).fail(function (e) {
alert(e);
});
//$.connection.hub.start();
});
For calling back to the client (or self) you should use:
Clients.Caller.addContosoChatMessageToPage(name, message);
And for calling users you should use:
Clients.Client(Context.ConnectionId).addContosoChatMessageToPage(name, message);
Reference - docs

Restrict number of users in a session in vline

Can I restrict the number of users in a session? Is there any option in vline.session? Please guide if this can be done by writing custom javascript.
EDIT:
Referring to https://vline.com/developer/docs/vline.js/vline.MediaSession#examples, a two party call controller is explained. I want to ask is there any way to restrict number of users in a session? There is no such option present in session's docs. Is it supported as a part of the API?
If this can be done using custom javascript, how?
As a part of my effort, I have tried to implement vline-django examples, but could not find a section in documentation that addresses this issue.
EDIT 2: The code that is working for me.
var vlineClient = (function(){
var client, session,
authToken = {{ user|vline_auth_token|safe }},
serviceId = {% vline_service_id %},
profile = {{ user|vline_user_profile|safe }};
// Create vLine client
window.vlineClient = client = vline.Client.create({"serviceId": serviceId, "ui": true});
// Add login event handler
client.on('login', onLogin);
// Do login
client.login(serviceId, profile, authToken);
function onLogin(event) {
session = event.target;
// Find and init call buttons
var callButtons = document.getElementsByClassName('callbutton');
for (var i=0; i < callButtons.length; ++i) {
initCallButton(callButtons[i]);
}
}
// add event handlers for call button
function initCallButton(button) {
var userId = button.getAttribute('data-userid');
// fetch person object associated with username
session.getPerson(userId).done(function(person) {
// update button state with presence
function onPresenceChange() {
button.setAttribute('data-presence', person.getPresenceState());
}
// set current presence
onPresenceChange();
// handle presence changes
person.on('change:presenceState', onPresenceChange);
// start a call when button is clicked
button.addEventListener('click', function() {
person.startMedia();
});
});
}
return client;
})();
How do I move ahead?
Reference: https://vline.com/developer/docs/vline.js/
if i understand correctly the OP is trying to make a multi-user chat room - this is also what i wanted to do with vline and because i wanted a/v chat as well the number of participants should obviously be capped - it appears that the term 'session' is causing the confusion here so i will refrain from using it
i worked around this by creating a fixed number of users in a db and handling authentication
myself before actually associating a visitor with one of the prepared users - so some javascript logs in each visitor as one of those existing 'anonymous' users and sets only a logged_in? flag in the db so that the next visitor will log in as the next vacant user slot and when all slots are occupied the visitor gets a "chat room full - try again later" response
probably not the most elegant solution - for example the visitor chosen usernames are stored client-side and must be re-assigned to one of the user-definable vline session vars so it can be passed along with each message and the logged_in? db flag needs to be reset when the user exits
note that this was almost a year ago so im a bit foggy on exactly what i did but my app (rails) in up on github if youre interested to fork it - also i should add that although this sort of thing wasnt strictly supported by the vline API at the time there were at least some hints that some analogous feature was being prepared for so there may be some API support for this now - i did notice since then that they have released a "chat room demo" app on github and i would expect that their implementation is more concise than mine so you may want to look at that first - my app tho does have a mostly complete UI with gravatars and collaboration is welcomed

Paypal Embedded Flow not using returnUrl or cancelUrl

I am using Paypals Adaptive Payments and Embedded flow feature to provide checkout via a minibrowser. Everything seems to be working correctly in the sandbox environment except that when the payment is completed successfully, the user is never redirected to my returnUrl set in the PAY API request. Same goes for my cancelUrl.
After the payment is complete, the user is shown an order overview in the minibrowser and a button labelled "close". If a user clicks this button, the minibrowser is closed.
If a user clicks cancel at any time, the minibrowser is closed.
There doesn't seem to be a way to have my page aware of the change besides setting up some polling or something which doesn't make sense, my returnUrl and cancelUrl should be used somewhere, right?
this is my code to get the redirect url (using adaptive payments gem):
pay_request = PaypalAdaptive::Request.new
data = {
'requestEnvelope' => {'errorLanguage' => 'en_US'},
'currencyCode' => 'USD',
'receiverList' =>
{ 'receiver' => [
{'email' => '...', 'amount'=> 10.00}
]},
'actionType' => 'PAY',
'returnUrl' => 'http://www.example.com/paid',
'cancelUrl' => 'http://www.example.com/cancelled',
'ipnNotificationUrl' => 'http://www.example.com/ipn'
}
pay_response = pay_request.pay(data)
redirect_to pay_response.approve_paypal_payment_url "mini"
And here is how I am setting up the paypal js:
var dg = new PAYPAL.apps.DGFlowMini({ trigger: "buyit", expType: "mini" });
It all seems pretty straight forward, not sure what I am missing.
Well - seems to be a bug on our side - just tried it myself and confirmed with our integration teams. :-(
Unfortunately the other short term fix I can think of other than what you've mentioned (checking for the existence of the popup window) is to call the PaymentDetails API from your server side to check the status of the Payment. I've opened the bug on our side but don't have an ETA.
Edit 10/18: Sorry I'm wrong. This is working - it's just that our developer guide is not providing all the required information. In case of the mini-browser flow, you would need to provide a 'callbackFunction' and also name your dgFlow variable as 'dgFlowMini'. (the latter is important - as apdg.js is expecting the 'dgFlowMini' variable to be defined) Here is the code that works:
var returnFromPayPal = function(){
alert("Returned from PayPal");
// Here you would need to pass on the payKey to your server side handle to call the PaymentDetails API to make sure Payment has been successful or not
// based on the payment status- redirect to your success or cancel/failed urls
}
var dgFlowMini = new PAYPAL.apps.DGFlowMini({trigger: 'em_authz_button', expType: 'mini', callbackFunction: 'returnFromPayPal'});
I have a working sample here: https://pp-ap-sample.appspot.com/adaptivesample?action=pay (make sure you select mini as the Experience Type)
We will get our docs updated and also cleanup apdg.js to remove the dependency on the JS variable name.
Looks like the PayPal experience for embedded flows has gotten worse. Now you'll receive an error message after invoking mini or lightbox that says "Payment can't be completed. This feature is currently unavailable."

Removing someone from a user hash on navigating away from a page / page close

I'm building a Node.js / Socket.io project.
I have a hash of Users based on their websocket id. When a user makes the following request i'd like to add them to a group of users viewing that page.
app.get('/board/:id', function(req, res){}
I'd like to keep something like
Browsing = {
username : id,
username : id,
...
}
However i'm unsure how to remove a user lets say if they navigate off the page. Is there some kind of function that gets called upon page leave?
Partial Solution:The following seems to do the trick on Chrome:
$(window).unload(function(){
var username = $('#username').text();
var pid = currentProject;
var data = {
username: username,
id : pid
}
socket.emit('leaving-page', data);
})
... Is there some kind of function that gets called upon page
leave? ...
Yes, but it is not reliable.
The way the people keep track of who is online and who isn't, is usually like this:
Add the time when the user last refreshed/visited a page
set a limit to you consider them offline
You could intercept the event which corresponds to leaving a page. There are several ways to do it, have a look at the following links and let me know if any suits your needs and if I can answer any more explicit questions about them:
Intercept page exit event
Best way to detect when a user leaves a web page?
jquery unload
with the last link you could do something like this:
$(window).unload(function() {
//remove the user from your json object with delete json_object[key];
});
Hope this helps.
Since you're using Socket.io, the server will know when the user has left the page because the socket will be disconnected. Simply listen for the disconnect event.
io.sockets.on('connection', function (socket) {
...
socket.on('disconnect', function () {
// The user on `socket` has closed the page
});
});
Better yet, take advantage of namespaces and let Socket.io handle all of the connection/disconnection (it's doing it anyway -- no need to duplicate effort).
On the client,
socket = io.connect('http://example.com/pagename');
pagename need not point to a valid URL on your domain – it's just the namespace you'll use in the server code:
io.sockets.clients('pagename')
Gets you all of the clients currently connected to the pagename namespace.

Categories