I am using a script in a Google Spreadsheet to, upon form submission, copy an existing spreadsheet and google form to create a new spreadsheet and new form and then connect the new form to the new spreadsheet so the new spreadsheet is receiving the responses from the new form.
The script in the copied spreadsheet is copied to the new spreadsheet, but the installed triggers don't exist. Is there a way to create those triggers from the original spreadsheet's script (the spreadsheet that received the form submission that created the new SS and form) or do I need to rely upon non-installed triggers in the new spreadsheet to create the installed trigger?
Triggers run scripts which require authorization by the user. Because your script is bound to a spreadsheet, it will need to be authorized on each copy.
I have a similar system (copies of a master sheet + code) and we addressed this by adding a custom menu to run a script when someone makes a copy. I added a custom menu and a setup script which would authorize the trigger.
function onOpen(e) {
var ui = SpreadsheetApp.getUi().createMenu("PGP Setup").addItem("Run", "setup").addToUi();
}
function setup() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('makeDocs')
.timeBased()
.everyHours(1)
.create();
}
It's easy on the user who created the sheet and has been reliable for us so far.
I was able to solve my issue with the code below. The function 'newSSTrigger' is in the original script (not a copied one) and is called after the new SS and Form are created and sync'd - this is where the the variable idOfNewSS comes from. The trigger will not create a script in the new objects or even be seen in the new object's scripts as an installed trigger. To find the trigger go to Edit>All Your Triggers from any script. Triggers not greyed out are attached to the document in some way.
This seems to have two benefits:
1) I never have to deal with any permissions - the triggers work without me touching the new docs at all.
2) If I update the 'myFunction' (below) it changes how the existing triggers work because the trigger retrieves its instructions from the original script - in its current state. This means I can update all existing triggers created by this script just by editing this function.
function newSSTrigger(idOfNewSS) {
var newSS = SpreadsheetApp.openById(idOfNewSS);
ScriptApp.newTrigger("myFunction")
.forSpreadsheet(newSS)
.onFormSubmit()
.create();
}
function myFunction() {
do stuff...
}
A different option that would work for people who are not just copying a sheet programmatically (or who are anticipating users making copies that the developer doesn't have permission to edit) is to take care of it for the user inside the bound script. You could use code similar to this:
function onOpen(){
var triggers = ScriptApp.getProjectTriggers();
if(triggers.length == 0){
ScriptApp.newTrigger('yourFunction')
.timeBased()
.everyHours(1)
.create();
}
This says that if there are no triggers, add a time-based trigger. You can see this documentation for variations.
Related
I am using the following code, in an attempt to batch upload the changes made on a table:
onConfirmActionPressed: function() {
var oModel = this.getModel();
oModel.setUseBatch(true);
oModel.submitChanges();
}
I am using setProperty() to set the new values, like this:
onSingleSwitchChange: function(oControlEvent) {
var oModel = this.getView().getModel();
var rowBindingContext = oControlEvent.getSource().getBindingContext();
oModel.setProperty(rowBindingContext.sPath + "/Zlspr", "A");
}
When onConfirmActionPressed is executed, I get a server error, saying that "Commit work during changeset processing not allowed" on SAP R3.
When I upload the lines of the table one-by-one, it works fine. However, uploading this way is very slow, and in some cases it takes more than 10 minutes for the process to complete.
Am I doing something wrong while batch submitting? Is there a chance the issue is due to server (R3) misconfiguration?
You need to override methods:
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_BEGIN
/IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_END
Keep track of the errors across all calls to update methods and if everything went OK then in changeset_end perform commit on the database
edit:
To clarify:
In your Data Provider Class Extension in SAP Gateway you need to find your YOURENTITY_UPDATE_ENTITY method and get rid off any COMMIT WORK statements.
Then you need to redefine /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_BEGIN method and, which is a method which is fired before any batch operation. You could define a class attribute such as table mt_batch_errors which would be emptied in this method.
When you post batch changes from UI5 using oModel.submitChanges() all single changes to Entities are directed to appropriate ..._UPDATE_ENTITY methods. You need to keep track of any possible errors and if any occurs then fill your mt_batch_errors table.
After all entities have been updated /IWBEP/IF_MGW_APPL_SRV_RUNTIME~CHANGESET_END method is fired in which you are able to check mt_batch_errors table if any errors occurred during the batch process. If there were errors then you should probably ROLLBACK WORK, and if not then you are free to COMMIT WORK.
That is just an example of how it could be done, I'm curious of other suggestions.
Good luck!
This is my first try into coding for sugarCRM / suiteCRM.
I should say I've been coding for Wordpress for nearly 10 years now, but I'm completely lost now I'm starting to dig into suiteCRM.
I've read that you can add a logic hook to modify the data after saving it to the database, but I don't know where to start...
Imagine I create a task for today, july 7th, related to a client I use to visit every 2 months, so there's a field in Accounts named "Visiting frequency". I'd like to add a future date (july 7th + 60 days = september 7th aprox) into the task's "Future Visiting Date" field, so I can use it to create that particular future task via Workflow.
What I'm trying to do is to calculate a field in tasks (Future visiting date), that equals to the amount of days on the accounts module's field (Visiting frequency) added to the task's own Date field.
I've been able to make it work, using the following layout:
Inside \custom\modules\Tasks\logic_hooks.php
<?php
$hook_version = 1;
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
1, //Processing index. For sorting the array.
'future_task_date_on_task_creation', //Label. A string value to identify the hook.
'custom/modules/Tasks/future_visit_date.php', //The PHP file where your class is located.
'before_save_class', //The class the method is in.
'future_visit_date' //The method to call.
);
?>
Inside \custom\modules\Tasks\future_visit_date.php
<?php
if (!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
class before_save_class {
function future_visit_date($bean, $event, $arguments) {
$bean->rhun_fecha_sig_c = date("Y-m-d H:i:s", $date);
}
}
?>
With this setup, the Future Visiting Date gets filled with the calculated date.
I've also read that this setup is not advised, and that I should use the Extension Framework and put the first file in this path:
/custom/Extension/modules/Tasks/Ext/LogicHooks/<file>.php
But I can't make it work.
Do I have to create the LogicHooks folder if it's not there?
Which filename should I assign to this file?
Do I have to change something else inside the code?
Yes, create the LogicHooks directory if it doesn't exist. The PHP file can be called anything you like.
/custom/Extension/modules/Tasks/Ext/LogicHooks/MyLogicHookFile.php
Define your logic hooks in this file as before.
<?php
$hook_version = 1;
$hook_array['before_save'] = Array();
$hook_array['before_save'][] = Array(
1, //Processing index. For sorting the array.
'future_task_date_on_task_creation', //Label. A string value to identify the hook.
'custom/modules/Tasks/future_visit_date.php', //The PHP file where your class is located.
'before_save_class', //The class the method is in.
'future_visit_date' //The method to call.
);
Then run a repair and rebuild from the Admin panel.
The main advantage to using the Extension framework is that it allows multiple developers to add components to a Sugar instance without worrying about overwriting existing code.
More info can be found about it in the Developer Guide
I want to create a standard typo3 extension but when I create a record (or modify it) I want to calculate something (in my case I want to call the Google Map API to get coordinates from a given address).
SO I search for a hook or something. Any idea?
One of my project example, may helps you for hook in backend when record has been changed.
In your extension file ext_localconf.php
// Hook for cancellation
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][] = 'EXT:femanager/class.tx_femanager_tcemainprocdm.php:tx_femanager_tcemainprocdm';
hook file class.tx_femanager_tcemainprocdm.php where you can execute
your script
class tx_femanager_tcemainprocdm{
function processDatamap_postProcessFieldArray ($status, $table, $id, &$fieldArray, &$reference){
// $status also called action like delete
// $table - table name of excute backend action
// $id - record UID
// $fieldArray - fields of your table
if($table = 'your_extension_table_name'){
// your script
}
}
}
Maybe this answer is useful to you.
Register your class as a data handling hook in your extension. This one "is called AFTER all commands of the commandmap" were executed. Maybe you need to look for a more appropriate one.
Then in your registered Hook i.e. 'typo3conf/ext/your_ext/Classes/Hooks/AfterCreate.php' do your calculation. Hope this sets you on the right track.
In my special case there was no need to calculate the coordinates when the record got saved. So I just used the listAction in the controller, check if coordinates are there and if not call the Google API (and send an email if the Google API does not give a coordinate back).
In another case where the new record comes from a frontend plugin and I had to do something with this data I used the createAction in the Controller. (I am not sure if the createAction is also called when the record is created from the backend.)
I've create an installable script which creates forms on the fly for 2 to 3 party verification. Since these forms are filled out asynchronously, and often in batches, it is important to determine which form called the function.
First the response form is created, then a trigger is installed.
CreateForm function:
function createForm(title){
var form = FormApp.create(title)
//set parameters for new form
form.setCollectEmail(true);
form.setShowLinkToRespondAgain(false);
...
return form;
};
InstallTrigger function:
function InstallTrigger(form, funct) {
var trigger= ScriptApp.newTrigger(funct)
.forForm(form)
.onFormSubmit()
.create()
return trigger.getUniqueId();
};
During the initial form submit, a function called createMessage() creates an email based on responses and runs the two scripts above. The email contains a link to the newly created form to await approval. To that point, everything works fine.
The problem comes when the newly installed trigger fires. I haven't found a way to set the trigger source as a variable. I'm sure that it has to do with an event, but the documentation on events doesn't go too far into forms from what I can see. I read that it can be done (Understanding Events). I just don't quite understand where (e) is defined on an installable script. Would it be in the install function or some other place?
Thanks again Serge inas for the walkthrough.
(e) is defined when the function defined. In my example above, the InstallTrigger() function adds a trigger to the form passed to it. The script that executes on submission is also passed to it. So, the installation of the trigger comes from a pre-installed onSubmit() trigger. In the submission function, it calls
var NewForm = FormApp.createForm();
InstallTrigger(NewForm, triggerFunction);
The function "triggerFunction" should look something like this:
function triggerFunction (e) {
Logger.log(JSON.stringify(e))
GmailApp.sendEmail("yourEmailString","Debugging",Logger.getLog() //sends an email to you with the logfile.
var form = FormApp.openByUrl(e.response.getEditResponseUrl());
...
}
The e.source mentioned in the aforementioned article says that it works for installable triggers, but when enumerated, only the e.resource and e.authMode were available. Fortunately, the e.response contains the method editUrl that can be used with the method .openByUrl() and return the original form.
TL;DR version:
In CQ workflows, is there a difference between what's available to the OR Split compared to the Process Step?
Is it possible to access the /history/ nodes of a workflow instance from within an OR Split?
How?!
The whole story:
I'm working on a workflow in CQ5 / AEM5.6.
In this workflow I have a custom dialog, which stores a couple of properties on the workflow instance.
The path to the property I'm having trouble with is: /workflow/instances/[this instance]/history/[workItem id]/workItem/metaData and I've called the property "reject-or-approve".
The dialog sets the property fine (via a dropdown that lets you set it to "reject" or "approve"), and I can access other properties on this node via a process step (in ecma script) using:
var actionReason;
var history = workflowSession.getHistory(workItem.getWorkflow());
// loop backwards through workItems
// and as soon as we find a Action Reason that is not empty
// store that as 'actionReason' and break.
for (var index = history.size() - 1; index >= 0; index--) {
var previous = history.get(index);
var tempActionReason = previous.getWorkItem().getMetaDataMap().get('action-message');
if ((tempActionReason != '')&&(tempActionReason != null)) {
actionReason = tempActionReason;
break;
}
}
The process step is not the problem though. Where I'm having trouble is when I try to do the same thing from inside an OR Split.
When I try the same workflowSession.getHistory(workItem.getWorkflow()) in an OR Split, it throws an error saying workItem is not defined.
I've tried storing this property on the payload instead (i.e. storing it under the page's jcr:content), and in that case the property does seem to be available to the OR Split, but my problems with that are:
This reject-or-approve property is only relevant to the current workflow instance, so storing it on the page's jcr:content doesn't really make sense. jcr:content properties will persist after the workflow is closed, and will be accessible to future workflow instances. I could work around this (i.e. don't let workflows do anything based on the property unless I'm sure this instance has written to the property already), but this doesn't feel right and is probably error-prone.
For some reason, when running through the custom dialog in my workflow, only the Admin user group seems to be able to write to the jcr:content property. When I use the dialog as any other user group (which I need to do for this workflow design), the dialog looks as though it's working, but never actually writes to the jcr:content property.
So for a couple of different reasons I'd rather keep this property local to the workflow instance instead of storing it on the page's jcr:content -- however, if anyone can think of a reason why my dialog isn't setting the property on the jcr:content when I use any group other than admin, that would give me a workaround even if it's not exactly the solution I'm looking for.
Thanks in advance if anyone can help! I know this is kind of obscure, but I've been stuck on it for ages.
a couple of days ago i ran into the same issue. The issue here is that you don't have the workItem object, because you don't really have an existing workItem. Imagine the following: you are going through the workflow, you got a couple of workItems, with means, either process step, either inbox item. When you are in an or split, you don't have existing workItems, you can ensure by visiting the /workItems node of the workflow instance. Your workaround seems to be the only way to go through this "issue".
I've solved it. It's not all that elegant looking, but it seems to be a pretty solid solution.
Here's some background:
Dialogs seem to reliably let you store properties either on:
the payload's jcr:content node (which wasn't practical for me, because the payload is locked during the workflow, and doesn't let non-admins write to its jcr:content)
the workItem/metaData for the current workflow step
However, Split steps don't have access to workItem. I found a fairly un-helpful confirmation of that here: http://blogs.adobe.com/dmcmahon/2013/03/26/cq5-failure-running-script-etcworkflowscriptscaworkitem-ecma-referenceerror-workitem-is-not-defined/
So basically the issue was, the Dialog step could store the property, but the OR Split couldn't access it.
My workaround was to add a Process step straight after the Dialog in my workflow. Process steps do have access to workItem, so they can read the property set by the Dialog. I never particularly wanted to store this data on the payload's jcr:content, so I looked for another location. It turns out the workflow metaData (at the top level of the workflow instance node, rather than workItem/metaData, which is inside the /history sub-node) is accessible to both the Process step and the OR Split. So, my Process step now reads the workItem's approveReject property (set by the Dialog), and then writes it to the workflow's metaData node. Then, the OR Split reads the property from its new location, and does its magic.
The way you access the workflow metaData from the Process step and the OR Split is not consistent, but you can get there from both.
Here's some code: (complete with comments. You're welcome)
In the dialog where you choose to approve or reject, the name of the field is set to rejectApprove. There's no ./ or anything before it. This tells it to store the property on the workItem/metaData node for the current workflow step under /history/.
Straight after the dialog, a Process step runs this:
var rejectApprove;
var history = workflowSession.getHistory(workItem.getWorkflow());
// loop backwards through workItems
// and as soon as we find a rejectApprove that is not empty
// store that as 'rejectApprove' and break.
for (var index = history.size() - 1; index >= 0; index--) {
var previous = history.get(index);
var tempRejectApprove = previous.getWorkItem().getMetaDataMap().get('rejectApprove');
if ((tempRejectApprove != '')&&(tempRejectApprove != null)) {
rejectApprove = tempRejectApprove;
break;
}
}
// steps up from the workflow step into the workflow metaData,
// and stores the rejectApprove property there
// (where it can be accessed by an OR Split)
workItem.getWorkflowData().getMetaData().put('rejectApprove', rejectApprove);
Then after the Process step, the OR Split has the following in its tabs:
function check() {
var match = 'approve';
if (workflowData.getMetaData().get('rejectApprove') == match) {
return true;
} else {
return false;
}
}
Note: use this for the tab for the "approve" path, then copy it and replace var match = 'approve' with var match = 'reject'
So the key here is that from a Process step:
workItem.getWorkflowData().getMetaData().put('rejectApprove', rejectApprove);
writes to the same property that:
workflowData.getMetaData().get('rejectApprove') reads from when you execute it in an OR Split.
To suit our business requirements, there's more to the workflow I've implemented than just this, but the method above seems to be a pretty reliable way to get values that are entered in a dialog, and access them from within an OR Split.
It seems pretty silly that the OR Split can't access the workItem directly, and I'd be interested to know if there's a less roundabout way of doing this, but for now this has solved my problem.
I really hope someone else has this same problem, and finds this useful, because it took me waaay to long to figure out, to only apply it once!