From 9a5c1283ce64d9219ee29b581d3c31822aa75191 Mon Sep 17 00:00:00 2001 From: Alan Rynne Date: Thu, 9 Nov 2023 16:48:36 +0100 Subject: [PATCH] Adds Frontend2 URL Support for single model urls (#58) * feat: Adds FE2 Url support for single model urls * fix: Minor formatting changes --- Speckle.pq | 17 +++++-- Speckle.query.pq | 2 +- speckle/api/Api.GetModel.pqm | 32 ++++++++++++ speckle/helpers/ParseStreamUrl.pqm | 78 +++++++++++++++++++----------- 4 files changed, 97 insertions(+), 32 deletions(-) create mode 100644 speckle/api/Api.GetModel.pqm diff --git a/Speckle.pq b/Speckle.pq index 2f97d68..83b2d28 100644 --- a/Speckle.pq +++ b/Speckle.pq @@ -14,7 +14,9 @@ Speckle = [ Label = "Speckle.xyz", StartLogin = (clientApplication, dataSourcePath, state, display) => let - server = Text.Combine({Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]}) + server = Text.Combine( + {Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]} + ) in [ LoginUri = Text.Combine({server, "authn", "verify", AuthAppId, state}, "/"), @@ -25,7 +27,9 @@ Speckle = [ ], FinishLogin = (clientApplication, dataSourcePath, context, callbackUri, state) => let - server = Text.Combine({Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]}), + server = Text.Combine( + {Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]} + ), Parts = Uri.Parts(callbackUri)[Query], Source = Web.Contents( Text.Combine({server, "auth", "token"}, "/"), @@ -53,7 +57,9 @@ Speckle = [ ], Refresh = (dataSourcePath, refreshToken) => let - server = Text.Combine({Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]}), + server = Text.Combine( + {Uri.Parts(dataSourcePath)[Scheme], "://", Uri.Parts(dataSourcePath)[Host]} + ), Source = Web.Contents( Text.Combine({server, "auth", "token"}, "/"), [ @@ -159,7 +165,10 @@ 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.ParseUrl = Value.ReplaceType( + Speckle.LoadFunction("ParseStreamUrl.pqm"), type function (url as Uri.Type) as record +); // [DataSource.Kind = "Speckle"] // shared Speckle.Api.REST.GetObject = Value.ReplaceType( diff --git a/Speckle.query.pq b/Speckle.query.pq index 3112ede..49bfa0d 100644 --- a/Speckle.query.pq +++ b/Speckle.query.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/4e51c4025f/commits/766c20c5fb") in result +let result = Speckle.GetByUrl("https://app.speckle.systems/projects/e2988234fb/models/60b2300470@b1f31a351a") in result diff --git a/speckle/api/Api.GetModel.pqm b/speckle/api/Api.GetModel.pqm new file mode 100644 index 0000000..428ccbd --- /dev/null +++ b/speckle/api/Api.GetModel.pqm @@ -0,0 +1,32 @@ +let + Speckle.Api.Fetch = Extension.LoadFunction("Api.Fetch.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 + (server as text, projectId as text, modelId as text) => + let + query = "query Project($projectId: String!, $modelId: String!) { + project(id: $projectId) { + model(id: $modelId) { + name + } + } + }", + variables = [ + projectId = projectId, + modelId = modelId + ] + in + // Read receipts should fail gracefully no matter what + try Speckle.Api.Fetch(server, query, variables)[project][model] otherwise null diff --git a/speckle/helpers/ParseStreamUrl.pqm b/speckle/helpers/ParseStreamUrl.pqm index b54cc35..2ed3e82 100644 --- a/speckle/helpers/ParseStreamUrl.pqm +++ b/speckle/helpers/ParseStreamUrl.pqm @@ -1,27 +1,51 @@ -(url as text) as record => - let - // Get server and streamId, and branchName / commitId / objectid from the input url - server = Text.Combine({Uri.Parts(url)[Scheme], "://", Uri.Parts(url)[Host]}), - segments = Text.Split(Text.AfterDelimiter(Uri.Parts(url)[Path], "/", 0), "/"), - streamId = segments{1}, - branchName = if (List.Count(segments) = 4 and segments{2} = "branches") then segments{3} else null, - commitId = if (List.Count(segments) = 4 and segments{2} = "commits") then segments{3} else null, - objectId = if (List.Count(segments) = 4 and segments{2} = "objects") then segments{3} else null, - urlType = - if (commitId <> null) then - "Commit" - else if (objectId <> null) then - "Object" - else if (branchName <> null) then - "Branch" - else - "Stream" - in - [ - urlType = urlType, - server = server, - id = streamId, - branch = branchName, - commit = commitId, - object = objectId - ] +let + GetModel = Extension.LoadFunction("Api.GetModel.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 + (url as text) as record => + let + // Get server and streamId, and branchName / commitId / objectid from the input url + server = Text.Combine({Uri.Parts(url)[Scheme], "://", Uri.Parts(url)[Host]}), + segments = Text.Split(Text.AfterDelimiter(Uri.Parts(url)[Path], "/", 0), "/"), + streamId = segments{1}, + modelList = if (List.Count(segments) = 4 and segments{2} = "models") then segments{3} else null, + firstModel = Text.Split(modelList, ","){0}, + modelAndVersion = Text.Split(firstModel, "@"), + modelId = modelAndVersion{0}, + versionId = if (List.Count(modelAndVersion) > 1) then modelAndVersion{1} else null, + model = if (modelId <> null) then GetModel(server, streamId, modelId) else null, + branchName = if (List.Count(segments) = 4 and segments{2} = "branches") then segments{3} else null, + commitId = if (List.Count(segments) = 4 and segments{2} = "commits") then segments{3} else null, + objectId = if (List.Count(segments) = 4 and segments{2} = "objects") then segments{3} else null, + modelOrBranchName = if (model <> null) then model[name] else branchName, + commitOrVersion = if (versionId <> null) then versionId else commitId, + urlType = + if (commitOrVersion <> null) then + "Commit" + else if (objectId <> null) then + "Object" + else if (modelOrBranchName <> null) then + "Branch" + else + "Stream" + in + [ + urlType = urlType, + server = server, + id = streamId, + branch = modelOrBranchName, + commit = commitOrVersion, + object = objectId + ]