Skip to content
Merged
Show file tree
Hide file tree
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
Expand Up @@ -23,15 +23,20 @@ namespace Synapse.Dashboard.Pages.Workflows.Create;
public record CreateWorkflowViewState
{
/// <summary>
/// Gets/sets the <see cref="WorkflowDefinition"/>'s <see cref="Namespace"/>
/// Gets/sets the <see cref="ServerlessWorkflow.Sdk.Models.WorkflowDefinition"/>'s <see cref="Namespace"/>
/// </summary>
public string? Namespace { get; set; }

/// <summary>
/// Gets/sets the <see cref="WorkflowDefinition"/>'s name
/// Gets/sets the <see cref="ServerlessWorkflow.Sdk.Models.WorkflowDefinition"/>'s name
/// </summary>
public string? Name { get; set; }

/// <summary>
/// Gets/sets the <see cref="ServerlessWorkflow.Sdk.Models.WorkflowDefinition"/>'s DSL version
/// </summary>
public string? DslVersion { get; set; }

/// <summary>
/// Gets/sets the definition of the workflow to create
/// </summary>
Expand Down
116 changes: 87 additions & 29 deletions src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
using Semver;
using ServerlessWorkflow.Sdk.Models;
using Synapse.Api.Client.Services;
using Synapse.Dashboard.Components.DocumentDetailsStateManagement;
using Synapse.Dashboard.Components.ResourceEditorStateManagement;
using Synapse.Resources;
using System.Text.RegularExpressions;

namespace Synapse.Dashboard.Pages.Workflows.Create;

Expand Down Expand Up @@ -50,7 +49,8 @@ MonacoInterop monacoInterop

private TextModel? _textModel = null;
private string _textModelUri = string.Empty;
private bool _disposed;
private bool _disposed = false;
private bool _processingVersion = false;

/// <summary>
/// Gets the service used to perform logging
Expand Down Expand Up @@ -122,6 +122,10 @@ MonacoInterop monacoInterop
/// Gets an <see cref="IObservable{T}"/> used to observe changes to the state's <see cref="CreateWorkflowViewState.WorkflowDefinition"/> property
/// </summary>
public IObservable<WorkflowDefinition?> WorkflowDefinition => this.Select(state => state.WorkflowDefinition).DistinctUntilChanged();
/// <summary>
/// Gets an <see cref="IObservable{T}"/> used to observe changes to the state's <see cref="CreateWorkflowViewState.WorkflowDefinitionText"/> property
/// </summary>
public IObservable<string?> WorkflowDefinitionText => this.Select(state => state.WorkflowDefinitionText).DistinctUntilChanged();

/// <summary>
/// Gets an <see cref="IObservable{T}"/> used to observe changes to the state's <see cref="CreateWorkflowViewState.Loading"/> property
Expand Down Expand Up @@ -296,16 +300,17 @@ public async Task OnTextBasedEditorInitAsync()
/// <returns></returns>
public async Task SetTextBasedEditorLanguageAsync()
{
if (this.TextEditor == null)
{
return;
}
try
{
var language = this.MonacoEditorHelper.PreferredLanguage;
if (this.TextEditor != null)
{
this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri);
this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri);
await Global.SetModelLanguage(this.JSRuntime, this._textModel, language);
await this.TextEditor!.SetModel(this._textModel);
}
this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri);
this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri);
await Global.SetModelLanguage(this.JSRuntime, this._textModel, language);
await this.TextEditor!.SetModel(this._textModel);
}
catch (Exception ex)
{
Expand All @@ -320,19 +325,33 @@ public async Task SetTextBasedEditorLanguageAsync()
async Task SetTextEditorValueAsync()
{
var document = this.Get(state => state.WorkflowDefinitionText);
if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document))
if (this.TextEditor == null || string.IsNullOrWhiteSpace(document))
{
await this.TextEditor.SetValue(document);
try
{
//await this.TextEditor.Trigger("", "editor.action.triggerSuggest");
await this.TextEditor.Trigger("", "editor.action.formatDocument");
}
catch (Exception ex)
{
this.Logger.LogError("Unable to set text editor value: {exception}", ex.ToString());
}
return;
}
await this.TextEditor.SetValue(document);
try
{
await this.TextEditor.Trigger("", "editor.action.formatDocument");
}
catch (Exception ex)
{
this.Logger.LogError("Unable to set text editor value: {exception}", ex.ToString());
}
}

/// <summary>
/// Handles text editor content changes
/// </summary>
/// <param name="e">The <see cref="ModelContentChangedEvent"/></param>
/// <returns>An awaitable task</returns>
public async Task OnDidChangeModelContent(ModelContentChangedEvent e)
{
if (this.TextEditor == null) return;
var document = await this.TextEditor.GetValue();
this.Reduce(state => state with {
WorkflowDefinitionText = document
});
}

/// <summary>
Expand Down Expand Up @@ -446,10 +465,6 @@ public override async Task InitializeAsync()
string document = "";
if (definition != null)
{
if (definition.Document?.Dsl != null)
{
await this.SetValidationSchema($"v{definition.Document.Dsl}");
}
document = this.MonacoEditorHelper.PreferredLanguage == PreferredLanguage.JSON ?
this.JsonSerializer.SerializeToText(definition) :
this.YamlSerializer.SerializeToText(definition);
Expand All @@ -468,6 +483,25 @@ public override async Task InitializeAsync()
{
await this.GetWorkflowDefinitionAsync(workflow.ns, workflow.name);
}, cancellationToken: this.CancellationTokenSource.Token);
this.WorkflowDefinitionText.Where(document => !string.IsNullOrEmpty(document)).Throttle(new(100)).SubscribeAsync(async (document) => {
if (string.IsNullOrWhiteSpace(document))
{
return;
}
var currentDslVersion = this.Get(state => state.DslVersion);
var versionExtractor = new Regex("'?\"?(dsl|DSL)'?\"?\\s*:\\s*'?\"?([\\w\\.\\-\\+]*)'?\"?");
var match = versionExtractor.Match(document);
if (match == null)
{
return;
}
var documentDslVersion = match.Groups[2].Value;
if (documentDslVersion == currentDslVersion)
{
return;
}
await this.SetValidationSchema("v" + documentDslVersion);
}, cancellationToken: this.CancellationTokenSource.Token);
await base.InitializeAsync();
}

Expand All @@ -479,10 +513,34 @@ public override async Task InitializeAsync()
protected async Task SetValidationSchema(string? version = null)
{
version ??= await this.SpecificationSchemaManager.GetLatestVersion();
var schema = await this.SpecificationSchemaManager.GetSchema(version);
var type = $"create_{typeof(WorkflowDefinition).Name.ToLower()}_{version}";
await this.MonacoInterop.AddValidationSchemaAsync(schema, $"https://synapse.io/schemas/{type}.json", $"{type}*").ConfigureAwait(false);
this._textModelUri = this.MonacoEditorHelper.GetResourceUri(type);
var currentVersion = this.Get(state => state.DslVersion);
if (currentVersion == version)
{
return;
}
if (this._processingVersion)
{
return;
}
this.SetProblemDetails(null);
this._processingVersion = true;
try
{
var schema = await this.SpecificationSchemaManager.GetSchema(version);
var type = $"create_{typeof(WorkflowDefinition).Name.ToLower()}_{version}_schema";
await this.MonacoInterop.AddValidationSchemaAsync(schema, $"https://synapse.io/schemas/{type}.json", $"{type}*").ConfigureAwait(false);
this._textModelUri = this.MonacoEditorHelper.GetResourceUri(type);
this.Reduce(state => state with
{
DslVersion = version
});
}
catch (Exception ex)
{
this.Logger.LogError("Unable to set the validation schema: {exception}", ex.ToString());
this.SetProblemDetails(new ProblemDetails(new Uri("about:blank"), "Unable to set the validation schema", 404, $"Unable to set the validation schema for the specification version '{version}'. Make sure the version exists."));
}
this._processingVersion = false;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ else
<StandaloneCodeEditor @ref="Store.TextEditor"
ConstructionOptions="Store.StandaloneEditorConstructionOptions"
OnDidInit="Store.OnTextBasedEditorInitAsync"
OnDidChangeModelContent="Store.OnDidChangeModelContent"
CssClass="h-100" />
<div class="problems">
@if (problemDetails != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace Synapse.Dashboard.Services;
public class SpecificationSchemaManager(IYamlSerializer yamlSerializer, HttpClient httpClient)
{

Dictionary<string, string> _knownSchemas = new Dictionary<string, string>();
readonly Dictionary<string, string> _knownSchemas = [];

string? _latestVersion = null;

Expand Down Expand Up @@ -56,7 +56,7 @@ public async Task<string> GetLatestVersion()
/// <returns>A awaitable task</returns>
public async Task<string> GetSchema(string version)
{
if (this._knownSchemas.ContainsKey(version)) return this._knownSchemas[version];
if (_knownSchemas.TryGetValue(version, out string? value)) return value;
var address = $"https://raw.githubusercontent.com/serverlessworkflow/specification/{version}/schema/workflow.yaml";
var yamlSchema = await this.HttpClient.GetStringAsync(address);
this._knownSchemas.Add(version, this.YamlSerializer.ConvertToJson(yamlSchema));
Expand Down