Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

Earn a 50% discount on the DP-600 certification exam by completing the Fabric 30 Days to Learn It challenge.

Reply
clno1
Frequent Visitor

dataformat.error we couldn't parse your query string because it was improperly formatted

Hi There, 

 

(Please see latest comment for current situation).

 

Got an issue building my first ever custom connector, wondered if folk here may be able to help? Here's my .pq file (no option to select M as a code sample when submitting, so have submitted as HTML).

 

 

// This file contains your Data Connector logic
section Zoho_Recruit;

urlPath = "candidates";

//oauth values
client_id = "xx";
client_secret = "xx";
redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html";
authorize_uri = "https://accounts.zoho.eu/oauth/v2/auth";
token_uri = "https://accounts.zoho.eu/oauth/v2/token";

// Login modal window dimensions
windowWidth = 1200;
windowHeight = 1000;

[DataSource.Kind="Zoho_Recruit", Publish="Zoho_Recruit.Publish"]
shared Zoho_Recruit.Contents = (url as text) =>
    let
        source = Json.Document(Web.Contents(url & urlPath))
    in
        source;

// Data Source Kind description
Zoho_Recruit = [
    TestConnection = (dataSourcePath) => { "Zoho_Recruit.Contents", dataSourcePath },
    Authentication = [
        OAuth = [
            StartLogin = StartLogin,
            FinishLogin = FinishLogin
        ]
    ],
Label = Extension.LoadString("DataSourceLabel")
];

// Data Source UI publishing description
Zoho_Recruit.Publish = [
    Beta = true,
    Category = "Other",
    ButtonText = { Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp") },
    LearnMoreUrl = "https://powerbi.microsoft.com/",
    SourceImage = Zoho_Recruit.Icons,
    SourceTypeImage = Zoho_Recruit.Icons
];

// StartLogin
StartLogin = (dataSourcePath, state, display) =>
        let
            AuthorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
                response_type = "code",
                client_id = client_id,
                redirect_uri = redirect_uri,
                state = state,
                scope = "ZohoRecruit.modules.ALL"
                ])
        in
            [
                LoginUri = authorize_uri,
                CallbackUri = redirect_uri,
                WindowHeight = windowHeight,
                WindowWidth = windowWidth,
                Context = null
            ];

FinishLogin = (context, callbackUri, state) =>
    let
        // parse the full callbackUri, and extract the Query string
        parts = Uri.Parts(callbackUri)[Query],
        // if the query string contains an "error" field, raise an error
        // otherwise call TokenMethod to exchange our code for an access_token
        result = if (Record.HasFields(parts, {"error", "error_description"})) then 
                    error Error.Record(parts[error], parts[error_description], parts)
                 else
                    TokenMethod("authorization_code", "code", parts[code])
    in
        result;

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),
                   
        tokenResponse = Web.Contents(token_uri, [
            Content = Text.ToBinary(Uri.BuildQueryString(queryWithCode)),
            Headers=[
                #"Content-type" = "application/x-www-form-urlencoded",
                #"Accept" = "application/json"
            ],
            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;

Zoho_Recruit.Icons = [
    Icon16 = { Extension.Contents("Zoho_Recruit16.png"), Extension.Contents("Zoho_Recruit20.png"), Extension.Contents("Zoho_Recruit24.png"), Extension.Contents("Zoho_Recruit32.png") },
    Icon32 = { Extension.Contents("Zoho_Recruit32.png"), Extension.Contents("Zoho_Recruit40.png"), Extension.Contents("Zoho_Recruit48.png"), Extension.Contents("Zoho_Recruit64.png") }
];

 

 

 

Whether run in Visual Studio, or in PBI desktop, after having successfully authenticated and given the app rights to the scope that's defined in the code, I get the issue below:

 

error.jpg

 

Zoho API documentation here:

https://www.zoho.com/recruit/developer-guide/apiv2/oauth-overview.html

https://www.zoho.com/accounts/protocol/oauth/web-apps/authorization.html

 

Clearly, response_type is defined in the code, so I'm not sure what I'm missing. There may well be some pretty rookie errors in here, so please be gentle! 😉

 

I also have a follow up question, which is how does one obfuscate the Client_ID and Client_Secret values when building custom connectors? There is an obvious security concern around having these values stored as open text either within a .mez file which can be reverse engineered, or a text file that has the same frailties. 

 

Thanks,


clno1

 

5 REPLIES 5
clno1
Frequent Visitor

Ok, 

 

So the latest on this is that, after a whole lot of learning, I think I know what the issue is, but I can't prove it.

 

Having successfully proved the OAuth flow using Postman, I learned two important things:

 

1. That the request to get token (using the "code" returned by the authorize_uri web service as part of the the redirect_url) needs to be embedded in the body of the request, not the header. The code was trying to do this by default, but it's an important point that I'd missed, so thought I'd mention it here.

 

2. The token provided in response to that request also needs to go in to the header of the web.contents call to get the actual data from the intended endpoint (in this case, the recruit api endpoint) AND The Header Prefix (which is usually "Bearer") expected in the case of this Zoho endpoint, is actually "Zoho-oauthtoken". 

 

So my question is this:

 

How can I force the custom connector I've developed to use "Zoho_oauthtoken" as the header prefix? Presumably I cannot simply add Header = [#"Zoho_oauthtoken"] to the web.contents call, as it would be missing the access_token pulled by the TokenMethod function. So am I not stuck in a chicken and egg situation?

 

Follow on questions:

 

I can put similar (but vastly reduce) set of program code directly in to the advanced query editor in the Transform Data tool within Power BI desktop similar to this example for twitter here, but when I try to "execute" the step to get the initial "code" from the Authorise_uri, the login page presented for me to allow me to "accept" that I'm giving access to the intended scope won't let me type anything in the window. Does anyone know why this is? Is there a way around it?

 

Or I can try to write a non "Data connector" template based flow which doesn't rely on StartLogin / FinishLogin, which calls the Auth and Token webservices first, and then uses that token to call the main data (recruit) service. However, again, I don't know how to take the response to the first request to the authorize_Uri, and present this in a browser for me to then respond to....

 

Oh - and there's a lot of ambiguity in the documentation as to whether or not I actually need a personal mode On premises data gateway to make this work, I've tried it and it doesn't make any difference, but just through I'd ask what the formal position is on this. 

 

Truly stuck - any help greatly appreciated.

 

 

 

Clno1

clno1
Frequent Visitor

A quick update - I changed 

LoginUri = authorize_uri

to

LoginUri = AuthorizeUrl

Under the StartLogin function.

This actually got me further on, in that now I am repeatedly asked to approve access to the scope defined within the code, which feels like things are working better than they were before.

However, now I get. 

"dataformat.error we couldn't parse your query string because it was improperly formatted"

And this is what I'm struggling to solve now.

 

Hi @clno1 

 

Does it indicate on which row of codes this error may occur? I didn't find any possible solutions/website links related to this error message. I have no idea ☹️ sorry...

 

Best Regards,
Community Support Team _ Jing

Hi @v-jingzhang

 

I'm not quite sure how to get any greater level of debugging information, and so at the moment, no I don't have any better information to go on.

 

I've enabled the logging in PBI desktop and this doesn't give me anything, next step I'd planned was for me to try and enable the trace option in Visual Studio but I'll be honest, that's a little bit beyond me at the moment - I have no idea where to insert the code that will enable the tracing.

 

My other plan was to try this against a totally different endpoint and see if I get similar issues. Whilst not foolproof, that way I can determine whether it's likely my code, or some sort of incompatibility between PBI and Zoho.

 

Also, I wonder if there is a way to decompile / reverse engineer one of the existing built in connectors as there is one for Zoho Connect, which is a different product by the same vendor - wondered if that might shed any light on where I'm going wrong.

 

Be interested in your thoughts, thanks for taking the time.

 

Cheers,

Hi @clno1  have u found the solution for the problem, I am also facing this same exact problem while  integrating powerbi with one of zoho's services . Anything woud be of great help

 

Helpful resources

Announcements
LearnSurvey

Fabric certifications survey

Certification feedback opportunity for the community.

Top Solution Authors
Top Kudoed Authors