Compare commits

...

265 Commits

Author SHA1 Message Date
Jedd Morgan 0ca9162e7b pass etag to trigger processing (#447)
.NET Build and Publish / build (push) Has been cancelled
2026-02-26 16:09:19 +00:00
Jedd Morgan 070f21b075 feat(progress): Add progress reporting to uploader (#446)
.NET Build and Publish / build (push) Has been cancelled
* refactor uploader for progress

* progress

* not so many decimal places

* small tweak to RenderStreamProgress

* fix unit tests

* uploading data
2026-02-26 10:45:48 +00:00
Dimitrie Stefanescu 9bf6995b15 Add headers for Azure S3 compat 2026-02-24 11:57:16 +00:00
Jedd Morgan 43ebc84881 feat(api): DI Refactor for Duck DB + Gergo's API endpoint changes (#444)
* di

* di2

* Gergo's api changes
2026-02-20 14:20:29 +00:00
Jedd Morgan 7652cd385d Merge remote-tracking branch 'origin/main' into duckdev 2026-02-20 13:40:29 +00:00
Jedd Morgan a81aaca8fe Updated tests for recent server changes (#445) 2026-02-20 12:02:34 +00:00
Jedd Morgan 57843cc454 Test(integration): Add reusable workflow for running integration tests from the server repo (#443)
* experiment

* rename

* we'll give this a go

* fix path

* correct path

* the correct path this time

* build docker image first

* correct docker build

* try this

* ensure editor config is observed

* typo
2026-02-12 18:20:55 +00:00
Jedd Morgan bf6ae0f6af Duck: Cleanup dependencies (#441)
* Remove unused dependencies

* remove unused package references

* how's this

* missed a bit
2026-02-10 16:29:27 +00:00
Jedd Morgan 309cead189 changes to serializer value handilng (#440) 2026-02-10 12:25:05 +00:00
Dimitrie Stefanescu 4980796cd6 Dim/flopper experiments (#438)
* Adds object flopper for experimental object uploads

Implements a new mechanism for uploading objects to the server.

* wip

* pipe works

* WIP

* wip - refactors send pipeline
2026-02-10 10:17:38 +00:00
Jedd Morgan 12df19e431 fix(test): Ensure InitializeWebsocket runs async (#437)
* Force InitializeWebsocket

* experiment with lower values

* let's play it safe
2026-02-10 09:52:19 +00:00
Jedd Morgan c186d98ea7 feat(api): Add CanCreateModelIngestion model permission check (#436)
.NET Build and Publish / build (push) Has been cancelled
* CanCreateModelIngestion check

* assert this way

* correct exception type
2026-01-29 14:41:57 +00:00
Jedd Morgan 00a6619cbe feat(api)!: Add model permission checks and deprecate canPublish (#434)
.NET Build and Publish / build (push) Has been cancelled
* Add permission checks and deprecate canPublish

* Fix tests

* How's this

* make tests more reliable

* lets test this first

* test

* This should speed up unit tests

* skip slow tests

* I HATE flaky tests
2026-01-27 16:35:48 +00:00
Jedd Morgan 49ef9917c4 Model Ingestion Version message (#427)
.NET Build and Publish / build (push) Has been cancelled
2026-01-26 16:03:16 +00:00
Jedd Morgan 94b0473157 MarkReceivedVersionInput clarification (#433) 2026-01-26 15:52:56 +00:00
Jedd Morgan 8071990dd5 deprecate file import api (#431) 2026-01-15 16:29:33 +00:00
Jedd Morgan 8c7dbc89aa update nullability of invitedBy (#432) 2026-01-15 14:21:40 +00:00
Jedd Morgan 676a3df153 Update docker-compose-internal.yml (#430) 2026-01-07 15:54:30 +00:00
Jedd Morgan c75538e1c7 feat(api): Model Ingestion Get (#426)
.NET Build and Publish / build (push) Has been cancelled
* model ingeston get

* reinstate localhost:3000
2025-12-11 10:35:20 +00:00
Jedd Morgan 5d10b77ee4 feat(api): Model Ingestion api (#420) (#425)
.NET Build and Publish / build (push) Has been cancelled
* First pass

* format

* subscriptions

* Fixes

* fake a release

* fix tests

* subscription tests

* tests(sdk): fix model ingestion sub test'

* tests(integration): fix model ingestion test expectations

* todos

* revert this too

* Filter Integration-Internal tests

* use a different trait

* capitalize

* codecov tweaks

* fix

* add requeue and start processing

* requeue

---------

Co-authored-by: Gergo Jedlicska <gergo@jedlicska.com>
2025-12-10 10:20:33 +00:00
Jedd Morgan 82dca56fbd feat(api): Model Ingestion api (#420)
* First pass

* format

* subscriptions

* Fixes

* fake a release

* fix tests

* subscription tests

* tests(sdk): fix model ingestion sub test'

* tests(integration): fix model ingestion test expectations

* todos

* revert this too

* Filter Integration-Internal tests

* use a different trait

* capitalize

* codecov tweaks

* fix

* add requeue and start processing

* requeue

---------

Co-authored-by: Gergo Jedlicska <gergo@jedlicska.com>
2025-12-10 13:18:31 +03:00
Dogukan Karatas 80d1df8eca Merge pull request #424 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
dev -> main for release
2025-12-04 12:01:06 +01:00
Dogukan Karatas b5796245aa Merge pull request #422 from specklesystems/dogukan/solidx-class
.NET Build and Publish / build (push) Has been cancelled
feat (objects): introducing SolidX class
2025-12-04 09:20:34 +01:00
Dogukan Karatas 639c774f80 sat added 2025-12-04 09:07:30 +01:00
Dogukan Karatas 3bb5d1e73a SolidX class added
.NET Build and Publish / build (push) Has been cancelled
2025-11-26 12:09:24 +01:00
Jedd Morgan e01360ad03 mark version received (#419) 2025-11-24 19:53:09 +00:00
dependabot[bot] 2494b160e8 chore(deps): bump actions/checkout from 5 to 6 (#421)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-24 19:46:31 +00:00
Jedd Morgan b0da4510bf Merge pull request #410 from specklesystems/jrm/subscription-tests
test(subscriptions): Make subscription tests a bit more reliable
2025-11-05 11:28:23 +00:00
Jedd Morgan 96392d0d2f chore(ci): Reliable integration Tests (#418)
* remove bad tests

* add pack to PR workflow
2025-11-05 11:22:07 +00:00
Jedd Morgan 0aacc3fe89 Merge pull request #417 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
main -> dev for 3.9.0 release
2025-11-05 11:03:28 +00:00
Björn Steinhagen 39f5257f85 Merge pull request #409 from specklesystems/bjorn/cnx-2722-grasshopper-root-collection-props
feat(grasshopper): add model-wide properties to send/receive
2025-11-05 12:53:21 +02:00
Björn Steinhagen 2ea6348689 fix: again :/ 2025-11-05 12:24:06 +02:00
Björn Steinhagen 42c26e38bf fix: unnecessary using directive 2025-11-05 12:22:27 +02:00
Björn Steinhagen cb31fd1a08 chore: namespace change 2025-11-05 12:18:40 +02:00
Björn 67236abafe refactor: rootCollection to use IProperties 2025-10-29 14:39:06 +02:00
Björn 59a4f8f864 Merge remote-tracking branch 'origin/dev' into bjorn/cnx-2722-grasshopper-root-collection-props 2025-10-29 14:19:33 +02:00
Jedd Morgan 0e98e1cccd refactor(ci): Refactor CI to run integration tests as separate workflow (#413)
* Refactor CI to run integration tests as separate workflow

* Tool restore

* correct cache path

* conditionally use container registry

* use sln because net8

* fix typo

* Correct trait filter

* Correct mistake again

* fix again

* fml

* clarify names

* hopefully we're properly filtering test categories now

* maybe this?

* What does this do?

* revert is test project changes

* IsTestProject fix

* Correct test setup for automate

* maybe fix unit tests

* docker-compose-file alighment

* remove debug

* Ok tests should now pass
2025-10-29 10:00:51 +00:00
Claire Kuang 79c6f02544 Merge branch 'dev' into bjorn/cnx-2722-grasshopper-root-collection-props 2025-10-29 09:45:31 +00:00
Jedd Morgan 07713b41e1 Fix(gql)!: Treat UNAUTHORIZED_ACCESS_ERROR as an SpeckleGraphQLForbiddenException (#411)
* Respect UNAUTHORIZED_ACCESS_ERROR

* Correct test setup for automate

* dumb dumb typo
2025-10-28 15:48:37 +00:00
Claire Kuang c3f944dcf1 Delete ViewProxy.cs (#415)
.NET Build and Publish / build (push) Has been cancelled
2025-10-28 13:52:19 +00:00
Claire Kuang 3c0b9e8b1c Merge pull request #414 from specklesystems/claire/add-camera-class
feat(objects): add Camera class
2025-10-28 13:42:39 +00:00
Jedd Morgan 8890f8cb36 Merge pull request #416 from specklesystems/jrm/attribute-mask-revert
feat(sdk)!: Unexpose attribute mask
2025-10-28 13:29:30 +00:00
Jedd Morgan a0eae88479 Merge branch 'dev' into jrm/attribute-mask-revert 2025-10-28 13:20:52 +00:00
Jedd Morgan 8785e49f73 Add send/receive integration tests (#412) 2025-10-28 14:12:12 +01:00
Jedd Morgan 6e35d6af6d Unexpose attribute mask 2025-10-28 10:33:47 +00:00
Björn b67eb8d8af refactor: model properties to properties 2025-10-27 13:03:52 +02:00
Björn 63f06c8541 chore: format 2025-10-25 15:41:00 +02:00
Björn 3f49bb05d1 chore: cleanup 2025-10-25 15:20:24 +02:00
Björn 9a879fd1ac fix: parameterless constructor 2025-10-25 13:35:58 +02:00
Björn c3230d5d91 refactor: naming conflict 2025-10-25 12:09:34 +02:00
Björn f1a64590d7 chore(models): adds RootCollection 2025-10-25 11:59:39 +02:00
Claire Kuang c2735f0a32 chore(objects): update view proxy and camera classes (#408)
* Adds new camera and view proxy classes

* Update ViewProxy.cs

* removes viewproxy and orthographic prop on camera class
2025-10-24 11:46:30 +01:00
Claire Kuang 0b01091209 feat(objects): Adds new camera and view proxy classes (#407)
* Adds new camera and view proxy classes

* Update ViewProxy.cs
2025-10-22 10:53:38 +01:00
Jedd Morgan 6568781275 Merge pull request #405 from specklesystems/dev
Dev -> Main
2025-10-15 11:07:32 +01:00
Jedd Morgan 98223e251c Use OIDC for auth (#397)
.NET Build and Publish / build (push) Has been cancelled
2025-10-15 10:45:58 +01:00
Jedd Morgan 6740659af4 dev -> main for release (#404)
* Expose options for sending and receiving (#394)

* chore(docs): Update doc comments (#398)

* path provider

* tweaks

* Update RenderMaterial.cs (#399)

* removes the extra serializer (#402)

* feat(sdk): align SpecklePathProvider with connector repo (#400)

* path provider

* tweaks

* Align with duplicated class

* skip some slow tests (#403)

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
Co-authored-by: Dogukan Karatas <61163577+dogukankaratas@users.noreply.github.com>
2025-10-15 10:45:26 +01:00
Jedd Morgan 08f702794a skip some slow tests (#403) 2025-10-15 09:25:07 +00:00
Jedd Morgan 879ebf7e3c feat(sdk): align SpecklePathProvider with connector repo (#400)
* path provider

* tweaks

* Align with duplicated class
2025-10-15 10:14:57 +01:00
Dogukan Karatas 1046e2aafc removes the extra serializer (#402)
.NET Build and Publish / build (push) Has been cancelled
2025-10-14 16:20:30 +01:00
Jedd Morgan 5cb0eddf4e Update RenderMaterial.cs (#399) 2025-10-13 19:00:26 +00:00
Jedd Morgan e97ce83c6b chore(docs): Update doc comments (#398)
* path provider

* tweaks
2025-10-13 17:01:38 +00:00
Adam Hathcock ea23e72c77 Expose options for sending and receiving (#394) 2025-10-08 10:16:28 +01:00
Adam Hathcock 701013ad46 Merge pull request #393 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
dev to main for release (DONUT squash)
2025-09-24 10:22:05 +01:00
Adam Hathcock 37358570ec Use new endpoint with attribute mask support (#392)
* Use new endpoint with attribute mask support

* fix test
2025-09-24 11:00:44 +02:00
Adam Hathcock 02b9a73164 Don't record cancelled exceptions (#391)
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
2025-09-17 11:05:21 +01:00
Adam Hathcock fdc0842b03 Merge pull request #388 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Main to dev (no squash)
2025-09-12 12:04:04 +01:00
Adam Hathcock 530387b87c Remove the cache that avoids recalculating IDs from JSON (#384)
Co-authored-by: Jedd Morgan <45512892+JR-Morgan@users.noreply.github.com>
Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-09-12 10:47:53 +00:00
Adam Hathcock b16e32d1ff fix: Correctly pass options to object saver (#387)
* Correctly pass options to object saver

* formatting
2025-09-12 10:30:47 +00:00
Jedd Morgan d91fccbb10 Merge pull request #386 from specklesystems/jrm/local-account-ids
chore(sdk): deprecate local ids
2025-09-11 11:03:27 +00:00
Jedd Morgan 62f687b589 Merge pull request #385 from specklesystems/jrm/hashed-account-fallbacks
.NET Build and Publish / build (push) Has been cancelled
chore(sdk)!: Remove fallback behaviour from hashed account info
2025-09-10 14:50:27 +01:00
Adam Hathcock c0d4d951df Merge pull request #383 from specklesystems/dependabot/github_actions/actions/setup-dotnet-5
Bump actions/setup-dotnet from 4 to 5
2025-09-09 08:26:29 +01:00
dependabot[bot] 4e1753e1fd Bump actions/setup-dotnet from 4 to 5
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4 to 5.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 19:09:58 +00:00
Jedd Morgan 23d5dd44bc Merge pull request #382 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Dev -> Main for release
2025-09-08 10:54:56 +01:00
Jedd Morgan 4c615ae8c6 Back Merge Main -> dev (#381)
Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-09-08 10:43:16 +01:00
Jedd Morgan d5ee9fb76c Merge branch 'dev' into jrm/dev-main 2025-09-08 10:42:30 +01:00
Björn Steinhagen ea118bcdbb fix: collection order (#380)
* fix: collection order

* chore: format

* refactor: keeping depth first
2025-09-08 09:21:02 +00:00
Adam Hathcock ba456ee3eb Merge pull request #378 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Dev to main for release (NO SQUASH)
2025-08-28 09:33:09 +01:00
Adam Hathcock 10d283a9f7 Report increment rather than total position (#377)
* Report increment rather than total position

* Report progress before saving SQLite
2025-08-26 16:45:53 +01:00
dependabot[bot] 647c4733cb Bump actions/checkout from 4 to 5 (#376)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-20 10:32:24 +01:00
Jedd Morgan 30c9b17dab Merge pull request #374 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Dev -> Main
2025-08-06 13:39:59 +01:00
Jedd Morgan f7ddc19086 Merge pull request #375 from specklesystems/jrm/main-dev-7
Main -> Dev
2025-08-06 13:28:59 +01:00
Jedd Morgan 6d06901b4f Merge branch 'dev' into jrm/main-dev-7 2025-08-06 13:13:39 +01:00
Jedd Morgan 4b588fc287 Do not dispose the activity factory (#372)
.NET Build and Publish / build (push) Has been cancelled
2025-08-04 14:21:50 +00:00
Jedd Morgan 786e683d89 Merge pull request #371 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Dev -> Main for 3.5.0 stable
2025-08-01 14:49:47 +01:00
Jedd Morgan 8d999f4f9c Update README.md (#370) 2025-07-30 07:53:10 +00:00
Jedd Morgan bb7542e254 chore(ci): Remove Gitversion (#369)
* Strip out gitversion

* Pr workflow

* fix?

* fix x2

* pass envvars properly
2025-07-30 08:41:32 +01:00
Jedd Morgan d6f6254a92 feat(file-import): Added file import resource and blob api functions (#367)
.NET Build and Publish / build (push) Has been cancelled
* add file import resource

* disabled health check

* re-enable healthcheck

* git ignore volumes

* disabled importer

* start_period

* Skipped broken tests

* Verify tests

* Fixed tests

* reverted volumes path

* Update docker-compose.yml
2025-07-29 14:52:12 +00:00
Jedd Morgan f60f85b639 Merge pull request #368 from specklesystems/jrm/main-dev-5
chore: main -> dev
2025-07-28 18:01:43 +01:00
Jedd Morgan bcdf73cc70 Updated active workspace query (#365) 2025-07-25 08:42:43 +01:00
Adam Hathcock 47e72ee1a7 Merge pull request #364 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Main to dev
2025-07-23 14:19:52 +01:00
Adam Hathcock f3de5324db Merge pull request #362 from specklesystems/main
Main to dev
2025-07-23 13:44:14 +01:00
Adam Hathcock 4dd6db886f insert or replace always...don't use ignore or insert (#363)
* SaveObject is always insert or replace.  Never use insert or ignore

* add/fix tests

* always replace even for bulk
2025-07-23 12:16:08 +00:00
Adam Hathcock 4b82db8ea2 Merge pull request #361 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Main to dev
2025-07-23 10:23:04 +01:00
Adam Hathcock 9e7f26f7a6 Add ModelCacheManager class and use it (#356)
* Introduce ModelCacheManager to manage cache and sizes and deletions

* move and abstract

* add tests and format

* Update src/Speckle.Sdk/Caching/ModelCacheManager.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Clean up

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-23 10:07:57 +01:00
Adam Hathcock b19f8c4219 Merge pull request #358 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
(NO SQUASH) Main to dev for release
2025-07-21 15:49:49 +01:00
Adam Hathcock c517e61517 Merge pull request #360 from specklesystems/main-dev
Main to dev
2025-07-21 15:47:38 +01:00
Adam Hathcock b3e0623856 Merge remote-tracking branch 'origin/main' into main-dev 2025-07-21 15:36:30 +01:00
Adam Hathcock e5d1ef2448 Merge pull request #354 from specklesystems/main-dev
Merge pull request #348 from specklesystems/dev
2025-07-21 11:12:54 +01:00
Adam Hathcock 83c3de05fa Merge remote-tracking branch 'origin/dev' into main-dev 2025-07-21 11:02:02 +01:00
Adam Hathcock 507ded7d4a Fix shallow copy allocations and perf (#357)
* add more DynamicBase Tests

* Move ShallowCopy to dynamic and try to be faster with copy

* Correct tests for macOS

* use cache obsolete attribute

* Update src/Speckle.Sdk/Models/DynamicBase.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Speckle.Sdk.Tests.Unit/Models/DynamicBaseTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update tests/Speckle.Sdk.Tests.Unit/Models/DynamicBaseTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix AI

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-21 11:01:00 +01:00
Adam Hathcock e15029bab3 Merge pull request #350 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
(dev to main) If we're already cancelling, ignore extra exceptions (#349)
2025-07-14 10:34:06 +01:00
Adam Hathcock a43fd44206 Stop recording an exception that's rethrown (#355)
* Stop recording an exception that's rethrown

* Update src/Speckle.Sdk/Api/GraphQL/Client.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-14 10:15:40 +01:00
Adam Hathcock 1bcd8ac3a4 Merge branch 'dev' into main-dev 2025-07-03 10:42:05 +01:00
Adam Hathcock a8dc93e22b Adds detail to message so that user isn't going WTF SDK (#351)
* Adds detail to message so that user isn't going WTF SDK

* update exception tests
2025-07-03 09:39:53 +00:00
Jedd Morgan 5a0f883b98 Add compatibility with :local docker images (#353)
Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-07-03 10:28:35 +01:00
Adam Hathcock a5d035671a If we're already cancelling, ignore extra exceptions (#349)
* If we're already cancelling, ignore extra exceptions

* Do more robust cancellation

* Try to have more robust disposal and cancellation check
2025-07-01 10:20:32 +01:00
Adam Hathcock cd6ebad619 Merge pull request #348 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
New release for connectors
2025-06-30 15:15:51 +01:00
Adam Hathcock 33c2e6e1a4 Better handle graphql commit errors (#343)
* Better handle graphql commit errors

* add graphql error test
2025-06-30 10:54:44 +00:00
Adam Hathcock b97702adb1 Small fixes to SDK (#347)
* Increase channel capacity to make things more performant

* Avoid logging send cancellation exceptions, caller did it

* Try to avoid collection modified errors when cancelling by more aggressive checks

* oops, rethrow, don't catch
2025-06-30 10:37:30 +00:00
Adam Hathcock 80c4f694ec Merge pull request #346 from specklesystems/main-dev
Main to dev
2025-06-30 11:26:36 +01:00
Adam Hathcock fb5042004f Merge remote-tracking branch 'origin/dev' into main-dev 2025-06-30 08:58:57 +01:00
Adam Hathcock c0a9291632 Merge pull request #344 from specklesystems/oguzhan/level-proxies
.NET Build and Publish / build (push) Has been cancelled
Feat(objects): level proxies
2025-06-23 15:05:15 +01:00
oguzhankoral b783d2acb6 Format 2025-06-23 15:57:24 +03:00
oguzhankoral 93539adc1e Add level proxies 2025-06-23 15:42:48 +03:00
Adam Hathcock 98005933de Remove DistinctBy as we don't use it (#342) 2025-06-19 09:34:34 +01:00
Adam Hathcock 50906b172a Merge pull request #340 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
2025-06-11 17:35:27 +01:00
Adam Hathcock 05f7353925 Revert "Merge pull request #335 from specklesystems/adam/cnx-1786-allow-multiple-sends-to-access-sqlite-in-a-non-locking-2" (#339)
This reverts commit 59019bf846, reversing
changes made to 3afaf61a1a.

Co-authored-by: Adam Hathcock <adam@Adams-Mac-mini.localdomain>
2025-06-11 15:32:06 +00:00
Adam Hathcock 8328498553 Merge pull request #338 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Dev to Main (no squash)
2025-06-11 11:07:20 +01:00
Adam Hathcock 59019bf846 Merge pull request #335 from specklesystems/adam/cnx-1786-allow-multiple-sends-to-access-sqlite-in-a-non-locking-2
Use an object saver per stream instead of sqlite manager per stream
2025-06-11 10:50:34 +01:00
Adam Hathcock 3afaf61a1a Merge pull request #337 from specklesystems/main
Main to dev (no squash)
2025-06-11 10:50:18 +01:00
Adam Hathcock 424609fad0 fix tests 2025-06-10 13:18:34 +01:00
Adam Hathcock 46c067308e Fix DI dependency and tests 2025-06-10 11:39:42 +01:00
Adam Hathcock 0e33e8df8f add tests 2025-06-10 11:33:17 +01:00
Adam Hathcock bc81c21e9d format 2025-06-10 11:15:30 +01:00
Adam Hathcock 7f8b59d348 Pool object savers instead of sqlite 2025-06-10 11:15:01 +01:00
Adam Hathcock 44ba61e4a5 Adjustments to avoid sqlite "database is locked" errors (#333)
* add new exception test

* Make memory tests and file path tests be explicit

* set the default write parallelism to 1

* set to single reader for caching channel

* format

* Try to have consistent DB locked error test

* always a single reader of the channel

* Remove extra snapshot

* Revert "Try to have consistent DB locked error test"

This reverts commit 93669c57a3.

* remove extra test that doesn't do anything
2025-06-09 16:24:39 +00:00
Adam Hathcock 4f7b470901 Merge pull request #329 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
SDK 3.4 release (do not squash)
2025-06-05 13:59:43 +01:00
Jedd Morgan 8c6426d617 Updated one more usage of newtonsoft (#332)
Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-06-05 10:39:16 +00:00
Adam Hathcock 5562ce1a2d Add detail to the message in logs (#331) 2025-06-05 09:54:36 +01:00
Jedd Morgan 7019b8d7c6 Fix(automate): STJ camel casing (#330)
* stj required

* More requireds

* Use JSON serializer settings for camel case rule

* readonly

* static naming
2025-06-04 15:56:50 +00:00
Adam Hathcock 58a0326060 Receive should sort the root closures to see a speed improvement (#311)
* Maybe really fixes closures

* fornat

* add ai generated tests

* fix tests

* fix tests

* added test with correct number of closures?

* closures are self contained.  don't increment on attached properties

* format

* MergeClosure should reuse if exists, not just set

* Add generated tests and sort the parser correctly when using get closures

* add extra options to not sort and make sorting default for receive

* hide private method
2025-06-04 13:54:26 +00:00
Adam Hathcock 55f83919d1 Don't log aggregate exceptions that only contain cancelled exceptions (#326)
* Don't log aggregate exceptions that only contain cancelled exceptions

* check if all are not cancelled
2025-06-04 12:59:51 +00:00
Adam Hathcock 46c57b18be Cancel before kicking off tasks to ensure they throw (#328) 2025-06-04 13:49:03 +01:00
Adam Hathcock 7b5ada57cd Changing uploaded to uploading to better show progress than just rate (#321) 2025-06-03 16:27:02 +00:00
Adam Hathcock e29b27bcd3 Add some debugging stats about the sent or received payloads to add debugging when things are massive (#319) 2025-06-03 14:15:37 +00:00
Jedd Morgan ff1b688321 refactor(accountmanager): Refactor account manager for automate (#320)
* First pass refactor account manager

* Use GraphQLClient factory in account manager also

* update account references

* cleanup

* Added test + comments

* more docstrings

* More tests
2025-06-03 12:54:44 +00:00
Adam Hathcock 0be143d391 Make nulls appear as soon as possible (#324) 2025-06-03 12:09:55 +00:00
Adam Hathcock c0a66a297a Add info to send/receives for debugging (#325) 2025-06-03 12:48:05 +01:00
Adam Hathcock aad604e819 Merge pull request #323 from specklesystems/main-dev
Main to dev
2025-06-03 09:41:16 +01:00
Adam Hathcock 48313cb082 Merge remote-tracking branch 'origin/dev' into main-dev 2025-06-02 15:50:19 +01:00
kekesidavid 0361a6e5b7 Merge pull request #318 from specklesystems/david/move-text-class-update-pr-to-main
.NET Build and Publish / build (push) Has been cancelled
fix (sdk) Text class updates: removed origin from Text class and added screenAligned prop
2025-06-02 09:22:57 +02:00
David Kekesi 0e97782c29 fixed comment 2025-06-02 09:12:12 +02:00
David Kekesi 298dedc3af text class update pr moved to main 2025-05-30 19:01:02 +02:00
Adam Hathcock 422403d499 fix concurrent tests 2 - This should wait for cancellation to happen (#316)
* Fix test to be deterministic

* This should wait for cancellation to happen

* update csharpier
2025-05-30 14:17:04 +00:00
Adam Hathcock b652ffa773 Fix test to be deterministic (#315) 2025-05-30 12:30:57 +00:00
Adam Hathcock 68ace02e2d Use custom md5 just for account/user IDs, not anything real (#314)
* Use custom md5 just for account/user IDs, not anything real

* test fixes

* To lower and upper as needed
2025-05-30 12:15:44 +00:00
Jedd Morgan b6be7a351f feat(automate): Add automate SDK (#313)
* First pass

* First pass adding service registraiton

* Finished up service registration

* Json exception

* Moved to the right place

* Fixed tests

* Added some missing docs strings

* Reflecting Gergo's specklepy changes

* Correct the DI registration

* Readme

* No warn beta packages

* Format

* renamed misleading variable

* Fixed lock files

* Disable SQLite for automate
2025-05-30 13:05:14 +01:00
Adam Hathcock 1039e75d0c Calculate closures correctly (#309)
* Maybe really fixes closures

* fornat

* add ai generated tests

* fix tests

* fix tests

* added test with correct number of closures?

* closures are self contained.  don't increment on attached properties

* format

* MergeClosure should reuse if exists, not just set

* add not null on a method
2025-05-27 14:05:10 +01:00
Jedd Morgan 0f8752d5ab feat(api): Improvements to GrahpQL error handling (#304)
* Graphql extras

* extra server resource test

* usings

* Fixed test
2025-05-20 12:44:23 +00:00
Adam Hathcock 64a93345d6 Merge pull request #310 from specklesystems/main
Main to dev (no squash)
2025-05-19 12:00:56 +01:00
Jedd Morgan efc38d8f5c Added Workspace project visibility (#307)
.NET Build and Publish / build (push) Has been cancelled
2025-05-14 21:37:39 +03:00
Jedd Morgan e3ca75abe1 removed csharp 4.7 dependency from .net8 target (#306) 2025-05-14 13:12:37 +01:00
Adam Hathcock b479d368ad SLNX added, only supported in IDEs and .NET 9+ (#294) 2025-05-14 09:35:43 +00:00
Adam Hathcock 8d3985f93b Merge pull request #305 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
Dev to Main
2025-05-14 10:19:51 +01:00
Jedd Morgan 915a18dc98 Merge pull request #303 from specklesystems/jrm/main-dev
Main -> Dev
2025-05-13 11:38:56 +01:00
Jedd Morgan 5fcb3223d6 Merge branch 'dev' into jrm/main-dev 2025-05-13 11:28:13 +01:00
Adam Hathcock 21851c06d2 Use WhenAll instead of WhenAny and avoid manual task exception processing (#300)
* No reason to process exceptions manually

* formatting

* use a pool for gathering child task results

* Use a smaller whenany with a cancellation

* formatting
2025-05-13 10:00:53 +01:00
Adam Hathcock 3a9a633d30 Add empty DataObject tests (#301)
* Add empty DataObject tests

* format
2025-05-09 14:51:03 +00:00
Jedd Morgan 7f092d529c feat(api): Add ActiveUserResource.GetProjectsWithPermissions (#299)
.NET Build and Publish / build (push) Has been cancelled
* Fixed Mistakes (#296)

* Added extra permission checks (#297)

* Add extra query for project with permissions

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
Co-authored-by: Adam Hathcock <adam@hathcock.uk>
2025-05-08 15:11:53 +00:00
Jedd Morgan a4f0e0e4aa Added extra permission checks (#297)
.NET Build and Publish / build (push) Has been cancelled
2025-05-08 09:25:08 +00:00
Jedd Morgan 227729a0df Fixed Mistakes (#296)
.NET Build and Publish / build (push) Has been cancelled
2025-05-07 17:01:56 +00:00
Jedd Morgan 178085f3f8 dev -> main (#295)
.NET Build and Publish / build (push) Has been cancelled
* Sanitize test references

* add registration tests to SDK and ignore classes that can't be made

* add additional test

* chore(dev) Update to csharpier 1.0 (#284)

* Update to csharpier 1.0

* Fix check and nowarn

* format

* Update dependencies (#285)

* Added extra test for GetMembers (#290)

* Added extra test for GetMembers

* fixed tests

* verify

* Format

* Run the module init on unit tests and make it json

* update deps

* gitignore support was disabled in csharpier 1.0.1

---------

Co-authored-by: Adam Hathcock <adam@hathcock.uk>

* Add workspaces queries (#291)

* Add workspaces queries

* Format

* extra tweaks

* init speckle verify

* Add workspace creation state

* Add workspace creation test

* test exceptional cases

* GetActiveWorkspace tests

* fixed test

---------

Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
Co-authored-by: Adam Hathcock <adam@hathcock.uk>
2025-05-06 18:02:59 +03:00
Adam Hathcock 9794195e9c Merge pull request #293 from specklesystems/main-dev
.NET Build and Publish / build (push) Has been cancelled
Main to dev (NO SQUASH)
2025-05-06 14:00:54 +01:00
Adam Hathcock a61c442930 Merge branch 'dev' into main-dev 2025-05-06 13:50:16 +01:00
Jedd Morgan 68a407905d Add workspaces queries (#291)
* Add workspaces queries

* Format

* extra tweaks

* init speckle verify

* Add workspace creation state

* Add workspace creation test

* test exceptional cases

* GetActiveWorkspace tests

* fixed test
2025-05-01 21:23:30 +03:00
Adam Hathcock 0f2abaf532 Merge remote-tracking branch 'origin/dev' into main-dev 2025-04-30 16:24:06 +01:00
Adam Hathcock 07634b6f6a Merge remote-tracking branch 'origin/dev' into main-dev 2025-04-30 16:22:39 +01:00
Jedd Morgan e938725d35 Added extra test for GetMembers (#290)
* Added extra test for GetMembers

* fixed tests

* verify

* Format

* Run the module init on unit tests and make it json

* update deps

* gitignore support was disabled in csharpier 1.0.1

---------

Co-authored-by: Adam Hathcock <adam@hathcock.uk>
2025-04-30 16:13:41 +01:00
Adam Hathcock d3369e3ce5 Merge pull request #289 from specklesystems/main-dev
Main to dev (don't squash)
2025-04-30 16:00:46 +01:00
KatKatKateryna d75a61d775 Add text class (#271)
.NET Build and Publish / build (push) Has been cancelled
* draft class

* corrections

* edits

* max width

* remove import

* typo

* naming

* move directories

* delete from old location

* comment

* formatting

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
2025-04-30 10:56:29 +02:00
Adam Hathcock 2ae4003afb Merge branch 'main' into main-dev 2025-04-28 10:52:58 +01:00
Adam Hathcock 24db4c4ae4 Merge pull request #288 from specklesystems/adam/no-drop-writes
.NET Build and Publish / build (push) Has been cancelled
fix (main) Don't drop items to write when sending fast
2025-04-28 10:18:48 +01:00
Adam Hathcock edf63d4a1b fix build issue 2025-04-28 09:39:46 +01:00
Adam Hathcock b5b0922e7f Revert to write async 2025-04-28 09:35:02 +01:00
Adam Hathcock ff390f772d just wait for space instead of another task and reduce size to 1000 2025-04-25 18:24:34 +01:00
Adam Hathcock d69f0bba2a fmt 2025-04-25 18:13:05 +01:00
Adam Hathcock 33c14fc14c Remove extras 2025-04-25 18:09:04 +01:00
Adam Hathcock 536e58aacc Don't drop items to write when sending fast 2025-04-25 17:45:01 +01:00
Adam Hathcock 88188aace6 Merge pull request #287 from specklesystems/main-dev
Main to dev (NO SQUASH)
2025-04-24 12:12:06 +01:00
Adam Hathcock ad44a7cdbc Merge branch 'dev' into main-dev 2025-04-24 12:01:32 +01:00
Adam Hathcock 38449dca9a Update dependencies (#285) 2025-04-24 11:59:26 +01:00
Adam Hathcock 764eb43838 Merge branch 'dev' into main-dev 2025-04-24 11:52:02 +01:00
Adam Hathcock a84e6d89ca chore(dev) Update to csharpier 1.0 (#284)
* Update to csharpier 1.0

* Fix check and nowarn

* format
2025-04-24 10:45:09 +00:00
Adam Hathcock 377829adae fix(main) exception test correction and token usage (#283)
.NET Build and Publish / build (push) Has been cancelled
* add parallelism on exception after count test

* use scoped token source correctly

* format

* Centralized token usage and made sqlite busy timeout be 5 seconds

* restore write parallelism to 4

* add to comment
2025-04-24 10:40:46 +00:00
Adam Hathcock a479440b66 Merge pull request #286 from specklesystems/adam/check-registration
feature (dev) check registration
2025-04-24 11:29:27 +01:00
Adam Hathcock cc9639b179 Merge pull request #282 from specklesystems/adam/error-fix
fix(main) Wrong error message being displayed in UI
2025-04-24 11:29:10 +01:00
Adam Hathcock d44b4fa52b add additional test 2025-04-24 08:33:42 +01:00
Adam Hathcock ea6ca8c555 add registration tests to SDK and ignore classes that can't be made 2025-04-23 15:57:03 +01:00
Adam Hathcock 113f0fd551 Sanitize test references 2025-04-23 15:42:57 +01:00
Adam Hathcock bcc4e25970 Merge pull request #280 from specklesystems/main-dev
Main->Dev
2025-04-23 13:04:37 +01:00
Adam Hathcock b733ce5f29 fix snapshot test message 2025-04-22 11:09:50 +01:00
Adam Hathcock 1c8b2b82d7 Wrong error message being displayed in UI 2025-04-22 10:13:59 +01:00
Jedd Morgan 11cd2dc1cb Update ProjectResourceExceptionalTests.cs (#279) 2025-04-11 12:27:04 +00:00
Adam Hathcock 3789898ea2 Merge pull request #278 from specklesystems/dev
.NET Build and Publish / build (push) Has been cancelled
SDK 3.2 release from dev to main
2025-04-09 12:35:06 +01:00
Adam Hathcock 0b3318f9e1 Merge pull request #277 from specklesystems/main-dev
Main to dev again
2025-04-09 11:56:35 +01:00
Adam Hathcock 7589ad5f05 Merge remote-tracking branch 'origin/dev' into main-dev 2025-04-09 11:45:06 +01:00
Adam Hathcock 6df32262bd fix: Update HostAppVersion.cs (#276)
Adding v2026 for next autodesk releases support.

Co-authored-by: Jonathon Broughton <760691+jsdbroughton@users.noreply.github.com>
2025-04-09 10:42:40 +00:00
Adam Hathcock 001ca1c287 Update some dependencies (#275) 2025-04-09 10:31:23 +00:00
Adam Hathcock 59e459559b Merge branch 'dev' into main-dev 2025-04-09 11:22:51 +01:00
Adam Hathcock d305fe59cb feat(sdk) clean up registration of sdk to not be connector specific (#274)
* First pass of ObjectSaver and better in-memory usage

* fix some tests

* add commit to match deserialize process

* correct more tests

* format

* make a deserialize factory

* fix tests? and format

* use distinct

* Fix mismerge

* Fix serialization issues with tests

* fix merges

* follow copilot suggestions

* remove disables

* change registration to take strings and TypeLoader isn't public

* remove unused transports

* more test fixes

* fmt

* add Application object back
2025-04-08 09:49:31 +00:00
Adam Hathcock f163b2822e (feat) add memory serialize and make relevant tests use it (#252)
* First pass of ObjectSaver and better in-memory usage

* fix some tests

* add commit to match deserialize process

* correct more tests

* format

* make a deserialize factory

* fix tests? and format

* use distinct

* Fix mismerge

* Fix serialization issues with tests

* fix merges

* follow copilot suggestions

* remove disables
2025-04-08 10:21:47 +01:00
Adam Hathcock 630bb38b8b fix: Update HostAppVersion.cs (#273)
Adding v2026 for next autodesk releases support.

Co-authored-by: Jonathon Broughton <760691+jsdbroughton@users.noreply.github.com>
2025-04-07 13:43:22 +01:00
Adam Hathcock bacff2da34 Merge branch 'dev' into main-dev 2025-04-07 12:46:07 +01:00
Adam Hathcock 129d5285ed Merge branch 'main' into main-dev 2025-04-07 12:45:09 +01:00
Adam Hathcock 5d07fe0ea0 refactor error handling to no propagate through channels. (#268)
* refactor error handling to no propagate through channels.  Use cancellation to shut down on exception

* don't try to shutdown scheduler when exception happens...handle tokens in a tree

* some cleanup

* ConfigureAwait(false) again

* Add test and clean up exception handling

* fmt

* fixed tests
2025-04-07 11:49:47 +01:00
Adam Hathcock 062e3c2838 Make packs require tests too (#254) 2025-04-07 10:10:41 +00:00
Adam Hathcock f4cc4bc77e Merge pull request #272 from specklesystems/adam/add-2026
.NET Build and Publish / build (push) Has been cancelled
fix: Update HostAppVersion.cs for 2026
2025-04-07 10:55:25 +01:00
Jonathon Broughton d395f03219 fix: Update HostAppVersion.cs
Adding v2026 for next autodesk releases support.
2025-04-07 10:10:18 +01:00
Adam Hathcock 56ed399d70 Removes log statement that is confusing people when viewing log files (#267) 2025-03-26 14:21:06 +00:00
Adam Hathcock 7e0766fc7f Merge pull request #262 from specklesystems/main-dev
Main to dev
2025-03-25 10:24:16 +00:00
Adam Hathcock 0df343ebe1 Merge remote-tracking branch 'origin/main' into main-dev 2025-03-25 09:02:02 +00:00
Oğuzhan Koral 3cad57ceb3 Update object instead save (#266)
.NET Build and Publish / build (push) Has been cancelled
2025-03-24 20:24:15 +03:00
Oğuzhan Koral 11937ad7c9 Handle refresh token on catch (#265)
.NET Build and Publish / build (push) Has been cancelled
2025-03-24 13:39:23 +03:00
Adam Hathcock 8210fde69a Merge pull request #264 from specklesystems/oguzhan/parameter-for-app-id-secret
.NET Build and Publish / build (push) Has been cancelled
Chore(accounts): Extract hard coded app as parameter into function
2025-03-20 15:29:50 +00:00
oguzhankoral fa6f90621e Extract hard coded app as parameter into function 2025-03-20 18:18:23 +03:00
Adam Hathcock f6f1852664 Merge branch 'dev' into main-dev 2025-03-17 12:36:33 +00:00
Adam Hathcock b3f4190614 Merge pull request #260 from specklesystems/adam/fix-cancel-check
.NET Build and Publish / build (push) Has been cancelled
Cancellation check for sending needs to not be wrapped
2025-03-17 12:21:44 +00:00
Adam Hathcock 2fc0024cd2 Cancellation check for sending needs to not be wrapped 2025-03-17 12:08:42 +00:00
Adam Hathcock 300a5627fd Merge pull request #259 from specklesystems/revit-levels
.NET Build and Publish / build (push) Has been cancelled
feat(objects): adds level to revitobject
2025-03-17 11:06:13 +00:00
Claire Kuang 22f029fe33 Update RevitObject.cs 2025-03-17 10:32:47 +00:00
Claire Kuang c728266c88 Update RevitObject.cs 2025-03-17 10:31:43 +00:00
Claire Kuang 7f2d57cdad adds level 2025-03-17 10:05:41 +00:00
Adam Hathcock 81a22bd4cc Merge pull request #256 from specklesystems/main-dev
Main to dev
2025-03-13 10:39:11 +00:00
Adam Hathcock f64da099ab Merge branch 'dev' into main-dev 2025-03-13 08:48:36 +00:00
Adam Hathcock f4ba200640 Merge branch 'main' into main-dev 2025-03-13 08:47:23 +00:00
Adam Hathcock 4e84766be9 Merge pull request #253 from specklesystems/regions-PR-to-main
.NET Build and Publish / build (push) Has been cancelled
2025-03-12 17:55:01 +00:00
KatKatKateryna 73a95aded0 Regions class (#241)
* region class

* comments

* adjusted constructor

* todos

* comment

* change displayValur to curves

* small refactor

* assign correct property

* generalize display value

* some minor xml changes

* transform meshes; bbox not required

* remove comment

* Update Region.cs

---------

Co-Authored-By: Claire Kuang <kuang.claire@gmail.com>
Co-Authored-By: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-03-12 17:38:28 +00:00
KatKatKateryna 404600a839 Regions class (#241)
* region class

* comments

* adjusted constructor

* todos

* comment

* change displayValur to curves

* small refactor

* assign correct property

* generalize display value

* some minor xml changes

* transform meshes; bbox not required

* remove comment

* Update Region.cs

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
Co-authored-by: Adam Hathcock <adamhathcock@users.noreply.github.com>
2025-03-12 19:10:45 +08:00
Jedd Morgan 93d517eab7 test(objects): Verify tests for serialization (#227)
* Objects Verify Tests

* VerifyTests

* Verify as array with deterministic order

* lock

* Updated verified
2025-03-12 08:29:30 +00:00
Adam Hathcock 4110d90107 Merge pull request #245 from specklesystems/adam/more-code-cov2
Client tests and another SpeckleHttp one
2025-03-11 13:54:14 +00:00
Adam Hathcock 2d134bf7e1 make ExecuteWithResiliencePolicies internal 2025-03-11 13:24:39 +00:00
Adam Hathcock 686d0fd31c Merge remote-tracking branch 'origin/dev' into adam/more-code-cov2 2025-03-11 13:15:32 +00:00
Adam Hathcock b06878bbe1 Merge pull request #242 from specklesystems/remove-more-attributes
Remove more attributes
2025-03-11 12:09:56 +00:00
Adam Hathcock 64114167a8 Merge branch 'dev' into adam/more-code-cov2 2025-03-11 12:07:03 +00:00
Adam Hathcock 0c7cd75353 Merge branch 'dev' into remove-more-attributes 2025-03-11 11:58:47 +00:00
Adam Hathcock 3c5679ad2a Merge pull request #251 from specklesystems/main-dev
Main to dev
2025-03-11 11:58:12 +00:00
Adam Hathcock b043623021 Merge remote-tracking branch 'origin/dev' into main-dev 2025-03-10 09:36:28 +00:00
Adam Hathcock bd0997913a Add back the integration test 2025-03-10 09:31:56 +00:00
Jedd Morgan a79f0fd035 Deprecated public visibility (#249) 2025-03-08 17:50:52 +00:00
Adam Hathcock 7a29d27e46 Merge remote-tracking branch 'origin/dev' into adam/more-code-cov2 2025-03-07 11:14:32 +00:00
Adam Hathcock 82e3d37dd1 graphql test with client 2025-03-07 11:12:31 +00:00
Adam Hathcock 9695ec8c51 SpeckleHttp coverage (#244)
* Show code coverage for dev

* SpeckleHttp coverage and moved base stuff around

* add SpeckleHttpTests
2025-03-07 10:48:01 +00:00
Adam Hathcock 15fa319433 Show code coverage for dev (#243) 2025-03-07 10:26:13 +00:00
Adam Hathcock 793bbb9cd3 Complete speckle http 2025-03-07 09:29:05 +00:00
Adam Hathcock 3291010d43 add SpeckleHttpTests 2025-03-07 09:09:25 +00:00
Adam Hathcock 14732ce174 SpeckleHttp coverage and moved base stuff around 2025-03-07 09:03:07 +00:00
Adam Hathcock ba655988b0 Show code coverage for dev 2025-03-07 08:39:51 +00:00
Adam Hathcock 96822c4e66 Fix tests 2025-03-06 16:55:01 +00:00
Adam Hathcock 08356de1ad Merge remote-tracking branch 'origin/dev' into remove-more-attributes 2025-03-06 13:46:28 +00:00
Adam Hathcock 375f5071ae Improve test coverage by adding tests and removing unused code (#240)
* maybe all objects need to be false

* fmt

* remove extra code form hosts

* fix build

* add speckle serializer exception test

* add DownloadObjects and DownloadSingleObject test

* fmt

* add HasObjects test

* BaseItem tests

* adjust code and add test for UploadObjects

* Remove try/catch

* can't test compressed?  use lib to parse multipart

* remove unused verify snapshots
2025-03-06 13:20:11 +00:00
Adam Hathcock 5900a3c178 compiles 2025-03-06 12:00:57 +00:00
Adam Hathcock d7bf324029 Remove more unused attributes 2025-03-06 11:59:13 +00:00
Adam Hathcock c784fbf462 remove unused verify snapshots 2025-03-05 14:14:42 +00:00
Adam Hathcock 26f1802787 can't test compressed? use lib to parse multipart 2025-03-05 14:13:05 +00:00
Adam Hathcock a96e0f8c8e Remove try/catch 2025-03-05 12:38:34 +00:00
Adam Hathcock 9f36c9cfe5 adjust code and add test for UploadObjects 2025-03-05 12:18:19 +00:00
Adam Hathcock 4959f277e8 BaseItem tests 2025-03-05 11:10:21 +00:00
Adam Hathcock ea08b83f7a add HasObjects test 2025-03-05 09:54:31 +00:00
Adam Hathcock b24dc685fa fmt 2025-03-05 09:44:32 +00:00
Adam Hathcock 7cad14fe25 add DownloadObjects and DownloadSingleObject test 2025-03-05 09:34:44 +00:00
Adam Hathcock 4d552b6834 add speckle serializer exception test 2025-03-04 16:44:15 +00:00
Adam Hathcock 378a91995e fix build 2025-03-04 16:41:59 +00:00
Adam Hathcock 53b66dd26b remove extra code form hosts 2025-03-04 13:29:36 +00:00
Adam Hathcock 9dd04c0881 fmt 2025-03-03 16:39:17 +00:00
Adam Hathcock 474c18c29f maybe all objects need to be false 2025-03-03 16:32:02 +00:00
381 changed files with 13386 additions and 2991 deletions
+2 -9
View File
@@ -3,16 +3,9 @@
"isRoot": true,
"tools": {
"csharpier": {
"version": "0.30.6",
"version": "1.1.2",
"commands": [
"dotnet-csharpier"
],
"rollForward": false
},
"gitversion.tool": {
"version": "6.1.0",
"commands": [
"dotnet-gitversion"
"csharpier"
],
"rollForward": false
}
+26
View File
@@ -0,0 +1,26 @@
Directory.Build.targets
Directory.Build.props
**/bin/*
**/obj/*
_ReSharper.SharpCompress/
bin/
*.suo
*.user
TestArchives/Scratch/
TestArchives/Scratch2/
TestResults/
*.nupkg
packages/*/
project.lock.json
tests/TestArchives/Scratch
.vs
tools
.vscode
.idea/
.DS_Store
*.snupkg
coverage.xml
*.received.*
+1 -1
View File
@@ -1,6 +1,6 @@
printWidth: 120
useTabs: false
tabWidth: 2
indentSize: 2
preprocessorSymbolSets:
- ""
- "DEBUG"
+14
View File
@@ -0,0 +1,14 @@
# Coding standards, domain knowledge, and preferences that AI should follow
## C# Coding Standards
- Use the csharpier formatter for formatting C# code.
- Use the .editorconfig file for code style settings.
- Always use `var` when the type is obvious from the right side of the assignment.
- Always add braces for `if`, `else`, `for`, `foreach`, `while`, and `do` statements, even if they are single-line statements.
## Testing
- Use xUnit for unit testing.
- Use FluentAssertions for assertions in tests.
- Use Moq for mocking dependencies in tests.
+22
View File
@@ -0,0 +1,22 @@
# Git Commit Instructions
To ensure high-quality and consistent commits, please follow these guidelines:
1. **Format your code**
- Run the `csharpier` formatter on all C# files before committing.
- Ensure your code adheres to the `.editorconfig` settings.
2. **Write clear commit messages**
- Use the present tense ("Add feature" not "Added feature").
- Start with a short summary (max 72 characters), followed by a blank line and a detailed description if necessary.
3. **Test your changes**
- Run all unit tests before committing.
- Add or update xUnit tests as needed.
- Use AwesomeAssertions for assertions and Moq for mocking in tests.
4. **Review your changes**
- Double-check for accidental debug code or commented-out code.
- Ensure only relevant files are staged.
Thank you for helping maintain code quality!
@@ -0,0 +1,61 @@
name: Integration Test
on:
workflow_call:
inputs:
speckle-sharp-sdk-ref:
required: true
type: string
jobs:
integration-test:
env:
CLIENT_DIR: "./client"
CLIENT_REPO: "specklesystems/speckle-sharp-sdk"
SERVER_DIR: "./server"
SERVER_REPO: "specklesystems/speckle-server-internal"
SOLUTION: "Speckle.Sdk.sln"
SPECKLE_SERVER_IMAGE: "speckle-server:local"
runs-on: ubuntu-latest
steps:
- name: Checkout ${{ env.CLIENT_REPO }}
uses: actions/checkout@v6
with:
path: ${{ env.CLIENT_DIR }}
repository: ${{ env.CLIENT_REPO }}
ref: ${{ inputs.speckle-sharp-sdk-ref }}
- name: Checkout ${{ env.SERVER_REPO }}
uses: actions/checkout@v6
with:
repository: ${{ env.SERVER_REPO }}
path: ${{ env.SERVER_DIR }}
- name: Setup .NET SDK
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x.x
# cache: true
# cache-dependency-path: "**/packages.lock.json"
- name: 🏗️ Build Server
run: docker build --file "./packages/server/Dockerfile" --tag ${{ env.SPECKLE_SERVER_IMAGE }} .
working-directory: ${{ env.SERVER_DIR }}
- name: ⚙️ Spin up Server
run: docker compose --file "../${{ env.CLIENT_DIR }}/docker-compose-internal.yml" up --wait
working-directory: ${{ env.SERVER_DIR }}
env:
SPECKLE_SERVER_IMAGE: ${{ env.SPECKLE_SERVER_IMAGE }}
- name: 📦 Restore .NET Solution
run: dotnet restore ${{ env.SOLUTION }} --locked-mode
working-directory: ${{ env.CLIENT_DIR }}
- name: 🏗️ Build .NET Solution
run: dotnet build ${{ env.SOLUTION }} --configuration Release --no-restore -warnaserror
working-directory: ${{ env.CLIENT_DIR }}
- name: 🔨 Run .NET Integration Tests
run: dotnet test ${{ env.SOLUTION }} --filter "(Category=Integration)&(Server!=Public)" --configuration Release --no-build --no-restore --verbosity=normal
working-directory: ${{ env.CLIENT_DIR }}
+62
View File
@@ -0,0 +1,62 @@
name: Integration Test
on:
workflow_call:
inputs:
docker-compose-file:
required: true
type: string
use-internal-image:
default: false
type: boolean
secrets:
CODECOV_TOKEN:
required: true
jobs:
integration-test:
env:
Solution: "Speckle.Sdk.sln"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x.x
cache: true
cache-dependency-path: "**/packages.lock.json"
- name: 🔐 Login to Github Container Registry
if: ${{ inputs.use-internal-image }}
uses: docker/login-action@v3
with:
registry: "ghcr.io"
username: ${{ github.actor }}
password: ${{ github.token }}
- name: ⚙️ Spin up Server
run: docker compose --file ${{ inputs.docker-compose-file }} up --wait
- name: 📦 Restore
run: dotnet restore ${{ env.Solution }} --locked-mode
- name: 🏗️ Build
run: dotnet build ${{ env.Solution }} --configuration Release --no-restore -warnaserror
- name: 🔨 Integration Tests against Public Server
if: ${{ !inputs.use-internal-image }}
run: dotnet test ${{ env.Solution }} --filter "(Category=Integration)&(Server!=Internal)" --configuration Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
- name: 🔨 Integration Tests against Internal Server
if: ${{ inputs.use-internal-image }}
run: dotnet test ${{ env.Solution }} --filter "(Category=Integration)&(Server!=Public)" --configuration Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
continue-on-error: true
with:
fail_ci_if_error: true
files: tests/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
+50 -21
View File
@@ -1,32 +1,61 @@
name: .NET CI Build
name: PR Test
on:
pull_request:
jobs:
build:
env:
Solution: "Speckle.Sdk.sln"
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Checkout
uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.x.x
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x.x
cache: true
cache-dependency-path: "**/packages.lock.json"
- name: 🔫 Build All
run: ./build.sh
- name: 📦 Tool Restore
run: dotnet tool restore
- name: 📄 Format
run: dotnet csharpier check .
- name: 📦 Restore
run: dotnet restore ${{ env.Solution }} --locked-mode
- name: 🏗️ Build
run: dotnet build ${{ env.Solution }} --configuration Release --no-restore -warnaserror
- name: 🔨 Unit Tests
run: dotnet test ${{ env.Solution }} --configuration Release --filter "Category!=Integration" --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
- name: 🎁 Pack
run: dotnet pack ${{ env.Solution }} --configuration Release --no-build
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
continue-on-error: true
with:
fail_ci_if_error: true
files: tests/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
integration-test-internal:
uses: "./.github/workflows/integration-test.yml"
with:
docker-compose-file: "docker-compose-internal.yml"
use-internal-image: true
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
with:
files: tests/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
integration-test-public:
uses: "./.github/workflows/integration-test.yml"
with:
docker-compose-file: "docker-compose.yml"
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+41 -14
View File
@@ -2,36 +2,63 @@ name: .NET Build and Publish
on:
push:
branches: ["main", "dev"]
tags: ["3.*"]
tags: ["3.*.*"]
jobs:
build:
runs-on: ubuntu-latest
environment:
name: "nuget.org"
permissions:
id-token: write # enable GitHub OIDC token issuance for this job
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v4
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.x.x
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
cache: true
cache-dependency-path: "**/packages.lock.json"
- id: set-version
name: Set version to output
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="3.0.99.${{ github.run_number }}"
fi
SEMVER="${TAG}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
echo "fileVersion=$FILE_VERSION" >> "$GITHUB_OUTPUT"
echo $SEMVER
echo $FILE_VERSION
- name: 🔫 Build and Pack
run: ./build.sh pack
env:
SEMVER: ${{ steps.set-version.outputs.SEMVER }}
FILE_VERSION: ${{ steps.set-version.outputs.FILE_VERSION }}
- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v5
continue-on-error: true
with:
fail_ci_if_error: true
files: tests/**/coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
- name: NuGet login (OIDC → temp API key)
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- name: Push to nuget.org
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{secrets.CONNECTORS_NUGET_TOKEN }} --skip-duplicate
run: dotnet nuget push output/*.nupkg --source "https://api.nuget.org/v3/index.json" --api-key ${{steps.login.outputs.NUGET_API_KEY}}
+1
View File
@@ -15,6 +15,7 @@ tests/TestArchives/Scratch
tools
.vscode
.idea/
.volumes/
.DS_Store
*.snupkg
+1
View File
@@ -0,0 +1 @@
dotnet 8.0.400
+8 -21
View File
@@ -1,5 +1,4 @@
<Project>
<PropertyGroup Label="Compiler Properties">
<LangVersion>12</LangVersion>
<Nullable>enable</Nullable>
@@ -7,7 +6,6 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<!-- Defines common Nugetspec properties -->
<!-- Inheriting packable projects should define the rest of the nugetspec properties (PackageId, Description) -->
@@ -22,18 +20,16 @@
<PackageTags>speckle</PackageTags>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>false</IsPackable> <!--Can be set to true in inheriting .props/.csproj files for projects that should be packed-->
<IsPackable>false</IsPackable>
<!--Can be set to true in inheriting .props/.csproj files for projects that should be packed-->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Label="Analyers">
<EnableNetAnalyzers>true</EnableNetAnalyzers>
<AnalysisLevel>latest-AllEnabledByDefault</AnalysisLevel>
@@ -41,7 +37,6 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Ingored warnings, some aspirational but too noisy for now, some by design. -->
<NoWarn>
<!--Disabled by design-->
@@ -59,28 +54,20 @@
<!-- Aspirational -->
CA1502;CA1716;NETSDK1206;
$(NoWarn)
</NoWarn>
</NoWarn
>
</PropertyGroup>
<PropertyGroup>
<!-- Expose the repository root to all projects -->
<RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
</PropertyGroup>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\README.md" Pack="true" PackagePath="\"/>
<None
Condition="'$(IsPackable)' == 'true'"
Include="..\..\logo.png"
Pack="true"
PackagePath="\"
Visible="false"/>
<None Include="..\..\README.md" Pack="true" PackagePath="\" />
<None Condition="'$(IsPackable)' == 'true'" Include="..\..\logo.png" Pack="true" PackagePath="\" Visible="false" />
</ItemGroup>
<ItemGroup>
<!-- This file contains the configuration for some analyzer warnings, such as cyclomatic
complexity threshold -->
<AdditionalFiles Include="$(RepositoryRoot)CodeMetricsConfig.txt"/>
<AdditionalFiles Include="$(RepositoryRoot)CodeMetricsConfig.txt" />
</ItemGroup>
</Project>
+13 -14
View File
@@ -1,18 +1,17 @@
<Project>
<PropertyGroup Condition="'$(IsTestProject)' == 'true'">
<NoWarn>
$(NoWarn);
<!-- Things we need to test -->
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;
IDE0044;IDE0130;CA1508;
<!-- Analysers that provide no tangeable value to a test project -->
CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831;
</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(IsTestProject)' == 'true' or '$(TestProjectAnalyserRules)' == 'true' ">
<NoWarn>
<!-- Things we need to test -->
CS0618;CA1034;CA2201;CA1051;CA1040;CA1724;CA1065;
IDE0044;IDE0130;CA1508;
<!-- Analysers that provide no tangeable value to a test project -->
CA5394;CA2007;CA1852;CA1819;CA1711;CA1063;CA1816;CA2234;CS8618;CA1054;CA1810;CA2208;CA1019;CA1831;
$(NoWarn);
</NoWarn>
</PropertyGroup>
<Target Name="DeepClean">
<Message Text="Deep clean of $(MSBuildProjectName).csproj" Importance="high"/>
<RemoveDir Directories="$(BaseIntermediateOutputPath)"/>
<RemoveDir Directories="$(BaseOutputPath)"/>
<Message Text="Deep clean of $(MSBuildProjectName).csproj" Importance="high" />
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<RemoveDir Directories="$(BaseOutputPath)" />
</Target>
</Project>
+12 -8
View File
@@ -1,32 +1,36 @@
<Project>
<ItemGroup>
<PackageVersion Include="altcover" Version="9.0.1" />
<PackageVersion Include="AwesomeAssertions" Version="8.0.0" />
<PackageVersion Include="AwesomeAssertions" Version="8.1.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="Bullseye" Version="5.0.0" />
<PackageVersion Include="Bullseye" Version="6.0.0" />
<PackageVersion Include="GraphQL.Client" Version="6.0.0" />
<PackageVersion Include="Glob" Version="1.1.9" />
<PackageVersion Include="HttpMultipartParser" Version="9.0.0" />
<PackageVersion Include="ILRepack.FullAuto" Version="1.6.0" />
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" />
<!-- Keep at exactly 7.0.5 for side by side with V2 -->
<PackageVersion Include="Microsoft.Data.Sqlite" Version="[7.0.5,)" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="9.0.4" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="[2.2.0,)" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="[5.0.0,)" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="Open.ChannelExtensions" Version="9.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Newtonsoft.Json.Schema" Version="4.0.1" />
<PackageVersion Include="Open.ChannelExtensions" Version="9.1.0" />
<PackageVersion Include="Polly" Version="7.2.3" />
<PackageVersion Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageVersion Include="Polly.Extensions.Http" Version="3.0.0" />
<PackageVersion Include="RichardSzalay.MockHttp" Version="7.0.0" />
<PackageVersion Include="Speckle.Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="Speckle.DoubleNumerics" Version="4.1.0" />
<PackageVersion Include="SimpleExec" Version="12.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="9.0.2" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.1" />
<PackageVersion Include="Verify.Quibble" Version="2.1.1" />
<PackageVersion Include="Verify.Xunit" Version="28.10.1" />
<PackageVersion Include="Verify.Xunit" Version="29.4.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.assert" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2" />
-6
View File
@@ -1,6 +0,0 @@
workflow: GitFlow/v1
next-version: 3.0.0
branches:
main:
prevent-increment:
when-current-commit-tagged: true
+26 -12
View File
@@ -8,18 +8,22 @@ Speckle | Sharp | SDK
### .NET SDK, Tests, and Objects
[![Codecov](https://codecov.io/gh/specklesystems/speckle-sharp-sdk/graph/badge.svg?token=TTM5OGr38m)](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
[![codecov](https://codecov.io/gh/specklesystems/speckle-sharp-sdk/branch/dev/graph/badge.svg?token=TTM5OGr38m)](https://codecov.io/gh/specklesystems/speckle-sharp-sdk)
<a href="https://www.nuget.org/packages/Speckle.Sdk/"><img alt="NuGet Version" src="https://img.shields.io/nuget/v/Speckle.Sdk?label=Speckle.Sdk"></a>
<a href="https://www.nuget.org/packages/Speckle.Objects/"><img alt="NuGet Version" src="https://img.shields.io/nuget/v/Speckle.Sdk?label=Speckle.Objects"></a>
<a href="https://www.nuget.org/packages/Speckle.Automate.Sdk/"><img alt="NuGet Version" src="https://img.shields.io/nuget/v/Speckle.Sdk?label=Speckle.Automate.Sdk"></a>
> [!WARNING]
> This is an early beta release, not meant for use in production! We're working to stabilise the 3.0 API, and until then there will be breaking changes. You have been warned!
> Releases Speckle.Sdk and Speckle.Objects are reliable for production use, but the APIs may not be wholly stable, and there may be breaking changes between releases, with little documentation.
# Repo structure
This repo is the home of our next-generation Speckle .NET SDK. It uses .NET Standard 2.0 and has been tested on Windows and MacOS.
- **SDK**
- [`Speckle.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk): Transports, serialization, API wrappers, and logging.
- [`Speckle.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk): Send/Receive operations, Serialization, API wrappers, and more!.
- [`Speckle.Sdk.Dependencies`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Sdk.Dependencies): Dependencies and code that shouldn't cause conflicts in Host Apps. This uses [IL Repack](https://github.com/gluck/il-repack) to merge together and interalized only to be used by Speckle.
- [`Speckle.Automate.Sdk`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Automate.Sdk): .NET SDK for [Speckle Automate](https://www.speckle.systems/product/automate)
- **Speckle Objects**
- [`Speckle.Objects`](https://github.com/specklesystems/speckle-sharp-sdk/tree/dev/src/Speckle.Objects): The Speckle Objects classes used for conversions.
- **Tests**
@@ -27,11 +31,13 @@ This repo is the home of our next-generation Speckle .NET SDK. It uses .NET Stan
### Other repos
Make sure to also check and ⭐️ these other Speckle next generation repositories:
Make sure to also check and ⭐️ these other repositories:
- [`speckle-sharp-connectors`](https://github.com/specklesystems/speckle-sharp-connectors): our csharp repo of next gen connectors
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-sketchup): Sketchup connector
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector
- [`speckle-sharp-connectors`](https://github.com/specklesystems/speckle-sharp-connectors): our csharp repo of next gen connectors.
- [`speckle-server`](https://github.com/specklesystems/speckle-server): the speckle server.
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-blender): Blender connector.
- [`speckle-sketchup`](https://github.com/specklesystems/speckle-sketchup): Sketchup connector.
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector.
- and more [connectors & tooling](https://github.com/specklesystems/)!
## Documentation
@@ -44,19 +50,27 @@ Comprehensive developer and user documentation can be found in our:
### Building
Make sure you clone this repository together with its submodules: `git clone https://github.com/specklesystems/speckle-sharp-sdk.git -recursive`.
Afterwards, just restore all the NuGet packages and hit Build!
Ensure you're using a [8.0.4xx](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) .NET SDK.
After cloning this repository, just restore all the NuGet packages and hit Build!
### Developing
This project is evolving fast, to better understand how to use Core we suggest checking out the Unit and Integration tests. Running the integration tests locally requires a local server running on your computer.
It is highly recommended you use
- Either Jetbrains Rider or Visual Studio 2022
- Ensure your IDE is set to use [the correct .NET SDK version](https://github.com/specklesystems/speckle-sharp-sdk/blob/main/global.json) (newer major versions may work, but may incorrectly run analysers we haven't configured)
- You should install the cshapier plugin ([Rider](https://plugins.jetbrains.com/plugin/18243-csharpier), [VS](https://marketplace.visualstudio.com/items?itemName=csharpier.CSharpier)) and configure it to run on save
We'll be also adding [preliminary documentation on our forum](https://discourse.speckle.works/c/speckle-insider/10).
Docs are a bit patchy [https://docs.speckle.systems/developers/looking-for-developer-docs](https://docs.speckle.systems/developers/looking-for-developer-docs)
### Tests
There are two test projects, one for unit tests and one for integration tests. The latter needs a server running locally in order to run.
There are several test projects. It is a requirement that all tests pass for PRs to be merged.
The Integration test projects require a local server to be running.
You must have docker installed. Then you can run `docker compose up` from the root of the repo to start the required containers.
In CI, they will be run against both the public and private versions of the server.
It is important that we remain compatible with both server versions.
## Contributing
Before embarking on submitting a patch, please make sure you read:
+19 -1
View File
@@ -9,6 +9,9 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Objects.Tests.Unit", "tests\Speckle.Objects.Tests.Unit\Speckle.Objects.Tests.Unit.csproj", "{A0338FC0-3011-498F-AD09-01230FABD3ED}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CB96C27-FC5B-4A41-86B6-951AF99B8116}"
ProjectSection(SolutionItems) = preProject
src\graphql.config.yml = src\graphql.config.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35047EE7-AD1D-4741-80A7-8F0E874718E9}"
EndProject
@@ -20,11 +23,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "config", "config", "{DA2AED
Directory.Packages.props = Directory.Packages.props
global.json = global.json
README.md = README.md
GitVersion.yml = GitVersion.yml
docker-compose.yml = docker-compose.yml
CodeMetricsConfig.txt = CodeMetricsConfig.txt
Directory.Build.Targets = Directory.Build.Targets
.config\dotnet-tools.json = .config\dotnet-tools.json
docker-compose-internal.yml = docker-compose-internal.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{58D37DA9-F948-48CA-9A73-F5BBBD533DBF}"
@@ -39,6 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
ProjectSection(SolutionItems) = preProject
.github\workflows\pr.yml = .github\workflows\pr.yml
.github\workflows\release.yml = .github\workflows\release.yml
.github\workflows\integration-test.yml = .github\workflows\integration-test.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Tests.Performance", "tests\Speckle.Sdk.Tests.Performance\Speckle.Sdk.Tests.Performance.csproj", "{870E3396-E6F7-43AE-B120-E651FA4F46BD}"
@@ -51,6 +55,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Sdk.Testing", "test
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "performance", "performance", "{FFB07238-87E8-463A-AA39-3B38AAAA94C1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Automate.Sdk", "src\Speckle.Automate.Sdk\Speckle.Automate.Sdk.csproj", "{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Speckle.Automate.Sdk.Integration", "tests\Speckle.Automate.Sdk.Integration\Speckle.Automate.Sdk.Integration.csproj", "{B6129DC3-F285-4E5F-85E2-6D2533A4005E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -105,6 +113,14 @@ Global
{7B617C0D-2354-415C-993C-5071D4113E27}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B617C0D-2354-415C-993C-5071D4113E27}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B617C0D-2354-415C-993C-5071D4113E27}.Release|Any CPU.Build.0 = Release|Any CPU
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5}.Release|Any CPU.Build.0 = Release|Any CPU
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B6129DC3-F285-4E5F-85E2-6D2533A4005E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{A413E196-3696-4F48-B635-04B5F76BF9C9} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
@@ -119,5 +135,7 @@ Global
{7B617C0D-2354-415C-993C-5071D4113E27} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
{FF922B6D-D416-4348-8CB8-0C8B28691070} = {FFB07238-87E8-463A-AA39-3B38AAAA94C1}
{870E3396-E6F7-43AE-B120-E651FA4F46BD} = {FFB07238-87E8-463A-AA39-3B38AAAA94C1}
{4EB20EFA-5A38-415E-B3FD-29CA3ACD1EF5} = {5CB96C27-FC5B-4A41-86B6-951AF99B8116}
{B6129DC3-F285-4E5F-85E2-6D2533A4005E} = {35047EE7-AD1D-4741-80A7-8F0E874718E9}
EndGlobalSection
EndGlobal
+48
View File
@@ -0,0 +1,48 @@
<Solution>
<Folder Name="/build/">
<Project Path="build/build.csproj" />
</Folder>
<Folder Name="/config/">
<File Path=".config/dotnet-tools.json" />
<File Path=".csharpierrc.yaml" />
<File Path=".editorconfig" />
<File Path="CodeMetricsConfig.txt" />
<File Path="Directory.Build.props" />
<File Path="Directory.Build.Targets" />
<File Path="Directory.Packages.props" />
<File Path="docker-compose-internal.yml" />
<File Path="docker-compose.yml" />
<File Path="global.json" />
<File Path="README.md" />
<File Path=".github\copilot-instructions.md" />
<File Path=".github\git-commit-instructions.md" />
</Folder>
<Folder Name="/config/workflows/">
<File Path=".github/workflows/integration-test-callable-from-server-repo.yml" />
<File Path=".github/workflows/integration-test.yml" />
<File Path=".github/workflows/pr.yml" />
<File Path=".github/workflows/release.yml" />
</Folder>
<Folder Name="/performance/">
<Project Path="tests/Speckle.Sdk.Serialization.Testing/Speckle.Sdk.Serialization.Testing.csproj" />
<Project Path="tests/Speckle.Sdk.Tests.Performance/Speckle.Sdk.Tests.Performance.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Speckle.Automate.Sdk/Speckle.Automate.Sdk.csproj" />
<Project Path="src/Speckle.Objects/Speckle.Objects.csproj" />
<Project Path="src/Speckle.Sdk.Dependencies/Speckle.Sdk.Dependencies.csproj" />
<Project Path="src/Speckle.Sdk/Speckle.Sdk.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Speckle.Sdk.Testing/Speckle.Sdk.Testing.csproj" />
</Folder>
<Folder Name="/tests/integration/">
<Project Path="tests/Speckle.Automate.Sdk.Integration/Speckle.Automate.Sdk.Integration.csproj" />
<Project Path="tests/Speckle.Sdk.Tests.Integration/Speckle.Sdk.Tests.Integration.csproj" />
</Folder>
<Folder Name="/tests/unit/">
<Project Path="tests/Speckle.Objects.Tests.Unit/Speckle.Objects.Tests.Unit.csproj" />
<Project Path="tests/Speckle.Sdk.Serialization.Tests/Speckle.Sdk.Serialization.Tests.csproj" />
<Project Path="tests/Speckle.Sdk.Tests.Unit/Speckle.Sdk.Tests.Unit.csproj" />
</Folder>
</Solution>
+16 -18
View File
@@ -1,4 +1,3 @@
using System.Text.Json;
using GlobExpressions;
using static Bullseye.Targets;
using static SimpleExec.Command;
@@ -16,14 +15,13 @@ const string CLEAN_LOCKS = "clean-locks";
const string PERF = "perf";
const string DEEP_CLEAN = "deep-clean";
static async Task<(string, string)> GetVersions()
static (string semver, string fileVerison) GetVersions()
{
var (output, _) = await ReadAsync("dotnet", "dotnet-gitversion /output json").ConfigureAwait(false);
output = output.Trim();
var jDoc = JsonDocument.Parse(output);
var version = jDoc.RootElement.GetProperty("FullSemVer").GetString() ?? "3.0.0-localBuild";
var fileVersion = jDoc.RootElement.GetProperty("AssemblySemFileVer").GetString() ?? "3.0.0.0";
return (version, fileVersion);
string semver =
Environment.GetEnvironmentVariable("SEMVER") ?? throw new ArgumentException("Expected SEMVER env var");
string fileVersion =
Environment.GetEnvironmentVariable("FILE_VERSION") ?? throw new ArgumentException("Expected FILE_VERSION env var");
return (semver, fileVersion);
}
Target(
@@ -42,7 +40,7 @@ Target(
Target(
CLEAN,
ForEach("**/output"),
forEach: ["**/output"],
dir =>
{
IEnumerable<string> GetDirectories(string d)
@@ -68,16 +66,16 @@ Target(
Target(RESTORE_TOOLS, () => RunAsync("dotnet", "tool restore"));
Target(FORMAT, DependsOn(RESTORE_TOOLS), () => RunAsync("dotnet", "csharpier --check ."));
Target(FORMAT, dependsOn: [RESTORE_TOOLS], () => RunAsync("dotnet", "csharpier check ."));
Target(RESTORE, DependsOn(FORMAT), () => RunAsync("dotnet", "restore Speckle.Sdk.sln --locked-mode"));
Target(RESTORE, dependsOn: [FORMAT], () => RunAsync("dotnet", "restore Speckle.Sdk.sln --locked-mode"));
Target(
BUILD,
DependsOn(RESTORE),
dependsOn: [RESTORE],
async () =>
{
var (version, fileVersion) = await GetVersions().ConfigureAwait(false);
var (version, fileVersion) = GetVersions();
Console.WriteLine($"Version: {version} & {fileVersion}");
await RunAsync(
"dotnet",
@@ -89,7 +87,7 @@ Target(
Target(
TEST,
DependsOn(BUILD),
dependsOn: [BUILD],
Glob.Files(".", "**/*.Tests.Unit.csproj").Concat(Glob.Files(".", "**/*.Tests.csproj")),
async file =>
{
@@ -103,7 +101,7 @@ Target(
Target(
INTEGRATION,
DependsOn(BUILD),
dependsOn: [BUILD],
async () =>
{
await RunAsync("docker", "compose -f docker-compose.yml up --wait").ConfigureAwait(false);
@@ -170,11 +168,11 @@ Target(
Target(
PACK,
DependsOn(BUILD),
dependsOn: [TEST],
async () =>
{
{
var (version, fileVersion) = await GetVersions().ConfigureAwait(false);
var (version, fileVersion) = GetVersions();
Console.WriteLine($"Version: {version} & {fileVersion}");
await RunAsync("dotnet", $"pack Speckle.Sdk.sln -c Release -o output --no-build -p:Version={version}")
.ConfigureAwait(false);
@@ -182,6 +180,6 @@ Target(
}
);
Target("default", DependsOn(FORMAT, TEST, INTEGRATION), () => Console.WriteLine("Done!"));
Target("default", dependsOn: [FORMAT, TEST, INTEGRATION], () => Console.WriteLine("Done!"));
await RunTargetsAndExitAsync(args).ConfigureAwait(true);
+1 -2
View File
@@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ItemGroup>
<PackageReference Include="Bullseye" />
<PackageReference Include="Glob" />
<PackageReference Include="SimpleExec" />
+3 -3
View File
@@ -4,9 +4,9 @@
"net8.0": {
"Bullseye": {
"type": "Direct",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "bqyt+m17ym+5aN45C5oZRAjuLDt8jKiCm/ys1XfymIXSkrTFwvI/QsbY3ucPSHDz7SF7uON7B57kXFv5H2k1ew=="
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "vgwwXfzs7jJrskWH7saHRMgPzziq/e86QZNWY1MnMxd7e+De7E7EX4K3C7yrvaK9y02SJoLxNxcLG/q5qUAghw=="
},
"Glob": {
"type": "Direct",
+115
View File
@@ -0,0 +1,115 @@
name: "speckle-server"
services:
####
# Speckle Server dependencies
#######
postgres:
image: "postgres:16.4-alpine3.20@sha256:d898b0b78a2627cb4ee63464a14efc9d296884f1b28c841b0ab7d7c42f1fffdf"
restart: always
environment:
POSTGRES_DB: speckle
POSTGRES_USER: speckle
POSTGRES_PASSWORD: speckle
volumes:
- ./.volumes/postgres-data:/var/lib/postgresql/data/
healthcheck:
# the -U user has to match the POSTGRES_USER value
test: ["CMD-SHELL", "pg_isready -U speckle"]
interval: 5s
timeout: 5s
retries: 30
redis:
image: "valkey/valkey:8.1-alpine@sha256:0d27f0bca0249f61d060029a6aaf2e16b2c417d68d02a508e1dfb763fa2948b4"
restart: always
volumes:
- ./.volumes/redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 5s
timeout: 5s
retries: 30
minio:
image: "minio/minio:RELEASE.2023-10-25T06-33-25Z"
command: server /data --console-address ":9001"
restart: always
volumes:
- ./.volumes/minio-data:/data
ports:
- '127.0.0.1:9000:9000'
- '127.0.0.1:9001:9001'
healthcheck:
test:
[
"CMD-SHELL",
"curl -s -o /dev/null http://127.0.0.1:9000/minio/index.html",
]
interval: 5s
timeout: 30s
retries: 30
start_period: 10s
speckle-server:
image: ${SPECKLE_SERVER_IMAGE:-ghcr.io/specklesystems/speckle-server:latest}
restart: always
healthcheck:
test:
- CMD
- /nodejs/bin/node
- -e
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(Number(res.statusCode != 200 || body.toLowerCase().includes('error')));}); }).end(); } catch { process.exit(1); }"
interval: 10s
timeout: 10s
retries: 3
start_period: 90s
ports:
- "0.0.0.0:3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
minio:
condition: service_healthy
environment:
# TODO: Change this to the URL of the speckle server, as accessed from the network
CANONICAL_URL: "http://127.0.0.1:8080"
SPECKLE_AUTOMATE_URL: "http://127.0.0.1:3030"
FRONTEND_ORIGIN: "http://127.0.0.1:8081"
# TODO: Change thvolumes:
REDIS_URL: "redis://redis"
S3_ENDPOINT: "http://minio:9000"
S3_PUBLIC_ENDPOINT: "http://127.0.0.1:9000"
S3_ACCESS_KEY: "minioadmin"
S3_SECRET_KEY: "minioadmin"
S3_BUCKET: "speckle-server"
S3_CREATE_BUCKET: "true"
FILE_SIZE_LIMIT_MB: 100
MAX_PROJECT_MODELS_PER_PAGE: 500
# TODO: Change this to a unique secret for this server
SESSION_SECRET: "TODO:ReplaceWithLongString"
STRATEGY_LOCAL: "true"
POSTGRES_URL: 'postgres://speckle:speckle@postgres:5432/speckle'
ENABLE_MP: "false"
LOG_PRETTY: "true"
FF_NEXT_GEN_FILE_IMPORTER_ENABLED: "true"
FF_LARGE_FILE_IMPORTS_ENABLED: "true"
networks:
default:
name: speckle-server
volumes:
postgres-data:
redis-data:
minio-data:
+15 -8
View File
@@ -1,4 +1,3 @@
version: "3.9"
name: "speckle-server"
services:
@@ -13,7 +12,7 @@ services:
POSTGRES_USER: speckle
POSTGRES_PASSWORD: speckle
volumes:
- postgres-data:/var/lib/postgresql/data/
- ./.volumes/postgres-data:/var/lib/postgresql/data/
healthcheck:
# the -U user has to match the POSTGRES_USER value
test: ["CMD-SHELL", "pg_isready -U speckle"]
@@ -22,10 +21,10 @@ services:
retries: 30
redis:
image: "redis:6.0-alpine"
image: "valkey/valkey:8.1-alpine@sha256:0d27f0bca0249f61d060029a6aaf2e16b2c417d68d02a508e1dfb763fa2948b4"
restart: always
volumes:
- redis-data:/data
- ./.volumes/redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 5s
@@ -37,7 +36,10 @@ services:
command: server /data --console-address ":9001"
restart: always
volumes:
- minio-data:/data
- ./.volumes/minio-data:/data
ports:
- '127.0.0.1:9000:9000'
- '127.0.0.1:9001:9001'
healthcheck:
test:
[
@@ -57,7 +59,7 @@ services:
- CMD
- /nodejs/bin/node
- -e
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(res.statusCode != 200 || body.toLowerCase().includes('error'));}); }).end(); } catch { process.exit(1); }"
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(Number(res.statusCode != 200 || body.toLowerCase().includes('error')));}); }).end(); } catch { process.exit(1); }"
interval: 10s
timeout: 10s
retries: 3
@@ -81,6 +83,7 @@ services:
REDIS_URL: "redis://redis"
S3_ENDPOINT: "http://minio:9000"
S3_PUBLIC_ENDPOINT: "http://127.0.0.1:9000"
S3_ACCESS_KEY: "minioadmin"
S3_SECRET_KEY: "minioadmin"
S3_BUCKET: "speckle-server"
@@ -93,7 +96,6 @@ services:
SESSION_SECRET: "TODO:ReplaceWithLongString"
STRATEGY_LOCAL: "true"
DEBUG: "speckle:*"
POSTGRES_URL: "postgres"
POSTGRES_USER: "speckle"
@@ -101,6 +103,11 @@ services:
POSTGRES_DB: "speckle"
ENABLE_MP: "false"
LOG_PRETTY: "true"
FF_NEXT_GEN_FILE_IMPORTER_ENABLED: "true"
FF_LARGE_FILE_IMPORTS_ENABLED: "true"
networks:
default:
name: speckle-server
@@ -108,4 +115,4 @@ networks:
volumes:
postgres-data:
redis-data:
minio-data:
minio-data:
@@ -0,0 +1,61 @@
using System.Diagnostics;
using System.Text.Json;
using GraphQL;
using GraphQL.Client.Http;
using Speckle.Automate.Sdk.Schema;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Api;
using Speckle.Sdk.Credentials;
namespace Speckle.Automate.Sdk;
[GenerateAutoInterface(VisibilityModifier = "public")]
internal sealed class AutomationContextFactory(
IClientFactory clientFactory,
IAccountFactory accountFactory,
IOperations operations
) : IAutomationContextFactory
{
private static readonly JsonSerializerOptions s_jsonSerializerSettings = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <inheritdoc cref="Initialize(AutomationRunData, string)"/>
public async Task<IAutomationContext> Initialize(string automationRunData, string speckleToken)
{
var runData = JsonSerializer.Deserialize<AutomationRunData>(automationRunData, s_jsonSerializerSettings);
return await Initialize(runData, speckleToken).ConfigureAwait(false);
}
/// <inheritdoc cref="Initialize(AutomationRunData, Account)"/>
/// <exception cref="GraphQLHttpRequestException">Request failed on the HTTP layer (received a non-successful response code)</exception>
/// <exception cref="AggregateException"><inheritdoc cref="Speckle.Sdk.Api.GraphQL.GraphQLErrorHandler.EnsureGraphQLSuccess(IGraphQLResponse)"/></exception>
public async Task<IAutomationContext> Initialize(AutomationRunData automationRunData, string speckleToken)
{
Account account = await accountFactory
.CreateAccount(automationRunData.SpeckleServerUrl, speckleToken)
.ConfigureAwait(false);
return Initialize(automationRunData, account);
}
/// <summary>
/// Creates an <see cref="AutomationContext"/> from the provided data
/// </summary>
public IAutomationContext Initialize(AutomationRunData automationRunData, Account account)
{
IClient client = clientFactory.Create(account);
Stopwatch initTime = Stopwatch.StartNew();
return new AutomationContext(operations)
{
AutomationRunData = automationRunData,
SpeckleClient = client,
_speckleToken = account.token,
_initTime = initTime,
AutomationResult = new AutomationResult(),
};
}
}
@@ -0,0 +1,379 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using GraphQL;
using Speckle.Automate.Sdk.Schema;
using Speckle.Automate.Sdk.Schema.Triggers;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Inputs;
using Speckle.Sdk.Api.GraphQL.Models;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
namespace Speckle.Automate.Sdk;
[GenerateAutoInterface(VisibilityModifier = "public")]
internal sealed class AutomationContext(IOperations operations) : IAutomationContext
{
public AutomationRunData AutomationRunData { get; set; }
public string? ContextView
{
get => AutomationResult.ResultView;
private set => AutomationResult.ResultView = value;
}
public required IClient SpeckleClient { get; init; }
public required string _speckleToken { get; init; }
// added for performance measuring
public required Stopwatch _initTime { get; init; }
public required AutomationResult AutomationResult { get; init; }
public string RunStatus => AutomationResult.RunStatus;
public string? StatusMessage => AutomationResult.StatusMessage;
public TimeSpan Elapsed => _initTime.Elapsed;
/// <summary>
/// Receive version for automation.
/// </summary>
/// <returns> Commit object. </returns>
/// <exception cref="SpeckleException">Throws if commit object is null.</exception>
public async Task<Base> ReceiveVersion(CancellationToken cancellationToken = default)
{
// TODO: this is a quick hack to keep implementation consistency. Move to proper receive many versions
if (AutomationRunData.Triggers.First() is not VersionCreationTrigger trigger)
{
throw new SpeckleException("Processed automation run data without any triggers");
}
var versionId = trigger.Payload.VersionId;
var version = await SpeckleClient
.Version.Get(versionId, AutomationRunData.ProjectId, cancellationToken)
.ConfigureAwait(false);
if (version.referencedObject == null)
{
throw new SpeckleException(
"The requested speckle model version has exceeded workspace version history limits or the reference object is otherwise null"
);
}
Base rootObject = await operations
.Receive2(
SpeckleClient.ServerUrl,
AutomationRunData.ProjectId,
version.referencedObject,
SpeckleClient.Account.token,
null,
cancellationToken
)
.ConfigureAwait(false);
await SpeckleClient
.Version.Received(new(version.id, AutomationRunData.ProjectId, "automate_function"), cancellationToken)
.ConfigureAwait(false);
Console.WriteLine($"It took {Elapsed.TotalSeconds} seconds to receive the speckle version {versionId}");
return rootObject;
}
/// <summary>
/// Creates new version in the project.
/// </summary>
/// <param name="rootObject">Object to send to project.</param>
/// <param name="model">The model to create the version under</param>
/// <param name="versionMessage">Version message.</param>
/// <param name="cancellationToken">Version message.</param>
/// <returns>Version id.</returns>
/// <exception cref="SpeckleException"> Throws if given model name is as same as with model name in automation run data.
/// The reason is to prevent circular run loop in automation.</exception>
public async Task<Version> CreateNewVersionInProject(
Base rootObject,
Model model,
string versionMessage = "",
CancellationToken cancellationToken = default
)
{
// Confirm target branch is not the same as source branch
foreach (var trigger in AutomationRunData.Triggers)
{
if (trigger.Payload.ModelId == model.id)
{
throw new SpeckleException(
$"""
The target model: {model.name} ({model.id}) cannot match the model
that triggered this automation:
{trigger.Payload.ModelId}
"""
);
}
}
var (rootObjectId, _) = await operations
.Send2(
SpeckleClient.ServerUrl,
AutomationRunData.ProjectId,
SpeckleClient.Account.token,
rootObject,
null,
cancellationToken
)
.ConfigureAwait(false);
var newVersion = await SpeckleClient
.Version.Create(
new CreateVersionInput(rootObjectId, model.id, AutomationRunData.ProjectId, versionMessage),
cancellationToken
)
.ConfigureAwait(false);
AutomationResult.ResultVersions.Add(newVersion.id);
return newVersion;
}
/// <summary>
/// Set context view for automation result view.
/// </summary>
/// <param name="resourceIds"> Resource contexts to bind into view.</param>
/// <param name="includeSourceModelVersion"> Whether bind source version into result view or not.</param>
/// <exception cref="SpeckleException"> Throws if there is no context to create result view.</exception>
[MemberNotNull(nameof(ContextView))]
[AutoInterfaceIgnore] //Ignore so we can explicitly add the MemberNotNull attibute to the interface method
public void SetContextView(IReadOnlyCollection<string>? resourceIds = null, bool includeSourceModelVersion = true)
{
List<string> linkResources = new();
if (includeSourceModelVersion)
{
foreach (var trigger in AutomationRunData.Triggers)
{
switch (trigger)
{
case VersionCreationTrigger versionCreationTrigger:
{
linkResources.Add($"{versionCreationTrigger.Payload.ModelId}@{versionCreationTrigger.Payload.VersionId}");
break;
}
default:
{
throw new SpeckleException($"Could not link resource specified by {trigger.TriggerType} trigger");
}
}
}
}
if (resourceIds is not null)
{
linkResources.AddRange(resourceIds);
}
if (linkResources.Count == 0)
{
throw new SpeckleException("We do not have enough resource ids to compose a context view");
}
ContextView = $"/projects/{AutomationRunData.ProjectId}/models/{string.Join(",", linkResources)}";
}
public async Task ReportRunStatus()
{
ObjectResults? objectResults = null;
if (RunStatus is "SUCCEEDED" or "FAILED")
{
objectResults = new ObjectResults
{
Version = 2,
Values = new ObjectResultValues
{
BlobIds = AutomationResult.Blobs,
ObjectResults = AutomationResult.ObjectResults,
},
};
}
//language=graphql
const string QUERY = """
mutation AutomateFunctionRunStatusReport($projectId: String!, $functionRunId: String!, $status: AutomateRunStatus!, $statusMessage: String, $results: JSONObject, $contextView: String) {
automateFunctionRunStatusReport(
input: {projectId: $projectId, functionRunId: $functionRunId, status: $status, statusMessage: $statusMessage, contextView: $contextView, results: $results}
)
}
""";
GraphQLRequest request = new()
{
Query = QUERY,
Variables = new
{
projectId = AutomationRunData.ProjectId,
functionRunId = AutomationRunData.FunctionRunId,
status = RunStatus,
statusMessage = AutomationResult.StatusMessage,
contextView = ContextView,
results = objectResults,
},
};
await SpeckleClient.ExecuteGraphQLRequest<Dictionary<string, object>>(request).ConfigureAwait(false);
}
/// <summary>
/// Stores result file in automation result. It will be available to download on Frontend if added.
/// </summary>
/// <param name="filePath"> File path to store.</param>
/// <exception cref="FileNotFoundException"> Throws if given file path is not exist.</exception>
/// <exception cref="SpeckleException"> Throws if upload requests return no result.</exception>
public async Task StoreFileResult(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException("The given file path doesn't exist", fileName: filePath);
}
using MultipartFormDataContent formData = new();
FileStream fileStream = new(filePath, FileMode.Open, FileAccess.Read);
using StreamContent streamContent = new(fileStream);
formData.Add(streamContent, "files", Path.GetFileName(filePath));
HttpResponseMessage request = await SpeckleClient
.GQLClient.HttpClient.PostAsync(
new Uri(AutomationRunData.SpeckleServerUrl, $"api/stream/{AutomationRunData.ProjectId}/blob"),
formData
)
.ConfigureAwait(false);
request.EnsureSuccessStatusCode();
string responseString = await request.Content.ReadAsStringAsync().ConfigureAwait(false);
Console.WriteLine("RESPONSE - " + responseString);
BlobUploadResponse uploadResponse = JsonConvert.DeserializeObject<BlobUploadResponse>(responseString);
if (uploadResponse.UploadResults.Count != 1)
{
throw new SpeckleException("Expected one upload result.");
}
AutomationResult.Blobs.AddRange(uploadResponse.UploadResults.Select(r => r.BlobId));
}
private void MarkRun(AutomationStatus status, string? statusMessage)
{
double duration = Elapsed.TotalSeconds;
AutomationResult.StatusMessage = statusMessage;
string statusValue = AutomationStatusMapping.Get(status);
AutomationResult.RunStatus = statusValue;
AutomationResult.Elapsed = duration;
string msg = $"Automation run {statusValue} after {duration} seconds.";
if (statusMessage is not null)
{
msg += $"\n{statusMessage}";
}
Console.WriteLine(msg);
}
public void MarkRunFailed(string statusMessage) => MarkRun(AutomationStatus.Failed, statusMessage);
public void MarkRunException(string? statusMessage) => MarkRun(AutomationStatus.Exception, statusMessage);
public void MarkRunSuccess(string? statusMessage) => MarkRun(AutomationStatus.Succeeded, statusMessage);
/// <summary>
/// Add a new error case to the run results.
/// </summary>
/// <param name="category">A short tag for the error type.</param>
/// <param name="affectedObjects">A list of objects that are causing the result.</param>
/// <param name="message">Optional error message.</param>
/// <param name="metadata">User provided metadata key value pairs.</param>
/// <param name="visualOverrides">Case specific 3D visual overrides.</param>
/// <exception cref="ArgumentException">Throws if the provided <paramref name="affectedObjects"/> input is empty.</exception>
public void AttachErrorToObjects(
string category,
IReadOnlyCollection<Base> affectedObjects,
string? message = null,
Dictionary<string, object>? metadata = null,
Dictionary<string, object>? visualOverrides = null
) => AttachResultToObjects(ObjectResultLevel.Error, category, affectedObjects, message, metadata, visualOverrides);
/// <summary>
/// Add a new warning case to the run results.
/// </summary>
/// <inheritdoc cref="AttachErrorToObjects"/>
public void AttachWarningToObjects(
string category,
IReadOnlyCollection<Base> affectedObjects,
string? message = null,
Dictionary<string, object>? metadata = null,
Dictionary<string, object>? visualOverrides = null
) => AttachResultToObjects(ObjectResultLevel.Warning, category, affectedObjects, message, metadata, visualOverrides);
/// <summary>
/// Add a new info case to the run results.
/// </summary>
/// <inheritdoc cref="AttachErrorToObjects"/>
public void AttachInfoToObjects(
string category,
IReadOnlyCollection<Base> affectedObjects,
string? message = null,
Dictionary<string, object>? metadata = null,
Dictionary<string, object>? visualOverrides = null
) => AttachResultToObjects(ObjectResultLevel.Info, category, affectedObjects, message, metadata, visualOverrides);
/// <summary>
/// Add a new success case to the run results.
/// </summary>
/// <inheritdoc cref="AttachErrorToObjects"/>
public void AttachSuccessToObjects(
string category,
IReadOnlyCollection<Base> affectedObjects,
string? message = null,
Dictionary<string, object>? metadata = null,
Dictionary<string, object>? visualOverrides = null
) => AttachResultToObjects(ObjectResultLevel.Success, category, affectedObjects, message, metadata, visualOverrides);
/// <summary>
/// Add a new case to the run results.
/// </summary>
/// <param name="level">The level assigned to this result.</param>
/// <inheritdoc cref="AttachErrorToObjects"/>
public void AttachResultToObjects(
ObjectResultLevel level,
string category,
IReadOnlyCollection<Base> affectedObjects,
string? message = null,
Dictionary<string, object>? metadata = null,
Dictionary<string, object>? visualOverrides = null
)
{
if (affectedObjects.Count == 0)
{
throw new ArgumentException($"Need at least one affected object to report a(n) {level}");
}
string levelString = ObjectResultLevelMapping.Get(level);
Dictionary<string, string?> ids = affectedObjects.ToDictionary(
x => x.id.NotNull($"You can only attach {level} results to objects with an id"),
x => x.applicationId
);
Console.WriteLine($"Created new {levelString.ToUpper()} category: {category} caused by: {message}");
ResultCase resultCase = new()
{
Category = category,
Level = levelString,
ObjectAppIds = ids,
Message = message,
Metadata = metadata,
VisualOverrides = visualOverrides,
};
AutomationResult.ObjectResults.Add(resultCase);
}
}
public partial interface IAutomationContext
{
[MemberNotNull(nameof(ContextView))]
public void SetContextView(IReadOnlyCollection<string>? resourceIds = null, bool includeSourceModelVersion = true);
}
@@ -0,0 +1,7 @@
namespace Speckle.Automate.Sdk.DataAnnotations;
/// <summary>
/// If specified, the given function input will be redacted in all contexts.
/// </summary>
[AttributeUsage(AttributeTargets.All)]
public sealed class SecretAttribute : Attribute { }
+195
View File
@@ -0,0 +1,195 @@
using System.CommandLine;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
using Newtonsoft.Json.Serialization;
using Speckle.Automate.Sdk.DataAnnotations;
using Speckle.Automate.Sdk.Schema;
using Speckle.InterfaceGenerator;
using Speckle.Sdk;
namespace Speckle.Automate.Sdk;
/// <summary>
/// Provides mechanisms to execute any function that conforms to the AutomateFunction "interface"
/// </summary>
[GenerateAutoInterface(VisibilityModifier = "public")]
internal class AutomationRunner(IAutomationContextFactory contextFactory) : IAutomationRunner
{
[SuppressMessage("Design", "CA1031:Do not catch general exception types")]
public async Task<IAutomationContext> RunFunction<TInput>(
Func<IAutomationContext, TInput, Task> automateFunction,
AutomationRunData automationRunData,
string speckleToken,
TInput inputs
)
where TInput : struct
{
var automationContext = await contextFactory.Initialize(automationRunData, speckleToken).ConfigureAwait(false);
try
{
await automateFunction.Invoke(automationContext, inputs).ConfigureAwait(false);
if (automationContext.RunStatus is not ("FAILED" or "SUCCEEDED"))
{
automationContext.MarkRunSuccess(
"WARNING: Automate assumed a success status, but it was not marked as so by the function."
);
}
}
catch (Exception ex) when (!ex.IsFatal())
{
Console.WriteLine(ex.ToString());
automationContext.MarkRunException("Function error. Check the automation run logs for details.");
}
finally
{
if (automationContext.ContextView is null)
{
automationContext.SetContextView();
}
await automationContext.ReportRunStatus().ConfigureAwait(false);
}
return automationContext;
}
public async Task<IAutomationContext> RunFunction(
Func<IAutomationContext, Task> automateFunction,
AutomationRunData automationRunData,
string speckleToken
) =>
await RunFunction(
async (context, _) => await automateFunction(context).ConfigureAwait(false),
automationRunData,
speckleToken,
new Fake()
)
.ConfigureAwait(false);
private struct Fake { }
/// <summary>
/// Main entrypoint to execute an Automate function with no input data
/// </summary>
/// <param name="args">The command line arguments passed into the function by automate</param>
/// <param name="automateFunction">The automate function to execute</param>
/// <remarks>This should always be called in your own functions, as it contains the logic to trigger the function automatically.</remarks>
public async Task<int> Main(string[] args, Func<IAutomationContext, Task> automateFunction)
{
return await Main(
args,
async (IAutomationContext context, Fake _) => await automateFunction(context).ConfigureAwait(false)
)
.ConfigureAwait(false);
}
/// <summary>
/// Main entrypoint to execute an Automate function with input data of type <typeparamref name="TInput"/>.
/// </summary>
/// <param name="args">The command line arguments passed into the function by automate</param>
/// <param name="automateFunction">The automate function to execute</param>
/// <typeparam name="TInput">The provided input data</typeparam>
/// <remarks>This should always be called in your own functions, as it contains the logic to trigger the function automatically.</remarks>
public async Task<int> Main<TInput>(string[] args, Func<IAutomationContext, TInput, Task> automateFunction)
where TInput : struct
{
Argument<string> pathArg = new(name: "Input Path", description: "A file path to retrieve function inputs");
RootCommand rootCommand = new();
// a stupid hack to be able to exit with a specific integer exit code
// read more at https://github.com/dotnet/command-line-api/issues/1570
var exitCode = 0;
rootCommand.AddArgument(pathArg);
rootCommand.SetHandler(
async inputPath =>
{
try
{
FunctionRunData<TInput> data = FunctionRunDataParser.FromPath<TInput>(inputPath);
var context = await RunFunction(
automateFunction,
data.AutomationRunData,
data.SpeckleToken,
data.FunctionInputs
)
.ConfigureAwait(false);
if (context.RunStatus is "EXCEPTION")
{
exitCode = 1;
}
}
catch (Exception)
{
exitCode = 1;
throw;
}
},
pathArg
);
Argument<string> schemaFilePathArg = new(
name: "Function inputs file path",
description: "A token to talk to the Speckle server with"
);
Command generateSchemaCommand = new("generate-schema", "Generate JSON schema for the function inputs");
generateSchemaCommand.AddArgument(schemaFilePathArg);
generateSchemaCommand.SetHandler(
schemaFilePath =>
{
try
{
JSchemaGenerator generator = new() { ContractResolver = new CamelCasePropertyNamesContractResolver() };
generator.GenerationProviders.Add(new SpeckleSecretProvider());
JSchema schema = generator.Generate(typeof(TInput));
schema.ToString(SchemaVersion.Draft2019_09);
File.WriteAllText(schemaFilePath, schema.ToString());
}
catch (Exception)
{
exitCode = 1;
throw;
}
},
schemaFilePathArg
);
rootCommand.Add(generateSchemaCommand);
await rootCommand.InvokeAsync(args).ConfigureAwait(false);
// if we've gotten this far, the execution should technically be completed as expected
// thus exiting with 0 is the semantically correct thing to do
return exitCode;
}
}
internal sealed class SpeckleSecretProvider : JSchemaGenerationProvider
{
public override JSchema? GetSchema(JSchemaTypeGenerationContext context)
{
var attributes = context.MemberProperty?.AttributeProvider?.GetAttributes(false) ?? new List<Attribute>();
var isSecretString = attributes.Any(att => att is SecretAttribute);
if (isSecretString)
{
return CreateSchemaWithWriteOnly(context.ObjectType, context.Required);
}
return null;
}
private static JSchema CreateSchemaWithWriteOnly(Type type, Required required)
{
JSchemaGenerator generator = new();
JSchema schema = generator.Generate(type, required != Required.Always);
schema.WriteOnly = true;
return schema;
}
}
@@ -0,0 +1,12 @@
namespace Speckle.Automate.Sdk.Schema;
public class AutomationResult
{
public double Elapsed { get; set; }
public string? ResultView { get; set; }
public List<string> ResultVersions { get; set; } = new();
public List<string> Blobs { get; set; } = new();
public string RunStatus { get; set; } = AutomationStatusMapping.Get(AutomationStatus.Running);
public string? StatusMessage { get; set; }
public List<ResultCase> ObjectResults { get; set; } = new();
}
@@ -0,0 +1,28 @@
using System.Text.Json.Serialization;
using Speckle.Automate.Sdk.Schema.Triggers;
namespace Speckle.Automate.Sdk.Schema;
///<summary>
/// Values of the project, model and automation that triggered this function run.
///</summary>
public readonly struct AutomationRunData
{
[JsonRequired]
public required string ProjectId { get; init; }
[JsonRequired]
public required Uri SpeckleServerUrl { get; init; }
[JsonRequired]
public required string AutomationId { get; init; }
[JsonRequired]
public required string AutomationRunId { get; init; }
[JsonRequired]
public required string FunctionRunId { get; init; }
[JsonRequired]
public required List<VersionCreationTrigger> Triggers { get; init; }
}
@@ -0,0 +1,13 @@
namespace Speckle.Automate.Sdk.Schema;
///<summary>
/// Set the status of the automation.
///</summary>
public enum AutomationStatus
{
Initializing,
Running,
Failed,
Succeeded,
Exception,
}
@@ -0,0 +1,21 @@
namespace Speckle.Automate.Sdk.Schema;
public abstract class AutomationStatusMapping
{
private const string INITIALIZING = "INITIALIZING";
private const string RUNNING = "RUNNING";
private const string FAILED = "FAILED";
private const string SUCCEEDED = "SUCCEEDED";
private const string EXCEPTION = "EXCEPTION";
public static string Get(AutomationStatus status) =>
status switch
{
AutomationStatus.Running => RUNNING,
AutomationStatus.Failed => FAILED,
AutomationStatus.Succeeded => SUCCEEDED,
AutomationStatus.Initializing => INITIALIZING,
AutomationStatus.Exception => EXCEPTION,
_ => throw new ArgumentOutOfRangeException($"Not valid value for enum {status}"),
};
}
@@ -0,0 +1,6 @@
namespace Speckle.Automate.Sdk.Schema;
public readonly struct BlobUploadResponse
{
public required List<UploadResult> UploadResults { get; init; }
}
@@ -0,0 +1,18 @@
using System.Text.Json.Serialization;
namespace Speckle.Automate.Sdk.Schema;
/// <summary>
/// Required data to run a function.
/// </summary>
/// <typeparam name="T"> Type for <see cref="FunctionInputs"/>.</typeparam>
public sealed class FunctionRunData<T>
{
[JsonRequired]
public required string SpeckleToken { get; init; }
[JsonRequired]
public required AutomationRunData AutomationRunData { get; init; }
public required T? FunctionInputs { get; init; }
}
@@ -0,0 +1,52 @@
using System.Text.Json;
namespace Speckle.Automate.Sdk.Schema;
public static class FunctionRunDataParser
{
private static readonly JsonSerializerOptions s_jsonSerializerSettings = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <summary>
/// Function run data parser from json file path./>
/// </summary>
/// <param name="inputLocation"> Path to retrieve function run data.</param>
/// <typeparam name="T"> Type for function inputs.</typeparam>
/// <returns>The data to be able to run function.</returns>
/// <exception cref="JsonException">Json was not valid</exception>
/// <exception cref="FileNotFoundException"> Throws unless file exists.</exception>
public static FunctionRunData<T> FromPath<T>(string inputLocation)
{
string inputJsonString = ReadInputData(inputLocation);
//It's important to use System.Text.Json here. The template FunctionInputs are decorated with STJ attributes
FunctionRunData<T>? functionRunData = JsonSerializer.Deserialize<FunctionRunData<T>>(
inputJsonString,
s_jsonSerializerSettings
);
if (functionRunData is null)
{
throw new JsonException($"Function run data couldn't deserialized at {inputLocation}");
}
return functionRunData;
}
/// <summary>
/// Read text from file.
/// </summary>
/// <param name="inputLocation"> Path to check file is exist.</param>
/// <returns>Text in file.</returns>
/// <exception cref="FileNotFoundException"> Throws unless file exists.</exception>
private static string ReadInputData(string inputLocation)
{
if (!File.Exists(inputLocation))
{
throw new FileNotFoundException($"Cannot find the function inputs file at {inputLocation}");
}
return File.ReadAllText(inputLocation);
}
}
@@ -0,0 +1,9 @@
namespace Speckle.Automate.Sdk.Schema;
public enum ObjectResultLevel
{
Success,
Info,
Warning,
Error,
}
@@ -0,0 +1,19 @@
namespace Speckle.Automate.Sdk.Schema;
public abstract class ObjectResultLevelMapping
{
private const string SUCCESS = "SUCCESS";
private const string INFO = "INFO";
private const string WARNING = "WARNING";
private const string ERROR = "ERROR";
public static string Get(ObjectResultLevel level) =>
level switch
{
ObjectResultLevel.Error => ERROR,
ObjectResultLevel.Warning => WARNING,
ObjectResultLevel.Info => INFO,
ObjectResultLevel.Success => SUCCESS,
_ => throw new ArgumentOutOfRangeException($"Not valid value for enum {level}"),
};
}
@@ -0,0 +1,7 @@
namespace Speckle.Automate.Sdk.Schema;
public readonly struct ObjectResultValues
{
public required List<ResultCase> ObjectResults { get; init; }
public required List<string> BlobIds { get; init; }
}
@@ -0,0 +1,7 @@
namespace Speckle.Automate.Sdk.Schema;
public readonly struct ObjectResults
{
public int Version { get; init; }
public ObjectResultValues Values { get; init; }
}
@@ -0,0 +1,11 @@
namespace Speckle.Automate.Sdk.Schema;
public readonly struct ResultCase
{
public required string Category { get; init; }
public required string Level { get; init; }
public required Dictionary<string, string?> ObjectAppIds { get; init; }
public required string? Message { get; init; }
public required Dictionary<string, object>? Metadata { get; init; }
public required Dictionary<string, object>? VisualOverrides { get; init; }
}
@@ -0,0 +1,6 @@
namespace Speckle.Automate.Sdk.Schema.Triggers;
public abstract class AutomationRunTriggerBase
{
public required string TriggerType { get; init; }
}
@@ -0,0 +1,36 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
namespace Speckle.Automate.Sdk.Schema.Triggers;
/// <summary>
/// Represents a single version creation trigger for the automation run.
/// </summary>
public sealed class VersionCreationTrigger : AutomationRunTriggerBase
{
public const string VERSION_CREATION_TRIGGER_TYPE = "versionCreation";
[JsonRequired]
public required VersionCreationTriggerPayload Payload { get; init; }
public VersionCreationTrigger() { }
[SetsRequiredMembers]
public VersionCreationTrigger(string modelId, string versionId)
{
Payload = new() { ModelId = modelId, VersionId = versionId };
TriggerType = VERSION_CREATION_TRIGGER_TYPE;
}
}
/// <summary>
/// Represents the version creation trigger payload.
/// </summary>
public sealed record VersionCreationTriggerPayload
{
[JsonRequired]
public required string ModelId { get; init; }
[JsonRequired]
public required string VersionId { get; init; }
}
@@ -0,0 +1,8 @@
namespace Speckle.Automate.Sdk.Schema;
public readonly struct UploadResult
{
public required string BlobId { get; init; }
public required string FileName { get; init; }
public required int UploadStatus { get; init; }
}
@@ -0,0 +1,50 @@
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Speckle.Objects.Geometry;
using Speckle.Sdk;
using Speckle.Sdk.Models;
using Speckle.Sdk.Serialisation.V2;
namespace Speckle.Automate.Sdk;
public static class ServiceRegistration
{
/// <summary>
/// Sets-up the serviceCollection with all the services in Speckle.Automate.Sdk and Speckle.Sdk
/// </summary>
/// <param name="serviceCollection"></param>
/// <returns></returns>
public static IServiceCollection AddAutomateSdk(this IServiceCollection serviceCollection)
{
var executingAssembly = Assembly.GetExecutingAssembly().GetName();
var speckleAssembly = typeof(Base).Assembly.GetName();
AddAutomateSdk(
serviceCollection,
new SpeckleSdkOptions(
new(executingAssembly.FullName, "automatefunction"),
executingAssembly.Version?.ToString() ?? "Unknown",
speckleAssembly.Version?.ToString(),
[typeof(Base).Assembly, typeof(Point).Assembly]
)
);
return serviceCollection;
}
public static IServiceCollection AddAutomateSdk(
this IServiceCollection serviceCollection,
SpeckleSdkOptions speckleSdkOptions
)
{
serviceCollection.AddSpeckleSdk(speckleSdkOptions);
//Overwrite the SDK's default IDeserializeProcessFactory to ensure SQLite is not used to cache objects
serviceCollection.AddTransient<IDeserializeProcessFactory, DeserializeProcessFactoryNoCache>();
//Add automate assembly services
serviceCollection.AddTransient<IAutomationContextFactory, AutomationContextFactory>();
serviceCollection.AddTransient<IAutomationRunner, AutomationRunner>();
return serviceCollection;
}
}
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Compiler Properties">
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>Speckle.Automate.Sdk</PackageId>
<Description>Speckle Automate SDK</Description>
<PackageTags>$(PackageTags) speckle automation</PackageTags>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup Label="Expose internals to test projects">
<InternalsVisibleTo Include="Speckle.Automate.Sdk.Tests.Integration" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Newtonsoft.Json.Schema" />
<PackageReference Include="System.CommandLine" NoWarn="NU5104" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\Speckle.Objects\Speckle.Objects.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,79 @@
using Speckle.Newtonsoft.Json;
using Speckle.Sdk;
namespace Speckle.Automate.Sdk.Test;
public class TestAppSettings
{
public string? SpeckleToken { get; set; }
public string? SpeckleServerUrl { get; set; }
public string? SpeckleProjectId { get; set; }
public string? SpeckleAutomationId { get; set; }
}
public static class TestAutomateEnvironment
{
public static TestAppSettings? AppSettings { get; private set; }
private static string GetEnvironmentVariable(string environmentVariableName)
{
var value = TryGetEnvironmentVariable(environmentVariableName);
if (value is null)
{
throw new SpeckleException($"Cannot run tests without a {environmentVariableName} environment variable");
}
return value;
}
private static string? TryGetEnvironmentVariable(string environmentVariableName)
{
return Environment.GetEnvironmentVariable(environmentVariableName);
}
private static TestAppSettings? GetAppSettings()
{
if (AppSettings != null)
{
return AppSettings;
}
var path = "./appsettings.json";
var json = File.ReadAllText(path);
var appSettings = JsonConvert.DeserializeObject<TestAppSettings>(json);
AppSettings = appSettings;
return AppSettings;
}
public static string GetSpeckleToken()
{
return GetAppSettings()?.SpeckleToken ?? GetEnvironmentVariable("SPECKLE_TOKEN");
}
public static Uri GetSpeckleServerUrl()
{
var urlString =
GetAppSettings()?.SpeckleServerUrl ?? TryGetEnvironmentVariable("SPECKLE_SERVER_URL") ?? "http://127.0.0.1:3000";
return new Uri(urlString);
}
public static string GetSpeckleProjectId()
{
return GetAppSettings()?.SpeckleProjectId ?? GetEnvironmentVariable("SPECKLE_PROJECT_ID");
}
public static string GetSpeckleAutomationId()
{
return GetAppSettings()?.SpeckleAutomationId ?? GetEnvironmentVariable("SPECKLE_AUTOMATION_ID");
}
public static void Clear()
{
AppSettings = null;
}
}
@@ -0,0 +1,90 @@
using GraphQL;
using Speckle.Automate.Sdk.Schema;
using Speckle.Automate.Sdk.Schema.Triggers;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Api;
using Speckle.Sdk.Api.GraphQL.Models.Responses;
namespace Speckle.Automate.Sdk.Test;
internal class TestAutomationRun
{
[JsonRequired]
public required string AutomationRunId { get; init; }
[JsonRequired]
public required string FunctionRunId { get; init; }
[JsonRequired]
public required IReadOnlyList<TestAutomationRunTrigger> Triggers { get; init; }
}
internal class TestAutomationRunTrigger : AutomationRunTriggerBase
{
/// <remarks>This should really be a TestAutomationRunTriggerPayload, but right now, they look the samee</remarks>
public required VersionCreationTriggerPayload Payload { get; init; }
}
public static class TestAutomateUtils
{
public static async Task<AutomationRunData> CreateTestRun(
IClient speckleClient,
CancellationToken cancellationToken = default
)
{
//language=graphql
const string QUERY = """
mutation Mutation($projectId: ID!, $automationId: ID!) {
data:projectMutations {
data:automationMutations(projectId: $projectId) {
data:createTestAutomationRun(automationId: $automationId) {
automationRunId
functionRunId
triggers {
payload {
modelId
versionId
}
triggerType
}
}
}
}
}
""";
GraphQLRequest request = new(
query: QUERY,
variables: new
{
automationId = TestAutomateEnvironment.GetSpeckleAutomationId(),
projectId = TestAutomateEnvironment.GetSpeckleProjectId(),
}
);
var res = await speckleClient
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<TestAutomationRun>>>>(
request,
cancellationToken
)
.ConfigureAwait(false);
var runData = res.data.data.data;
var triggerData = runData.Triggers[0].Payload;
string modelId = triggerData.ModelId;
string versionId = triggerData.VersionId;
var data = new AutomationRunData()
{
ProjectId = TestAutomateEnvironment.GetSpeckleProjectId(),
SpeckleServerUrl = TestAutomateEnvironment.GetSpeckleServerUrl(),
AutomationId = TestAutomateEnvironment.GetSpeckleAutomationId(),
AutomationRunId = runData.AutomationRunId,
FunctionRunId = runData.FunctionRunId,
Triggers = [new(modelId: modelId, versionId: versionId)],
};
return data;
}
}
+634
View File
@@ -0,0 +1,634 @@
{
"version": 2,
"dependencies": {
".NETStandard,Version=v2.0": {
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"NETStandard.Library": {
"type": "Direct",
"requested": "[2.0.3, )",
"resolved": "2.0.3",
"contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"Newtonsoft.Json.Schema": {
"type": "Direct",
"requested": "[4.0.1, )",
"resolved": "4.0.1",
"contentHash": "rbHUKp5WTIbqmLEeJ21nTTDGcfR0LA7bVMzm0bYc3yx6NFKiCIHzzvYbwA4Sqgs7+wNldc5nBlkbithWj8IZig==",
"dependencies": {
"Newtonsoft.Json": "13.0.3"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.15.0, )",
"resolved": "1.15.0",
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"System.CommandLine": {
"type": "Direct",
"requested": "[2.0.0-beta4.22272.1, )",
"resolved": "2.0.0-beta4.22272.1",
"contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg==",
"dependencies": {
"System.Memory": "4.5.4"
}
},
"System.Text.Json": {
"type": "Direct",
"requested": "[8.0.5, )",
"resolved": "8.0.5",
"contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "8.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0",
"System.Text.Encodings.Web": "8.0.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.NETCore.Platforms": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
},
"Microsoft.NETCore.Targets": {
"type": "Transitive",
"resolved": "1.1.0",
"contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.Buffers": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg=="
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.5",
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.4.0",
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"System.Numerics.Vectors": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
"dependencies": {
"System.Runtime.InteropServices.WindowsRuntime": "4.3.0",
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"System.Runtime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
"resolved": "4.3.0",
"contentHash": "J4GUi3xZQLUBasNwZnjrffN8i5wpHrBtZoLG+OhRyGo/+YunMRWWtwoMDlUAIdmX0uRfpHIBDSV6zyr3yf00TA==",
"dependencies": {
"System.Runtime": "4.3.0"
}
},
"System.Text.Encodings.Web": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==",
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
}
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"speckle.objects": {
"type": "Project",
"dependencies": {
"Speckle.Sdk": "[1.0.0, )"
}
},
"speckle.sdk": {
"type": "Project",
"dependencies": {
"GraphQL.Client": "[6.0.0, )",
"Microsoft.CSharp": "[4.7.0, )",
"Microsoft.Data.Sqlite": "[7.0.5, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
}
},
"speckle.sdk.dependencies": {
"type": "Project"
},
"GraphQL.Client": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"Microsoft.CSharp": {
"type": "CentralTransitive",
"requested": "[4.7.0, )",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "CentralTransitive",
"requested": "[7.0.5, )",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Newtonsoft.Json": {
"type": "CentralTransitive",
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "10.0.1",
"System.Threading.Tasks.Extensions": "4.6.3"
}
}
},
"net8.0": {
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
"requested": "[8.0.0, )",
"resolved": "8.0.0",
"contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==",
"dependencies": {
"Microsoft.Build.Tasks.Git": "8.0.0",
"Microsoft.SourceLink.Common": "8.0.0"
}
},
"Newtonsoft.Json.Schema": {
"type": "Direct",
"requested": "[4.0.1, )",
"resolved": "4.0.1",
"contentHash": "rbHUKp5WTIbqmLEeJ21nTTDGcfR0LA7bVMzm0bYc3yx6NFKiCIHzzvYbwA4Sqgs7+wNldc5nBlkbithWj8IZig==",
"dependencies": {
"Newtonsoft.Json": "13.0.3"
}
},
"PolySharp": {
"type": "Direct",
"requested": "[1.15.0, )",
"resolved": "1.15.0",
"contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g=="
},
"Speckle.InterfaceGenerator": {
"type": "Direct",
"requested": "[0.9.6, )",
"resolved": "0.9.6",
"contentHash": "HKH7tYrYYlCK1ct483hgxERAdVdMtl7gUKW9ijWXxA1UsYR4Z+TrRHYmzZ9qmpu1NnTycSrp005NYM78GDKV1w=="
},
"System.CommandLine": {
"type": "Direct",
"requested": "[2.0.0-beta4.22272.1, )",
"resolved": "2.0.0-beta4.22272.1",
"contentHash": "1uqED/q2H0kKoLJ4+hI2iPSBSEdTuhfCYADeJrAqERmiGQ2NNacYKRNEQ+gFbU4glgVyK8rxI+ZOe1onEtr/Pg=="
},
"GraphQL.Client.Abstractions": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"dependencies": {
"GraphQL.Primitives": "6.0.0"
}
},
"GraphQL.Client.Abstractions.Websocket": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0"
}
},
"GraphQL.Primitives": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ=="
},
"Microsoft.Data.Sqlite.Core": {
"type": "Transitive",
"resolved": "7.0.5",
"contentHash": "FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "nOP8R1mVb/6mZtm2qgAJXn/LFm/2kMjHDAg/QJLFG6CuWYJtaD3p1BwQhufBVvRzL9ceJ/xF0SQ0qsI2GkDQAA==",
"dependencies": {
"Microsoft.Extensions.Configuration.Abstractions": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "65MrmXCziWaQFrI0UHkQbesrX5wTwf9XPjY5yFm/VkgJKFJ5gqvXRoXjIZcf2wLi5ZlwGz/oMYfyURVCWbM5iw==",
"dependencies": {
"Microsoft.Extensions.Primitives": "2.2.0"
}
},
"Microsoft.Extensions.Configuration.Binder": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "vJ9xvOZCnUAIHcGC3SU35r3HKmHTVIeHzo6u/qzlHAqD8m6xv92MLin4oJntTvkpKxVX3vI1GFFkIQtU3AdlsQ==",
"dependencies": {
"Microsoft.Extensions.Configuration": "2.2.0"
}
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "B2WqEox8o+4KUOpL7rZPyh6qYjik8tHi2tN8Z9jZkHzED8ElYgZa/h6K+xliB435SqUcWT290Fr2aa8BtZjn8A=="
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "UpZLNLBpIZ0GTebShui7xXYh6DmBHjWM8NxGxZbdQh/bPZ5e6YswqI+bru6BnEL5eWiOdodsXtEz3FROcgi/qg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Primitives": "2.2.0",
"System.ComponentModel.Annotations": "4.5.0"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "2.2.0",
"contentHash": "azyQtqbm4fSaDzZHD/J+V6oWMFaf2tWP4WEGIYePLCMw3+b2RQdj9ybgbQyjCshcitQKQ4lEDOZjmSlTTrHxUg==",
"dependencies": {
"System.Memory": "4.5.1",
"System.Runtime.CompilerServices.Unsafe": "4.5.1"
}
},
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "8.0.0",
"contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"SQLitePCLRaw.bundle_e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
}
},
"SQLitePCLRaw.core": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"dependencies": {
"System.Memory": "4.5.3"
}
},
"SQLitePCLRaw.lib.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg=="
},
"SQLitePCLRaw.provider.e_sqlite3": {
"type": "Transitive",
"resolved": "2.1.4",
"contentHash": "CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
}
},
"System.ComponentModel.Annotations": {
"type": "Transitive",
"resolved": "4.5.0",
"contentHash": "UxYQ3FGUOtzJ7LfSdnYSFd7+oEv6M8NgUatatIN2HxNtDdlcvFAf+VIq4Of9cDMJEJC0aSRv/x898RYhB4Yppg=="
},
"System.Memory": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
},
"System.Reactive": {
"type": "Transitive",
"resolved": "5.0.0",
"contentHash": "erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ=="
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.1",
"contentHash": "Zh8t8oqolRaFa9vmOZfdQm/qKejdqz0J9kr7o2Fu0vPeoH3BL1EOXipKWwkWtLT1JPzjByrF19fGuFlNbmPpiw=="
},
"speckle.objects": {
"type": "Project",
"dependencies": {
"Speckle.Sdk": "[1.0.0, )"
}
},
"speckle.sdk": {
"type": "Project",
"dependencies": {
"GraphQL.Client": "[6.0.0, )",
"Microsoft.Data.Sqlite": "[7.0.5, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
}
},
"speckle.sdk.dependencies": {
"type": "Project"
},
"GraphQL.Client": {
"type": "CentralTransitive",
"requested": "[6.0.0, )",
"resolved": "6.0.0",
"contentHash": "8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"dependencies": {
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
}
},
"Microsoft.Data.Sqlite": {
"type": "CentralTransitive",
"requested": "[7.0.5, )",
"resolved": "7.0.5",
"contentHash": "KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "f9hstgjVmr6rmrfGSpfsVOl2irKAgr1QjrSi3FgnS7kulxband50f2brRLwySAQTADPZeTdow0mpSMcoAdadCw=="
},
"Microsoft.Extensions.Logging": {
"type": "CentralTransitive",
"requested": "[2.2.0, )",
"resolved": "2.2.0",
"contentHash": "Nxqhadc9FCmFHzU+fz3oc8sFlE6IadViYg8dfUdGzJZ2JUxnCsRghBhhOWdM4B2zSZqEc+0BjliBh/oNdRZuig==",
"dependencies": {
"Microsoft.Extensions.Configuration.Binder": "2.2.0",
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.2.0",
"Microsoft.Extensions.Logging.Abstractions": "2.2.0",
"Microsoft.Extensions.Options": "2.2.0"
}
},
"Speckle.DoubleNumerics": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
"resolved": "4.1.0",
"contentHash": "20DtS+FsDRsOD9+AU3TwNFZ0qrKo5f6f7B5ZR9wStsIHHHC9k7DpjbCvuNtmnSjx54MD+TJC7wV2f5iyGVPj1A=="
},
"Speckle.Newtonsoft.Json": {
"type": "CentralTransitive",
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
}
+68
View File
@@ -0,0 +1,68 @@
using Speckle.Objects.Geometry;
using Speckle.Sdk.Models;
namespace Speckle.Objects.Annotation;
/// <summary>
/// Text class for representation in the viewer
/// </summary>
[SpeckleType("Objects.Annotation.Text")]
public class Text : Base
{
/// <summary>
/// Plain text, without formatting
/// </summary>
public required string value { get; set; }
/// <summary>
/// Height in linear units or pixels (if Units.None)
/// </summary>
public required double height { get; set; }
/// <summary>
/// Units will be 'Units.None' if the text size is defined in pixels (stays the same size
/// independently of zooming the model). Default height in pixels is 17px (used for Viewer measurements)
/// </summary>
public required string units { get; set; }
/// <summary>
/// If true, the text is oriented to face the screen (camera-aligned).
/// </summary>
public required bool screenOriented { get; set; }
/// <summary>
/// Horizontal alignment: Left, Center or Right
/// </summary>
public AlignmentHorizontal alignmentH { get; set; }
/// <summary>
/// Vertical alignment: Top, Center or Bottom
/// </summary>
public AlignmentVertical alignmentV { get; set; }
/// <summary>
/// Plane axis vectors will be ignored if screenOriented is true
/// </summary>
public required Plane plane { get; set; }
/// <summary>
/// Maximum width of the text field (in 'units').
/// Text will be split into lines (wrapped) to fit into the width.
/// null, if text should not be wrapped.
/// </summary>
public double? maxWidth { get; set; }
}
public enum AlignmentHorizontal
{
Left,
Center,
Right,
}
public enum AlignmentVertical
{
Top,
Center,
Bottom,
}
+7
View File
@@ -12,6 +12,13 @@ public class RevitObject : DataObject, IRevitObject
public required string family { get; set; }
public required string category { get; set; }
/// <summary>
/// The level constraint of the object.
/// For objects constrained by multiple levels, this represents the base constraint.
/// For objects with no level constraint, this should be null.
/// </summary>
public required string? level { get; set; }
/// <summary>
/// A Curve or Point object representing the location of a Revit element.
/// </summary>
+8 -9
View File
@@ -3,7 +3,6 @@ using Speckle.Newtonsoft.Json;
using Speckle.Objects.Other;
using Speckle.Objects.Primitive;
using Speckle.Sdk.Common;
using Speckle.Sdk.Host;
using Speckle.Sdk.Models;
namespace Speckle.Objects.Geometry;
@@ -31,7 +30,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <summary>
/// Gets or sets the flat list of numbers representing the <see cref="Brep"/>'s surfaces.
/// </summary>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> SurfacesValue
{
get
@@ -77,7 +76,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Curve3D"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> Curve3DValues
{
get => CurveArrayEncodingExtensions.ToArray(Curve3D);
@@ -102,7 +101,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Curve2D"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> Curve2DValues
{
get => CurveArrayEncodingExtensions.ToArray(Curve2D);
@@ -127,7 +126,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Vertices"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(31250)]
[DetachProperty, Chunkable(31250)]
public List<double> VerticesValue
{
get
@@ -167,7 +166,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Edges"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<double?> EdgesValue
{
get =>
@@ -241,7 +240,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Loops"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<int> LoopsValue
{
get =>
@@ -297,7 +296,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Trims"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<int> TrimsValue
{
get
@@ -363,7 +362,7 @@ public class Brep : Base, IHasArea, IHasVolume, IHasBoundingBox, ITransformable<
/// <remarks>
/// This is only used for the <see cref="Brep"/> class serialisation/deserialisation. You should use <see cref="Brep.Faces"/> instead.
/// </remarks>
[DetachProperty, SchemaIgnore, Chunkable(62500)]
[DetachProperty, Chunkable(62500)]
public List<int> FacesValue
{
get =>
-1
View File
@@ -68,7 +68,6 @@ public class Plane : Base, ITransformable<Plane>
/// Returns the values of this <see cref="Plane"/> as a list of numbers
/// </summary>
/// <returns>A list of values representing the Plane.</returns>
public List<double> ToList()
{
var list = new List<double>();
+97
View File
@@ -0,0 +1,97 @@
using Speckle.Objects.Other;
using Speckle.Sdk.Common;
using Speckle.Sdk.Models;
namespace Speckle.Objects.Geometry;
/// <summary>
/// Flat polygon, defined by an outer boundary and inner loops.
/// </summary>
[SpeckleType("Objects.Geometry.Region")]
public class Region : Base, IHasArea, IHasBoundingBox, ITransformable, IDisplayValue<List<Mesh>>
{
/// <summary>
/// Boundary of a region.
/// Should be a planar, closed, non-self-intersecting ICurve.
/// </summary>
public required ICurve boundary { get; set; }
/// <summary>
/// Loops (voids) in the region.
/// Each loop should be planar, closed, non-self-intersecting ICurve, located inside the boundary.
/// The loops should not intersect or touch each other.
/// </summary>
public required List<ICurve> innerLoops { get; set; } = new();
/// <summary>
/// The units of object's coordinates.
/// This should be one of <see cref="Units"/>
/// </summary>
public required string units { get; set; }
/// <summary>
/// Indication whether the region is just a geometry (false) or has a hatch pattern (true).
/// It's a distinction for receiving in apps that support both Region and Hatch (aka region with hatch pattern)
/// </summary>
public required bool hasHatchPattern { get; set; }
/// <inheritdoc/>
public double area { get; set; }
/// <inheritdoc/>
public Box? bbox { get; set; }
/// <inheritdoc/>
[DetachProperty]
public List<Mesh> displayValue { get; set; } = new();
/// <inheritdoc/>
public bool TransformTo(Transform transform, out ITransformable transformed)
{
// assign self to the returned object, in case transformation fails
transformed = this;
// transform boundary
if (boundary is ITransformable boundaryTransformable)
{
boundaryTransformable.TransformTo(transform, out ITransformable transformedBoundaryResult);
var transformedBoundary = (ICurve)transformedBoundaryResult;
// transform inner loops
var transformedLoops = new List<ICurve>();
foreach (var loop in innerLoops)
{
if (loop is ITransformable loopTransformable)
{
loopTransformable.TransformTo(transform, out ITransformable transformedLoop);
transformedLoops.Add((ICurve)transformedLoop);
}
else
{
return false;
}
}
// transform display meshes
var transformedMeshes = new List<Mesh>();
foreach (var mesh in displayValue)
{
mesh.TransformTo(transform, out ITransformable transformedMesh);
transformedMeshes.Add((Mesh)transformedMesh);
}
// if boundary and loops transformations succeeded
transformed = new Region
{
boundary = transformedBoundary,
innerLoops = transformedLoops,
hasHatchPattern = hasHatchPattern,
displayValue = transformedMeshes,
units = units,
};
return true;
}
return false;
}
}
+6
View File
@@ -0,0 +1,6 @@
using Speckle.Sdk.Models;
namespace Speckle.Objects.Geometry;
[SpeckleType("Objects.Geometry.SolidX")]
public class SolidX : RawEncodedObject;
-1
View File
@@ -147,7 +147,6 @@ public class Surface : Base, IHasBoundingBox, IHasArea, ITransformable<Surface>
/// </summary>
/// <returns>A 2-dimensional array representing this <see cref="Surface"/>s control points.</returns>
/// <remarks>The ControlPoints will be ordered following directions "[u][v]"</remarks>
public List<List<ControlPoint>> GetControlPoints()
{
var matrix = new List<List<ControlPoint>>();
+2 -9
View File
@@ -2,6 +2,7 @@ using Speckle.Objects.Geometry;
using Speckle.Objects.Other;
using Speckle.Objects.Primitive;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Data;
namespace Speckle.Objects;
@@ -110,15 +111,7 @@ public interface IDisplayValue<out T> : ISpeckleObject
#region Data objects
/// <summary>
/// Specifies properties on objects to be used for data-based workflows
/// </summary>
public interface IProperties : ISpeckleObject
{
Dictionary<string, object?> properties { get; }
}
public interface IDataObject : IProperties, IDisplayValue<IReadOnlyList<Base>>
public interface IDataObject : IProperties, IDisplayValue<IReadOnlyList<Base>>, ISpeckleObject
{
/// <summary>
/// The name of the object, primarily used to decorate the object for consumption in frontend and other apps
+32
View File
@@ -0,0 +1,32 @@
using Speckle.Objects.Geometry;
using Speckle.Sdk.Models;
namespace Speckle.Objects.Other;
/// <summary>
/// Camera class to represent a perspective camera for a 3D view.
/// </summary>
/// <remarks>Assumes a Z-up, right-handed convention for orientation vectors</remarks>
[SpeckleType("Objects.Other.Camera")]
public class Camera : Base
{
/// <summary>
/// The name of the view that is created by this camera
/// </summary>
public required string name { get; set; }
/// <summary>
/// The location of the camera
/// </summary>
public required Point position { get; set; }
/// <summary>
/// The unit up vector of the camera
/// </summary>
public required Vector up { get; set; }
/// <summary>
/// The unit forward vector of the camera
/// </summary>
public required Vector forward { get; set; }
}
+20
View File
@@ -0,0 +1,20 @@
using Speckle.Objects.Data;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Objects.Other;
/// <summary>
/// Proxy for levels as DataObject value.
/// <remarks> These proxy lives in Objects library because it depends on DataObject</remarks>
/// </summary>
[SpeckleType("Objects.Other.LevelProxy")]
public class LevelProxy : Base, IProxyCollection
{
/// <summary>
/// The list of application ids of objects that use this level
/// </summary>
public required List<string> objects { get; set; }
public required DataObject value { get; set; }
}
+2
View File
@@ -20,4 +20,6 @@ public class RawEncoding : Base // note: at this stage, since we're using this f
public static class RawEncodingFormats
{
public const string RHINO_3DM = "3dm";
public const string ACAD_DWG = "dwg";
public const string ACAD_SAT = "sat";
}
+1 -19
View File
@@ -1,7 +1,6 @@
using System.Drawing;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Objects.Other;
@@ -36,23 +35,6 @@ public class RenderMaterial : Base
public Color emissiveColor
{
get => Color.FromArgb(emissive);
set => diffuse = value.ToArgb();
set => emissive = value.ToArgb();
}
}
/// <summary>
/// Used to store render material to object relationships in root collections
/// </summary>
[SpeckleType("Objects.Other.RenderMaterialProxy")]
public class RenderMaterialProxy : Base, IProxyCollection
{
/// <summary>
/// The list of application ids of objects that use this render material
/// </summary>
public required List<string> objects { get; set; }
/// <summary>
/// The render material used by <see cref="objects"/>
/// </summary>
public required RenderMaterial value { get; set; }
}
@@ -0,0 +1,22 @@
using Speckle.Sdk.Models;
using Speckle.Sdk.Models.Proxies;
namespace Speckle.Objects.Other;
/// <summary>
/// Used to store render material to object relationships in root collections
/// <remarks> These proxy lives in Objects library because it depends on RenderMaterial</remarks>
/// </summary>
[SpeckleType("Objects.Other.RenderMaterialProxy")]
public class RenderMaterialProxy : Base, IProxyCollection
{
/// <summary>
/// The list of application ids of objects that use this render material
/// </summary>
public required List<string> objects { get; set; }
/// <summary>
/// The render material used by <see cref="objects"/>
/// </summary>
public required RenderMaterial value { get; set; }
}
@@ -1,37 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Compiler Properties">
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<PolySharpExcludeGeneratedTypes>System.Runtime.CompilerServices.RequiresLocationAttribute</PolySharpExcludeGeneratedTypes>
<Configurations>Debug;Release;Local</Configurations>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>Speckle.Objects</PackageId>
<Description>Objects is the default object model for Speckle</Description>
<PackageTags>$(PackageTags) objects</PackageTags>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<PropertyGroup Label="Analyers">
<NoWarn>
$(NoWarn);
CA1819;CA1008;CA2225;
</NoWarn>
</PropertyGroup>
<ItemGroup Label="Expose internals to test projects">
<InternalsVisibleTo Include="Speckle.Objects.Tests.Unit" />
</ItemGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\Speckle.Sdk\Speckle.Sdk.csproj" />
</ItemGroup>
</Project>
+33 -24
View File
@@ -54,6 +54,14 @@
"resolved": "6.0.0",
"contentHash": "yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -205,8 +213,8 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "4.5.3",
"contentHash": "3TIsJhD1EiiT0w2CcDMN/iSSwnNnsrnbzeVHSKkaEgV85txMprmuO+Yq2AdSbeVGcg28pdNDTPK87tJhX7VFHw=="
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Runtime.InteropServices.WindowsRuntime": {
"type": "Transitive",
@@ -218,24 +226,24 @@
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
},
"speckle.sdk": {
"type": "Project",
"dependencies": {
"GraphQL.Client": "[6.0.0, )",
"Microsoft.Bcl.AsyncInterfaces": "[5.0.0, )",
"Microsoft.CSharp": "[4.7.0, )",
"Microsoft.Data.Sqlite": "[7.0.5, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
}
},
"speckle.sdk.dependencies": {
@@ -252,15 +260,6 @@
"System.Reactive": "5.0.0"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
"resolved": "5.0.0",
"contentHash": "W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
}
},
"Microsoft.CSharp": {
"type": "CentralTransitive",
"requested": "[4.7.0, )",
@@ -306,6 +305,16 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "10.0.1",
"System.Threading.Tasks.Extensions": "4.6.3"
}
}
},
"net8.0": {
@@ -472,13 +481,13 @@
"type": "Project",
"dependencies": {
"GraphQL.Client": "[6.0.0, )",
"Microsoft.CSharp": "[4.7.0, )",
"Microsoft.Data.Sqlite": "[7.0.5, )",
"Microsoft.Extensions.DependencyInjection.Abstractions": "[2.2.0, )",
"Microsoft.Extensions.Logging": "[2.2.0, )",
"Speckle.DoubleNumerics": "[4.1.0, )",
"Speckle.Newtonsoft.Json": "[13.0.2, )",
"Speckle.Sdk.Dependencies": "[1.0.0, )"
"Speckle.Sdk.Dependencies": "[1.0.0, )",
"System.Threading.Channels": "[10.0.1, )"
}
},
"speckle.sdk.dependencies": {
@@ -495,12 +504,6 @@
"System.Reactive": "5.0.0"
}
},
"Microsoft.CSharp": {
"type": "CentralTransitive",
"requested": "[4.7.0, )",
"resolved": "4.7.0",
"contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA=="
},
"Microsoft.Data.Sqlite": {
"type": "CentralTransitive",
"requested": "[7.0.5, )",
@@ -540,6 +543,12 @@
"requested": "[13.0.2, )",
"resolved": "13.0.2",
"contentHash": "g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA=="
},
"System.Threading.Channels": {
"type": "CentralTransitive",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
}
}
}
@@ -19,13 +19,13 @@ public static class BatchExtensions
public static void AddBatchItem<T>(this IMemoryOwner<T> batch, T item)
where T : IHasByteSize => ((Batch<T>)batch).Add(item);
public static int GetBatchSize<T>(this IMemoryOwner<T> batch, Action<string> logAsWarning, int maxBatchSize)
public static int GetBatchSize<T>(this IMemoryOwner<T> batch, int maxBatchSize)
where T : IHasByteSize
{
var currentSize = ((Batch<T>)batch).BatchByteSize;
if (currentSize > maxBatchSize)
{
logAsWarning($"Batch size exceeded. Current size: {currentSize} bytes. Max size: {maxBatchSize} bytes.");
//doing this to say it's full since the channel reader only does full being equivalent
return maxBatchSize;
}
@@ -8,7 +8,6 @@ public static class ChannelExtensions
{
public static BatchingChannelReader<T, IMemoryOwner<T>> BatchByByteSize<T>(
this ChannelReader<T> source,
Action<string> logAsWarning,
int batchSize,
bool singleReader = false,
bool allowSynchronousContinuations = false
@@ -16,7 +15,6 @@ public static class ChannelExtensions
where T : IHasByteSize =>
new SizeBatchingChannelReader<T>(
source ?? throw new ArgumentNullException(nameof(source)),
logAsWarning,
batchSize,
singleReader,
allowSynchronousContinuations
@@ -5,7 +5,7 @@ namespace Speckle.Sdk.Dependencies.Serialization;
public abstract class ChannelLoader<T>(CancellationToken cancellationToken)
{
private const int RECEIVE_CAPACITY = 5000;
private const int RECEIVE_CAPACITY = 10000;
private const int HTTP_GET_CHUNK_SIZE = 500;
private const int MAX_PARALLELISM_HTTP = 4;
@@ -109,6 +109,9 @@ public abstract class ChannelLoader<T>(CancellationToken cancellationToken)
Exception = ex;
_channel.Writer.TryComplete(ex);
//cancel everything!
_cts.Cancel();
if (!_cts.IsCancellationRequested)
{
_cts.Cancel();
}
}
}
@@ -5,18 +5,16 @@ using Speckle.Sdk.Serialisation.V2.Send;
namespace Speckle.Sdk.Dependencies.Serialization;
public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationToken cancellationToken)
public abstract class ChannelSaver<T>
where T : IHasByteSize
{
private const int SEND_CAPACITY = 500;
private const int SEND_CAPACITY = 10000;
private const int HTTP_SEND_CHUNK_SIZE = 25_000_000; //bytes
private static readonly TimeSpan HTTP_BATCH_TIMEOUT = TimeSpan.FromSeconds(2);
private const int MAX_PARALLELISM_HTTP = 4;
private const int HTTP_CAPACITY = 500;
private const int MAX_CACHE_WRITE_PARALLELISM = 4;
private const int MAX_CACHE_BATCH = 500;
private readonly CancellationTokenSource _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
private const int MAX_CACHE_WRITE_PARALLELISM = 1;
private const int MAX_CACHE_BATCH = 1000;
private readonly Channel<T> _checkCacheChannel = Channel.CreateBounded<T>(
new BoundedChannelOptions(SEND_CAPACITY)
@@ -30,26 +28,31 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
_ => throw new NotImplementedException("Dropping items not supported.")
);
public Task Start() =>
public Task Start(
int? maxParallelism,
int? httpBatchSize,
int? cacheBatchSize,
CancellationToken cancellationToken
) =>
_checkCacheChannel
.Reader.BatchByByteSize(logAsWarning, HTTP_SEND_CHUNK_SIZE)
.Reader.BatchByByteSize(httpBatchSize ?? HTTP_SEND_CHUNK_SIZE)
.WithTimeout(HTTP_BATCH_TIMEOUT)
.PipeAsync(
MAX_PARALLELISM_HTTP,
maxParallelism ?? MAX_PARALLELISM_HTTP,
async x => await SendToServer(x).ConfigureAwait(false),
HTTP_CAPACITY,
false,
_cts.Token
cancellationToken
)
.Join()
.Batch(MAX_CACHE_BATCH)
.Batch(cacheBatchSize ?? MAX_CACHE_BATCH, singleReader: true)
.WithTimeout(HTTP_BATCH_TIMEOUT)
.ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, _cts.Token)
.ReadAllConcurrently(MAX_CACHE_WRITE_PARALLELISM, SaveToCache, cancellationToken)
.ContinueWith(
t =>
{
Exception? ex = t.Exception;
if (ex is null && t.Status is TaskStatus.Canceled && !_cts.Token.IsCancellationRequested)
if (ex is null && t.Status is TaskStatus.Canceled && !cancellationToken.IsCancellationRequested)
{
ex = new OperationCanceledException();
}
@@ -60,25 +63,27 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
}
_checkCacheChannel.Writer.TryComplete(ex);
},
_cts.Token,
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current
);
public async ValueTask Save(T item)
public async Task SaveAsync(T item, CancellationToken cancellationToken)
{
if (Exception is not null || _cts.IsCancellationRequested)
if (Exception is not null)
{
return; //don't save if we're already done through an error
}
await _checkCacheChannel.Writer.WriteAsync(item).ConfigureAwait(false);
//can switch to check then try pattern when back pressure is needed or exceptions are too much
//the trees don't need to respond to back pressure
await _checkCacheChannel.Writer.WriteAsync(item, cancellationToken).ConfigureAwait(false);
}
private async Task<IMemoryOwner<T>> SendToServer(IMemoryOwner<T> batch)
{
try
{
await SendToServer((Batch<T>)batch).ConfigureAwait(false);
await SendToServerInternal((Batch<T>)batch).ConfigureAwait(false);
return batch;
}
#pragma warning disable CA1031
@@ -90,20 +95,6 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
}
}
public async Task SendToServer(Batch<T> batch)
{
try
{
await SendToServerInternal(batch).ConfigureAwait(false);
}
#pragma warning disable CA1031
catch (Exception ex)
#pragma warning restore CA1031
{
RecordException(ex);
}
}
protected abstract Task SendToServerInternal(Batch<T> batch);
public abstract void SaveToCache(List<T> item);
@@ -118,13 +109,11 @@ public abstract class ChannelSaver<T>(Action<string> logAsWarning, CancellationT
}
}
protected Exception? Exception { get; set; }
public Exception? Exception { get; set; }
private void RecordException(Exception ex)
{
Exception = ex;
_checkCacheChannel.Writer.TryComplete(ex);
//cancel everything!
_cts.Cancel();
}
}
@@ -11,7 +11,6 @@ public interface IHasByteSize
public sealed class SizeBatchingChannelReader<T>(
ChannelReader<T> source,
Action<string> logAsWarning,
int batchSize,
bool singleReader,
bool syncCont = false
@@ -34,5 +33,5 @@ public sealed class SizeBatchingChannelReader<T>(
protected override void AddBatchItem(IMemoryOwner<T> batch, T item) => batch.AddBatchItem(item);
protected override int GetBatchSize(IMemoryOwner<T> batch) => batch.GetBatchSize(logAsWarning, _batchSize);
protected override int GetBatchSize(IMemoryOwner<T> batch) => batch.GetBatchSize(_batchSize);
}
@@ -1,5 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Compiler Properties">
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<Configurations>Debug;Release;Local</Configurations>
@@ -7,30 +6,26 @@
<ILRepackRenameInternalized>true</ILRepackRenameInternalized>
<ILRepackMergeDebugSymbols>true</ILRepackMergeDebugSymbols>
</PropertyGroup>
<PropertyGroup Label="Nugetspec Package Properties">
<PackageId>Speckle.Sdk.Dependencies</PackageId>
<Description>The .NET SDK for Speckle</Description>
<PackageTags>$(PackageTags) core sdk</PackageTags>
</PropertyGroup>
<PropertyGroup Label="Nuget Package Properties">
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ILRepack.FullAuto">
<PackageReference Include="ILRepack.FullAuto">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" PrivateAssets="all" />
<PackageReference Include="Polly" PrivateAssets="all" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" PrivateAssets="all" />
<PackageReference Include="Polly.Extensions.Http" PrivateAssets="all" />
<PackageReference Include="Open.ChannelExtensions" PrivateAssets="all" />
<PackageReference Include="System.Threading.Channels" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -3,7 +3,7 @@ using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Helpers;
public sealed class SpeckleHttpClientHandler : DelegatingHandler
internal sealed class SpeckleHttpClientHandler : DelegatingHandler
{
private readonly IAsyncPolicy<HttpResponseMessage> _resiliencePolicy;
private readonly ISdkActivityFactory _activityFactory;
@@ -41,13 +41,7 @@ public sealed class SpeckleHttpClientHandler : DelegatingHandler
activity?.InjectHeaders((k, v) => request.Headers.TryAddWithoutValidation(k, v));
var policyResult = await _resiliencePolicy
.ExecuteAndCaptureAsync(
ctx =>
{
return base.SendAsync(request, cancellationToken);
},
context
)
.ExecuteAndCaptureAsync(ctx => base.SendAsync(request, cancellationToken), context)
.ConfigureAwait(false);
context.TryGetValue("retryCount", out var retryCount);
activity?.SetTag("retryCount", retryCount);
@@ -40,8 +40,13 @@ public sealed class SpeckleHttpClientHandlerFactory(ISdkActivityFactory activity
return Policy.WrapAsync(retryPolicy, timeoutPolicy);
}
public SpeckleHttpClientHandler Create(
public DelegatingHandler Create(
HttpMessageHandler? innerHandler = null,
int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS
) => new(innerHandler ?? new HttpClientHandler(), activityFactory, HttpAsyncPolicy(timeoutSeconds: timeoutSeconds));
) =>
new SpeckleHttpClientHandler(
innerHandler ?? new HttpClientHandler(),
activityFactory,
HttpAsyncPolicy(timeoutSeconds: timeoutSeconds)
);
}
+38 -39
View File
@@ -13,9 +13,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "r64veU9uYILp6pYqfo3qzRab8zLMALvXZgT4VRY79tXMLu8X79uTlJ6nqPLtPIVhfCPXycRh8ILyFz/gGBDQdQ=="
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "G7p1k2xVZ+2aVANz0JdSiafr+AHDHeS1kF8+Y0ABbIsByd0erOL59IDXBs9vcdJf3pPV/murO0mbtr4k40QxWw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -38,13 +38,13 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g==",
"requested": "[9.1.0, )",
"resolved": "9.1.0",
"contentHash": "D6c24vMGy1oZ06vmkD2/FNzWHK7ZIihuv2spDgYEeaUp+eobrILQnrNQKRoASFXD4JGfZ7nfvTM0e+AX79dt8Q==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.0",
"System.Collections.Immutable": "9.0.0",
"System.Threading.Channels": "9.0.0"
"Microsoft.Bcl.AsyncInterfaces": "9.0.4",
"System.Collections.Immutable": "9.0.4",
"System.Threading.Channels": "9.0.4"
}
},
"Polly": {
@@ -82,12 +82,12 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "pUmqkuBS9OxWHOlfNad09Oxc8gRbxgN9UQtsqHPst4jfcgZRxQetNcsT2oe+VnUpEFAtBy1FZcJZiOscrBmA7g==",
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg==",
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "9.0.2",
"System.Threading.Tasks.Extensions": "4.5.4"
"Microsoft.Bcl.AsyncInterfaces": "10.0.1",
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"ILRepack": {
@@ -95,6 +95,14 @@
"resolved": "2.0.33",
"contentHash": "xb2h1CsOepoYwdXEPui9VcQglwABQwNf9cccZbf+acarEzF5PUp8Xx71nFXIhOgEdm6wrxAoF6xAxK4m/XFRUQ=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
"resolved": "10.0.1",
"contentHash": "E1HSLkPHXEO30JEij2pWbOuzz1Z5ND4a5l7IP1T2RgQuE0a0NzEIvtO64RNy3Otn6PFezbT80cfm3M/Cgt70PA==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.6.3"
}
},
"Microsoft.Build.Tasks.Git": {
"type": "Transitive",
"resolved": "8.0.0",
@@ -117,8 +125,8 @@
},
"System.Collections.Immutable": {
"type": "Transitive",
"resolved": "9.0.0",
"contentHash": "QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
"resolved": "9.0.4",
"contentHash": "wfm2NgK22MmBe5qJjp52qzpkeDZKb4l9LbdubhZSehY1z4LS+lld6R+B+UQNb2AZRHu/QJlHxEUcRst5hIEejg==",
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
@@ -141,24 +149,15 @@
},
"System.Runtime.CompilerServices.Unsafe": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
"resolved": "6.1.2",
"contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw=="
},
"System.Threading.Tasks.Extensions": {
"type": "Transitive",
"resolved": "4.5.4",
"contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"resolved": "4.6.3",
"contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==",
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "4.5.3"
}
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "CentralTransitive",
"requested": "[5.0.0, )",
"resolved": "9.0.2",
"contentHash": "1CED0BGD7dCKsbe7tDhzpPB2Qdi9x35QChu6zkBEI4s0T5bDkkttGReqQnOeOfRNSxtP2WvpX6Ik/0O93XDuMw==",
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
"System.Runtime.CompilerServices.Unsafe": "6.1.2"
}
}
},
@@ -174,9 +173,9 @@
},
"Microsoft.Extensions.ObjectPool": {
"type": "Direct",
"requested": "[9.0.1, )",
"resolved": "9.0.1",
"contentHash": "r64veU9uYILp6pYqfo3qzRab8zLMALvXZgT4VRY79tXMLu8X79uTlJ6nqPLtPIVhfCPXycRh8ILyFz/gGBDQdQ=="
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "G7p1k2xVZ+2aVANz0JdSiafr+AHDHeS1kF8+Y0ABbIsByd0erOL59IDXBs9vcdJf3pPV/murO0mbtr4k40QxWw=="
},
"Microsoft.SourceLink.GitHub": {
"type": "Direct",
@@ -190,9 +189,9 @@
},
"Open.ChannelExtensions": {
"type": "Direct",
"requested": "[9.0.0, )",
"resolved": "9.0.0",
"contentHash": "DP+l5S6G46wcuY4I4kNXE+RDOmJr0DKuMienOdt0mMBN9z7vmLSC8YQbqCyb9i9LNjXj1tgCx5LyitJiRr/v7g=="
"requested": "[9.1.0, )",
"resolved": "9.1.0",
"contentHash": "D6c24vMGy1oZ06vmkD2/FNzWHK7ZIihuv2spDgYEeaUp+eobrILQnrNQKRoASFXD4JGfZ7nfvTM0e+AX79dt8Q=="
},
"Polly": {
"type": "Direct",
@@ -229,9 +228,9 @@
},
"System.Threading.Channels": {
"type": "Direct",
"requested": "[9.0.2, )",
"resolved": "9.0.2",
"contentHash": "pUmqkuBS9OxWHOlfNad09Oxc8gRbxgN9UQtsqHPst4jfcgZRxQetNcsT2oe+VnUpEFAtBy1FZcJZiOscrBmA7g=="
"requested": "[10.0.1, )",
"resolved": "10.0.1",
"contentHash": "YRqU6Y2Cl6C+HrG5h1ftgKZ5VDTSA7j1wMKs5RtlauPeQ2EZ639Jt5aOFHdX3naP01hDDWFOWPApmNDVKwOpmg=="
},
"ILRepack": {
"type": "Transitive",
+248
View File
@@ -0,0 +1,248 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Api.GraphQL.Resources;
using Speckle.Sdk.Common;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
using Speckle.Sdk.Transports;
using Speckle.Sdk.Transports.ServerUtils;
namespace Speckle.Sdk.Api.Blob;
public partial interface IBlobApi : IDisposable;
/// <summary>
/// Low level access to the blob API
/// </summary>
/// <seealso cref="FileImportResource"/>
/// <seealso cref="ServerApi"/>
[GenerateAutoInterface]
public sealed class BlobApi : IBlobApi
{
public const int DEFAULT_TIMEOUT_SECONDS = SpeckleHttp.DEFAULT_TIMEOUT_SECONDS;
private static readonly string[] s_filenameSeparator = ["filename="];
private readonly ISdkActivityFactory _activityFactory;
/// <summary>
/// HTTP client for communicating with Speckle Server with auth token header
/// </summary>
private readonly HttpClient _authedClient;
/// <summary>
/// HTTP client for communicating with pre-signed s3 url
/// </summary>
private readonly HttpClient _unauthedClient;
public BlobApi(
ISpeckleHttp speckleHttp,
ISdkActivityFactory activityFactory,
Account account,
int timeoutSeconds = DEFAULT_TIMEOUT_SECONDS
)
{
_activityFactory = activityFactory;
_authedClient = speckleHttp.CreateHttpClient(
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip },
timeoutSeconds: timeoutSeconds,
authorizationToken: account.token
);
_authedClient.BaseAddress = new(account.serverInfo.url);
_unauthedClient = speckleHttp.CreateHttpClient(
new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip },
timeoutSeconds: timeoutSeconds
);
}
private static string GetBlobDownloadPath(string blobId, HttpResponseMessage response)
{
response.Content.Headers.TryGetValues("Content-Disposition", out IEnumerable<string>? cdHeaderValues);
var cdHeader = (cdHeaderValues?.FirstOrDefault()).NotNull(
"Expected response from server to contain attachment header"
);
string fileName = cdHeader.Split(s_filenameSeparator, StringSplitOptions.None)[1].TrimStart('"').TrimEnd('"');
return Path.Combine(
SpecklePathProvider.BlobStoragePath(),
$"{blobId[..Models.Blob.LocalHashPrefixLength]}-{fileName}"
);
}
/// <param name="blobId">The ID of the blob to download</param>
/// <param name="progress"></param>
/// <param name="cancellationToken"></param>
/// <exception cref="HttpRequestException">Request for the blob fails</exception>
/// <exception cref="OperationCanceledException"></exception>
/// <returns>File Path of the downloaded file</returns>
public async Task<string> DownloadBlob(
string projectId,
string blobId,
string? pathOverride = null,
IProgress<ProgressArgs>? progress = null,
CancellationToken cancellationToken = default
)
{
using var _ = _activityFactory.Start();
var url = new Uri($"api/stream/{projectId}/blob/{blobId}", UriKind.Relative);
using var response = await _authedClient.GetAsync(url, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
string fileLocation = pathOverride ?? GetBlobDownloadPath(blobId, response);
using var source = new ProgressStream(
#if NET5_0_OR_GREATER
await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false),
#else
await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
#endif
response.Content.Headers.ContentLength,
progress,
true
);
using var fs = new FileStream(fileLocation, FileMode.OpenOrCreate);
#if NET5_0_OR_GREATER
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
#else
await source.CopyToAsync(fs).ConfigureAwait(false);
#endif
return fileLocation;
}
/// <summary>Queries the server for diff of the given <paramref name="blobIds"/></summary>
/// <param name="blobIds"></param>
/// <param name="cancellationToken"></param>
/// <returns>A list of blob ids that the server doesn't have</returns>
/// <exception cref="HttpRequestException">Request for the blob fails</exception>
/// <exception cref="OperationCanceledException"></exception>
/// <exception cref="ArgumentNullException"></exception>
public async Task<List<string>> HasBlobs(
string projectId,
IReadOnlyCollection<string> blobIds,
CancellationToken cancellationToken
)
{
using var _ = _activityFactory.Start();
cancellationToken.ThrowIfCancellationRequested();
var payload = JsonConvert.SerializeObject(blobIds);
var url = new Uri($"/api/stream/{projectId}/blob/diff", UriKind.Relative);
using StringContent stringContent = new(payload, Encoding.UTF8, "application/json");
using var response = await _authedClient.PostAsync(url, stringContent, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
#if NET5_0_OR_GREATER
var responseString = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
#else
var responseString = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
var parsed = JsonConvert
.DeserializeObject<List<string>>(responseString)
.NotNull($"Failed to deserialize successful response {response.Content}");
return parsed;
}
/// <summary>
/// Uploads a single file to the given S3 url.
/// This method should be used together with the <see cref="FileImportResource"/> <see cref="FileImportResource.GenerateUploadUrl"/> method,
/// which generates a pre-signed S3 url, that can be used to upload the file to.
/// </summary>
/// <param name="filePath"></param>
/// <param name="url"></param>
/// <param name="cancellationToken"></param>
/// <returns>etag header</returns>
/// <seealso cref="FileImportResource"/>
/// <exception cref="HttpRequestException"></exception>
/// <exception cref="ArgumentException">Unexpected response header the server</exception>
/// <exception cref="FileNotFoundException"><paramref name="filePath"/> does not point to a file</exception>
/// <exception cref="OperationCanceledException"></exception>
public async Task<string> UploadFile(
string filePath,
Uri url,
IProgress<ProgressArgs>? progress = null,
CancellationToken cancellationToken = default
)
{
using var _ = _activityFactory.Start();
if (!File.Exists(filePath))
{
throw new FileNotFoundException("File not found.", filePath);
}
var fileInfo = new FileInfo(filePath);
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var requestMessage = new HttpRequestMessage(HttpMethod.Put, url);
requestMessage.Content = progress is null
? new StreamContent(fileStream)
: new ProgressContent(new StreamContent(fileStream), progress);
requestMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
requestMessage.Content.Headers.ContentLength = fileInfo.Length;
using var response = await _unauthedClient.SendAsync(requestMessage, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
return BlobApiHelpers.ParseEtagHeader(response.Headers);
}
/// <summary>
/// Uploads blobs via the <c>"/api/stream/:streamId/blob"</c> endpoint
/// </summary>
/// <param name="projectId"></param>
/// <param name="blobPaths"></param>
/// <param name="progress"></param>
/// <param name="cancellationToken"></param>
public async Task UploadBlobs(
string projectId,
IReadOnlyCollection<(string id, string filePath)> blobPaths,
IProgress<ProgressArgs>? progress,
CancellationToken cancellationToken
)
{
using var _ = _activityFactory.Start();
cancellationToken.ThrowIfCancellationRequested();
if (blobPaths.Count == 0)
{
return;
}
using var multipartFormDataContent = new MultipartFormDataContent();
foreach (var (id, filePath) in blobPaths)
{
var fileName = Path.GetFileName(filePath);
var stream = File.OpenRead(filePath);
var fsc = new StreamContent(stream);
multipartFormDataContent.Add(fsc, $"hash:{id}", fileName);
}
using HttpContent content = progress is null
? multipartFormDataContent
: new ProgressContent(multipartFormDataContent, progress);
var url = new Uri($"/api/stream/{projectId}/blob", UriKind.Relative);
using var response = await _authedClient.PostAsync(url, content, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
}
[AutoInterfaceIgnore]
public void Dispose()
{
_authedClient.Dispose();
_unauthedClient.Dispose();
}
}
@@ -0,0 +1,13 @@
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Api.Blob;
[GenerateAutoInterface]
public sealed class BlobApiFactory(ISpeckleHttp speckleHttp, ISdkActivityFactory activityFactory) : IBlobApiFactory
{
public IBlobApi Create(Account account, int timeoutSeconds = BlobApi.DEFAULT_TIMEOUT_SECONDS) =>
new BlobApi(speckleHttp, activityFactory, account, timeoutSeconds);
}
+47 -1
View File
@@ -1,4 +1,5 @@
using Speckle.Sdk.Api.GraphQL;
using Speckle.Sdk.Api.GraphQL.Models;
namespace Speckle.Sdk.Api;
@@ -22,10 +23,15 @@ public class SpeckleGraphQLException : SpeckleException
}
/// <summary>
/// Represents a "FORBIDDEN" or "UNAUTHORIZED" GraphQL error as an exception.
/// Represents a "FORBIDDEN" or "UNAUTHENTICATED" or "UNAUTHORIZED" or "UNAUTHORIZED_ACCESS_ERROR" GraphQL error as an exception.
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors/#unauthenticated
/// https://www.apollographql.com/docs/apollo-server/v2/data/errors/#forbidden
/// https://github.com/specklesystems/speckle-server/blob/v2.23.18/packages/server/modules/shared/errors/index.ts#L34
/// </summary>
/// <remarks>
/// Server is a bit inconsistent with these error codes, hence there's 4 different codes that mean "auth no work"
/// Apollo no longer considers "FORBIDDEN" or "UNAUTHENTICATED" as built in error codes, so everything is custom anyway.
/// </remarks>
public sealed class SpeckleGraphQLForbiddenException : SpeckleGraphQLException
{
public SpeckleGraphQLForbiddenException() { }
@@ -96,3 +102,43 @@ public sealed class SpeckleGraphQLInvalidQueryException : SpeckleGraphQLExceptio
public SpeckleGraphQLInvalidQueryException(string? message, Exception? innerException)
: base(message, innerException) { }
}
/// <summary>
/// Represents a <c>WORKSPACES_MODULE_DISABLED_ERROR</c> GraphQL error as an exception
/// </summary>
/// <remarks>
/// A GraphQL request for workspace resources was made to a server that does not have the <c>FF_WORKSPACES_MODULE_ENABLED</c> feature flag enabled
/// </remarks>
public sealed class SpeckleGraphQLWorkspaceNotEnabledException : SpeckleGraphQLException
{
public SpeckleGraphQLWorkspaceNotEnabledException() { }
public SpeckleGraphQLWorkspaceNotEnabledException(string? message)
: base(message) { }
public SpeckleGraphQLWorkspaceNotEnabledException(string? message, Exception? innerException)
: base(message, innerException) { }
}
/// <seealso cref="PermissionCheckResult"/>
public sealed class WorkspacePermissionException : SpeckleGraphQLException
{
public WorkspacePermissionException() { }
public WorkspacePermissionException(string? message)
: base(message) { }
public WorkspacePermissionException(string? message, Exception? innerException)
: base(message, innerException) { }
}
public sealed class CannotCreateCommitException : SpeckleGraphQLException
{
public CannotCreateCommitException() { }
public CannotCreateCommitException(string? message)
: base(message) { }
public CannotCreateCommitException(string? message, Exception? innerException)
: base(message, innerException) { }
}
+49 -76
View File
@@ -1,23 +1,26 @@
using System.Diagnostics.CodeAnalysis;
using System.Net.WebSockets;
using System.Reflection;
using GraphQL;
using GraphQL.Client.Http;
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Newtonsoft.Json;
using Speckle.Newtonsoft.Json.Serialization;
using Speckle.Sdk.Api.Blob;
using Speckle.Sdk.Api.GraphQL;
using Speckle.Sdk.Api.GraphQL.Resources;
using Speckle.Sdk.Api.GraphQL.Serializer;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Dependencies;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Api;
public partial interface IClient : IDisposable
{
GraphQLHttpClient GQLClient { get; }
}
[SuppressMessage("Maintainability", "CA1506:Avoid excessive class coupling", Justification = "Class needs refactor")]
public sealed class Client : ISpeckleGraphQLClient, IDisposable
[GenerateAutoInterface]
public sealed class Client : ISpeckleGraphQLClient, IClient
{
private readonly ILogger<Client> _logger;
private readonly ISdkActivityFactory _activityFactory;
@@ -29,14 +32,17 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
public ProjectInviteResource ProjectInvite { get; }
public CommentResource Comment { get; }
public SubscriptionResource Subscription { get; }
public WorkspaceResource Workspace { get; }
public ServerResource Server { get; }
public FileImportResource FileImport { get; }
public ModelIngestionResource Ingestion { get; }
public Uri ServerUrl => new(Account.serverInfo.url);
[JsonIgnore]
public Account Account { get; }
private HttpClient HttpClient { get; }
[AutoInterfaceIgnore]
public GraphQLHttpClient GQLClient { get; }
/// <param name="account"></param>
@@ -44,14 +50,16 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
public Client(
ILogger<Client> logger,
ISdkActivityFactory activityFactory,
ISpeckleApplication application,
ISpeckleHttp speckleHttp,
Account account
IGraphQLClientFactory graphqlClientFactory,
IBlobApiFactory blobApiFactory,
[NotNull] Account? account
)
{
_logger = logger;
_activityFactory = activityFactory;
Account = account ?? throw new ArgumentException("Provided account is null.");
GQLClient = graphqlClientFactory.CreateGraphQLClient(account);
Project = new(this);
Model = new(this);
@@ -61,22 +69,44 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
ProjectInvite = new(this);
Comment = new(this);
Subscription = new(this);
HttpClient = CreateHttpClient(application, speckleHttp, account);
GQLClient = CreateGraphQLClient(account, HttpClient);
Workspace = new(this);
Server = new(this);
FileImport = new(this, blobApiFactory.Create(account));
Ingestion = new(this);
}
[AutoInterfaceIgnore]
public void Dispose()
{
try
{
FileImport.Dispose();
Subscription.Dispose();
GQLClient.Dispose();
}
catch (Exception ex) when (!ex.IsFatal()) { }
}
/// <summary>
/// Ensure the <see cref="GQLClient"/>'s websocket is fully initialized.
/// <br/>
/// You don't <i>need</i> to call this function, if you don't, then it will be setup for you when you call <see cref="SubscribeTo"/> (e.g. when you create a <see cref="Subscription"/>),
/// but due to <see cref="GraphQL"/>'s WebSocket implementation, it's not awaited (deferred) thus the subscription make take a while to actually be setup.
/// </summary>
/// <remarks>
/// We only use websockets for GraphQL subscriptions, so if you're not using subscriptions, don't call this
///
/// Note. due to other sources (potentially on the GraphQL side) you still need a ~100ms delay between setting up the subscription, and being able to relaibly trigger it
/// This should only really negatively affect test projects.
/// </remarks>
public async Task InitializeWebsocket()
{
if (GQLClient.WebSocketSubProtocol is null)
{
await GQLClient.InitializeWebsocketConnection().ConfigureAwait(false);
}
}
internal async Task<T> ExecuteWithResiliencePolicies<T>(Func<Task<T>> func) =>
await GraphQLRetry
.ExecuteAsync<T, SpeckleGraphQLInternalErrorException>(
@@ -96,7 +126,7 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
)
.ConfigureAwait(false);
/// <inheritdoc/>
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}" />
public async Task<T> ExecuteGraphQLRequest<T>(GraphQLRequest request, CancellationToken cancellationToken = default)
{
using var activity = _activityFactory.Start();
@@ -124,14 +154,15 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
activity?.SetStatus(SdkActivityStatusCode.Ok);
return ret;
}
catch (Exception ex)
catch (Exception)
{
activity?.SetStatus(SdkActivityStatusCode.Error);
activity?.RecordException(ex);
// Don't record exception as it's rethrown.
throw;
}
}
[AutoInterfaceIgnore]
IDisposable ISpeckleGraphQLClient.SubscribeTo<T>(GraphQLRequest request, Action<object, T> callback) =>
SubscribeTo(request, callback);
@@ -176,62 +207,4 @@ public sealed class Client : ISpeckleGraphQLClient, IDisposable
throw new SpeckleGraphQLException($"Subscription for {typeof(T)} failed to start", ex);
}
}
private static GraphQLHttpClient CreateGraphQLClient(Account account, HttpClient httpClient)
{
var gQLClient = new GraphQLHttpClient(
new GraphQLHttpClientOptions
{
EndPoint = new Uri(new Uri(account.serverInfo.url), "/graphql"),
UseWebSocketForQueriesAndMutations = false,
WebSocketProtocol = "graphql-ws",
ConfigureWebSocketConnectionInitPayload = _ =>
{
return SpeckleHttp.CanAddAuth(account.token, out string? authValue)
? new { Authorization = authValue }
: null;
},
},
new NewtonsoftJsonSerializer(
new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver { IgnoreIsSpecifiedMembers = true }, //(Default)
MissingMemberHandling = MissingMemberHandling.Error, //(not default) If you query for a member that doesn't exist, this will throw (except websocket responses see https://github.com/graphql-dotnet/graphql-client/issues/660)
Converters =
{
new ConstantCaseEnumConverter(),
} //(Default) enums will be serialized using the GraphQL const case standard
,
}
),
httpClient
);
gQLClient.WebSocketReceiveErrors.Subscribe(e =>
{
if (e is WebSocketException we)
{
Console.WriteLine(
$"WebSocketException: {we.Message} (WebSocketError {we.WebSocketErrorCode}, ErrorCode {we.ErrorCode}, NativeErrorCode {we.NativeErrorCode}"
);
}
else
{
Console.WriteLine($"Exception in websocket receive stream: {e}");
}
});
return gQLClient;
}
private static HttpClient CreateHttpClient(ISpeckleApplication application, ISpeckleHttp speckleHttp, Account account)
{
var httpClient = speckleHttp.CreateHttpClient(timeoutSeconds: 30, authorizationToken: account.token);
httpClient.DefaultRequestHeaders.Add("apollographql-client-name", application.ApplicationAndVersion);
httpClient.DefaultRequestHeaders.Add(
"apollographql-client-version",
Assembly.GetExecutingAssembly().GetName().Version?.ToString()
);
return httpClient;
}
}
+5 -5
View File
@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging;
using Speckle.InterfaceGenerator;
using Speckle.Sdk.Api.Blob;
using Speckle.Sdk.Credentials;
using Speckle.Sdk.Helpers;
using Speckle.Sdk.Logging;
namespace Speckle.Sdk.Api;
@@ -10,10 +10,10 @@ namespace Speckle.Sdk.Api;
public class ClientFactory(
ILoggerFactory loggerFactory,
ISdkActivityFactory activityFactory,
ISpeckleApplication application,
ISpeckleHttp speckleHttp
IGraphQLClientFactory graphQLClientFactory,
IBlobApiFactory blobApiFactory
) : IClientFactory
{
public Client Create(Account account) =>
new(loggerFactory.CreateLogger<Client>(), activityFactory, application, speckleHttp, account);
public IClient Create(Account account) =>
new Client(loggerFactory.CreateLogger<Client>(), activityFactory, graphQLClientFactory, blobApiFactory, account);
}
@@ -1,6 +1,9 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
// ReSharper disable InconsistentNaming
namespace Speckle.Sdk.Api.GraphQL.Enums;
//This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum)
/// <remarks>
/// This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum)
/// </remarks>
public enum FileUploadConversionStatus
{
Queued = 0,
@@ -0,0 +1,14 @@
// ReSharper disable InconsistentNaming
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ModelIngestionStatus
{
cancelled,
failed,
processing,
queued,
success,
}
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectCommentsUpdatedMessageType
{
ARCHIVED,
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectFileImportUpdatedMessageType
{
CREATED,
@@ -0,0 +1,13 @@
// ReSharper disable InconsistentNaming
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectModelIngestionUpdatedMessageType
{
cancellationRequested,
created,
deleted,
updated,
}
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectModelsUpdatedMessageType
{
CREATED,
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectPendingModelsUpdatedMessageType
{
CREATED,
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectUpdatedMessageType
{
DELETED,
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectVersionsUpdatedMessageType
{
CREATED,
@@ -1,8 +1,15 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
// ReSharper disable InconsistentNaming
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ProjectVisibility
{
Private,
Public,
[Obsolete("Use Public instead")]
Unlisted,
Workspace,
}
@@ -1,5 +1,9 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
// ReSharper disable InconsistentNaming
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum ResourceType
{
commit,
@@ -1,5 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Enums;
/// <remarks>
/// string based enum
/// </remarks>
public enum UserProjectsUpdatedMessageType
{
ADDED,
@@ -28,10 +28,13 @@ internal static class GraphQLErrorHandler
var ex = code switch
{
"GRAPHQL_PARSE_FAILED" or "GRAPHQL_VALIDATION_FAILED" => new SpeckleGraphQLInvalidQueryException(message),
"FORBIDDEN" or "UNAUTHENTICATED" => new SpeckleGraphQLForbiddenException(message),
"FORBIDDEN" or "UNAUTHENTICATED" or "UNAUTHORIZED" or "UNAUTHORIZED_ACCESS_ERROR" =>
new SpeckleGraphQLForbiddenException(message),
"STREAM_NOT_FOUND" => new SpeckleGraphQLStreamNotFoundException(message),
"BAD_USER_INPUT" => new SpeckleGraphQLBadInputException(message),
"INTERNAL_SERVER_ERROR" => new SpeckleGraphQLInternalErrorException(message),
"WORKSPACES_MODULE_DISABLED_ERROR" => new SpeckleGraphQLWorkspaceNotEnabledException(message),
"COMMIT_CREATE_ERROR" => new CannotCreateCommitException(message),
_ => new SpeckleGraphQLException(message),
};
exceptions.Add(ex);
@@ -40,7 +40,8 @@ public static class GraphQLHttpClientExtensions
response.EnsureGraphQLSuccess();
string versionString = response.Data.data.data;
if (versionString == "dev")
//Local server builds will have a non-numerical version string
if (versionString == "dev" || versionString == "custom")
{
return new Version(999, 999, 999);
}
@@ -1,8 +1,8 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
internal sealed record CommentContentInput(IReadOnlyCollection<string>? blobIds, object? doc);
internal record CommentContentInput(IReadOnlyCollection<string>? blobIds, object? doc);
internal sealed record CreateCommentInput(
internal record CreateCommentInput(
CommentContentInput content,
string projectId,
string resourceIdString,
@@ -10,10 +10,10 @@ internal sealed record CreateCommentInput(
object? viewerState
);
internal sealed record EditCommentInput(CommentContentInput content, string commentId, string projectId);
internal record EditCommentInput(CommentContentInput content, string commentId, string projectId);
internal sealed record CreateCommentReplyInput(CommentContentInput content, string threadId, string projectId);
internal record CreateCommentReplyInput(CommentContentInput content, string threadId, string projectId);
public sealed record MarkCommentViewedInput(string commentId, string projectId);
public record MarkCommentViewedInput(string commentId, string projectId);
public sealed record ArchiveCommentInput(string commentId, string projectId, bool archived = true);
public record ArchiveCommentInput(string commentId, string projectId, bool archived = true);
@@ -0,0 +1,50 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public record GenerateFileUploadUrlInput(string projectId, string fileName);
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
public record StartFileImportInput(string projectId, string modelId, string fileId, string etag);
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
public record FileImportResult(
double durationSeconds,
double downloadDurationSeconds,
double parseDurationSeconds,
string parser,
string? versionId
);
public abstract class FileImportInputBase
{
internal const string FILE_IMPORT_DEPRECATION_MESSAGE =
"Part of the old API surface and will be removed in the future. Use the new ingestion API instead. Field will be deleted on June 1st, 2026";
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
protected FileImportInputBase() { }
public required string projectId { get; init; }
public required string jobId { get; init; }
public required IReadOnlyCollection<string> warnings { get; init; }
[Obsolete(FileImportInputBase.FILE_IMPORT_DEPRECATION_MESSAGE)]
public required FileImportResult result { get; init; }
}
#pragma warning disable CA1822 //Mark members as static
[Obsolete(FILE_IMPORT_DEPRECATION_MESSAGE)]
public sealed class FileImportSuccessInput() : FileImportInputBase()
{
public const string TYPE_STATUS = "success";
public string status => TYPE_STATUS;
}
[Obsolete(FILE_IMPORT_DEPRECATION_MESSAGE)]
public sealed class FileImportErrorInput() : FileImportInputBase()
{
public const string TYPE_STATUS = "error";
public string status => TYPE_STATUS;
public required string reason { get; init; }
}
@@ -0,0 +1,70 @@
using Speckle.Newtonsoft.Json;
using Speckle.Sdk.Api.GraphQL.Enums;
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public record SourceDataInput(
string sourceApplicationSlug,
string sourceApplicationVersion,
string? fileName,
long? fileSizeBytes
);
public record ModelIngestionCreateInput(
string modelId,
string projectId,
string progressMessage,
SourceDataInput sourceData
);
public record ModelIngestionUpdateInput(string ingestionId, string projectId, string progressMessage, double? progress);
public record ModelIngestionSuccessInput(
string ingestionId,
string projectId,
string rootObjectId,
string? versionMessage
);
public record ModelIngestionFailedInput(
string ingestionId,
string projectId,
string errorReason,
string? errorStacktrace
)
{
public static ModelIngestionFailedInput FromException(string ingestionId, string projectId, Exception ex)
{
return new ModelIngestionFailedInput(ingestionId, projectId, ex.Message, ex.ToString());
}
}
public record ModelIngestionCancelledInput(string ingestionId, string projectId, string cancellationMessage);
public record ModelIngestionStartProcessingInput(
string ingestionId,
string projectId,
string progressMessage,
SourceDataInput sourceData
);
public record ModelIngestionRequeueInput(string ingestionId, string projectId, string progressMessage);
public record ProjectModelIngestionSubscriptionInput(
string projectId,
ModelIngestionReference ingestionReference,
[property: JsonIgnore] ProjectModelIngestionUpdatedMessageType messageType
)
{
// The Newtonsoft serializer is setup to handle SCREAMING_CASE enums.
// But the API requires the enum to look exactly like they are
[JsonProperty(nameof(messageType))]
public string serializedType => messageType.ToString();
}
/// <remarks>
/// <c>@oneOf</c> i.e. server expects <b>either</b> <paramref name="ingestionId"/> or <paramref name="modelId"/>, but not both.
/// </remarks>
/// <param name="ingestionId"></param>
/// <param name="modelId"></param>
public record ModelIngestionReference(string? ingestionId, string? modelId);
@@ -1,9 +1,9 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public sealed record CreateModelInput(string name, string? description, string projectId);
public record CreateModelInput(string name, string? description, string projectId);
public sealed record DeleteModelInput(string id, string projectId);
public record DeleteModelInput(string id, string projectId);
public sealed record UpdateModelInput(string id, string? name, string? description, string projectId);
public record UpdateModelInput(string id, string? name, string? description, string projectId);
public sealed record ModelVersionsFilter(IReadOnlyList<string> priorityIds, bool? priorityIdsOnly);
public record ModelVersionsFilter(IReadOnlyList<string> priorityIds, bool? priorityIdsOnly);
@@ -2,15 +2,22 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public sealed record ProjectCommentsFilter(bool? includeArchived, bool? loadedVersionsOnly, string? resourceIdString);
public record ProjectCommentsFilter(bool? includeArchived, bool? loadedVersionsOnly, string? resourceIdString);
public sealed record ProjectCreateInput(string? name, string? description, ProjectVisibility? visibility);
public record ProjectCreateInput(string? name, string? description, ProjectVisibility? visibility);
public sealed record ProjectInviteCreateInput(string? email, string? role, string? serverRole, string? userId);
public record WorkspaceProjectCreateInput(
string? name,
string? description,
ProjectVisibility? visibility,
string workspaceId
);
public sealed record ProjectInviteUseInput(bool accept, string projectId, string token);
public record ProjectInviteCreateInput(string? email, string? role, string? serverRole, string? userId);
public sealed record ProjectModelsFilter(
public record ProjectInviteUseInput(bool accept, string projectId, string token);
public record ProjectModelsFilter(
IReadOnlyList<string>? contributors = null,
IReadOnlyList<string>? excludeIds = null,
IReadOnlyList<string>? ids = null,
@@ -19,7 +26,7 @@ public sealed record ProjectModelsFilter(
IReadOnlyList<string>? sourceApps = null
);
public sealed record ProjectUpdateInput(
public record ProjectUpdateInput(
string id,
string? name = null,
string? description = null,
@@ -27,6 +34,6 @@ public sealed record ProjectUpdateInput(
ProjectVisibility? visibility = null
);
public sealed record ProjectUpdateRoleInput(string userId, string projectId, string? role);
public record ProjectUpdateRoleInput(string userId, string projectId, string? role);
public sealed record UserProjectsFilter(string search, IReadOnlyList<string>? onlyWithRoles = null);
public record WorkspaceProjectsFilter(string? search, bool? withProjectRoleOnly);
@@ -1,7 +1,3 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public sealed record ViewerUpdateTrackingTarget(
string projectId,
string resourceIdString,
bool? loadedVersionsOnly = null
);
public record ViewerUpdateTrackingTarget(string projectId, string resourceIdString, bool? loadedVersionsOnly = null);
@@ -1,8 +1,13 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public sealed record UserUpdateInput(
string? avatar = null,
string? bio = null,
string? company = null,
string? name = null
public record UserUpdateInput(string? avatar = null, string? bio = null, string? company = null, string? name = null);
public record UserProjectsFilter(
string? search = null,
IReadOnlyList<string>? onlyWithRoles = null,
string? workspaceId = null,
bool? personalOnly = null,
bool? includeImplicitAccess = null
);
public record UserWorkspacesFilter(string? search);
@@ -1,12 +1,14 @@
namespace Speckle.Sdk.Api.GraphQL.Inputs;
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
public sealed record UpdateVersionInput(string versionId, string projectId, string? message);
namespace Speckle.Sdk.Api.GraphQL.Inputs;
public sealed record MoveVersionsInput(string projectId, string targetModelName, IReadOnlyList<string> versionIds);
public record UpdateVersionInput(string versionId, string projectId, string? message);
public sealed record DeleteVersionsInput(IReadOnlyList<string> versionIds, string projectId);
public record MoveVersionsInput(string projectId, string targetModelName, IReadOnlyList<string> versionIds);
public sealed record CreateVersionInput(
public record DeleteVersionsInput(IReadOnlyList<string> versionIds, string projectId);
public record CreateVersionInput(
string objectId,
string modelId,
string projectId,
@@ -16,7 +18,11 @@ public sealed record CreateVersionInput(
IReadOnlyList<string>? parents = null
);
public sealed record MarkReceivedVersionInput(
/// <param name="versionId"></param>
/// <param name="projectId"></param>
/// <param name="sourceApplication">IMPORTANT: this is meant to be the slug of the application that has done the receiving, not to be confused with <see cref="Version.sourceApplication"/></param>
/// <param name="message"></param>
public record MarkReceivedVersionInput(
string versionId,
string projectId,
string sourceApplication,
@@ -14,7 +14,7 @@ public sealed class Comment
public string rawText { get; init; }
public ResourceCollection<Comment> replies { get; init; }
public CommentReplyAuthorCollection replyAuthors { get; init; }
public List<ResourceIdentifier> resources { get; init; }
public List<ResourceIdentifier> resources { get; init; } //todo: add resourceIds/baseResourceIds
public string? screenshot { get; init; }
public DateTime updatedAt { get; init; }
public DateTime? viewedAt { get; init; }
@@ -0,0 +1,13 @@
namespace Speckle.Sdk.Api.GraphQL.Models;
public sealed class FileImport
{
public string id { get; init; }
public string projectId { get; init; }
public string? convertedVersionId { get; init; }
public string userId { get; init; }
public int convertedStatus { get; init; }
public string? convertedMessage { get; init; }
public string? modelId { get; init; }
public DateTime updatedAt { get; init; }
}

Some files were not shown because too many files have changed in this diff Show More