Compare commits

...

58 Commits

Author SHA1 Message Date
Alan Rynne f558d5dce3 Adds support for REST endpoint to get objects (#34)
* feat: Working REST endpoint to fetch objects instead of GraphQL

* hack: Temporarily exposed rest function

* feat: Added OAuth test login

* chore: Cleanup for release
2022-12-15 12:06:19 +01:00
Alan Rynne f9093a34e5 fixes #33 2022-12-06 12:06:42 +01:00
Alan Rynne 23cb40c4ab Fixes slow behaviour in Speckle.GetByUrl (#31)
* fix: Swapped slow RemoveItems for faster Select

* feat: Added `select` input to `GetObjectChildren` function

* fix: Increased pagination to 1000
2022-12-05 22:53:56 +01:00
Alan Rynne 9bd6140065 CI: Adds build and deploy configs (#30)
* Add .circleci/config.yml

* First attempt

* fix: Missing win orb

* fix: Wrong persist path

* ci: Added version updating step

* ci: Fixed artifacts paths

* ci: Added publish to github release step

* Fixing ci paths to persist

* fix: Artifacts path

* ci: Fix deploy dependency order

* ci: Adds tags to ghr inputs
2022-12-05 17:59:20 +01:00
Alan Rynne a14bb06d4e Merge pull request #29 from specklesystems/unit-testing
General improvements in DataConnector
2022-12-05 13:51:11 +01:00
Alan Rynne 01576e77df chore: Reorganised tests 2022-12-05 10:19:32 +01:00
Alan Rynne f1e16e9d55 feat: More refactoring, tests and new exposed functions 2022-12-04 22:50:42 +01:00
Alan Rynne 4f210dae04 feat: Refactor of helper methods 2022-12-02 00:00:04 +01:00
Alan Rynne 8669cf59f8 fix: Minor fixes in imports + test.query.pq file 2022-12-01 17:03:15 +01:00
Alan Rynne 6e6402cb38 Merge pull request #18 from specklesystems/refactor/vs-code-extension
Refactor: Use new VSCode Extension + Modularise data connector
2022-11-29 12:34:08 +01:00
Alan Rynne 7b62630fd1 fix: MIssing getUser function in LogEvent 2022-11-29 12:28:24 +01:00
Alan Rynne eee4f0f8c2 feat: Added utility functions from powerquery docs 2022-11-28 23:05:41 +01:00
Alan Rynne 23fa79bdd9 fix: Renamed commitReceived function 2022-11-28 22:53:23 +01:00
Alan Rynne edc9917a20 feat: Split down into modules 2022-11-28 22:38:59 +01:00
Alan Rynne 99cffeba8c feat: Working with new VSCode extension 2022-11-28 20:47:28 +01:00
Alan Rynne 7e92e7a2aa Fixes #16 2022-11-25 13:48:06 +01:00
Alan Rynne 15aa627d7f fix: Failed receive on public streams due to read receipts (fixes #15) 2022-10-27 23:23:37 +02:00
Alan Rynne 8844f1837e feat: Updated mixpanel logging with sourceApp variables 2022-10-07 12:58:35 +02:00
Alan Rynne 6d1b85a14f Revert "fix: Read receipt was using hardcoded token? "
This reverts commit b2d6568b66.
2022-09-01 10:39:01 +02:00
Alan Rynne b2d6568b66 fix: Read receipt was using hardcoded token? 2022-09-01 10:33:05 +02:00
Alan Rynne 72f455393b fix: Prevent read receipt when no credentials are found 2022-09-01 10:13:20 +02:00
Alan Rynne bd3fe6a47c feat: Refactored output table for viewer compatibility 2022-08-31 11:25:46 +02:00
Alan Rynne e7710f0f8e fix: Remove null row at the end or result table 2022-08-25 11:08:09 +02:00
Alan Rynne e9034027eb feat: Added columns to FetchByUrl result table for plug-and-play with the viewer 2022-08-25 10:42:03 +02:00
Alan Rynne 685384b147 feat: Added mixpanel user and server hashed 2022-04-07 15:37:52 +02:00
Alan Rynne 5973aaea10 feat: Added user fetch function 2022-04-07 12:17:17 +02:00
Alan Rynne 639287f69c feat: Added mixpanel tracking 2022-04-06 12:55:49 +02:00
Alan Rynne a43decd473 Merge pull request #12 from specklesystems/fix/branch-special-chars
Fix: Uses powerquery `buildQueryString` method to ensure branch name is url safe first
2021-12-03 12:18:19 +01:00
Alan Rynne a532061879 Fix: Uses powerquery buildQueryString method to ensure branch name is url safe first 2021-12-03 12:16:50 +01:00
Alan Rynne 15ac7c041e Merge pull request #10 from specklesystems/hotfix/url-parsing
fix: Fixes wrong implementation assignment and optional input removal
2021-12-02 12:17:03 +01:00
Alan Rynne 770d45f981 fix: Fixes wrong implementation assignment and optional input removal 2021-12-02 12:15:58 +01:00
Alan Rynne 50b59a88c3 Merge pull request #9 from specklesystems/alan/error-reporting
Stable release final touches 🪄
2021-11-29 12:41:06 +01:00
Alan Rynne 0bad9914c8 fix: Removed shared tag from API function 2021-11-29 12:40:54 +01:00
Alan Rynne aee00f3f21 fix: Removed __closure and totalChildrenCount fields. 2021-11-29 12:31:45 +01:00
Alan Rynne 2abdd4b645 fix: Removed duplicated icons and fixed folder path 2021-11-29 12:31:19 +01:00
Alan Rynne 71ef1acd8b feat: Final polish!! 2021-11-29 11:39:39 +01:00
Alan Rynne 23114a2912 chore: Pending file deletion 2021-11-25 10:02:42 +01:00
Alan Rynne ee989f927d feat: Better error handling and initial function documentation 2021-11-25 10:01:09 +01:00
Alan Rynne 1ff15c7b44 chore: Moved logos into subfolder 2021-11-25 10:00:45 +01:00
Alan Rynne 6e9fb590a0 chore: Removed duplicated api call 2021-11-24 18:58:53 +01:00
Alan Rynne 9700865934 Merge pull request #7 from specklesystems/alan/table-pagination
Adds pagination to object queries
2021-11-24 10:58:48 +01:00
Alan Rynne cf9e5a70b6 chore: Reordering of methods for clarity 2021-11-24 09:52:04 +01:00
Alan Rynne 50c360ae79 feat: Pagination works!! Plus query formating 2021-11-24 09:31:39 +01:00
Alan Rynne b671945fa7 feat: More re-structuring + pagination boilerplate 2021-11-24 09:05:52 +01:00
Alan Rynne eb4787ddfa fix: Removed redundant implementation 2021-11-23 16:40:31 +01:00
Alan Rynne 3ffd6c63e0 feat: Fetch refactoring 2021-11-23 16:33:08 +01:00
Alan Rynne a5a8316ee0 Merge pull request #6 from specklesystems/alan/receive-receipts
feat: Added receive receipts to PowerBI and branch URL support
2021-11-19 15:21:13 +01:00
Alan Rynne 17083c9474 feat: Branch url support! + minor mismatch in auth 2021-11-19 12:07:12 +01:00
Alan Rynne 3e6f9988d6 feat: Added receive receipts to PowerBI 2021-11-18 13:51:10 +01:00
Alan Rynne fe1504d1a9 Merge pull request #4 from specklesystems/claire/remove-chunks-etc
PowerBI Improvements
2021-10-08 18:44:47 +02:00
Alan Rynne c753e13859 LogToMatomo() cleanup 2021-10-08 12:46:58 +02:00
Alan Rynne fc2ffa75ba metrics: Added explanation of how logToMatomo works 2021-10-07 19:43:09 +02:00
Alan Rynne de43e34c11 metrics: third's the charm 🪄 2021-10-07 19:41:53 +02:00
Alan Rynne 70eb1059d2 metrics: second attempt 2021-10-07 19:29:14 +02:00
Alan Rynne 9e5c204c8f metrics: First attempt 2021-10-07 18:51:26 +02:00
Claire Kuang e0f2782c8b removes closures and datachunks 2021-10-07 12:01:26 +01:00
Matteo Cominetti baf2132d1b Create close-issue.yml 2021-10-02 17:10:13 +01:00
Matteo Cominetti eef502f42f Create open-issue.yml 2021-10-02 17:09:59 +01:00
55 changed files with 2161 additions and 481 deletions
+58
View File
@@ -0,0 +1,58 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1
orbs:
win: circleci/windows@5.0
jobs:
build-connector:
executor:
name: win/default
shell: powershell.exe
steps:
- checkout
- run:
name: "Set connector internal version"
command: |
$VERSION = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "2.0.0.$($env:WORKFLOW_NUM)" } else { $env:CIRCLE_TAG }
(Get-Content ./Speckle.pq).replace('[Version = "2.0.0"]', '[Version = "'+$($env:VERSION)+'"]') | Set-Content ./Speckle.pq
- run:
name: "Build Data Connector"
command: "msbuild Speckle.proj /restore /consoleloggerparameters:NoSummary /property:GenerateFullPaths=true"
- store_artifacts:
path: ./bin
- persist_to_workspace:
root: ./
paths:
- bin/*
deploy-connector:
docker:
- image: cibuilds/github:0.10
steps:
- attach_workspace:
at: ./
- run:
name: "Publish Release on GitHub"
command: |
ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${CIRCLE_TAG} ./bin/
workflows:
build:
jobs:
- build-connector
deploy:
jobs:
- build-connector:
filters:
branches:
ignore: /.*/ # For testing only: /ci\/.*/
tags:
only: /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w{1,10})?$/
- deploy-connector:
filters:
branches:
ignore: /.*/ # For testing only: /ci\/.*/
tags:
only: /^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w{1,10})?$/
requires:
- build-connector
+77
View File
@@ -0,0 +1,77 @@
name: Update issue Status
on:
issues:
types: [closed]
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 11
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
+50
View File
@@ -0,0 +1,50 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 11
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
+12
View File
@@ -0,0 +1,12 @@
{
"configurations": [
{
"type": "powerquery",
"request": "launch",
"name": "Test powerquery file",
"program": "${workspaceFolder}/${command:AskForPowerQueryFileName}",
"additionalArgs": ["--logMashupEngineTraces", "user"],
"preLaunchTask": "build"
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"powerquery.general.mode": "SDK",
"powerquery.sdk.defaultExtension": "${workspaceFolder}\\bin\\Speckle.mez",
"powerquery.sdk.defaultQueryFile": "${workspaceFolder}\\Speckle.query.pq"
}
+31
View File
@@ -0,0 +1,31 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "powerquery",
"operation": "msbuild",
"additionalArgs": [
"/restore",
"/consoleloggerparameters:NoSummary",
"/property:GenerateFullPaths=true"
],
"problemMatcher": ["$msCompile"],
"group": "build",
"label": "build"
},
{
"type": "shell",
"label": "tests",
"command": "${config:powerquery.sdk.tools.location}\\PQTest.exe",
"args": [
"run-test",
"--extension",
"${config:powerquery.sdk.defaultExtension}",
"--queryFile",
"${workspaceFolder}\\tests",
"--prettyPrint"
],
"problemMatcher": []
}
]
}
+201
View File
@@ -0,0 +1,201 @@
[Version = "2.0.0"]
section Speckle;
// The data source definition, used when connecting to any speckle server
Speckle = [
// This is used when running the connector on an on-premises data gateway
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"
],
Implicit = [
Label = "Public stream"
]
],
Label = "Speckle"
];
// Gets the object referenced by a specific speckle URL
[DataSource.Kind = "Speckle", Publish = "Get.ByUrl.Publish"]
shared Speckle.GetByUrl.Structured = Value.ReplaceType(
Speckle.LoadFunction("Get.ByUrl.pqm"),
type function (
url as (
Uri.Type meta [
Documentation.FieldCaption = "Gets a Speckle Object preserving it's structure",
Documentation.FieldDescription = "The url of a stream in a Speckle server. You can copy it directly from your browser.",
Documentation.SampleValues = {
"https://speckle.xyz/streams/23401adf",
"https://speckle.xyz/streams/23401adf/branches/main"
}
]
)
) as record meta [
Documentation.Name = "Speckle - Get Structured Object by URL",
Documentation.LongDescription = "Returns the Speckle object the URL points to, while also preserving it's structure.
Supports all types of stream url:#(lf)
- Stream: will get the latest commit on the 'main' branch (i.e. 'https://speckle.xyz/streams/STREAM_ID')#(lf)
- Branch: will get the latest commit on the specified branch (i.e. 'https://speckle.xyz/streams/STREAM_ID/branches/BRANCH_NAME')#(lf)
- Commit: will get a specific commit from the stream (i.e. 'https://speckle.xyz/streams/STREAM_ID/commits/COMMIT_ID')
"
]
);
// [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(
Speckle.LoadFunction("GetByUrl.pqm"),
type function (
url as (
Uri.Type meta [
Documentation.FieldCaption = "Stream URL",
Documentation.FieldDescription = "The url of a stream in a Speckle server. You can copy it directly from your browser.",
Documentation.SampleValues = {
"https://speckle.xyz/streams/23401adf",
"https://speckle.xyz/streams/23401adf/branches/main"
}
]
)
) as table meta [
Documentation.Name = "Speckle - Get stream by URL",
Documentation.LongDescription = "Returns a flat list of all objects contained in a specific Speckle stream/branch/commit/object.
Supports all types of stream url:#(lf)
- Stream: will get the latest commit on the 'main' branch (i.e. 'https://speckle.xyz/streams/STREAM_ID')#(lf)
- Branch: will get the latest commit on the specified branch (i.e. 'https://speckle.xyz/streams/STREAM_ID/branches/BRANCH_NAME')#(lf)
- Commit: will get a specific commit from the stream (i.e. 'https://speckle.xyz/streams/STREAM_ID/commits/COMMIT_ID')
"
]
);
// Gets the current authenticated user, if any
[DataSource.Kind = "Speckle"]
shared Speckle.Api.GetUser = Value.ReplaceType(
Speckle.LoadFunction("Api.GetUser.pqm"), type function (url as Uri.Type) as record
);
// Generic fetch function to our GraphQL endpoint
[DataSource.Kind = "Speckle"]
shared Speckle.Api.Fetch = Value.ReplaceType(
Speckle.LoadFunction("Api.Fetch.pqm"),
type function (url as Uri.Type, optional query as text, optional variables as record) as record
);
// 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");
GetByUrl.Publish = GetPublish("GetByUrl");
GetPublish = Speckle.LoadFunction("GetPublish.pqm");
// Navigation table utility function
Table.ToNavigationTable = Speckle.LoadFunction("Table.ToNavigationTable.pqm");
// Function to load `pqm` files
shared Speckle.LoadFunction = (fileName as text) =>
let
binary = Extension.Contents(fileName), asText = Text.FromBinary(binary)
in
try
Expression.Evaluate(asText, #shared) catch (e) =>
error
[
Reason = "Speckle.LoadFunction Failure",
Message.Format = "Loading '#{0}' failed - '#{1}': '#{2}'",
Message.Parameters = {fileName, e[Reason], e[Message]},
Detail = [File = fileName, Error = e]
];
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" DefaultTargets="BuildMez">
<PropertyGroup>
<OutputPath Condition="'$(OutputPath)' == ''">$(MSBuildProjectDirectory)\bin\</OutputPath>
<IntermediateOutputPath Condition="'$(IntermediateOutputPath)' == ''">$(MSBuildProjectDirectory)\obj\</IntermediateOutputPath>
<MezIntermediatePath>$(IntermediateOutputPath)MEZ\</MezIntermediatePath>
<MezOutputPath>$(OutputPath)$(MsBuildProjectName).mez</MezOutputPath>
</PropertyGroup>
<ItemGroup>
<MezContent Include="Speckle.pq" />
<MezContent Include="utilities\**\*.pqm" />
<MezContent Include="speckle\**\*.pqm" />
<MezContent Include="assets\SpeckleLogo16.png" />
<MezContent Include="assets\SpeckleLogo20.png" />
<MezContent Include="assets\SpeckleLogo24.png" />
<MezContent Include="assets\SpeckleLogo32.png" />
<MezContent Include="assets\SpeckleLogo40.png" />
<MezContent Include="assets\SpeckleLogo48.png" />
<MezContent Include="assets\SpeckleLogo64.png" />
<MezContent Include="assets\SpeckleLogo80.png" />
<MezContent Include="assets\resources.resx" />
</ItemGroup>
<Target Name="BuildMez" AfterTargets="Build" Inputs="@(MezContent)" Outputs="$(MezOutputPath)">
<RemoveDir Directories="$(MezIntermediatePath)" />
<Copy SourceFiles="@(MezContent)" DestinationFolder="$(MezIntermediatePath)" />
<MakeDir Directories="$(OutputPath)" Condition="!Exists('$(OutputPath)')" />
<ZipDirectory SourceDirectory="$(MezIntermediatePath)" DestinationFile="$(MezOutputPath)" Overwrite="true" />
</Target>
<Target Name="CopyToConnectors" AfterTargets="BuildMez">
<Message Text="Copying .mez file to: $(UserProfile)\Documents\Power BI Desktop\Custom Connectors" Importance="High" />
<MakeDir Directories="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors\" />
<Copy SourceFiles="$(MezOutputPath)" DestinationFolder="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors\"/>
</Target>
<Target Name="Clean">
<RemoveDir Directories="$(MezIntermediatePath)" />
<Delete Files="$(MezOutputPath)" />
</Target>
</Project>
+2
View File
@@ -0,0 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.Api.Fetch("https://latest.speckle.dev") in Record.ToTable(result)
-25
View File
@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{4DF76451-A46A-4C0B-BE03-459FAAFA07E6}") = "Speckle", "Speckle\Speckle.mproj", "{D61F9470-E614-45C7-8047-E354FF219408}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x86 = Debug|x86
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D61F9470-E614-45C7-8047-E354FF219408}.Debug|x86.ActiveCfg = Debug|x86
{D61F9470-E614-45C7-8047-E354FF219408}.Debug|x86.Build.0 = Debug|x86
{D61F9470-E614-45C7-8047-E354FF219408}.Release|x86.ActiveCfg = Release|x86
{D61F9470-E614-45C7-8047-E354FF219408}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {765772BB-45FA-4661-9573-F0F182FEB7C1}
EndGlobalSection
EndGlobal
-121
View File
@@ -1,121 +0,0 @@
<Project DefaultTargets="BuildExtension" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0b5fe03d-aad9-4bc4-9edf-b543d5e50d29}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>MyRootNamespace</RootNamespace>
<AssemblyName>MyAssemblyName</AssemblyName>
<EnableUnmanagedDebugging>False</EnableUnmanagedDebugging>
<AllowNativeQuery>False</AllowNativeQuery>
<AsAction>False</AsAction>
<FastCombine>False</FastCombine>
<ClearLog>False</ClearLog>
<ShowEngineTraces>False</ShowEngineTraces>
<ShowUserTraces>False</ShowUserTraces>
<LegacyRedirects>False</LegacyRedirects>
<SuppressRowErrors>False</SuppressRowErrors>
<SuppressCellErrors>False</SuppressCellErrors>
<MaxRows>1000</MaxRows>
<ExtensionProject>Yes</ExtensionProject>
<Name>Speckle</Name>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>false</DebugSymbols>
<!--Should be true, fix this when the debugger is implemented -->
<OutputPath>bin\Debug\</OutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>false</DebugSymbols>
<OutputPath>bin\Release\</OutputPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Speckle.pq">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo16.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo20.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo24.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo32.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo40.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo48.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo64.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="SpeckleLogo80.png">
<SubType>Code</SubType>
</Compile>
<Compile Include="resources.resx">
<SubType>Code</SubType>
</Compile>
<Content Include="Speckle.query.pq">
<SubType>Code</SubType>
</Content>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<UsingTask TaskName="BuildExtension" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
<ParameterGroup>
<InputDirectory ParameterType="System.String" Required="true" />
<OutputFile ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Using Namespace="System.Globalization" />
<Using Namespace="System.IO.Compression " />
<Code Type="Fragment" Language="cs"><![CDATA[
using(FileStream fileStream = File.Create(OutputFile))
using(ZipArchive archiveOut = new ZipArchive(fileStream, ZipArchiveMode.Create, false))
{
foreach(string fullPath in Directory.EnumerateFiles(InputDirectory))
{
string filename = Path.GetFileName(fullPath);
archiveOut.CreateEntryFromFile(fullPath, filename, CompressionLevel.Optimal);
}
}
]]></Code>
</Task>
</UsingTask>
<Target Name="BuildExtension" DependsOnTargets="ExtensionClean">
<ItemGroup>
<PQFiles Include="@(Compile)" Condition="'%(Extension)' == '.pq'" />
</ItemGroup>
<ItemGroup>
<NonPQFiles Include="@(Compile)" Condition="'%(Extension)' != '.pq'" />
</ItemGroup>
<MakeDir Directories="$(IntermediateOutputPath)" />
<MakeDir Directories="$(OutputPath)" />
<Copy SourceFiles="@(NonPQFiles)" DestinationFolder="$(IntermediateOutputPath)" />
<Copy SourceFiles="@(PQFiles)" DestinationFiles="@(PQFiles->'$(IntermediateOutputPath)%(RecursiveDir)%(FileName).m')" />
<BuildExtension InputDirectory="$(IntermediateOutputPath)" OutputFile="$(OutputPath)\$(ProjectName).mez" />
<Message Text="Copying .mez file to: $(UserProfile)\Documents\Power BI Desktop\Custom Connectors" Importance="High" />
<MakeDir Directories="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors\" />
<Copy SourceFiles="$(OutputPath)\$(ProjectName).mez" DestinationFolder="$(UserProfile)\Documents\Power BI Desktop\Custom Connectors">
</Copy>
</Target>
<Target Name="ExtensionClean">
<!-- Remove obj folder -->
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<!-- Remove bin folder -->
<RemoveDir Directories="$(OutputPath)" />
</Target>
</Project>
-201
View File
@@ -1,201 +0,0 @@
section Speckle;
/* This is an additional nav bar that can display branches of a stream: not using this for now
[DataSource.Kind="Speckle", Publish="Speckle.Publish"]
shared Speckle.Contents = Value.ReplaceType(NavigationTable.Simple, type function (url as Uri.Type) as any);
// set up nav table
shared NavigationTable.Simple = (url) as table =>
let
baseUrl = Uri.Parts(url)[Host] as text,
streamId = Text.Split(Uri.Parts(url)[Path], "/"){2},
objects = Speckle.GetBranches(baseUrl, streamId),
table = #table(
{"Name", "Key", "Data", "ItemKind", "ItemName", "IsLeaf"},
List.InsertRange(objects, List.Count(objects), {{"GetCommit", "GetCommit", Speckle.GetObjectFromObject(baseUrl, streamId), "Function", "Function", true}})
),
NavTable = Table.ToNavigationTable(table, {"Key"}, "Name", "Data", "ItemKind", "ItemName", "IsLeaf")
in
NavTable;
Speckle.GetBranches = (url, id) =>
let
Source = Web.Contents(
Text.Combine({"https:/", url, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json"
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&id&"\"" ) { branches { items { name commits { items { id message sourceApplication authorName createdAt } } } } } }""}")
]
),
#"JSON" = Json.Document(Source),
branches = #"JSON"[data][stream][branches][items],
branchList = List.Generate(
() => [x = 0, y = Speckle.GetBranchAsList(branches{x})],
each [x] < List.Count(branches),
each [x = [x] + 1, y = Speckle.GetBranchAsList(branches{x})],
each [y]
)
in
branchList;
Speckle.GetBranchAsList = (branchRecord) =>
let
commits = Table.FromRecords(branchRecord[commits][items]),
list = {branchRecord[name], branchRecord[name], commits, "Table", "Table", true}
in
list;
*/
[DataSource.Kind="Speckle", Publish="Speckle.Publish"]
shared Speckle.Contents = Value.ReplaceType(CommitTable, type function (StreamUrl as Uri.Type) as any);
shared CommitTable = (url) as table =>
let
// Get server and streamId, and branchName / commitId / objectid from the input url
server = Text.Combine({"https://", 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,
commitTable = if (commitId <> null) then Speckle.GetObjectFromCommit(server, streamId, commitId)
else if (objectId <> null) then Speckle.GetObjectFromObject(server, streamId, objectId, false)
else if (branchName <> null) then #table( { "Error" }, { { "Invalid URL, use a stream or commit or object url" } } ) // currently not implemented, see reason below
else Speckle.GetObjectFromStream(server, streamId)
in
commitTable;
Speckle.GetObjectFromStream = (server, streamId) =>
let
branchName = "main",
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json"
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&streamId&"\"" ) { branch (name: \"""&branchName&"\""){ commits (limit: 1) { items { referencedObject } } } } }""}")
]
),
#"JSON" = Json.Document(Source),
objectId = #"JSON"[data][stream][branch][commits][items]{0}[referencedObject],
objectsTable = Speckle.GetObjectFromObject(server, streamId, objectId, true)
in
objectsTable;
/* Not implemented since power query M uri does not have a decode method...def not manually writing a method to handle special chars and emojis
Speckle.GetObjectFromBranch = (server, streamId, branchName) =>
let
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json"
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&streamId&"\"" ) { branch (name: \"""&branchName&"\""){ commits (limit: 1) { items { referencedObject } } } } }""}")
]
),
#"JSON" = Json.Document(Source),
objectId = #"JSON"[data][stream][branch][commits][items]{0}[referencedObject],
objectsTable = Speckle.GetObjectFromObject(server, streamId, objectId)
in
objectsTable;
*/
shared Speckle.GetObjectFromCommit = (server, streamId, commitId) =>
let
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json"
],
Content=Text.ToBinary("{""query"": ""query { stream( id: \"""&streamId&"\"" ) { commit (id: \"""&commitId&"\""){ referencedObject } } }""}")
]
),
#"JSON" = Json.Document(Source),
objectId = #"JSON"[data][stream][commit][referencedObject],
objectsTable = Speckle.GetObjectFromObject(server, streamId, objectId, true)
in
objectsTable;
Speckle.GetObjectFromObject = (server, streamId, objectId, IsCommitObject) =>
let
query = if (IsCommitObject) then "{""query"": ""query { stream( id: \"""&streamId&"\"" ) { object (id: \"""&objectId&"\"") { children { objects { data } } } } }""}"
else "{""query"": ""query { stream( id: \"""&streamId&"\"" ) { object (id: \"""&objectId&"\"") { data } } }""}",
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers=[
#"Method"="POST",
#"Content-Type"="application/json"
],
Content=Text.ToBinary(query)
]
),
#"JSON" = Json.Document(Source),
objects = if (IsCommitObject) then #"JSON"[data][stream][object][children][objects]
else {#"JSON"[data][stream][object][data]},
objectsTable = Table.FromRecords(objects)
in
objectsTable;
// Data Source Kind description
Speckle = [
Authentication = [
Key = [
KeyLabel="Personal Access Token",
Label = "Private stream"
],
Implicit = [
Label = "Public stream"
]
],
Label = Extension.LoadString("Speckle Connector")
];
// Data Source UI publishing description
Speckle.Publish = [
Beta = true,
Category = "Other",
ButtonText = { Extension.LoadString("ButtonTitle"), Extension.LoadString("ButtonHelp") },
LearnMoreUrl = "https://speckle.guide",
SourceImage = Speckle.Icons,
SourceTypeImage = Speckle.Icons
];
Speckle.Icons = [
Icon16 = { Extension.Contents("SpeckleLogo16.png"), Extension.Contents("SpeckleLogo20.png"), Extension.Contents("SpeckleLogo24.png"), Extension.Contents("SpeckleLogo32.png") },
Icon32 = { Extension.Contents("SpeckleLogo32.png"), Extension.Contents("SpeckleLogo40.png"), Extension.Contents("SpeckleLogo48.png"), Extension.Contents("SpeckleLogo64.png") }
];
// copy and pasted function from microsoft docs since it's not included yet in M standard lib
Table.ToNavigationTable = (
table as table,
keyColumns as list,
nameColumn as text,
dataColumn as text,
itemKindColumn as text,
itemNameColumn as text,
isLeafColumn as text
) as table =>
let
tableType = Value.Type(table),
newTableType = Type.AddTableKey(tableType, keyColumns, true) meta
[
NavigationTable.NameColumn = nameColumn,
NavigationTable.DataColumn = dataColumn,
NavigationTable.ItemKindColumn = itemKindColumn,
Preview.DelayColumn = itemNameColumn,
NavigationTable.IsLeafColumn = isLeafColumn
],
navigationTable = Value.ReplaceType(table, newTableType)
in
navigationTable;
-5
View File
@@ -1,5 +0,0 @@
// Use this file to write queries to test your data connector
let
result = Speckle.Contents("https://speckle.xyz/streams/5dfbeb49c9/objects/ed4748572b27cfe008f2592a44ab85f1")
in
result
-129
View File
@@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="ButtonHelp" xml:space="preserve">
<value>Connect to Speckle</value>
</data>
<data name="ButtonTitle" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="DataSourceLabel" xml:space="preserve">
<value>Speckle</value>
</data>
</root>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

+174
View File
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string"/>
<xsd:attribute name="type" type="xsd:string"/>
<xsd:attribute name="mimetype" type="xsd:string"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string"/>
<xsd:attribute name="name" type="xsd:string"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
<xsd:attribute ref="xml:space"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="GetByUrl.Help" xml:space="preserve">
<value>Connect to Speckle by Stream URL</value>
</data>
<data name="GetByUrl.Label" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetByUrl.Title" xml:space="preserve">
<value>Speckle - Get stream by URL</value>
</data>
<data name="GetObjFromBranch.Help" xml:space="preserve">
<value>Connect to Speckle by serer URL, stream ID and branch name</value>
</data>
<data name="GetObjFromBranch.Label" xml:space="preserve">
<value>Get the latest commit from a stream's branch</value>
</data>
<data name="GetObjFromBranch.Title" xml:space="preserve">
<value>Speckle - Get Stream branch</value>
</data>
<data name="GetObjFromCommit.Help" xml:space="preserve">
<value>Connect to Speckle by server URL, stream ID and commit ID</value>
</data>
<data name="GetObjFromCommit.Label" xml:space="preserve">
<value>A label</value>
</data>
<data name="GetObjFromCommit.Title" xml:space="preserve">
<value>Speckle - Get Stream commit</value>
</data>
<data name="GetStream.Help" xml:space="preserve">
<value>Connect to Speckle by server URL and stream ID</value>
</data>
<data name="GetStream.Label" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetStream.Title" xml:space="preserve">
<value>Speckle - Get Stream</value>
</data>
<data name="GetObjectAsNavTable.Title" xml:space="preserve">
<value>Speckle - Get Object as NavTable</value>
</data>
<data name="GetObjectAsNavTable.Label" xml:space="preserve">
<value>Speckle</value>
</data>
<data name="GetObjectAsNavTable.Help" xml:space="preserve">
<value>Returns a navigation table for a given object</value>
</data>
<data name="Traverse.Title" xml:space="preserve">
<value>Traverse an object and populate refs</value>
</data>
<data name="Traverse.Label" xml:space="preserve">
<value>Traverse</value>
</data>
<data name="Traverse.Help" xml:space="preserve">
<value>Traverse help</value>
</data>
</root>
+48
View File
@@ -0,0 +1,48 @@
let
Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
CommitReceived = Extension.LoadFunction("Api.CommitReceived.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, streamId as text, branchName as text, limit as number) as list =>
let
decodedBranchName = Record.Field(
Record.Field(Uri.Parts("http://www.dummy.com?" & Uri.BuildQueryString([A = branchName])), "Query"),
"A"
),
// Hacky way to decode base64 strings: Put them in a url query param and parse the URL
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
query = "query($streamId: String!, $branchName: String!, $limit: Int!) {
stream( id: $streamId ) {
branch (name: $branchName ){
commits (limit: $limit) {
items {
id
referencedObject
sourceApplication
}
}
}
}
}",
res = Fetch(server, query, [streamId = streamId, branchName = decodedBranchName, limit = limit]),
branch = res[stream][branch],
commits = branch[commits][items]
in
if branch = null then
error Text.Format("The branch '#{0}' does not exist in stream '#{1}'", {decodedBranchName, streamId})
else if List.Count(branch[commits][items]) = 0 then
error Text.Format("The branch '#{0}' in stream #{1} has no commits", {decodedBranchName, streamId})
else
commits
+42
View File
@@ -0,0 +1,42 @@
let
Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
Traverse = Extension.LoadFunction("Traverse.pqm"),
GetObject = Extension.LoadFunction("Api.GetObject.pqm"),
GetStreamCommit = Extension.LoadFunction("Get.StreamCommit.pqm"),
GetBranchCommits = Extension.LoadFunction("Get.BranchCommits.pqm"),
CommitReceived = Extension.LoadFunction("Api.CommitReceived.pqm"),
ParseStreamUrl = Extension.LoadFunction("ParseStreamUrl.pqm"),
CleanUpObject = Extension.LoadFunction("CleanUpObject.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
stream = ParseStreamUrl(url),
id = stream[id],
server = stream[server],
commit =
if (stream[urlType] = "Stream") then
GetBranchCommits(server, id, "main", 1){0}
else if (stream[urlType] = "Branch") then
GetBranchCommits(server, id, stream[branch], 1){0}
else if (stream[urlType] = "Commit") then
GetStreamCommit(server, id, stream[commit])
else
//We deal with object URLs directly
[referencedObject = stream[object]],
object = GetObject(server, id, commit[referencedObject])
in
Traverse(CleanUpObject(object) meta [server = server, stream = id, commit = commit])
+36
View File
@@ -0,0 +1,36 @@
let
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, streamId as text, commitId as text) as record =>
let
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
query = "query($streamId: String!, $commitId: String!) {
stream( id: $streamId ) {
commit (id: $commitId) {
id
sourceApplication
referencedObject
}
}
}",
variables = [streamId = streamId, commitId = commitId],
#"JSON" = Fetch(server, query, variables),
commit = #"JSON"[stream][commit]
in
if commit = null then
error "The commit did not exist on this stream"
else
commit
+64
View File
@@ -0,0 +1,64 @@
(appName as text) =>
let
replaced = Text.Replace(appName, " ", ""), name = Text.Lower(replaced)
in
if Text.Contains(name, "dynamo") then
"dynamo"
else if Text.Contains(name, "revit") then
"revit"
else if Text.Contains(name, "autocad") then
"autocad"
else if Text.Contains(name, "civil") then
"civil"
else if Text.Contains(name, "rhino") then
"rhino"
else if Text.Contains(name, "grasshopper") then
"grasshopper"
else if Text.Contains(name, "unity") then
"unity"
else if Text.Contains(name, "gsa") then
"gsa"
else if Text.Contains(name, "microstation") then
"microstation"
else if Text.Contains(name, "openroads") then
"openroads"
else if Text.Contains(name, "openrail") then
"openrail"
else if Text.Contains(name, "openbuildings") then
"openbuildings"
else if Text.Contains(name, "etabs") then
"etabs"
else if Text.Contains(name, "sap") then
"sap"
else if Text.Contains(name, "csibridge") then
"csibridge"
else if Text.Contains(name, "safe") then
"safe"
else if Text.Contains(name, "teklastructures") then
"teklastructures"
else if Text.Contains(name, "dxf") then
"dxf"
else if Text.Contains(name, "excel") then
"excel"
else if Text.Contains(name, "unreal") then
"unreal"
else if Text.Contains(name, "powerbi") then
"powerbi"
else if Text.Contains(name, "blender") then
"blender"
else if Text.Contains(name, "qgis") then
"qgis"
else if Text.Contains(name, "arcgis") then
"arcgis"
else if Text.Contains(name, "sketchup") then
"sketchup"
else if Text.Contains(name, "archicad") then
"archicad"
else if Text.Contains(name, "topsolid") then
"topsolid"
else if Text.Contains(name, "python") then
"python"
else if Text.Contains(name, "net") then
"net"
else
"other"
+48
View File
@@ -0,0 +1,48 @@
let
Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
GetAllObjectChildren = Extension.LoadFunction("Api.GetAllObjectChildren.pqm"),
GetObjectFromCommit = Extension.LoadFunction("GetObjectFromCommit.pqm"),
GetObjectFromBranch = Extension.LoadFunction("GetObjectFromBranch.pqm"),
CommitReceived = Extension.LoadFunction("Api.CommitReceived.pqm"),
ParseStreamUrl = Extension.LoadFunction("ParseStreamUrl.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 table =>
let
// Get server and streamId, and branchName / commitId / objectid from the input url
stream = ParseStreamUrl(url),
id = stream[id],
server = stream[server],
commitObjectsTable =
if (stream[urlType] = "Commit") then
GetObjectFromCommit(server, id, stream[commit])
else if (stream[urlType] = "Object") then
GetAllObjectChildren(server, id, stream[object])
else if (stream[urlType] = "Branch") then
GetObjectFromBranch(server, id, stream[branch])
else
GetObjectFromBranch(server, id, "main"),
removeEmpty = Table.RemoveLastN(commitObjectsTable, 1),
addStreamUrl = Table.AddColumn(removeEmpty, "Stream URL", each server & "/streams/" & id),
addUrlType = Table.AddColumn(addStreamUrl, "URL Type", each stream[urlType]),
addObjectIdCol = Table.AddColumn(addUrlType, "Object ID", each try[data][id] otherwise null),
addSpeckleTypeCol = Table.AddColumn(
addObjectIdCol, "speckle_type", each try[data][speckle_type] otherwise null
),
final = Table.ReorderColumns(
addSpeckleTypeCol, {"Stream URL", "URL Type", "Object ID", "speckle_type", "data"}
)
in
final
+55
View File
@@ -0,0 +1,55 @@
let
Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
GetAllObjectChildren = Extension.LoadFunction("Api.GetAllObjectChildren.pqm"),
CommitReceived = Extension.LoadFunction("Api.CommitReceived.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, streamId as text, branchName as text) as table =>
let
decodedBranchName = Record.Field(
Record.Field(Uri.Parts("http://www.dummy.com?" & Uri.BuildQueryString([A = branchName])), "Query"),
"A"
),
// Hacky way to decode base64 strings: Put them in a url query param and parse the URL
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
query = "query($streamId: String!, $branchName: String!) {
stream( id: $streamId ) {
branch (name: $branchName ){
commits (limit: 1) {
items {
id
referencedObject
sourceApplication
}
}
}
}
}",
res = Fetch(server, query, [streamId = streamId, branchName = decodedBranchName]),
branch = res[stream][branch],
commit = branch[commits][items]{0},
objectsTable = GetAllObjectChildren(server, streamId, commit[referencedObject]),
rr = CommitReceived(server, streamId, commit)
in
if branch = null then
error Text.Format("The branch '#{0}' does not exist in stream '#{1}'", {decodedBranchName, streamId})
else if List.Count(branch[commits][items]) = 0 then
error Text.Format("The branch '#{0}' in stream #{1} has no commits", {decodedBranchName, streamId})
else
// Force evaluation of read receipt (ideally it should happen after fetching, but can't find a way)
if rr then
objectsTable
else
objectsTable
+42
View File
@@ -0,0 +1,42 @@
let
Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
GetAllObjectChildren = Extension.LoadFunction("Api.GetAllObjectChildren.pqm"),
CommitReceived = Extension.LoadFunction("Api.CommitReceived.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, streamId as text, commitId as text) as table =>
let
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
query = "query($streamId: String!, $commitId: String!) {
stream( id: $streamId ) {
commit (id: $commitId) {
id
sourceApplication
referencedObject
}
}
}",
variables = [streamId = streamId, commitId = commitId],
#"JSON" = Fetch(server, query, variables),
commit = #"JSON"[stream][commit],
objectsTable = GetAllObjectChildren(server, streamId, commit[referencedObject]),
rr = CommitReceived(server, streamId, commit)
in
if commit = null then
error "The commit did not exist on this stream"
else if rr then
objectsTable
else
objectsTable
+33
View File
@@ -0,0 +1,33 @@
let
Speckle.Api.Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
Speckle.LogEvent = Extension.LoadFunction("LogEvent.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, streamId, commit) =>
let
query = "mutation($input: CommitReceivedInput!) {
commitReceive(input: $input)
}",
variables = [
input = [
streamId = streamId,
commitId = commit[id],
sourceApplication = "PowerBI"
]
],
s = Speckle.LogEvent(server, commit)
in
// Read receipts should fail gracefully no matter what
try Speckle.Api.Fetch(s, query, variables)[commitReceive] otherwise false
+33
View File
@@ -0,0 +1,33 @@
(server as text, optional query as text, optional variables as record) as record =>
let
apiKey = try Extension.CurrentCredential()[Key] otherwise null,
defaultQuery = "query {
activeUser {
email
name
}
serverInfo {
name
company
version
}
}",
Source = Web.Contents(
Text.Combine({server, "graphql"}, "/"),
[
Headers = [
#"Method" = "POST",
#"Content-Type" = "application/json",
#"Authorization" = if apiKey = null then "" else Text.Format("Bearer #{0}", {apiKey})
],
ManualStatusHandling = {400},
Content = Json.FromValue([query = Text.From(query ?? defaultQuery), variables = variables])
]
),
#"JSON" = Json.Document(Source)
in
// Check if response contains errors, if so, return first error.
if Record.HasFields(#"JSON", {"errors"}) then
error #"JSON"[errors]{0}[message]
else
#"JSON"[data]
+35
View File
@@ -0,0 +1,35 @@
let
Table.GenerateByPage = Extension.LoadFunction("Table.GenerateByPage.pqm"),
Speckle.Api.GetObjectChildren = Extension.LoadFunction("Api.GetObjectChildren.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
// Read all pages of data.
// After every page, we check the "nextCursor" record on the metadata of the previous request.
// Table.GenerateByPage will keep asking for more pages until we return null.
(server as text, streamId as text, objectId as text, optional cursor as text) as table =>
Table.GenerateByPage(
(previous) =>
let
// if previous is null, then this is our first page of data
nextCursor = if (previous = null) then cursor else Value.Metadata(previous)[Cursor]?,
// if the cursor is null but the prevous page is not, we've reached the end
page =
if (previous <> null and nextCursor = null) then
null
else
Speckle.Api.GetObjectChildren(server, streamId, objectId, 1000, nextCursor)
in
page
) meta [server = server, streamId = streamId, objectId = objectId]
+28
View File
@@ -0,0 +1,28 @@
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, streamId as text, objectId as text) =>
let
query = "query($streamId: String!, $objectId: String!) {
stream( id: $streamId ) {
object (id: $objectId) {
data
}
}
}",
#"JSON" = Speckle.Api.Fetch(server, query, [streamId = streamId, objectId = objectId])
in
#"JSON"[stream][object][data]
+54
View File
@@ -0,0 +1,54 @@
let
Speckle.Api.Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
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
(
server as text,
streamId as text,
objectId as text,
optional limit as number,
optional cursor as text,
optional select as list
) =>
let
query = "query($streamId: String!, $objectId: String!, $limit: Int, $cursor: String, $select: [String]) {
stream( id: $streamId ) {
object (id: $objectId) {
children(select: $select, limit: $limit, cursor: $cursor) {
cursor
objects {
data
}
}
}
}
}",
#"JSON" = Speckle.Api.Fetch(
server,
query,
[
streamId = streamId,
objectId = objectId,
limit = limit,
cursor = cursor,
select = select
]
),
children = #"JSON"[stream][object][children],
nextCursor = children[cursor],
clean = Speckle.CleanUpObjects(children[objects])
in
Table.FromRecords(clean) meta [Cursor = nextCursor]
+24
View File
@@ -0,0 +1,24 @@
(url as text) =>
let
query = "query {
activeUser { name email }
}",
// Imports
Speckle.Api.Fetch = Extension.LoadFunction("Api.Fetch.pqm"),
server = Extension.LoadFunction("ParseStreamUrl.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
// Read receipts should fail gracefully no matter what
try Speckle.Api.Fetch(server, query)[activeUser][email] otherwise "hey"
+38
View File
@@ -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
+7
View File
@@ -0,0 +1,7 @@
(object as record) as record =>
let
hiddenFields = {"__closure", "totalChildrenCount"},
// remove closures from records
clean = Record.RemoveFields(object, hiddenFields, MissingField.Ignore)
in
clean
+17
View File
@@ -0,0 +1,17 @@
(objects as list) as list =>
let
// remove closures from records, and remove DataChunk records
removeClosureField = List.Transform(
objects, each [data = Record.RemoveFields(_[data], "__closure", MissingField.Ignore)]
),
removeTotals = List.Transform(
removeClosureField,
each
[
data = try
Record.RemoveFields(_[data], "totalChildrenCount", MissingField.Ignore) otherwise _[data]
]
),
removed = List.Select(removeTotals, each _[data][speckle_type] <> "Speckle.Core.Models.DataChunk")
in
removed
+30
View File
@@ -0,0 +1,30 @@
let
beta = true,
category = "Other",
icons = [
Icon16 = {
Extension.Contents("SpeckleLogo16.png"),
Extension.Contents("SpeckleLogo20.png"),
Extension.Contents("SpeckleLogo24.png"),
Extension.Contents("SpeckleLogo32.png")
},
Icon32 = {
Extension.Contents("SpeckleLogo32.png"),
Extension.Contents("SpeckleLogo40.png"),
Extension.Contents("SpeckleLogo48.png"),
Extension.Contents("SpeckleLogo64.png")
}
]
in
(key as text) as record =>
[
Beta = beta,
Category = category,
ButtonText = {
Extension.LoadString(Text.Format("#{0}.Title", {key})),
Extension.LoadString(Text.Format("#{0}.Label", {key}))
},
LearnMoreUrl = "https://speckle.guide",
SourceImage = icons,
SourceTypeImage = icons
]
+48
View File
@@ -0,0 +1,48 @@
let
GetApplicationSlug = Extension.LoadFunction("GetApplicationSlug.pqm"),
GetUser = Extension.LoadFunction("GetUser.pqm"),
Hash = Extension.LoadFunction("Hash.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, commit as any) =>
let
trackUrl = "https://analytics.speckle.systems/track?ip=1",
user = GetUser(server),
body = [
event = "Receive",
properties = [
server_id = Hash(server),
token = "acd87c5a50b56df91a795e999812a3a4",
hostApp = "powerbi",
sourceHostApp = GetApplicationSlug(commit[sourceApplication]),
sourceHostAppVersion = commit[sourceApplication]
]
],
Result = Web.Contents(
trackUrl,
[
Headers = [
#"Method" = "POST",
#"Accept" = "text/plain",
#"Content-Type" = "application/json"
],
Content = Text.ToBinary(Text.Combine({"data=", Text.FromBinary(Json.FromValue(body))}))
]
),
// Hack to force execution
Join = Text.Combine({server, Text.From(Json.Document(Result))}, "_____"),
Disjoin = Text.Split(Join, "_____"){0}
in
Disjoin
+27
View File
@@ -0,0 +1,27 @@
(url as text) as record =>
let
// Get server and streamId, and branchName / commitId / objectid from the input url
server = Text.Combine({"https://", 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
]
+67
View File
@@ -0,0 +1,67 @@
let
GetObject = Extension.LoadFunction("Api.GetObject.pqm"),
Diagnostics.Log = Extension.LoadFunction("Diagnostics.pqm")[LogValue],
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]
],
//TODO: Not implemented yet
TraverseTable = (item as table) as table => item,
// Will traverse an undetermined value (list, table, record).
TraverseValue = (i as any) as any =>
let
item = Diagnostics.Log("Traverse value", i) meta Value.Metadata(i)
in
if Value.Is(item, type list) then
// Return a transformed list by traversing all items
Diagnostics.Log(
"List travered",
List.Transform(item, (a) => @TraverseValue(Value.ReplaceMetadata(a, Value.Metadata(i))))
)
else if Value.Is(item, type record) then
// Traverse this record individually
TraverseRecord(item)
else if Value.Is(item, type table) then
// Traverse this table
TraverseTable(item)
else
// If none of the above, assume it's just a primitive type and return it as-is.
item,
// Traverses a generic record
TraverseRecord = (object as record) as any =>
let
isSpeckle = Diagnostics.Log("Is Speckle", Record.HasFields(object, {"speckle_type"})),
isReference = Diagnostics.Log("Is Reference", object[speckle_type] = "reference"),
// Get the names of all fields
fields = Record.FieldNames(object),
// Remove all known fields that don't need traversing
cleanFields = List.RemoveItems(fields, {"id", "speckle_type", "applicationId"}),
// Transform the list of field names into a set of transform operations
transformOps = List.Transform(
cleanFields, each {_, (a) => TraverseValue(Value.ReplaceMetadata(a, Value.Metadata(object)))}
),
// Get the object's metadata (server and stream will be saved in here)
info = Value.Metadata(object)
in
// Transform all fields and return the modified object
if (isReference) then
// Swap reference for call to GetObject
() =>
TraverseValue(
Value.ReplaceMetadata(
GetObject(info[server], info[stream], object[referencedId]), Value.Metadata(object)
)
)
else
try Record.TransformFields(object, transformOps, MissingField.Error) otherwise error "oopsies"
in
TraverseValue
+2
View File
@@ -0,0 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.Api.Fetch("https://latest.speckle.dev") in Record.ToTable(result)
+7
View File
@@ -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
+30
View File
@@ -0,0 +1,30 @@
section UnitTestingUnitTests;
UT = Speckle.LoadFunction("Facts.pqm");
Fact = UT[Fact];
Facts.Summarize = UT[SummarizeFacts];
shared Speckle.UnitTest = [
// Put any common variables here if you only want them to be evaluated once
// Fact(<Name of the Test>, <Expected Value>, <Actual Value>)
// <Expected Value> and <Actual Value> can be a literal or let statement
facts = {
Fact(
"Check that this function returns 'ABC'",
// name of the test
"ABC",
// expected value
UnitTesting.ReturnsABC()
// expression to evaluate (let or single statement)
),
Fact("Check that this function returns '123'", "123", UnitTesting.Returns123()),
Fact("Result should contain 5 rows", 5, Table.RowCount(UnitTesting.ReturnsTableWithFiveRows())),
Fact("Values should be equal (using a let statement)", "Hello World", let a = "Hello World" in a)
},
report = Facts.Summarize(facts)
][report];
shared UnitTesting.ReturnsABC = () => "ABC";
shared UnitTesting.Returns123 = () => "123";
shared UnitTesting.ReturnsTableWithFiveRows = () => Table.Repeat(#table({"a"}, {{1}}), 5);
+2
View File
@@ -0,0 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.Get.ByUrl("https://latest.speckle.dev/streams/3d25474a18") in Record.ToTable(result)
+2
View File
@@ -0,0 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.GetByUrl("https://latest.speckle.dev/streams/5f284e5c70/objects/85e5f250fe591ea74d8d5dc1137a9341") in result
+376
View File
@@ -0,0 +1,376 @@
let
Diagnostics.LogValue = (prefix, value) =>
Diagnostics.Trace(
TraceLevel.Information,
prefix & ": " & (try Diagnostics.ValueToText(value) otherwise "<error getting value>"),
value
),
Diagnostics.LogValue2 = (prefix, value, result, optional delayed) =>
Diagnostics.Trace(TraceLevel.Information, prefix & ": " & Diagnostics.ValueToText(value), result, delayed),
Diagnostics.LogFailure = (text, function) =>
let
result = try function()
in
if result[HasError] then
Diagnostics.LogValue2(text, result[Error], () => error result[Error], true)
else
result[Value],
Diagnostics.WrapFunctionResult = (innerFunction as function, outerFunction as function) as function =>
Function.From(Value.Type(innerFunction), (list) => outerFunction(() => Function.Invoke(innerFunction, list))),
Diagnostics.WrapHandlers = (handlers as record) as record =>
Record.FromList(
List.Transform(
Record.FieldNames(handlers),
(h) =>
Diagnostics.WrapFunctionResult(Record.Field(handlers, h), (fn) => Diagnostics.LogFailure(h, fn))
),
Record.FieldNames(handlers)
),
Diagnostics.ValueToText = (value) =>
let
_canBeIdentifier = (x) =>
let
keywords = {
"and",
"as",
"each",
"else",
"error",
"false",
"if",
"in",
"is",
"let",
"meta",
"not",
"otherwise",
"or",
"section",
"shared",
"then",
"true",
"try",
"type"
},
charAlpha = (c as number) => (c >= 65 and c <= 90) or (c >= 97 and c <= 122) or c = 95,
charDigit = (c as number) => c >= 48 and c <= 57
in
try
charAlpha(Character.ToNumber(Text.At(x, 0)))
and List.MatchesAll(
Text.ToList(x), (c) => let num = Character.ToNumber(c) in charAlpha(num)
or charDigit(num)
)
and not List.MatchesAny(keywords, (li) => li = x) otherwise false,
Serialize.Binary = (x) => "#binary(" & Serialize(Binary.ToList(x)) & ") ",
Serialize.Date = (x) =>
"#date(" & Text.From(Date.Year(x)) & ", " & Text.From(Date.Month(x)) & ", " & Text.From(Date.Day(x))
& ") ",
Serialize.Datetime = (x) =>
"#datetime("
& Text.From(Date.Year(DateTime.Date(x)))
& ", "
& Text.From(Date.Month(DateTime.Date(x)))
& ", "
& Text.From(Date.Day(DateTime.Date(x)))
& ", "
& Text.From(Time.Hour(DateTime.Time(x)))
& ", "
& Text.From(Time.Minute(DateTime.Time(x)))
& ", "
& Text.From(Time.Second(DateTime.Time(x)))
& ") ",
Serialize.Datetimezone = (x) =>
let
dtz = DateTimeZone.ToRecord(x)
in
"#datetimezone("
& Text.From(dtz[Year])
& ", "
& Text.From(dtz[Month])
& ", "
& Text.From(dtz[Day])
& ", "
& Text.From(dtz[Hour])
& ", "
& Text.From(dtz[Minute])
& ", "
& Text.From(dtz[Second])
& ", "
& Text.From(dtz[ZoneHours])
& ", "
& Text.From(dtz[ZoneMinutes])
& ") ",
Serialize.Duration = (x) =>
let
dur = Duration.ToRecord(x)
in
"#duration("
& Text.From(dur[Days])
& ", "
& Text.From(dur[Hours])
& ", "
& Text.From(dur[Minutes])
& ", "
& Text.From(dur[Seconds])
& ") ",
Serialize.Function = (x) =>
_serialize_function_param_type(
Type.FunctionParameters(Value.Type(x)), Type.FunctionRequiredParameters(Value.Type(x))
)
& " as "
& _serialize_function_return_type(Value.Type(x))
& " => (...) ",
Serialize.List = (x) =>
"{"
& List.Accumulate(
x, "", (seed, item) => if seed = "" then Serialize(item) else seed & ", " & Serialize(item)
)
& "} ",
Serialize.Logical = (x) => Text.From(x),
Serialize.Null = (x) => "null",
Serialize.Number = (x) =>
let
Text.From = (i as number) as text =>
if Number.IsNaN(i) then
"#nan"
else if i = Number.PositiveInfinity then
"#infinity"
else if i = Number.NegativeInfinity then
"-#infinity"
else
Text.From(i)
in
Text.From(x),
Serialize.Record = (x) =>
"[ "
& List.Accumulate(
Record.FieldNames(x),
"",
(seed, item) =>
(if seed = "" then Serialize.Identifier(item) else seed & ", " & Serialize.Identifier(
item
))
& " = "
& Serialize(Record.Field(x, item))
)
& " ] ",
Serialize.Table = (x) =>
"#table( type " & _serialize_table_type(Value.Type(x)) & ", " & Serialize(Table.ToRows(x)) & ") ",
Serialize.Text = (x) => """" & _serialize_text_content(x) & """",
_serialize_text_content = (x) =>
let
escapeText = (n as number) as text =>
"#(#)(" & Text.PadStart(Number.ToText(n, "X", "en-US"), 4, "0") & ")"
in
List.Accumulate(
List.Transform(
Text.ToList(x),
(c) =>
let
n = Character.ToNumber(c)
in
if n = 9 then
"#(#)(tab)"
else if n = 10 then
"#(#)(lf)"
else if n = 13 then
"#(#)(cr)"
else if n = 34 then
""""""
else if n = 35 then
"#(#)(#)"
else if n < 32 then
escapeText(n)
else if n < 127 then
Character.FromNumber(n)
else
escapeText(n)
),
"",
(s, i) => s & i
),
Serialize.Identifier = (x) => if _canBeIdentifier(x) then x else "#""" & _serialize_text_content(x) & """",
Serialize.Time = (x) =>
"#time("
& Text.From(Time.Hour(x))
& ", "
& Text.From(Time.Minute(x))
& ", "
& Text.From(Time.Second(x))
& ") ",
Serialize.Type = (x) => "type " & _serialize_typename(x),
_serialize_typename = (x, optional funtype as logical) =>
/* Optional parameter: Is this being used as part of a function signature? */ let
isFunctionType = (x as type) =>
try if Type.FunctionReturn(x) is type then true else false otherwise false,
isTableType = (x as type) =>
try if Type.TableSchema(x) is table then true else false otherwise false,
isRecordType = (x as type) =>
try if Type.ClosedRecord(x) is type then true else false otherwise false,
isListType = (x as type) => try if Type.ListItem(x) is type then true else false otherwise false
in
if funtype = null and isTableType(x) then
_serialize_table_type(x)
else if funtype = null and isListType(x) then
"{ " & @_serialize_typename(Type.ListItem(x)) & " }"
else if funtype = null and isFunctionType(x) then
"function " & _serialize_function_type(x)
else if funtype = null and isRecordType(x) then
_serialize_record_type(x)
else if x = type any then
"any"
else
let
base = Type.NonNullable(x)
in
(if Type.IsNullable(x) then "nullable " else "")
& (
if base = type anynonnull then
"anynonnull"
else if base = type binary then
"binary"
else if base = type date then
"date"
else if base = type datetime then
"datetime"
else if base = type datetimezone then
"datetimezone"
else if base = type duration then
"duration"
else if base = type logical then
"logical"
else if base = type none then
"none"
else if base = type null then
"null"
else if base = type number then
"number"
else if base = type text then
"text"
else if base = type time then
"time"
else if base = type type then
"type"
else /* Abstract types: */ if base = type function then
"function"
else if base = type table then
"table"
else if base = type record then
"record"
else if base = type list then
"list"
else
"any /*Actually unknown type*/"
),
_serialize_table_type = (x) =>
let
schema = Type.TableSchema(x)
in
"table "
& (
if Table.IsEmpty(schema) then
""
else
"["
& List.Accumulate(
List.Transform(
Table.ToRecords(Table.Sort(schema, "Position")),
each Serialize.Identifier(_[Name]) & " = " & _[Kind]
),
"",
(seed, item) => (if seed = "" then item else seed & ", " & item)
)
& "] "
),
_serialize_record_type = (x) =>
let
flds = Type.RecordFields(x)
in
if Record.FieldCount(flds) = 0 then
"record"
else
"["
& List.Accumulate(
Record.FieldNames(flds),
"",
(seed, item) =>
seed
& (if seed <> "" then ", " else "")
& (
Serialize.Identifier(item)
& "="
& _serialize_typename(Record.Field(flds, item)[Type])
)
)
& (if Type.IsOpenRecord(x) then ",..." else "")
& "]",
_serialize_function_type = (x) =>
_serialize_function_param_type(Type.FunctionParameters(x), Type.FunctionRequiredParameters(x))
& " as "
& _serialize_function_return_type(x),
_serialize_function_param_type = (t, n) =>
let
funsig = Table.ToRecords(
Table.TransformColumns(
Table.AddIndexColumn(Record.ToTable(t), "isOptional", 1), {"isOptional", (x) => x > n}
)
)
in
"("
& List.Accumulate(
funsig,
"",
(seed, item) =>
(if seed = "" then "" else seed & ", ")
& (if item[isOptional] then "optional " else "")
& Serialize.Identifier(item[Name])
& " as "
& _serialize_typename(item[Value], true)
)
& ")",
_serialize_function_return_type = (x) => _serialize_typename(Type.FunctionReturn(x), true),
Serialize = (x) as text =>
if x is binary then
try Serialize.Binary(x) otherwise "null /*serialize failed*/"
else if x is date then
try Serialize.Date(x) otherwise "null /*serialize failed*/"
else if x is datetime then
try Serialize.Datetime(x) otherwise "null /*serialize failed*/"
else if x is datetimezone then
try Serialize.Datetimezone(x) otherwise "null /*serialize failed*/"
else if x is duration then
try Serialize.Duration(x) otherwise "null /*serialize failed*/"
else if x is function then
try Serialize.Function(x) otherwise "null /*serialize failed*/"
else if x is list then
try Serialize.List(x) otherwise "null /*serialize failed*/"
else if x is logical then
try Serialize.Logical(x) otherwise "null /*serialize failed*/"
else if x is null then
try Serialize.Null(x) otherwise "null /*serialize failed*/"
else if x is number then
try Serialize.Number(x) otherwise "null /*serialize failed*/"
else if x is record then
try Serialize.Record(x) otherwise "null /*serialize failed*/"
else if x is table then
try Serialize.Table(x) otherwise "null /*serialize failed*/"
else if x is text then
try Serialize.Text(x) otherwise "null /*serialize failed*/"
else if x is time then
try Serialize.Time(x) otherwise "null /*serialize failed*/"
else if x is type then
try Serialize.Type(x) otherwise "null /*serialize failed*/"
else
"[#_unable_to_serialize_#]"
in
try Serialize(value) otherwise "<serialization failed>"
in
[
LogValue = Diagnostics.LogValue,
LogValue2 = Diagnostics.LogValue2,
LogFailure = Diagnostics.LogFailure,
WrapFunctionResult = Diagnostics.WrapFunctionResult,
WrapHandlers = Diagnostics.WrapHandlers,
ValueToText = Diagnostics.ValueToText
]
+17
View File
@@ -0,0 +1,17 @@
// This is here as reference for copy/pasting wherever there is need for importing pqm files.
let
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
Extension.LoadFunction
+231
View File
@@ -0,0 +1,231 @@
let
/// COMMON UNIT TESTING CODE
Fact = (_subject as text, _expected, _actual) as record =>
[
expected = try _expected,
safeExpected = if expected[HasError] then "Expected : " & @ValueToText(expected[Error]) else expected[
Value
],
actual = try _actual,
safeActual = if actual[HasError] then "Actual : " & @ValueToText(actual[Error]) else actual[Value],
attempt = try safeExpected = safeActual,
result = if attempt[HasError] or not attempt[Value] then "Failure" else "Success",
resultOp = if result = "Success" then " = " else " <> ",
addendumEvalAttempt = if attempt[HasError] then @ValueToText(attempt[Error]) else "",
addendumEvalExpected = try @ValueToText(safeExpected) otherwise "...",
addendumEvalActual = try @ValueToText(safeActual) otherwise "...",
fact = [
Result = result & " " & addendumEvalAttempt,
Notes = _subject,
Details = " (" & addendumEvalExpected & resultOp & addendumEvalActual & ")"
]
][fact],
Facts = (_subject as text, _predicates as list) => List.Transform(_predicates, each Fact(_subject, _{0}, _{1})),
Facts.Summarize = (_facts as list) as table =>
[
Fact.CountSuccesses = (count, i) =>
[
result = try i[Result],
sum = if result[HasError] or not Text.StartsWith(result[Value], "Success") then count else count + 1
][sum],
passed = List.Accumulate(_facts, 0, Fact.CountSuccesses),
total = List.Count(_facts),
format = if passed = total then "All #{0} Passed !!!" else "#{0} Passed - #{1} Failed",
result = if passed = total then "Success" else "Failed",
rate = Number.IntegerDivide(100 * passed, total),
header = [
Result = result,
Notes = Text.Format(format, {passed, total - passed}),
Details = Text.Format("#{0}% success rate", {rate})
],
report = Table.FromRecords(List.Combine({{header}, _facts}))
][report],
ValueToText = (value, optional depth) =>
let
List.TransformAndCombine = (list, transform, separator) =>
Text.Combine(List.Transform(list, transform), separator),
Serialize.Binary = (x) => "#binary(" & Serialize(Binary.ToList(x)) & ") ",
Serialize.Function = (x) =>
_serialize_function_param_type(
Type.FunctionParameters(Value.Type(x)), Type.FunctionRequiredParameters(Value.Type(x))
)
& " as "
& _serialize_function_return_type(Value.Type(x))
& " => (...) ",
Serialize.List = (x) => "{" & List.TransformAndCombine(x, Serialize, ", ") & "} ",
Serialize.Record = (x) =>
"[ "
& List.TransformAndCombine(
Record.FieldNames(x),
(item) => Serialize.Identifier(item) & " = " & Serialize(Record.Field(x, item)),
", "
)
& " ] ",
Serialize.Table = (x) =>
"#table( type " & _serialize_table_type(Value.Type(x)) & ", " & Serialize(Table.ToRows(x)) & ") ",
Serialize.Identifier = Expression.Identifier,
Serialize.Type = (x) => "type " & _serialize_typename(x),
_serialize_typename = (x, optional funtype as logical) =>
/* Optional parameter: Is this being used as part of a function signature? */ let
isFunctionType = (x as type) =>
try if Type.FunctionReturn(x) is type then true else false otherwise false,
isTableType = (x as type) =>
try if Type.TableSchema(x) is table then true else false otherwise false,
isRecordType = (x as type) =>
try if Type.ClosedRecord(x) is type then true else false otherwise false,
isListType = (x as type) => try if Type.ListItem(x) is type then true else false otherwise false
in
if funtype = null and isTableType(x) then
_serialize_table_type(x)
else if funtype = null and isListType(x) then
"{ " & @_serialize_typename(Type.ListItem(x)) & " }"
else if funtype = null and isFunctionType(x) then
"function " & _serialize_function_type(x)
else if funtype = null and isRecordType(x) then
_serialize_record_type(x)
else if x = type any then
"any"
else
let
base = Type.NonNullable(x)
in
(if Type.IsNullable(x) then "nullable " else "")
& (
if base = type anynonnull then
"anynonnull"
else if base = type binary then
"binary"
else if base = type date then
"date"
else if base = type datetime then
"datetime"
else if base = type datetimezone then
"datetimezone"
else if base = type duration then
"duration"
else if base = type logical then
"logical"
else if base = type none then
"none"
else if base = type null then
"null"
else if base = type number then
"number"
else if base = type text then
"text"
else if base = type time then
"time"
else if base = type type then
"type"
else /* Abstract types: */ if base = type function then
"function"
else if base = type table then
"table"
else if base = type record then
"record"
else if base = type list then
"list"
else
"any /*Actually unknown type*/"
),
_serialize_table_type = (x) =>
let
schema = Type.TableSchema(x)
in
"table "
& (
if Table.IsEmpty(schema) then
""
else
"["
& List.TransformAndCombine(
Table.ToRecords(Table.Sort(schema, "Position")),
each Serialize.Identifier(_[Name]) & " = " & _[Kind],
", "
)
& "] "
),
_serialize_record_type = (x) =>
let
flds = Type.RecordFields(x)
in
if Record.FieldCount(flds) = 0 then
"record"
else
"["
& List.TransformAndCombine(
Record.FieldNames(flds),
(item) =>
Serialize.Identifier(item)
& "="
& _serialize_typename(Record.Field(flds, item)[Type]),
", "
)
& (if Type.IsOpenRecord(x) then ", ..." else "")
& "]",
_serialize_function_type = (x) =>
_serialize_function_param_type(Type.FunctionParameters(x), Type.FunctionRequiredParameters(x))
& " as "
& _serialize_function_return_type(x),
_serialize_function_param_type = (t, n) =>
let
funsig = Table.ToRecords(
Table.TransformColumns(
Table.AddIndexColumn(Record.ToTable(t), "isOptional", 1), {"isOptional", (x) => x > n}
)
)
in
"("
& List.TransformAndCombine(
funsig,
(item) =>
(if item[isOptional] then "optional " else "")
& Serialize.Identifier(item[Name])
& " as "
& _serialize_typename(item[Value], true),
", "
)
& ")",
_serialize_function_return_type = (x) => _serialize_typename(Type.FunctionReturn(x), true),
Serialize = (x) as text =>
if x is binary then
try Serialize.Binary(x) otherwise "null /*serialize failed*/"
else if x is date then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is datetime then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is datetimezone then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is duration then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is function then
try Serialize.Function(x) otherwise "null /*serialize failed*/"
else if x is list then
try Serialize.List(x) otherwise "null /*serialize failed*/"
else if x is logical then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is null then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is number then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is record then
try Serialize.Record(x) otherwise "null /*serialize failed*/"
else if x is table then
try Serialize.Table(x) otherwise "null /*serialize failed*/"
else if x is text then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is time then
try Expression.Constant(x) otherwise "null /*serialize failed*/"
else if x is type then
try Serialize.Type(x) otherwise "null /*serialize failed*/"
else
"[#_unable_to_serialize_#]"
in
try Serialize(value) otherwise "<serialization failed>"
in
[
Fact = Fact,
Facts = Facts,
SummarizeFacts = Facts.Summarize,
ValueToText = ValueToText
]
+12
View File
@@ -0,0 +1,12 @@
(Value as text) =>
let
Solution = Binary.ToText(
Binary.FromList(
Binary.ToList(Binary.Compress(Text.ToBinary(Value, BinaryEncoding.Base64), Compression.GZip))
)
)
in
if Value = null then
null
else
Solution
+23
View File
@@ -0,0 +1,23 @@
(getNextPage as function) as table =>
let
listOfPages = List.Generate(
() => getNextPage(null),
// get the first page of data
(lastPage) => lastPage <> null,
// stop when the function returns null
(lastPage) => getNextPage(lastPage)
// pass the previous page to the next function call
),
// concatenate the pages together
tableOfPages = Table.FromList(listOfPages, Splitter.SplitByNothing(), {"Column1"}),
firstRow = tableOfPages{0} ?
in
// if we didn't get back any pages of data, return an empty table
// otherwise set the table type based on the columns of the first page
if (firstRow = null) then
Table.FromRows({})
else
Value.ReplaceType(
Table.ExpandTableColumn(tableOfPages, "Column1", Table.ColumnNames(firstRow[Column1])),
Value.Type(firstRow[Column1])
)
+21
View File
@@ -0,0 +1,21 @@
(
table as table,
keyColumns as list,
nameColumn as text,
dataColumn as text,
itemKindColumn as text,
itemNameColumn as text,
isLeafColumn as text
) as table =>
let
tableType = Value.Type(table),
newTableType = Type.AddTableKey(tableType, keyColumns, true) meta [
NavigationTable.NameColumn = nameColumn,
NavigationTable.DataColumn = dataColumn,
NavigationTable.ItemKindColumn = itemKindColumn,
Preview.DelayColumn = itemNameColumn,
NavigationTable.IsLeafColumn = isLeafColumn
],
navigationTable = Value.ReplaceType(table, newTableType)
in
navigationTable
+14
View File
@@ -0,0 +1,14 @@
(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)