I'm going to start by saying it's immensely frustrating half knowing how to do something but never quite being able to finish; this is another one of those projects for me.
Scenario: Using a Google Sheet and Apps Script I am attempting to update several User records in Zendesk using their API.
I think i probably have most if it right (i stand to be corrected of course) with the following script however I just cannot get it to update any records. I suspect it might be to do with how the array is presented (an area I sadly don't know enough about).
function updateManyUsers(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var [headers, ...rows] = sheet.getDataRange().getValues();
var data = {}
var items = []
rows.forEach(function(r) {
var obj={}
r.forEach(function (c, j) {
obj[headers[j]] = c
})
var data = {}//moved
data['users'] = obj // moved this inside your loop
items.push(data) // pushed the object into the items array
})
Logger.log("Log JSON Stringify Items: " + JSON.stringify(items))
items.forEach(function(i) { // added this to loop over objects in items
var url = 'https://itsupportdesk1611575857.zendesk.com/api/v2/users/update_many.json'; //https://developer.zendesk.com/api-reference/ticketing/users/users/#update-user
var user = 'myemailaddresshere/token';
var pwd = 'mytoken';
var options = {
'method' : 'PUT',
'headers': {
'Authorization': "Basic " + Utilities.base64Encode(user + ':' + pwd)
},
'payload' : JSON.stringify(i),
'contentType': 'application/json',
'muteHttpExceptions': true
};
UrlFetchApp.fetch(url, options);
Logger.log(i)
var response = UrlFetchApp.fetch(url, options);
Logger.log(response);
})
}
I've gone through as much as I can following the documentation, I know i had the end points incorrect and the method(?) too (set to Post instead of Push). I have gone through varying error messages that I have tried to act upon and this is my current one:
This is an image of the data in my sheet
Suplimental: In order to get better at this i would like to put myself on a learning path but am unsure what the path is; most of my automation work and scripting is done using Google Apps script so would people recommend a JavaScript course? I alter between that and Python not knowing what would suit me best to get a better understanding of this kind of issue.
Many thanks in advance.
From your endpoint in your script, I thought that you might have wanted to use "Batch update". Ref If my understanding is correct, the following sample curl in the official document can be used. Ref
curl https://{subdomain}.zendesk.com/api/v2/users/update_many.json \
-d '{"users": [{"id": 10071, "name": "New Name", "organization_id": 1}, {"external_id": "123", "verified": true}]}' \
-H "Content-Type: application/json" -X PUT \
-v -u {email_address}:{password}
If this sample curl command is converted to Google Apps Script using your script, how about the following modification?
Modified script:
function updateManyUsers2() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
var [headers, ...rows] = sheet.getDataRange().getDisplayValues();
var users = rows.map(r => {
var temp = {};
headers.forEach((h, j) => {
if (r[j] != "") temp[h] = r[j];
});
return temp;
});
var url = 'https://itsupportdesk1611575857.zendesk.com/api/v2/users/update_many.json';
var user = 'myemailaddresshere/token';
var pwd = 'mytoken';
var options = {
'method': 'PUT',
'headers': {
'Authorization': "Basic " + Utilities.base64Encode(user + ':' + pwd)
},
'payload': JSON.stringify({ users }),
'contentType': 'application/json',
'muteHttpExceptions': true
};
var response = UrlFetchApp.fetch(url, options);
Logger.log(response.getContentText());
}
Note:
From the official document, it says Bulk or batch updates up to 100 users.. So, when you want to use more data, please modify the above script. Please be careful about this.
If an error occurs, please check the values of users, user and pwd, again.
Reference:
fetch(url, params)
Related
This is my first time working with XML and I am not that techy but trying to get to understand programming to make my work easier. I am using Google App script and finding it a challenge in passing XML data that I get via API.
I need to get this data so that I can set the specific values to Google sheets using google app script.
I am not sure how to iterate/loop through elements to get everyone's data and then set it to google sheet.
And here is the code I have worked on so far. When I log to say the first name, I only get one name instead of about 50 names in the system. Any help here will highly be appreciated.
ak ='key'
start = '2019-01-01'
end = '2019-12-31'
function getData() {
var options = {
method: 'get',
headers: {
Authorization: 'Bearer ' + ak
}
};
var url = 'https://data.purelyhr.com/daily?ak='+ ak + '&sDate=' + start + '&eDate=' + end + '&TimeOffTypeName';
var response = UrlFetchApp.fetch(url).getContentText();
var document = XmlService.parse(response);
var root = document.getRootElement();
//set variables to data from PurelyHR
var TimeOffDate = root.getChild('Request').getChild('TimeOffDate').getText();
var TimeOffDayOfWeek = root.getChild('Request').getChild('TimeOffDayOfWeek').getText();
var TimeStart = root.getChild('Request').getChild('TimeStart').getText();
var TimeEnd = root.getChild('Request').getChild('TimeEnd').getText();
var TimeOffHours = root.getChild('Request').getChild('TimeOffHours').getText();
var TimeOffTypeName = root.getChild('Request').getChild('TimeOffTypeName').getText();
var LoginID= root.getChild('Request').getChild('LoginID').getText();
var Firstname = root.getChild('Request').getChild('Firstname').getText();
var Lastname = root.getChild('Request').getChild('Lastname').getText();
var UserCategory = root.getChild('Request').getChild('UserCategory').getText();
var SubmittedDate = root.getChild('Request').getChild('SubmittedDate').getText();
var Deducted = root.getChild('Request').getChild('Deducted').getText();
var Comment = root.getChild('Request').getChild('Comment').getText();
//populate the sheet with variable data
Logger.log(response)
}
Sample response
<?xml version='1.0' encoding='ISO-8859-1'?>
<DataService>
<Request ID="1253" Status="Approved">
<TimeOffDate>2020-02-07</TimeOffDate>
<TimeOffDayOfWeek>Friday</TimeOffDayOfWeek>
<TimeStart></TimeStart>
<TimeEnd></TimeEnd>
<TimeOffHours>8.000</TimeOffHours>
<TimeOffTypeName>Annual Vacation</TimeOffTypeName>
<LoginID>testuser</LoginID>
<Firstname>test</Firstname>
<Lastname>user</Lastname>
<UserCategory></UserCategory>
<SubmittedDate>2019-10-03</SubmittedDate>
<Deducted>Yes</Deducted>
<Comment>
<![CDATA[* time-off request created by administrator]]>
</Comment>
</Request>
<Request ID="126292" Status="Approved">
<TimeOffDate>2020-02-07</TimeOffDate>
<TimeOffDayOfWeek>Friday</TimeOffDayOfWeek>
<TimeStart></TimeStart>
<TimeEnd></TimeEnd>
<TimeOffHours>8.000</TimeOffHours>
<TimeOffTypeName>Annual Vacation</TimeOffTypeName>
<LoginID>usertwo</LoginID>
<Firstname>user</Firstname>
<Lastname>two</Lastname>
<UserCategory></UserCategory>
<SubmittedDate>2019-10-15</SubmittedDate>
<Deducted>Yes</Deducted>
<Comment>
<![CDATA[Neil (as my mentor)]]>
</Comment>
</Request>
If I understand correctly, the problem is that you have multiple <Request> elements, but your code is only looking at one of them. This is because you're using getChild(), which will only provide the first element with the given name.
I can't fully test that this works because you haven't provided the XML text, but you should instead use the getChildren() method to get all of the Request elements. Then you can loop through that.
function getData() {
var options = {
method: 'get',
headers: {
Authorization: 'Bearer ' + ak
}
};
var url = 'https://data.purelyhr.com/daily?ak=' + ak + '&sDate=' + start + '&eDate=' + end + '&TimeOffTypeName';
var response = UrlFetchApp.fetch(url).getContentText();
var document = XmlService.parse(response);
var root = document.getRootElement();
//set variables to data from PurelyHR
var requestElements = root.getChildren('Request'); // Get all <Request> elements
var requestObjects = []; // Request objects for logging / eventual printing
for (var i = 0; i < requestElements.length; i++) {
var request = requestElements[i]; // A single <Request> element
// Add to requestObjects array
requestObjects.push({
TimeOffDate: request.getChild('TimeOffDate').getText(),
TimeOffDayOfWeek: request.getChild('TimeOffDayOfWeek').getText(),
TimeStart: request.getChild('TimeStart').getText(),
TimeEnd: request.getChild('TimeEnd').getText(),
TimeOffHours: request.getChild('TimeOffHours').getText(),
TimeOffTypeName: request.getChild('TimeOffTypeName').getText(),
LoginID: request.getChild('LoginID').getText(),
Firstname: request.getChild('Firstname').getText(),
Lastname: request.getChild('Lastname').getText(),
UserCategory: request.getChild('UserCategory').getText(),
SubmittedDate: request.getChild('SubmittedDate').getText(),
Deducted: request.getChild('Deducted').getText(),
Comment: request.getChild('Comment').getText()
});
}
Logger.log(JSON.stringify(requestObjects));
}
Since I don't know how you're printing, I created an array of request objects and logged that in the sample above. I hope this made sense, but please let me know if you have any questions or if I'm completely off with my response.
I would like to create a custom function that pulls a Drive URL from a file name in Google Sheets.
So, using the code below:
If I have a valid file name in cell A1
The function =getFile(A1) would return the URL
When I run my script from within the script editor, the return value works.
When I run the function getFile() from within my sheet, I get the error below.
My code:
function getFile(cell) {
var filename = encodeURI(cell);
var url = "https://www.googleapis.com/drive/v3/files?fields=files(id,name)&q=name+contains+'" + filename + "' and trashed=false";
var params = {
method: "GET",
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
var res = UrlFetchApp.fetch(url, params).getContentText();
var json = JSON.parse(res);
return res; // outputs response below
if(json){
var objFiles = json.files[0];
var fileID = objFiles.id
var resURL = "https://docs.google.com/spreadsheets/d/" + fileID;
Logger.log(resURL);
//return resURL; // only works when run within script editor
}
}
Error:
"{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Invalid Credentials",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Invalid Credentials"
}
}
"
I'm guessing something's wrong with my Auth token. Can someone direct me to resolving this? Thanks in advance!
Custom functions runs as if run by a anonymous animal(user). ScriptApp.getOAuthToken will return a anonymous token without the required scopes. What you're attempting is not possible, unless the file in question is public.
References:
Custom functions permissions
Custom functions access services
This may be a solution for some needs.
My particular need was: loop through a column of file names and pull the Google Docs URL at a set interval. The code below just loops through filenames in "Column A" of "My Sheet" and returns the value into the adjacent cell of "Column B" (starting at row 2 because I had column headers). I'm not concerned about security because I'm only referencing internal organization files.
To get the code below to work you need to:
Google Sheet Doc Nav > Tools > Script Editor
Create a .gs file & input code below (Referencing The Respective Sheet
Within Script Editor > Edit > Current Project’s Triggers > Name Your Project
Within Script Editor > Edit > Current Project’s Triggers > Click on modal link “No triggers set up. Click here to add one now” > set your time-based trigger (reference replaceFileColumn in the select field within that modal)
My mistake was: thinking that I needed to use a custom function in each cell to do so. (I still don't fully understand the auth reasons why this wouldn't work, so if anyone could explain in lay-man's terms that would be fabulous; my solution is just a workaround for expediency's sake).
In my spreadsheet I have a time-driven trigger calling replaceFileColumn()
Hope this helps someone!
function getMyFile(cell) {
var filename = encodeURI(cell);
var files = DriveApp.getFilesByName(cell);
while (files.hasNext()) {
var file = files.next();
if(file){
var fileValue = file.getUrl();
return(fileValue);
};
};
}
function replaceFileColumn() {
var spreadsheet = SpreadsheetApp.getActive().getSheetByName('My Sheet');
var range = spreadsheet.getRange("A2:A");
var range_update = spreadsheet.getRange("B2:B");
var values = range.getValues();
for (var i = 0; i < values.length; i++) {
var fileName = values[i];
var getFileUrl = getMyFile(fileName);
values[i][0] = getFileUrl;
}
range_update.setValues(values);
}
#I'-'I's answer is correct. Although I'm not sure whether this is what you want, how about this workaround? I have also experienced the same issue. At that time, I had used the following workaround.
Set and get access token using PropertiesService.
The flow is as follows.
Flow:
Set access token every 1 hour by the time-driven trigger.
By this, the access token is updated every 1 hour. Because the expiration time of access token is 1 hour.
When the custom function is run, it gets the access token using PropertiesService.
By this, the access token can be used.
Modified script:
Please install this function as the time-driven trigger. Of course, you can run manually this function.
function setAccessToken() {
PropertiesService.getScriptProperties().setProperty("accessToken", ScriptApp.getOAuthToken());
}
In your script, please modify as follows.
From:
var params = {
method: "GET",
headers: {"Authorization": "Bearer " + ScriptApp.getOAuthToken()},
muteHttpExceptions: true
};
To:
var params = {
method: "GET",
headers: {"Authorization": "Bearer " + PropertiesService.getScriptProperties().getProperty("accessToken")},
muteHttpExceptions: true
};
Note:
In this case, the owner of access token is the owner of project.
I think that this can be also used by CacheService.
Reference:
PropertiesService
I am a newbie in coding.
I am trying to create a function in google app script that acts like a dictionary and pulls out the meaning of the word passed as the argument. Its using the API of oxford dictionaries but its not working. Its showing the error 403. "var response = UrlFetchApp.fetch(url,headers);" shows the error.
function Word_meaning(word){
var url="https://odapi.oxforddictionaries.com:443/api/v1/entries/en/" + word + "/regions=us";
var headers =
{
'Accept': 'application/json',
'app_id': 'abc',
'app_key': '123'
};
var response = UrlFetchApp.fetch(url,headers);
var data = JSON.parse(response.getContentText());
Logger.log(data);
}
A couple of things - why do you include the port number in the API call? My API endpoint for querying Oxford Dictionaries looks different. Also, there's a dash in "od-api".
https://od-api.oxforddictionaries.com/api/v1/entries/en/{word_id}/regions={region}
Testing the link in the address bar, I get the expected server response of "Authorization required" while the URL you provided doesn't seem to exist.
Anyway, the error pops up because the optional 'params' object for the UrlFetchApp.fetch(url, params) method is not constructed properly. The "headers" property must be contained within that object. Somewhat ambiguous here, but please read:
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app#fetch(String,Object)
I was able to get things up and running using the code below.
function getData(word, region){
var word = word || "leprechaun";
var region = region || "us";
var wordId = encodeURI(word);
var baseUrl = "https://od-api.oxforddictionaries.com/api/v1/entries/en/{word_id}/regions={region}";
var app_id = "app_id";
var app_key = "app_key";
var headers = {
"app_id": app_id,
"app_key": app_key
};
var options = {
"headers": headers,
"muteHttpExceptions": true
};
var url = baseUrl.replace("{word_id}", wordId)
.replace("{region}", region);
var res = UrlFetchApp.fetch(url, options);
var responseCode = res.getResponseCode();
if (responseCode == 200) {
var data = JSON.parse(res.getContentText());
} else {
Logger.log(res.getContentText());
}
}
I don't know my JavaScript. So I am using someone else's:
var CONSUMER_KEY = 'xxxx'; // Register your app with Twitter.
var CONSUMER_SECRET = 'xxxx'; // Register your app with Twitter.
function getTwitterUserFollowers(id) {
// Encode consumer key and secret
var tokenUrl = "https://api.twitter.com/oauth2/token";
var tokenCredential = Utilities.base64EncodeWebSafe(
CONSUMER_KEY + ":" + CONSUMER_SECRET);
// Obtain a bearer token with HTTP POST request
var tokenOptions = {
headers: {
Authorization: "Basic " + tokenCredential,
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
},
method: "post",
payload: "grant_type=client_credentials"
};
var responseToken = UrlFetchApp.fetch(tokenUrl, tokenOptions);
var parsedToken = JSON.parse(responseToken);
var token = parsedToken.access_token;
// Authenticate Twitter API requests with the bearer token
var apiUrl = 'https://api.twitter.com/1.1/users/show.json?screen_name='+id;
var apiOptions = {
headers: {
Authorization: 'Bearer ' + token
},
"method": "get"
};
var responseApi = UrlFetchApp.fetch(apiUrl, apiOptions);
var result = "";
if (responseApi.getResponseCode() == 200) {
// Parse the JSON encoded Twitter API response
var tweets = JSON.parse(responseApi.getContentText());
return tweets.followers_count
}
Logger.log(result);
}
source: http://sarahmarshall.io/post/70812214349/how-to-add-twitter-follower-counts-to-a-google
Sarah Marshall provides a pretty awesome how-to on using a Google Sheets script with the Twitter API to get follower counts for a list of Twitter user names. But it times out after about 100 user names:
Service invoked too many times for one day: urlfetch. (line 21, file "Code")
I'm wondering how I can get around the rate limit, or account for it, and return follower counts for more than 100 user names. Any ideas?
The function you are using does too many things: it both obtains a token and uses it to retrieve followers. So, if you are invoking it 100 times, you obtain a token 100 times (and notably, time out on the line obtaining it); but you need it once. You should store the token somewhere, e.g., in the spreadsheet itself since you already have your private data in the script associated with it. Example:
function getToken() {
// Obtain a bearer token with HTTP POST request
var tokenOptions = {
headers: {
Authorization: "Basic " + tokenCredential,
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8"
},
method: "post",
payload: "grant_type=client_credentials"
};
var responseToken = UrlFetchApp.fetch(tokenUrl, tokenOptions);
var parsedToken = JSON.parse(responseToken);
var token = parsedToken.access_token;
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('SheetWithToken').getRange('A1').setValue(token);
}
The last line stores the token string in cell A1 of the sheet named SheetWithToken. You'd invoke this function once, manually from the Script Editor.
The following function does the rest: it can be invoked as a custom function =getFollowers(A2) from the spreadsheet. Custom functions, like other spreadsheet functions, are re-evaluated only when the parameter changes. Thus, if the column with IDs (say, A) has a thousand of entries, you can paste the custom function gradually, for a handful at once.
function getFollowers(id) {
// Authenticate Twitter API requests with the bearer token
var token = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('SheetWithToken').getRange('A1').getValue();
var apiUrl = 'https://api.twitter.com/1.1/users/show.json?screen_name='+id;
var apiOptions = {
headers: {
Authorization: 'Bearer ' + token
},
"method": "get"
};
var responseApi = UrlFetchApp.fetch(apiUrl, apiOptions);
var result = "";
if (responseApi.getResponseCode() == 200) {
// Parse the JSON encoded Twitter API response
var tweets = JSON.parse(responseApi.getContentText());
return tweets.followers_count
}
}
You could even hardcode your access token into the second function, instead of fetching it from the spreadsheet. Twitter's tokens do not expire.
Another thing to consider is to recast the function getFollowers so that it accepts an array of IDs and loops through them, returning an array of follower counts. I don't think this would help, though: you would still have to worry about rate-limit on Twitter side, and at the same time be limited to 30 second execution time limit for custom functions.
I am trying to write some code that submits a ticket automatically with information from a page I created in Apps Script. I have tried numerous examples, but I can't seem to get my code to work.
var headers = {
'Content-type': 'application/json',
'Authorization': 'Basic ' + Utilities.base64Encode(API_KEY + ':X')
};
//Puts together the ticket according to the freshdesk api.
//var payload = '{"helpdesk_ticket":{"description":"' + message + '","subject":"' + subject + '","email":"' + arr[0][0] + '","priority":"' + ticketPriority + '","status":2}}';
//var payload = '{"helpdesk_ticket":{"description": message ,"subject": subject,"email": arr[0][0],"priority": ticketPriority,"status":2}}';
var payload = '{"helpdesk_ticket":{"description":"TEST","subject":"TEST","email":"test#test.com","priority":1,"status":2}}';
//Adds the extensions that are needed to post a new ticket to the end of the url
var url = ENDPOINT + '/helpdesk/tickets.json';
var options = {
'method': 'post',
'headers': headers,
'payload': payload,
muteHttpExceptions: true
};
var response = UrlFetchApp.fetch(url, options);
This is what I currently have. I have gotten it to work once, but only when I do not have any variables being assigned to the 'description' or 'subject' header (the line with the payload variables that is uncommented. When I use that line, a ticket is successfully created). I am not sure why my first or second lines with the payload variables would not work. The variable 'message' is just a String with some new line characters '\n' in it. Does anyone know why this might be happening?
Solved by building the message variable with HTML code and using the 'description_html' property instead of 'description'.