I need to do some processing on the background after the user has submitted a request. After the job is done, i want to show a message to user that your job is done.
So far i have this
lib/jobs/custom_job.rb
class CustomJob
def perform
sleep 1 # Here will do the background processing.
ActionView::Base.new('app/views', {}, ActionController::Base.new).render(file: 'teacher/courses/custom')
end
end
custom.js
$(document).ready(function() {
alert("Your job is done.");
});
controller
class Teacher::CoursesController < ApplicationController
def index
Delayed::Job.enqueue(CustomJob.new)
#courses = current_teacher.courses.all
end
end
Worker start a job and finished it with no error.
[Worker(host:Aragorn pid:3360)] Job CustomJob (id=16) COMPLETED after 1.0635
[Worker(host:Aragorn pid:3360)] 1 jobs processed at 0.9035 j/s, 0 failed
Thanks in advance.
You will need to have some sort of polling interface on the front end looking for a complete event on the object on the back end or use some sort of service like pusher.com.
We do this often and this is one of the better patterns we have found which will allow for the user to reload / leave the page and come back to the notice still available if it is still pending completion.
Possible example to check out
Related
We have an Angular application which was written for us and we're trying to figure out a specific problem (see below for my voluminous essay). Basically, it's a system where some digital input is supposed to control both the backlight and the page that's displayed. If the digital input is on, the backlight should also be on, and the app should work normally, allowing user interaction.
However, without that digital input signal, the application should give a blank page and turn off the backlight so that the user sees nothing and cannot interact with a real page in some blind fashion.
The backlight is controlled by a back end process, which also deliver information to the front end (both of these run on the same device) using Angular routing. But the decision as to what's displayed on the web page is totally under the control of the front end.
We're seeing a situation where, if the digital input is toggled off and on quickly, we sometimes get the backlight on but with a blank page and I'm wondering whether there's some sort of race condition in the delivery of event through the router.
In terms of code, we have an effect which monitors the digital input signal from the back end (not directly) that looks something like:
#Effect({ dispatch: false })
receiveDigitalInputEvent$ = this.actions.pipe(
ofType(AppActionTypes.RECEIVE_DIGITAL_INPUT),
map((action: AppActions.ReceiveDigitalInput) => action.payload),
withLatestFrom(this.store.pipe(select(getRouterState))),
tap(([data, routerState]) => {
const currUrl = routerState ? routerState.state.url : '';
const { isDigitalInputSet, someOtherStuff } = data;
this.forceNavigateIfNeeded(isDigitalInputSet, currUrl);
})
);
forceNavigateIfNeeded(isDigitalInputSet: boolean, currUrl) {
if (isDigitalInputSet && currUrl.endsWith('/blank')) {
this.router.navigateByUrl('/welcome');
else if (! isDigitalInputSet && ! currUrl.endsWith('/blank')) {
this.router.navigateByUrl('/blank');
}
}
What this basically does is, on digital input event arrival:
if the input is on and the screen is currently blank, go to the welcome screen; or
if the input is off and you're not currently on the blank screen, go to the blank screen.
Now, this works normally, it's just the quick transitions that seem to be causing a problem.
I don't have that much knowledge of the Angular internals but I assume the incoming events are being queued somehow for delivery via the routing mechanism (I believe the back end pushed through stuff via web sockets to the front end).
I've looked into the Angular source and noticed that the navigateByUrl uses promises to do its work so the code may return to get the next message queue item before the page is actually changed. I just wanted someone with more Angular nous to check my reasoning or let me know if what I'm suggesting is rubbish. As to what I'm suggesting:
The digital input goes off and a message (OFF) is queued from the back end to the front end. The back end also turns off the backlight.
The digital input goes on again quickly and a message (ON) is queued from the back end to the front end. The back end also reactivates the backlight.
The front end receives the OFF message and processes it. Because we've gotten an OFF message while not on the blank page, we call navigateByUrl('/blank') which starts up a promise.
Before that promise can be fulfilled (and the routerState.state.url changed from non-blank to blank), we start processing the ON message. At this stage, we have an ON message with a (stale) non-blank page, so forceNavigateIfNeeded() does nothing.
The in-progress navigateByUrl('/blank') promise completes, setting the page to blank.
Now we seem to have the situation described as originally, a blank page with the backlight on.
Is this even possible with Angular/Typescript? It seems to rely on the fact that messages coming in are queued and are acted upon (in a single threaded manner) as quickly as possible, while front-end navigation (and, more importantly, the updated record of your current page) may take some time.
Can someone advise as to whether this is possible and, if so, what would be a good way to fix it?
I assume it's a bad idea to wait around in forceNavigateIfNeeded() for the promise to complete, but I'm not sure how else to do it.
Although your problem is quite complex I can see a suspicious code which might be worth inspecting.
...
withLatestFrom(this.store.pipe(select(getRouterState))),
...
This might be the cause of your issue because it's taking what's there, what's latest. It doesn't request the update nor does it wait for it. We had similar issue where also milliseconds were in play and I came up with the workaround, where I replaced withLatestFrom with switchMap operator. I'm not sure if it works in your case but you can give it try.
map((action: AppActions.ReceiveDigitalInput) => action.payload),
switchMap(data => //data === action.payload
this.store.select(getRouterState).pipe(map(routerState => ({ data, routerState }))
),
tap(({data, routerState}) => { ... }
EDIT:
In our case we used a custom selector in the switchMap, which compares current and previous state and returns comparison result. Honestly I'm not 100% sure if that would also help you but it's a necessary piece of a puzzle in our case.
export const getFeshRouterState= pipe(
select(getRouterState),
startWith(null as RouterStateInterface),
pairwise(),
map(([prev, curr]) => /* do some magic here and return result */),
);
Sorry for initial confusion.
I have the below scenario:
I search for an element in search box. when the element appears (it may take some time), I click on the element which loads a new view. Then I try to click on some button in new view. since it takes some time to load the search result, the new view does not load. Any way I can avoid wait()?
This is what I have:
cy.get('[data-cy="search"][data-component="input_box"]').type('Find me{enter}')
cy.wait(200) // How to avoid this???
cy.get('[data-name="Find me"]').contains('Find me').click()// Should load a new view
cy.get('[data-cy = "filter"]').click() // failing here as new view is not loaded
Any suggestions will be highly appreciated.
Similar one:
describe('Tutorialspoint Test', function () {
// test case
it('Test Case1', function (){
// test step to launch a URL
cy.visit("https://www.tutorialspoint.com/videotutorials/index.php");
// enter test in the edit box
cy.get("#search-strings").type("Java");
// wait for some time
cy.wait(3000); // ---> how to avoid this?
// using jQuery selector to identify only visible elements
// assertion to validate the number of search results
cy.get('.clsHeadQuestion:visible'). should('have.length',19);
});
});
In similar situation you can use cy.route() which grab the network request and wait for it. In your case, the route will grab the n/w req and wait for the "Find me" request to get processed completely so that you can proceed with the next filter action.
Steps:
Open the chrome console, navigate to network tab, filter for XHR request. Now click on the ''Find me'' button in you page. You can see usually a GET request, for example if the request looks as follows, https://example.com.au/v1/api/listview?page=2
Please copy the url from /v1/api/... and add into the url value of cy.route() as below.
note: On click on Find me button, see its a GET or POST request, usually it will be GET request, check if the url has any encoded value, if it has encoded value you have to use decodeURIComponent() and the pass the url inside as follows >> url:decodeURIComponent('**/v1/api/listview?page=2**'),
beforeEach(()=>{
cy.server();
cy.route({
method: 'GET',
url:'**/v1/api/listview?page=2**',
delay: 2000
}).as('getLoadNewView');
})
it('Route waiting for loading the new view for filter action', () => {
cy.get('[data-cy="search"][data-component="input_box"]').type('Find me{enter}')
cy.get('[data-name="Find me"]').contains('Find me').click()// Should load a new view
cy.wait('#getLoadNewView');
cy.get('[data-cy = "filter"]').click();
})
Could you try adding an extra timeout to the get? Something like:
cy.get('[data-name="Find me"]', { timeout: 1000 }).contains('Find me').click()
It's not a perfect solution, but sometimes adding a timeout like that can help.
I would guess that there is some network activity going on on the background after you finish typing in the search bar. I usually wait for the request(s) to be completed and then check UI elements, when I have similar cases.
https://www.cypress.io/blog/2019/12/23/asserting-network-calls-from-cypress-tests/, scroll down to “Waiting for the network call to happen” section.
cy.wait('....') is still there, but this time I wait for something explicit that effects the UI
My task: a dynamic progress bar in odoo.
I'm using the Odoo widget: 'progressbar'. I want to update the view every time the value is updated - hence I want to trigger the on_change_input javascript function inside my python write method to render the view.
#api.one
def updatevalue(self, val):
self.value = val
# TODO call javascript function on_change_input()
The purpose is, that the progressbar should be updated while a process is running and the user should see the progress without updating the site.
Is my task possible with the progressbar widget? Or is there another possibility to show dynamic content in Odoo?
If I use my updatevalue method as button, the progressbar is updated after clicking the button without calling the javascript function & without refreshing the page... but I do want to call the method in my code (and probably over rpc) therefore this does not help -.-
Thank you for your time!
Here is the workflow I have so far:
The user clicks on the button do_time_consuming_task
and the following function is called:
def do_timeconsuming_task(self):
ws = websocket.WebSocket()
ws.connect('ws:/129.0.0.1:1234/')
data = { 'topic' : 'server_command', 'id' : self.id, 'commandName' : 'do_sth',}
payload = ujson.dumps(data)
ws.send(payload)
ws.close()
On the server, the command is received and processed. There is an open rpc connection:
odoo = odoorpc.ODOO("129.0.0.1", port=8069)
odoo.login("database", "user", "password")
my_module = odoo.env['my_module.progress_widget_test']
progress_instance = my_module.browse(id)
Every time the progress value changes I call the following method of my module:
progress_instance.updatevalue(new_value)
when the value equals 100 % I close the connection
odoo.logout()
This functionality already exists and you can copy parts of it from account/static/src/js/account_reconciliation_widgets.js from the method updateProgressBar and processReconciliations. You will see here the correct way of updating the progress bar.
The purpose is, that the progressbar should be updated while a process
is running and the user should see the progress without updating the
site.
See on the processReconciliations how it is done, basically you call the process_reconciliations method that exists on the back end and you get a deferred object back. From that deferred object you can use progress()
Looking through the documentation of .progress() you will see that you need to report your progress using .[notify][2]()
How do you define the percentage of completion of your process?
In my Rails 4 application I have a Task model which can be sorted/grouped in various ways (group by user, group by transaction, order by due date, etc...) on several different pages. Users can create a new Task via an AJAX popup modal from anywhere in the application, and if they are on a page that is displaying a list of tasks, I would like to append the task to the list if appropriate.
# NOTE: tasks should only ever be displayed one of the ways below, not multiple
# Displaying tasks grouped by user
<div id="user-1-tasks">
<div class="task">...</div>
...
</div>
<div id="user-2-tasks">
<div class="task">...</div>
...
</div>
# Displaying tasks grouped by transaction
<div id="transaction-1-tasks">
<div class="task">...</div>
...
</div>
<div id="transaction-2-tasks">
<div class="task">...</div>
...
</div>
# Displaying all tasks together (ordered by due date)
<div id="tasks">
<div class="task">...</div>
...
</div>
The logic for determining which proper list to update is complex, because there may not be any list on the page (no update) or it could be sorted in several different ways like the examples I list above. It all depends on what page the user was on when they created the Task and how they had their tasks sorted (if any).
I came up with a "hack", whereby I pass all the possible DOM IDs that can be updated in a specific order and it updates the proper one on the page, or none if there aren't any.
Calculating what page the user is currently viewing and how the list is sorted is complicated, so it seemed much simpler to tell the JS to update "all" of the lists, knowing that only 1 should be present on the DOM at a time. The order of the DOM IDs is important, using the most-specific first and the fallback option last.
Note I am using server-generated JS via Rails' js.erb. I'm aware of the discussion surrounding this practice, but for this application it was much cleaner to maintain 1 copy of my HTML templates on the server instead of trying to do it all client-side and pass JSON back and forth.
# app/models/task.rb
class Task < ApplicationRecord
belongs_to :user
belongs_to :transaction
def domids_to_update
"#transaction-#{transaction.id}-tasks, #user-#{user.id}-tasks, #tasks"
end
end
# app/controllers/tasks_controller.rb
class TasksController < ApplicationController
# Only one of several pages that can display tasks a variety of ways
def index
if params[:sort] == "by-transaction"
#tasks = Task.includes(:transaction).group_by(&:transaction_record)
elsif params[:sort] == "by-user"
#tasks = Task.includes(:user).group_by(&:user)
else # default is order by due date
#tasks = Task.order(:due_date)
end
end
def create
#task = Task.create(task_params.merge(user: current_user))
# create.js.erb
end
private
def task_params
params.require(:task).permit(
:title,
:transaction_id,
:due_date
)
end
end
# app/views/tasks/create.js.erb
$("<%= #task.domids_to_update %>").append("<%=j render(partial: 'tasks/task', locals: { task: #task } %>");
As crazy as it seems, this actually works, because there "should" only be 1 of the lists present on the page at a time.
However, when I do things that seem crazy, it's usually because they are.
Is there a way to tell jQuery (or even with plan JavaScript) to only update the first occurrence of the #task.domids_to_update? The returned IDs are in a specific order, with the most specific first and the catch-all at the end.
Or is there a better way to implement the functionality I'm trying to achieve?
I'm slowly trying to remove my jQuery dependency, so my preference is vanilla JavaScript solutions. I only need to support modern browsers, so I don't have to worry about IE6, etc.
Or if the solution is resolved server-side by calculating the appropriate list ID to update I'm open to that as well.
You could use plain js to check for element existence and assign the first existing element to a variable:
# app/models/task.rb
# first, in your assignment, lose the spaces and the hashes,
# to make it simple to convert to array:
def domids_to_update
"transaction-#{transaction.id}-tasks,user-#{user.id}-tasks,tasks"
end
# app/controllers/tasks_controller.rb
var ids = "<%= #task.domids_to_update %>".split(',')
var theElement = document.getElementById(ids[0]) || document.getElementById(ids[1]) || document.getElementById(ids[2])
if (theElement !== null)
jQuery(theElement).append(...)
// or with plain js:
// var html = theElement.innerHTML
// html = html + "appended html"
// theElement.innerHTML = html
end
I have an Angular app used to track hours worked by the user. When the user adds a new job, they are taken through a wizard, with each page/controller adding a new property to the job object. Once the wizard is complete, the user can start tracking by navigating to the jobs home page from the app main page.
It is, however, possible to exit the wizard before it is completed (via the back button) and then navigate to the home page of the job. What I need is for the controller for that home page to redirect to the appropriate wizard page for whichever job property is missing.
The job variable is retrieved from local storage at the start of the controller code.
var job = DatastoreService.objectJob();
job.initFromHash($routeParams.jobHash);
function checkJobProps(prop, route){
if(!job.data.hasOwnProperty(prop))
$location.path('/wizard/add-position/' + $routeParams.jobHash + '/' + route);
}
checkJobProps('taxSettings', 'tax');
checkJobProps('payrollSettings','payroll-opt');
checkJobProps('breaks', 'breaks');
checkJobProps('allowances', 'allowances');
checkJobProps('deductions', 'deductions');
checkJobProps('generalSettings', 'general-settings');
There is code below this on the controller that breaks if certain properties are not available. None of these function calls execute fast enough to prevent errors. They will redirect, but not elegantly and it will also always be the last one in the list that takes effect.
Do I do this with a promise? The navigation that is used from the home page of the app to the home page of each job is a directive so, I guess it may be possible to init the job from the $routeParams.jobhash and check these properties within the directive, but I would have to learn more about directives first.
Any help would be much appreciated.
$location.path() is asynchronous and will not prevent the code that follows it from executing. You will have to manually stop the execution with a return statement.
Note that the return statement must belong to the controller function block. You cannot put it inside another function since that will only stop the execution of that specific function.
Something along these lines should work:
var job = DatastoreService.objectJob();
job.initFromHash($routeParams.jobHash);
var redirectPath;
function checkJobProps(prop, route) {
if (redirectPath || job.data.hasOwnProperty(prop)) return;
redirectPath = '/wizard/add-position/' + $routeParams.jobHash + '/' + route;
}
checkJobProps('taxSettings', 'tax');
checkJobProps('payrollSettings', 'payroll-opt');
checkJobProps('breaks', 'breaks');
checkJobProps('allowances', 'allowances');
checkJobProps('deductions', 'deductions');
checkJobProps('generalSettings', 'general-settings');
if (redirectPath) return $location.path(redirectPath);
... rest of the code ...