Unable to restrict editing rights with timestamp on google sheet - google-sheets-api

I am creating a google sheet where there are multiple users who need to enter the data at different time intervals. Every time they enter the data, there is an automatic timestamp to record when the data has been entered with date & time. I have used the below script for the timestamp. I have protected the timestamp range/column so that no one can make changes to it later. Now with this condition, when users are updating their tasks, the timestamp is not getting updated automatically. But as I remove the protection, the timestamp is working well.
Can someone please help how can I have timestamp & also ensure no one is able to edit it.
Script for timestampfunction
onEdit(event) {
  var timezone = "IST";
  var timestamp_format = "MM-dd-yyyy HH:mm"; // Timestamp Format
  var date = Utilities.formatDate(new Date(), timezone, timestamp_format);
  var range = event.range; // this will be the last cell edited
  var column = range.getColumn(); // use this to check the cell edited was in a relevant column
  var rangeValue = range.getValue(); // use this to check something was actually entered into the last cell edited
  var newRange  = range.offset(0,1); // this is one cell to the right of the last cell edited
  if ((column == 10|| column == 12|| column == 16|| column == 18|| column == 20|| column == 22|| column == 26|| column == 30|| column == 32|| column == 36|| column == 40|| column == 44) && rangeValue) { //if the last cell edited was in a relevant column, and there is a value in it
    newRange.setValue(date);
  } else if ((column == 10|| column == 12|| column == 16|| column == 18|| column == 20|| column == 22|| column == 26|| column == 30|| column == 32|| column == 36|| column == 40|| column == 44) && !rangeValue) { // there was no question or no response, so make sure there is no timestamp
    newRange.setValue("");
  }
}
Glimpse of the sheet:
enter image description here

You need to replace the simple onEdit trigger throuhg an installable one
Why?
The simple trigger executes on behalf of the user who edits the sheet
If the function running on trigger is meant to set values in a range that the user does not have permission to edit, the execution will fail
An installable trigger will run on behalf of the user who created the trigger
If the you as the main editor who is excluded from protections create the trigger, it will always run on your behalf, no matter which user performed an edit that fired the trigger

Related

Uploading Multiple Records Via Excel Upload in Database Using sprinboot in java

How to insert multiple records into a database using an Excel upload?
When working on a project there was a requirement for me to upload an Excel file, let's say with the number of records up to 50000 per sheet. But while uploading the Excel file there were certain validations that need to be done and if any of the records fail then the error message needs to be displayed for that respective row of the Excel on the page and none of the records needs to be be saved inside the database table.
Validations could be in any sense, for example:
The age of all employees needs to be >= 18 and <= 60
The age column should not have a non-numeric value
There were certain columns that were mandatory, if the value is not filled in that column of the Excel then the user should be notified of the error. Like the name of the Employee should not be null.
Checking that a date value is in the format of DD/MM/YYYY
and so on
When uploading this Excel file, if the preceding validations are successfully satisfied then the data needs to be saved in the database against a specific batch id that is basically a unique number to identify the records inserted within that specific batch and when they all were inserted. Also, special attention needs to be provided to the Excel column headings that we provided to the user for data entry purposes. Let's say there are 10 columns in the Excel and if the user changes the order of the Excel column heading or if they delete some column or change the name of the column, this validation also needs to be performed against the uploaded file.
For performing this validation we have made use of an ExcelStructure table that will have Excel fields/columns stored with their respective sizes, types, parameters, and whether these fields/columns are mandatory or not. The following is the structure of the ExcelStructure table. Remember we are using Entity Framework with the code-first approach. But just for demo/looking purposes, our SQL table will have the following query.
Somebody help if I upload excel in spring boot, the column name in excel that match with the database column name then save the value from the database in spring boot
public static List<Hday> excelToDatabase(InputStream inputStream) {
try {
//Workbook workbook = new XSSFWorkbook(inputStream);
Workbook workbook =WorkbookFactory.create(inputStream);
System.out.println("workbook : " + workbook);
Sheet sheet = workbook.getSheetAt(0);
System.out.println("sheet : " + sheet);
Iterator<Row> rows = sheet.iterator();
List<Hday> hdayList = new ArrayList<Hday>();
int rowNumber = 0;
while (rows.hasNext()) {
Row currentRow = rows.next();
// skip header
if (rowNumber == 0) {
rowNumber++;
continue;
}
Iterator<Cell> cellsInRow = currentRow.iterator();
Hday refhd = new Hday();
int idx = 0;
while (cellsInRow.hasNext()) {
Cell cells = cellsInRow.next();
switch (idx) {
case 0:
if (cells .getCellType() == CellType.STRING) {
String inId = cells .getStringCellValue();
refhd.Id(Long.parseLong(inId));
} else {
refhd.setinId((long) cells .getNumericCellValue());
}
break;
case 1:
if (cells .getCellType() == CellType.NUMERIC) {
long week = (long) cells .getNumericCellValue();
refhd.setWeek(Long.toString(week));
} else {
refhd.setWeek(cells .getStringCellValue());
}
break;
case 2:
if (cells .getCellType() == CellType.STRING) {
String temphdDate = cells .getStringCellValue();
Date hdayDate;
try {
hdayDate = new SimpleDateFormat("dd/MM/yyyy").parse(temphdDate);
} catch (ParseException e) {
throw new BadRequestException(e.toString());
}
refhd.setHolidayDate(hdayDate);
} else {
refhd.setHolidayDate(cells .getDateCellValue());
}
break;
case 3:
if (cells .getCellType() == CellType.STRING) {
refhd.setHolidayName(cells .getStringCellValue());
} else {
throw new BadRequestException("Cell Value Is Not a String");
}
break;
default:
break;
}
idx++;
}
hdayList.add(refhd);
}
workbook.close();
return hdayList;
} catch (IOException e) {
throw new RuntimeException("fail to parse Excel file: " + e.getMessage());
}
}
Whatever I use as the Excel column name -> same code order
Alternatively I use a wrong order col name but that value would correct but database would store wrong order that is the issue
Excel
id week date HolidayName
1 1st week 5/10/2021
SAME order save to database
That's the output in the case in the database
id week date HolidayName
1 1stweek 5/10/2021 null
Again my input
id date
1 6/10/2021
I am getting an error like Cell Value Is Not a String
So I want that output in the case in database
id week date HolidayName
1 null 6/10/2021 null
What I want match with excel col name match database col name

How do I use variables to avoid having to create a set of these for all of the columns in my sheet?

I'm trying to get my sheet to automatically recalculate a set of dates within a schedule, in both directions, when a cell is changed.
The code works fine, but I need to add a bunch more columns and I'd really rather not copy/paste/find/replace a load more times. I'm fairly certain I can do this with variables (just looking up the column identifier and feeding that into the code somehow), but I don't know-how.
functJon onEdJt(e) {
var sh = e.source.getActJveSheet();
Jf(sh.getName() === 'Date Calculator' && e.range.getA1NotatJon() === 'C9'
)
{
sh.getRange("C10").setFormula("=WORKDAY(C9,+$C$3)");
sh.getRange("C11").setFormula("=WORKDAY(C10,+10)");
sh.getRange("C12").setFormula("=WORKDAY(C11,+$C$4)");
sh.getRange("C13").setFormula("=WORKDAY(C12,+$C$3)");
sh.getRange("C14").setFormula("=WORKDAY(C13,+10)");
sh.getRange("C15").setFormula("=WORKDAY(C14,+1)");
sh.getRange("C16").setFormula("=WORKDAY(C15,+$C$5)");
}
else Jf (sh.getName() === 'Date Calculator' && e.range.getA1NotatJon()
=== 'C10' )
{
sh.getRange("C9").setFormula("=WORKDAY(C10,-$C$3)");
sh.getRange("C11").setFormula("=WORKDAY(C10,+10)");
sh.getRange("C12").setFormula("=WORKDAY(C11,+$C$4)");
sh.getRange("C13").setFormula("=WORKDAY(C12,+$C$3)");
sh.getRange("C14").setFormula("=WORKDAY(C13,+10)");
sh.getRange("C15").setFormula("=WORKDAY(C14,+1)");
sh.getRange("C16").setFormula("=WORKDAY(C15,+$C$5)");
Ideally the code should then just "work" for any number of columns in the sheet, so I don't need to add more code if I add more columns.
Update
Here's an example of what I'm trying (but it's not working) - attempting to check that the active cell is in row 9 of a specific column before then running the "set.Formula" functions:
function onEdit(e) {
var sh = e.source.getActiveSheet();
var col = e.source.getActiveSheet().getRange().getColumn();
var row = e.source.getActiveSheet().getRange().getRow();
if(sh.getName() === 'Date Calculator' && e.getRange('9',col) )
Event Objects
Even though the code was written as onEdit(e), you didn't take advantage of the Event Objects.
In this answer, the code returns the new value of the edited cell and also the range. The range is then used to work out the row, column and sheet name and these is used for validation as well as for building the ranges and the setFormula
Variables
The code includes variables for the valid range of columns that can be used for data entry (Column C to Column H), and respective input rows (rows 9 and 10). These are expressed as values, but they could just as easily be written into the spreadsheet as assumptions and the values obtained in the code by using getValue.
The absolute cell references used in the setFormula are partly variable (column reference) and part hard-coded (the respective rows-3,4 and 5). If desired, the rows could be variable as well.
Efficiency
There is just one if statement containing one version of the code to build setFormula.
This is achieved by designing the if statement:
1. if the sheet = "Date Calculator" AND
2. if the editColumn is between the valid ColumnStart and ColumnEnd values (Column C to H) AND
3. if the editRow is between the valid Row values (rows 9 or 10) AND
4. if the edited value isn't a blank (length != 0).
The last condition ("edited value is blank") ensures that if cell contents are been deleted (and/or have no value), then the code won't proceed.
Convert column number to letter
I used a routine written by #AdamL found at Convert column index into corresponding column letter; this converts a column number into a letter. It's used to build the "targetcolumn" address in Workdays. It's valid for the letters A-Z; there's a version for letters beyond Z.
Cleanup
If data is entered into row 10 of a given column, then any value in row 9 (of the same column) needs to be deleted. The code does this and also deletes any pre-existing formula dates in the rows below so there is no confusion about the dates derived by the data entry.
function onEdit(e){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "Date Calculator";
var sheet = ss.getSheetByName(sheetname);
// get the event source data
var editedCell = e.range;
var editRow = editedCell.getRow();
var editCol = editedCell.getColumn();
var eValue = e.value;
var editedSheet = editedCell.getSheet().getName();
//Logger.log("DEBUG: the cell = "+editedCell.getA1Notation()+", the column = "+editCol+", the row is "+editRow+", the value is "+eValue+", the edited sheet is "+editedSheet);
// create some variables for column and row range
var columnStart = 3; // Column C
var columnEnd = 8; // Column H
var rowOption1 = 9; // row 9
var rowOption2 = 10 // row 10
// create some variables for target cells
var absolutecolumn = "C";
//var absoluterow1 = 3; // not used
//var absoluterow2 = 4; // not used
//var absoluterow3 = 5; // not used
// test for valid edit in row option 1 // Row 9
if(editedSheet === sheetname && columnEnd >=editCol && editCol>=columnStart && rowOption2>=editRow && editRow>=rowOption1 && eValue.length !=0 ){
//Logger.log("DEBUG: You got the right sheet, the edit is in the right range of columns and the edited row was = "+rowOption1);
if (editRow == rowOption2){
// clear row 9
sheet.getRange((+editRow-1),editCol).clear();
}
// clear following 8 rows of data
sheet.getRange((+editRow+1),editCol,8).clear();
// set the targetcolumn as a letter
var targetcolumn = columnToLetter(editCol);
// set formula for row+1
sheet.getRange((+editRow+1),editCol).setFormula("=WORKDAY("+targetcolumn+editRow+",$"+absolutecolumn+"$3)"); //
// set formula row +2
sheet.getRange((+editRow+2),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+1)+",+10)");
// set formula row +3
sheet.getRange((+editRow+3),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+2)+",$"+absolutecolumn+"$4)");
// set formula row +4
sheet.getRange((+editRow+4),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+3)+",$"+absolutecolumn+"$3)");
// set formula row + 5
sheet.getRange((+editRow+5),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+4)+",+10)");
// set formula row + 6
sheet.getRange((+editRow+6),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+5)+",+1)");
// set formula row + 7
sheet.getRange((+editRow+7),editCol).setFormula("=WORKDAY("+targetcolumn+(+editRow+6)+",$"+absolutecolumn+"$5)");
// change the background to show entry in rowoption1
sheet.getRange(editRow,editCol).setBackground("yellow");
sheet.getRange((+editRow+1),editCol).setBackground("white");
}
}
function columnToLetter(column)
{
var temp, letter = '';
while (column > 0)
{
temp = (column - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column = (column - temp - 1) / 26;
}
return letter;
}
Screenshot

Time stamp a cell when multiple columns are updated

I have been checking out multiple codes on trying to update my Google Spreadsheet but have been unsuccessful when trying to do this with multiple cells. On my spreadsheet I have multiple tabs and when I update a row in column 2,3 or 4, I would like it to enter the date in column 5.
Thank you for your help.
Step 1.
In the Google Spreadsheet, click on "Script editor..." under the "Tools" menu.
Step 2.
Remove any sample script that might be in there and paste the following ...
// Sets the targetColumn on the edited row to the current date if the
// edited column in within columnBounds.
// Note: This will only handle single cell editing.
// Columns that need to be monitored for changes. Use CAPITAL letters.
var monitoredColumns = ['B', 'C', 'D'];
// Colum that will receive the date.
var targetColumn = 'E'
// To avoid adding the date in the title row, we need to consider the starting row.
var startingRow = 4
// onEdit() is a reserved function name that will be called every time the sheet will be edited.
function onEdit(e) {
var range = e.range;
// Row of the edited cell.
var row = range.getRow();
// Column of the edited cell.
var col = String.fromCharCode(64 + range.getColumn());
if (row < startingRow) {
// None of the monitored rows have been edited.
return;
}
if (monitoredColumns.indexOf(col) < 0) {
// Column B, C or D (2, 3 or 4) was not modified.
// Do not proceed any further.
return;
}
// Current spreadsheet.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
// Date cell.
var dateCell = sheet.getRange(targetColumn + range.getRow());
// Set it to the current date.
dateCell.setValue(new Date());
}
Step 3
Adjust the values of monitoredColumns, targetColumn and startingRow
Step 4
Start entering some content in the cells.

Problems with insert a Timestamp

I have to insert a link with a sheet with the basic's of my original sheet.
=> At the original sheet there is a importrange which insert the data. In the next tab a query take the data - now there should be appear a timestamp when the data in col B (in the Sheet at the link) is updated, but only at the first time the col change from an empty col to a filled col.
I searching at the internet, but I didn't find a helpful answer. On the one hand the most of the Scripts I found didn't work in general or doing a little bit. As a example:
This script worked:
function onEdit(event)
{
var timezone = "GMT-5";
var timestamp_format = "MM-dd-yyyy";
var updatedColName = "Bid Responses";
var sheet = event.source.getSheetByName('Overview - Working (Hidden)');
var actRng = event.source.getActiveRange();
var editColumn = actRng.getColum();
var index = actRng.getRowIndex();
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues();
var dateCol = headers[0].indexOf(timeStampCoName);
var updateCol = headers[0].indexOf(updateColName); updateCol = updateCol+1;
if (dateCol > -1 && index > 1 && editColumn == updateCol) {
var cell = sheet.getRange(index, dateCol + 1);
var date = Utilities.formatDate(new Date(), timezone, timestamp_format);
cell.setValue(date);
}
}
But the timestamp was not inserted at the row where the col data change, it appear in a completely different row.
Can someone help me to write a Script that do exactly what I want?
Unru,
an onEdit trigger will always require a manual edit to the spreadsheet. In other words: the script runs when a user changes a value in a spreadsheet.A recalculation of a formula does NOT fire an onEdit script.
More info: here

Installable Trigger Failing with Test Add-On

I have been wrestling with an installable trigger issue for a couple of days now. All of my research indicates that an add-on should allow for an installable onEdit() trigger within a spreadsheet, but my attempts keep erroring out. I have simplified my project code a bit to exemplify my issue.
The error message:
Execution failed: Test add-on attempted to perform an action that is not allowed.
My code (listing functions is the order that they are called):
function onOpen() //creates custom menu for the evaluation tool ***FOR ADMININSTRATORS ONLY***
{
var ui = SpreadsheetApp.getUi();
if(!PropertiesService.getDocumentProperties().getProperty('initialized'))
{
ui.createMenu('Evaluation Menu') // Menu Title
.addItem('Create Installable OnEdit Trigger', 'createInstallableOnEditTrigger')
.addToUi();
}
else
{
ui.createMenu('Evaluation Menu') // Menu Title
.addSubMenu(ui.createMenu('Manage Observations & Evidence')
.addSubMenu(ui.createMenu('Create New Observation')
.addItem('Formal', 'createNewFormalObservation')
.addItem('Informal', 'createNewInformalObservation')
)
.addToUi();
}
}
function createInstallableOnEditTrigger() { // installable trigger to create employee look-up listener when user edits the EIN fields on the Documentation Sheet.
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger('onEditListener')
.forSpreadsheet(ss)
.onOpen()
.create();
PropertiesService.getDocumentProperties().setProperty('initialized','true');
}
function onEditListener(event) //this function conitnually listens to all edit, but only engages only certain conditions such as when a timestamp is determined to be needed or the Documentation Sheet needs to be auto-populated
{
//Determine whether or not the conditions are correct for continuing this function
var sheetName = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getName(); //determines the name of the currently active sheet
if (sheetName.indexOf("Evidence") > -1) // if the active sheet is an evidence collection sheet, a timestamp may be needed
{
populateEvidenceTimeStamp(event, sheetName);
}
else if (sheetName == "Documentation Sheet") //if the active sheet is the "Documentation Sheet" than auto-population and EIN lookups may be needed
{
employeeLookup(event, sheetName);
}
}
What am I missing? Any help is greatly appreciated!!
The below code has been added as requested by #Mogsdad.
populateEvidenceTimeStamp() is dependent upon generateTimeStamp() which is also included below:
function populateEvidenceTimeStamp(event, sheetName)
{
var evidenceColumnName = "Evidence";
var timeStampColumnName = "Timestamp";
var sheet = event.source.getSheetByName(sheetName);
var actRng = event.source.getActiveRange();
var indexOfColumnBeingEdited = actRng.getColumn();
var indexOfRowBeingEdited = actRng.getRowIndex();
var columnHeadersArr = sheet.getRange(3, 1, 1, sheet.getLastColumn()).getValues(); // grabs the column headers found in the 3rd row of the evidence sheet
var timeStampColumnIndex = columnHeadersArr[0].indexOf(timeStampColumnName); //determines the index of the Timestamp column based on its title
var evidenceColumnIndex = columnHeadersArr[0].indexOf(evidenceColumnName); evidenceColumnIndex = evidenceColumnIndex+1; //determines the index of the evidence column based on its title
var cell = sheet.getRange(indexOfRowBeingEdited, timeStampColumnIndex + 1); //determines the individual timestap cell that will be updated
if (timeStampColumnIndex > -1 && indexOfRowBeingEdited > 3 && indexOfColumnBeingEdited == evidenceColumnIndex && cell.getValue() == "") // only create a timestamp if 1) the timeStampColumn exists, 2) you are not actually editing the row containing the column headers and 3) there isn't already a timestamp in the Timestamp column for that row
{
cell.setValue(generateTimeStamp());
}
}
function generateTimeStamp()
{
var timezone = "GMT-7"; // Arizona's time zone
var timestamp_format = "MM.dd.yyyy hh:mm:ss a"; // timestamp format based on the Java SE SimpleDateFormat class. http://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html
var currTimeStamp = Utilities.formatDate(new Date(), timezone, timestamp_format);
return currTimeStamp;
}
Below is the employeeLookup() function which is dependent upon lookupEIN()
function employeeLookup(event, sheetName)
{
if(sheetName == "Documentation Sheet" && !PropertiesService.getDocumentProperties().getProperty('initialized')) // if the activeSheet is "Documentation Sheet" and the sheet has not yet been initialized
{
var actRng = event.source.getActiveRange();
Logger.log("employeeLookup(): actRng: "+actRng.getRow()+" , "+actRng.getColumn());
if(actRng.getRow() == 4 && actRng.getColumn() == 9 && event.source.getActiveRange().getValue() != "") //if the "Teacher EIN" cell is the active range and it's not empty
{
var ein = actRng.getValue();
clearDocumentationSheetTeacherProfile(); //first clear the teacher profile information to avoid the possibility of EIN/Teacher Info mismatch if previous search did not yield results
var teacherDataArr = lookupEIN(ein, "Teachers");
if(teacherDataArr)
{
//write retrieved teacher data to Documentation Spreadsheet
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Documentation Sheet");
sheet.getRange(5, 9, 1, 1).setValue(teacherDataArr[1]); // Teacher First Name
sheet.getRange(6, 9, 1, 1).setValue(teacherDataArr[2]); // Teacher Last Name
sheet.getRange(7, 9, 1, 1).setValue(teacherDataArr[3]); // Teacher Email
sheet.getRange(11, 9, 1, 1).setValue(teacherDataArr[4]); // School Name
sheet.getRange(11, 39, 1, 1).setValue(teacherDataArr[5]); // Site Code
sheet.getRange(10, 30, 1, 1).setValue(calculateSchoolYear()); //School Year
}
else
{
Logger.log("employeeLookup(): type:Teachers 'died. lookupEIN() did not return a valid array'"); //alert message already sent by lookUpEIN
}
}
else if (actRng.getRow() == 4 && actRng.getColumn() == 30 && actRng.getValue() != "" && !PropertiesService.getDocumentProperties().getProperty('initialized')) //if the "Observer EIN" cell is the active range
{
Logger.log("employeeLookup(): 'active range is Observer EIN'");
var ein = actRng.getValue();
clearDocumentationSheetObserverProfile(); //first clear the teacher profile information to avoid the possibility of EIN/Observer Info mismatch if previous search did not yield results
var observerDataArr = lookupEIN(ein, "Observers");
if(observerDataArr)
{
//write retrieved observer data to Documentation Spreadsheet
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Documentation Sheet");
sheet.getRange(5, 30, 1, 1).setValue(observerDataArr[1]); // Observer First Name
sheet.getRange(6, 30, 1, 1).setValue(observerDataArr[2]); // Observer Last Name
sheet.getRange(7, 30, 1, 1).setValue(observerDataArr[3]); // Observer Email
}
else
{
Logger.log("employeeLookup(): type:Observers 'died. lookupEIN() did not return a valid array'"); //alert message already sent by lookUpEIN
}
}
else
{
Logger.log("employeeLookup(): 'active range is not a trigger'");
//do nothing (not the right cell)
}
}
else
{
//Observer log has already been initialized and documentation cannot be altered. notify user
Logger.log("employeeLookup(): 'log already saved.... alerting user'");
logAlreadyInitializedDialogue();
restoreDocumentationSheetData();
}
}
function lookupEIN(ein, type)
{
Logger.log ("lookUpEIN(): 'engaged'");
var ss = SpreadsheetApp.openById(teacherObserverIndex_GID);
var sheet = ss.getSheetByName(type); //lookup type aligns with the individual sheet names on the teacherObserverIndex_GID document
var values = sheet.getDataRange().getValues();
var val = sheet.getDataRange();
for (var i = 1; i < values.length; i++)
{
if(values[i][0] == ein)
{
Logger.log ("lookUpEIN(): values[i]: "+values[i]);
return values[i];
}
else
{
Logger.log ("lookUpEIN(): 'no match found'");
}
}
//a match could not be found
Logger.log("An EIN match could not be found"); // create a feedback pop-up
einNotFoundDialogue(type); //alert user that there is a problem with the provided ein
}
Triggers can't be created when running a script as Test as add-on.
From https://developers.google.com/apps-script/add-ons/test :
There are a number of things to keep in mind while testing add-ons:
Installable triggers are currently not supported when testing.
Functionality that depends on installable triggers will not be
testable.
Some possible workarounds
For on open and on edit installable triggers, temporally add simple triggers to call the functions of the installable triggers. This might only work if the execution time of is less than the simple triggers limit.
Call the functions from the installable triggers from functions that create object that emulates the corresponding event object
Instead of using a stand-alone project use bounded projects. You might use CLASP or an extension like Google Apps Script GitHub Assistant Chrome extension to make it easier to copy the code from the stand-alone project to a bounded project.
Related
How can I test a trigger function in GAS?
In my experience onEdit() is not available for test as Add-On.
I agree the documentation is not clear, it seems to be referring to only "Installable Triggers" but I think it applies to all Triggers except for the "onInstall" trigger that is run as soon as you start the test. (see: Testing Google Sheet Addon Triggers for more details)