I want to experiment with Google Datastore via Apps Script because I have a current solution based on Google sheets that runs into timeout issues inherent in constantly transacting with Drive files. I've created a test project in Google cloud with a service account and enabled library MZx5DzNPsYjVyZaR67xXJQai_d-phDA33
(cGoa) to handle the Oauth2 work. I followed the guide to start it up here and got all the pertinent confirmation that it works with my token (and that removing the token throws an 'authentication failed prompt').
Now I want to start with a basic query to display the one entity I already put in. I can use the API Explorer here and run this query body:
{
"query": {}
}
and get this result:
{
"batch": {
"entityResultType": "FULL",
"entityResults": [
{
"entity": {
"key": {
"partitionId": {
"projectId": "project-id-5200707333336492774"
},
"path": [
{
"kind": "Transaction",
"id": "5629499534213120"
}
]
},
"properties": {
"CommentIn": {
"stringValue": "My First Test Transaction"
},
"Status": {
"stringValue": "Closed"
},
"auditStatus": {
"stringValue": "Logged"
},
"User": {
"stringValue": "John Doe"
},
"Start": {
"timestampValue": "2017-08-17T18:07:04.681Z"
},
"CommentOut": {
"stringValue": "Done for today!"
},
"End": {
"timestampValue": "2017-08-17T20:07:38.058Z"
},
"Period": {
"stringValue": "08/16/2017-08/31/2017"
}
}
},
"cursor": "CkISPGogc35whh9qZWN0LWlkLTUyMDA3MDcwODA1MDY0OTI3NzRyGAsSC1RyYW5zYWN0aW9uGICAgICAgIAKDBgAIAA=",
"version": "1503004124243000"
}
],
"endCursor": "CkISPGogc35wcm9qZWN0LWlkLTUyMDAxxDcwODA1MDY0OTI3NzRyGAsSC1RyYW5zYWN0aW9uGICAgICAgIAKDBgAIAA=",
"moreResults": "NO_MORE_RESULTS"
}
}
I try to do the same thing with this code:
function doGet(e)
{
var goa = cGoa.GoaApp.createGoa('Oauth2-Service-Account',
PropertiesService.getScriptProperties()).execute(e);
if(goa.hasToken()) {var token = goa.getToken();}
var payload = {"query":{}}
;
var result = UrlFetchApp.fetch('https://datastore.googleapis.com/v1/projects/project-id-5200707333336492774:runQuery',
{
method: "POST",
headers: {authorization: "Bearer " + goa.getToken()},
muteHttpExceptions : true,
payload: payload
});
Logger.log(result.getBlob().getDataAsString());
}
and get this error in the logger:
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"query\": Cannot bind query parameter. 'query' is a message type. Parameters can only be bound to primitive types.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"description": "Invalid JSON payload received. Unknown name \"query\": Cannot bind query parameter. 'query' is a message type. Parameters can only be bound to primitive types."
}
]
}
]
}
}
If I try to use another word such as 'resource' or 'GqlQuery', I get this error:
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"GqlQuery\": Cannot bind query parameter. Field 'GqlQuery' could not be found in request message.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"description": "Invalid JSON payload received. Unknown name \"GqlQuery\": Cannot bind query parameter. Field 'GqlQuery' could not be found in request message."
}
]
}
]
}
}
I can't tell from the API Documentation what my syntax is supposed to be. Can anyone tell me how to compile a functional request body from Apps Script to Datastore?
You need to set the contentType of your payload as well as stringify your JSON payload as follows:
var result = UrlFetchApp.fetch(
'https://datastore.googleapis.com/v1/projects/project-id-5200707333336492774:runQuery',
{
'method':'post',
'contentType':'application/json',
'headers': {authorization: "Bearer " + goa.getToken()},
'payload':JSON.stringify(payload)
}
);
Related
What am I trying to do?
Query the field named price in documents inside the collection named product_page and return the document where price==100
What did I do?
URL:https://firestore.googleapis.com/v1/projects//databases/(default)/documents/product_page/
METHOD: POST
Request Body:
{
"structuredQuery": {
"from": [
{
"collectionId": "product_page",
"allDescendants": true
}
],
"where": {
"fieldFilter": {
"field": {
"fieldPath": "price"
},
"op": "EQUAL",
"value": {
"stringValue": "100"
}
}
}
What error do I get?
code": 400,
"message": "Invalid JSON payload received. Unknown name \"structuredQuery\" at 'document': Cannot find field.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "document",
"description": "Invalid JSON payload received. Unknown name \"structuredQuery\" at 'document': Cannot find field."
}
You use the wrong endpoint (or method).
With the following URL and a POST method
https://firestore.googleapis.com/v1/projects/.../databases/(default)/documents/product_page
you actually call the createDocument method.
You need to use the runQuery method, which allows passing a structuredQuery object.
I'm attempting to send a row to an appsheet app with their provided API. I have it all set up in app script and I'm getting a code 200 (success) but it's not adding the data to the spreadsheet. Am I doing something wrong?
function testingAPI(){
let appId = APP ID HERE
var url = `https://api.appsheet.com/api/v2/apps/${appId}/tables/opportunity/Action`;
var options = {
"method": "post",
"headers": {
"applicationAccessKey": ACCESS KEY HERE
},
"httpBody": {
"Action": "Add",
"Properties": {
"Locale": "en-US",
"Location": "47.623098, -122.330184",
"Timezone": "Pacific Standard Time",
"UserSettings": {
"Option 1": "value1",
"Option 2": "value2"
}
},
"Rows": [
{
"IntentionSetID": "1H3t8Dt",
"AgentID": "11234",
"ActivityID": 12,
"taskComplete": true,
"taskVerified": false,
"task_verified_by": "",
"proof": "https://drive.google.com/open?id=1CpzebeLtAljqHTsFHvVCCDFc-A6aXC3O",
"agent_expected_part_detail": "",
"poc_expected_part_detail": '',
"assigned_point_value": '',
"points_assigned_by": "10/31/2014",
"actual_part_detail": "8:15:25",
"verification_date": "18:30:33"
}
]
}
};
var response = UrlFetchApp.fetch(url, options);
console.log(response.getResponseCode())
}
I was able to find that the API call is indeed going through, but it's saying the "Action" is missing:
{
"RestAPIVersion": 2,
"TableName": "opportunity",
"AppTemplateVersion": "1.000247",
"Errors": "'Action' is missing.",
"AppTemplateName": "e280cf5a-e2de-4d24-89d3-95d384a2c044",
"Operation": "REST API invoke",
"RecordType": "Stop",
"Result": "Success",
"ResultSuccess": true,
"StatusCode": "OK"
}
changing httpBody to payload got me a step further, so now it's recognizing the "add" aspect, but it's still not understanding the properties correctly. it's showing a bunch of escape characters.
REST API:
{
"Action": "Add",
"Properties": {},
"Rows": []
}
Properties:
{
"RestAPIVersion": 2,
"TableName": "opportunity",
"AppTemplateVersion": "1.000250",
"Action": "Add",
"Errors": "API 'Properties' does not have unexpected Type of 'JObject'. Value is: '{\r\n \"Action\": \"Add\",\r\n \"Properties\": \"{Timezone=Pacific Standard Time, Location=47.623098, -122.330184, Locale=en-US}\",\r\n \"Rows\": \"[Ljava.lang.Object;#489763c2\"\r\n}'",
"AppTemplateName": "e280cf5a-e2de-4d24-89d3-95d384a2c044",
"Operation": "REST API invoke",
"RecordType": "Start",
"Result": "Failure"
}
The AppSheets API POST https://api.appsheet.com/api/v2/apps/{appId}/tables/{tableName}/Action can be used to invoke an action. This means that the action should be created in AppSheet before it can be called.
Resources
Invoke an Action | AppSheet
dialogflow sends the following:
{
"responseId": "XXX-YYY-ZZZ",
"queryResult": {
"queryText": "random text",
"parameters": {
"command": "do it"
},
"allRequiredParamsPresent": true,
"fulfillmentText": "Sorry, can't understand it.",
"fulfillmentMessages": [
{
"text": {
"text": [
"Sorry, can't understand it."
]
}
}
],
"outputContexts": [
{
"name": "projects/xxx-vyyy/agent/sessions/xxx-yyy-zzz/contexts/__system_counters__",
"parameters": {
"no-input": 0,
"no-match": 0,
"command": "do it",
"command.original": "do it"
}
}
],
"intent": {
"name": "projects/xxx-vyyy/agent/sessions/xxx-yyy-zzz",
"displayName": "testCommands"
},
"intentDetectionConfidence": 1,
"languageCode": "en"
},
"originalDetectIntentRequest": {
"payload": {}
},
"session": "projects/xxx-vyyy/agent/sessions/xxx-yyy-zzz"
}
TypeError: Cannot read property 'parameters' of undefined (line 5, file "this-file")
and the goal is to query the command field. This is the current code:
function doPost(e) {
//EXTRACTION
var dialogflow = e.postData.contents;
var desiredField = dialogflow.queryResult.parameters.command;
}
but then this error thrown:
TypeError: Cannot read property 'parameters' of undefined (line 4, file "this-file")
I tried JSON.stringify(e) and e.postData.contents, but still not working.
Documentation on doPost() https://developers.google.com/apps-script/guides/web
and WebhookRequest sent by dialogflow https://cloud.google.com/dialogflow/docs/fulfillment-webhook#webhook_request
Thanks in advance!
At doPost(e), e.postData.contents is not parsed as JSON object. I think that your error message is due to this. So how about the following modification using JSON.parse()?
Modified script:
function doPost(e) {
//EXTRACTION
var dialogflow = JSON.parse(e.postData.contents); // Modified
var desiredField = dialogflow.queryResult.parameters.command;
}
Reference:
Web Apps
Im trying to do a post request to GCP Natural Language for sentiment analysis.
When I try the data format on the code explorer on google it works fine but when I run it on a html page I get an error that reads
{
"error": {
"code": 400,
"message": "Invalid JSON payload received. Unknown name \"document[type]\": Cannot bind query parameter. Field 'document[type]' could not be found in request message.\nInvalid JSON payload received. Unknown name \"document[content]\": Cannot bind query parameter. Field 'document[content]' could not be found in request message.",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"description": "Invalid JSON payload received. Unknown name \"document[type]\": Cannot bind query parameter. Field 'document[type]' could not be found in request message."
},
{
"description": "Invalid JSON payload received. Unknown name \"document[content]\": Cannot bind query parameter. Field 'document[content]' could not be found in request message."
}
]
}
]
}
}
My code is:
<!DOCTYPE html>
<html>
<body>
<h1> Testing sentiment Analysis </h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
var APIKEY = "AN API KEY";
$.ajax({
type : "POST",
url : "https://language.googleapis.com/v1/documents:analyzeSentiment?key="+APIKEY,
data : {
"document": {
"type": "PLAIN_TEXT",
"content": "Hello I am great"
},
"encodingType": "UTF8",
},
success: function(res) {
console.log(res);
alert(res['message']);
},
error: function(res) {
console.log(res['message']);
alert(res);
},
});
</script>
</body>
</html>
UPDATE:
A colleague has pointed me to the MISTAKE I was making. We have to use JSON.stringify() in order to send the request. The code should be like this:
$.ajax({
type : "POST",
url : "https://language.googleapis.com/v1/documents:analyzeEntitySentiment?key=YOUR-API-KEY",
contentType : "application/json; charset=utf-8",
data :
JSON.stringify({
"document": {
"type": "PLAIN_TEXT",
"language": "en",
"content": "Hola Victor"
},
"encodingType": "UTF8"}),
success : function(_result){
if (_result) {
alert('SUCCESS');
} else {
alert('ERROR');
}
},
error : function(_result){
}
});
I have tested it and it works.
I have the following pipeline:
A file is uploaded to S3, it triggers a Lambda (Let's call it L1) which runs and does some processing.
So at the moment, my entry point looks like this:
public Response handleRequest(S3Event event, Context context) {
....
}
Now, a S3Event JSON looks like this:
{
"Records": [
{
"awsRegion": "xxxxx",
"eventName": "ObjectCreated:Put",
"eventSource": "aws:s3",
"eventTime": "2017-09-12T09:27:59.471Z",
"eventVersion": "2.0",
"requestParameters": {
"sourceIPAddress": "xxxxxx"
},
"responseElements": {
"x-amz-id-2": "xxxxxx",
"x-amz-request-id": "xxxx"
},
"s3": {
"configurationId": "xxxxxx1",
"bucket": {
"name": "xxxxx",
"ownerIdentity": {
"principalId": "xxxxx"
},
"arn": "xxx"
},
"object": {
"key": "xxx",
"size": xxx,
"eTag": "xxxx",
"versionId": null,
"sequencer": "xxx",
"urlDecodedKey": "xxx"
},
"s3SchemaVersion": "1.0"
},
"userIdentity": {
"principalId": "xxxx"
}
}
],
}
If you pass this JSON in the "Test" section, it will succeed.
Now, to the point: I wish to add information to this JSON, something that would look like this:
{
"Records": [
{
"awsRegion": "xxxxx",
"eventName": "ObjectCreated:Put",
"eventSource": "aws:s3",
"eventTime": "2017-09-12T09:27:59.471Z",
"eventVersion": "2.0",
"requestParameters": {
"sourceIPAddress": "xxxxxx"
},
"responseElements": {
"x-amz-id-2": "xxxxxx",
"x-amz-request-id": "xxxx"
},
"s3": {
"configurationId": "xxxxxx1",
"bucket": {
"name": "xxxxx",
"ownerIdentity": {
"principalId": "xxxxx"
},
"arn": "xxx"
},
"object": {
"key": "xxx",
"size": xxx,
"eTag": "xxxx",
"versionId": null,
"sequencer": "xxx",
"urlDecodedKey": "xxx"
},
"s3SchemaVersion": "1.0"
},
"userIdentity": {
"principalId": "xxxx"
}
}
],
"MyErrorMessage":
{
"EnvelopeErrors": [
{
"EnvelopeErrorTrace": "stackTrace",
"EnvelopeErrorPositions": 1,
"EnvelopeErrorLength": 2
},
{
"EnvelopeErrorTrace": "SecondTrace",
"EnvelopeErrorPositions": 3,
"EnvelopeErrorLength": 4
}
],
}
}
Notice is the S3Event JSon but with a bit more data.
My question problem is the following: I want to have a custom input that also works when a pure S3Event is called.
public Response handleRequest(MyS3Event event, Context context) {
....
}
However, I have not been able to achieve this.
I have tried a custom POJO but it does not work when I upload to S3 a file.
I tried to extend the S3EventNotification class (from which S3Event extends), but again with no success.
Is it possible what I am trying to do?
What you can do is to have your Lambda (L1) call itself (asynchronously) by sending it the new, modified event similar to how recursive functions work.
Just be very careful though. You have to put a limit as to how deep you want to keep recursing. You don't want to end up with infinite calls. I am not sure if AWS guards against this.
In the AWS SDK Lambda has an invoke method:
Invokes a specific Lambda function. For an example, see Create the Lambda Function and Test It Manually.
If you are using the versioning feature, you can invoke the specific
function version by providing function version or alias name that is
pointing to the function version using the Qualifier parameter in the
request. If you don't provide the Qualifier parameter, the $LATEST
version of the Lambda function is invoked. Invocations occur at least
once in response to an event and functions must be idempotent to
handle this. For information about the versioning feature, see AWS Lambda Function Versioning and Aliases.
This operation requires permission for the lambda:InvokeFunction
action.
var params = {
FunctionName: 'STRING_VALUE', /* required */
ClientContext: 'STRING_VALUE',
InvocationType: Event | RequestResponse | DryRun,
LogType: None | Tail,
Payload: new Buffer('...') || 'STRING_VALUE',
Qualifier: 'STRING_VALUE'
};
lambda.invoke(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
One of the params you send a Payload which is the event the invoked function receives so you can send your MyErrorMessage in/as this payload to get the desired result.