diff --git a/Speckle.pq b/Speckle.pq index 98ac7b4..933ce1c 100644 --- a/Speckle.pq +++ b/Speckle.pq @@ -7,7 +7,85 @@ Speckle = [ TestConnection = (path) => {"Speckle.Api.GetUser", path}, // This is the custom authentication strategy for our Connector Authentication = [ - // + OAuth = [ + Label = "Speckle Login (latest only)", + StartLogin = (clientApplication, dataSourcePath, state, display) => + [ + LoginUri = Text.Combine({"https://latest.speckle.dev/authn/verify", "1ea917e25a", state}, "/"), + CallbackUri = "https://oauth.powerbi.com/views/oauthredirect.html", + WindowHeight = 800, + WindowWidth = 600, + Context = null + ], + FinishLogin = (clientApplication, dataSourcePath, context, callbackUri, state) => + let + Parts = Uri.Parts(callbackUri)[Query], + Source = Web.Contents( + Text.Combine({"https://latest.speckle.dev", "auth", "token"}, "/"), + [ + Headers = [ + #"Content-Type" = "application/json" + ], + Content = Json.FromValue( + [ + accessCode = Parts[access_code], + appId = "1ea917e25a", + appSecret = "7d1cb26028", + challenge = state + ] + ) + ] + ), + json = Json.Document(Source) + in + [ + access_token = json[token], + scope = null, + token_type = "bearer", + refresh_token = json[refreshToken] + ], + Refresh = (dataSourcePath, refreshToken) => + let + Source = Web.Contents( + Text.Combine({"https://latest.speckle.dev", "auth", "token"}, "/"), + [ + Headers = [ + #"Content-Type" = "application/json" + ], + Content = Json.FromValue( + [ + refreshToken = refreshToken, + appId = "1ea917e25a", + appSecret = "7d1cb26028" + ] + ) + ] + ), + json = Json.Document(Source) + in + [ + access_token = json[token], + scope = null, + token_type = "bearer", + refresh_token = json[refreshToken] + ], + Logout = (clientApplication, dataSourcePath, accessToken) => + let + Source = Web.Contents( + Text.Combine({"https://latest.speckle.dev", "auth", "logout"}, "/"), + [ + Headers = [ + #"Content-Type" = "application/json" + ], + Content = Json.FromValue([ + token = accessToken + ]) + ] + ), + json = Json.Document(Source) + in + json + ], Key = [ KeyLabel = "Personal Access Token", Label = "Private stream" @@ -21,7 +99,7 @@ Speckle = [ // Gets the object referenced by a specific speckle URL [DataSource.Kind = "Speckle", Publish = "Get.ByUrl.Publish"] -shared Speckle.Get.ByUrl = Value.ReplaceType( +shared Speckle.GetByUrl.Structured = Value.ReplaceType( Speckle.LoadFunction("Get.ByUrl.pqm"), type function ( url as ( @@ -45,11 +123,10 @@ shared Speckle.Get.ByUrl = Value.ReplaceType( ] ); -[DataSource.Kind = "Speckle", Publish = "NavTable.Publish"] -shared Speckle.GetObjectAsNavTable = Value.ReplaceType( - NavigationTable.Simple, type function (url as Uri.Type) as table -); - +// [DataSource.Kind = "Speckle", Publish = "NavTable.Publish"] +// shared Speckle.GetObjectAsNavTable = Value.ReplaceType( +// NavigationTable.Simple, type function (url as Uri.Type) as table +// ); // Get's a flat list of speckle objects from a URL [DataSource.Kind = "Speckle", Publish = "GetByUrl.Publish"] shared Speckle.GetByUrl = Value.ReplaceType( @@ -92,6 +169,11 @@ shared Speckle.Api.Fetch = Value.ReplaceType( // Parses a stream url and returns a record with the type and values shared Speckle.ParseUrl = Speckle.LoadFunction("ParseStreamUrl.pqm"); +// [DataSource.Kind = "Speckle"] +// shared Speckle.Api.REST.GetObject = Value.ReplaceType( +// Speckle.LoadFunction("Api.REST.GetObject.pqm"), +// type function (url as Uri.Type, optional streamId as text, optional objectId as text) as list +// ); Get.ByUrl.Publish = GetPublish("GetStream"); NavTable.Publish = GetPublish("GetObjectAsNavTable"); @@ -117,33 +199,3 @@ shared Speckle.LoadFunction = (fileName as text) => Message.Parameters = {fileName, e[Reason], e[Message]}, Detail = [File = fileName, Error = e] ]; - -//HACK: Test functions for nav table stuff -NavigationTable.Simple = (url as text) => - let - objects = #table( - {"Name", "Key", "Data", "ItemKind", "ItemName", "IsLeaf"}, - { - {"Item1", "item1", #table({"Column1"}, {{"Item1"}}), "Table", "Table", true}, - {"Item2", "item2", #table({"Column1"}, {{"Item2"}}), "Table", "Table", true}, - {"Item3", "item3", FunctionCallThatReturnsATable(), "Table", "Table", true}, - {url, "myfunction", FunctionCallThatReturnsATable, "Function", "Function", true} - } - ), - NavTable = Table.ToNavigationTable(objects, {"Key"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf") - in - NavTable; - -FunctionCallThatReturnsATable = () => #table({"DynamicColumn"}, {{"Dynamic Value"}}); -CreateNavTable = (message as text) as table => - let - objects = #table( - {"Name", "Key", "Data", "ItemKind", "ItemName", "IsLeaf"}, - { - {"Item1", "item1", #table({"Column1"}, {{message}}), "Table", "Table", true}, - {"Item2", "item2", #table({"Column1"}, {{message}}), "Table", "Table", true} - } - ), - NavTable = Table.ToNavigationTable(objects, {"Key"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf") - in - NavTable; diff --git a/speckle/api/Api.REST.GetObject.pqm b/speckle/api/Api.REST.GetObject.pqm new file mode 100644 index 0000000..ec2d72f --- /dev/null +++ b/speckle/api/Api.REST.GetObject.pqm @@ -0,0 +1,38 @@ +(server as text, optional streamId as text, optional objectId as text) as table => + let + apiKey = try Extension.CurrentCredential()[Key] otherwise null, + Source = Web.Contents( + Text.Combine({server, "objects", streamId, objectId}, "/"), + [ + Headers = [ + #"Method" = "GET", + #"Content-Type" = "application/json", + #"Authorization" = if apiKey = null then "" else Text.Format("Bearer #{0}", {apiKey}) + ], + ManualStatusHandling = {400} + ] + ), + json = Json.Document(Source), + clean = List.Select(json, each _[speckle_type] <> "Speckle.Core.Models.DataChunk"), + t = Table.FromColumns({clean}, {"data"}), + addStreamUrl = Table.AddColumn(t, "Stream URL", each server & "/streams/" & streamId), + addObjectIdCol = Table.AddColumn(addStreamUrl, "Object ID", each try _[data][id] otherwise null), + addSpeckleTypeCol = Table.AddColumn( + addObjectIdCol, "speckle_type", each try _[data][speckle_type] otherwise null + ), + Speckle.CleanUpObjects = Extension.LoadFunction("CleanUpObjects.pqm"), + Extension.LoadFunction = (fileName as text) => + let + binary = Extension.Contents(fileName), asText = Text.FromBinary(binary) + in + try + Expression.Evaluate(asText, #shared) catch (e) => + error + [ + Reason = "Extension.LoadFunction Failure", + Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'", + Message.Parameters = {fileName, e[Reason], e[Message]}, + Detail = [File = fileName, Error = e] + ] + in + addSpeckleTypeCol diff --git a/speckle/helpers/CleanUpObjects.pqm b/speckle/helpers/CleanUpObjects.pqm index a3f505f..c1f7c12 100644 --- a/speckle/helpers/CleanUpObjects.pqm +++ b/speckle/helpers/CleanUpObjects.pqm @@ -14,4 +14,4 @@ ), removed = List.Select(removeTotals, each _[data][speckle_type] <> "Speckle.Core.Models.DataChunk") in - objects + removed diff --git a/tests/api/api.rest.getobject.test.pq b/tests/api/api.rest.getobject.test.pq new file mode 100644 index 0000000..e1e72ec --- /dev/null +++ b/tests/api/api.rest.getobject.test.pq @@ -0,0 +1,7 @@ +// Use this file to write queries to test your data connector +let + result = Speckle.Api.REST.GetObject( + "https://latest.speckle.dev", "5f284e5c70", "85e5f250fe591ea74d8d5dc1137a9341" + ) +in + result diff --git a/tests/getbyurl.test.pq b/tests/getbyurl.test.pq index 2432750..8ab6148 100644 --- a/tests/getbyurl.test.pq +++ b/tests/getbyurl.test.pq @@ -1,2 +1,2 @@ // Use this file to write queries to test your data connector -let result = Speckle.GetByUrl("https://latest.speckle.dev/streams/3d25474a18") in result +let result = Speckle.GetByUrl("https://latest.speckle.dev/streams/5f284e5c70/objects/85e5f250fe591ea74d8d5dc1137a9341") in result