How to pull data from Toggl API with Power Query? - json

First timer when it comes to connecting to API. I'm trying to pull data from Toggl using my API token but I can't get credentials working. I tried to replicate the method by Chris Webb (https://blog.crossjoin.co.uk/2014/03/26/working-with-web-services-in-power-query/) but I can't get it working. Here's my M code:
let
Source = Web.Contents(
"https://toggl.com/reports/api/v2/details?workspace_id=xxxxx&client=xxxxxx6&billable=yes&user_agent=xxxxxxx",
[
Query=[ #"filter"="", #"orderBy"=""],
ApiKeyName="api-token"
])
in
Source
After that I'm inputting my API Token into Web API method in Access Web content windows but I get an error that credentials could not be authenticated. Here's Toggl API specification:
https://github.com/toggl/toggl_api_docs/blob/master/reports.md

Web.Contents function receives two parameters: url + options
Inside options, you define the headers and the api_key, and other queryable properties, such as:
let
baseUrl = "https://toggl.com/",
// the token part can vary depending on the requisites of the API
accessToken = "Bearer" & "insert api token here"
options = [
Headers = [Authorization = accessToken, #"Content-Type" =
"application/Json"], RelativePath ="reports/api/v2/details", Query =
[workspace_id=xxxxx, client=xxxxxx6 , billable=yes, user_agent=xxxxxxx]
]
Source = Web.Contents(baseUrl, options)
// since Web.Contents() doesn't parse the binaries it fetches, you must use another
// function to see if the data was retreived, based on the datatype of the data
parsedData = Json.Document(Source)
in
parsedData
The baseUrl is the smallest url that works and never changes;
The RelativePath is the next part of the url before the first "?".
The Query record is where you define all the attributes to query as a record.
This is usually the format, but check the documentation of the API you're querying to see if it is similar.

Related

What is the recommended and most performant API call to check if I have read permission on a dataset in Foundry?

On our Stack users by default have discoverer permissions on resources. I was surprised that this also gives users the ability to query the last_transaction_rid of the dataset (using catalog/datasets/<rid>/reverse-transactions2/<branch>), so this method to check if a user has access is not working.
What would be the recommended and most performant API call to check if I, with my current Foundry token, can read the actual content of a dataset? Note: I don't want to query the content, but just understand if I would have the permissions to do so.
Would Data Lineage App (Monocle) work for you?
Open it workspace/data-integration/monocle/ > Find the dataset(s) > top right dropdown ("Resource Type" -> "Permissions")
The API that powers it can be extracted by checking the Dev Tools > Network Tab if you want / need to automate it (I've done this in the past and worked well for my use case)
I have resorted back to calling the compass/resources API and checking if response['operations'] contains compass:view:
def get_dataset_details(api_base: str, dataset_path_or_rid: str, headers: dict) -> dict:
"""
Returns the resource information of a dataset
Args:
dataset_path_or_rid: The full path or rid to the dataset
Returns: (dict) the json response of the api
"""
if 'ri.foundry.main.dataset' in dataset_path_or_rid:
response = requests.get(f"{api_base}/compass/api/resources/{dataset_path_or_rid}",
headers=headers,
params={'decoration': 'path'})
else:
response = requests.get(f"{api_base}/compass/api/resources",
headers=headers,
params={
'path': dataset_path_or_rid,
'decoration': 'path'
})
response.raise_for_status()
if response.status_code != 200:
raise ValueError(f"Dataset {dataset_path_or_rid} not found; "
f"If you are sure your dataset_path is correct, "
f"check if your jwt token "
f"is still valid!")
return response.json()
details = get_dataset_details(...)
if 'compass:view' not in dataset_details['operations']:
raise ValueError("No compass:view access to dataset")

M Language - How to get JSON in HTTP Request Body? (Vimeo API Unsupported Grant Type Error)

I am attempting to create my first PowerBI Custom Connecter to connect to the Vimeo API. I am stuck on the final step of the authorization flow - getting back an access token. When trying out the Connecter in PowerBI, it seems to authenticate properly when I hit the access token endpoint, but I get back a warning "[unsupported_grant_type] Unsupported grant type"
It appears I am not sending the grant_type properly in the request. Here are Vimeo's requirements of what is sent along in the header and body of the request:
Header
Set value to
Authorization
basic base64_encode(x:y), where x is the client identifier and y is the client secret
Content-Type
application/json
Accept
application/vnd.vimeo.*+json;version=3.4
"In the request body, send the grant_type field with the value authorization_code. You must also set code to the authorization code string that you just received and redirect_uri to the redirect URI that you specified previously — don't use a different redirect URI."
{
"grant_type": "authorization_code",
"code": "{code}",
"redirect_uri": "{redirect_uri}"
}
Here is a snippet of code from the Customer Connector I am building. It is within this TokenMethod function that I am trying to fulfill the requirements of the table above. I am getting the sense I am not correctly placing the JSON in the body of the request, but I am stuck on what to try next:
TokenMethod = (grantType, tokenField, code) =>
let
queryString = [
grant_type = "authorization_code",
redirect_uri = redirect_uri,
client_id = client_id,
client_secret = client_secret
],
queryWithCode = Record.AddField(queryString, tokenField, code),
authKey = "Basic " & Binary.ToText(Text.ToBinary("client_id:client_secret"),BinaryEncoding.Base64),
tokenResponse = Web.Contents(token_uri, [
Content = Text.ToBinary(Uri.BuildQueryString(queryWithCode)),
Headers = [
#"Authorization" = authKey,
#"Content-type" = "application/json",
#"Accept" = "application/vnd.vimeo.*+json;version=3.4"
],
ManualStatusHandling = {400}
]),
body = Json.Document(tokenResponse),
result = if (Record.HasFields(body, {"error", "error_description"})) then
error Error.Record(body[error], body[error_description], body)
else
body
in
result;
I'm wondering if someone could please point out where I might be going astray in the code and why I am receiving the [unsupported_grant_type] error.
Many thanks for your time!
I changed Content-Type to "application/x-www-form-urlencoded" and it worked!

NativeScript Throwing Error Response with status: 200 for URL: null

I am using Angular4 with TypeScript version 2.2.2
My web app is running fine when I call JSON with Filters but my NativeScript app fails when I call the Filter Values as an Object but works fine when I call filter values as a string.
Error Response with status: 200 for URL: null
THIS WORKS
https://domainname.com/api/v1/searchevents?token=057001a78b8a7e5f38aaf8a682c05c414de4eb20&filter=text&search=upcoming
If the filter value and search value is STRING it works whereas if they are objects as below, it does not work
THIS DOES NOT WORK
https://api.domainname.com/api/v1/searchevents?token=057001a78b8a7e5f38aaf8a682c05c414de4eb20&filter={"limit":"12","skip":"0"}&search={"search":"","latitude":"","longitude":"","categories":"","address":"","type":"upcoming"}
The Code I used is below
getData(serverUrl, type, skip_limit) {
console.log(serverUrl);
let headers = this.createRequestHeader();
let token_value = localStorage.getItem('access_token')
let url;
var filter;
filter = '{"limit":"10","skip":"0"}'
url = this.apiUrl + serverUrl + '?token=' + token_value + '&filter=' + filter
return this.http.get(url, { headers: headers })
.map(res => res.json());
}
The URL as formed above for the API is fine and works fine. Yet the error comes Error Response with status: 200 for URL: null
CAN ANYONE HELP ME SOLVE THIS?
Looks like the issue is the "filter" values are of different type and from what you mentioned as what worked, your service is expecting a string and not an object/array. So it fails to send the proper response when it gets one. With an object in the URL, you may have to rewrite the service to read it as an object (parse the two attributes and get them individually)
To make it simple, you can make these two as two different variables in the URL. like below,
https://api.domainName.in/api/v1/oauth/token?limit=10&skip=0
Be more precise in whats happening in your question,
1) Log the exact URL and post it in the question. No one can guess what goes in "text" in your first URL.
2) Your URL which you mentioned as worked have "token" as part of path, but in the code, its a variable which will have a dynamic value from "token_value".
3) Post your service code. Especially the signature and input parsing part.
Got the solution:
All you have to do is encode the Filter and Search Parameters if it is an Object or Array using Typescript encodeURI()
var filter = '{"limit":"12","skip":"0"}'
var search = '{"search":"","latitude":"","longitude":"","categories":"","address":"","type":"upcoming"}'
var encoded_filter = encodeURI(filter);
var encoded_search = encodeURI(search);
url = this.apiUrl+serverUrl+'?token='+token_value+'&filter='+encoded_filter+'&search='+encoded_search

PUT requests with Custom Ember-Data REST Adapter

I'm using Ember-Data 1.0.0.Beta-9 and Ember 1.7 to consume a REST API via DreamFactory's REST Platform. (http://www.dreamfactory.com).
I've had to extend the RESTAdapter in order to use DF and I've been able to implement GET and POST requests with no problems. I am now trying to implement model.save() (PUT) requests and am having a serious hiccup.
Calling model.save() sends the PUT request with the correct data to my API endpoint and I get a 200 OK response with a JSON response of { "id": "1" } which is what is supposed to happen. However when I try to access the updated record all of the properties are empty except for ID and the record on the server is not updated. I can take the same JSON string passed in the request, paste it into the DreamFactory Swagger API Docs and it works no problem - response is good and the record is updated on the DB.
I've created a JSBin to show all of the code at http://emberjs.jsbin.com/nagoga/1/edit
Unfortunately I can't have a live example as the servers in question are locked down to only accept requests from our company's public IP range.
DreamFactory provides a live demo of the API in question at
https://dsp-sandman1.cloud.dreamfactory.com/swagger/#!/db/replaceRecordsByIds
OK in the end I discovered that you can customize the DreamFactory response by adding a ?fields=* param to the end of the PUT request. I monkey-patched that into my updateRecord method using the following:
updateRecord: function(store, type, record) {
var data = {};
var serializer = store.serializerFor(type.typeKey);
serializer.serializeIntoHash(data, type, record);
var adapter = this;
return new Ember.RSVP.Promise(function(resolve, reject) {
// hack to make DSP send back the full object
adapter.ajax(adapter.buildURL(type.typeKey) + '?fields=*', "PUT", { data: data }).then(function(json){
// if the request is a success we'll return the same data we passed in
resolve(json);
}, function(reason){
reject(reason.responseJSON);
});
});
}
And poof we haz updates!
DreamFactory has support for tacking several params onto the end of the requests to fully customize the response - at some point I will look to implement this correctly but for the time being I can move forward with my project. Yay!
EmberData is interpreting the response from the server as an empty object with an id of "1" an no other properties in it. You need to return the entire new object back from the server with the changes reflected.

Unexpected Error on UrlFetchApp

I'm trying to call my ServiceNow JSON web service. I'm getting an unexpected error when I execute URLFetchApp. I'm guessing I'm passing in the authorization headers in the wrong way but both the GAS and ServiceNow documentation is beyond terrible. I've seen some of the other SO questions similar to this but none have worked. Any help would be appreciated.
function getOpenTickets(){
var headers = {
"Authorization":"Basic RgRJ5U6EsxHt00229KX5Hj0WV1z18q08==",
"Content-Type":"application/json",
"Username":"myusername",
"Password":"mypassword"
}
var url = "https://mysninstance.service-now.com/u_equipment_repair.do?JSONv2=&sysparm_view=vendor&displayvalue=true&sysparm_action=getRecords&sysparm_query=state=500^assignment_group.name=MyGroup^ORDERBYDESCnumber";
var url = encodeURIComponent(url);
var options = {
"method":"get",
"headers":headers
}
var result = UrlFetchApp.fetch(url,options);
Logger.log(result.getContentText());
}
OK so I found the solution. There were actually two problems.
The first was with the way I was passing the authorization headers. I was passing the basic authentication as an already encoded base64 string, on top of which I was still passing the username and password which was redundant. For whatever reason Google Apps Script (GAS) doesn't like this. Once I changed the headers and the options as shown below it was fine.
The second problem was the the URI encoding. The query string did need to be encoded because of the caret "^" symbols, but for whatever reason GAS's encodeURIComponent was not encoding it properly. As soon as I manually replaced the caret symbols with their URL encoded equivalents , which is "%5E", everything worked fine and I was able to retrieve my ServiceNow data via Google Apps Script.
function getOpenTickets3(){
var headers =
{
Authorization : "Basic " + Utilities.base64Encode('myusername:mypassword'),
"Content-Type":"application/json"
}
var options =
{
"method" : "get",
"headers": headers
};
var url = "https://mysninstance.service-now.com/u_equipment_repair.do?JSONv2=&sysparm_view=vendor&displayvalue=true&sysparm_action=getRecords&sysparm_query=state=500%5Eassignment_group.name=Somevendor%5EORDERBYDESCnumber";
var result = UrlFetchApp.fetch(url,options);
Logger.log(result.getContentText());
}
You are URI encoding your entire URL in this line:
var url = encodeURIComponent(url);
In your URL, the base path needs to be unescaped when passed to fetch(...):
https://mysninstance.service-now.com/u_equipment_repair.do
Each parameter following the ? is a URI component, like:
sysparm_view=vendor
In this case, the parameter name is sysparm_view and the value is vendor, you would need to URI encode the value (vendor) if it contained special characters like one of /?&.
In the static URL you provide, there's actually nothing that needs to be encoded, so removing that call to encodeURIComponent(url) should work.
If you are dealing with dynamic values for your URL parameters, then you'd want to URI encode each parameter value separately, before concatenating onto the main string.