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>
This commit is contained in:
@@ -6,10 +6,12 @@ on:
|
|||||||
docker-compose-file:
|
docker-compose-file:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
use-github-container-registry:
|
use-internal-image:
|
||||||
default: false
|
default: false
|
||||||
type: boolean
|
type: boolean
|
||||||
|
secrets:
|
||||||
|
CODECOV_TOKEN:
|
||||||
|
required: true
|
||||||
jobs:
|
jobs:
|
||||||
integration-test:
|
integration-test:
|
||||||
env:
|
env:
|
||||||
@@ -27,7 +29,7 @@ jobs:
|
|||||||
cache-dependency-path: "**/packages.lock.json"
|
cache-dependency-path: "**/packages.lock.json"
|
||||||
|
|
||||||
- name: 🔐 Login to Github Container Registry
|
- name: 🔐 Login to Github Container Registry
|
||||||
if: ${{ inputs.use-github-container-registry }}
|
if: ${{ inputs.use-internal-image }}
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: "ghcr.io"
|
registry: "ghcr.io"
|
||||||
@@ -43,11 +45,18 @@ jobs:
|
|||||||
- name: 🏗️ Build
|
- name: 🏗️ Build
|
||||||
run: dotnet build ${{ env.Solution }} --configuration Release --no-restore -warnaserror
|
run: dotnet build ${{ env.Solution }} --configuration Release --no-restore -warnaserror
|
||||||
|
|
||||||
- name: 🔨 Integration Tests
|
- 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" --configuration Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
|
run: dotnet test ${{ env.Solution }} --filter "Category=Integration" --configuration Release --no-build --no-restore --verbosity=normal /p:AltCover=true /p:AltCoverAttributeFilter=ExcludeFromCodeCoverage
|
||||||
|
|
||||||
- name: Upload coverage reports to Codecov with GitHub Action
|
- name: Upload coverage reports to Codecov with GitHub Action
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
fail_ci_if_error: true
|
||||||
files: tests/**/coverage.xml
|
files: tests/**/coverage.xml
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -39,7 +39,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload coverage reports to Codecov with GitHub Action
|
- name: Upload coverage reports to Codecov with GitHub Action
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
fail_ci_if_error: true
|
||||||
files: tests/**/coverage.xml
|
files: tests/**/coverage.xml
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
@@ -47,9 +49,13 @@ jobs:
|
|||||||
uses: "./.github/workflows/integration-test.yml"
|
uses: "./.github/workflows/integration-test.yml"
|
||||||
with:
|
with:
|
||||||
docker-compose-file: "docker-compose-internal.yml"
|
docker-compose-file: "docker-compose-internal.yml"
|
||||||
use-github-container-registry: true
|
use-internal-image: true
|
||||||
|
secrets:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
integration-test-public:
|
integration-test-public:
|
||||||
uses: "./.github/workflows/integration-test.yml"
|
uses: "./.github/workflows/integration-test.yml"
|
||||||
with:
|
with:
|
||||||
docker-compose-file: "docker-compose.yml"
|
docker-compose-file: "docker-compose.yml"
|
||||||
|
secrets:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload coverage reports to Codecov with GitHub Action
|
- name: Upload coverage reports to Codecov with GitHub Action
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v5
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
fail_ci_if_error: true
|
||||||
files: tests/**/coverage.xml
|
files: tests/**/coverage.xml
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
dotnet 8.0.400
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
<File Path=".github\git-commit-instructions.md" />
|
<File Path=".github\git-commit-instructions.md" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/config/workflows/">
|
<Folder Name="/config/workflows/">
|
||||||
|
<File Path=".github/workflows/integration-test.yml" />
|
||||||
<File Path=".github/workflows/pr.yml" />
|
<File Path=".github/workflows/pr.yml" />
|
||||||
<File Path=".github/workflows/release.yml" />
|
<File Path=".github/workflows/release.yml" />
|
||||||
</Folder>
|
</Folder>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public sealed class Client : ISpeckleGraphQLClient, IClient
|
|||||||
public WorkspaceResource Workspace { get; }
|
public WorkspaceResource Workspace { get; }
|
||||||
public ServerResource Server { get; }
|
public ServerResource Server { get; }
|
||||||
public FileImportResource FileImport { get; }
|
public FileImportResource FileImport { get; }
|
||||||
|
public ModelIngestionResource Ingestion { get; }
|
||||||
|
|
||||||
public Uri ServerUrl => new(Account.serverInfo.url);
|
public Uri ServerUrl => new(Account.serverInfo.url);
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ public sealed class Client : ISpeckleGraphQLClient, IClient
|
|||||||
Workspace = new(this);
|
Workspace = new(this);
|
||||||
Server = new(this);
|
Server = new(this);
|
||||||
FileImport = new(this, blobApiFactory.Create(account));
|
FileImport = new(this, blobApiFactory.Create(account));
|
||||||
|
Ingestion = new(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[AutoInterfaceIgnore]
|
[AutoInterfaceIgnore]
|
||||||
|
|||||||
@@ -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
|
public enum FileUploadConversionStatus
|
||||||
{
|
{
|
||||||
Queued = 0,
|
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;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectCommentsUpdatedMessageType
|
public enum ProjectCommentsUpdatedMessageType
|
||||||
{
|
{
|
||||||
ARCHIVED,
|
ARCHIVED,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectFileImportUpdatedMessageType
|
public enum ProjectFileImportUpdatedMessageType
|
||||||
{
|
{
|
||||||
CREATED,
|
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;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectModelsUpdatedMessageType
|
public enum ProjectModelsUpdatedMessageType
|
||||||
{
|
{
|
||||||
CREATED,
|
CREATED,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectPendingModelsUpdatedMessageType
|
public enum ProjectPendingModelsUpdatedMessageType
|
||||||
{
|
{
|
||||||
CREATED,
|
CREATED,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectUpdatedMessageType
|
public enum ProjectUpdatedMessageType
|
||||||
{
|
{
|
||||||
DELETED,
|
DELETED,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectVersionsUpdatedMessageType
|
public enum ProjectVersionsUpdatedMessageType
|
||||||
{
|
{
|
||||||
CREATED,
|
CREATED,
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
// ReSharper disable InconsistentNaming
|
||||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum ProjectVisibility
|
public enum ProjectVisibility
|
||||||
{
|
{
|
||||||
Private,
|
Private,
|
||||||
|
|||||||
@@ -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
|
public enum ResourceType
|
||||||
{
|
{
|
||||||
commit,
|
commit,
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
namespace Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// string based enum
|
||||||
|
/// </remarks>
|
||||||
public enum UserProjectsUpdatedMessageType
|
public enum UserProjectsUpdatedMessageType
|
||||||
{
|
{
|
||||||
ADDED,
|
ADDED,
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
|
||||||
|
public sealed class ModelIngestion
|
||||||
|
{
|
||||||
|
public required string id { get; init; }
|
||||||
|
public required DateTime createdAt { get; init; }
|
||||||
|
public required DateTime updatedAt { get; init; }
|
||||||
|
public required string modelId { get; init; }
|
||||||
|
public required bool cancellationRequested { get; init; }
|
||||||
|
public required ModelIngestionStatusData statusData { get; init; }
|
||||||
|
// public required LimitedUser user { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
|
||||||
|
namespace Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
|
||||||
|
public sealed class ModelIngestionStatusData
|
||||||
|
{
|
||||||
|
public required ModelIngestionStatus status { get; init; }
|
||||||
|
public required string? progressMessage { get; init; }
|
||||||
|
}
|
||||||
@@ -6,10 +6,10 @@ namespace Speckle.Sdk.Api.GraphQL.Models;
|
|||||||
public sealed class UserProjectsUpdatedMessage : EventArgs
|
public sealed class UserProjectsUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public UserProjectsUpdatedMessageType type { get; init; }
|
public required UserProjectsUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
public Project? project { get; init; }
|
public Project? project { get; init; }
|
||||||
}
|
}
|
||||||
@@ -17,10 +17,10 @@ public sealed class UserProjectsUpdatedMessage : EventArgs
|
|||||||
public sealed class ProjectCommentsUpdatedMessage : EventArgs
|
public sealed class ProjectCommentsUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public ProjectCommentsUpdatedMessageType type { get; init; }
|
public required ProjectCommentsUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
public Comment? comment { get; init; }
|
public Comment? comment { get; init; }
|
||||||
}
|
}
|
||||||
@@ -28,10 +28,10 @@ public sealed class ProjectCommentsUpdatedMessage : EventArgs
|
|||||||
public sealed class ProjectFileImportUpdatedMessage : EventArgs
|
public sealed class ProjectFileImportUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public ProjectFileImportUpdatedMessageType type { get; init; }
|
public required ProjectFileImportUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
public FileUpload? upload { get; init; }
|
public FileUpload? upload { get; init; }
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,10 @@ public sealed class ProjectFileImportUpdatedMessage : EventArgs
|
|||||||
public sealed class ProjectModelsUpdatedMessage : EventArgs
|
public sealed class ProjectModelsUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public ProjectModelsUpdatedMessageType type { get; init; }
|
public required ProjectModelsUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
public Model? model { get; init; }
|
public Model? model { get; init; }
|
||||||
}
|
}
|
||||||
@@ -50,10 +50,10 @@ public sealed class ProjectModelsUpdatedMessage : EventArgs
|
|||||||
public sealed class ProjectPendingModelsUpdatedMessage : EventArgs
|
public sealed class ProjectPendingModelsUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public ProjectPendingModelsUpdatedMessageType type { get; init; }
|
public required ProjectPendingModelsUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
public FileUpload? model { get; init; }
|
public FileUpload? model { get; init; }
|
||||||
}
|
}
|
||||||
@@ -61,10 +61,10 @@ public sealed class ProjectPendingModelsUpdatedMessage : EventArgs
|
|||||||
public sealed class ProjectUpdatedMessage : EventArgs
|
public sealed class ProjectUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public ProjectUpdatedMessageType type { get; init; }
|
public required ProjectUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
public Project? project { get; init; }
|
public Project? project { get; init; }
|
||||||
}
|
}
|
||||||
@@ -72,13 +72,22 @@ public sealed class ProjectUpdatedMessage : EventArgs
|
|||||||
public sealed class ProjectVersionsUpdatedMessage : EventArgs
|
public sealed class ProjectVersionsUpdatedMessage : EventArgs
|
||||||
{
|
{
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string id { get; init; }
|
public required string id { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public ProjectVersionsUpdatedMessageType type { get; init; }
|
public required ProjectVersionsUpdatedMessageType type { get; init; }
|
||||||
|
|
||||||
[JsonRequired]
|
[JsonRequired]
|
||||||
public string modelId { get; init; }
|
public required string modelId { get; init; }
|
||||||
|
|
||||||
public Version? version { get; init; }
|
public Version? version { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class ProjectModelIngestionUpdatedMessage : EventArgs
|
||||||
|
{
|
||||||
|
[JsonRequired]
|
||||||
|
public required ModelIngestion modelIngestion { get; init; }
|
||||||
|
|
||||||
|
[JsonRequired]
|
||||||
|
public required ProjectModelIngestionUpdatedMessageType type { get; init; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,440 @@
|
|||||||
|
using GraphQL;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Models.Responses;
|
||||||
|
|
||||||
|
namespace Speckle.Sdk.Api.GraphQL.Resources;
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class ModelIngestionResource
|
||||||
|
{
|
||||||
|
private readonly ISpeckleGraphQLClient _client;
|
||||||
|
|
||||||
|
internal ModelIngestionResource(ISpeckleGraphQLClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new model ingestion
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The model ingestion created will have a <c>processing</c> state (not <c>queued</c>). This mutation is designed to be used
|
||||||
|
/// by client/connectors that are immediately processing
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> Create(
|
||||||
|
ModelIngestionCreateInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionCreate($input: ModelIngestionCreateInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: create(input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For File Import / Cloud integrations only
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> StartProcessing(
|
||||||
|
ModelIngestionStartProcessingInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionStartProcessing($input: ModelIngestionStartProcessingInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: startProcessing(input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For File Import / Cloud integrations only
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> Requeue(
|
||||||
|
ModelIngestionRequeueInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionStartProcessing($input: ModelIngestionRequeueInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: requeue(input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> UpdateProgress(
|
||||||
|
ModelIngestionUpdateInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionUpdateProgress(
|
||||||
|
$input: ModelIngestionUpdateInput!
|
||||||
|
) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: updateProgress(input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request that the server completes the ingestion by creating a version
|
||||||
|
/// If successful, the job will be in a terminal "successful" state.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="FailWithError"/>
|
||||||
|
/// <seealso cref="FailWithCancel"/>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns>The version id</returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<string> Complete(ModelIngestionSuccessInput input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionComplete($input: ModelIngestionSuccessInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: completeWithVersion(input: $input) {
|
||||||
|
data:statusData {
|
||||||
|
... on ModelIngestionSuccessStatus {
|
||||||
|
data:versionId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<
|
||||||
|
RequiredResponse<RequiredResponse<RequiredResponse<RequiredResponse<RequiredResponse<string>>>>>
|
||||||
|
>(request, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fail the job with an error.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// For requested user cancellation, use <see cref="FailWithCancel"/> instead<br/>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="FailWithCancel"/>
|
||||||
|
/// <seealso cref="Complete"/>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> FailWithError(
|
||||||
|
ModelIngestionFailedInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionFailWithError($input: ModelIngestionFailedInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: failWithError(input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fail the ingestion with a <c>canceled</c> status.
|
||||||
|
/// This should only be done if the user has explicitly requested cancellation
|
||||||
|
/// Other forms of cancellation use <see cref="FailWithError"/>.
|
||||||
|
/// The ingestion should then enter a terminal "canceled" state.<br/>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="FailWithError"/>
|
||||||
|
/// <seealso cref="Complete"/>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> FailWithCancel(
|
||||||
|
ModelIngestionCancelledInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionFailWithCancel($input: ModelIngestionCancelledInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: failWithCancel(input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Request that the <see cref="ModelIngestion"/> is canceled.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Note simply calling this mutation does not imediatly cancel, it doesn't even guarantee it will be canceled at all.
|
||||||
|
/// It's up to the client to observe this cancellation request
|
||||||
|
/// via <see cref="SubscriptionResource.CreateProjectModelIngestionCancellationRequestedSubscription"/>
|
||||||
|
/// and report it as canceled via <see cref="ModelIngestionResource.FailWithCancel"/>
|
||||||
|
/// See "cooperative cancellation pattern"<br/>
|
||||||
|
/// Model Ingestion API is available for server versions <c>3.0.3-alpha.583</c> and above
|
||||||
|
/// </remarks>
|
||||||
|
/// <seealso cref="FailWithError"/>
|
||||||
|
/// <seealso cref="Complete"/>
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ModelIngestion> RequestCancellation(
|
||||||
|
ModelIngestionCancelledInput input,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestionRequestCancellation($input: ModelIngestionRequestCancellationInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: modelIngestionMutations {
|
||||||
|
data: requestCancellation (input: $input) {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ModelIngestion>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using GraphQL;
|
using GraphQL;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
using Speckle.Sdk.Api.GraphQL.Inputs;
|
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
using Speckle.Sdk.Api.GraphQL.Models;
|
using Speckle.Sdk.Api.GraphQL.Models;
|
||||||
using Speckle.Sdk.Api.GraphQL.Models.Responses;
|
using Speckle.Sdk.Api.GraphQL.Models.Responses;
|
||||||
@@ -212,6 +213,60 @@ public sealed class SubscriptionResource : IDisposable
|
|||||||
return subscription;
|
return subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Subscribe to a cancellation request being made for a Model Ingestion</summary>
|
||||||
|
/// <remarks><inheritdoc cref="CreateUserProjectsUpdatedSubscription"/></remarks>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.SubscribeTo{T}"/>
|
||||||
|
public Subscription<ProjectModelIngestionUpdatedMessage> CreateProjectModelIngestionUpdatedSubscription(
|
||||||
|
ProjectModelIngestionSubscriptionInput input
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
subscription IngestionUpdated($input: ProjectModelIngestionSubscriptionInput!) {
|
||||||
|
data: projectModelIngestionUpdated(input: $input) {
|
||||||
|
modelIngestion {
|
||||||
|
id
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
modelId
|
||||||
|
cancellationRequested
|
||||||
|
statusData {
|
||||||
|
... on HasModelIngestionStatus {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
... on HasProgressMessage {
|
||||||
|
progressMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input } };
|
||||||
|
|
||||||
|
Subscription<ProjectModelIngestionUpdatedMessage> subscription = new(_client, request);
|
||||||
|
_subscriptions.Add(subscription);
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Subscribe to a cancellation request being made for a Model Ingestion</summary>
|
||||||
|
/// <remarks><inheritdoc cref="CreateUserProjectsUpdatedSubscription"/></remarks>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.SubscribeTo{T}"/>
|
||||||
|
public Subscription<ProjectModelIngestionUpdatedMessage> CreateProjectModelIngestionCancellationRequestedSubscription(
|
||||||
|
string ingestionId,
|
||||||
|
string projectId
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return CreateProjectModelIngestionUpdatedSubscription(
|
||||||
|
new ProjectModelIngestionSubscriptionInput(
|
||||||
|
projectId,
|
||||||
|
new(ingestionId, null),
|
||||||
|
ProjectModelIngestionUpdatedMessageType.cancellationRequested
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
foreach (var subscription in _subscriptions)
|
foreach (var subscription in _subscriptions)
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Type": "AggregateException",
|
||||||
|
"InnerException": {
|
||||||
|
"Data": {},
|
||||||
|
"Message": "NOT_FOUND_ERROR: Model ingestion not found",
|
||||||
|
"Type": "SpeckleGraphQLException"
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Type": "AggregateException",
|
||||||
|
"InnerException": {
|
||||||
|
"Data": {},
|
||||||
|
"Message": "STREAM_NOT_FOUND: Project not found",
|
||||||
|
"Type": "SpeckleGraphQLStreamNotFoundException"
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Type": "AggregateException",
|
||||||
|
"InnerException": {
|
||||||
|
"Data": {},
|
||||||
|
"Message": "NOT_FOUND_ERROR: Model ingestion not found",
|
||||||
|
"Type": "SpeckleGraphQLException"
|
||||||
|
}
|
||||||
|
}
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Speckle.Sdk.Api;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Resources;
|
||||||
|
using Speckle.Sdk.Host;
|
||||||
|
using Speckle.Sdk.Models;
|
||||||
|
|
||||||
|
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
|
||||||
|
|
||||||
|
[Trait("Server", "Internal")]
|
||||||
|
public sealed class ModelIngestionResourceExceptionalTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private IClient _testUser;
|
||||||
|
private ModelIngestionResource Sut => _testUser.Ingestion;
|
||||||
|
private Project _project;
|
||||||
|
private Model _model;
|
||||||
|
|
||||||
|
public Task DisposeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
TypeLoader.Reset();
|
||||||
|
TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly());
|
||||||
|
|
||||||
|
_testUser = await Fixtures.SeedUserWithClient();
|
||||||
|
_project = await _testUser.Project.Create(new("Test project", "", null));
|
||||||
|
_model = await _testUser.Model.Create(new("Test Model 1", "", _project.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateIngestionNonExistentProject()
|
||||||
|
{
|
||||||
|
var createInput = new ModelIngestionCreateInput(
|
||||||
|
_model.id,
|
||||||
|
"Doesn't exist...",
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<AggregateException>(async () =>
|
||||||
|
{
|
||||||
|
_ = await Sut.Create(createInput);
|
||||||
|
});
|
||||||
|
await Verify(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateNonExistentNonExistent()
|
||||||
|
{
|
||||||
|
var updateInput = new ModelIngestionUpdateInput("Doesn't exist", _project.id, "Can't be", 0.5);
|
||||||
|
|
||||||
|
var ex = await Assert.ThrowsAsync<AggregateException>(async () =>
|
||||||
|
{
|
||||||
|
_ = await Sut.UpdateProgress(updateInput);
|
||||||
|
});
|
||||||
|
await Verify(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CancelNonExistentIngestion()
|
||||||
|
{
|
||||||
|
var input = new ModelIngestionCancelledInput(
|
||||||
|
"Non-existent-ingestion",
|
||||||
|
_project.id,
|
||||||
|
cancellationMessage: "This was cancelled for testing purposes"
|
||||||
|
);
|
||||||
|
var ex = await Assert.ThrowsAsync<AggregateException>(async () =>
|
||||||
|
{
|
||||||
|
_ = await Sut.FailWithCancel(input);
|
||||||
|
});
|
||||||
|
await Verify(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Speckle.Sdk.Api;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Resources;
|
||||||
|
using Speckle.Sdk.Host;
|
||||||
|
using Speckle.Sdk.Models;
|
||||||
|
using Speckle.Sdk.Transports;
|
||||||
|
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
|
||||||
|
|
||||||
|
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
|
||||||
|
|
||||||
|
[Trait("Server", "Internal")]
|
||||||
|
public sealed class ModelIngestionResourceTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private IClient _testUser;
|
||||||
|
private ModelIngestionResource Sut => _testUser.Ingestion;
|
||||||
|
private Project _project;
|
||||||
|
private Model _model;
|
||||||
|
private IOperations _operations;
|
||||||
|
|
||||||
|
public Task DisposeAsync() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
TypeLoader.Reset();
|
||||||
|
TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly());
|
||||||
|
var serviceProvider = TestServiceSetup.GetServiceProvider();
|
||||||
|
_operations = serviceProvider.GetRequiredService<IOperations>();
|
||||||
|
|
||||||
|
_testUser = await Fixtures.SeedUserWithClient();
|
||||||
|
_project = await _testUser.Project.Create(new("Test project", "", null));
|
||||||
|
_model = await _testUser.Model.Create(new("Test Model 1", "", _project.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAndError()
|
||||||
|
{
|
||||||
|
var createInput = new ModelIngestionCreateInput(
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
ModelIngestion ingest = await Sut.Create(createInput);
|
||||||
|
|
||||||
|
var errorInput = new ModelIngestionFailedInput(ingest.id, _project.id, "A bad thing happened", "Over hear!");
|
||||||
|
var res = await Sut.FailWithError(errorInput);
|
||||||
|
Assert.Equal(ingest.id, res.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAndUpdate()
|
||||||
|
{
|
||||||
|
var createInput = new ModelIngestionCreateInput(
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
ModelIngestion ingest = await Sut.Create(createInput);
|
||||||
|
|
||||||
|
await Update(null, "None");
|
||||||
|
await Update(0.1, "0.1");
|
||||||
|
await Update(0.5, "Whoa-oh! We're half way there!");
|
||||||
|
await Update(1, "Finished");
|
||||||
|
await Update(0.2, "Back to processing again");
|
||||||
|
|
||||||
|
async Task Update(double? progress, string message)
|
||||||
|
{
|
||||||
|
var updateInput = new ModelIngestionUpdateInput(ingest.id, _project.id, message, progress);
|
||||||
|
var res = await Sut.UpdateProgress(updateInput);
|
||||||
|
|
||||||
|
Assert.Equal(message, res.statusData.progressMessage);
|
||||||
|
Assert.False(res.cancellationRequested);
|
||||||
|
Assert.Equal(ModelIngestionStatus.processing, res.statusData.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAndCancel()
|
||||||
|
{
|
||||||
|
var createInput = new ModelIngestionCreateInput(
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
ModelIngestion ingest = await Sut.Create(createInput);
|
||||||
|
|
||||||
|
var input = new ModelIngestionCancelledInput(
|
||||||
|
ingest.id,
|
||||||
|
_project.id,
|
||||||
|
cancellationMessage: "This was cancelled for testing purposes"
|
||||||
|
);
|
||||||
|
var res = await Sut.FailWithCancel(input);
|
||||||
|
Assert.Equal(ingest.id, res.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAndComplete()
|
||||||
|
{
|
||||||
|
ModelIngestionCreateInput createInput = new(
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
ModelIngestion ingest = await Sut.Create(createInput);
|
||||||
|
|
||||||
|
Base myObject = Fixtures.GenerateNestedObject();
|
||||||
|
var sendResult = await _operations.Send2(
|
||||||
|
_testUser.ServerUrl,
|
||||||
|
_project.id,
|
||||||
|
_testUser.Account.token,
|
||||||
|
myObject,
|
||||||
|
new Progress<ProgressArgs>(x =>
|
||||||
|
{
|
||||||
|
var updateInput = new ModelIngestionUpdateInput(
|
||||||
|
ingest.id,
|
||||||
|
_project.id,
|
||||||
|
$"{x.Count} / {x.Total}",
|
||||||
|
x.Total == null ? null : x.Count / x.Total
|
||||||
|
);
|
||||||
|
_ = Sut.UpdateProgress(updateInput).Result;
|
||||||
|
}),
|
||||||
|
CancellationToken.None,
|
||||||
|
new(true, true)
|
||||||
|
);
|
||||||
|
|
||||||
|
ModelIngestionSuccessInput finish = new(ingest.id, _project.id, sendResult.RootId);
|
||||||
|
string versionId = await Sut.Complete(finish);
|
||||||
|
Version version = await _testUser.Version.Get(versionId, _project.id);
|
||||||
|
Assert.Equal(version.id, versionId);
|
||||||
|
Assert.Equal(sendResult.RootId, version.referencedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TestRequeue()
|
||||||
|
{
|
||||||
|
//Not sure if is desirable that ingestions created by the modelIngestionMutations.create mutation can be re-queued
|
||||||
|
//But the server allows it, so we test it
|
||||||
|
var createInput = new ModelIngestionCreateInput(
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
var ingestion = await Sut.Create(createInput);
|
||||||
|
var res = await Sut.Requeue(new(ingestion.id, _project.id, "we'll try and requeue this ingestion"));
|
||||||
|
|
||||||
|
Assert.Equal(ingestion.id, res.id);
|
||||||
|
Assert.Equal(ModelIngestionStatus.queued, res.statusData.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task TestStartProcessing()
|
||||||
|
{
|
||||||
|
//Not sure if is desirable that StartProcessing can be used by ingestions created by the modelIngestionMutations.create mutation
|
||||||
|
//But the server allows it, so we test it
|
||||||
|
var createInput = new ModelIngestionCreateInput(
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
"Starting processing",
|
||||||
|
new(".NET test runner", "0.0.0", null, null)
|
||||||
|
);
|
||||||
|
var ingestion = await Sut.Create(createInput);
|
||||||
|
var res = await Sut.StartProcessing(
|
||||||
|
new(ingestion.id, _project.id, "", new SourceDataInput("what", "happens", "now", 0))
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(ingestion.id, res.id);
|
||||||
|
Assert.Equal(ModelIngestionStatus.processing, res.statusData.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
-6
@@ -1,4 +1,4 @@
|
|||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using Speckle.Sdk.Api;
|
using Speckle.Sdk.Api;
|
||||||
using Speckle.Sdk.Api.GraphQL.Enums;
|
using Speckle.Sdk.Api.GraphQL.Enums;
|
||||||
using Speckle.Sdk.Api.GraphQL.Inputs;
|
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
@@ -11,7 +11,7 @@ namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
|
|||||||
public class SubscriptionResourceTests : IAsyncLifetime
|
public class SubscriptionResourceTests : IAsyncLifetime
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
private const int WAIT_PERIOD = 3000; // WSL is slow AF, so for local runs, we're being extra generous
|
private const int WAIT_PERIOD = 4000; // WSL is slow AF, so for local runs, we're being extra generous
|
||||||
#else
|
#else
|
||||||
private const int WAIT_PERIOD = 400; // For CI runs, a much smaller wait time is acceptable
|
private const int WAIT_PERIOD = 400; // For CI runs, a much smaller wait time is acceptable
|
||||||
#endif
|
#endif
|
||||||
@@ -80,15 +80,15 @@ public class SubscriptionResourceTests : IAsyncLifetime
|
|||||||
public async Task ProjectUpdated_SubscriptionIsCalled()
|
public async Task ProjectUpdated_SubscriptionIsCalled()
|
||||||
{
|
{
|
||||||
TaskCompletionSource<ProjectUpdatedMessage> tcs = new();
|
TaskCompletionSource<ProjectUpdatedMessage> tcs = new();
|
||||||
using var sub = Sut.CreateProjectUpdatedSubscription(_testProject.id);
|
using Subscription<ProjectUpdatedMessage> sub = Sut.CreateProjectUpdatedSubscription(_testProject.id);
|
||||||
sub.Listeners += (_, message) => tcs.SetResult(message);
|
sub.Listeners += (_, message) => tcs.SetResult(message);
|
||||||
|
|
||||||
await Task.Delay(WAIT_PERIOD); // Give time to subscription to be setup
|
await Task.Delay(WAIT_PERIOD); // Give time to subscription to be setup
|
||||||
|
|
||||||
var input = new ProjectUpdateInput(_testProject.id, "This is my new name");
|
ProjectUpdateInput input = new(_testProject.id, "This is my new name");
|
||||||
var created = await _testUser.Project.Update(input);
|
Project created = await _testUser.Project.Update(input);
|
||||||
|
|
||||||
var subscriptionMessage = await tcs.Task;
|
ProjectUpdatedMessage subscriptionMessage = await tcs.Task;
|
||||||
|
|
||||||
subscriptionMessage.Should().NotBeNull();
|
subscriptionMessage.Should().NotBeNull();
|
||||||
subscriptionMessage.id.Should().Be(created.id);
|
subscriptionMessage.id.Should().Be(created.id);
|
||||||
@@ -135,4 +135,74 @@ public class SubscriptionResourceTests : IAsyncLifetime
|
|||||||
subscriptionMessage.type.Should().Be(ProjectCommentsUpdatedMessageType.CREATED);
|
subscriptionMessage.type.Should().Be(ProjectCommentsUpdatedMessageType.CREATED);
|
||||||
subscriptionMessage.comment.Should().NotBeNull();
|
subscriptionMessage.comment.Should().NotBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(Timeout = TIMEOUT), Trait("Server", "Internal")]
|
||||||
|
public async Task ProjectModelIngestionCancellationRequested_SubscriptionIsCalled()
|
||||||
|
{
|
||||||
|
ModelIngestion ingestion = await _testUser.Ingestion.Create(
|
||||||
|
new(_testModel.id, _testProject.id, "", new(".NET test", "0.0.0", null, null))
|
||||||
|
);
|
||||||
|
TaskCompletionSource<ProjectModelIngestionUpdatedMessage> tcs = new();
|
||||||
|
|
||||||
|
using var sub = Sut.CreateProjectModelIngestionCancellationRequestedSubscription(ingestion.id, _testProject.id);
|
||||||
|
sub.Listeners += (_, message) => tcs.SetResult(message);
|
||||||
|
|
||||||
|
await Task.Delay(WAIT_PERIOD); // Give time to subscription to be setup
|
||||||
|
|
||||||
|
await _testUser.Ingestion.RequestCancellation(new(ingestion.id, _testProject.id, "please cancel"));
|
||||||
|
|
||||||
|
var subscriptionMessage = await tcs.Task;
|
||||||
|
|
||||||
|
subscriptionMessage.Should().NotBeNull();
|
||||||
|
subscriptionMessage.type.Should().Be(ProjectModelIngestionUpdatedMessageType.cancellationRequested);
|
||||||
|
subscriptionMessage.modelIngestion.id.Should().Be(ingestion.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Timeout = TIMEOUT), Trait("Server", "Internal")]
|
||||||
|
public async Task ProjectModelIngestionUpdate_UpdateSubscriptionIs()
|
||||||
|
{
|
||||||
|
ModelIngestion ingestion = await _testUser.Ingestion.Create(
|
||||||
|
new(_testModel.id, _testProject.id, "", new(".NET test", "0.0.0", null, null))
|
||||||
|
);
|
||||||
|
TaskCompletionSource<ProjectModelIngestionUpdatedMessage> tcs = new();
|
||||||
|
|
||||||
|
using var sub = Sut.CreateProjectModelIngestionUpdatedSubscription(
|
||||||
|
new(
|
||||||
|
_testProject.id,
|
||||||
|
new ModelIngestionReference(ingestion.id, null),
|
||||||
|
ProjectModelIngestionUpdatedMessageType.updated
|
||||||
|
)
|
||||||
|
);
|
||||||
|
sub.Listeners += (_, message) => tcs.SetResult(message);
|
||||||
|
|
||||||
|
await Task.Delay(WAIT_PERIOD); // Give time to subscription to be setup
|
||||||
|
|
||||||
|
await _testUser.Ingestion.UpdateProgress(new(ingestion.id, _testProject.id, "Here's an update", 0.314));
|
||||||
|
|
||||||
|
var subscriptionMessage = await tcs.Task;
|
||||||
|
|
||||||
|
subscriptionMessage.Should().NotBeNull();
|
||||||
|
subscriptionMessage.type.Should().Be(ProjectModelIngestionUpdatedMessageType.updated);
|
||||||
|
subscriptionMessage.modelIngestion.id.Should().Be(ingestion.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(Timeout = TIMEOUT), Trait("Server", "Internal")]
|
||||||
|
public async Task ProjectModelIngestionUpdate_CancelSubscriptionIsNotCalled()
|
||||||
|
{
|
||||||
|
ModelIngestion ingestion = await _testUser.Ingestion.Create(
|
||||||
|
new(_testModel.id, _testProject.id, "", new(".NET test", "0.0.0", null, null))
|
||||||
|
);
|
||||||
|
TaskCompletionSource<ProjectModelIngestionUpdatedMessage> tcs = new();
|
||||||
|
|
||||||
|
using var sub = Sut.CreateProjectModelIngestionCancellationRequestedSubscription(ingestion.id, _testProject.id);
|
||||||
|
sub.Listeners += (_, message) => tcs.SetResult(message);
|
||||||
|
|
||||||
|
await Task.Delay(WAIT_PERIOD); // Give time to subscription to be setup
|
||||||
|
|
||||||
|
await _testUser.Ingestion.UpdateProgress(new(ingestion.id, _testProject.id, "this shouldn't cancel", null));
|
||||||
|
|
||||||
|
await Task.Delay(WAIT_PERIOD); // Give time to subscription to maybe fire
|
||||||
|
|
||||||
|
tcs.Task.IsCompleted.Should().BeFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user