I use the below script with a google form which sends an email to different people based on the answer chosen on the form. The script works and sends an email to the correct person when I use it with a form where the user can only choose one option.
But now I have a form where a user can choose between 1 and 6 options for the selection of cases to match. For every option chosen, I need an email to be sent to the corresponding department. With switch command, I understand that it tests an expression against a list of cases and returns the corresponding value of the first matching case. What I need is for it to test against a list of cases and return ALL corresponding values and then email based on that. Sometimes that would be one email, sometimes that could be 3 emails to 3 different people, etc.
The question on the google form is a checkbox question so a user can choose any and all if it applies. Each option correlates with a different email in my script.
Currently, if the form is filled out and only one option is chosen (see screenshot question "announcement outlet"), the script runs and sends the email as it should. But if two or more options are selected, no email goes out. When I check the trigger notes, the error is:
Exception: Failed to send email: no recipient
at sendFormByEmail(Code:47:13)
Here is my current script which works when only one option can be chosen. I believe I need a different command other than switch, but don't know what. I looked into fallthrough, but don't think that would work for this either.
function sendFormByEmail(e)
{
// Remember to replace XYZ with your own email address
var named_values = e.namedValues
var teachername = named_values["Teacher Name"];
var info = named_values["Your message/announcement"];
var time = named_values["Please include time frame"];
var photos = named_values["Include photos with this form if applicable; you can also create the graphic for social media and include below"];
var announce = named_values["Choose announcement outlet"].toString();
var email;
// Optional but change the following variable
// to have a custom subject for Google Docs emails
// The variable e holds all the form values in an array.
// Loop through the array and append values to the body.
var message = "";
for(var field in e.namedValues) {
message += field + ' :: '
+ e.namedValues[field].toString() + "\n\n";
}
switch (announce) {
case "School Intercom Announcement":
var email = "person1#school.net";
break;
case "MHHS Website":
var email = "person2#school.net";
break;
case "MHHS Social Media (Instagram, Facebook, Twitter)":
var email = "person3#school.net"
break;
case "Week in Pics":
var email = "person4#school.net"
var body = "Week in Pics Request"
break;
case "Remind text message (goes to students - please specify in your message info if it is all grades or specific grades)":
var email = "person5#school.net"
var subject = "Remind Text Request"
break;
case "Phone call home":
var email = "person6#school.net"
break;
}
// This is the MailApp service of Google Apps Script
// that sends the email. You can also use GmailApp here.
MailApp.sendEmail(email, subject, message);
}
When multiple options are chosen, the data in the cell reads "Option 2, Option 4" and all my listed cases in the script above are for only "Option 2" or "Option 4", etc. Is there a way make this happen without having to make a case for every possible combination of the 6 choices that could potentially happen?
One way to fix the code
Remove .toString() from var announce = named_values["Choose announcement outlet"].toString();. Rationale: There is no need to convert an Array into a string for this case.
Instead of switch use if's, more specifically, add one if for each case clause, using an expression to test if the case label is included in announce. Example:
Replace
case "School Intercom Announcement":
by
if(announce.includes("School Intercom Announcement")){
// put here the case clause, don't include break;
// Add the following line before the closing `}` of the if statement
MailApp.sendEmail(email, subject, message);
}
Rationale: It's easier to implement the required control logic using if and Array.prototype.includes rather than doing it using switch.
Related
I'm creating a chatbot for fun and want to implement something to collect the user's name, but only if he says something like "my name is ..." or close to that; the intention of giving the name would come from the user, the bot won't ask for it, maybe only suggest it. Kind of like in Google Assistant, I think. So, it could be given at any time the user wants.
My idea is:
1st create an intent with different ways the user would tell his name to the bot (like in the example above).
2nd use slots and, if the intent is detected, save it as a variable. So far, I've managed.
3rd is the part that I'm stuck in, since it's only an idea and I don't know how I'd do it. Before saving the whole text as a variable, I'd like to delete the part that's included in the intent (my name is) and save only the rest in the variable. So, for example, the user says "my name is XXX"; the command deletes the "my name is" part and saves only "XXX" in the $name intent.
I don't know if this'd be possible, since I don't know coding. I used some special syntax before, like to capitalize the first letter of some other variable, but I don't really know how to use the JSON editor.
Is my idea viable? I don't know how I'd delete the intent corresponding portion and save only the remaining part as the intent. Dunno what would be the command for that, nor where I'd write it.
You can suggest something else if you have an idea.
Last thing, I'm created the skill in portuguese, so there's no access to the #sys-person entity.
Thanks for reading.
I use portuguese skills and face the same issue. I see two solutions, althougt they are not perfect:
Using intents:
When the intent is identified, the bot asks the name again, telling the user that he should only say his name, e.g without "my name is", then store the whole input on a context variable, using:
<? input.text ?>
For embedding such logic inside slots, you probaly will need to use digressions.
But, this is boring to the user.
Using entities:
Entities identification carries along they start and end position in the input text, but intents not. With this, is possible to slice the input, cutting the entity:
<? input.text.substring(entities.name.location[1], input.text.length()) ?>
Entites would be "My name is", "People call me", "I'm called", "I'm baptized", "I was baptized".
So, "Hello, my name is Gustavo", would be cut after "my name is" ending, resulting in "Gustavo". Additional input in the beggining is ignored, but problems arrise with additional input after the name. Also, you need to define more "my name is" like entities, likely all possibilities, than would need if using intents, cause even with fuzzy matching, entities identification doesn't take in account synonymus and similar meaning words.
https://cloud.ibm.com/docs/assistant?topic=assistant-dialog-methods#dialog-methods-strings-substring
https://cloud.ibm.com/docs/assistant?topic=assistant-expression-language#expression-language-access-entity
I try to display a message that contains the number inserted in the question, for example when user inserts a number of 12 digits I want to display that 12 digits and a text.
Until now:
I created an Entity with pattern (/d{12}) named #ticket_number
An Intent named #myTicket that has as example #ticket_number
Dialog that triggers when #myTicket | #ticket_number and has on context TicketNumer "<?#ticket_number.literal?>" and display a message as follows "Do you want to get info for ticket $ticketnumber ?".
The problem is that when I try it the Intent result is irrelevant, the message looks ok but I need to match the Intent. What could I do?
Can you share a picture of your node? There is no need that you match the intent; as indicating the ticket number alone, should not be an intent itself, but the entity. I'd remove the #myTicket | part of the node. And the condition should not include an OR; otherwise, if #myTicket triggers the node, and there is no ticket number, the response would fail.
As mentioned in the IBM documentation
it is not yet possible to use pattern entity in Intents.
Currently, you can only directly reference synonym entities that you
define (pattern values are ignored). You cannot use system entities.
Some Background:
I use Lists a lot for a Google Action with a NodeJS fulfillment backend. The Action is primarily Voice-based. The reason for using List is that I can encode information in List's key and use it later to make a decision. Another reason is that Google Assistant will try to fuzzy match the user's input with the Title of the List's items to find the closest matched option. This is where thing's get a bit hard for me. Consider the following example:
{
JSON.stringify(SOME_OBJECT): {
title: 'Yes'
},
JSON.stringify(ANOTHER_OBJECT): {
title: 'No'
}
}
Now if I say Yes / No, I can get the user's choice and do something with information stored as stringified JSON in the choice's Key.
But, the users may say Sure or Yup or OK as they basically mean the same thing as saying Yes. But as those words don't match Yes, Google Assistant will ignore the "Yes" option. But all of these words belong to the smalltalk.confirmation.yes built-in intent. So, if I could use this intent instead of hardcoding the string Yes then I would be able to capture all of the inputs that mean Yes.
I know I could do this with a Synonyms list or Confirmation intent. But they also have some problems.
Using Synonyms would require me finding every word which is similar. Besides, I would also need to localize these synonyms to all the supported language.
With Confirmation intent, I won't be able to show some information to the user before asking them to choose an option. Besides, it also doesn't support encoding the options as I can do in List's key.
So, List is a good choice for me in this case.
So, is there any way to leverage the built-in intents for this purpose? What do you do in this situation?
I want to build kind of an automatic system to update some race results for a championship. I have an automated spreadsheet were all the results are shown but it takes me a lot to update all of them so I was wondering if it would be possible to make a form in order to update them more easily.
In the form I will enter the driver name and the number o points he won on a race. The championship has 4 races each month so yea, my question is if you guys know a way to update an existing data (stored in a spreadsheet) using a form. Lets say that in the first race, the driver 'X' won 10 points. I will insert this data in a form and then call it from the spreadsheet to show it up, that's right. The problem comes when I want to update the second race results and so on. If the driver 'X' gets on the second race 12 points, is there a way to update the previous 10 points of that driver and put 22 points instead? Or can I add the second race result to the first one automatically? I mean, if I insert on the form the second race results can it look for the driver 'X' entry and add this points to the ones that it previously had. Dunno if it's possible or not.
Maybe I can do it in another way. Any help will be much appreciated!
Thanks.
Maybe I missed something in your question but I don't really understand Harold's answer...
Here is a code that does strictly what you asked for, it counts the total cumulative value of 4 numbers entered in a form and shows it on a Spreadsheet.
I called the 4 questions "race number 1", "race number 2" ... and the result comes on row 2 so you can setup headers.
I striped out any non numeric character so you can type responses more freely, only numbers will be retained.
form here and SS here (raw results in sheet1 and count in Sheet2)
script goes in spreadsheet and is triggered by an onFormSubmit trigger.
function onFormSubmit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var responses = []
responses[0] = Number(e.namedValues['race number 1'].toString().replace(/\D/g,''));
responses[1] = Number(e.namedValues['race number 2'].toString().replace(/\D/g,''));
responses[2] = Number(e.namedValues['race number 3'].toString().replace(/\D/g,''));
responses[3] = Number(e.namedValues['race number 4'].toString().replace(/\D/g,''));
var totals = sh.getRange(2,1,1,responses.length).getValues();
for(var n in responses){
totals[0][n]+=responses[n];
}
sh.getRange(2,1,1,responses.length).setValues(totals);
}
edit : I changed the code to allow you to change easily the number of responses... range will update automatically.
EDIT 2 : a version that accepts empty responses using an "if" condition on result:
function onFormSubmit(e) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet2');
var responses = []
responses[0] = Number((e.namedValues['race number 1']==null ? 0 :e.namedValues['race number 1']).toString().replace(/\D/g,''));
responses[1] = Number((e.namedValues['race number 2']==null ? 0 :e.namedValues['race number 2']).toString().replace(/\D/g,''));
responses[2] = Number((e.namedValues['race number 3']==null ? 0 :e.namedValues['race number 3']).toString().replace(/\D/g,''));
responses[3] = Number((e.namedValues['race number 4']==null ? 0 :e.namedValues['race number 4']).toString().replace(/\D/g,''));
var totals = sh.getRange(2,1,1,responses.length).getValues();
for(var n in responses){
totals[0][n]+=responses[n];
}
sh.getRange(2,1,1,responses.length).setValues(totals);
}
I believe you can found everything you want here.
It's a form url, when you answer this form you'll have the url of the spreadsheet where the data are stored. One of the information stored is the url to modify your response, if you follow the link it will open the form again and update the spreadsheet in consequence. the code to do this trick is in the second sheet of the spreadsheet.
It's a google apps script code that need to be associated within the form and triggered with an onFormSubmit trigger.
It may be too late now. I believe we need a few things (I have not tried it)
A unique key to map each submitted response, such as User's ID or email.
Two Google Forms:
a. To request the unique key
b. To retrieve relevant data with that unique key
Create a pre-filled URL (See http://www.cagrimmett.com/til/2016/07/07/autofill-google-forms.html)
Open the URL from your form (See Google Apps Script to open a URL)
I am a novice at programming. I have setup a google spreadsheet that will send a weekly email reminder to those who have upcoming assignments. The spreadsheet automatically pulls the 4 email addresses of those who are assigned each week and places them in A1, B1, A2, B2 (i.e. A1:B2). I want the script to find the 4 email addresses (that change each week) and send the same email to all four of them.
When I hardcode the cc recipients into the MailApp line, it works fine. But because the recipients list changes weekly I want to reference the cells A1:B2 instead. When I try to do that, I get an Invalid Email error. The debugger shows that var ccrecipients is picking up the right cells, but I think the problem is that it returns it as an array instead of as a string. That's as far as I'm able to reason through it.
A few snippets of my code:
var ccrecipients = sheet.getRange('A1:B2').getValues();
MailApp.sendEmail(recipients, subject, "", {htmlBody: message, cc: ccrecipients});
Thanks in advance for your help. I've relied heavily on these forums to put together the code that I have. I've always been able to find an answer, until this one. Thanks!
Your observation is almost correct, sheet.getRange('A1:B2').getValues(); return an array of arrays, something like [[A1,B1],[A2,B2]] A & B representing the values in the cells to simplify my example.
And what you need in the end is a "flat" string with all the needed recipients separated by commas "A1,B1,A2,B2".
The simplest way to do that is to flatten the whole thing,that will create a string with comma separations and that's exactly what we need.
sheet.getRange('A1:B2').getValues().toString();
That should do the trick ;-)