Skip to content

Commit 0406e79

Browse files
authored
Merge pull request #408 from serverlessworkflow/fix-dashboard-version-lookup
Improved schema validation in the workflow editor of the Dashboard
2 parents a127e94 + 952ff67 commit 0406e79

File tree

4 files changed

+97
-33
lines changed

4 files changed

+97
-33
lines changed

src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/State.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,20 @@ namespace Synapse.Dashboard.Pages.Workflows.Create;
2323
public record CreateWorkflowViewState
2424
{
2525
/// <summary>
26-
/// Gets/sets the <see cref="WorkflowDefinition"/>'s <see cref="Namespace"/>
26+
/// Gets/sets the <see cref="ServerlessWorkflow.Sdk.Models.WorkflowDefinition"/>'s <see cref="Namespace"/>
2727
/// </summary>
2828
public string? Namespace { get; set; }
2929

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

35+
/// <summary>
36+
/// Gets/sets the <see cref="ServerlessWorkflow.Sdk.Models.WorkflowDefinition"/>'s DSL version
37+
/// </summary>
38+
public string? DslVersion { get; set; }
39+
3540
/// <summary>
3641
/// Gets/sets the definition of the workflow to create
3742
/// </summary>

src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616
using Semver;
1717
using ServerlessWorkflow.Sdk.Models;
1818
using Synapse.Api.Client.Services;
19-
using Synapse.Dashboard.Components.DocumentDetailsStateManagement;
20-
using Synapse.Dashboard.Components.ResourceEditorStateManagement;
2119
using Synapse.Resources;
20+
using System.Text.RegularExpressions;
2221

2322
namespace Synapse.Dashboard.Pages.Workflows.Create;
2423

@@ -50,7 +49,8 @@ MonacoInterop monacoInterop
5049

5150
private TextModel? _textModel = null;
5251
private string _textModelUri = string.Empty;
53-
private bool _disposed;
52+
private bool _disposed = false;
53+
private bool _processingVersion = false;
5454

5555
/// <summary>
5656
/// Gets the service used to perform logging
@@ -122,6 +122,10 @@ MonacoInterop monacoInterop
122122
/// Gets an <see cref="IObservable{T}"/> used to observe changes to the state's <see cref="CreateWorkflowViewState.WorkflowDefinition"/> property
123123
/// </summary>
124124
public IObservable<WorkflowDefinition?> WorkflowDefinition => this.Select(state => state.WorkflowDefinition).DistinctUntilChanged();
125+
/// <summary>
126+
/// Gets an <see cref="IObservable{T}"/> used to observe changes to the state's <see cref="CreateWorkflowViewState.WorkflowDefinitionText"/> property
127+
/// </summary>
128+
public IObservable<string?> WorkflowDefinitionText => this.Select(state => state.WorkflowDefinitionText).DistinctUntilChanged();
125129

126130
/// <summary>
127131
/// Gets an <see cref="IObservable{T}"/> used to observe changes to the state's <see cref="CreateWorkflowViewState.Loading"/> property
@@ -296,16 +300,17 @@ public async Task OnTextBasedEditorInitAsync()
296300
/// <returns></returns>
297301
public async Task SetTextBasedEditorLanguageAsync()
298302
{
303+
if (this.TextEditor == null)
304+
{
305+
return;
306+
}
299307
try
300308
{
301309
var language = this.MonacoEditorHelper.PreferredLanguage;
302-
if (this.TextEditor != null)
303-
{
304-
this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri);
305-
this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri);
306-
await Global.SetModelLanguage(this.JSRuntime, this._textModel, language);
307-
await this.TextEditor!.SetModel(this._textModel);
308-
}
310+
this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri);
311+
this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri);
312+
await Global.SetModelLanguage(this.JSRuntime, this._textModel, language);
313+
await this.TextEditor!.SetModel(this._textModel);
309314
}
310315
catch (Exception ex)
311316
{
@@ -320,19 +325,33 @@ public async Task SetTextBasedEditorLanguageAsync()
320325
async Task SetTextEditorValueAsync()
321326
{
322327
var document = this.Get(state => state.WorkflowDefinitionText);
323-
if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document))
328+
if (this.TextEditor == null || string.IsNullOrWhiteSpace(document))
324329
{
325-
await this.TextEditor.SetValue(document);
326-
try
327-
{
328-
//await this.TextEditor.Trigger("", "editor.action.triggerSuggest");
329-
await this.TextEditor.Trigger("", "editor.action.formatDocument");
330-
}
331-
catch (Exception ex)
332-
{
333-
this.Logger.LogError("Unable to set text editor value: {exception}", ex.ToString());
334-
}
330+
return;
335331
}
332+
await this.TextEditor.SetValue(document);
333+
try
334+
{
335+
await this.TextEditor.Trigger("", "editor.action.formatDocument");
336+
}
337+
catch (Exception ex)
338+
{
339+
this.Logger.LogError("Unable to set text editor value: {exception}", ex.ToString());
340+
}
341+
}
342+
343+
/// <summary>
344+
/// Handles text editor content changes
345+
/// </summary>
346+
/// <param name="e">The <see cref="ModelContentChangedEvent"/></param>
347+
/// <returns>An awaitable task</returns>
348+
public async Task OnDidChangeModelContent(ModelContentChangedEvent e)
349+
{
350+
if (this.TextEditor == null) return;
351+
var document = await this.TextEditor.GetValue();
352+
this.Reduce(state => state with {
353+
WorkflowDefinitionText = document
354+
});
336355
}
337356

338357
/// <summary>
@@ -446,10 +465,6 @@ public override async Task InitializeAsync()
446465
string document = "";
447466
if (definition != null)
448467
{
449-
if (definition.Document?.Dsl != null)
450-
{
451-
await this.SetValidationSchema($"v{definition.Document.Dsl}");
452-
}
453468
document = this.MonacoEditorHelper.PreferredLanguage == PreferredLanguage.JSON ?
454469
this.JsonSerializer.SerializeToText(definition) :
455470
this.YamlSerializer.SerializeToText(definition);
@@ -468,6 +483,25 @@ public override async Task InitializeAsync()
468483
{
469484
await this.GetWorkflowDefinitionAsync(workflow.ns, workflow.name);
470485
}, cancellationToken: this.CancellationTokenSource.Token);
486+
this.WorkflowDefinitionText.Where(document => !string.IsNullOrEmpty(document)).Throttle(new(100)).SubscribeAsync(async (document) => {
487+
if (string.IsNullOrWhiteSpace(document))
488+
{
489+
return;
490+
}
491+
var currentDslVersion = this.Get(state => state.DslVersion);
492+
var versionExtractor = new Regex("'?\"?(dsl|DSL)'?\"?\\s*:\\s*'?\"?([\\w\\.\\-\\+]*)'?\"?");
493+
var match = versionExtractor.Match(document);
494+
if (match == null)
495+
{
496+
return;
497+
}
498+
var documentDslVersion = match.Groups[2].Value;
499+
if (documentDslVersion == currentDslVersion)
500+
{
501+
return;
502+
}
503+
await this.SetValidationSchema("v" + documentDslVersion);
504+
}, cancellationToken: this.CancellationTokenSource.Token);
471505
await base.InitializeAsync();
472506
}
473507

@@ -479,10 +513,34 @@ public override async Task InitializeAsync()
479513
protected async Task SetValidationSchema(string? version = null)
480514
{
481515
version ??= await this.SpecificationSchemaManager.GetLatestVersion();
482-
var schema = await this.SpecificationSchemaManager.GetSchema(version);
483-
var type = $"create_{typeof(WorkflowDefinition).Name.ToLower()}_{version}";
484-
await this.MonacoInterop.AddValidationSchemaAsync(schema, $"https://synapse.io/schemas/{type}.json", $"{type}*").ConfigureAwait(false);
485-
this._textModelUri = this.MonacoEditorHelper.GetResourceUri(type);
516+
var currentVersion = this.Get(state => state.DslVersion);
517+
if (currentVersion == version)
518+
{
519+
return;
520+
}
521+
if (this._processingVersion)
522+
{
523+
return;
524+
}
525+
this.SetProblemDetails(null);
526+
this._processingVersion = true;
527+
try
528+
{
529+
var schema = await this.SpecificationSchemaManager.GetSchema(version);
530+
var type = $"create_{typeof(WorkflowDefinition).Name.ToLower()}_{version}_schema";
531+
await this.MonacoInterop.AddValidationSchemaAsync(schema, $"https://synapse.io/schemas/{type}.json", $"{type}*").ConfigureAwait(false);
532+
this._textModelUri = this.MonacoEditorHelper.GetResourceUri(type);
533+
this.Reduce(state => state with
534+
{
535+
DslVersion = version
536+
});
537+
}
538+
catch (Exception ex)
539+
{
540+
this.Logger.LogError("Unable to set the validation schema: {exception}", ex.ToString());
541+
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."));
542+
}
543+
this._processingVersion = false;
486544
}
487545

488546
/// <summary>

src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/View.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ else
3939
<StandaloneCodeEditor @ref="Store.TextEditor"
4040
ConstructionOptions="Store.StandaloneEditorConstructionOptions"
4141
OnDidInit="Store.OnTextBasedEditorInitAsync"
42+
OnDidChangeModelContent="Store.OnDidChangeModelContent"
4243
CssClass="h-100" />
4344
<div class="problems">
4445
@if (problemDetails != null)

src/dashboard/Synapse.Dashboard/Services/SpecificationSchemaManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ namespace Synapse.Dashboard.Services;
2323
public class SpecificationSchemaManager(IYamlSerializer yamlSerializer, HttpClient httpClient)
2424
{
2525

26-
Dictionary<string, string> _knownSchemas = new Dictionary<string, string>();
26+
readonly Dictionary<string, string> _knownSchemas = [];
2727

2828
string? _latestVersion = null;
2929

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

0 commit comments

Comments
 (0)