Skip to content

Commit 53f7e52

Browse files
committed
fix(OData): Fixed the EdmModelBuilder to configure the V1Schedule entity set
fix(OData): Fixed the ODataSearchBinder to allow V1Schedule searches
1 parent b4cb2c1 commit 53f7e52

File tree

9 files changed

+175
-75
lines changed

9 files changed

+175
-75
lines changed

src/apis/management/Synapse.Apis.Management.Http/Extensions/ISynapseApplicationBuilderExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
using Neuroglia;
2626
using Newtonsoft.Json;
2727
using Newtonsoft.Json.Serialization;
28-
using Synapse.Apis.Management.Http.Services;
2928
using Synapse.Application.Configuration;
3029
using Synapse.Application.Services;
3130
using Synapse.Integration.Serialization.Converters;

src/core/Synapse.Application/Commands/Schedules/v1/V1CompleteScheduleOccurenceCommand.cs

Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -17,87 +17,90 @@
1717

1818
using ServerlessWorkflow.Sdk;
1919

20-
namespace Synapse.Application.Commands.Schedules;
21-
22-
/// <summary>
23-
/// Represents the <see cref="ICommand"/> used to complete a <see cref="V1Schedule"/>'s occurence
24-
/// </summary>
25-
[DataTransferObjectType(typeof(Integration.Commands.Schedules.V1CompleteScheduleOccurenceCommand))]
26-
public class V1CompleteScheduleOccurenceCommand
27-
: Command<Integration.Models.V1Schedule>
20+
namespace Synapse.Application.Commands.Schedules
2821
{
2922

3023
/// <summary>
31-
/// Initializes a new <see cref="V1CompleteScheduleOccurenceCommand"/>
24+
/// Represents the <see cref="ICommand"/> used to complete a <see cref="V1Schedule"/>'s occurence
3225
/// </summary>
33-
protected V1CompleteScheduleOccurenceCommand() { }
3426

35-
/// <summary>
36-
/// Initializes a new <see cref="V1CompleteScheduleOccurenceCommand"/>
37-
/// </summary>
38-
/// <param name="scheduleId">The id of the <see cref="V1Schedule"/> to complete an occurence of</param>
39-
/// <param name="workflowInstanceId">The id of the <see cref="V1WorkflowInstance"/> that has been executed</param>
40-
public V1CompleteScheduleOccurenceCommand(string scheduleId, string workflowInstanceId)
27+
public class V1CompleteScheduleOccurenceCommand
28+
: Command<Integration.Models.V1Schedule>
4129
{
42-
this.ScheduleId = scheduleId;
43-
this.WorkflowInstanceId = workflowInstanceId;
44-
}
4530

46-
/// <summary>
47-
/// Gets the id of the <see cref="V1Schedule"/> to complete an occurence of
48-
/// </summary>
49-
public virtual string ScheduleId { get; protected set; } = null!;
31+
/// <summary>
32+
/// Initializes a new <see cref="V1CompleteScheduleOccurenceCommand"/>
33+
/// </summary>
34+
protected V1CompleteScheduleOccurenceCommand() { }
5035

51-
/// <summary>
52-
/// Gets the id of the <see cref="V1WorkflowInstance"/> that has been executed
53-
/// </summary>
54-
public virtual string WorkflowInstanceId { get; protected set; } = null!;
36+
/// <summary>
37+
/// Initializes a new <see cref="V1CompleteScheduleOccurenceCommand"/>
38+
/// </summary>
39+
/// <param name="scheduleId">The id of the <see cref="V1Schedule"/> to complete an occurence of</param>
40+
/// <param name="workflowInstanceId">The id of the <see cref="V1WorkflowInstance"/> that has been executed</param>
41+
public V1CompleteScheduleOccurenceCommand(string scheduleId, string workflowInstanceId)
42+
{
43+
this.ScheduleId = scheduleId;
44+
this.WorkflowInstanceId = workflowInstanceId;
45+
}
5546

56-
}
47+
/// <summary>
48+
/// Gets the id of the <see cref="V1Schedule"/> to complete an occurence of
49+
/// </summary>
50+
public virtual string ScheduleId { get; protected set; } = null!;
5751

58-
/// <summary>
59-
/// Represents the service used to handle <see cref="V1CompleteScheduleOccurenceCommand"/>s
60-
/// </summary>
61-
public class V1CompleteScheduleOccurenceCommandHandler
62-
: CommandHandlerBase,
63-
ICommandHandler<V1CompleteScheduleOccurenceCommand, Integration.Models.V1Schedule>
64-
{
52+
/// <summary>
53+
/// Gets the id of the <see cref="V1WorkflowInstance"/> that has been executed
54+
/// </summary>
55+
public virtual string WorkflowInstanceId { get; protected set; } = null!;
6556

66-
/// <summary>
67-
/// Initializes a new <see cref="V1CompleteScheduleOccurenceCommandHandler"/>
68-
/// </summary>
69-
/// <param name="loggerFactory">The service used to create <see cref="ILogger"/>s</param>
70-
/// <param name="mediator">The service used to mediate calls</param>
71-
/// <param name="mapper">The service used to map objects</param>
72-
/// <param name="schedules">The <see cref="IRepository"/> used to manage <see cref="V1Schedule"/>s</param>
73-
/// <param name="backgroundJobManager">The service used to manage background jobs</param>
74-
public V1CompleteScheduleOccurenceCommandHandler(ILoggerFactory loggerFactory, IMediator mediator, IMapper mapper, IRepository<V1Schedule> schedules, IBackgroundJobManager backgroundJobManager)
75-
: base(loggerFactory, mediator, mapper)
76-
{
77-
this.Schedules = schedules;
78-
this.BackgroundJobManager = backgroundJobManager;
7957
}
8058

8159
/// <summary>
82-
/// Gets the <see cref="IRepository"/> used to manage <see cref="V1Schedule"/>s
60+
/// Represents the service used to handle <see cref="V1CompleteScheduleOccurenceCommand"/>s
8361
/// </summary>
84-
protected IRepository<V1Schedule> Schedules { get; }
62+
public class V1CompleteScheduleOccurenceCommandHandler
63+
: CommandHandlerBase,
64+
ICommandHandler<V1CompleteScheduleOccurenceCommand, Integration.Models.V1Schedule>
65+
{
8566

86-
/// <summary>
87-
/// Gets the service used to manage background jobs
88-
/// </summary>
89-
protected IBackgroundJobManager BackgroundJobManager { get; }
67+
/// <summary>
68+
/// Initializes a new <see cref="V1CompleteScheduleOccurenceCommandHandler"/>
69+
/// </summary>
70+
/// <param name="loggerFactory">The service used to create <see cref="ILogger"/>s</param>
71+
/// <param name="mediator">The service used to mediate calls</param>
72+
/// <param name="mapper">The service used to map objects</param>
73+
/// <param name="schedules">The <see cref="IRepository"/> used to manage <see cref="V1Schedule"/>s</param>
74+
/// <param name="backgroundJobManager">The service used to manage background jobs</param>
75+
public V1CompleteScheduleOccurenceCommandHandler(ILoggerFactory loggerFactory, IMediator mediator, IMapper mapper, IRepository<V1Schedule> schedules, IBackgroundJobManager backgroundJobManager)
76+
: base(loggerFactory, mediator, mapper)
77+
{
78+
this.Schedules = schedules;
79+
this.BackgroundJobManager = backgroundJobManager;
80+
}
81+
82+
/// <summary>
83+
/// Gets the <see cref="IRepository"/> used to manage <see cref="V1Schedule"/>s
84+
/// </summary>
85+
protected IRepository<V1Schedule> Schedules { get; }
86+
87+
/// <summary>
88+
/// Gets the service used to manage background jobs
89+
/// </summary>
90+
protected IBackgroundJobManager BackgroundJobManager { get; }
91+
92+
/// <inheritdoc/>
93+
public virtual async Task<IOperationResult<Integration.Models.V1Schedule>> HandleAsync(V1CompleteScheduleOccurenceCommand command, CancellationToken cancellationToken = default)
94+
{
95+
var schedule = await this.Schedules.FindAsync(command.ScheduleId, cancellationToken);
96+
if (schedule == null) throw DomainException.NullReference(typeof(V1Schedule), command.ScheduleId);
97+
schedule.CompleteOccurence(command.WorkflowInstanceId);
98+
await this.Schedules.UpdateAsync(schedule, cancellationToken);
99+
await this.Schedules.SaveChangesAsync(cancellationToken);
100+
if (schedule.Definition.Type == ScheduleDefinitionType.Interval && schedule.NextOccurenceAt.HasValue) await this.BackgroundJobManager.ScheduleJobAsync(schedule, cancellationToken);
101+
return this.Ok(this.Mapper.Map<Integration.Models.V1Schedule>(schedule));
102+
}
90103

91-
/// <inheritdoc/>
92-
public virtual async Task<IOperationResult<Integration.Models.V1Schedule>> HandleAsync(V1CompleteScheduleOccurenceCommand command, CancellationToken cancellationToken = default)
93-
{
94-
var schedule = await this.Schedules.FindAsync(command.ScheduleId, cancellationToken);
95-
if (schedule == null) throw DomainException.NullReference(typeof(V1Schedule), command.ScheduleId);
96-
schedule.CompleteOccurence(command.WorkflowInstanceId);
97-
await this.Schedules.UpdateAsync(schedule, cancellationToken);
98-
await this.Schedules.SaveChangesAsync(cancellationToken);
99-
if(schedule.Definition.Type == ScheduleDefinitionType.Interval && schedule.NextOccurenceAt.HasValue) await this.BackgroundJobManager.ScheduleJobAsync(schedule, cancellationToken);
100-
return this.Ok(this.Mapper.Map<Integration.Models.V1Schedule>(schedule));
101104
}
102105

103106
}

src/core/Synapse.Application/Services/EdmModelBuilder.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public virtual IEdmModel Build()
4242
builder.EntitySet<Integration.Models.V1WorkflowActivity>("V1WorkflowActivities");
4343
builder.EntitySet<Integration.Models.V1Correlation>("V1Correlations");
4444
builder.EntitySet<Integration.Models.V1FunctionDefinitionCollection>("V1FunctionDefinitionCollections");
45+
builder.EntitySet<Integration.Models.V1Schedule>("V1Schedules");
4546

4647
builder.AddComplexType(typeof(Dynamic));
4748
builder.AddComplexType(typeof(Neuroglia.Serialization.DynamicObject));

src/apis/management/Synapse.Apis.Management.Http/Services/ODataSearchBinder.cs renamed to src/core/Synapse.Application/Services/ODataSearchBinder.cs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717

1818
using Microsoft.AspNetCore.OData.Query.Expressions;
1919
using Microsoft.OData.UriParser;
20-
using Neuroglia;
2120
using System.Linq.Expressions;
2221
using System.Reflection;
2322

24-
namespace Synapse.Apis.Management.Http.Services
23+
namespace Synapse.Application.Services
2524
{
2625

2726
/// <summary>
2827
/// Represents the default <see cref="ISearchBinder"/> implementation
2928
/// </summary>
30-
public partial class ODataSearchBinder
29+
public class ODataSearchBinder
3130
: QueryBinder, ISearchBinder
3231
{
3332

@@ -42,6 +41,7 @@ public partial class ODataSearchBinder
4241
private static readonly MethodInfo FilterWorkflowActivityMethod = typeof(ODataSearchBinder).GetMethod(nameof(FilterWorkflowActivity), BindingFlags.Static | BindingFlags.NonPublic)!;
4342
private static readonly MethodInfo FilterCorrelationMethod = typeof(ODataSearchBinder).GetMethod(nameof(FilterCorrelation), BindingFlags.Static | BindingFlags.NonPublic)!;
4443
private static readonly MethodInfo FilterFunctionCollectionMethod = typeof(ODataSearchBinder).GetMethod(nameof(FilterFunctionCollection), BindingFlags.Static | BindingFlags.NonPublic)!;
44+
private static readonly MethodInfo FilterScheduleMethod = typeof(ODataSearchBinder).GetMethod(nameof(FilterSchedule), BindingFlags.Static | BindingFlags.NonPublic)!;
4545

4646
/// <inheritdoc/>
4747
public Expression BindSearch(SearchClause searchClause, QueryBinderContext context)
@@ -101,6 +101,8 @@ public Expression BindSearchTerm(SearchTermNode term, QueryBinderContext context
101101
return this.BindCorrelationSearchTerm(term, context);
102102
else if (context.ElementClrType == typeof(Integration.Models.V1FunctionDefinitionCollection))
103103
return this.BindFunctionCollectionSearchTerm(term, context);
104+
else if (context.ElementClrType == typeof(Integration.Models.V1Schedule))
105+
return this.BindScheduleSearchTerm(term, context);
104106
else
105107
throw new NotSupportedException($"Search is not allowed on element type '{context.ElementClrType.Name}'");
106108
}
@@ -170,6 +172,19 @@ protected virtual Expression BindFunctionCollectionSearchTerm(SearchTermNode sea
170172
return searchQuery;
171173
}
172174

175+
/// <summary>
176+
/// Binds the specified <see cref="Domain.Models.V1Schedule"/> <see cref="SearchTermNode"/>
177+
/// </summary>
178+
/// <param name="searchTermNode">The <see cref="Domain.Models.V1Schedule"/> <see cref="SearchTermNode"/> to bind</param>
179+
/// <param name="context">The current <see cref="QueryBinderContext"/></param>
180+
/// <returns>A new <see cref="Expression"/></returns>
181+
protected virtual Expression BindScheduleSearchTerm(SearchTermNode searchTermNode, QueryBinderContext context)
182+
{
183+
var searchTerm = searchTermNode.Text.ToLowerInvariant();
184+
var searchQuery = Expression.IsTrue(Expression.Call(null, FilterScheduleMethod, context.CurrentParameter, Expression.Constant(searchTerm)));
185+
return searchQuery;
186+
}
187+
173188
static bool FilterWorkflow(Integration.Models.V1Workflow workflow, string searchTerm)
174189
{
175190
return workflow.Id.ToLower().Contains(searchTerm)
@@ -225,6 +240,22 @@ static bool FilterFunctionCollection(Integration.Models.V1FunctionDefinitionColl
225240
|| (collection.Functions != null && collection.Functions.Any(f => EnumHelper.Stringify(f.Type).Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)));
226241
}
227242

243+
static bool FilterSchedule(Integration.Models.V1Schedule schedule, string searchTerm)
244+
{
245+
return schedule.Id.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
246+
|| schedule.WorkflowId.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
247+
|| EnumHelper.Stringify(schedule.ActivationType).Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
248+
|| EnumHelper.Stringify(schedule.Status).Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
249+
|| (schedule.Definition.Cron != null && schedule.Definition.Cron!.Expression.Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase))
250+
|| (schedule.Definition.Interval.HasValue && schedule.Definition.Interval.Value.ToString().Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase))
251+
|| schedule.CreatedAt.ToString().Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
252+
|| schedule.LastModified.ToString().Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase)
253+
|| (schedule.LastOccuredAt.HasValue && schedule.LastOccuredAt.Value.ToString().Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase))
254+
|| (schedule.NextOccurenceAt.HasValue && schedule.NextOccurenceAt.Value.ToString().Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase))
255+
|| (schedule.LastCompletedAt.HasValue && schedule.LastCompletedAt.Value.ToString().Contains(searchTerm, StringComparison.InvariantCultureIgnoreCase));
256+
}
257+
258+
228259
}
229260

230261
}

src/core/Synapse.Integration/Commands/Generic/v1/Generated/V1PatchCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public partial class V1PatchCommand
3636
/// </summary>
3737
[DataMember(Name = "Id", Order = 1)]
3838
[Description("The id of the entity to patch")]
39-
public virtual object Id { get; set; }
39+
public virtual Dynamic Id { get; set; }
4040

4141
/// <summary>
4242
/// The JsonPatchDocument`1 to apply

src/core/Synapse.Integration/Commands/Schedules/v1/Generated/V1CompleteScheduleOccurenceCommand.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,28 @@
2323
namespace Synapse.Integration.Commands.Schedules
2424
{
2525

26+
/// <summary>
27+
/// Represents the ICommand used to complete a V1Schedule's occurence
28+
/// </summary>
2629
[DataContract]
2730
public partial class V1CompleteScheduleOccurenceCommand
31+
: Command
2832
{
2933

34+
/// <summary>
35+
/// The id of the V1Schedule to complete an occurence of
36+
/// </summary>
37+
[DataMember(Name = "ScheduleId", Order = 1)]
38+
[Description("The id of the V1Schedule to complete an occurence of")]
39+
public virtual string ScheduleId { get; set; }
40+
41+
/// <summary>
42+
/// The id of the V1WorkflowInstance that has been executed
43+
/// </summary>
44+
[DataMember(Name = "WorkflowInstanceId", Order = 2)]
45+
[Description("The id of the V1WorkflowInstance that has been executed")]
46+
public virtual string WorkflowInstanceId { get; set; }
47+
3048
}
3149

3250
}

src/core/Synapse.Integration/Models/v1/V1PluginInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ protected V1PluginInfo()
3535
/// <summary>
3636
/// Initializes a new <see cref="V1PluginInfo"/>
3737
/// </summary>
38-
/// <param name="location">The plugin's location/param>
38+
/// <param name="location">The plugin's location</param>
3939
/// <param name="metadata">The plugin's metadata</param>
4040
/// <param name="isLoaded">A boolean indicating whether or not the described plugin is loaded</param>
4141
public V1PluginInfo(string location, V1PluginMetadata metadata, bool isLoaded)

src/dashboard/Synapse.Dashboard/Pages/Schedules/List/Actions.cs

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using OData.QueryBuilder.Builders;
2+
using OData.QueryBuilder.Conventions.AddressingEntities.Query;
23
using Synapse.Integration.Models;
34

45
namespace Synapse.Dashboard.Pages.Schedules.List
56
{
67

78
/// <summary>
8-
/// Represents the action used to query scheduled <see cref="V1Schedule"/>s
9+
/// Represents the action used to query <see cref="V1Schedule"/>s
910
/// </summary>
1011
public class QuerySchedules
1112
{
@@ -14,18 +15,50 @@ public class QuerySchedules
1415
/// Initializes a new <see cref="QuerySchedules"/>
1516
/// </summary>
1617
public QuerySchedules()
18+
{
19+
20+
}
21+
22+
/// <summary>
23+
/// Initializes a new <see cref="QuerySchedules"/>
24+
/// </summary>
25+
/// <param name="searchTerm">The term to search for</param>
26+
/// <param name="querySetup">An <see cref="Action{T}"/> used to setup the query to perform</param>
27+
public QuerySchedules(string? searchTerm, Action<IODataQueryCollection<V1Schedule>> querySetup)
1728
{
1829
var builder = new ODataQueryBuilder(new Uri("https://test.com"))
19-
.For<V1Workflow>("V1Schedules")
30+
.For<V1Schedule>("V1V1Schedules")
2031
.ByList();
21-
this.Query = builder.ToUri(UriKind.Absolute).Query;
22-
if (!string.IsNullOrWhiteSpace(this.Query)) this.Query = this.Query[1..];
32+
querySetup(builder);
33+
Query = builder.ToUri(UriKind.Absolute).Query;
34+
if (!string.IsNullOrWhiteSpace(searchTerm))
35+
Query = $"$search={searchTerm}&{Query}";
36+
}
37+
38+
/// <summary>
39+
/// Initializes a new <see cref="QuerySchedules"/>
40+
/// </summary>
41+
/// <param name="searchTerm">The term to search for</param>
42+
public QuerySchedules(string searchTerm)
43+
: this(searchTerm, _ => { })
44+
{
45+
46+
}
47+
48+
/// <summary>
49+
/// Initializes a new <see cref="QuerySchedules"/>
50+
/// </summary>
51+
/// <param name="querySetup">An <see cref="Action{T}"/> used to setup the query to perform</param>
52+
public QuerySchedules(Action<IODataQueryCollection<V1Schedule>> querySetup)
53+
: this(null, querySetup)
54+
{
55+
2356
}
2457

2558
/// <summary>
2659
/// Gets the query to perform
2760
/// </summary>
28-
public string Query { get; }
61+
public string? Query { get; }
2962

3063
}
3164

src/dashboard/Synapse.Dashboard/Pages/Schedules/List/View.razor

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131

3232
@if(schedules != null)
3333
{
34+
<div class="row">
35+
<div class="col-6">
36+
<SearchBox PlaceHolder="Search schedules..." OnSearch="OnSearchSchedules" OnClear="OnClearScheduleSearch" />
37+
</div>
38+
</div>
3439
<Table Items="schedules" AutoGenerateColumns="false">
3540
<Columns>
3641
<Column T="V1Schedule"
@@ -123,6 +128,16 @@ else
123128
this.NavigationManager.NavigateTo($"/schedules/{schedule.Id}");
124129
}
125130

131+
void OnSearchSchedules(string searchTerm)
132+
{
133+
this.Dispatcher.Dispatch(new QuerySchedules(searchTerm));
134+
}
135+
136+
void OnClearScheduleSearch()
137+
{
138+
this.Dispatcher.Dispatch(new QuerySchedules());
139+
}
140+
126141
protected override void Dispose(bool disposing)
127142
{
128143
base.Dispose(disposing);

0 commit comments

Comments
 (0)