Skip to content

Commit 2b50bef

Browse files
committed
fix(Dashboard): added specification schema support
Signed-off-by: Jean-Baptiste Bianchi <[email protected]>
1 parent 957fa6a commit 2b50bef

File tree

9 files changed

+199
-18
lines changed

9 files changed

+199
-18
lines changed

src/dashboard/Synapse.Dashboard/Components/MonacoEditor/MonacoEditorHelper.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ public Func<StandaloneCodeEditor, StandaloneEditorConstructionOptions> GetStanda
3535
Language = language,
3636
ReadOnly = readOnly,
3737
Value = value,
38-
TabSize = 2
38+
TabSize = 2,
39+
QuickSuggestions = new QuickSuggestionsOptions
40+
{
41+
Other = "true",
42+
Strings = "true"
43+
}
3944
};
4045
}
4146

src/dashboard/Synapse.Dashboard/Components/WorkflowInstancesList/WorkflowInstancesList.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150
</div>
151151
break;
152152
case "Delete":
153-
<a class="dropdown-item text-danger" href="#" @onclick="async _ => await OnDeleteClickedAsync(instance)" @onclick:stopPropagation="true"><Icon Name="IconName.Trash" /></a>
153+
<button class="btn btn-sm text-danger" @onclick="async _ => await OnDeleteClickedAsync(instance)" @onclick:stopPropagation="true"><Icon Name="IconName.Trash" /></button>
154154
break;
155155
default:
156156
break;

src/dashboard/Synapse.Dashboard/Layout/MainLayout.razor

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@
108108
</main>
109109
</div>
110110

111+
<Toasts class="p-3" AutoHide="true" Delay="2000" Placement="ToastsPlacement.MiddleCenter" />
112+
111113
@code{
112114

113115
ClaimsPrincipal? user;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public record CreateWorkflowViewState
3939
{
4040
Document = new()
4141
{
42-
Dsl = "1.0.0",
42+
Dsl = "1.0.0-alpha2",
4343
Namespace = Neuroglia.Data.Infrastructure.ResourceOriented.Namespace.DefaultNamespaceName,
4444
Name = "new-workflow",
4545
Version = "0.1.0"

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

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
// See the License for the specific language governing permissions and
1212
// limitations under the License.
1313

14+
using Json.Schema;
1415
using JsonCons.Utilities;
1516
using Neuroglia.Data;
1617
using Semver;
@@ -30,19 +31,23 @@ namespace Synapse.Dashboard.Pages.Workflows.Create;
3031
/// <param name="yamlSerializer">The service used to serialize/deserialize data to/from YAML</param>
3132
/// <param name="jsRuntime">The service used for JS interop</param>
3233
/// <param name="navigationManager">The service used to provides an abstraction for querying and managing URI navigation</param>
34+
/// <param name="specificationSchemaManager">The service used to download the specification schemas</param>
35+
/// <param name="monacoInterop">The service to build a bridge with the monaco interop extension</param>
3336
public class CreateWorkflowViewStore(
3437
ISynapseApiClient api,
3538
IMonacoEditorHelper monacoEditorHelper,
3639
IJsonSerializer jsonSerializer,
3740
IYamlSerializer yamlSerializer,
3841
IJSRuntime jsRuntime,
39-
NavigationManager navigationManager
42+
NavigationManager navigationManager,
43+
SpecificationSchemaManager specificationSchemaManager,
44+
MonacoInterop monacoInterop
4045
)
4146
: ComponentStore<CreateWorkflowViewState>(new())
4247
{
4348

4449
private TextModel? _textModel = null;
45-
private readonly string _textModelUri = monacoEditorHelper.GetResourceUri(typeof(WorkflowDefinition).Name.ToLower());
50+
private string _textModelUri = string.Empty;
4651
private bool _disposed;
4752

4853
/// <summary>
@@ -75,6 +80,16 @@ NavigationManager navigationManager
7580
/// </summary>
7681
protected NavigationManager NavigationManager { get; } = navigationManager;
7782

83+
/// <summary>
84+
/// Gets the service used to download the specification schemas
85+
/// </summary>
86+
protected SpecificationSchemaManager SpecificationSchemaManager { get; } = specificationSchemaManager;
87+
88+
/// <summary>
89+
/// Gets the service to build a bridge with the monaco interop extension
90+
/// </summary>
91+
protected MonacoInterop MonacoInterop { get; } = monacoInterop;
92+
7893
/// <summary>
7994
/// The <see cref="BlazorMonaco.Editor.StandaloneEditorConstructionOptions"/> provider function
8095
/// </summary>
@@ -281,6 +296,7 @@ public async Task SetTextBasedEditorLanguageAsync()
281296
{
282297
this._textModel = await Global.GetModel(this.JSRuntime, this._textModelUri);
283298
this._textModel ??= await Global.CreateModel(this.JSRuntime, "", language, this._textModelUri);
299+
await Global.SetModelLanguage(this.JSRuntime, this._textModel, language);
284300
await this.TextEditor!.SetModel(this._textModel);
285301
}
286302
}
@@ -301,6 +317,16 @@ async Task SetTextEditorValueAsync()
301317
if (this.TextEditor != null && !string.IsNullOrWhiteSpace(document))
302318
{
303319
await this.TextEditor.SetValue(document);
320+
try
321+
{
322+
//await this.TextEditor.Trigger("", "editor.action.triggerSuggest");
323+
await this.TextEditor.Trigger("", "editor.action.formatDocument");
324+
}
325+
catch (Exception ex)
326+
{
327+
Console.WriteLine(ex.ToString());
328+
// todo: handle exception
329+
}
304330
}
305331
}
306332

@@ -416,6 +442,10 @@ public override async Task InitializeAsync()
416442
string document = "";
417443
if (definition != null)
418444
{
445+
if (definition.Document?.Dsl != null)
446+
{
447+
await this.SetValidationSchema($"v{definition.Document.Dsl}");
448+
}
419449
document = this.MonacoEditorHelper.PreferredLanguage == PreferredLanguage.JSON ?
420450
this.JsonSerializer.SerializeToText(definition) :
421451
this.YamlSerializer.SerializeToText(definition);
@@ -424,7 +454,7 @@ public override async Task InitializeAsync()
424454
{
425455
WorkflowDefinitionText = document
426456
});
427-
await this.SetTextEditorValueAsync();
457+
await this.OnTextBasedEditorInitAsync();
428458
}, cancellationToken: this.CancellationTokenSource.Token);
429459
Observable.CombineLatest(
430460
this.Namespace.Where(ns => !string.IsNullOrWhiteSpace(ns)),
@@ -437,6 +467,20 @@ public override async Task InitializeAsync()
437467
await base.InitializeAsync();
438468
}
439469

470+
/// <summary>
471+
/// Adds validation for the specification of the specified version
472+
/// </summary>
473+
/// <param name="version">The version of the spec to add the validation for</param>
474+
/// <returns>An awaitable task</returns>
475+
protected async Task SetValidationSchema(string? version = null)
476+
{
477+
version = version ?? await this.SpecificationSchemaManager.GetLatestVersion();
478+
var schema = await this.SpecificationSchemaManager.GetSchema(version);
479+
var type = $"create_{typeof(WorkflowDefinition).Name.ToLower()}_{version}";
480+
await this.MonacoInterop.AddValidationSchemaAsync(schema, $"https://synapse.io/schemas/{type}.json", $"{type}*").ConfigureAwait(false);
481+
this._textModelUri = this.MonacoEditorHelper.GetResourceUri(type);
482+
}
483+
440484
/// <summary>
441485
/// Disposes of the store
442486
/// </summary>

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

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@ namespace Synapse.Dashboard.Pages.Workflows.Details;
2828
/// <param name="jsonSerializer">The service used to serialize and deserialize JSON</param>
2929
/// <param name="yamlSerializer">The service used to serialize and deserialize YAML</param>
3030
/// <param name="monacoInterop">The service used to build a bridge with the monaco interop extension</param>
31+
/// <param name="toastService">The service used display toast messages</param>
3132
public class WorkflowDetailsStore(
3233
ISynapseApiClient apiClient,
3334
ResourceWatchEventHubClient resourceEventHub,
3435
IJSRuntime jsRuntime,
3536
IMonacoEditorHelper monacoEditorHelper,
3637
IJsonSerializer jsonSerializer,
3738
IYamlSerializer yamlSerializer,
38-
MonacoInterop monacoInterop
39+
MonacoInterop monacoInterop,
40+
ToastService toastService
3941
)
4042
: NamespacedResourceManagementComponentStore<WorkflowDetailsState, WorkflowInstance>(apiClient, resourceEventHub)
4143
{
@@ -68,6 +70,11 @@ MonacoInterop monacoInterop
6870
/// </summary>
6971
protected MonacoInterop MonacoInterop { get; } = monacoInterop;
7072

73+
/// <summary>
74+
/// Gets the service used display toast messages
75+
/// </summary>
76+
protected ToastService ToastService { get; } = toastService;
77+
7178
/// <summary>
7279
/// Gets/sets the <see cref="BlazorMonaco.Editor.StandaloneEditorConstructionOptions"/> provider function
7380
/// </summary>
@@ -191,7 +198,7 @@ public void SetWorkflowInstanceName(string? instanceName)
191198
/// <summary>
192199
/// Changes the value of the text editor
193200
/// </summary>
194-
/// <returns></returns>
201+
/// <returns>A awaitable task</returns>
195202
async Task SetTextEditorValueAsync()
196203
{
197204
var document = this.Get(state => state.WorkflowDefinitionJson);
@@ -219,7 +226,7 @@ async Task SetTextEditorValueAsync()
219226
/// </summary>
220227
/// <param name="ns"></param>
221228
/// <param name="name"></param>
222-
/// <returns></returns>
229+
/// <returns>A awaitable task</returns>
223230
public async Task GetWorkflowAsync(string ns, string name)
224231
{
225232
try
@@ -241,7 +248,7 @@ public async Task GetWorkflowAsync(string ns, string name)
241248
/// Handles changed of the text editor's language
242249
/// </summary>
243250
/// <param name="_"></param>
244-
/// <returns></returns>
251+
/// <returns>A awaitable task</returns>
245252
public async Task ToggleTextBasedEditorLanguageAsync(string _)
246253
{
247254
await this.OnTextBasedEditorInitAsync();
@@ -250,7 +257,7 @@ public async Task ToggleTextBasedEditorLanguageAsync(string _)
250257
/// <summary>
251258
/// Handles initialization of the text editor
252259
/// </summary>
253-
/// <returns></returns>
260+
/// <returns>A awaitable task</returns>
254261
public async Task OnTextBasedEditorInitAsync()
255262
{
256263
await this.SetTextBasedEditorLanguageAsync();
@@ -260,7 +267,7 @@ public async Task OnTextBasedEditorInitAsync()
260267
/// <summary>
261268
/// Sets the language of the text editor
262269
/// </summary>
263-
/// <returns></returns>
270+
/// <returns>A awaitable task</returns>
264271
public async Task SetTextBasedEditorLanguageAsync()
265272
{
266273
try
@@ -289,10 +296,32 @@ public async Task SetTextBasedEditorLanguageAsync()
289296
}
290297
}
291298

299+
/// <summary>
300+
/// Copies to content of the Monaco editor to the clipboard
301+
/// </summary>
302+
/// <returns>A awaitable task</returns>
303+
public async Task OnCopyToClipboard()
304+
{
305+
if (this.TextEditor == null) return;
306+
var text = await this.TextEditor.GetValue();
307+
if (string.IsNullOrWhiteSpace(text)) return;
308+
try
309+
{
310+
await this.JSRuntime.InvokeVoidAsync("navigator.clipboard.writeText", text);
311+
this.ToastService.Notify(new(ToastType.Success, "Definition copied to the clipboard!"));
312+
}
313+
catch (Exception ex)
314+
{
315+
this.ToastService.Notify(new(ToastType.Danger, "Failed to copy the definition to the clipboard."));
316+
Console.WriteLine(ex.ToString());
317+
// todo: handle exception
318+
}
319+
}
320+
292321
/// <summary>
293322
/// Displays the modal used to provide the new workflow input
294323
/// </summary>
295-
/// <returns></returns>
324+
/// <returns>A awaitable task</returns>
296325
public async Task OnShowCreateInstanceAsync(WorkflowDefinition workflowDefinition)
297326
{
298327
if (this.Modal != null)
@@ -310,7 +339,7 @@ public async Task OnShowCreateInstanceAsync(WorkflowDefinition workflowDefinitio
310339
/// Creates a new instance of the workflow
311340
/// </summary>
312341
/// <param name="input">The input data, if any</param>
313-
/// <returns></returns>
342+
/// <returns>A awaitable task</returns>
314343
public async Task CreateInstanceAsync(string input)
315344
{
316345
var workflowName = this.Get(state => state.ActiveResourceName);
@@ -358,7 +387,7 @@ public async Task CreateInstanceAsync(string input)
358387
/// Delete the provided <see cref="WorkflowInstance"/>
359388
/// </summary>
360389
/// <param name="workflowInstance">The workflow instance to delete</param>
361-
/// <returns></returns>
390+
/// <returns>A awaitable task</returns>
362391
public async Task DeleteWorkflowInstanceAsync(WorkflowInstance workflowInstance)
363392
{
364393
await this.ApiClient.ManageNamespaced<WorkflowInstance>().DeleteAsync(workflowInstance.GetName(), workflowInstance.GetNamespace()!).ConfigureAwait(false);
@@ -368,6 +397,7 @@ public async Task DeleteWorkflowInstanceAsync(WorkflowInstance workflowInstance)
368397
/// Selects the target node in the code editor
369398
/// </summary>
370399
/// <param name="e">The source of the event</param>
400+
/// <returns>A awaitable task</returns>
371401
public async Task SelectNodeInEditor(GraphEventArgs<MouseEventArgs> e)
372402
{
373403
if (e.GraphElement == null) return;

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
Columns="@columns"
3737
OnSearchInput="Store.SetSearchTerm"
3838
OnShowDetails="OnShowInstanceDetails"
39+
OnDelete="OnDeleteWorkflowInstanceAsync"
3940
OnToggleSelected="Store.ToggleResourceSelection"
4041
OnDeleteSelected="OnDeleteSelectedResourcesAsync" />
4142
<Button Outline="true" Color="ButtonColor.Primary" @onclick="async _ => await Store.OnShowCreateInstanceAsync(workflowDefinition)" class="w-100 mt-3">
@@ -75,9 +76,14 @@
7576
{
7677
<div class="d-flex flex-column h-100 mh-100">
7778
<div class="d-flex justify-content-between mb-2">
78-
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" @onclick="_ => OnCreateWorkflowVersion()">
79-
<Icon Name="IconName.Copy" />
80-
</Button>
79+
<div>
80+
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" class="ms-2" @onclick="_ => OnCreateWorkflowVersion()">
81+
<Icon Name="IconName.FileEarmarkPlus" />
82+
</Button>
83+
<Button Outline="true" Color="ButtonColor.Primary" Size="ButtonSize.Small" class="ms-2" @onclick="_ => Store.OnCopyToClipboard()">
84+
<Icon Name="IconName.Clipboard" />
85+
</Button>
86+
</div>
8187
<PreferredLanguageSelector PreferedLanguageChange="Store.ToggleTextBasedEditorLanguageAsync" />
8288
</div>
8389
<div class="flex-grow">

src/dashboard/Synapse.Dashboard/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848

4949
builder.Services.AddSingleton<IMonacoEditorHelper, MonacoEditorHelper>();
5050
builder.Services.AddScoped<IApplicationLayout, ApplicationLayout>();
51+
builder.Services.AddScoped<SpecificationSchemaManager>();
5152
builder.Services.AddSingleton<JSInterop>();
5253
builder.Services.AddSingleton<MonacoInterop>();
5354
builder.Services.AddSingleton<IGraphLayoutService, GraphLayoutService>();

0 commit comments

Comments
 (0)