Skip to content

Commit

Permalink
Adds support for REST endpoint to get objects (#34)
Browse files Browse the repository at this point in the history
* feat: Working REST endpoint to fetch objects instead of GraphQL

* hack: Temporarily exposed rest function

* feat: Added OAuth test login

* chore: Cleanup for release
  • Loading branch information
AlanRynne authored Dec 15, 2022
1 parent f9093a3 commit f558d5d
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 39 deletions.
126 changes: 89 additions & 37 deletions Speckle.pq
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 (
Expand All @@ -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(
Expand Down Expand Up @@ -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");
Expand All @@ -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;
38 changes: 38 additions & 0 deletions speckle/api/Api.REST.GetObject.pqm
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion speckle/helpers/CleanUpObjects.pqm
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
),
removed = List.Select(removeTotals, each _[data][speckle_type] <> "Speckle.Core.Models.DataChunk")
in
objects
removed
7 changes: 7 additions & 0 deletions tests/api/api.rest.getobject.test.pq
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/getbyurl.test.pq
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit f558d5d

Please sign in to comment.