3

我正在尝试使用 oauth 方法和 power bi 连接到 youtube/google 分析。我已经成功了一半,我需要一些帮助。这就是我所在的位置:

我使用以下方法手动获取授权令牌:

https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/yt-analytics.readonly&response_type=code&access_type=offline&redirect_uri=urn:ietf:wg:oauth:2.0:oob&approval_prompt=force&client_id={clientid}

一旦我有了它,我把它放在我的查询中,我就可以获得 access_token 和 refresh_token:

在此处输入图像描述

现在,如果我正确理解文档,当 access_token 一个小时后过期时,我可以使用我得到的 refresh_token 自动创建一个新的 access_token,

在 Power Query 中可以这样做吗?有人试过吗?

我完全不知道如何做到这一点,而且我不是开发人员,所以我的技能有限:(

任何帮助表示赞赏!

4

3 回答 3

2

对于 Power BI 开发人员团队来说,弄清楚如何自动执行 OAuth 流程也不是一件容易的事;)

PowerBI DesktopGoogleAnalytics.Accounts()具有自动处理 OAuth 令牌的内置数据源连接器。

(Google Analytics 今天在 Power Query 中不可用,抱歉。)

对于 YouTube 分析,此功能有PowerBI UserVoice线程跟踪需求。在那里表达你的支持!

于 2016-11-23T16:09:31.543 回答
2

我们也有类似的需要直接连接到 Analytics API,以规避内置连接器的缺点。让 Web 版本的 PowerBI 接受身份验证端点作为“匿名”源有点尴尬,但是反向代理可以通过使用200 OK. 这是主要的 PowerQuery / M 逻辑,分解为函数:

GetAccessToken_GA

let
    Source = (optional nonce as text) as text => let
        // use `nonce` to force a fresh fetch

        someNonce = if nonce = null or nonce = ""
            then "nonce"
            else nonce,

        // Reverse proxy required to trick PowerBI Cloud into allowing its malformed "anonymous" requests to return `200 OK`.
        // We can skip this and connect directly to GA, but then the Web version will not be able to refresh.
        url = "https://obfuscated.herokuapp.com/oauth2/v4/token",

        GetJson = Web.Contents(url,
            [
                Headers = [
                    #"Content-Type"="application/x-www-form-urlencoded"
                ],
                Content = Text.ToBinary(
                    // "code=" & #"Google API - Auth Code"
                    // "&redirect_uri=urn:ietf:wg:oauth:2.0:oob"

                    "refresh_token=" & #"Google API - Refresh Token"
                    & "&client_id=" & #"Google API - Client ID"
                    & "&client_secret=" & #"Google API - Client Secret"
                    // & "&scope=https://www.googleapis.com/auth/analytics.readonly"
                    & "&grant_type=refresh_token"
                    & "&nonce=" & someNonce
                )
            ]
        ),
        FormatAsJson = Json.Document(GetJson),

        // Gets token from the Json response
        AccessToken = FormatAsJson[access_token],
        AccessTokenHeader = "Bearer " & AccessToken
    in
        AccessTokenHeader
in
    Source

returnAccessHeaders_GA

GA API 不使用 nonce,我在这里使用它来允许 Power BI 最多缓存 API 请求一分钟。

let
    returnAccessHeaders = () as text => let
        nonce = DateTime.ToText(DateTime.LocalNow(), "yyyyMMddhhmm"),
        AccessTokenHeader = GetAccessToken_GA(nonce)

    in
        AccessTokenHeader
in
    returnAccessHeaders

parseJsonResponse_GA

let
    fetcher = (jsonResponse as binary) as table => let
        FormatAsJsonQuery = Json.Document(jsonResponse),

        columnHeadersGA = FormatAsJsonQuery[columnHeaders],
        listRows = Record.FieldOrDefault(
            FormatAsJsonQuery,
            "rows",
            {List.Transform(columnHeadersGA, each null)}
            // a list of (lists of length exactly matching the # of columns) of null
        ),
        columnNames = List.Transform(columnHeadersGA, each Record.Field(_, "name")),

        matchTypes = (column as record) as list => let
            values = {
                { "STRING", type text },
                { "FLOAT", type number },
                { "INTEGER", Int64.Type },
                { "TIME", type number },
                { "PERCENT", type number },
                { column[dataType], type text } // default type
            },

            columnType = List.First(
                List.Select(
                    values,
                    each _{0} = column[dataType]
                )
            ){1},

            namedColumnType = { column[name], columnType }

        in namedColumnType,

        recordRows = List.Transform(
            listRows,
            each Record.FromList(_, columnNames)
        ),

        columnTypes = List.Transform(columnHeadersGA, each matchTypes(_)),
        rowsTable = Table.FromRecords(recordRows),
        typedRowsTable = Table.TransformColumnTypes(rowsTable, columnTypes)

    in typedRowsTable

in fetcher

fetchAndParseGA

第一个参数Web.Contents()必须是字符串文字,否则会出现悲伤。

let
    AccessTokenHeader = returnAccessHeaders_GA(),

    fetchAndParseGA_fn = (url as text) as table => let
        JsonQuery = Web.Contents(
            "https://gapis-powerbi-revproxy.herokuapp.com/analytics",
                [
                    RelativePath = url,
                    Headers = [
                        #"Authorization" = AccessTokenHeader
                    ]
                ]
            ),
        Response = parseJsonResponse_GA(JsonQuery)
    in
        Response
in
    fetchAndParseGA_fn

queryUrlHelper

允许我们使用 Power BI 的“步骤编辑器”UI 来调整查询参数,并使用自动 URL 编码。

let
    safeString = (s as nullable text) as text => let
        result = if s = null
            then ""
            else s
    in
        result,

    uriEncode = (s as nullable text) as text => let
        result = Uri.EscapeDataString(safeString(s))
    in
        result,

    optionalParam = (name as text, s as nullable text) => let
        result = if s = null or s = ""
            then ""
            else "&" & name & "=" & uriEncode(s)
    in
        result,

    queryUrlHelper = (
        gaID as text,
        startDate as text,
        endDate as text,
        metrics as text,
        dimensions as nullable text,
        sort as nullable text,
        filters as nullable text,
        segment as nullable text,
        otherParameters as nullable text
    ) as text => let
        result = "/v3/data/ga?ids=" & uriEncode(gaID)
            & "&start-date=" & uriEncode(startDate)
            & "&end-date=" & uriEncode(endDate)
            & "&metrics=" & uriEncode(metrics)
            & optionalParam("dimensions", dimensions)
            & optionalParam("sort", sort)
            & optionalParam("filters", filters)
            & optionalParam("segment", segment)
            & safeString(otherParameters)
    in
        result,

    Example = queryUrlHelper(
        "ga:59361446", // gaID
        "MONTHSTART", // startDate
        "MONTHEND", // endDate
        "ga:sessions,ga:pageviews", // metrics
        "ga:userGender", // dimensions
        "-ga:sessions", // sort
        null, // filters
        "gaid::BD_Im9YKTJeO9xDxV4w6Kw", // segment
        null // otherParameters (must be manually url-encoded, and start with "&")
    )
in
    queryUrlHelper

getLinkForQueryExplorer

只是为了方便,在查询资源管理器中打开查询。

let
    getLinkForQueryExplorer = (querySuffixUrl as text) as text => let
        // querySuffixUrl should start like `/v3/data/ga?ids=ga:132248814&...`
        link = Text.Replace(
            querySuffixUrl,
            "/v3/data/ga",
            "https://ga-dev-tools.appspot.com/query-explorer/"
        )
    in
        link
in
    getLinkForQueryExplorer

Identity

返回其输入不变;此功能的主要用途是允许通过方便的“步骤编辑器”UI以另一种方式更新查询变量。

let
    Identity = (x as any) as any => let 
        x = x
    in
        x
in
    Identity

getMonthBoundary

// Get a list of the start and end dates of the relative month, as ISO 8601 formatted dates.
//
// The end date of the current month is considered to be the current date.
//
// E.g.:
// ```
// {
//     "2016-09-01",
//     "2016-09-31"
// }
// ```
//
// Source: <https://gist.github.com/r-k-b/db1eb0e00364cb592e1d8674bb03cb5c>

let
    GetMonthDates = (monthOffset as number) as list => let
        now = DateTime.LocalNow(),
        otherMonth = Date.AddMonths(now, monthOffset),
        month1Start = Date.StartOfMonth(otherMonth),
        month1End = Date.AddDays(Date.EndOfMonth(otherMonth), -1),

        dates = {
            month1Start,
            month1End
        },

        result = List.Transform(
            dates,
            each DateTime.ToText(_, "yyyy-MM-dd")
        )
    in
        result
in
    GetMonthDates

replaceUrlDates

// 
// E.g., on 2016-10-19 this is the result:
// ```
// replaceDates(-1, "/foo?s=MONTHSTART&e=MONTHEND") === "/foo?s=2016-09-01&e=2016-09-28"
// ```

let
    replaceDates = (monthOffset as number, rawUrl as text) as text => let
        boundaryList = getMonthBoundary(monthOffset),

        stage01 = Text.Replace(
            rawUrl,
            "MONTHSTART",
            boundaryList{0}
        ),

        stage02 = Text.Replace(
            stage01,
            "MONTHEND",
            boundaryList{1}
        ),

        stage03 = replaceViewNames(stage02)
    in
        stage03

in
    replaceDates

示例查询

let
    QueryBase = queryUrlHelper("All Web Site Data", "MONTHSTART", "today", "ga:sessions,ga:pageviews,ga:pageviewsPerSession", "ga:deviceCategory,ga:yearMonth", null, null, null, null),
    MonthOffset = Identity(#"Months Back to Query"),
    QueryURL = replaceUrlDates(MonthOffset, QueryBase),
    CopyableLinkToQueryExplorer = getLinkForQueryExplorer(QueryURL),
    Source = fetchAndParseGA(QueryURL)
in
    Source

作为奖励,这可以推广到任何 OAuthV2 数据源,并且还需要最少的调整来使用强大的 V4 API。

于 2016-11-25T04:50:37.857 回答
0

我不知道 power bia 但如果您可以发送 HTTP POST 您应该能够使用刷新令牌获取新的访问令牌

POST https://accounts.google.com/o/oauth2/token
client_id={ClientId}&client_secret={ClientSecret}&refresh_token={RefreshToken}&grant_type=refresh_token

响应应该是这样的

{
"access_token" : "ya29.1.AADtN_XK16As2ZHlScqOxGtntIlevNcasMSPwGiE3pe5ANZfrmJTcsI3ZtAjv4sDrPDRnQ",
"token_type" : "Bearer",
"expires_in" : 3600
}
于 2016-11-23T07:58:58.087 回答