Skip to content

Updates the modal to create a new workflow instance by adding a form-based editor for the instance's input #467

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
@@ -43,7 +43,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.0" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.1" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
</ItemGroup>

6 changes: 3 additions & 3 deletions src/api/Synapse.Api.Http/Synapse.Api.Http.csproj
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
<OutputType>Library</OutputType>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
@@ -43,8 +43,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.16.2" />
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.16.2" />
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.18.0" />
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.18.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="7.1.0" />
</ItemGroup>

2 changes: 1 addition & 1 deletion src/api/Synapse.Api.Server/Synapse.Api.Server.csproj
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
4 changes: 2 additions & 2 deletions src/cli/Synapse.Cli/Synapse.Cli.csproj
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
@@ -33,7 +33,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="moment.net" Version="1.3.4" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.1" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha5.2" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
<NeutralLanguage>en</NeutralLanguage>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha5.3</VersionSuffix>
<VersionSuffix>alpha5.7</VersionSuffix>
<AssemblyVersion>$(VersionPrefix)</AssemblyVersion>
<FileVersion>$(VersionPrefix)</FileVersion>
<Authors>The Synapse Authors</Authors>
Original file line number Diff line number Diff line change
@@ -11,8 +11,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Neuroglia.Data.Infrastructure.ResourceOriented;
using ServerlessWorkflow.Sdk;
using ServerlessWorkflow.Sdk.Models.Authentication;
using Synapse.Core.Infrastructure.Services;
using System.Text;

namespace Synapse;
@@ -41,28 +45,28 @@ public class AuthorizationInfo(string scheme, string parameter)
/// <summary>
/// Creates a new <see cref="AuthorizationInfo"/> based on the specified <see cref="AuthenticationPolicyDefinition"/>
/// </summary>
/// <param name="workflow">The <see cref="WorkflowDefinition"/> that defines the <see cref="AuthenticationPolicyDefinition"/> to create a new <see cref="AuthorizationInfo"/> for</param>
/// <param name="authentication">The <see cref="AuthenticationPolicyDefinition"/> to create a new <see cref="AuthorizationInfo"/> for</param>
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
/// <param name="workflow">The <see cref="WorkflowDefinition"/>, if any, that defines the <see cref="AuthenticationPolicyDefinition"/> to create a new <see cref="AuthorizationInfo"/> for</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>A new <see cref="AuthorizationInfo"/> based on the specified <see cref="AuthenticationPolicyDefinition"/></returns>
public static async Task<AuthorizationInfo> CreateAsync(WorkflowDefinition workflow, AuthenticationPolicyDefinition authentication, IServiceProvider serviceProvider, CancellationToken cancellationToken = default)
public static async Task<AuthorizationInfo> CreateAsync( AuthenticationPolicyDefinition authentication, IServiceProvider serviceProvider, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(authentication);
ArgumentNullException.ThrowIfNull(serviceProvider);
string scheme, parameter;
var logger = serviceProvider.GetRequiredService<ILoggerFactory>().CreateLogger("AuthenticationPolicyHandler");
if (!string.IsNullOrWhiteSpace(authentication.Use))
{
if (workflow.Use?.Authentications?.TryGetValue(authentication.Use, out AuthenticationPolicyDefinition? referencedAuthentication) != true || referencedAuthentication == null) throw new NullReferenceException($"Failed to find the specified authentication policy '{authentication.Use}'");
if (workflow?.Use?.Authentications?.TryGetValue(authentication.Use, out AuthenticationPolicyDefinition? referencedAuthentication) != true || referencedAuthentication == null) throw new NullReferenceException($"Failed to find the specified authentication policy '{authentication.Use}'");
else authentication = referencedAuthentication;
}
var isSecretBased = authentication.TryGetBaseSecret(out var secretName);
object? authenticationProperties = null;
if (isSecretBased && !string.IsNullOrWhiteSpace(secretName))
{
logger.LogDebug("Authentication is secret based");
var secretsManager = serviceProvider.GetRequiredService<ISecretsManager>();
var secretsManager = serviceProvider.GetService<ISecretsManager>() ?? throw new NotSupportedException("Secret based authentication is not supported in this context");
var secrets = await secretsManager.GetSecretsAsync(cancellationToken).ConfigureAwait(false);
if (!secrets.TryGetValue(secretName, out authenticationProperties) || authenticationProperties == null)
{
Original file line number Diff line number Diff line change
@@ -25,16 +25,16 @@ public static class HttpClientExtensions
/// Configures the <see cref="HttpClient"/> to use the specified authentication mechanism
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/> to configure</param>
/// <param name="workflow">The <see cref="WorkflowDefinition"/> that defines the authentication to configure</param>
/// <param name="authentication">An object that describes the authentication mechanism to use</param>
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
/// <param name="workflow">The <see cref="WorkflowDefinition"/>, if any, that defines the authentication to configure</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>A new awaitable <see cref="Task"/></returns>
public static async Task ConfigureAuthenticationAsync(this HttpClient httpClient, WorkflowDefinition workflow, AuthenticationPolicyDefinition? authentication, IServiceProvider serviceProvider, CancellationToken cancellationToken = default)
public static async Task ConfigureAuthenticationAsync(this HttpClient httpClient, AuthenticationPolicyDefinition? authentication, IServiceProvider serviceProvider, WorkflowDefinition? workflow = null, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(workflow);
ArgumentNullException.ThrowIfNull(serviceProvider);
if (authentication == null) return;
var authorization = await AuthorizationInfo.CreateAsync(workflow, authentication, serviceProvider, cancellationToken).ConfigureAwait(false);
var authorization = await AuthorizationInfo.CreateAsync(authentication, serviceProvider, workflow, cancellationToken).ConfigureAwait(false);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Parameter);
}

Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@
using Neuroglia.Plugins;
using Neuroglia.Security.Services;
using Neuroglia.Serialization;
using Neuroglia.Serialization.Xml;
using ServerlessWorkflow.Sdk.IO;
using Synapse.Core.Infrastructure.Services;
using Synapse.Resources;
@@ -45,6 +46,7 @@ public static IServiceCollection AddSynapse(this IServiceCollection services, IC
{
services.AddHttpClient();
services.AddSerialization();
services.AddSingleton<IXmlSerializer, XmlSerializer>();
services.AddMediator();
services.AddScoped<IUserInfoProvider, UserInfoProvider>();
services.AddServerlessWorkflowIO();
@@ -61,8 +63,12 @@ public static IServiceCollection AddSynapse(this IServiceCollection services, IC
services.AddSingleton<ITextDocumentRepository<string>, RedisTextDocumentRepository<string>>();
services.AddSingleton<ITextDocumentRepository>(provider => provider.GetRequiredService<ITextDocumentRepository<string>>());

services.AddSingleton<IExternalResourceProvider, ExternalResourceProvider>();
services.AddSingleton<IOAuth2TokenManager, OAuth2TokenManager>();
services.AddSingleton<ISchemaHandlerProvider, SchemaHandlerProvider>();
services.AddSingleton<ISchemaHandler, AvroSchemaHandler>();
services.AddSingleton<ISchemaHandler, JsonSchemaHandler>();
services.AddSingleton<ISchemaHandler, XmlSchemaHandler>();

services.AddScoped<IResourceRepository, ResourceRepository>();
services.AddScoped<IAdmissionControl, AdmissionControl>();
177 changes: 177 additions & 0 deletions src/core/Synapse.Core.Infrastructure/Services/AvroSchemaHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
// Copyright © 2024-Present The Synapse Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"),
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Neuroglia;
using Neuroglia.Serialization;
using ServerlessWorkflow.Sdk;
using System.Net;
using Avro.IO;
using Avro;
using Avro.Generic;

namespace Synapse.Core.Infrastructure.Services;

/// <summary>
/// Represents the <see cref="ISchemaHandler"/> implementation used to handle Avro schemas
/// </summary>
/// <param name="externalResourceProvider">The service used to provide external resources</param>
/// <param name="jsonSerializer">The service used to serialize/deserialize data to/from JSON</param>
public class AvroSchemaHandler(IExternalResourceProvider externalResourceProvider, IJsonSerializer jsonSerializer)
: ISchemaHandler
{

/// <summary>
/// Gets the service used to provide external resources
/// </summary>
protected IExternalResourceProvider ExternalResourceProvider { get; } = externalResourceProvider;

/// <summary>
/// Gets the service used to serialize/deserialize data to/from JSON
/// </summary>
protected IJsonSerializer JsonSerializer { get; } = jsonSerializer;

/// <inheritdoc/>
public virtual bool Supports(string format) => format.Equals(SchemaFormat.Avro, StringComparison.OrdinalIgnoreCase);

/// <inheritdoc/>
public virtual async Task<IOperationResult> ValidateAsync(object graph, SchemaDefinition schema, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(graph);
ArgumentNullException.ThrowIfNull(schema);
if (!this.Supports(schema.Format)) throw new NotSupportedException($"The specified schema format '{schema.Format}' is not supported in this context");
Schema avroSchema;
try
{
var json = string.Empty;
if (schema.Resource == null)
{
json = this.JsonSerializer.SerializeToText(schema.Document);
}
else
{
using var stream = await this.ExternalResourceProvider.ReadAsync(schema.Resource, cancellationToken: cancellationToken).ConfigureAwait(false);
using var streamReader = new StreamReader(stream);
json = await streamReader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
}
avroSchema = Schema.Parse(json);
}
catch (Exception ex)
{
return new OperationResult((int)HttpStatusCode.BadRequest, null, new Neuroglia.Error()
{
Type = ErrorType.Validation,
Title = ErrorTitle.Validation,
Status = ErrorStatus.Validation,
Detail = $"An error occurred while parsing the specified schema: {ex}"
});
}
byte[] avroData;
try
{
using var memoryStream = new MemoryStream();
var writer = new BinaryEncoder(memoryStream);
var datumWriter = new GenericDatumWriter<object>(avroSchema);
var avroObject = ConvertToAvroCompatible(graph, avroSchema);
datumWriter.Write(avroObject, writer);
avroData = memoryStream.ToArray();
}
catch (Exception ex)
{
return new OperationResult((int)HttpStatusCode.BadRequest, null, new Neuroglia.Error()
{
Type = ErrorType.Validation,
Title = ErrorTitle.Validation,
Status = ErrorStatus.Validation,
Detail = $"An error occurred while serializing the specified data to Avro: {ex}"
});
}
try
{
using var memoryStream = new MemoryStream(avroData);
var reader = new BinaryDecoder(memoryStream);
var datumReader = new GenericDatumReader<GenericRecord>(avroSchema, avroSchema);
datumReader.Read(null!, reader);
return await Task.FromResult(new OperationResult((int)HttpStatusCode.OK));
}
catch (Exception ex)
{
return new OperationResult((int)HttpStatusCode.BadRequest, null, new Neuroglia.Error()
{
Type = ErrorType.Validation,
Title = ErrorTitle.Validation,
Status = ErrorStatus.Validation,
Detail = ex.Message
});
}
}

object ConvertToAvroCompatible(object graph, Schema schema)
{
return schema switch
{
RecordSchema recordSchema => ConvertToGenericRecord(graph, recordSchema),
ArraySchema arraySchema => ConvertToArray(graph, arraySchema),
MapSchema mapSchema => ConvertToMap(graph, mapSchema),
UnionSchema unionSchema => ConvertToUnion(graph, unionSchema),
FixedSchema fixedSchema => graph,
EnumSchema enumSchema => graph,
PrimitiveSchema primitiveSchema => graph,
_ => throw new NotSupportedException($"Unsupported schema type: {schema.GetType().Name}")
};
}

GenericRecord ConvertToGenericRecord(object graph, RecordSchema recordSchema)
{
var record = new GenericRecord(recordSchema);
var properties = graph.GetType().GetProperties();
foreach (var field in recordSchema.Fields)
{
var property = properties.FirstOrDefault(p => p.Name.Equals(field.Name, StringComparison.OrdinalIgnoreCase));
if (property != null)
{
var value = property.GetValue(graph)!;
record.Add(field.Name, ConvertToAvroCompatible(value, field.Schema));
}
}
return record;
}

object ConvertToArray(object graph, ArraySchema arraySchema)
{
if (graph is not IEnumerable<object> enumerable) throw new InvalidOperationException("Provided object is not an array or enumerable");
return enumerable.Select(item => ConvertToAvroCompatible(item, arraySchema.ItemSchema)).ToList();
}

object ConvertToMap(object graph, MapSchema mapSchema)
{
if (graph is not IDictionary<string, object> dictionary) throw new InvalidOperationException("Provided object is not a map or dictionary");
return dictionary.ToDictionary(kvp => kvp.Key, kvp => ConvertToAvroCompatible(kvp.Value, mapSchema.ValueSchema));
}

object ConvertToUnion(object graph, UnionSchema unionSchema)
{
foreach (var schema in unionSchema.Schemas)
{
try
{
return ConvertToAvroCompatible(graph, schema);
}
catch
{
// Continue to next schema in the union
}
}
throw new InvalidOperationException("Provided object does not match any schema in the union.");
}

}
Loading