我們有類似的需求直接連接到Analytics API,以規避內置連接器的缺點。讓PowerBI的Web版本接受作爲「匿名」源的auth端點是有點尷尬的,但是一個反向代理可以通過用200 OK
響應'probe'GET請求來欺騙它。 這裏的主要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,我用它這裏允許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
可以讓我們美國的力量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
只是一個方便,在the Query Explorer.
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
返回其I輸入不變;此功能的主要用途是允許通過方便的「步驟編輯器」UI以另一種方式訪問update query variables。
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數據源,還需要最少的調整與the powerful V4 API.
是工作,我有錯誤HTTP!這就是爲什麼我得到了錯誤。我終於搞定了,謝謝! – ruthpozuelo
我可以問一個跟進問題嗎?爲了獲得驗證碼,我使用了上面在瀏覽器中提到的URL,然後在Power Query中複製粘貼驗證碼。現在,Power Query中有沒有辦法做到這一點? – ruthpozuelo