So I have a single function that I want to be made available for ALL of my Google Sheets.
It is very simple. Simple enough that I'll post it here in it's entirety.
function swapMonthAndDay() {
// The code below will swap the month and day for any Date objects in the selected cells.
var range = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getActiveRange()
var values = range.getValues();
values.forEach(function(row, y){
row.forEach(function(value, x){
if (value instanceof Date){
var month = value.getMonth() + 1,
day = value.getDate();
value.setMonth(day - 1);
value.setDate(month);
}
});
});
range.setValues(values)
}
I was able to get the result that I want via the "test as add-on..." from the run menu. But this is temporary as the term "test" implies. So, this leads me to believe that I need to publish this as a Sheets add-on. But, I really don't want to publish this to the web store. Too many steps, authentications, certificates etc to get there.
Okay I've finally figured it out.
Turns out, it has nothing to do with the Chrome Web Store. There is the GSuite Marketplace and the old and depreciating Chrome Web store. Currently, the deploy as add-on button links through to the Chrome Web Store deployment flow.
It all relies in the Cloud Project settings.
https://developers.google.com/gsuite/add-ons/how-tos/publish-addons
https://developers.google.com/gsuite/add-ons/how-tos/publish-for-domains
It needs to be accessed via Resources > Cloud Platform project..., within the menu when editing the script.
Related
Update: I'm sorry I didn't post the code originally. In my newbie-ness, I didn't think it honestly mattered, but I've been around long enough to know better. See below:
function openForm(e)
{
populateQuestions();
}
function populateQuestions() {
var form = FormApp.getActiveForm();
var googleSheetsQuestions = getQuestionValues();
var itemsArray = form.getItems();
itemsArray.forEach(function(item){
googleSheetsQuestions[0].forEach(function(header_value, header_index) {
if(header_value == item.getTitle())
{
var choiceArray = [];
for(j = 1; j < googleSheetsQuestions.length; j++)
{
(googleSheetsQuestions[j][header_index] != '') ? choiceArray.push(googleSheetsQuestions[j][header_index]) : null;
}
item.asCheckboxItem().setChoiceValues(choiceArray);
// If using Dropdown Questions use line below instead of line above.
//item.asListItem().setChoiceValues(choiceArray);
}
});
});
}
function getQuestionValues() {
var ss= SpreadsheetApp.openById('1QeckPxMYSYGMkZ-QggY76u03N1qBKBGL2UEMcUvu7sM');
var questionSheet = ss.getSheetByName('Sheet5');
var returnData = questionSheet.getDataRange().getValues();
return returnData;
}
After months of work, I finally have cracked the script to getting what I wanted. Now I need to use this script over and over in multiple Google Forms (forms, for short). By copy and pasting the Form within Drive, I carry the App Script with each copy, but not the trigger or the authorization needed to run it. I need assistance in learning how I can make this process quicker.
I have very limited knowledge of add-ons and things, but I’m willing to learn. I do not necessarily need this to be a public access Script, just want to make my life less repetitious.
I would also like to make it so after installing the add-on, users can just pick the Sheet/Tab through a UI and not editing the code specifically, but that's a different story.
For the moment, You can create an installable trigger so you don't have to add the trigger manually anymore. You should use this:
function createFormOpenTrigger() {
const gf = FormApp.getActiveForm()
ScriptApp.newTrigger('populateQuestions')
.forForm(gf)
.onOpen()
.create();
}
Instead of:
function openForm(e)
{
populateQuestions();
}
As for the credentials, deploying the script as an add-on I will help you with the OAuth part. However, I was unable to test it today due to propagation. I’m still getting a message that the Apps Script is not bound to the GCP project. I will try my luck tomorrow, in the meantime you can read the following documentation about it. Here.
Sorry, I took some time to post the answer since you mentioned that you didn't know how to create an add-on, so I tried to add step by step information.
Update:
You can add this part at the top of the code, this will create a menu that will allow you to run the script for the first time in the Google Form after we make it an Add-on.
function onOpen() {
//you can change the names of the menu, just not change
// this part "createFormOpenTrigger"
FormApp.getUi()
.createMenu('formTest')
.addItem('Run Apps Script', 'createFormOpenTrigger')
.addToUi();
}
If you do not have a GCP project, you can created one by following the steps here, or following the steps:
Open the Google API Console projects list.
Click Create Project.
Fill out the project information for your add-on.
Click Create.
Copy the Project number, and add it to the Apps Script to switch to a different standard Cloud project.
Deploy the Apps Script as an Add-on.
Remember the version number you will need later on. Access to the Google Cloud Project link with the Apps Script.
Setup the OAuth consent screen, select internal to make sure that only you have access to it (and users of your Workspace if you have one)
We will need to the Add-on listing, so we will need to add "Google Workspace Marketplace SDK" in the Library.
Add the App configuration information, add all the require information there, the only recommendation I can provide you are these 2:
Note: this can be found under "Project Settings"
Lastly, fill out the information under "Store listing".
Note: You can add dummy information if only you are going to use the add-on.
Once all that is done, use the "App URL" link to install the add-on. When you open a Google Form it will show up under the Add-on list:
And you will see the option to Run the Apps Script, thanks to the last part of the code we added.
Click on it, and it might ask you for permission the very first time you use it. But it will not request it again even if you add it to a new form.
I am running Google's Analytics Add-on for Google Sheets, for Chrome, to pull in analytics for several properties. This runs once a day and works very well. Two data points that I pull are the approximate latitude and longitude of the visitor.
Prior to using the add-on I used the older "magic script" written by Nick Mihailovski however this doesnt work any longer so we're advised to the add-on.
What I would like to do is extend the add-on such that after it populates the sheets I would like to add a column that shows the reverse geocode of the coordinates.
I have this function that I used to modify Nick's script:
function reverse_geocode(lat,lng) {
Utilities.sleep(1500);
var response = Maps.newGeocoder().reverseGeocode(lat,lng);
for (var i = 0; i < response.results.length; i++) {
var result = response.results[i];
Logger.log('%s: %s, %s', result.formatted_address, result.geometry.location.lat,
result.geometry.location.lng);
return result.formatted_address;
}
}
I was able to modify Nick's code so that as each row was written to the sheet I could add the cell with the address from the lat/long. Now I must do this manually. I'd like to get it back to working automatically.
Is it possible to do this with an add-on for which I can not see nor access the code? I have tried to add this function to a file in my sheet called "geocode.gs" and tried to call it via a trigger but it does nothing. No visible error that I can see, nothing in the execution log either. Is there another way to automate this with a closed-source add-on?
You can do this by creating a bound script in the Sheet you are running the report on.
If you've already tried and it doesn't work it may just be due to an error in your code.
I don't think there are add-ons that can do what you want specifically, also because doing it with the code in Google Apps Script (since you already have a function that does its job) is the quickest and most effective way.
I have created a Google Sheets spreadsheet and some scripts to help me track the attendance of members at a gym.
My project has the following scripts associated:
- a trigger that sends periodic events based on some checks I do and also updates some cells. This works fine I scheduled it to run daily and it seems to work properly even if my PC isn't on :D
- I have also added a onEdit function that's supposed to remind the user that he/she has modified a cell and let him know that he can undo in order to revert the changes. This is the function:
function onEdit(e) {
var ui = SpreadsheetApp.getUi();
var cell = e.range;
var actual_cell = e.source.getActiveRange().getA1Notation();
var row = cell.getRow();
var column = cell.getColumn();
var old_content = e.oldValue;
ui.alert('Modificare celula' + ' ' + actual_cell + '.' + ' UNDO to rever the changes')
}
This, however, does not work on the mobile version: Sheets for Android.
Is there any way I can make my app fully functional on the mobile version as well? I want to have that warning message that tells the user a cell was modified on the mobile version as well. How could I do that?
Also another question regarding the trigger that sends periodic emails.
I have used a Time Based daily trigger that runs some function. Do I have to be logged on my account at all times on my PC(or any other device) in order for this script to run periodically? Or is it on the cloud and it will run even if I'm not logged on any device?
To be able to trigger the script bound to the spreadsheet from a mobile App (Android or iOS), you have to deploy the script as a API executable (Publish -> Deploy as API executable from menu bar in script) [1].
As stated by a Googler in this comment [2], since last year you can't display any element from the Ui class [3] in mobile versions.
About your other question: It doesn't matter if your computer is on, the script/trigger will run either way.
[1] https://developers.google.com/apps-script/api/quickstart/target-script
[2] https://b.corp.google.com/issues/36763692#comment6
[3] https://developers.google.com/apps-script/reference/base/ui
I have written a script that, when published as a web app, will count the number of emails in my inbox, along with the date of the earliest sent message, and output as a .csv:
function doGet() {
// get all threads in inbox
var threads = GmailApp.getInboxThreads();
var dataLength = threads.length
var earliestMessage = new Date();
for (var y = 0; y < dataLength; y++) {
var message = threads[y]
//sets last message date in thread y
var msgDate = message.getLastMessageDate();
//checks last message date to current value of earliestMessage, updates if necessary
if(msgDate.valueOf() < earliestMessage.valueOf()) {
earliestMessage = msgDate
}
}
//save to external files
return ContentService.createTextOutput(dataLength + '\n' + Utilities.formatDate(earliestMessage, 'GMT', 'dd/MM/yyyy')).downloadAsFile('emailData.csv');
}
This works fine, but I would now like this script to return the same information from a shared mailbox that I have access to. Since this is a shared mailbox, I do not have the option of just accessing its Google Drive and saving the script there (please let me know if I am mistaken in this!), so the script will only ever return information about my own account.
I have tried searching for similar issues, but the closest I have found are the following, neither of which seem to have clear solutions:
How do I process emails in a shared mailbox using Google Scripts
Google Help Forum -
How do I run a Google Script in a shared mailbox
Will the script need to be rewritten (and if so, how?), or is there a way of directing the GmailApp class to the shared mailbox instead of my own?
So I believe that your shared mailbox should be setup as just a plain old google user. If so then there is no reason why you can't setup a google drive for them.
The second part of this problem is what you want to create with this script, If you just want the information from your shared mailbox you can either:
Place the script in the shared user's drive and run it as them
Leave the script in your drive, set it to run as User accessing the web app(in Publish -> Deploy as web app...) and when you run it, make sure that you are logged in as the shared user.
Now if you wanted to combine the two feeds of information, that's a different story, I don't believe that you can, in the same script, run as two different users. I have also tested Libraries and they seem to use the same session user, so as far as I can see you really only have one option. Creating a webapp script to extract the information and run it as your shared user, and return it as json/xml (or even CSV if you are just appending it), then use a HTTP call(using URLFetchApp) in your existing script to retrieve that information and process it and do whatever you need with it (running as you). This will be a slow option, but at least it will do what you want.
Don't know if you found a solution to your issue.
Nor do I know if this would solve your problem, but a feature request has been placed on Google Cloud Community and on Google Issue Tracker to enable access to Gmail Apps Script add-ons from delegated mailboxes.
The more upvotes, the more chances Google seriously looks at this issue ;).
I have a gradebook web app script which looks at a logged in student's email address, finds the email in the gradebook, and then displays the student's grades based on the column the email is in. The only problem is this only works if the spreadhseet is made public. How can I keep the spreadhseet private and still make this script work? I know that if I choose "Anyone with the link" it is unlikely someone will find the spreadsheet, but I'd prefer it to stay private. In addition, from the "Deploy as Web App" interface, the app must be executed as the user, not myself. Any ideas?
var ss = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheet/ccc?key=ID');
var sh1 = ss.getSheetByName('StudentGrades');
var logsheet = ss.getSheetByName('logsheet');
var data = sh1.getDataRange().getValues();
var user = Session.getEffectiveUser()
Logger.log(user)
function doGet() {
var app = UiApp.createApplication();
if(!getcol(user)){
var warn = app.createTextBox().setWidth('500').setValue("Your results are not available or you don't have permission to view these data");// if user is not in the list, warning + return
app.add(warn)
return app
}
var grid = app.createGrid(data.length, 2).setWidth('300px').setBorderWidth(1).setCellPadding(0).setCellSpacing(0).setStyleAttribute('borderCollapse','collapse').setId('grid');
var text = app.createLabel(user).setWidth('300px');
var col = getcol(user)
grid.setWidget(0,1,text).setText(0, 0, 'Results for');
grid.setStyleAttribute('textAlign','center')
for(n=1;n<data.length;++n){
grid.setText(n, 0, string(data[n][0]));
grid.setText(n, 1, string(data[n][col]));
grid.setStyleAttributes(n-1, 0, {'fontWeight':'bold','background':'#fff','border':'1px solid #000'});//left column css attributes
grid.setStyleAttributes(n-1, 1, {'fontWeight':'bold','background':'#fff','border':'1px solid #000'});//right column css attributes
}
app.add(grid);
return app
}
function string(value){
Logger.log(typeof(value))
if (typeof(value)=='string'){return value};// if string then don't do anything
if (typeof(value)=='number'){return Utilities.formatString('%.1f / 20',value)};// if number ther format with 1 decimal
if (typeof(value)=='object'){return Utilities.formatDate(value, Session.getTimeZone(), "MM-dd")};//object >> date in this case, format month/day
return 'error'
}
function getcol(mail){
if(data[0].toString().indexOf(mail.toString())!=-1){
for(zz=1;zz<data[0].length;++zz){
if(data[0][zz] == mail){var colindex=zz;break}
}
return colindex
}
return false
}
As I was suggesting in another of your posts you could setup a 'manual login' that will allow you to keep the spreadsheet private while running the webapp as yourself.
That implies that you'll have to create a list of user/password keys/values and that each student will have to enter his user name and password (you'll have to send them that information by email along with the link to the webapp) in a front end login screen before they gain access to the results display part.
I guess the best place to hold that list would be in script properties in the form of key:value.
If you need more that this to implement that solution (and if you think you'll go that way) feel free to ask.
EDIT following your comment :
Ok, I understand it can be unpleasant :)
Just wondering, wouldn't it be easier to store these data in the script itself in a scriptDB that you could automatically update with the spreadsheet values ?
The update would have to be executed by you (or by a timer trigger you create so that it runs on your account) and so the spreadsheet would remain private.
The students could then access the webapp with their accounts without accessing the spreadsheet.
From that service accessed as the user, call through urlget another apps script service published as yourself. That one does the ss-related work.
Might get timeouts on the service call though.
Why not setup a google group for each of your classes? Then add the google group to the SHARE permissions for the appropriate spreadsheet(s). This would have the added benefit of allowing you to easily send emails to your various classes via the appropriate google group. Under this scenario, your grade spreadsheets remain private and are only accessible by you and those email addresses listed in your google group.
I am a domain admin running the Google Apps for Education suite with many similar applications in place. You don't mention if you have administration rights for the domain or not, but I assume you do?
I keep all spreadsheets private and run the web apps as myself (giving the script the necessary access) which I think is precisely what you are aiming for?
I think your main issue stems from using .getEffectiveUser() which isn't providing you with what you need. I use Session.getActiveUser.getEmail() and then iterate through student objects (this is essentially just a sheet of student details returned as objects using the standard getRowsData function) to match the email value (if not found user = unknown).
This works perfectly for me with a student base of over 2000, all the data is private and the apps respond quickly serving personalised information.
If you'd like to see an example app please let me know, it's a little large to post here.