Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions PQGoogleSpreadsheet/PQGoogleSpreadsheet.mproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>
</ProjectGuid>
<ProjectGuid>{2bd3d055-377f-4ad2-8362-acb114b459dd}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MyRootNamespace</RootNamespace>
<AssemblyName>MyAssemblyName</AssemblyName>
Expand Down Expand Up @@ -67,12 +66,6 @@
<Compile Include="resources.resx">
<SubType>Code</SubType>
</Compile>
<Content Include="client_id.txt">
<SubType>Content</SubType>
</Content>
<Content Include="client_secret.txt">
<SubType>Content</SubType>
</Content>
<Content Include="PQGoogleSpreadsheet.query.pq">
<SubType>Code</SubType>
</Content>
Expand Down
142 changes: 91 additions & 51 deletions PQGoogleSpreadsheet/PQGoogleSpreadsheet.pq
Original file line number Diff line number Diff line change
@@ -1,54 +1,95 @@
// This file contains your Data Connector logic
section PQGoogleSpreadsheet;
section PQGoogleSpreadsheet;


// Constants
client_id = Text.FromBinary(Extension.Contents("client_id.txt"));
client_secret = Text.FromBinary(Extension.Contents("client_secret.txt"));
appKey = "YOUR_APP_KEY";
appSecret = "YOUR_APP_SECRET";
redirect_uri = "https://preview.powerbi.com/views/oauthredirect.html";
authorize_uri = "https://accounts.google.com/o/oauth2/v2/auth";
token_uri = "https://www.googleapis.com/oauth2/v4/token";
logout_uri = "https://accounts.google.com/o/oauth2/revoke?token=";

scope_prefix = "https://www.googleapis.com/auth/";
scopes = {
"drive.readonly"
};

windowWidth = 1200;
windowHeight = 1000;


[DataSource.Kind="PQGoogleSpreadsheet", Publish="PQGoogleSpreadsheet.UI"]

shared PQGoogleSpreadsheet.Contents = Value.ReplaceType(PQGoogleSpreadsheetCore.Contents, type function (#"Google Spreadsheet url" as Uri.Type) as any);

shared PQGoogleSpreadsheetCore.Contents = (url as text) =>
let
finalUrl = Text.BeforeDelimiter(url, "/", {0, RelativePosition.FromEnd}) & "/export?format=xlsx",
content = Web.Contents(finalUrl),
excel = Excel.Workbook(content, null, true)
excel = Web.ContentsCustomRetry(finalUrl)
in
excel;


Excel.Workbook(excel, null, true);

// https://gist.github.com/CurtHagenlocher/68ac18caa0a17667c805
Web.ContentsCustomRetry = (url as text, optional options as record) => Value.WaitFor(
(i) =>
let
options2 = if options = null then [] else options,
options3 = if i=0 then options2 else options2 & [IsRetry=true],
result = Web.Contents(url, options3 & [ManualStatusHandling={429}]),
buffered = Binary.Buffer(result), /* avoid risk of double request */
status = if buffered = null then 0 else Value.Metadata(result)[Response.Status],
actualResult = if status = 429 then null else buffered
in
actualResult,
(i) => #duration(0, 0, 0, i*0.1));

// https://docs.microsoft.com/en-us/power-query/helperfunctions#valuewaitfor
// This function is useful when making an asynchronous HTTP request and you need to poll the server until the request is complete.
Value.WaitFor = (producer as function, interval as function, optional count as number) as any =>
let
list = List.Generate(
() => {0, null},
(state) => state{0} <> null and (count = null or state{0} < count),
(state) => if state{1} <> null then {null, state{1}} else {1 + state{0}, Function.InvokeAfter(() => producer(state{0}), interval(state{0}))},
(state) => state{1})
in
List.Last(list);

// Data Source Kind description
PQGoogleSpreadsheet = [
Authentication = [
Authentication = [
OAuth = [
StartLogin = StartLogin,
FinishLogin = FinishLogin,
Logout = Logout,
Refresh = Refresh,
Label = "Google Spreadsheet Auth"
]
],
Label = "Google Spreadsheet Connector"
]
// TestConnection is required to enable the connector through the Gateway
,TestConnection = (dataSourcePath) => { "PQGoogleSpreadsheet.Contents" , dataSourcePath }
,Label = "Google Spreadsheet Connector"
];

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;

StartLogin = (resourceUrl, state, display) =>
let
AuthorizeUrl = "https://accounts.google.com/o/oauth2/v2/auth?" & Uri.BuildQueryString([
scope = "https://www.googleapis.com/auth/drive.readonly",
access_type = "offline",
include_granted_scopes = "true",
client_id = client_id,
state = state,
redirect_uri = redirect_uri,
response_type = "code"])
AuthorizeUrl = authorize_uri & "?" & Uri.BuildQueryString([
client_id = appKey,
redirect_uri = redirect_uri,
state= state,
scope = GetScopeString(scopes, scope_prefix),
response_type = "code",
access_type = "offline",
prompt = "consent"])
in
[
LoginUri = AuthorizeUrl,
Expand All @@ -58,53 +99,52 @@ StartLogin = (resourceUrl, state, display) =>
Context = null
];


FinishLogin = (context, callbackUri, state) =>
let
Parts = Uri.Parts(callbackUri)[Query]
parts = Uri.Parts(callbackUri)[Query],
result = if (Record.HasFields(parts, {"error", "error_description"})) then
error Error.Record(parts[error], parts[error_description], parts)
else
TokenMethod("authorization_code", parts[code])
in
TokenMethod(Parts[code]);

result;

TokenMethod = (code) =>
TokenMethod = (grantType, code) =>
let
Response = Web.Contents("https://www.googleapis.com/oauth2/v4/token", [
Content = Text.ToBinary(Uri.BuildQueryString([
client_id = client_id,
client_secret = client_secret,
code = code,
grant_type = "authorization_code",
access_type = "offline",
redirect_uri = redirect_uri])),
Headers=[#"Content-type" = "application/x-www-form-urlencoded", #"Accept" = "application/json"],
ManualStatusHandling = {400}
]),
Body = Json.Document(Response),
Result = if Record.HasFields(Body, {"error", "error_description"}) then
error Error.Record(Body[error], Body[error_description], Body)
else
Body
query = [
grant_type = grantType,
client_id = appKey,
client_secret = appSecret,
redirect_uri = redirect_uri
],

// {CODE From Matt Masson} added for Google API - field is "code" on initial auth, and "refresh_token" for refresh
queryWithCode = if (grantType = "refresh_token") then [ refresh_token = code ] else [code = code],

response = Web.Contents(token_uri, [
Content = Text.ToBinary(Uri.BuildQueryString(query & queryWithCode)),
Headers=[#"Content-type" = "application/x-www-form-urlencoded",#"Accept" = "application/json"], ManualStatusHandling = {400}]),
body = Json.Document(response),
result = if (Record.HasFields(body, {"error", "error_description"})) then
error Error.Record(body[error], body[error_description], body)
else
body
in
Result;
result;

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

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

Logout = (token) => "https://accounts.google.com/o/oauth2/revoke?token=" & token;

Logout = (token) => logout_uri & token;

// Data Source UI publishing description
PQGoogleSpreadsheet.UI = [
Beta = true,
Category = "Other",
ButtonText = { "Google Spreadsheet Connector", "Google Spreadsheet Connector Help" },
LearnMoreUrl = "http://skolenipowerbi.cz/",
Category = "File",
SourceImage = PQGoogleSpreadsheet.Icons,
SourceTypeImage = PQGoogleSpreadsheet.Icons
];



PQGoogleSpreadsheet.Icons = [
Icon16 = { Extension.Contents("PQGoogleSpreadsheet16.png"), Extension.Contents("PQGoogleSpreadsheet20.png"), Extension.Contents("PQGoogleSpreadsheet24.png"), Extension.Contents("PQGoogleSpreadsheet32.png") },
Icon32 = { Extension.Contents("PQGoogleSpreadsheet32.png"), Extension.Contents("PQGoogleSpreadsheet40.png"), Extension.Contents("PQGoogleSpreadsheet48.png"), Extension.Contents("PQGoogleSpreadsheet64.png") }
Expand Down
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet20.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet24.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet40.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet48.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified PQGoogleSpreadsheet/PQGoogleSpreadsheet80.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions PQGoogleSpreadsheet/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Google Spreadsheets Connector for Microsoft Power BI

To make it run add client_id.txt and client_secret.txt containing your Google API client credentials generated in:
https://console.developers.google.com/

To make it work with please replace in the code:

appKey = "YOUR_APP_KEY";
appSecret = "YOUR_APP_SECRET";

with the credentials you've created at https://console.developers.google.com/apis
Binary file removed PQGoogleSpreadsheet/logo-drive.png
Binary file not shown.
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
# Google Spreadsheets Connector for Microsoft Power BI


To make this connector work fully securely use this tutorial:
http://www.skolenipowerbi.cz/l/google-drive-connector-kompletni-pruvodce/


If you need just an a quick way to connect to Google Drive, use this tutorial:
http://www.skolenipowerbi.cz/l/google-drive-connector-compact-tutorial/

How to set up:
https://fpvmorais.com/post/google-spreadsheets-custom-connector/

Hope you enjoy the Google Spreadsheets Connector.