Grails loading widgets in Parallel - grails

I have some widgets in my application. I want to load them in a single page. It works always when loaded sequentially,but fails(throws exception-java.lang.IllegalStateException-Cannot ask for request attribute - request is not active anymore!) when loaded in parallel. Am I missing something ? Is there any other way to load these in parallel? Please suggest. Here is my code snippet
class TestController {
def widget1() {
render "Widget 1"
}
def widget2() {
render "Widget 2"
}
}
class ManagerController {
def loadSequential(){
def data = [:]
data['view1'] = g.include(action: 'widget1', controller: 'test')
data['view2'] = g.include(action: 'widget2', controller: 'test')
render data['view1']
render data['view2']
}
def loadParallel(){
def data = [:]
PromiseMap pm = new PromiseMap()
pm['view1'] = { g.include(action: 'widget1', controller: 'test') }
pm['view2'] = { g.include(action: 'widget2', controller: 'test') }
def result = pm.get()
render result['view1']
render result['view2']
}
}

Here's my 2p on this matter.
We've tried really hard to make Grails render pages in parallel but we couldn't make it work. After digging really deep we simply found that Grails is not ready for this kind of stuff. It's build in a way where rendering is always thread-exclusive.
I'd say it's all due to the org.codehaus.groovy.grails.web.pages.GroovyPageOutputStack that is not ready for a concurrent access.
I'll watch this space just in case someone managed to make it work.

Try this:
import static grails.async.Promises.*
...
def loadParallel() {
task {
def data = [:]
PromiseMap pm = new PromiseMap()
pm['view1'] = { g.include(action: 'widget1', controller: 'test') }
pm['view2'] = { g.include(action: 'widget2', controller: 'test') }
def result = pm.get()
render result['view1'] + result['view2']
}
}
In the loadSequential() example you are not loading response sequentially, but concatenating two views into a single response. Parallel rendering should be a client side job. See for example how Lift does it with jQuery.

Related

How to dynamically inject permission checking logic for RAILS_API method

The current project adopts the front-end and back-end separation architecture, the front-end is realized by VUE, and the back-end adopts RAILS. Now we need to solve the front-end and back-end authentication logic, and hope that the back-end will force the front-end authentication for each API to have access rights. I hope someone can help to answer, the following is some of my implementation logic:
Base class controller logic:
def collect_metrics
start_time = (Time.now.to_f.round(3) * 1000).to_i
# ap self.as_json
yield
end_time = (Time.now.to_f.round(3) * 1000).to_i
duration = end_time - start_time
# 日志转储
save_logger(duration: duration, severity: "INFO")
# 刷新用户操作时间
update_user_online
Rails.logger.debug("正在调用 API 接口:#{#api_operation},授权用户为 #{#current_user}")
end
User association permission dictionary:
u.roles = ["log:list", "error:list", "online:list", "log:edit", "log_error:del", "user:offline", "user:list", "menu:list", "job:list", "role:list", "dept:list", "dict:list", "dept:edit", "dept:del", "dept:add", "dict:edit", "dict:del", "dict:add"]
Each ACTION binds a class variable:
# 创建部门
def create
#api_operation = "创建部门"
#api_authorize = "dept:add"
pre_authorize
data = dept_params.except(:id, :isTop, :sub_count).as_json
pid = data.delete("pid")
data[:parent] = SysDept.find(pid) if pid.to_i > 0
dept = SysDept.new data
if dept.save
render json: { content: "成功创建部门" }, status: :ok
else
render json: { content: "创建部门异常" }, status: :not_acceptable
end
end
# 更新部门
def update
#api_operation = "更新部门"
#api_authorize = "dept:edit"
pre_authorize
data = dept_params.except(:label, :hasChildren, :leaf, :isTop, :sub_count)
id = data.delete("id")
if id.present? && SysDept.find(id).update!(data)
render json: { message: "更新部门成功" }, status: :ok
else
render json: { message: "更新部门异常" }, status: :not_acceptable
end
end
here is java's solution code,I don't know how to convert the following code into RUBY style。
#Log("删除部门")
#ApiOperation("删除部门")
#DeleteMapping
#PreAuthorize("#el.check('dept:del')")
public ResponseEntity<Object> deleteDept(#RequestBody Set<Long> ids){
Set<DeptDto> deptDtos = new HashSet<>();
for (Long id : ids) {
List<Dept> deptList = deptService.findByPid(id);
deptDtos.add(deptService.findById(id));
if(CollectionUtil.isNotEmpty(deptList)){
deptDtos = deptService.getDeleteDepts(deptList, deptDtos);
}
}
// 验证是否被角色或用户关联
deptService.verification(deptDtos);
deptService.delete(deptDtos);
return new ResponseEntity<>(HttpStatus.OK);
}
#Log("修改部门")
#ApiOperation("修改部门")
#PutMapping
#PreAuthorize("#el.check('dept:edit')")
public ResponseEntity<Object> updateDept(#Validated(Dept.Update.class) #RequestBody Dept resources){
deptService.update(resources);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
If I understand your problem, you can add a before_action :check_preauth in your controller, then implement the method check_preauth checking the user's permission to perform the action.

Dynamically add path in Groovy

I'm trying to add new functionality, without refactoring the entire code. Actually the code calls msg.typeOfMsg('msg'), but I want to add validation when it comes true change all the typeOfMsg to a specific type:
def specialRule = true
def message = changeMessageType()
def changeMessageType() {
def toInform = { def message -> thePath.toChange.inform(message) }
return specialRule ? [reprove : toInform, approve : toInform] : thePath.toChange
}
println(message.reprove('Standart reprove message')
This is the solution I found, it works, but I have a lot of message types and custom message types, there's some way to dynamically change all calls to inform, without refactoring all the code to calls something like message(typeOfMessage, message)?
-----Edit-----
Here's a runnable version
def message = changeMessageType()
def changeMessageType() {
def originalPath = new OriginalPath()
def specialRule = true
def toInform = { def message -> originalPath.inform(message) }
return specialRule ? [reprove : toInform, approve : toInform] : originalPath
}
message.approve('Standart reprove message')
class OriginalPath {
def reprove(message) {
println "Reprove: ${message}"
}
def approve(message) {
println "Approve: ${message}"
}
def inform(message) {
println "Inform: ${message}"
}
}

Rspec : Declaring a variable that will be created into the method as record

I'm quite new to rails, but I seem to be facing an issue I cannot solve. I am trying to test a background job that will create a new record and use it later to instanciate a new service.
As I am testing a background job, it seems that I shoud be making a double of this service BEFORE the test, but one of the parameters is created whithin the job, using another record that is created whithin the job as a reference. Hence, there will always be one difference in terms of ID everytime I try to run it.
Here is the sample code :
RSpec.describe UpdateVideoPerfDatasJob, :type => :job do
it { expect(described_class).to be < ActiveJob::Base }
context '#perform' do
let (:split_test) { create(:split_test_for_result_slot) }
let(:adwords_account) { split_test.adwords_account }
let(:user) { split_test.adwords_account.user }
let(:report_service) { instance_double('VideoPerformanceReportBis') }
let(:api_service) { instance_double('GoogleAdsApiWrapper') }
let(:campaigns_datas) { '#2 Api called...' }
let(:segments_datas) { '#3 Api called...' }
let(:data) { '#4 Global Array Created' }
before do
allow(VideoPerformanceReportBis).to receive(:new).with(split_test: split_test, adwords_account: adwords_account, user: user, result_slot: result_slot).and_return(report_service)
allow(GoogleAdsApiWrapper).to receive(:new).with(adwords_account: adwords_account, user: user).and_return(api_service)
allow(report_service).to receive(:refresh).with(no_args).and_return('#1 Refresh method')
allow(report_service).to receive(:call_api_campaigns).with(no_args).and_return(campaigns_datas)
allow(api_service).to receive(:get_campaigns).with(no_args).and_return('...and campaign gotten !')
allow(report_service).to receive(:call_api_segments).with(no_args).and_return(segments_datas)
allow(api_service).to receive(:get_campaigns_segments).with(no_args).and_return('... and segments gotten !')
allow(report_service).to receive(:create_global_array).with(campaigns_datas, segments_datas).and_return(data)
allow(report_service).to receive(:update).with(data).and_return('#5 update data')
class UpdateVideoPerfDatasJob < ApplicationJob
def perform(split_test:, adwords_account:, user:)
logger.warn 'Updating video performance data'
history_log = HistoryLog.create(
split_test: split_test,
user: user,
adwords_account: adwords_account,
operation: 'Update vidéo performance data',
messages: '',
status: 'Initiated'
)
result_slot = ResultSlot.create(
split_test: split_test,
history_log: history_log,
)
result_slot.previous_result_slot_id = split_test.result_slot.id unless split_test.result_slot.nil?
report_service = VideoPerformanceReportBis.new(
split_test: split_test,
adwords_account: adwords_account,
user: user,
result_slot: result_slot
)
response = report_service.refresh
history_log.update!(
messages: "Data updated on #{Date.today}",
status: 'Video performance data updated'
)
NotificationsChannel.broadcast_to user, 'Finished Update Video Performance Data Worker'
end
end
As you can see, the VideoPerformanceReportService is using a result_slot as parameter, that is created inside the job, using an history_log (also created inside the job) as a reference.
How can I test this ?
(Thank you in advance for everyone helping)

remoteFunction won't work without render

Here is a fragment of my javascript code:
${remoteFunction(controller: 'job', action: 'updateTimeStamp', update: 'randomString', params: '{timeStamp:timeStamp, sessionId:sessionId}')};
var jobIsDone = ${remoteFunction(controller: 'job', action: 'jobIsDone', params: '{sessionId:sessionId}')};
The first line works as expected, and the second one gives me 404 exception. jobIsDone() method doesn't render anything. I couldn't find proper explanation of remoteFunction tag, but as far as I understood it can be used without rendering, am I mistaken?
Here are the controller methods:
def updateTimeStamp(){
timeStampMap.putAt(params.sessionId, params.timeStamp)
def randomString = bcvjobService.saySomething()
render "<p>${randomString}</p>"
}
def jobIsDone(){
if (jobDone.get(params.sessionId)){
return true
}
else return false
}
Try with this:
def jobIsDone(){
if (jobDone.get(params.sessionId)){
response.sendError(200,"ok")
}
else {
response.sendError(500,"error")
}
}
This will prevent the 404 not found. If you want that come back a true or false with a JSON for example is:
def jobIsDone(){
def result = [error:true]
if (jobDone.get(params.sessionId)){
result.error = false
}
render result as JSON
}

webflow in grails application

how can i redirect to the same state more than one time using web flow
ex:
on('submit'){
def destinationInstance = Destination.get(params.destination)
def destinationGroupsInstance = DestinationGroup.get(params.destinationGroups)
def h = destinationInstance.addToDestinationGroups(destinationGroupsInstance)
}.to('flowList')
what i need is how to enter to this state more than one time until destinations ends
thx
on('submit'){
def destinationInstance = Destination.get(params.destination)
def destinationGroupsInstance = DestinationGroup.get(params.destinationGroups)
def h = destinationInstance.addToDestinationGroups(destinationGroupsInstance)
}.to{
(condition or while loop or for loop)
if success then
return "<state name>"
else
return "flowList"
}
Reference:
http://livesnippets.cloudfoundry.com/docs/guide/2.%20Grails%20webflow%20plugin.html
Well, you'd probably have something like the following code, which is untested but may give you a general idea.
def destinationFlow = {
initialize {
action {
flow.destination = Destination.get(params.id)
}
on('success').to 'destinationList'
}
destinationList {
render(view: 'destinationList')
on('addDestination') {
def destinationGroup = DestinationGroup.get(params.destinationGroupId)
flow.destination.addToDestinationGroups(destinationGroup)
}.to 'destinationList'
on('finish').to 'done'
}
done {
flow.destination.save()
redirect(...) // out of the flow
}
}
You'll need buttons on your destinationList view that invoke the 'addDestination' or 'finish' actions. See the WebFlow documentation and Reference Guide.

Resources