Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fbd9c17ba | |||
| 937eb94730 |
@@ -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 IngestResource Ingest { 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));
|
||||||
|
Ingest = new(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[AutoInterfaceIgnore]
|
[AutoInterfaceIgnore]
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
|
|
||||||
|
public record IngestCreateInput(
|
||||||
|
string fileName,
|
||||||
|
int? maxIdleTimeoutMinutes,
|
||||||
|
string modelId,
|
||||||
|
string projectId,
|
||||||
|
string sourceApplication,
|
||||||
|
string sourceApplicationVersion,
|
||||||
|
IReadOnlyDictionary<string, object?> sourceFileData
|
||||||
|
);
|
||||||
|
|
||||||
|
public record IngestFinishInput(string id, string? message, string objectId, string projectId);
|
||||||
|
|
||||||
|
public record IngestErrorInput(string errorReason, string errorStacktrace, string id, string projectId);
|
||||||
|
|
||||||
|
public record CancelRequestInput(string id, string projectId);
|
||||||
|
|
||||||
|
public record IngestUpdateInput(string id, double? progress, string? progressMessage, string projectId);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
namespace Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
|
||||||
|
public sealed class Ingest
|
||||||
|
{
|
||||||
|
public required DateTime createdAt { get; init; }
|
||||||
|
public required string errorReason { get; init; }
|
||||||
|
public required string errorStacktrace { get; init; }
|
||||||
|
public required string fileName { get; init; }
|
||||||
|
public required string id { get; init; }
|
||||||
|
public required long maxIdleTimeoutMinutes { get; init; }
|
||||||
|
public required string modelId { get; init; }
|
||||||
|
public required Dictionary<string, object?> performanceData { get; init; }
|
||||||
|
public required double progress { get; init; }
|
||||||
|
public required string? progressMessage { get; init; }
|
||||||
|
public required string projectId { get; init; }
|
||||||
|
public required string sourceApplication { get; init; }
|
||||||
|
public required string sourceApplicationVersion { get; init; }
|
||||||
|
public required Dictionary<string, object?> sourceFileData { get; init; }
|
||||||
|
public required string status { get; init; }
|
||||||
|
public required DateTime updatedAt { get; init; }
|
||||||
|
public required string versionId { get; init; }
|
||||||
|
public required LimitedUser user { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
using GraphQL;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Inputs;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Models;
|
||||||
|
using Speckle.Sdk.Api.GraphQL.Models.Responses;
|
||||||
|
using Version = Speckle.Sdk.Api.GraphQL.Models.Version;
|
||||||
|
|
||||||
|
namespace Speckle.Sdk.Api.GraphQL.Resources;
|
||||||
|
|
||||||
|
public sealed class IngestResource
|
||||||
|
{
|
||||||
|
private readonly ISpeckleGraphQLClient _client;
|
||||||
|
|
||||||
|
internal IngestResource(ISpeckleGraphQLClient client)
|
||||||
|
{
|
||||||
|
_client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="modelId"></param>
|
||||||
|
/// <param name="projectId"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<ResourceCollection<Ingest>> GetIngests(
|
||||||
|
string modelId,
|
||||||
|
string projectId,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
query GetIngest($modelId: String!, $projectId: String!) {
|
||||||
|
data:project(id: $projectId) {
|
||||||
|
data:model(id: $modelId) {
|
||||||
|
data:ingests {
|
||||||
|
cursor
|
||||||
|
items {
|
||||||
|
createdAt
|
||||||
|
errorReason
|
||||||
|
errorStacktrace
|
||||||
|
fileName
|
||||||
|
id
|
||||||
|
maxIdleTimeoutMinutes
|
||||||
|
modelId
|
||||||
|
performanceData
|
||||||
|
progress
|
||||||
|
progressMessage
|
||||||
|
projectId
|
||||||
|
sourceApplication
|
||||||
|
sourceApplicationVersion
|
||||||
|
sourceFileData
|
||||||
|
status
|
||||||
|
updatedAt
|
||||||
|
versionId
|
||||||
|
user {
|
||||||
|
avatar
|
||||||
|
bio
|
||||||
|
company
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
var request = new GraphQLRequest { Query = QUERY, Variables = new { modelId, projectId } };
|
||||||
|
|
||||||
|
var response = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<ResourceCollection<Ingest>>>>>(
|
||||||
|
request,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return response.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<bool> Update(IngestUpdateInput input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestUpdate($projectId: ID!, $input: IngestUpdateInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data: ingestMutations(projectId: $projectId) {
|
||||||
|
data: update(input: $input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input, input.projectId } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<bool>>>>(request, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<Ingest> Create(IngestCreateInput input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestCreate($projectId: ID!, $input: IngestCreateInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data:ingestMutations(projectId: $projectId) {
|
||||||
|
data:create(input: $input) {
|
||||||
|
createdAt
|
||||||
|
errorReason
|
||||||
|
errorStacktrace
|
||||||
|
fileName
|
||||||
|
id
|
||||||
|
maxIdleTimeoutMinutes
|
||||||
|
modelId
|
||||||
|
performanceData
|
||||||
|
progress
|
||||||
|
progressMessage
|
||||||
|
projectId
|
||||||
|
sourceApplication
|
||||||
|
sourceApplicationVersion
|
||||||
|
sourceFileData
|
||||||
|
status
|
||||||
|
updatedAt
|
||||||
|
versionId
|
||||||
|
user {
|
||||||
|
avatar
|
||||||
|
bio
|
||||||
|
company
|
||||||
|
id
|
||||||
|
name
|
||||||
|
role
|
||||||
|
verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input, input.projectId } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<Ingest>>>>(request, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<Version> End(IngestFinishInput input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestEnd($projectId: ID!, $input: IngestFinishInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data:ingestMutations(projectId: $projectId) {
|
||||||
|
data:end(input: $input) {
|
||||||
|
id
|
||||||
|
referencedObject
|
||||||
|
message
|
||||||
|
sourceApplication
|
||||||
|
createdAt
|
||||||
|
previewUrl
|
||||||
|
authorUser {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
bio
|
||||||
|
company
|
||||||
|
verified
|
||||||
|
role
|
||||||
|
avatar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input, input.projectId } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<Version>>>>(request, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<bool> Error(IngestErrorInput input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestError($projectId: ID!, $input: IngestErrorInput!) {
|
||||||
|
data: projectMutations {
|
||||||
|
data:ingestMutations(projectId: $projectId) {
|
||||||
|
data:error(input: $input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input, input.projectId } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<bool>>>>(request, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <param name="input"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <inheritdoc cref="ISpeckleGraphQLClient.ExecuteGraphQLRequest{T}"/>
|
||||||
|
public async Task<bool> Cancel(CancelRequestInput input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
//language=graphql
|
||||||
|
const string QUERY = """
|
||||||
|
mutation IngestCancel($projectId: ID!, $input: CancelRequestInput!) {
|
||||||
|
data:projectMutations {
|
||||||
|
data:ingestMutations(projectId: $projectId) {
|
||||||
|
data:cancel(input: $input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
GraphQLRequest request = new() { Query = QUERY, Variables = new { input, input.projectId } };
|
||||||
|
|
||||||
|
var res = await _client
|
||||||
|
.ExecuteGraphQLRequest<RequiredResponse<RequiredResponse<RequiredResponse<bool>>>>(request, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
return res.data.data.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
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;
|
||||||
|
using Speckle.Sdk.Transports;
|
||||||
|
|
||||||
|
namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources;
|
||||||
|
|
||||||
|
public class IngestResourceTests : IAsyncLifetime
|
||||||
|
{
|
||||||
|
private IClient _testUser;
|
||||||
|
private IngestResource Sut => _testUser.Ingest;
|
||||||
|
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 input = new IngestCreateInput(
|
||||||
|
"myTestFile",
|
||||||
|
1,
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
".NET",
|
||||||
|
"0.0.0",
|
||||||
|
new Dictionary<string, object?>()
|
||||||
|
);
|
||||||
|
Ingest ingest = await Sut.Create(input);
|
||||||
|
|
||||||
|
var errorInput = new IngestErrorInput("A bad thing happened", "Over hear!", ingest.id, _project.id);
|
||||||
|
var res = await Sut.Error(errorInput);
|
||||||
|
Assert.True(res);
|
||||||
|
|
||||||
|
var result = await Sut.GetIngests(_model.id, _project.id);
|
||||||
|
|
||||||
|
await Verify(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAndCancel()
|
||||||
|
{
|
||||||
|
var input = new IngestCreateInput(
|
||||||
|
"myTestFile",
|
||||||
|
1,
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
".NET",
|
||||||
|
"0.0.0",
|
||||||
|
new Dictionary<string, object?>()
|
||||||
|
);
|
||||||
|
Ingest ingest = await Sut.Create(input);
|
||||||
|
|
||||||
|
var errorInput = new CancelRequestInput(ingest.id, _project.id);
|
||||||
|
var res = await Sut.Cancel(errorInput);
|
||||||
|
Assert.True(res);
|
||||||
|
|
||||||
|
var result = await Sut.GetIngests(_model.id, _project.id);
|
||||||
|
|
||||||
|
await Verify(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAndEnd()
|
||||||
|
{
|
||||||
|
var create = new IngestCreateInput(
|
||||||
|
"myTestFile",
|
||||||
|
1,
|
||||||
|
_model.id,
|
||||||
|
_project.id,
|
||||||
|
".NET",
|
||||||
|
"0.0.0",
|
||||||
|
new Dictionary<string, object?>()
|
||||||
|
);
|
||||||
|
Ingest ingest = await Sut.Create(create);
|
||||||
|
|
||||||
|
var myObject = Fixtures.GenerateNestedObject();
|
||||||
|
var sendResult = await _operations.Send2(
|
||||||
|
_testUser.ServerUrl,
|
||||||
|
_project.id,
|
||||||
|
_testUser.Account.token,
|
||||||
|
myObject,
|
||||||
|
new Progress<ProgressArgs>(x =>
|
||||||
|
{
|
||||||
|
var updateInput = new IngestUpdateInput(
|
||||||
|
ingest.id,
|
||||||
|
x.Total == null ? null : x.Count / x.Total,
|
||||||
|
$"{x.Count} / {x.Total}",
|
||||||
|
_project.id
|
||||||
|
);
|
||||||
|
_ = Sut.Update(updateInput).Result;
|
||||||
|
}),
|
||||||
|
CancellationToken.None,
|
||||||
|
new(true, true)
|
||||||
|
);
|
||||||
|
|
||||||
|
var finish = new IngestFinishInput(ingest.id, "Yay! we completed", sendResult.RootId, _project.id);
|
||||||
|
var res = await Sut.End(finish);
|
||||||
|
Assert.NotNull(res);
|
||||||
|
|
||||||
|
var result = await Sut.GetIngests(_model.id, _project.id);
|
||||||
|
await Verify(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user