Changes in webhook payload for reviewed objects in Autodeks Forge - autodesk-forge

I had a webhook listening to VersionModified for files on a folder.
I was tracking reviews approvals checking the payload body to see if the revision was approved or not.
var reviewInfo = body.payload.CustomMetadata.DmSysApproveState?.ToString();
if (reviewInfo == null || !reviewInfo.Contains("\"label\":\"Approved\"")) return Ok();
Since this week, that information is not longer available in the payload.
Are there other way to know the status of an issue in ACC/BIM360?

Related

Maximum Number of Request Parameters for fetchAll()

Is there a maximum number of fetch requests that can be made in a single call to URLFetchApp.fetchAll()? I have limited experience with AppScripts and could not find anything on the docs indicating that there is a limit on the number of requests, but I'm curious to see if a limit does exist. Any input is welcome.
Answer:
Expanding on the research undertaken by Tanaike in his benchmarking research for UrlFetchApp.fetchAll(). I was able to make 2199 requests successfully, but more triggered a rate limit on the endpoint so I was unable to test furhter.
Research:
After reading the aforementioned benchmark, I took to Apps Script to see if I could find a limit for the method. I used httpbin for testing, as this is what is provided by Google in the example snippet for fetchAll(requests):
// Make both a POST request with form data, and a GET request.
var resumeBlob = Utilities.newBlob('Hire me!', 'text/plain', 'resume.txt');
var formData = {
'name': 'Bob Smith',
'email': 'bob#example.com',
'resume': resumeBlob
};
// Because payload is a JavaScript object, it is interpreted as
// as form data. (No need to specify contentType; it defaults to either
// 'application/x-www-form-urlencoded' or 'multipart/form-data')
var request1 = {
'url': 'https://httpbin.org/post',
'method' : 'post',
'payload' : formData
};
// A request may also just be a URL.
var request2 = 'https://httpbin.org/get?key=value';
UrlFetchApp.fetchAll([request1, request2]);
I modified this by initialising an array of length n and populating it with requests like so:
function fetchAllTester() {
var resumeBlob = Utilities.newBlob('Hire me!', 'text/plain', 'resume.txt');
var formData = {
'name': 'Bob Smith',
'email': 'bob#example.com',
'resume': resumeBlob
}
var request1 = {
'url': 'https://httpbin.org/post',
'method' : 'post',
'payload' : formData
}
let requests = new Array(1000).fill(request1)
UrlFetchApp.fetchAll(requests).map(x => console.log(x.getContentText()))
}
I started with 1000 as this has already been confirmed to work. The results were then as follows:
2000 requests - worked
2500 requests - HTTP 502 returned by httpbin.
2001 requests - worked
2100 requests - worked
2200 requests - HTTP 502 returned by httpbin.
2199 requests - worked
2199 requests - HTTP 502 returned by httpbin.
At this point sending 2199 requests seemed to be intermittantly responsive, but with an HTTP 502: Bad Gateway error which pointed to the problem coming from outside of the fetchAll() method.
Conclusion:
It appears that is a limit on UrlFetchApp.fetchAll() exists, then it is high.
From the Quotas for Google Services page, the only limit on calls that is listed is:
Feature
Consumer (e.g., gmail.com) and G Suite free edition (legacy)
Google Workspace accounts
URL Fetch calls
20,000 / day
100,000 / day
My assumption therefore, based on the testing I have done and the documentation provided by Google, is that the method is limited only by regular Apps Script Quotas; that is to say, 20,000 requests per day for consumer accounts and 100,000 requests per day for Workspace accounts.
References:
Benchmark: fetchAll method in UrlFetch service for Google Apps Script - tanaikech.github.io
Class UrlFetchApp - fetchAll(requests) | Apps Script | Google Developers
Quotas for Google Services | Apps Script | Google Developers

How to validate a PayPal webhook signature for Google Script?

After a PayPal order is complete, it sends a webhook to a google script URL. Before information is sent, it has to validate the signature. Down below is a link to a Java example.
https://developer.paypal.com/docs/api-basics/notifications/webhooks/notification-messages/#
How to validate a PayPal webhook signature for Google Script?
I've looked through the google-apps-script and PayPal tags for a solution, but it only provides IPN solutions. From my understanding that's an older method. Perhaps that's the only way. I don't know. I'm new to API handling, so I may be wording the problem incorrectly.
EDIT: Did some more digging and was able to receive and process a paypal webhook.
function doPost(e) {
var params = JSON.stringify(e.postData.contents);
params = JSON.parse(params);
var myData = JSON.parse(e.postData.contents);
SpreadsheetApp.flush();
return HtmlService.createHtmlOutput("post request received");
}
The next problem is setting up the PayPal Smart button to send the correct webhook. The correct webhook event would be "Checkout order completed" (tested via PayPal's Webhooks simulator). This webhook contains the order information. The examples over at PayPal Smart Button Demo calls onAuthorize as a webhook. This webhook does not contain the information I want. I'll make a separate post about this.
onAuthorize: function(data, actions) {
return actions.payment.execute().then(function() {
window.alert('Payment Complete!');
});
}
So I guess no validation is required for webhooks, maybe.
IPN is a much older solution that is separate to webhooks.
Regarding webhook verification, you linked to a page for DIY verification, where you calculate whether the signature matches. The example is in Java, but can conceivably be adapted to any language (with some considerable work)
The alternative way to verify a webhook is via the webhooks API, posting the message back using this API call: https://developer.paypal.com/docs/api/webhooks/v1/#verify-webhook-signature
Avoid using the PayPal-*-SDKs, since that documentation was written before those SDKs were deprecated
After a lot of trial and error, I got a semi-working system.
function doPost(e) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Debug').getRange('A1').setValue("Recieved doPostCommand");
var myData = JSON.parse(e.postData.contents);
try {
if (myData.event_type == 'PAYMENTS.PAYMENT.CREATED') {
var email = myData.resource.payer.payer_info.email
var name = myData.resource.payer.payer_info.first_name + " " + myData.resource.payer.payer_info.last_name
var payID = myData.resource.id
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Debug').getRange('A3').setValue(email);
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Debug').getRange('A4').setValue(name);
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Debug').getRange('A5').setValue(payID);
} else if (myData.event_type == 'PAYMENT.SALE.COMPLETED'){
var payID = myData.resource.parent_payment
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Debug').getRange('A7').setValue(payID);
}
} catch (e) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Debug').getRange('E1').setValue("Error: " + e);
}
SpreadsheetApp.flush();
return HtmlService.createHtmlOutput("post request received");
}
I found the webhooks to work some of the time. Sometimes they failed while other times they are received. When they are received, the webhooks don't necessarily arrive in a specific order.
So to confirm a payment complete, append the customer details (including PayPal ID) to a "customer" sheet. To a separate "confirmed payment id" sheet, append the PayPal IDs. From there, add a listener to when a PAYMENTS.PAYMENT.CREATED or a PAYMENT.SALE.COMPLETED webhook is received, check if there are matching PayPal IDs. The "confirmed payment id" sheet will act as a buffer. From there set the payment completed column to true for the correct customer and delete the PayPal ID from the buffer sheet.
That's how I would do it, but webhooks seem to be trash atm. I'll have to resort to some eCommerce service. I hope someone finds this useful. Cheers

Google forms approval workflow

I'm trying to set up a workflow using Google Forms, Sheets and Apps Script. For a simple example lets say I am setting up a leave form for a company. I have a form for the employee enters their leave details. On submit the details are populated into a sheet and an email is sent to an someone for approval.
This is where I am getting stuck. What is the best way to approve the leave requests? I can just edit the sheet and once the column has been changed from unapproved to approved and email could be sent but I was hoping to avoid having to edit the sheet at all.
Is there a better way to handle this workflow?
Assume that 3 people need to approve the request. The approvers that need to review the request are approvers X,Y,Z.
User X gets an Email requesting that they review the request
The email that user X gets will use HTML
The HTML will have a link in it
A link in HTML submits a GET request
Whenever you click a link, or enter a url into the browser address
bar, a GET request is made to the url
An Apps Script Web App is an Apps Script project that is published as
a Web App
A Web App has a url that can be used to make a request to it
When the url (link) is used, a GET request is made
A GET request made to an Apps Script Web App will run the doGet()
function
Once that doGet() function run, you can have your Apps Script code do
anything that you want
It can get information sent in to it from a search string
Then send out another email to user Y
So, when user X clicks the link, the url needs to have information
appended to the url in the form of a search string
httpz://the link?whichUser=X&approved=true
You can get the user and whether it was approved or not from the "event object" often designated by the letter "e"
function doGet(e) {
var whatUserJustReviewed,isItApproved;
//Get the values passed in from "e" which is the event object
eventParam = e.parameter;
whatUserJustReviewed = eventParam.whichUser;
Logger.log(' whatUserJustReviewed : ' + whatUserJustReviewed )
isItApproved = eventParam. approved;
switch (whatUserJustReviewed) {
case "X"
if (isItApproved === 'true') {
// //Send an email to the next user which is Y
}
break;
case "Y"
if (isItApproved === 'true') {
// //Send an email to the next user which is Z
}
break;
case "Z"
if (isItApproved === 'true') {
//Send an email to all involved who need to know that it was approved.
}
break;
default:
console.error('There was an error getting user or status');
};
}

Use Google Script's Web App as Webhook to receive Push Notification directly

My Goal: Changes in Google Drive => Push Notification to https://script.google.com/a/macros/my-domain/... => App is pushed to take action.
I don't want to setup an middle Webhook agent for receiving notification. Instead, let the Web App (by Google Script) to receive it and be pushed directly.
Since the relevant function is quite undocumented (just here: https://developers.google.com/drive/web/push) , below is the code I tried but failure.
1. Is above idea feasible??
2. My code doPost(R) seems cannot receive notification (R parameter) properly. Anyway, no response after I change the Google Drive. Any problem? (I have tried to log the input parameter R so as to see its real structure and decide if the parameter Obj for OAuth is the same as normal Drive App, but error occur before log)
function SetWatchByOnce(){
var Channel = {
'address': 'https://script.google.com/a/macros/my-domain/.../exec',
'type': 'web_hook',
'id': 'my-UUID'
};
var Result = Drive.Changes.watch(Channel);
...
}
function doPost(R) {
var SysEmail = "My Email";
MailApp.sendEmail(SysEmail, 'Testing ', 'Successfully to received Push Notification');
var Response = JSON.parse(R.parameters);
if (Response.kind == "drive#add") {
var FileId = Response.fileId;
MyFile = DriveApp.getFolderById(FileId);
...
}
}
function doGet(e) {
var HTMLToOutput;
var SysEmail = "My Email";
if (e.parameters.kind) {
//I think this part is not needed, since Push Notification by Drive is via Post, not Get. I should use onPost() to receive it. Right?
} else if (e.parameters.code) {
getAndStoreAccessToken(e.parameters.code);
HTMLToOutput = '<html><h1>App is successfully installed.</h1></html>';
} else { //we are starting from scratch or resetting
HTMLToOutput = "<html><h1>Install this App now...!</h1><a href='" + getURLForAuthorization() + "'>click here to start</a></html>";
}
return HtmlService.createHtmlOutput(HTMLToOutput);
}
....
Cloud Functions HTTP trigger(s) might also be an option ...
(which not yet existed at time of this question). this just requires setting the trigger URL as the notification URL, in the Google Drive settings - and adding some NodeJS code for the trigger; whatever it shall do. one can eg. send emails and/or FCM push notifications alike that. that trigger could also be triggered from App Script, with UrlFetchApp and there is the App Script API. one can have several triggers, which are performing different tasks (App Script is only one possibilty).
Cicada,
We have done similar functions to receive webhooks/API calls many times. Notes:
to get R, you need: var Response = R.parameters and then you can do Response.kind, Response.id, etc.
Logger will not work with doGet() and doPost(). I set it up a write to spreadsheet -- before any serious code. That way I know if it is getting triggered.

Google Maps API - Finding Waypoint That Caused Error

We have been developing an ASP.NET application that works with the Google Maps API to assist in logistics and planning for a small shipping company in our area. One of the features is for the company to input a list of customers and the system will package up all the customer addresses as a series of waypoints, send it off to Google, and get back the direction information for the route.
One problem that we have been having is that many of the addresses in our database are not great and will often times are not able to be processed by Google Maps. When we do this we receive back an error code, but I would like to be able to determine which waypoint was the one to fail (that way the client can then go and fix the address in the database).
EDIT Here is a chunk of the code that handles the initialization and current error handling:
function initialize() {
if (GBrowserIsCompatible()) {
map = new GMap2(document.getElementById("map"));
map.addControl(new GSmallMapControl());
gdir = new GDirections(map);
GEvent.addListener(gdir, "load", onGDirectionsLoad);
GEvent.addListener(gdir, "error", handleErrors);
if (document.getElementById("<%=hiddenWayPoints.ClientID %>").getAttribute("value") != '')
{
setDirections();
}
}
}
function setDirections() {
var waypoints = new Array();
var str = document.getElementById("<%=hiddenWayPoints.ClientID%>").getAttribute("value");
waypoints = document.getElementById("<%=hiddenWayPoints.ClientID %>").getAttribute("value").split(":");
gdir.loadFromWaypoints(waypoints, {getSteps:true});
}
function handleErrors(){
if (gdir.getStatus().code == G_GEO_UNKNOWN_ADDRESS)
alert("No corresponding geographic location could be found for one of the specified addresses. This may be due to the fact that the address is relatively new, or it may be incorrect.\nError code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_SERVER_ERROR)
alert("A geocoding or directions request could not be successfully processed, yet the exact reason for the failure is not known.\n Error code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_MISSING_QUERY)
alert("The HTTP q parameter was either missing or had no value. For geocoder requests, this means that an empty address was specified as input. For directions requests, this means that no query was specified in the input.\n Error code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_BAD_KEY)
alert("The given key is either invalid or does not match the domain for which it was given. \n Error code: " + gdir.getStatus().code);
else if (gdir.getStatus().code == G_GEO_BAD_REQUEST)
alert("A directions request could not be successfully parsed.\n Error code: " + gdir.getStatus().code);
else alert("An unknown error occurred.");
}
The problem is that the LoadFromWayPoints() method in the GDirections object doesn't seem to return status based on each individual waypoint, but from just the entire list (if one fails it all fails).
The only real solution I can think of (without performing checks in other areas of the system) is to send each waypoint off to Google Maps in a separate request and check it's validity that way prior to sending off the entire waypoints list for the GDirections object, but that seems incredibly inefficient (especially when dealing with a larger set of customer locations).
If you are using the GClientGeocoder object to do your requests, then you will get back an appropriate response code for each getLocations call:'
function logAddress (response)
{
if (!response || response.Status.code != 200)
{
// log the response.name and the response.Status.code
return;
}
// otherwise everything was fine
}
for (var i = 0; i < addresses.length; i++)
{
var geocoder = new GClientGeocoder ();
geocoder.getLocations (addresses[i], logAddress);
}
Their are various response codes, but I am guessing you want to inform the user when you get a 602 - Unknown Address.
EDIT:
Yep, you will only get a single error callback for loadFromWaypoints for the entire directions request. What you are doing is more than just a simple geocoding request, you are actually generating directions and rendering overlays to a map for a sequence of addresses values. I suggest a couple of solutions:
You could do a getLocation request before your loadFromWaypoints request (as you suggested) and then use the latitude,longitude data returned for each address as the parameter for your loadFromWayPoints. This splits the geocoding processing out of the loadFromWayPoints request, but adds the extra round trip for each geocoding lookup. This is really something you only want to do once, when the user first enters the address (see next).
When the user enters the address information for the first time you can do a GClientGeocoder getLocations lookup at the time and get the latitude,longitude to store in your database along with the address. That way, if the user enters an address that can't be geocoded, then you can ask them to re-enter the address, or perhaps let them select the location on a google map (and get the lat,lng from that). This doesn't solve the problems with address data you have now, but perhaps you can write some code to run through you existing data (offline) and flag the addresses in the db that are not able to be geocoded.