为 Xero API 构建 Power BI 连接器

问题描述

我的目标是将数据从 Xero API 获取到我的组织的 Power BI。为此,我相信我需要创建一个自定义连接器,因为 PBI 没有用于 Xero 的认证连接器。我有 Power BI 和 M 语言方面的经验,但这是我第一次尝试为 Power BI 构建自定义连接器。

由于 Xero API 需要 OAuth2 身份验证,我使用以下资源在 Visual Studio 中使用 Power Query SDK 模板创建自定义连接器。我相信我已经成功设置了身份验证,因为连接器允许我登录到我的 Xero 帐户并“允许访问”所请求的资源。似乎连接成功,因为在 Xero 中,我的应用名称出现在“设置”>“已连接的应用”中。

https://github.com/jussiroine/OuraCloudConnector/blob/master/OuraCloudConnector/OuraCloudConnector.pq

一旦我登录,我就会收到一个错误;详细信息:“禁止访问该资源。”

我能想到的可能原因:

我在 Xero 中拥有访问我请求的数据所需的权限。我还包括了相关的范围。任何人都可以告诉他们是否已成功为 Power BI 构建自定义连接器或提供一些有关如何解决错误的建议?

谢谢!

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

//JaimesXeroConnector OAuth2 values;

client_id = Text.FromBinary(Extension.Contents("client_id.txt"));
client_secret = Text.FromBinary(Extension.Contents("client_secret.txt"));
redirect_uri = "https://oauth.powerbi.com/views/oauthredirect.html";
token_uri = "https://identity.xero.com/connect/token";
authorize_uri = "https://login.xero.com/identity/connect/authorize";
logout_uri = "https://login.microsoftonline.com/logout.srf";
scopes = {"offline_access openid profile email accounting.transactions accounting.reports.read accounting.transactions.read accounting.reports.read accounting.journals.read accounting.settings accounting.settings.read accounting.contacts accounting.contacts.read accounting.attachments accounting.attachments.read assets projects"
};
System.Net.ServicePointManager.SecurityProtocol= "SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12";

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

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


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

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

// Helper functions for OAuth2: StartLogin,FinishLogin,Refresh,logout
StartLogin = (resourceUrl,state,display) =>
    let
        authorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
            response_type = "code",client_id = client_id,redirect_uri = redirect_uri,state = state,scope = scopes
        ])
    in
        [
            LoginUri = authorizeUrl,CallbackUri = redirect_uri,WindowHeight = 720,WindowWidth = 1024,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;

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

logout = (token) => logout_uri;

// see "Exchange code for access token: POST /oauth/token" for details
TokenMethod = (grantType,tokenField,code) =>
    let
        queryString = [
            grant_type = "authorization_code",client_secret = client_secret
        ],queryWithCode = Record.AddField(queryString,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_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," ")
    in
        asText;
        

JaimesXeroConnector.Icons = [
    Icon16 = { Extension.Contents("JaimesXeroConnector16.png"),Extension.Contents("JaimesXeroConnector20.png"),Extension.Contents("JaimesXeroConnector24.png"),Extension.Contents("JaimesXeroConnector32.png") },Icon32 = { Extension.Contents("JaimesXeroConnector32.png"),Extension.Contents("JaimesXeroConnector40.png"),Extension.Contents("JaimesXeroConnector48.png"),Extension.Contents("JaimesXeroConnector64.png") }
];

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)