Skip to content

Commit

Permalink
fix(Dashboard): added specification schema support
Browse files Browse the repository at this point in the history
Signed-off-by: Jean-Baptiste Bianchi <[email protected]>
  • Loading branch information
JBBianchi committed Aug 30, 2024
1 parent 957fa6a commit 2b50bef
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ public Func<StandaloneCodeEditor, StandaloneEditorConstructionOptions> GetStanda
Language = language,
ReadOnly = readOnly,
Value = value,
TabSize = 2
TabSize = 2,
QuickSuggestions = new QuickSuggestionsOptions
{
Other = "true",
Strings = "true"
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@
</div>
break;
case "Delete":
<a class="dropdown-item text-danger" href="#" @onclick="async _ => await OnDeleteClickedAsync(instance)" @onclick:stopPropagation="true"><Icon Name="IconName.Trash" /></a>
<button class="btn btn-sm text-danger" @onclick="async _ => await OnDeleteClickedAsync(instance)" @onclick:stopPropagation="true"><Icon Name="IconName.Trash" /></button>
break;
default:
break;
Expand Down
2 changes: 2 additions & 0 deletions src/dashboard/Synapse.Dashboard/Layout/MainLayout.razor
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@
</main>
</div>

<Toasts class="p-3" AutoHide="true" Delay="2000" Placement="ToastsPlacement.MiddleCenter" />

@code{

ClaimsPrincipal? user;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public record CreateWorkflowViewState
{
Document = new()
{
Dsl = "1.0.0",
Dsl = "1.0.0-alpha2",
Namespace = Neuroglia.Data.Infrastructure.ResourceOriented.Namespace.DefaultNamespaceName,
Name = "new-workflow",
Version = "0.1.0"
Expand Down
50 changes: 47 additions & 3 deletions src/dashboard/Synapse.Dashboard/Pages/Workflows/Create/Store.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Json.Schema;
using JsonCons.Utilities;
using Neuroglia.Data;
using Semver;
Expand All @@ -30,19 +31,23 @@ namespace Synapse.Dashboard.Pages.Workflows.Create;
/// <param name="yamlSerializer">The service used to serialize/deserialize data to/from YAML</param>
/// <param name="jsRuntime">The service used for JS interop</param>
/// <param name="navigationManager">The service used to provides an abstraction for querying and managing URI navigation</param>
/// <param name="specificationSchemaManager">The service used to download the specification schemas</param>
/// <param name="monacoInterop">The service to build a bridge with the monaco interop extension</param>
public class CreateWorkflowViewStore(
ISynapseApiClient api,
IMonacoEditorHelper monacoEditorHelper,
IJsonSerializer jsonSerializer,
IYamlSerializer yamlSerializer,
IJSRuntime jsRuntime,
NavigationManager navigationManager
NavigationManager navigationManager,
SpecificationSchemaManager specificationSchemaManager,
MonacoInterop monacoInterop
)
: ComponentStore<CreateWorkflowViewState>(new())
{

private TextModel? _textModel = null;
private readonly string _textModelUri = monacoEditorHelper.GetResourceUri(typeof(WorkflowDefinition).Name.ToLower());
private string _textModelUri = string.Empty;
private bool _disposed;

/// <summary>
Expand Down Expand Up @@ -75,6 +80,16 @@ NavigationManager navigationManager
/// </summary>
protected NavigationManager NavigationManager { get; } = navigationManager;

/// <summary>
/// Gets the service used to download the specification schemas
/// </summary>
protected SpecificationSchemaManager SpecificationSchemaManager { get; } = specificationSchemaManager;

/// <summary>
/// Gets the service to build a bridge with the monaco interop extension
/// </summary>
protected MonacoInterop MonacoInterop { get; } = monacoInterop;

/// <summary>
/// The <see cref="BlazorMonaco.Editor.StandaloneEditorConstructionOptions"/> provider function
/// </summary>
Expand Down Expand Up @@ -281,6 +296,7 @@ public async Task SetTextBasedEditorLanguageAsync()
{
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);
}
}
Expand All @@ -301,6 +317,16 @@ async Task SetTextEditorValueAsync()
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)
{
Console.WriteLine(ex.ToString());
// todo: handle exception
}
}
}

Expand Down Expand Up @@ -416,6 +442,10 @@ 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 @@ -424,7 +454,7 @@ public override async Task InitializeAsync()
{
WorkflowDefinitionText = document
});
await this.SetTextEditorValueAsync();
await this.OnTextBasedEditorInitAsync();
}, cancellationToken: this.CancellationTokenSource.Token);
Observable.CombineLatest(
this.Namespace.Where(ns => !string.IsNullOrWhiteSpace(ns)),
Expand All @@ -437,6 +467,20 @@ public override async Task InitializeAsync()
await base.InitializeAsync();
}

/// <summary>
/// Adds validation for the specification of the specified version
/// </summary>
/// <param name="version">The version of the spec to add the validation for</param>
/// <returns>An awaitable task</returns>
protected async Task SetValidationSchema(string? version = null)
{
version = 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);
}

/// <summary>
/// Disposes of the store
/// </summary>
Expand Down
48 changes: 39 additions & 9 deletions src/dashboard/Synapse.Dashboard/Pages/Workflows/Details/Store.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ namespace Synapse.Dashboard.Pages.Workflows.Details;
/// <param name="jsonSerializer">The service used to serialize and deserialize JSON</param>
/// <param name="yamlSerializer">The service used to serialize and deserialize YAML</param>
/// <param name="monacoInterop">The service used to build a bridge with the monaco interop extension</param>
/// <param name="toastService">The service used display toast messages</param>
public class WorkflowDetailsStore(
ISynapseApiClient apiClient,
ResourceWatchEventHubClient resourceEventHub,
IJSRuntime jsRuntime,
IMonacoEditorHelper monacoEditorHelper,
IJsonSerializer jsonSerializer,
IYamlSerializer yamlSerializer,
MonacoInterop monacoInterop
MonacoInterop monacoInterop,
ToastService toastService
)
: NamespacedResourceManagementComponentStore<WorkflowDetailsState, WorkflowInstance>(apiClient, resourceEventHub)
{
Expand Down Expand Up @@ -68,6 +70,11 @@ MonacoInterop monacoInterop
/// </summary>
protected MonacoInterop MonacoInterop { get; } = monacoInterop;

/// <summary>
/// Gets the service used display toast messages
/// </summary>
protected ToastService ToastService { get; } = toastService;

/// <summary>
/// Gets/sets the <see cref="BlazorMonaco.Editor.StandaloneEditorConstructionOptions"/> provider function
/// </summary>
Expand Down Expand Up @@ -191,7 +198,7 @@ public void SetWorkflowInstanceName(string? instanceName)
/// <summary>
/// Changes the value of the text editor
/// </summary>
/// <returns></returns>
/// <returns>A awaitable task</returns>
async Task SetTextEditorValueAsync()
{
var document = this.Get(state => state.WorkflowDefinitionJson);
Expand Down Expand Up @@ -219,7 +226,7 @@ async Task SetTextEditorValueAsync()
/// </summary>
/// <param name="ns"></param>
/// <param name="name"></param>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task GetWorkflowAsync(string ns, string name)
{
try
Expand All @@ -241,7 +248,7 @@ public async Task GetWorkflowAsync(string ns, string name)
/// Handles changed of the text editor's language
/// </summary>
/// <param name="_"></param>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task ToggleTextBasedEditorLanguageAsync(string _)
{
await this.OnTextBasedEditorInitAsync();
Expand All @@ -250,7 +257,7 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _)
/// <summary>
/// Handles initialization of the text editor
/// </summary>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task OnTextBasedEditorInitAsync()
{
await this.SetTextBasedEditorLanguageAsync();
Expand All @@ -260,7 +267,7 @@ public async Task OnTextBasedEditorInitAsync()
/// <summary>
/// Sets the language of the text editor
/// </summary>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task SetTextBasedEditorLanguageAsync()
{
try
Expand Down Expand Up @@ -289,10 +296,32 @@ public async Task SetTextBasedEditorLanguageAsync()
}
}

/// <summary>
/// Copies to content of the Monaco editor to the clipboard
/// </summary>
/// <returns>A awaitable task</returns>
public async Task OnCopyToClipboard()
{
if (this.TextEditor == null) return;
var text = await this.TextEditor.GetValue();
if (string.IsNullOrWhiteSpace(text)) return;
try
{
await this.JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text);
this.ToastService.Notify(new(ToastType.Success, "Definition copied to the clipboard!"));
}
catch (Exception ex)
{
this.ToastService.Notify(new(ToastType.Danger, "Failed to copy the definition to the clipboard."));
Console.WriteLine(ex.ToString());
// todo: handle exception
}
}

/// <summary>
/// Displays the modal used to provide the new workflow input
/// </summary>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task OnShowCreateInstanceAsync(WorkflowDefinition workflowDefinition)
{
if (this.Modal != null)
Expand All @@ -310,7 +339,7 @@ public async Task OnShowCreateInstanceAsync(WorkflowDefinition workflowDefinitio
/// Creates a new instance of the workflow
/// </summary>
/// <param name="input">The input data, if any</param>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task CreateInstanceAsync(string input)
{
var workflowName = this.Get(state => state.ActiveResourceName);
Expand Down Expand Up @@ -358,7 +387,7 @@ public async Task CreateInstanceAsync(string input)
/// Delete the provided <see cref="WorkflowInstance"/>
/// </summary>
/// <param name="workflowInstance">The workflow instance to delete</param>
/// <returns></returns>
/// <returns>A awaitable task</returns>
public async Task DeleteWorkflowInstanceAsync(WorkflowInstance workflowInstance)
{
await this.ApiClient.ManageNamespaced<WorkflowInstance>().DeleteAsync(workflowInstance.GetName(), workflowInstance.GetNamespace()!).ConfigureAwait(false);
Expand All @@ -368,6 +397,7 @@ public async Task DeleteWorkflowInstanceAsync(WorkflowInstance workflowInstance)
/// Selects the target node in the code editor
/// </summary>
/// <param name="e">The source of the event</param>
/// <returns>A awaitable task</returns>
public async Task SelectNodeInEditor(GraphEventArgs<MouseEventArgs> e)
{
if (e.GraphElement == null) return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
Columns="@columns"
OnSearchInput="Store.SetSearchTerm"
OnShowDetails="OnShowInstanceDetails"
OnDelete="OnDeleteWorkflowInstanceAsync"
OnToggleSelected="Store.ToggleResourceSelection"
OnDeleteSelected="OnDeleteSelectedResourcesAsync" />
<Button Outline="true" Color="ButtonColor.Primary" @onclick="async _ => await Store.OnShowCreateInstanceAsync(workflowDefinition)" class="w-100 mt-3">
Expand Down Expand Up @@ -75,9 +76,14 @@
{
<div class="d-flex flex-column h-100 mh-100">
<div class="d-flex justify-content-between mb-2">
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="_ => OnCreateWorkflowVersion()">
<Icon Name="IconName.Copy" />
</Button>
<div>
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" class="ms-2" @onclick="_ => OnCreateWorkflowVersion()">
<Icon Name="IconName.FileEarmarkPlus" />
</Button>
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" class="ms-2" @onclick="_ => Store.OnCopyToClipboard()">
<Icon Name="IconName.Clipboard" />
</Button>
</div>
<PreferredLanguageSelector PreferedLanguageChange="Store.ToggleTextBasedEditorLanguageAsync" />
</div>
<div class="flex-grow">
Expand Down
1 change: 1 addition & 0 deletions src/dashboard/Synapse.Dashboard/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

builder.Services.AddSingleton<IMonacoEditorHelper, MonacoEditorHelper>();
builder.Services.AddScoped<IApplicationLayout, ApplicationLayout>();
builder.Services.AddScoped<SpecificationSchemaManager>();
builder.Services.AddSingleton<JSInterop>();
builder.Services.AddSingleton<MonacoInterop>();
builder.Services.AddSingleton<IGraphLayoutService, GraphLayoutService>();
Expand Down
Loading

0 comments on commit 2b50bef

Please sign in to comment.