I am trying to create a class to import Google Sheets tables into a more maneuverable. The code:
class SheetData{
constructor(worksheet, spreadsheet=null) {
this.spreadsheet = spreadsheet ? spreadsheet : SpreadsheetApp.getActive();
this.sheet = this.spreadsheet.getSheetByName(worksheet);
this.values = this.sheet.getDataRange().getValues();
}
get records() {
let cols = this.values[0], temp = {}, out = [];
for (let row of this.values.slice(1)){
cols.forEach( (colName,idx) => temp[colName] = row[idx] );
out.push(temp);
temp = {};
}
}
How ever when I try to run it on a sheet Logger.log(new SheetData('Sheet1').values), I get an Unexpected identifier at the new. What am I doing wrong? I also am not getting any syntax highlighting in the editor, even though I have the V8 runtime enabled.
How about this modification?
Modification points:
If you are testing your script in your question, I think that in your script, } is required to be added at the last line (
} is one shortage.).
I think that this might be the reason of your issue.
And, new SheetData('Sheet1').records is run, undefined is returned. Because no value is returned.
When above points are reflected to your script, it becomes as follows.
Modified script:
class SheetData{
constructor(worksheet, spreadsheet=null) {
this.spreadsheet = spreadsheet ? spreadsheet : SpreadsheetApp.getActive();
this.sheet = this.spreadsheet.getSheetByName(worksheet);
this.values = this.sheet.getDataRange().getValues();
}
get records() {
let cols = this.values[0], temp = {}, out = [];
for (let row of this.values.slice(1)){
cols.forEach( (colName,idx) => temp[colName] = row[idx] );
out.push(temp);
temp = {};
}
return out; // <--- Added
}
} // <--- Added
// Please run this function.
function main() {
var s = new SheetData('Sheet1');
console.log(s.values); // or Logger.log(s.values);
console.log(s.records); // or Logger.log(s.records);
}
Note:
Please confirm whether V8 runtime is enabled again.
Related
I wish to run the next script, with a script.
When function "ConfigureTest1" has run, it should simply start "ConfigureTest2" and so on...
This is what I have, so far:**
function ConfigureTest1() {
const ss = SpreadsheetApp.getActiveSpreadsheet()
const sh = ss.getSheetByName('TEST')
sh
.getRange('A2:A')
.createTextFinder('T')
.replaceAllWith('TEST')
ScriptApp.newTrigger('ConfigureTest2') // RUN function ConfigureTest2
.create();
}
function ConfigureTest2() {
let ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("AUTO")
let data = ss.getRange(1, 1, ss.getLastRow(), 1).getValues()
let initialData = data.map(function(r) {
return r[0];
})
let conde = ["TEST"]
let writea = ["TEST"]
let rowNumber = 2
for (i = 0; i <= ss.getLastRow() - 2; i++) {
for (j = 0; j <= conde.length - 1; j++) {
if (initialData[i].includes(conde[j])) {
ss.getRange("B" + rowNumber).setValue(writea[j])
break
}
ss.getRange("B" + rowNumber).setValue("TEST")
}
rowNumber++
}
}
You can just add ConfigureTest2() after creating the trigger like this:
function ConfigureTest1() {
//what you already have
ConfigureTest2()
}
I'm not sure what's your final purpose with the script, but as some additional advice, the TriggerBuilder documentation explains that you first need to specify what kind of trigger this is along with the ID of the document that you want to attach it to, and then specify the type of trigger, so your trigger creation should look more like this:
ScriptApp.newTrigger('ConfigureTest2')
.forSpreadsheet("<Your-spreadsheet-ID>")
.onOpen() //or edit, etc.
.create();
And also note that the includes method applies to an entire array, but you're trying to call it on just one element of it, so instead of this:
if (initialData[i].includes(conde[j])) {
You need to change it to this:
if (initialData.includes(conde[j])) {
This does copy some "TEST" data to a different column when you run ConfigureTest1, but again, I'm not sure what your goal is. At least this should solve the syntax errors so you can focus on the logic of the script.
So I'm needing to get the list of file names from a range of Google Drive URLs in a spreadsheet. Browsing around the net, I came across the code below. It works but only for the old style urls, which I heard Google changed in September 2021.
Note that links are not fully functional, please replace with real links to check!
The old style is:
https://drive.google.com/file/d/1GMUwYxZxsNpLiaYOiVMBwl41LpreQ-fc/view?usp=sharing
This works correctly from the code below.
What I'd like though is two things.
It should handle a range of a couple of columns, currently reading AE2:AE, and printing out on AM2:AM. What I'd like is to go through the range: AE2:AL and print out: AM2:AT
Secondly it should also handle the newer form urls:
https://drive.google.com/file/d/0B9EZQqsLDEqDUGlsdy1oVEtETGs/view?usp=sharing&resourcekey=0-h7HOcxayPaHJ5r6dAAslVQ
Current Code:
function getNames() {
var activeRange = SpreadsheetApp.getActiveSheet().getDataRange();
var height = activeRange.getHeight();
var links = SpreadsheetApp.getActiveSheet()
.getRange("AE2:AE" + height)
.getValues();
var nameValues = [];
links.forEach((row) => {
try {
var link = row[0];
var fileID = getIdFromLink(link);
var name = DriveApp.getFileById(fileID).getName();
nameValues.push([name]);
} catch (e) {
nameValues.push(["NO NAME FOUND"]);
}
});
var nameRange = SpreadsheetApp.getActiveSheet().getRange("AM2:AM" + height);
nameRange.setValues(nameValues);
}
function getIdFromLink(link) {
var regex = new RegExp(
/(?<=https:\/\/drive\.google\.com\/file\/d\/)(.+)(?=\/)/
);
return regex.exec(link)[0];
}
How should the code above be modified to enable what I'm wanting. Sorry, I tried a couple of if/else statements, but my Javascript knowledge is severely limited.
Any help would be greatly appreciated.
Current "screenshot" showing:
(1) - Old style url - correctly picking up file name (2)
(3) - New style url - not picking up file name (4)
Your getIdFromLink() function should work just fine as long as the files have not been shared in such a way that they require a resource key as well.
To work with resource keys, use DriveApp.getFileByIdAndResourceKey(), like this:
function getFileNamesByLink() {
const sheet = SpreadsheetApp.getActiveSheet();
const sourceRange = sheet.getRange('AE2:AL');
const targetRange = sheet.getRange('AM2');
const fileNames = sourceRange.getValues()
.map(row => row.map(link => getFileNameFromLink_(link)));
targetRange
.offset(0, 0, fileNames.length, fileNames[0].length)
.setValues(fileNames);
}
function getFileNameFromLink_(link) {
if (!link) {
return null;
}
const fileId = getIdFromLink_(link);
if (!fileId) {
return NaN;
}
let file;
try {
file = DriveApp.getFileById(fileId);
} catch (error) {
try {
file = DriveApp.getFileByIdAndResourceKey(fileId, getResourceKeyFromLink_(link));
} catch (error) {
return NaN;
}
}
return file.getName();
}
function getIdFromLink_(link) {
const match = String(link).match(/file\/d\/([-\w]+)/i);
return match ? match[1] : null;
}
function getResourceKeyFromLink_(link) {
const match = String(link).match(/resourcekey=([-\w]+)/i);
return match ? match[1] : null;
}
Note that the script may time out if you have thousands of links. If that happens, process the links in a piecemeal fashion, or see if the Advanced Drive Service works for you.
I have a working script. need to improvise to have no manual interruption. We have multiple Profiles in Analytics, sometimes we lose access and sometimes we have. So when i run the Script, If we lost access to 1 of 60 profiles, i have to delete that entry manually then rerun the script.
What i want is, If there is below error, Then skip and continue with next row
"GoogleJsonResponseException: API call to analytics.data.ga.get failed with error: User does not have sufficient permissions for this profile."
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = doc2.getSheetByName("Dashboard");
for(var i=52;i<65;i++){
var viewId = dashboard.getRange(i,13).getValue(); // Your Google Analytics view ID
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = {'segment': 'gaid::-5'};
var result = Analytics.Data.Ga.get(viewId, metric, option);
var metric = result.totalsForAllResults['ga:metric'];
var metric2 = result.totalsForAllResults['ga:metric2'];
var metric3 = result.totalsForAllResults['ga:metric3'];
var doc = SpreadsheetApp.getActiveSpreadsheet(); // Current document
var sheet = doc.getActiveSheet(); // Current sheet
sheet.getRange(i,14,1,1).setValue(metric);
sheet.getRange(i,15,1,1).setValue(metric2);
sheet.getRange(i,16,1,1).setValue(metric3);
} }
try it this way:
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var sh = doc2.getSheetByName("Dashboard");
var sheet = doc2.getActiveSheet(); // Current sheet
const vs = sh.getRange(52, 13, 13).getValues();
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = { 'segment': 'gaid::-5' };
for (var i = 0; i < vs.length; i++) {
var viewId = vs[i][0]; // Your Google Analytics view ID
try {
var result = Analytics.Data.Ga.get(viewId, metric, option);
}
catch(e){
continue;
}
if (result) {
sheet.getRange(i + 52, 14, 1, 3).setValues([[result.totalsForAllResults['ga:metric'], result.totalsForAllResults['ga:metric2'], result.totalsForAllResults['ga:metric3']]]);
}
}
}
Without the benefit of working data some of this may not be correct but using setValues and getValues should speed it up considerably and the try catch blocks should help with not getting result consistently. Also you want to avoid making unnecessary declarations in loops.
I might understand the question incorrectly (if so, please clarify) but it sounds to me like you just need to add...
function GoogleAnalytics() {
var doc2 = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = doc2.getSheetByName("Dashboard");
for(var i=52;i<65;i++){
try { //...this line and...
var viewId = dashboard.getRange(i,13).getValue(); // Your Google Analytics view ID
var metric = 'ga:metric, ga:metric2, ga:metric3';
var option = {'segment': 'gaid::-5'};
var result = Analytics.Data.Ga.get(viewId, metric, option);
var metric = result.totalsForAllResults['ga:metric'];
var metric2 = result.totalsForAllResults['ga:metric2'];
var metric3 = result.totalsForAllResults['ga:metric3'];
var doc = SpreadsheetApp.getActiveSpreadsheet(); // Current document
var sheet = doc.getActiveSheet(); // Current sheet
sheet.getRange(i,14,1,1).setValue(metric);
sheet.getRange(i,15,1,1).setValue(metric2);
sheet.getRange(i,16,1,1).setValue(metric3);
} catch(e) { //...this part
console.log(e); //optional, catch(e){} is perfectly valid as well, or any code you might want to execute on error
}
} }
I get the following message when I try to save this code from the script editor on Google Sheets: "We're sorry, an unexpected error occurred during compilation." The code creates a class with a method.
If it doesn't create a method I don't get the error, but the point of the object is the method. I did this in the past in another Sheets application, but I can't even get it to work for a simple example.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet(); // curr active spreadsheet
var theSheet = ss.getActiveSheet(); // current active sheet
var aLogger = tLogger(theSheet, 5, 1, 5); // set up a logger
aLogger.tLog("A test line: 1");
}
// object to do logging on a sheet
function tLogger(ourSheet, startLine, startCol, logLength) {
// object properties:
this.curline = startLine;
this.sheetRef = ourSheet;
this.theColumn = startCol;
this.maxLog = startLine + logLength;
ourSheet.getRange(startLine, startCol, logLength, 1).clear(); // clear old
// including this is what causes the error:
function this.tLog(tText) { // log an entry on the sheet
this.sheetRef.getRange(this.curline++, this.theColumn).setValue(tText);
return;
}
return;
}
Re-code it with ES6 something like above
class tLogger {
constructor(ourSheet, startLine, startCol, logLength) {
this.curline = startLine;
this.sheetRef = ourSheet;
this.theColumn = startCol;
this.maxLog = startLine + logLength;
}
}
I'm trying to take my project from a stage to another, and I was able to make some good progress so far.
I've got the following script that runs when the sheet called Setup_Protections is edited: it removes all the sheets protections then add them back with the Emails specified in the Setup sheet (i.e. add those emails as editors of the protected sheets).
But the problem is that the spreadsheet needs to be shared beforehand so they can access it first. Is there a way to share in the same time the document with the emails entered in the Setup sheet ? (without necessary using a method that requires enabling Sheets API as I'll be duplicating many times the documents)
Thank you for your help
Sheet
MY SCRIPT:`
var environment = {
protectionConfigSheetName: "Setup_Protection",
};
// Script fires when Setup_Protection is edited
function onEdit(e) {
if (e.range.getSheet().getName() === environment.protectionConfigSheetName)
resetSpreadsheetProtections();
}
function removeSpreadsheetProtections(spreadsheet) {
[
SpreadsheetApp.ProtectionType.SHEET,
].forEach(function (type) {
return spreadsheet.getProtections(type).forEach(function (protection) { return protection.remove(); });
});
}
function getProtectionConfig(spreadsheet) {
var protectionConfigSheetName = "Setup_Protection";
var sheet = spreadsheet.getSheetByName(environment.protectionConfigSheetName);
var values = sheet.getDataRange().getValues();
var protectionConfig = values
.slice(1)
.reduce(function (protectionConfig, _a) {
var targetSheetName = _a[0], emailAddress = _a[1];
var config = protectionConfig.find(function (_a) {
var sheetName = _a.sheetName;
return sheetName === targetSheetName;
});
var editors = emailAddress.split(",");
if (config)
config.editors = config.editors.concat(editors);
else
protectionConfig.push({
sheetName: targetSheetName,
editors: editors.slice()
});
return protectionConfig;
}, []);
return protectionConfig;
}
function setSpreadsheetProtections(spreadsheet, protectionConfig) {
spreadsheet.getSheets().forEach(function (sheet) {
var protection = sheet.protect();
protection.removeEditors(protection.getEditors().map(function(editor) {
return editor.getEmail();
}));
var currentSheetName = sheet.getName();
var config = protectionConfig.find(function (_a) {
var sheetName = _a.sheetName;
return sheetName === currentSheetName;
});
if (config)
protection.addEditors(config.editors);
});
}
function resetSpreadsheetProtections() {
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var protectionConfig = getProtectionConfig(spreadsheet);
removeSpreadsheetProtections(spreadsheet);
setSpreadsheetProtections(spreadsheet, protectionConfig);
}
Note: there is also another script needed for this one called Polyfill.gs
Finally it's working now:
Add the following to the above code:
function addEditorsToSpreadsheetFromProtectionConfig(spreadsheet, protectionConfig) {
var editors = protectionConfig.reduce(function (accumulator, _a) {
var editors = _a.editors;
return accumulator.concat(editors);
}, []);
spreadsheet.addEditors(editors);
}
Then Add to resetSpreadsheetProtections() the following line:
addEditorsToSpreadsheetFromProtectionConfig(spreadsheet, protectionConfig);