Sheets API: Append Row with hidden columns - google-apps-script

I have a "appendRow" function for insert rows in the Google sheet called "Data", and this sheet has a column called "Id" (**A** column) which is hidden by client request.
function appendRow() {
var spreadsheetId = "SPREADSHEET_ID";
var range = "Data!A2:C";
var resource = {
values: [
["1", "James", "jam10#gmail.com"]
]
};
Sheets.Spreadsheets.Values.append(resource, spreadsheetId, range, {
valueInputOption: "USER_ENTERED"
});
}
However, when I execute this function, the result is not the expected, as seen in the image.
I appreciate any information, idea or solution that can help me to resolve this incident. Regards.

function appendRow() {
const ss = SpreadsheetApp.openById("SS_ID");
const sh = ss.getSheetByName('Data');
sh.appendRow(["1", "James", "jam10#gmail.com"]);
}
//row must be 2d array
function appendRows(rows) {
var spreadsheetId = "SPREADSHEET_ID";
const ss = SpreadsheetApp.openById("SSID");
const sh = ss.getSheetByName('Data');
sh.getRange(sh.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
}

Related

Sheets API Import sheet with formating

Using the script here I am able to import the sheet to another document however it does not work if I remove the columns from the srcRange. My goal is to copy the entire sheets formating over to the other sheet and the number of columns occasionally changes;
This works but the columns have to be specified:
const srcSpreadsheetId = "1mVlva8Dyxxxxxxxxxxxxxx"; // Please set source Spreadsheet ID.
const dstSpreadsheetId = "1a2Eb7fQOxxxxxxxxxxxxxx"; // Please set destination Spreadsheet ID.
const srcRange = "Database!A:I";
const dstRange = "Database";
// Here, the date object is retrieved as the serial number.
const values = Sheets.Spreadsheets.Values.get(srcSpreadsheetId, srcRange, { dateTimeRenderOption: "SERIAL_NUMBER", valueRenderOption: "UNFORMATTED_VALUE" }).values;
const dstSheet = SpreadsheetApp.openById(dstSpreadsheetId).getSheetByName(dstRange);
const sheetId = dstSheet.getSheetId();
Sheets.Spreadsheets.batchUpdate({ requests: [{ repeatCell: { range: { sheetId }, fields: "userEnteredValue" } }] }, dstSpreadsheetId);
Sheets.Spreadsheets.Values.update({ values }, dstSpreadsheetId, dstRange, { valueInputOption: "USER_ENTERED" });
// Here, the number format is copied.
const numberFormats = SpreadsheetApp.openById(srcSpreadsheetId).getRange(srcRange).getNumberFormats();
dstSheet.getRange(1, 1, numberFormats.length, numberFormats[0].length).setNumberFormats(numberFormats);
This does not work:
const srcSpreadsheetId = "1mVlva8Dyxxxxxxxxxxxxxx"; // Please set source Spreadsheet ID.
const dstSpreadsheetId = "1a2Eb7fQOxxxxxxxxxxxxxx"; // Please set destination Spreadsheet ID.
const srcRange = "Database"; // <<<<<<<<<<<<<<<<< Columns not specified
const dstRange = "Database";
// Here, the date object is retrieved as the serial number.
const values = Sheets.Spreadsheets.Values.get(srcSpreadsheetId, srcRange, { dateTimeRenderOption: "SERIAL_NUMBER", valueRenderOption: "UNFORMATTED_VALUE" }).values;
const dstSheet = SpreadsheetApp.openById(dstSpreadsheetId).getSheetByName(dstRange);
const sheetId = dstSheet.getSheetId();
Sheets.Spreadsheets.batchUpdate({ requests: [{ repeatCell: { range: { sheetId }, fields: "userEnteredValue" } }] }, dstSpreadsheetId);
Sheets.Spreadsheets.Values.update({ values }, dstSpreadsheetId, dstRange, { valueInputOption: "USER_ENTERED" });
// Here, the number format is copied.
const numberFormats = SpreadsheetApp.openById(srcSpreadsheetId).getRange(srcRange).getNumberFormats();
dstSheet.getRange(1, 1, numberFormats.length, numberFormats[0].length).setNumberFormats(numberFormats);
Have tried multiple variations to no avail. Keep getting > Exception: Range not found.
If you want to achieve The source range only works right now if you put in the columns but I want to copy the format for the entire sheet. and your actual error is No error > const srcRange = "Database!A:IX";, error happens if I use > const srcRange = "Database";, how about the following modification?
From:
const srcRange = "Database!A:IX";
To:
const srcRange = "Database";
And,
From:
const numberFormats = SpreadsheetApp.openById(srcSpreadsheetId).getRange(srcRange).getNumberFormats();
To:
const numberFormats = SpreadsheetApp.openById(srcSpreadsheetId).getSheetByName(srcRange).getDataRange().getNumberFormats();
or
const srcSheet = SpreadsheetApp.openById(srcSpreadsheetId).getSheetByName(srcRange);
const numberFormats = srcSheet.getRange(1, 1, srcSheet.getMaxRows(), srcSheet.getMaxColumns()).getNumberFormats();
Added:
From I tested both the first and second solution you suggested with a smaller amount of data and both work. The only wierd part is that for numbers stored as text they are in a white font and you cannot change the color., when I saw your Spreadsheet, I understood that the reason for this issue is due to plain text format. When the cells of plain text are retrieved with getNumberFormats(), it seems that null` is returned. By this, the number is not displayed. In order to reflect this issue, please modify it as follows.
Modified script:
Please set the Spreadsheet IDs and sheet names for your test situation.
function sample() {
const srcSpreadsheetId = "1mVlva8Dyxxxxxxxxxxxxxx"; // Please set source Spreadsheet ID.
const dstSpreadsheetId = "1a2Eb7fQOxxxxxxxxxxxxxx"; // Please set destination Spreadsheet ID.
const srcRange = "Database"; // <<<<<<<<<<<<<<<<< Columns not specified
const dstRange = "Database";
// Here, the date object is retrieved as the serial number.
const values = Sheets.Spreadsheets.Values.get(srcSpreadsheetId, srcRange, { dateTimeRenderOption: "SERIAL_NUMBER", valueRenderOption: "UNFORMATTED_VALUE" }).values;
const dstSheet = SpreadsheetApp.openById(dstSpreadsheetId).getSheetByName(dstRange);
const sheetId = dstSheet.getSheetId();
Sheets.Spreadsheets.batchUpdate({ requests: [{ repeatCell: { range: { sheetId }, fields: "userEnteredValue" } }] }, dstSpreadsheetId);
Sheets.Spreadsheets.Values.update({ values }, dstSpreadsheetId, dstRange, { valueInputOption: "USER_ENTERED" });
// Here, the number format is copied.
const srcSheet = SpreadsheetApp.openById(srcSpreadsheetId).getSheetByName(srcRange);
const range = srcSheet.getRange(1, 1, srcSheet.getMaxRows(), srcSheet.getMaxColumns());
const numberFormats = range.getNumberFormats().map(r => r.map(c => c || "#"));
const styles = range.getTextStyles();
dstSheet.getRange(1, 1, numberFormats.length, numberFormats[0].length).setNumberFormats(numberFormats).setTextStyles(styles);
}

Dynamic grouping in AppSript

I have a Spreadsheet with about 3000 rows which are grouped by order number. I'm trying to build an macro to:
remove all groups
multisort all rows
recreate groups
collapse all groups marked as finished orders (optional - have no idea how to achieve this )
SHEET_NAME = "PLAN";
SORT_DATA_RANGE = "A2:CJ";
GROUP_DATA_RANGE = "BQ2:BQ";
SORT_ORDER = [
{column: 40, ascending: false},
{column: 2, ascending: true},
{column: 4, ascending: true}
];
function Sortowanie() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
removeAllGroups();
multiSortColumns();
groupRows();
ss.toast('Zakończono.');
}
function multiSortColumns(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(SHEET_NAME);
var range = sheet.getRange(SORT_DATA_RANGE + sheet.getLastRow());
range.sort(SORT_ORDER);
ss.toast('Sortowanie zakończone.');
}
function removeAllGroups() {
const ss = SpreadsheetApp.getActive();
const ssId = ss.getId();
const sheet = ss.getSheetByName(SHEET_NAME);
const sheetId = sheet.getSheetId();
sheet.expandAllRowGroups();
const n = Sheets.Spreadsheets.get(ssId, { ranges: [SHEET_NAME] }).sheets[0].rowGroups.reduce((n, { depth }) => n < depth ? depth : n, 0);
const requests = Array(n).fill("").map(_ => ({ deleteDimensionGroup: { range: { sheetId, dimension: "ROWS" } } }));
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
ss.toast('Usuwanie grup zakończone.');
}
function groupRows() {
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(SHEET_NAME);
const levels = sheet.getRange(GROUP_DATA_RANGE + getLastRowSpecial()).getValues();
const sheetId = sheet.getSheetId();
const requests = levels.flatMap(([a], i) => Array(a).fill("").map(_ => ({ addDimensionGroup: { range: { sheetId, startIndex: i + 1, endIndex: i + 2, dimension: "ROWS" } } })));
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
ss.toast('Ponowne grupowanie zakończone.');
}
function getLastRowSpecial() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sheet = ss.getSheetByName(SHEET_NAME);
const lastRow = sheet.getRange(GROUP_DATA_RANGE).getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
return lastRow
};
Everything works perfectly when I limit range to 1000 rows. When I try to run this for a whole range or more than 1000 and debuging, I'm getting this:
HttpResponseException: Response Code: 413. Message: response too large.
Without debugger after a while I'm getting this:
The JavaScript runtime has unexpectedly terminated.
Most bizarre thing is that script is creating these groups for a whole range and then error comes out after a while.
I think that 3K rows is not a big range for that, maybe someone has an idea what is wrong?
Here it is sample data sheet:
https://docs.google.com/spreadsheets/d/1DLXxZVyrhDxrBe1AX3iy54nQTFVJkoIpeos7M9mEaIo/edit?usp=sharing
Issue:
If I understand your situation correctly:
You are able to remove existing groups and sort the rows according to the groups you want to create.
The groups are defined by a column with 0s and 1s, so that 0 refers to group headers and continguous 1s should be grouped under the same group.
I assume here that, after the column header (first row), all values in your column are 0 or 1.
Solution:
Use slice and findIndex to find the successive group headers, looking iteratively for the values different than 1, so that all the rows in between are part of the same group.
For each iteration, use the indexes of the current and the next header to build each request.
Code sample:
const FIRST_ROW = 2;
function groupRows() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName(SHEET_NAME);
const levels = sheet.getRange(GROUP_DATA_RANGE + getLastRowSpecial()).getValues().flat();
const sheetId = sheet.getSheetId();
const requests = [];
let currentHeader = FIRST_ROW;
while (currentHeader < levels.length) {
let groupLength = levels.slice(currentHeader-FIRST_ROW+1).findIndex(l => l !== 1);
if (groupLength < 0) groupLength = levels.length - currentHeader + 1;
const nextHeader = groupLength + currentHeader+1;
const request = { addDimensionGroup: { range: { sheetId, startIndex: currentHeader, endIndex: nextHeader-1, dimension: "ROWS" } } };
requests.push(request);
currentHeader = nextHeader;
}
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
ss.toast('Ponowne grupowanie zakończone.');
}
Note:
Your other functions are not displayed here, but you should use them to remove existing rows and to make sure your data is sorted according to your preferences.
One of you problems but probably not the only problem
Range specified like this generate a lot of nulls between last row and max rows
"BQ2:BQ"
Try rewriting them like this:
"BQ2:BQ" + sheet.getLastRow()

Group rows with appscript

I have a sheet with more than 3000 rows and I want to group those rows by a parameter in ColA. So all rows having '1' in colA should be grouped under the row above with '0' in colA. Once groups are created I want them to collapse.
Since the script will be triggered daily upon data update, I also need the before-created groups to be removed so the new ones can be properly created.
I have several scrips doing what I need but it takes forever for them to go through all the rows. Is it possible to optimize them in some way or perhaps a different approach can be used for my needs? Thanks in advance for your help!
function removeAllGroups1() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Pipeline");
const rg = sh.getDataRange();
const vs = rg.getValues();
vs.forEach((r, i) => {
let d = sh.getRowGroupDepth(i + 1);
if (d >= 1) {
sh.getRowGroup(i + 1, d).remove()
}
});
}
function groupRows1() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Pipeline');
const levels = sh.getRange(2, 1, sh.getLastRow() - 1).getValues().flat();
levels.forEach((e, i) => sh.getRange(i + 2, 1).shiftRowGroupDepth(e));
}
function collapse() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Pipeline');
let lastRow = sh.getDataRange().getLastRow();
for (let row = 1; row < lastRow; row++) {
let depth = sh.getRowGroupDepth(row);
if (depth < 1) continue;
sh.getRowGroup(row, depth).collapse();
}
}
Data Sample: https://docs.google.com/spreadsheets/d/10BNrnAyQw89gy-Sj3CLiz4AgVtFI0AjXTvc0REGGTfY/edit#gid=113574154
I believe your goal is as follows.
You want to group the rows with high process speed.
You want to delete all groups with high process speed.
You want to collapse all groups with high process speed.
From the above goal, I thought that when Sheets API is used, the process cost can be reduced. And, Sheets API can group rows, delete all groups and collapse all groups.
Sample script:
Before you run this script, please enable Sheets API at Advanced Google services.
function removeAllGroups2() {
const sheetName = "sample";
const ss = SpreadsheetApp.getActive();
const ssId = ss.getId();
const sheetId = ss.getSheetByName(sheetName).getSheetId();
const n = Sheets.Spreadsheets.get(ssId, { ranges: [sheetName] }).sheets[0].rowGroups.reduce((n, { depth }) => n < depth ? depth : n, 0);
const requests = Array(n).fill("").map(_ => ({ deleteDimensionGroup: { range: { sheetId, dimension: "ROWS" } } }));
Sheets.Spreadsheets.batchUpdate({ requests }, ssId);
}
function groupRows2() {
const sheetName = "sample";
const ss = SpreadsheetApp.getActive();
const sheet = ss.getSheetByName(sheetName);
const levels = sheet.getRange("A2:A" + sheet.getLastRow()).getValues();
const sheetId = sheet.getSheetId();
const requests = levels.flatMap(([a], i) => Array(a).fill("").map(_ => ({ addDimensionGroup: { range: { sheetId, startIndex: i + 1, endIndex: i + 2, dimension: "ROWS" } } })));
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
function collapse2() {
const ss = SpreadsheetApp.getActive();
const requests = Sheets.Spreadsheets.get(spreadsheetId, {ranges: ["sample"]}).sheets[0].rowGroups.map(r => {
r.collapsed = true;
return { updateDimensionGroup: { fields: "*", dimensionGroup: r }};
});
Sheets.Spreadsheets.batchUpdate({ requests }, ss.getId());
}
Note:
I tested these sample scripts using your sample Spreadsheet. So when the structure of the Spreadsheet is different from your sample Spreadsheet, these scripts might not be able to be used. Please be careful about this. At first, please test them using your sample script.
References:
Method: spreadsheets.get
Method: spreadsheets.batchUpdate
DeleteDimensionGroupRequest
AddDimensionGroupRequest
UpdateDimensionGroupRequest

Search and Add characters to Google sheet value array with GAS?

I'm having a problem with finding conditional values ​​and replacing them in a Google sheet data array.
I have an array of data in column A.
I want to find the values ​​without the "." is an integer
And add a . and any natural number.
Write new data in column B.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet()
var sheet = ss.getSheetByName('Sheet1')
var range = sheet.getRange('a:a')
}
I am new with GAS, and this topic will help me so much,
Thank you for give me a help.
function myFunction() {
const n = 123;
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Sheet1');
const row = sheet.getLastRow();
const range = sheet.getRange(1, 1, row, 1);
const values = range.getValues();
const results = values.map(e => {
if (Number.isInteger(e[0])) {
return [`${e[0]}.${n}`];
}
else {
return [''];
}
});
sheet.getRange(1, 2, row, 1).setValues(results);
}

How do I use Sheets.Spreadsheets.getByDataFilter from app script for a filter view that is created in spreadsheet?

I am seeking help with the following case
I have a spreadsheet, and it contains few filter views - f1, f2, ...
I have an app script associated with the spreadsheet. I have enabled Resources > Advanced Google Services to access the Sheets API v4.
Currently, I access that data as
var fruits = Sheets.Spreadsheets.Values.get("1YBPXShvssFpTI-5dPSsy_N_iEVaeHezdxymsdxpTy6w", "Fruits!A:B").values;
And I get the corresponding data back.
I would now, like to only get the data that is used by the filter view, so that I do not bring the entire data which is not necessary and slows down the processing.
I saw that there is something called Sheets.Spreadsheets.getByDataFilter(resource, spreadsheetId), but I am not sure how to create the resource object.
Given my filters, and knowing the spreadsheet Id, how do I only fetch the data based on the filter names that I know?
UPDATE
My latest attempt looks like
var ss = SpreadsheetApp.getActiveSpreadsheet();
function getUnpostedItems() {
Logger.log("This function will prioritize the new items that are added into the inventory");
var sheet = ss.getSheetByName("Items");
var filterSettings = {};
filterSettings.criteria = {};
var condition = {
"condition": {
"type": "LESS_THAN",
"values": [
{ "userEnteredValue": "=NOW()-30" }
]
}
}
filterSettings['criteria'][1] = {
'condition': condition
};
var filterSettings = {
range: {
sheetId: sheet.getSheetId(),
},
}
var req = {
"setBasicFilter": {
"filter": filterSettings
}
}
// var items = Sheets.Spreadsheets.batchUpdate({'requests': [req]}, ss.getId());
var items = ss.getRange("Items!A:B").getValues()
// var items1 = Sheets.Spreadsheets.Values.get("1YBPXShvssFpTI-5dPSsy_N_iEVaeHezdxymsdxpTy6c", "Items!A:B").values
Logger.log("Found items:" + items.length);
return [];
}
But no luck so far!
As per #tanaike's help, I was able to get the following working
function getUnpostedItems() {
Logger.log("This function will prioritize the new items that are added into the inventory");
// var ss = SpreadsheetApp.getActiveSpreadsheet(); // Added
var sheet = ss.getSheetByName("Items"); // Modified
var values = sheet.getDataRange().getValues();
Logger.log("VALUES "+values.length);
//var newCriteria = SpreadsheetApp.newFilterCriteria().whenDateBefore(new Date()).build();
var newCriteria = SpreadsheetApp.newFilterCriteria().whenDateBefore(subDaysFromDate(new Date(), 30)).build();
var range = sheet.getFilter().setColumnFilterCriteria(1, newCriteria).getRange(); //The 1-indexed position of the column.
// values = range.getValues();
// I added below script.
var res = Sheets.Spreadsheets.get(ss.getId(), {
ranges: ["Items"], // <--- Please set the sheet name.
fields: "sheets/data"
});
var values = res.sheets[0].data[0].rowMetadata.reduce(function(ar, e, i) {
if (!e.hiddenByFilter && res.sheets[0].data[0].rowData[i]) {
ar.push(
res.sheets[0].data[0].rowData[i].values.map(function(col) {
return col.userEnteredValue[Object.keys(col.userEnteredValue)[0]];
})
);
}
return ar;
}, []);
Logger.log("VALUES "+values.length);
Logger.log("VALUES "+values);
//Logger.log("Found Items:" + items.length);
return [];
}