Concurrent uses of Google Scripts - javascript

I've created a simple signup form that takes in the persons name & email, saves it to a database and gives him a seven digit code in return from a database. Each code can only belong to one person. The code is run under my user from Google App Scripts.
I'm now wondering, if I need to use Lockservice or anything else to allow for concurrent use of the program? If the 2 people use this program at the same time for example, would this likely cause any problems - in example that the input.name would be from the answers of one user accessing the script and input.email would be originated from another? A simple illustration of my code
document.getElementById("registerBtn").addEventListener("click",register);
function register (){
//take values from input
var input = {};
input.name = document.getElementById("namefield").value;
input.email = document.getElementById("emailfield").value;
if (input.email && input.name) {
google.script.run.withSuccessHandler(successfun).withFailureHandler(failurefun).checkInfo(input)
}else{
M.toast({html: 'Please insert your data'});
}}
function successfun (output){
M.toast({html: 'Your code is:' + output});
}
function failurefun (output){
M.toast({html: 'This name or email have already been registered'});
}
/// ... Google Scripts:
function checkInfo(input) {
// open google spreadsheet
//check with indexOf if the email exists
//if the email does not exist, check if name exists
//if the name does not exist - append new row.
//if this is successful, open up another tab and take the first value from there, that does not have 2 //next to it & change the value next to it to 2.
//return confirmation code;
//If name or email exist: return error;
}
// Editing the question taking the guidance from the comment into account: If I'd have the script run not from my account but from each individual user, I would not have this issue?

According to the Apps Script Lock Service documentation:
Lock Service allows scripts to prevent concurrent access to sections of code. This can be useful when you have multiple users or processes modifying a shared resource and want to prevent collisions.
Thus, since this is an Apps Script service, it can be used only in your Apps Script code and later called from the HTML code.
You might also want to take a look at the Apps Script's quotas, which states that you can get at most 30 simultaneously executions.
An alternative to your approach is the solution proposed in this answer here which suggests to make use of the Utilities.getUuid().
Reference
Apps Script Lock Service;
Apps Script Quotas;
How can I facilitate concurrent users on a Google Apps Script Webapp?;
Apps Script Utilites Class - getUuid().

Related

Monitor google sheet change event externally [duplicate]

What I wish to achieve:
Whenever a cell is changed in any google sheet on my shared drive (by
any user on the domain) I want to call an API endpoint and include
information about which cell was edited.
My approach:
I believe Google App Scripts Add-on is what I need. Installed for all users on the domain.
I see there are "bound" scripts and standalone scripts. For standalone scripts I am not able to create any other triggers than timer and calender based triggers. Bound scripts seem to be permanently bound to a single sheet and won't impact other sheets in any way.
What am I missing?
I find a few end-to-end tutorials on blogs for making bound scripts, but nothing for generic cross-domain stuff.
You can achieve all this through a standalone script. Create a standalone script and follow these steps:
Step 1: Get spreadsheet ids
First you would have to get the id of the different Spreadsheets in your shared drive. You can do it in Google Apps Script itself if you use the Advanced Drive Service (see Reference below). To activate this service, go to Resources > Advanced Google services... in your script editor and enable Drive API.
Then, write a function that will return an array of the spreadsheet ids in the shared drive. You will have to call Drive.Files.list for that. It could be something along the following lines (please write your shared driveId in the corresponding line):
function getFileIds() {
var params = {
corpora: "drive",
driveId: "your-shared-drive-id", // Please change this accordingly
includeItemsFromAllDrives: true,
q: "mimeType = 'application/vnd.google-apps.spreadsheet'",
supportsAllDrives: true
}
var files = Drive.Files.list(params)["items"];
var ids = files.map(function(file) {
return file["id"];
})
return ids;
}
Step 2: Create triggers for each spreadsheet
Install an onEdit trigger programmatically for each of the spreadsheets (an edit trigger fires a function every time the corresponding spreadsheet is edited, so I assume this is the trigger you want). For this, the ids retrieved in step 1 will be used. It could be something similar to this:
function createTriggers(ids) {
ids.forEach(function(id) {
var ss = SpreadsheetApp.openById(id);
createTrigger(ss);
})
}
function createTrigger(ss) {
ScriptApp.newTrigger('sendDataOnEdit')
.forSpreadsheet(ss)
.onEdit()
.create();
}
The function createTriggers gets an array of ids as a parameter and, for each id, creates an onEdit trigger: everytime any of these spreadsheets is edited, the function sendDataOnEdit will run, and that's where you will want to call your API endpoint with information about the edited cell.
Step 3: Call API endpoint
The function sendDataOnEdit has to get data from the edited cell and send it somewhere.
function sendDataOnEdit(e) {
// Please fill this up accordingly
var range = e.range;
var value = range.getValue();
UrlFetchApp.fetch(url, params) // Please fill this up accordingly
}
First, it can get information about the cell that was edited via the event object, passed to the function as the parameter e (you can get its column, its row, its value, the sheet and the spreadsheet where it is located, etc.). For example, to retrieve the value of the cell you can do e.range.getValue(). Check the link I provide in reference to get more details on this.
Second, when you have correctly retrieved the data you want to send, you can use UrlFetchApp.fetch(url, params) to make a request to your URL. In the link I provide below, you can see the parameters you can specify here (e.g., HTTP method, payload, etc.).
Please bear in mind that you might need to grant some authorization to access the API endpoint, if this is not public. Check the OAuth reference I attach below.
(You have to edit this function accordingly to retrieve and send exactly what you want. What I wrote is an example).
Summing this up:
In order to create the triggers you should run createTriggers once (if you run it more times, it will start creating duplicates). Run for example, this function, that first gets the file ids via Drive API and then creates the corresponding triggers:
function main() {
var ids = getFileIds();
createTriggers(ids);
}
Also, it would be useful to have a function that will delete all the triggers. Run this in case you want to start from fresh and make sure you don't have duplicates:
function deleteTriggers() {
var triggers = ScriptApp.getProjectTriggers();
triggers.forEach(function(trigger) {
ScriptApp.deleteTrigger(trigger);
})
}
Reference:
Advanced Drive Service
Drive.Files.list
onEdit trigger
Install trigger programmatically
onEdit event object
UrlFetchApp.fetch(url, params)
Connecting to external APIs
OAuth2 for Apps Script
ScriptApp.deleteTrigger(trigger)
I hope this is of any help.

How to use Google Script to send an email with information collected from Google Form?

For reference: Here is my Google Form.
Here is a Google PE Blank Sheet which has been tied to the output of my Google Form (answers will output on tab Form Responses 2).
I've built up a Google Form capable of collecting a good bit of data. It is meant to be filled out by a manager requesting SAP permissions for a new hire.
They proceed through the Form and enter their primary department (SAP Stream) which leads them to the next page/section that allows the manager to check off what the employee's required permissions will be.
Sometimes, an employee will interface with multiple departments, so the option for a supporting/secondary SAP Stream exists on the second page. This can be continued as needed until all of an employee's requisite roles/permissions are collected in a Google Sheet.
In the Sheet, you will see that:
Columns A-F include basic information
Columns H-T are for the Job Responsibilities
Columns U-AG are a range for the information of one's Supporting SAP Stream
Columns AH-AT are the Supporting SAP Stream's Job Responsibilities
Columns AU-BG collect the Secondary Supporting SAP Stream
I had built Google Script language to collect each of these bits of information in variables which would then get declared as global variables so they could be passed along to an HTML page in the App Script.
Finally, an email consisting of this HTML page would be sent out to the manager in charge of designating SAP permissions.
In previous iterations (which were much simpler), I used an IF statement to check whether a column at the end of the row was empty.
// Loop over the rows
data.forEach((row,i) => {
// Identify whether notification has been sent
if (row[59] === '') {
If it was, the Script would check the Sheet, collecting all of the aforementioned information, and send an email. Then, it would populate that last cell with "Notification Sent" so that the next time the Form was submitted and the Script checked, it would skip sending out a redundant email (and we would have a record that the process went through successfully).
The latest version included variables for
function jobResponsibilities(a){
var z = a.range.getRow();
var valuesA = a.range.getSheet().getRange(z,8,1,13).getValues();
var jobResponsibilities = valuesA.join(' ');
console.log(jobResponsibilities);
}
function supportingSAP(b){
var y = b.range.getRow();
var valuesB = b.range.getSheet().getRange(y,21,1,13).getValues();
var supportingSAP = valuesB.join(' ');
}
function secondaryJob(c){
var x = c.range.getRow();
var valuesC = c.range.getSheet().getRange(x,34,1,13).getValues();
var secondaryJob = valuesC.join(' ');
}
function secondarySAP(d){
var w = d.range.getRow();
var valuesD = d.range.getSheet().getRange(w,47,1,13).getValues();
var secondarySAP = valuesD.join(' ');
}
Collecting the four varying types of information. I used a getRange in order to scope out the exact parts of the Sheet which would have this information and join them together so that the variable could be pulled into the email in a simple table format.
For some reason, upon Form submission, the Notification IF doesn't seem to be running and the email never gets sent.
Is it a problem with the way I've ordered things? Did I improperly build the functions off what you showed me? Is it something as simple as my brackets at the bottom?

MS Dynamics CRM - how to test for presence of Notes records within Accounts

I need to check (OnLoad) whether an Account has Notes added to it. I've tried doing this using workflows but that only tests after a Note is added rather than if they are already there i.e. On Change, On updating, On status change etc.
Also tried accessing Notes via JavaScript but seems to not be able to reach the attribute. Will paste JScript here if this will help, though that might be the wrong way to go anyway.
function NotesAlert() {
// the 'notestext' field is the Notes description area
var NotesDesc = Xrm.Page.getAttribute("notestext").getValue();
if(NotesDesc !=null) {
Xrm.Page.getAttribute("dt_legacyuserurn").setValue("Notes exist for
this Acc");
}
}
Whether it is client side (JavaScript) or server side (C# plugins, workflows), you have to query the associated notes (annotations) for that particular account record & do the validation.
If you want to validate it in JS, use web api to get the associated notes of account from onLoad & onSave Form events, even onChange of fields can be done like below.
var query = "/api/data/v8.2/annotations?$filter=_objectid_value eq" + accountId;
If you want to validate in Plugins/Workflows (custom), use fetchxml or query expression to query the associated notes of account from Create, Update messages.

Automatically assign a customer to a specific customer group on sign-up - Bigcommerce

I've been told by BC support that this isn't possible, but I would be surprised if there really wasn't a way.
I need to be able to automatically assign a customer to a specific customer group when they create an account. My thought:
I would add an extra field to the sign-up form
Provide a user with a code (a string or number)
User enters code when creating new account
User hits submit
On form submit I would grab the value of the extra field:
var codeInput = document.getElementById('code-input').value;
I would then compare that value to a pre-defined string, and if there is a match, I would assign that customer to groupX (with a group id of 8):
if ( codeInput === "codeIGaveToTheUser" ) {
currentUserGroupID = 8;
}
Is it possible to assign a customer to a specific group on sign-up like this (or any other way)?
Any help is much appreciated.
Although using BigCommerce webhooks would ensure the highest success rate of executing your customer group assignment app, it requires quite a bit of setup on BigCommerce (creating a draft app, getting an oAuth key, jumping jacks, etc), and may be a bit of overkill for your requirements.
Here's an easier way, in my {mostly} humble opinion, that takes advantage of much of what you included in your original question. Any solution though will nonetheless require an external server to handle the customer group assignment through the BigCommerce API.
Within the BigCommerce control panel, add in the extra field to the user sign up form like you mentioned.
So as you can see, this new input field has been added natively to the default registration page:
So now, when a user creates an account on your site, the value for the Signup Code (the custom field created) will be directly accessible through the API for that customer's account. Take a look at what that JSON data looks like:
Okay, so this is nice and all, but how do we automate it?
To do so, we will have to let our external application know that a customer just registered. Furthermore, our external application will need some sort of reference to this newly created customer, so that it knows which customer to update the customer group for. Normally a BigCommerce webhook would notify us of all this, but since we aren't using a BigCommerce webhook, here's the alternative method to triggering the external script.
We will trigger our external application via the BigCommerce Registration Confirmation page - createaccount_thanks.html. This page is loaded immediately after a customer creates an account, so it is the perfect place to insert our trigger script.
Additionally, now that the customer is logged in, we can access the customer's email address via a BigCommerce Global system variable -%%GLOBAL_CurrentCustomerEmail%%.
We should make an HTTP request from this page to our external application along with the customer's email address. Specifically, we can make an XMLHttpRequest via JavaScript, or to be modern, we'll use Ajax via jQuery. This script should be inserted before the closing </body> tag on createaccount_thanks.html.
Example of POST request (although a GET would suffice as well):
<script>
$(function() {
$('.TitleHeading').text('One moment, we are finalizing your account. Please wait.').next().hide(); // Let the customer know they should wait a second before leaving this page.
//** Configure and Execute the HTTP POST Request! **//
$.ajax({
url: 'the_url_to_your_script.com/script.php',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({email:"%%GLOBAL_CurrentCustomerEmail%%"}),
success: function() {
// If the customer group assignment goes well, display page and proceed normally. This callback is only called if your script returns a 200 status code.
$('.TitleHeading').text('%%LNG_CreateAccountThanks%%').next().show();
},
error: function() {
// If the customer group assignment failed, you might want to tell your customer to contact you. This callback is called if your script returns any status except 200.
$('.TitleHeading').text('There was a problem creating your account').after('Please contact us at +1-123-456-7890 so that we can look into the matter. Please feel free to continue shopping in the meantime.');
}
});
});
</script>
Now finally, you just need to create your serverside application responsible for handling the request above, and updating the customer's customer group. You can use any language that you desire, and BigCommerce even offers several SDK's you can use to save mega development time. Just remember that you need to host it somewhere online, and then insert its URL to the JS script above.
PHP Example (quick & dirty):
git clone https://github.com/bigcommerce/bigcommerce-api-php.git
curl -sS https://getcomposer.org/installer | php && php composer.phar install
<?php
/**
* StackOverflow/BigCommerce :: Set Customer Group Example
* http://stackoverflow.com/questions/37201106/
*
* Automatically assigning a customer group.
*/
//--------------MAIN------------------------//
// Load Dependencies:
require ('bigcommerce-api-php/vendor/autoload.php');
use Bigcommerce\Api\Client as bc;
// Define BigCommerce API Credentials:
define('BC_PATH', 'https://store-abc123.mybigcommerce.com');
define('BC_USER', 'user');
define('BC_PASS', 'token');
// Load & Parse the Email From the Request Body;
$email = json_decode(file_get_contents('php://input'))->email;
// Execute Script if API Connection Good & Email Set:
if ($email && setConnection()) {
$customer = bc::getCollection('/customers?email=' .$email)[0]; //Load customer by email
$cgid = determineCustomerGroup($customer->form_fields[0]->value); //Determine the relevant customer group ID, via your own set string comparisons.
bc::updateCustomer($customer->id, array('customer_group_id' => $cgid)) ? http_send_status(200) : http_send_status(500); //Update the customer group.
} else {
http_send_status(500);
exit;
}
//-------------------------------------------------//
/**
* Sets & tests the API connection.
* #return bool true if the connection successful.
*/
function setConnection() {
try {
bc::configure(array(
'store_url' => BC_PATH,
'username' => BC_USER,
'api_key' => BC_PASS
));
} catch (Exception $e) {
return false;
}
return bc::getResource('/time') ? true : false; //Test Connection
}
/**
* Hard define the customer group & signup code associations here.
* #param string The code user used at signup.
* #return int The associated customergroup ID.
*/
function determineCustomerGroup($signupCode) {
switch ($signupCode) {
case 'test123':
return 1;
case 'codeIGaveToTheUser':
return 8;
default:
return 0;
}
}
So then you would do your customer group string comparisons directly in the serverside program. I'd recommend you rewrite your own BC API script as the one above in quality is really something along the lines of functional pseudo-code, but more so present to show the general idea. HTH
You would need to set up a server to listen for webhooks unless you wanted to do a cron job. We have some basic information on the developer portal, but I included more resources below. From there, you'd need to choose your server language of choice to listen for the webhooks once they been created, respond correctly (200 response if received), execute code based on this information, and then take action against the BC API.
So if you were looking for a code, you'd need to listen for the store/customer/created webhook, and have your code look for a custom field that contained the code. If it was present, then take action. Else, do nothing.
https://developer.github.com/webhooks/configuring/
http://coconut.co/how-to-create-webhooks
How do I receive Github Webhooks in Python

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

Categories