Forum Discussion

bgobbi's avatar
bgobbi
Frequent Visitor
3 years ago

On-Premises Data Gateway sources Custom Connector

Hi ,

I'm encountering an issue with date source setting with a Custom Connector. I find my Custom Connector in the List, but when I create the data source, it shows below error.

I'm sure that the username and password is correct. 

 

Can you help me please?

Thanks!

 

3 Replies

  • vanessafvg's avatar
    vanessafvg
    Icon for Community Champion rankCommunity Champion

    has this worked before or is it a new custom connector?  who is the author of the connector?

    • bgobbi's avatar
      bgobbi
      Frequent Visitor

      Hi
      it's a new custom connector. The connector work correctly in Power Bi Desktop. The author are our software house but we have the source of the connector.

      • bgobbi's avatar
        bgobbi
        Frequent Visitor

        Hi

        I think the problem is double "url". In the errot i see {"url":"{\"url\":\"https:\\/\\/contacts-api.finstral.cloud\\/api\\/Reports\\/LeadOverview\"}"}. Why? What is the error in connector?

         

         

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


        // OuraCloud OAuth2 values
        client_id = "xxxxxxxxxxxx"; //Text.FromBinary(Extension.Contents("client_id.txt"));
        client_secret = "xxxxxxxxxxx"; // Text.FromBinary(Extension.Contents("client_secret.txt"));
        redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html";
        token_uri = "https://login.finstral.cloud/connect/token";
        authorize_uri = "xxxxxxxxxxxxxx";
        logout_uri = "https://login.microsoftonline.com/logout.srf";

        // Login modal window dimensions
        windowWidth = 720;
        windowHeight = 1024;

        // OAuth2 scope
        scope_prefix = "";
        scopes = {
        "openid",
        "fin_contacts",
        "offline_access"
        };

        [DataSource.Kind="ContactsConnector", Publish="ContactsConnector.Publish"]
        shared ContactsConnector.Contents = (url as text) =>
        let
        // source = Excel.Workbook(Web.Contents(url, [Query = [ v = Number.ToText(Number.Random()) ], Timeout = #duration(0,1,0,0) ]), true)
        source = Excel.Workbook(Web.Contents(url, [Timeout = #duration(0,1,0,0)]), true)
        in
        source;

         

         

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

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

        ContactsConnector.Icons = [
        Icon16 = { Extension.Contents("ContactsConnector16.png"), Extension.Contents("ContactsConnector20.png"), Extension.Contents("ContactsConnector24.png"), Extension.Contents("ContactsConnector32.png") },
        Icon32 = { Extension.Contents("ContactsConnector32.png"), Extension.Contents("ContactsConnector40.png"), Extension.Contents("ContactsConnector48.png"), Extension.Contents("ContactsConnector64.png") }
        ];

        Base64UrlEncodeWithoutPadding = (hash as binary) as text =>
        let
        base64Encoded = Binary.ToText(hash, BinaryEncoding.Base64),
        base64UrlEncoded = Text.Replace(Text.Replace(base64Encoded, "+", "-"), "/", "_"),
        withoutPadding = Text.TrimEnd(base64UrlEncoded, "=")
        in
        withoutPadding;

        // Helper functions for OAuth2: StartLogin, FinishLogin, Refresh, Logout
        StartLogin = (resourceUrl, state, display) =>
        let
        codeVerifier = Text.NewGuid() & Text.NewGuid(),
        codeChallenge = Base64UrlEncodeWithoutPadding(Crypto.CreateHash(CryptoAlgorithm.SHA256, Text.ToBinary(codeVerifier, TextEncoding.Ascii))),
        authorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
        response_type = "code",
        client_id = client_id,
        code_challenge = codeChallenge,
        code_challenge_method = "S256",
        redirect_uri = redirect_uri,
        state = state,
        scope = GetScopeString(scopes, scope_prefix)
        ])
        in
        [
        LoginUri = authorizeUrl,
        CallbackUri = redirect_uri,
        WindowHeight = 720,
        WindowWidth = 1024,
        Context = [ CodeVerifier = codeVerifier ]
        ];

        FinishLogin = (context, callbackUri, state) =>
        let
        // parse the full callbackUri, and extract the Query string
        parts = Uri.Parts(callbackUri)[Query],
        codeVerifier = Record.FieldOrDefault(context, "CodeVerifier", null),
        // 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], codeVerifier)
        in
        result;

        Refresh = (resourceUrl, refresh_token) => TokenMethod("refresh_token", "refresh_token", refresh_token, null);

        Logout = (token) => logout_uri;

        // see "Exchange code for access token: POST /oauth/token" at https://cloud.ouraring.com/docs/authentication for details
        TokenMethod = (grantType, tokenField, code, codeVerifier) =>
        let
        queryString = [
        grant_type = grantType,
        redirect_uri = redirect_uri,
        client_id = client_id,
        client_secret = client_secret
        ],
        queryWithVerifier = if (codeVerifier <> null) then Record.AddField(queryString, "code_verifier", codeVerifier) else queryString,
        queryWithCode = Record.AddField(queryWithVerifier, 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;

        Value.IfNull = (a, b) => if a <> null then a else b;

        GetScopeString = (scopes as list, optional scopePrefix as text) as text =>
        let
        prefix = Value.IfNull(scopePrefix, ""),
        addPrefix = List.Transform(scopes, each prefix & _),
        asText = Text.Combine(addPrefix, " "),
        finalText = Text.Combine({"offline_access", asText}, " ")
        in
        finalText;


        shared ContactsConnector.ToNavigationTable = (
        table as table,
        keyColumns as list,
        nameColumn as text,
        dataColumn as text,
        itemKindColumn as text,
        itemNameColumn as text,
        isLeafColumn as text
        ) as table =>
        let
        tableType = Value.Type(table),
        newTableType = Type.AddTableKey(tableType, keyColumns, true) meta
        [
        NavigationTable.NameColumn = nameColumn,
        NavigationTable.DataColumn = dataColumn,
        NavigationTable.ItemKindColumn = itemKindColumn,
        Preview.DelayColumn = itemNameColumn,
        NavigationTable.IsLeafColumn = isLeafColumn
        ],
        navigationTable = Value.ReplaceType(table, newTableType)
        in
        navigationTable;