I'm new to Google Forms and Google Apps Script. I have ten Google Forms, and on submitting forms they populate a corresponding Google Sheet.
Now, here is what I want, on submitting form I want that information to also be stored in the cloud SQL database. What steps will accomplish that?
The normal data flow for a Google Form that is accepting responses and replicating them to a Spreadsheet looks like this:
You've got two opportunities for a Form Submission Trigger to replicate the form responses in your Cloud SQL database; you can either trigger from the Google Form Form submit event or the Google Sheets Form submit event.
Either way, you will have a script that gets called for every form submission, and an event object that contains the response values. Your trigger function should use the JDBC to connect to the database - that link includes an intro that walks you through the highlights.
Example
Say we have a form asking two questions, "Name" and "Age". That would result in 3 columns in our spreadsheet; "Timestamp" plus one for each question.
To match that, we have a Cloud SQL database set up with the same three columns.
A Google Sheets Form submit trigger function that wrote to a Cloud SQL database would look like this untested code:
// Replace the variables in this block with real values.
var address = 'database_IP_address';
var user = 'user_name';
var userPwd = 'user_password';
var db = 'database_name';
var dbUrl = 'jdbc:mysql://' + address + '/' + db;
// Receive form response and replicate to a row in SQL table
function handleFormSubmit( event ) {
var conn = Jdbc.getConnection(dbUrl, user, userPwd);
var stmt = conn.prepareStatement('INSERT INTO entries '
+ '(Timestamp, Name, Age) values (?, ?, ?)');
stmt.setString(1, event.namedValues['Timestamp']);
stmt.setString(2, event.namedValues['Name']);
stmt.setString(3, event.namedValues['Age']);
stmt.execute();
}
Posing an example of a solution I have working. I modified Mogsdad's script to use the most up to date parameters & connection function, and fixed the syntax errors.
//Event is automatially passed on form submission when executed as a trigger.
//It contains objects that the user submitted
function writeToCloudSQL(event) {
//Note we get subname from the 'Instance Connection Name' of the Overview tab in the CloudSQL portal
var subname = 'see image';
var user = 'user';
var userPwd = 'pwd';
var db = 'db_name';
var dbUrl = 'jdbc:google:mysql://' + subname + '/' + db;
var conn = Jdbc.getCloudSqlConnection(dbUrl, user, userPwd);
var stmt = conn.prepareStatement('INSERT INTO tbl_users (Timestamp, DL_ID, DOB, Last4SSN, HTMLGathered) values (NOW(), ?, ?, ?, \'No\');');
stmt.setString(1, event.namedValues['DL_ID']);
stmt.setString(2, event.namedValues['DOB']);
stmt.setString(3, event.namedValues['Last4SSN']);
stmt.execute();
}
Locating the subname:
Locating the subname
Read more about the Event parameter here:
https://developers.google.com/apps-script/guides/triggers/events
Read more about the connection string here:
https://developers.google.com/apps-script/reference/jdbc/jdbc#getCloudSqlConnection(String,String,String)
Also be sure you've allowed network access to google scripts
https://developers.google.com/apps-script/guides/jdbc?hl=en#using_jdbcgetconnectionurl
Zapier with their SQL Server and Google Sheets integrations can do this. I'm not affiliated.
https://zapier.com/apps/sql-server/integrations
If you don't want to roll your own solution, SeekWell lets you automatically send data from SQL to Sheets and can also sync changes from Sheets back to a database. Apps Script can do pieces of this, but I found it buggy and limited for our use case, so I built something else. There are both free and paid plans.
Disclaimer: I built this.
Related
I have written two very simple scripts to collect some data from a small team. Below is the entire code.
function recordProgress(){
var sps = SpreadsheetApp.getActiveSpreadsheet();
var shUser = sps.getSheetByName("User");
var shData = sps.getSheetByName("Data");
var lastrow = shData.getRange("I1").getValue();
shData.getRange("A" + lastrow).setValue(shData.getRange("A" + lastrow).getRow());
shData.getRange("B" + lastrow).setValue(shUser.getRange("D3").getValue());
shData.getRange("C" + lastrow).setValue(shUser.getRange("D5").getValue());
var str_a = shData.getRange("B" + lastrow).getDisplayValues();
var str_b = ("000" + shData.getRange("C" + lastrow).getDisplayValues()).slice(-3);
shData.getRange("D" + lastrow).setValue(str_a + "_" + str_b);
shData.getRange("E" + lastrow).setValue(shUser.getRange("D29").getValue());
shData.getRange("F" + lastrow).setValue(shUser.getRange("D27").getValue());
shData.getRange("G" + lastrow).setValue(shUser.getRange("D31").getValue());
shData.getRange("H" + lastrow).setValue(new Date()).setNumberFormat("dd-mmm-yyyy");
shUser.getRange("D3").clearContent();
shUser.getRange("D5").clearContent();
shUser.getRange("D27").clearContent();
shUser.getRange("D29").clearContent();
shUser.getRange("D31").clearContent();
}
function clearUser(){
var sps = SpreadsheetApp.getActiveSpreadsheet();
var shUser = sps.getSheetByName("User");
shUser.getRange("D3").clearContent();
shUser.getRange("D5").clearContent();
shUser.getRange("D27").clearContent();
shUser.getRange("D29").clearContent();
shUser.getRange("D31").clearContent();
}
As you can see the codes perform very simple steps of getting user inputs from the sheet User to the sheet Data with some minor formatting. I can execute the codes easily and it works as expected. I have deployed it as a web app to execute as me for the purpose of sharing.
The problem starts when I share the sheet with edit rights to anyone with the link. The other users are not able to run the scripts on their end (tested by me by trying it on incognito).
Previously I had some codes for showing some ui alerts which I have now deleted as I read that those might be requiring log-in for authorizing the code run.
As a test I have logged in from another gmail account and sure enough, there I am presented with an authorization prompt.
I can't enforce other users to sign in to their gmail accounts for this. Is there any way that scripts can be used without forcing other users to sign in? Maybe there is still some element in my code that triggers an authorization prompt?
One easy solution would be to create a form instead of sharing a spreadsheet for data input. Form responses automatically appear in a separate tab in the spreadsheet in a row-oriented fashion.
If necessary, you can customize a form with at a paid tool such as Formfacade.
Final Objective
The final objective is to have the ability to define custom validator for Google Forms item like requireTextMatchesPattern(pattern).
Use Case
This custom validator will be used for example to compare what the user enters in the form item field with more than one value. or at least to have a custom functionality to execute when the user enters not valid data in the field.
Example
I have 3 participants, I want to make a simple authentication mechanism to make sure that the targeted audiences are going to participate. I have a spreadsheet that contains 3 passwords. The first question in the Form will require the user to enter a password. If the password doesn't match with one of the stored passwords in the spreadsheet, then, a validation message will appear to the user.
Partially Solution
Based on this question we can make a simple validation using requireTextMatchesPattern validator or directly from UI. The problem is that this validator limits the compare values to one.
function validatePassword() {
// Create new custom form
var form = FormApp.create('New Form');
var ss = SpreadsheetApp.openById('SHEETID');
var password = ss.getSheetByName('SHEETNAME').getRange('A1').getValue();
// Create first question to check the password, it must be required so that the user does not have access to the rest
// of the form if failed to log in
var item = form.addTextItem().setRequired(true);
item.setTitle('TEST TITLE');
// Create validation for this question matching the password that we got from the sheet
var textValidation = FormApp.createTextValidation()
.setHelpText('You must enter the right password')
.requireTextMatchesPattern(password)
.build();
item.setValidation(textValidation);
}
What I am trying to do is to replace the .requireTextMatchesPattern(password) with a call to a custom validation function that does some validation process and then returns the type of TextValidationBuilder.
Research
I found this source code which defines an interface of TextValidationBuilder. I don't know if it is the key to accomplish the main objective.
Thanks!
What I can understand from your question is that for example, you have 3 passwords (words) in 3 cells (Ex: from A1 to A3). Hence, you want to use them as conditions for a form and the issue ahead of you for the moment is that you are only able to do it with only one password (word).
As you probably noticed, the requireTextMatchesPattern(pattern)'s argument is a pattern, therefore you can have a Regex structured as word1|word2|word3, which will verify if the 3 passwords are the correct ones. Your code will look like this now:
function validatePassword() {
// Create new custom form
var form = FormApp.create('New Form');
var ss = SpreadsheetApp.openById('your-sheet-id');
var passwords = ss.getSheetByName('Sheet1').getRange('A1:A3').getValues();
// Ex passwords: asd, 123, asd123
const conditions = passwords.map(element => `${element}`).join('|')
// Create first question to check the password, it must be required so that the user does not have access to the rest
// of the form if failed to log in
var item = form.addTextItem().setRequired(true);
item.setTitle('TEST TITLE');
// Create valid ation for this question matching the password that we got from the sheet
var textValidation = FormApp.createTextValidation()
.setHelpText('You must enter the right password')
.requireTextMatchesPattern(conditions)
.build();
item.setValidation(textValidation);
}
I was nodding enough to solve a problem I had, and while writing a question right here, I realized that I solved my problem hahaha.
I was looking for how to update the restrictions (validation) of a form based on a sheet of spreadsheets, which was updated with each form submission. Of course, I set it as a trigger when the spreadsheets change.
It is a bit complicated to explain, but basically the values recorded in a column are taken in other spreadsheet and "subtracted" from the "accepted" column, then these values are concatenated with the function (textjoin ("|",1,A:A)). Finally, this resulting data is entered into a variable ('allowed') which is entered into .requireTextMatchesPattern (allowed).
This is the simplest way I found for my problem:
Update the allowed list to avoid double voting or participation, and at the same time limit the participants with some known information (telephone number, identifier, last name, etc.)
I am sure that with Scripts there may be other solutions, but using spreadsheets seems easier and simpler to me.
Anyway, I leave the formula here in case it works for someone:
function test(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[2];
var allowed = sheet.getRange("B1").getValue();
var form = FormApp.openById('urlID');
var item = form.getItemById(388494367).asTextItem();
var validation = FormApp.createTextValidation()
.requireTextMatchesPattern(allowed)
.build();
item.setValidation(validation);
}
Objective & Implementation
I created a questionnair using Google Forms. I want to authenticate users using a simple one time password that should be entered in the first question.
The password is stored inside a spreadsheet that contains only one cell. https://prnt.sc/r8i8q1
The script below reads the content of this cell and create a custom validator against the value of the first item of the form.
function validatePassword() {
var passwordSpreadsheet = "[passwords_spreadsheet_id]";
var ss = SpreadsheetApp.openById(passwordSpreadsheet);
var passwordSheet = ss.getSheetByName("passwords");
var form = FormApp.getActiveForm();
var items = form.getItems();
var item = items[0];
if (item.getType() == 'TEXT') {
var textItem = item.asTextItem();
var namedRanges = passwordSheet.getNamedRanges();
if (namedRanges.length > 0) {
var range = namedRanges[0].getRange();
var values = range.getValues();
var currentPassword = values[0][0];
var textValidation = FormApp.createTextValidation()
.requireTextMatchesPattern(currentPassword)
.build();
textItem.setValidation(textValidation);
}
}
}
Current Situation
The above code is working as expected for one password, but, the problem is that I couldn't find a way to create a custom validator against range of values.
Questions
Is there any way to have this simple authentication mechanisim in Google Forms via Google Apps Script?
Is there a way to make this password a One Time Only password?
If (1.) and (2.) are not available, then, what is the best way to authenticate Google Forms?
Thank you in advance!
Solution
Using Apps Script
The following piece of code will make a password verification according to what you right on the Spreadsheet as a password. It has comments to explain what line of code does:
function validatePassword() {
// Create new custom form
var form = FormApp.create('New Form');
var ss = SpreadsheetApp.openById('SHEETID');
var password = ss.getSheetByName('SHEETNAME').getRange('A1').getValue();
// Create first question to check the password, it must be required so that the user does not have access to the rest
// of the form if failed to log in
var item = form.addTextItem().setRequired(true);
item.setTitle('TEST TITLE');
// Create validation for this question matching the password that we got from the sheet
var textValidation = FormApp.createTextValidation()
.setHelpText('You must enter the right password')
.requireTextMatchesPattern(password)
.build();
item.setValidation(textValidation);
}
Using the UI
The esieast way to validate forms with password is to use the custom functionalities of Google forms. In your case you should follow these steps:
In the first section of your form only place a required short text answer.
Go to the three dots in the lower right part of the question and select Response Validation
Then in the options change them to Regular expression, Matches and introduce your desired regular expression (it could just be a String) and the right error message.
Here is an example of this in action:
I hope this has helped you. Let me know if you specifically need to get the passwords from the Spreadsheet. Let me know if you need anything else or if you did not understood something. :)
I've created custom function in spreadsheet, which gets data from Fusion table.
function getData(){
var tableId = "********";
var sql = 'SELECT * FROM ' + tableId + ' LIMIT 100';
var response = FusionTables.Query.sql(sql);
Logger.log(response);
return response;
}
It works when I run the function in the Script editor, but when I call the function in a cell in the spreadsheet it returns:
Daily Limit for Unauthenticated Use Exceeded. Continued use requires signup.
Is there different approach to the data from spreadsheet and from script editor or am I doing something wrong?
Thank you for any idea!
Custom functions can not user services that can access users data. The docs have a list of what services can be used in custom functions.
https://developers.google.com/apps-script/guides/sheets/functions#using_apps_script_services
There is a work around for this by using the Execution API. The basic idea is you publish your script as an execution api and call it from your custom function. Check out a demo I put together on this at:
https://github.com/Spencer-Easton/Apps-Script-Authorized-Custom-Function
I wrote a custom function in Apps Script which constructs a query using the function arguments and calls BigQuery service (for which I enabled using an API key) supplying the query. But when I used the function in the spreadsheet, it always returned server error.
error: We're sorry, a server error occurred. Please wait a bit and try again.
Here is my code (it works when I run it in the debugger by supplying the variables manually):
function GetAge(first_name, last_name) {
var select_text = "SELECT first_name, last_name, age FROM Testing.FullNames WHERE ";
var filter_text = "first_name = '" + first_name + "' AND last_name= '" + last_name + "' ";
var group_text = "GROUP BY 1,2;";
var query_text = select_text + filter_text + group_text;
var query = {'query': query_text};
var response = BigQuery.Jobs.query('<My Project Id>', query);
var value = response.getRows()[0].getF()[2].getV();
return value;
}
The v2 API is enabled? And when first attempting to run, were you prompted to complete the OAuth flow?
Note - once you get past this initial error, you'll need to change your query parameters. The query call just takes a String as the 2nd parameter:
var response = BigQuery.Jobs.query('project_id', query_text);
The BigQuery services v2beta1 is limited to a subset of trusted testers at this time. If you instead using the v2 version (which takes a string query) it should work.
There's a tutorial about how to make calls to the BigQuery API from Google Apps Script here.