Fetch data from multiple sheets using Google app script - javascript

Trying to fetch data from multiple spreadsheet's. All Sheet's are stored in same folder. Have to fetch data in one master sheet from only specific files by file name. I have written below script. It's working fine if we enter only one file name in specified range (in master sheet tab) (getSheetByName) but showing error while trying to fetch data for multiple files.
Showing error - "TypeError: Cannot read property 'length' of undefined"
Below is Script -
function get_compiled_data() {
var filelist = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("FileName");
var filelists = filelist.getRange("A2:A").getValues();
var folder = DriveApp.getFolderById("1li9hBP_W5gPkb_ASKqin4j1ZGEn1Gvji");
var fileindex = folder.getFilesByName(filelists);
var file;
var filetype;
var sheetID;
var collect_data = [];
var data;
while (fileindex.hasNext()) {
file = fileindex.next();
filetype = file.getMimeType();
if (filetype === "application/vnd.google-apps.spreadsheet"){
sheetID = file.getId();
data = getData(sheetID);
data = data.map(function(r){return r.concat([file.getName()]);});
collect_data = collect_data.concat(data);
}
}
var target = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Compiled_Data");
target.getRange("A2:AX").clearContent();
target.getRange(2, 1, collect_data.length, collect_data[0].length).setValues(collect_data);
}
function getData (sheetID) {
var sheet = SpreadsheetApp.openById(sheetID);
var tab = sheet.getSheets()[0];
var data = tab.getRange("A3:AX" + tab.getLastRow()).getValues();
return data;
}

Issue:
You are providing a 2D array to getFilesByName(name), when you should be providing a string. Because of this, the code never enters the while block and collect_data remains an empty array, causing collect_data[0] to be undefined, and producing the observed error when trying to access its length.
When you were looking for a single file name you were probably using getValue(), which retrieves a single value, which can be a string, which can be used in getFilesByName. getValues() returns a 2D array instead, so you should adapt your code so that it iterates through each value returned by getValues().
Solution:
Edit the getValues() line and wrap all the actions made from getFilesByName inside a loop the following way:
function get_compiled_data() {
var filelist = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("FileName");
var filelists = filelist.getRange(2, 1, filelist.getLastRow() - 1).getValues();
var folder = DriveApp.getFolderById("FOLDER-ID");
filelists.forEach(fileName => {
var fileindex = folder.getFilesByName(fileName[0]);
// Rest of code
});
});
Reference:
getValues()
getValue()

Related

Javascript: Is there anything I can do on my end to stop a 403 from occurring?

im on javascript and im currently trying to work with pull requests, issues and commits from a repo. I have the following code:
const axios = require('axios');
var gitPullApiLink = "https://api.github.com/repos/elixir-lang/elixir/pulls";
var listOfCommits = [];
var listOfSHAs = [];
var mapOfInfoObjects = new Map();
var mapPullRequestNumberToCommits = new Map();
var mapPRNumbersToCommitObjects = new Map();
var listOfPrObjects = [];
var setOfFileObjects = new Set();
var listOfNumbersOfTargetedIssues = [];
var mapPRnumberToCloseOpenDateObjects = new Map();
class PullRequestParser {
async getListOfPullRequests(pullrequestLink) {
const message = await axios.get(pullrequestLink);
//console.log(message);
listOfPrObjects = message['data'];
}
async getCommitsForEachPullRequestAndPRinformation() {
var listOfPrNumbers = [];
var k;
// this loop will just make a list of Pull Request Numbers
for (k = 0; k < listOfPrObjects.length; k++){
var currPrNumber = listOfPrObjects[k]['number'];
listOfPrNumbers.push(currPrNumber);
}
// I created a separate list just because... I did it this way because on the github API website it seems
// like the pull request has the same number as the issue it affects. I explain how you can see this down below
listOfNumbersOfTargetedIssues = listOfPrNumbers;
// next loop will make objects that contain information about each pull request.
var n;
for (n = 0; n < listOfPrNumbers; n++){
var ApiLinkForEachPullRequest = gitPullApiLink + "/" + listOfPrNumbers[n];
const mes = await axios.get(ApiLinkForEachPullRequest);
var temp = {OpeningDate: mes['data']['created_at'],
ClosingDate: mes['data']['closed_at'],
IssueLink: mes['data']['_links']['issue']['href']};
//mapPRnumberToCloseOpenDateObjects will be a map where the key is the pull request number and the value
// is the object that stores the open date, close date, and issue link for that pull request. The reason
// why I said I think the pull request number is the same as the number of the issue it affects is because
// if you take any object from the map, say you do mapPRnumberToCloseOpenDateObjects.get(10). You'll
// get an object with a pull request number 10. Now if you take this object and look at it's "IssueLink"
// field, the very last part of the link will have the number 10, and if you look at the github API
// it says for a single issue, you do: /repos/:owner/:repo/issues/:issue_number <---- As you can see,
// the IssueLink field will have this structure and in place of the issue_number, the field will be 10
// for our example object.
mapPRnumberToCloseOpenDateObjects.set(listOfPrNumbers[n], temp);
}
//up to this point, we have the pull request numbers. we will now start getting the commits associated with
//each pull request
var j;
for (j = 0; j < listOfPrNumbers.length; j++){
var currentApiLink = "https://api.github.com/repos/elixir-lang/elixir/pulls/" + listOfPrNumbers[j] + "/commits";
const res = await axios.get(currentApiLink);
//here we map a single pull request to the information containing the commits. I'll just warn you in
// advance: there's another object called mapPRNumbersToCommitObjects. THIS MAP IS DIFFERENT! I know it's
// subtle, but I hope the language can make the distinction: mapPullRequestNumberToCommits will just
// map a pull request number to some data about the commits it's linked to. In contrast,
// mapPRNumbersToCommitObjects will be the map that actually maps pull request numbers to objects
// containing information about the commits a pull request is associated with!
mapPullRequestNumberToCommits.set(listOfPrNumbers[j], res['data']);
}
// console.log("hewoihoiewa");
}
async createCommitObjects(){
var x;
// the initial loop using x will loop over all pull requests and get the associated commits
for (x = 0; x < listOfPrObjects.length; x++){
//here we will get the commits
var currCommitObjects = mapPullRequestNumberToCommits.get(listOfPrObjects[x]['number']);
//console.log('dhsiu');
// the loop using y will iterate over all commits that we get from a single pull request
var y;
for (y = 0; y < currCommitObjects.length; y++){
var currentSHA = currCommitObjects[y]['sha'];
listOfSHAs.push(currentSHA);
var currApiLink = "https://api.github.com/repos/elixir-lang/elixir/commits/" + currentSHA;
const response = await axios.get(currApiLink);
//console.log("up to here");
// here we start extracting some information from a single commit
var currentAuthorName = response['data']['commit']['committer']['name'];
var currentDate = response['data']['commit']['committer']['date'];
var currentFiles = response['data']['files'];
// this loop will iterate over all changed files for a single commit. Remember, every commit has a list
// of changed files, so this loop will iterate over all those files, get the necessary information
// from those files.
var z;
// we create this temporary list of file objects because for every file, we want to make an object
// that will store the necessary information for that one file. after we store all the objects for
// each file, we will add this list of file objects as a field for our bigger commit object (see down below)
var tempListOfFileObjects = [];
for (z = 0; z < currentFiles.length; z++){
var fileInConsideration = currentFiles[z];
var nameOfFile = fileInConsideration['filename'];
var numberOfAdditions = fileInConsideration['additions'];
var numberOfDeletions = fileInConsideration['deletions'];
var totalNumberOfChangesToFile = fileInConsideration['changes'];
//console.log("with file");
var tempFileObject = {fileName: nameOfFile, totalAdditions: numberOfAdditions,
totalDeletions: numberOfDeletions, numberOfChanges: totalNumberOfChangesToFile};
// we add the same file objects to both a temporary, local list and a global set. Don't be tripped
// up by this; they're doing the same thing!
setOfFileObjects.add(tempFileObject);
tempListOfFileObjects.push(tempFileObject);
}
// here we make an object that stores information for a single commit. sha, authorName, date are single
// values, but files will be a list of file objects and these file objects will store further information
// for each file.
var tempObj = {sha: currentSHA, authorName: currentAuthorName, date: currentDate, files: tempListOfFileObjects};
var currPrNumber = listOfPrObjects[x]['number'];
console.log(currPrNumber);
// here we will make a single pull request number to an object that will contain all the information for
// every single commit associated with that pull request. So for every pull request, it will map to a list
// of objects where each object stores information about a commit associated with the pull request.
mapPRNumbersToCommitObjects.set(currPrNumber, tempObj);
}
}
return mapPRNumbersToCommitObjects;
}
startParsingPullRequests() {
this.getListOfPullRequests(gitPullApiLink + "?state=all").then(() => {
this.getCommitsForEachPullRequestAndPRinformation().then(() => {
this.createCommitObjects().then((response) => {
console.log("functions were successful");
return mapPRNumbersToCommitObjects;
}).catch((error) => {
console.log("printing first error");
// console.log(error);
})
}).catch((error2) => {
console.log("printing the second error");
console.log(error2);
})
}).catch((error3) => {
console.log("printing the third error");
// console.log(error3);
});
}
//adding some getter methods so they can be used to work with whatever information people may need.
//I start all of them with the this.startParsingPullRequests() method because by calling that method it gets all
// the information for the global variables.
async getSetOfFileObjects(){
var dummyMap = this.startParsingPullRequests();
return setOfFileObjects;
}
async OpenCloseDateObjects(){
var dummyMap = this.startParsingPullRequests();
return mapPRnumberToCloseOpenDateObjects;
}
async getNumbersOfTargetedIssues(){
var dummyMap = this.startParsingPullRequests();
return listOfNumbersOfTargetedIssues;
}
}
I then try to play around and run the function to make sure all the data I need is there by doing:
var dummy = new PullRequestParser();
var dummyMap = dummy.startParsingPullRequests();
And when I run it on webstorm using:
node PullRequestParser.js
It will print out some pull request numbers, then stop about halfway with a 403 error. I know what the 403 error is, but I'm wondering if there's anything on my end to stop it from happening, or is it just a matter of working with another repo that won't throw me this error. Thanks!
The 403 error from the server means that your access is forbidden. You either need to use different credentials (that is, log in as a user with that access), not use that repository, or gracefully handle the error and do something else. Retrying will not be effective, since GitHub wouldn't be very secure if it just let you have access to things you weren't supposed to.

Getting this error Uncaught RangeError: Maximum call stack size exceeded

I am getting this error while uploading the CSV files in my website.
Screenshot attached below.
I can see my data of csv file while debugging but it is stopping me to proceed further but i am not able to get this error, I have searched that on google to but they are not relevant to this.
I am using the library
https://d3js.org/d3-dsv.v1.min.js
The code I am trying to upload file is below.
function file(event){
var uploadFileEl = document.getElementById('upload');
if(uploadFileEl.files.length > 0){
var reader = new FileReader();
reader.onload = function(e) {
fileProcess(reader.result.split(/\[\r\n\]+/));
}
reader.readAsText(uploadFileEl.files\[0\]);
}
}
function fileProcess(data) {
var lines = data;
//Set up the data arrays
var time = \[\];
var data1 = \[\];
var data2 = \[\];
var data3 = \[\];
var headings = lines\[0\].split(','); // Splice up the first row to get the headings
var headerCheckbox = document.getElementById('includeHeader');
if(headerCheckbox.checked == true){
for (var j=1; j<lines.length; j++) {
var values = lines\[j\].split(','); // Split up the comma seperated values
// We read the key,1st, 2nd and 3rd rows
time.push(values\[0\]); // Read in as string
// Recommended to read in as float, since we'll be doing some operations on this later.
if (values\[0\] =="" || values\[0\] == null )
{
delete values\[0\];
delete values\[1\];
delete values\[2\];
delete values\[3\];
}
else{
data1.push(parseFloat(values\[1\]));
data2.push(parseFloat(values\[2\]));
data3.push(parseFloat(values\[3\]));
}
}
The error I am getting is on this line
fileProcess(reader.result.split(/[\r\n]+/));
What could be the reason for this.
That error means that you are filling the call stack.
If you call a function that returns a function recursively and doesn't have a stop condition or it never fulfils it's stop condition that error will be throwed.
It appears that fileProcess(reader.result.split(/[\r\n]+/)) it's being called recursively and it's filling the call stack.
Don't know exactly where, as I don't see any recursive calls in the code you posted, so I can't help you farer, but I hope this can shed light into your problem.
P.S: If you think there's some relevant extra code that you didn't post and edit your question leave me a comment and I'll update my answer aswell.

Getting array values from multidimensional array in JavaScript

I'm in need of some minor assistance. I'm having trouble getting an array (larray3) populated with two other array objects (larray1 and larray2) to pass both from data.js into the subsequent model.js and view.js. Data.js correctly builds the multidimensional array however when the results are received in model.js/view.js I only receive the results for larray1. Because only the first values come thru I cannot tell if both larray1 and larray2 are actually passing thru. Can someone please tell me how I should alter my syntax in either model.js or view.js to access both array values or what else I could change? Thank you in advance.
data.js.
function getCountries(done) {
var sqlite3 = require('sqlite3').verbose();
var file = 'db/locations.sqlite3';
var db = new sqlite3.Database(file);
var larray1 = [];
var larray2 = [];
var larray3 = [];
db.all('SELECT * FROM Country', function(err, rows) {
// This code only gets called when the database returns with a response.
rows.forEach(function(row) {
larray1.push(row.CountryName);
larray2.push(row.CountryCode);
})
larray3.push(larray1);
larray3.push(larray2);
return done(larray3[0], larray3[1]);
});
db.close();
}
model.js
function Countries(done) {
//Pull location values from data
return getCountries(done);
}
view.js
function viewCountries() {
var viewCou = Countries(function(results) {
// Code only gets triggered when Countries() calls return done(...);
var container = document.getElementById('country-select');
var fragment = document.createDocumentFragment();
results.forEach(function(loc, index) {
var opt = document.createElement('option');
opt.innerHTML = loc;
opt.value = loc;
fragment.appendChild(opt);
});
container.appendChild(fragment);
})
}
In data.js you send two arguments to the done callback:
return done(larray3[0], larray3[1]);
This done function is passed through in your model.js:
return getCountries(done);
And that done is passed in from view.js:
Countries(function(results) { // ...
So it is this anonymous function (function(results) {...}) that is called in data.js. But notice that this function only has one parameter, so you're doing nothing with the second argument that data.js sends. result gets the value of larray3[0], but larray3[1] is not captured anywhere.
You could solve this in different ways. Personally, I think the design with two arrays is wrong from the start. I would not separate data that belongs in pairs (name and code) into two different arrays.
Instead make an array of objects, and pass that single array around:
In data.js:
rows.forEach(function(row) {
larray1.push({
name: row.CountryName,
code: row.CountryCode
});
})
return done(larray1);
In view.js:
opt.textContent = loc.name;
opt.value = loc.code;
Side-note: .textContent is preferred over .innerHTML when assigning plain text.

Sharepoint 2010 JSOM getEnumerator 'The collection has not been initialized. It has not been requested...'

I'm no stranger to this error or the various solutions, but this one has me scratching my head. I'm using JavaScript object model to get all a list of all the files in a given folder. I get the error on the getEnumerator in the code below. I've stripped the code down to the bare minimum:
function getFilesInFolder() {
var folderServerRelativeUrl = folderPath + ID;
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(documentLibraryName);
var query = SP.CamlQuery.createAllItemsQuery();
query.set_folderServerRelativeUrl(folderServerRelativeUrl);
//Update "web part" link
$("#doclink").attr('href',folderServerRelativeUrl);
files = list.getItems(query)
context.load(files, 'Include(Id)');
context.executeQueryAsync(Function.createDelegate(this, this.OnSuccess), Function.createDelegate(this, this.OnFailure));
}
function OnSuccess()
{
//ERROR Next Line:
var listItemEnumerator = this.files.getEnumerator();
var table = $("#attachments");
while (listItemEnumerator.moveNext())
{
console.log("Found a file");
}
}
The code is called as such in the beginning of the file:
$(document).ready(function(){
//Other code...
ExecuteorDelayUntilScriptLoaded(getFilesInFolder,"sp.js");
});
I've tried a ton of variations on this AND it used to work (not sure what changed either server or client-side).
I tried your code, since I couldnt see any obvious errors, and it works.
I hardcoded the folderServerRelativeUrl as it works in Swedish:
"/sites/intranet/dokument" is my root web and the "Documents" folder.
You can try in the broswer "sitecollection/_api/web/getFolderByServerRelativeUrl('/path/to/folder/')"
To see if the url u are using is a correct one.
You could also set a breakpoint in your onsuccess and look in the console: files.get_count() to see if you have any results.
Your load is fine, so dont worry about that!
function getFilesInFolder() {
var folderServerRelativeUrl = "/sites/intranet/dokument";
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle("Dokument");
var query = SP.CamlQuery.createAllItemsQuery();
query.set_folderServerRelativeUrl(folderServerRelativeUrl);
//Update "web part" link
// $("#doclink").attr('href',folderServerRelativeUrl);
files = list.getItems(query)
context.load(files, 'Include(Id)');
context.executeQueryAsync(Function.createDelegate(this, this.OnSuccess), Function.createDelegate(this, this.OnFailure));
}
function OnSuccess()
{
//ERROR Next Line:
var listItemEnumerator = this.files.getEnumerator();
var table = $("#attachments");
while (listItemEnumerator.moveNext())
{
console.log("Found a file");
}
}
This error basically means that resulting object (this.files variable) has not been requested from the server once the iteration is performed. This error would be reproduced by executing simultaneously several queries (basically invoking getFilesInFolder several times).
Given the fact that result object (this.files) is stored in global scope (window object) i would strongly suggest against this approach of getting list items, instead you could consider to store the result in local scope as demonstrated below:
function getFilesInFolder() {
var folderServerRelativeUrl = "/Documents/Archive";
var context = new SP.ClientContext.get_current();
var web = context.get_web();
var list = web.get_lists().getByTitle(documentLibraryName);
var query = SP.CamlQuery.createAllItemsQuery();
query.set_folderServerRelativeUrl(folderServerRelativeUrl);
var items = list.getItems(query)
context.load(items, 'Include(Id)');
context.executeQueryAsync(
function(){
if(items.get_count() > 0)
{
console.log('Found a file(s)');
}
},
function(sender,args){
console.log(args.get_message());
});
}
With such approach you could easily avoid conflicts with overriding resulting object and therefore getting this exception.

how to access angular $scope from a javascript function

i have added a functionality to parse a CSV file in javascript. I want to assign the parsed data to $scope.data.arr. Currently, the below code gives error "Uncaught ReferenceError: scope is not defined". I am newbie to AngularJS and I have followed the official angular tutorials.
The code is:
application.js
'use strict';
/* Application module */
var ddvApp = angular.module('ddvApp', ['ddvControllers']);
dataController.js
'use strict';
/*Data Controller*/
var ddvControllers = angular.module('ddvControllers', []);
ddvControllers.controller('DataController', ['$scope', function($scope){
$scope.data = {}; //created a empty data object.
}]);
read-csv.js
function handleFiles(files) {
// Check for the various File API support.
if (window.FileReader) {
// FileReader are supported.
getAsText(files[0]);
}
else {
alert('FileReader are not supported in this browser.');
}
}
function getAsText(fileToRead) {
var reader = new FileReader();
// Read file into memory as UTF-8
reader.readAsText(fileToRead);
// Handle errors load
reader.onload = loadHandler;
reader.onerror = errorHandler;
}
function loadHandler(event) {
var csv = event.target.result;
scope.data.arr = processData(csv); //this is the line of code where i want to assign the parsed data to the angularjs $scope.
}
function processData(csv) {
var allTextLines = csv.split(/\r\n|\n/);
var lines = [];
for (var i = 0; i < allTextLines.length; i++) {
var data = allTextLines[i].split(',');
var arr = [];
for (var j = 0; j < data.length; j++) {
arr.push(data[j].trim());
}
lines.push(arr);
}
return lines;
}
function errorHandler(event) {
if(event.target.error.name == "NotReadableError") {
alert("Cannot read file !");
}
}
--UPDATE Problem Statement.
The handleFiles() function is called whenever user selects a new csv file to be parsed.
html code:
<input type="file" id="csvFileInput" onchange="handleFiles(this.files)" accept=".csv">
I firstly implemented the code to parse the CSV in javascript. I am using this data to render a graph on html canvas. I wanted a functionality where the user just selects a different csv file with updated data and the graph should update itself without further inputs. I added angularjs because (in future implementations) the file and graph need to be saved to a db. Also, some data can be requested from server instead of user loading it using a csv file.
While you can get acccess to the $scope via angular's element() function it looks like this is code that would be better put into an angular service.
If doing that is for some reason not an option, you need a reference to a DOM element that belongs to your controller.
Once you have that, you can do
angular.element(myDomElement).scope()
and that should give you a reference.
You can also use element.find() with css selectors but be aware that if you have not loaded JQuery you're left with a limited set of selectors (tag-names only).
See Docs for angular.element
What should be done is to copy all the JavaScript from read-csv.js and put it into dataController.js below this line:
$scope.data = {}; //created a empty data object.
define arr as an array:
$scope.data = {
arr: []
};
add a '$' in front of scope so that line should be:
$scope.data.arr = processData(csv);
This should fix the issue.
The proper way to do this is to put the functions in your read-csv.js file into a service, and then handle events using $rootScope.broadcast and $scope.$on.
So it may look something like:
module.factory('readCsv' ['$rootScope', function(rootScope){
var readCsv = {};
// load readCsv with read-csv.js functions
readCsv.loadHandler = function(event){
var csv = readCsv.processData(event.target.result);
$rootScope.$broadcast('$csvEvent', csv);
};
return readCsv;
}]).controller('DataController', ['$scope', function($scope){
$scope.data = {};
$scope.$on('$csvEvent', function(csv){
$scope.data.arr = csv;
};
}]);

Categories