Copying multiple non empty rows to a new sheet - javascript

I am trying to save the daily feedback that i get from a website from the "feedback" sheet into another google sheet that i called "Feedback Database"
Column A: the Date
Column B: Time
Column C: The email address
Column D: the feedback
The data rows start from row 3.
What i am currently doing to save the data based on the basic knowledge that i have is:
copying all the data in row 3 in the "feedback" sheet
Saving it to the "database" sheet
Getting back to the "feedback" sheet and deleting row 3 so that data in row 4 becomes in row 3.
I have another function that count the number of rows and run the saveFeedback_Data() function based on the number of rows.
I know that what i am doing in step 3 isn't a proper way, I would really appreciate it if you guys recommend a better way to perform this function with a commentary in order to understand.
Appreciate your help,
function saveFeedback_Data() {
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('feedback'), true);
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var Date= sheet.getRange('feedback !A3').getValue();
var Time = sheet.getRange('feedback !B3').getValue();
var Email = sheet.getRange('feedback !C3').getValue();
var Feedback = sheet.getRange('feedback !D3').getValue();
var sheet_dest = ss.getSheetByName("Database");
sheet_dest.appendRow([Date,Time, Email,Feedback]);
Logger.log('Issue Date : ' + Date + ' Issue time : ' + Time + ' Email Address : ' + Email + ' Feedback : ' + Feedback + '\n');
//now deleting the row
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('feedback'), true);
spreadsheet.getRange('3:3').activate();
spreadsheet.getActiveSheet().deleteRows(spreadsheet.getActiveRange().getRow(), spreadsheet.getActiveRange().getNumRows());
spreadsheet.getRange('A10').activate();
}

Save Feedback Data
This will run about ten times faster
function saveFeedback_Data() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Feedback');
const shsr = 3;
const dbsh = ss.getSheetByName('Database');
const rg = sh.getRange(shsr, 1, sh.getLastRow() - shsr + 1, 4);
const vs = rg.getValues().filter(r => !r.every(e => !e));//gets all non blank rows at one time
dbsh.getRange(dbsh.getLastRow() + 1, 1, vs.length, vs[0].length).setValues(vs);//moves all data at one time
rg.clearContent();//clears all data at one time
}
You can goto to Google Apps Script Reference and using the search box find any function that you don't understand. If it's a pure JavaScript function the go here

Related

Google Apps Script Prevent duplicate data Submit on Google Sheets Form

I need to submit data from one sheet to another using google script. When I click submitData, it should check whether it is previously entered data or not. If there was no same data, the code must add new data. But, if ther was same data, the code must update the existing.
Their Identity Number is mentioned in "H11" cell on "Form" sheet. The identity number is stored in "Column D" on "Data" sheet. Now When I run it , it's submitting to "Data" sheet even DUPLICATE data entered in "H11" Cell
function submitData() {
var ss = SpreadsheetApp.getActive();
var formSS = ss.getSheetByName("Form"); //Form Sheet
var datasheet = ss.getSheetByName("Data"); //Data Sheet
var ui = SpreadsheetApp.getUi(); //get UI
var IDform = formSS.getRange("H11").getValue();
var IDdata = datasheet.getRange(2, 4, datasheet.getLastRow()-1, 1).getValues();
var peringatan = formSS.getRange("H8").getDisplayValue() +"'s Passenger" + ' on ' + formSS.getRange("H10").getDisplayValue() + ' is ' + formSS.getRange("H12").getDisplayValue() + ' passenger?'
// Input Values
var values = [[ formSS.getRange("H4").getValue(),
formSS.getRange("H8").getValue(),
formSS.getRange("H10").getValue(),
formSS.getRange("H11").getValue(),
formSS.getRange("H12").getValue()]];
var clform = [[ formSS.getRange("H4").clear(),
formSS.getRange("H6").clear(),
formSS.getRange("H8").clear(),
formSS.getRange("H10").clear(),
formSS.getRange("H12").clear()]];
// Save New Data
if (formSS.getRange("H4").getValue() == "") {
ui.alert('Data Tidak Boleh Kosong');
} {
let idx = IDdata.indexOf(IDform);
if (~idx) {
if (ui.alert('Data akan diperbarui - ' + peringatan, ui.ButtonSet.OK_CANCEL) == ui.Button.OK) {
datasheet.getRange(idx, 1, 1, 5).setValues(values); //update data
formSS.getRange("H4").clear();
formSS.getRange("H6").clear();
formSS.getRange("H8").clear();
formSS.getRange("H10").clear();
formSS.getRange("H12").clear();
}
} else {
if (ui.alert('Data baru akan ditambahkan - ' + peringatan, ui.ButtonSet.OK_CANCEL) == ui.Button.OK) {
datasheet.getRange(datasheet.getLastRow()+1, 1, 1, 5).setValues(values); //new data
formSS.getRange("H4").clear();
formSS.getRange("H6").clear();
formSS.getRange("H8").clear();
formSS.getRange("H10").clear();
formSS.getRange("H12").clear();
}
}
}
}
I've tried to remove the Bitwise Not (~), so it tried to call the //update data condition but it's alerted Exception: The starting row of the range is too small.
Try flattening your nested array this way:
var IDdata = datasheet.getRange(2, 4, datasheet.getLastRow()-1, 1).getValues().flat();
Because you're reading a column, each element in the array is itself an array with one element that contains the value. Without flattening, you're searching for a value inside a list of arrays.

Google Sheets, stack report from multiple workbooks

Goal: To stack data from 90+ google workbooks, all with the same sheet name, into the one master sheet for reporting
Info:
All worksheets have the same number of columns.
I have the following script but it does not run properly, I think the issue is with how I am caching / Pushing the data to the array before pasting to the output sheet.
I am trying to build an array then paste it in one go.
The tables I am stacking have 47 columns, unknown number of rows.
The part that opens the sheets is all working perfectly.
// Get the data from the worksheets
var indexsheet = SpreadsheetApp.getActive().getSheetByName("Index");
var outputsheet = SpreadsheetApp.getActive().getSheetByName("Output");
var response = SpreadsheetApp.getUi().prompt('Current Cycle', 'Enter Cycle Name Exactly in YY-MMM-Cycle# format', SpreadsheetApp.getUi().ButtonSet.OK_CANCEL)
var CurrentCycleName = response.getResponseText()
// Assign datasets to variables
var indexdata = indexsheet.getDataRange().getValues();
// For each workbook in the index sheet, open it and copy the data to a cache
indexdata.forEach(function(row, r) {
try {
//open Entity specific workbook
var workbookid = indexsheet.getRange(r + 1, 7, 1, 1).getValues();
var Entityworkbook = SpreadsheetApp.openById(workbookid)
// Open workhseet
Entitysheet.getSheetByName(CurrentCycleName)
// Add PR Data to cache - stacking for all countrys
var PRDataCache = Entitysheet.getDataRange().push()
} catch {}
})
// Set the all values of the sheet at once
outputsheet.getRange(r + 1, 14).setValue('Issue Splitting Data')
Entitysheet.getRange(2, 1, PRDataCache.length || 1, 47).setValues(PRDataCache)
};
This is the index tab where we are getting the workbookid from to open each file
This is the output file, we are stacking all data from each country
I believe your goal is as follows.
You want to retrieve the Spreadsheet IDs from the column "G" of "Index" sheet.
You want to give the specific sheet name using a dialog.
You want to retrieve all values from the specification sheet in all Spreadsheets. In this case, you want to remove the header row.
You want to put the retrieved values on "Output" sheet.
In this case, how about the following sample script?
Sample script:
function myFunction() {
var ss = SpreadsheetApp.getActive();
var indexsheet = ss.getSheetByName("Index");
var outputsheet = ss.getSheetByName("Output");
var response = SpreadsheetApp.getUi().prompt('Current Cycle', 'Enter Cycle Name Exactly in YY-MMM-Cycle# format', SpreadsheetApp.getUi().ButtonSet.OK_CANCEL);
var CurrentCycleName = response.getResponseText();
var ids = indexsheet.getRange("G1:G" + indexsheet.getLastRow()).getValues();
var values = ids.reduce((ar, [id]) => {
try {
var [, ...values] = SpreadsheetApp.openById(id).getSheetByName(CurrentCycleName).getDataRange().getValues();
ar = [...ar, ...values];
} catch (e) {
console.log(`"${id}" was not found.`);
}
return ar;
}, []);
if (values.length == 0) return;
// If the number of columns is different in all Spreadsheets, please use the following script.
// var maxLen = Math.max(...values.map(r => r.length));
// values = values.map(r => r.length < maxLen ? [...r, ...Array(maxLen - r.length).fill("")] : r);
outputsheet.getRange(outputsheet.getLastRow() + 1, 1, values.length, values[1].length).setValues(values);
}
Note:
When the number of Spreadsheet IDs is large, the processing time might be over 6 minutes. I'm worried about this. At that time, how about separating the Spreadsheet IDs?
Reference:
reduce()

Need script to loop through stock ticker list, copy resulting output, and paste the output of multiple rows to separate sheet within workbook

My question is similar to Need script to loop through stock ticker list, copy resulting output, and paste output to separate sheet within workbook. However, instead of copying and pasting a single row (for each ticker) below the last non-empty row, I need to copy and paste 100 rows (for each ticker) below the last non-empty row.
I have three sheets named Tickers , Data, and Results within a single workbook.
If done manually, the job would be the following: Enter a stock symbol into cell A2 in the Data sheet. The sheet then retrieves the historical time series data from google finance, runs formulas and returns 100 rows and 7 columns of data (Ticker, Date, Open, High, Low, Close, Volume) into range A5:G254 within the same Data sheet. Copy the 100 rows of returned data and paste the data on the Results sheet below the last non-empty row. Then repeat the process with each ticker.
Request: Create a script to loop through a list of 50 stock symbols from the Ticker sheet (range B2:B51), paste 1 symbol at a time into cell A2 in the Data sheet, wait for google finance API to run, and then copy all the resulting rows generated in range A5:G254, and pasting those results into the Results sheet, below the data generated from previous ticker, until there is a "long format" table of time series data of each of the 50 tickers.
The issue with my current script is that it pastes only the first row of the wanted range A5:G254.
function getNewPrices() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const tickerSheet = ss.getSheetByName('Tickers');
const dataSheet = ss.getSheetByName('Data');
const resultsSheet = ss.getSheetByName('Results');
let data;
let myListOfTickers = tickerSheet.getRange('B2:B51').getValues().filter(String).sort();
myListOfTickers.forEach(ticker => {
dataSheet.getRange('A2').setValue(ticker[0]);
data = dataSheet.getRange('A5:G254').getValues()[0];
pasteData(resultsSheet, data);
});
};
function pasteData(resultsSheet,data){
let nextRow = resultsSheet.getLastRow() + 1;
data.forEach((datum,index) => {
resultsSheet.getRange(nextRow,index + 1,1,1).setValue(datum);
});
};
I'm not completely sure but I think this is what you want
function getNewPrices() {
const ss = SpreadsheetApp.getActive();
const tsh = ss.getSheetByName('Tickers');
let tvs = tsh.getRange('B2:B51').getValues().filter(String).sort();
const dsh = ss.getSheetByName('Data');
const rsh = ss.getSheetByName('Results');
dsh.getRange(2,1,tvs.length,tvs[0].length).setValues(tvs);
let dvs = dsh.getRange('A5:G254').getValues();
rsh.getRange(rsh.getLastRow() + 1, 1, dvs.length, dvs[0].length).setValues(dvs);
}
You can goto to Google Apps Script Reference and using the search box find any function that you don't understand. If it's a pure JavaScript function the go here
Problem solved. Here is the final code if anyone wants to use it later on. Thank you for the initial code Cooper.
function getNewPrices() {
const ss = SpreadsheetApp.getActive();
const tsh = ss.getSheetByName('Tickers');
let tvs = tsh.getRange('B2:B51').getValues().filter(String).sort();
const dsh = ss.getSheetByName('Data');
const rsh = ss.getSheetByName('Results');
tvs.forEach(ticker =>{
dsh.getRange('A2').setValue(ticker[0]);
let dvs = dsh.getRange('A5:G254').getValues();
rsh.getRange(rsh.getLastRow() + 1, 1, dvs.length, dvs[0].length).setValues(dvs);
});
}

How can I fix/link/connect Telegram bot inline button to cell in Google Sheet?

So I have Telegram Bot that works with Google App Script.
My bot sends the last row that was edited in my Sheet in any List ( more then 10 list).
And the message from bot goes with inline button "Accepted" - and I'm receiving an array with call_back data.
But I don't know how can I fix "Accepted" to the row ( fixed cell ) where I got message from.
Func - message to telegram
function onEdit(e) {
sendTelegram(e)
}
function sendTelegram(e){
var row = e.range.getRow();
var col = e.range.getColumn();
var startRow = 2; // Starting row
var targetColumn = 2; // Row, where placed trigger to send msg
var ws = sheetName; //List name
var sheet = e.source.getActiveSheet();
var sheetName = e.source.getActiveSheet().getName(); //Takes list name
let Company = e.source.getActiveSheet().getRange(row,13).getValue(); //Takes data from row 2 to 13 Column in every list
var firstCol = 2; // Starting from 2 column
var numOfCols = 13;//Ending in 13 column
var fullRowValues = sheet.getRange(row, firstCol, 1, numOfCols).getValues();
var fullRowString = fullRowValues.flat().toString(); // Makes array toString.
let chatId = "ChatId";// Telegram groupId
var text = encodeURIComponent(Company + " New Document has Been added" + ws)
if(e.source.getActiveSheet().getRange(row,2).getValue() == "Yanson"){ //Yanson- trigger.If in column 2 "Yanson" is set - sends row to group chat.
sendText(chatId,"[New Doc Added!] " + Company + sheetName + " , " + fullRowString,accepted);} //accepted - inline button
Send Text Func.
function sendText(chatId, text, keyBoard) {
let data = {
method: 'post',
payload: {
method: 'sendMessage',
chat_id: String(chatId),
text: text,
parse_mode: 'HTML',
reply_markup: JSON.stringify(keyBoard)
}
}
UrlFetchApp.fetch('https://api.telegram.org/bot' + token + '/', data);
}
And doPost func that track when button was clicked and saving it in "Log" list as array of data.
function doPost(e) {
let contents = JSON.parse(e.postData.contents);
SpreadsheetApp.openById("SheetId").getSheetByName("Log").appendRow([contents]);
So what I just need is to make When someone click on the button "Accepted" - text "Accepted" appears in 15 column near edited row that just was sent by bot.
This is how one of my list looks (example).
And this is how looks array that I'm receiving from bot[Contents].
I'm sure we can connect list and button by the Case Name (111/111/111) but don't know how :(
1.Yanson - trigger to send msg
2.Bot sends Msg with row.
3.Pressing inline button in bot msg.
4. Accepted appears near the row that was sent. Thats what I'm trying to get and searching for the solution for step 4.

Make a copy of google sheet into google drive folder

I have two Spreadsheets. The first spreadsheet contains my raw data that indicates the employee # and name of employees. The second spreadsheet is the spreadsheet I want to copy into a google drive folder. I want to update the specific fields on the 2nd spreadsheet based on the employee number and employee name from the 1st spreadsheet. Everytime it updates the cells in the second spreadsheet, it will create copy of the 2nd spreadsheet into google drive folder.
However, it keeps on setting just one value inside the replicated spreadsheets. It doesn't loop the employee names and employee numbers from the 1st spreadsheet.
My code is already replicating the 2nd spreadsheet. It's just the values aren't updating.
function replicateCards() {
var ss = SpreadsheetApp.openById('xxxxxxxx');
var copyCard = SpreadsheetApp.openById('zzzzzzzzz');
var getID = DriveApp.getFileById(copyCard.getId())
var card = copyCard.getSheetByName("Card");
var mastersheet = ss.getSheetByName("Mastersheet");
var getLastRow = mastersheet.getLastRow();
var destinationFolder = DriveApp.getFolderById('yyyyyyyyyy');
;
var changeColorToGrayList = card.getRangeList(['C7', 'E7', 'G7', 'I7', 'K7', 'M7', 'O7', 'Q7',
'C9', 'E9', 'G9', 'I9', 'K9', 'M9', 'O9', 'Q9',
'C11', 'E11', 'G11', 'I11', 'K11', 'M11', 'O11', 'Q11']);
var setValueToZero = card.getRangeList(['C8', 'E8', 'G8', 'I8', 'K8', 'M8', 'O8', 'Q8',
'C10', 'E10', 'G10', 'I10', 'K10', 'M10', 'O10', 'Q10',
'C12', 'E12', 'G12', 'I12', 'K12', 'M12', 'O12', 'Q12']);
for (i = 1; i < getLastRow; i++) {
var employeeNumber = mastersheet.getRange(i + 1, 1).getValue();
var employeeName = mastersheet.getRange(i + 1, 2).getValue();
card.getRange("C3").setValue(employeeName);
card.getRange("H3").setValue(employeeNumber);
card.setActiveRangeList(changeColorToGrayList).setBackground("gray");
card.setActiveRangeList(setValueToZero).setValue(0);
// var getID = DriveApp.getFileById(card).getId();
getID.makeCopy(employeeNumber + " High Flyer Card", destinationFolder);
}
}
I expect the output of getID.makeCopy(employeeNumber + " High Flyer Card", destinationFolder); contains different employee # and employee names, not just one value inside the google folder.
Your code should work as intended, unless a bulky file makes the code overlap.
If so, implementation of flush() will make your code run sequentially, see here for a detailed explanation.
In your case, modifying the for loop to
for (i = 1; i < getLastRow; i++) {
var employeeNumber = mastersheet.getRange(i + 1, 1).getValue();
var employeeName = mastersheet.getRange(i + 1, 2).getValue();
card.getRange("C3").setValue(employeeName);
card.getRange("H3").setValue(employeeNumber);
card.setActiveRangeList(changeColorToGrayList).setBackground("gray");
card.setActiveRangeList(setValueToZero).setValue(0);
getID.makeCopy(employeeNumber + " High Flyer Card", destinationFolder);
SpreadsheetApp.flush();
}
should solve the issue.

Categories

Resources