Merge pull request #1902 from sbwalker/dev

improve Scheduled Job start/stop user experience, utilize start time when setting next job execution
This commit is contained in:
Shaun Walker 2022-01-02 20:53:00 -05:00 committed by GitHub
commit ea93ab2a83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 85 additions and 30 deletions

View File

@ -36,13 +36,13 @@ else
<td>@context.NextExecution</td> <td>@context.NextExecution</td>
<td> <td>
@if (context.IsStarted) @if (context.IsStarted)
{ {
<button type="button" class="btn btn-danger" @onclick="(async () => await StopJob(context.JobId))">@Localizer["Stop"]</button> <button type="button" class="btn btn-danger" @onclick="(async () => await StopJob(context.JobId))">@Localizer["Stop"]</button>
} }
else else
{ {
<button type="button" class="btn btn-success" @onclick="(async () => await StartJob(context.JobId))">@Localizer["Start"]</button> <button type="button" class="btn btn-success" @onclick="(async () => await StartJob(context.JobId))">@Localizer["Start"]</button>
} }
</td> </td>
</Row> </Row>
</Pager> </Pager>
@ -100,11 +100,6 @@ else
break; break;
} }
if (interval > 1)
{
result += Localizer["s"];
}
return result; return result;
} }
@ -114,6 +109,7 @@ else
{ {
await JobService.DeleteJobAsync(job.JobId); await JobService.DeleteJobAsync(job.JobId);
await logger.LogInformation("Job Deleted {Job}", job); await logger.LogInformation("Job Deleted {Job}", job);
_jobs = await JobService.GetJobsAsync();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -125,12 +121,36 @@ else
private async Task StartJob(int jobId) private async Task StartJob(int jobId)
{ {
await JobService.StartJobAsync(jobId); try
{
await JobService.StartJobAsync(jobId);
await logger.LogInformation("Job Started {JobId}", jobId);
AddModuleMessage(Localizer["Message.Job.Start"], MessageType.Success);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Starting Job {JobId} {Error}", jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Start"], MessageType.Error);
}
} }
private async Task StopJob(int jobId) private async Task StopJob(int jobId)
{ {
await JobService.StopJobAsync(jobId); try
{
await JobService.StopJobAsync(jobId);
await logger.LogInformation("Job Stopped {JobId}", jobId);
AddModuleMessage(Localizer["Message.Job.Stop"], MessageType.Success);
_jobs = await JobService.GetJobsAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Stopping Job {JobId} {Error}", jobId, ex.Message);
AddModuleMessage(Localizer["Error.Job.Stop"], MessageType.Error);
}
} }
private async Task Refresh() private async Task Refresh()

View File

@ -144,9 +144,6 @@
<data name="Month" xml:space="preserve"> <data name="Month" xml:space="preserve">
<value>Month</value> <value>Month</value>
</data> </data>
<data name="s" xml:space="preserve">
<value>s</value>
</data>
<data name="Error.Job.Delete" xml:space="preserve"> <data name="Error.Job.Delete" xml:space="preserve">
<value>Error Deleting Job</value> <value>Error Deleting Job</value>
</data> </data>
@ -177,4 +174,16 @@
<data name="JobLog.Text" xml:space="preserve"> <data name="JobLog.Text" xml:space="preserve">
<value>Log</value> <value>Log</value>
</data> </data>
<data name="Error.Job.Start" xml:space="preserve">
<value>An error occurred when starting the job</value>
</data>
<data name="Error.Job.Stop" xml:space="preserve">
<value>An error occurred when stopping the job</value>
</data>
<data name="Message.Job.Start" xml:space="preserve">
<value>The process responsible for executing this job has been started. The next execution will be based on the schedule criteria for the job.</value>
</data>
<data name="Message.Job.Stop" xml:space="preserve">
<value>The process responsible for executing this job has been stopped. In order to restart the process you will need to use the Start button or restart the application.</value>
</data>
</root> </root>

View File

@ -114,7 +114,7 @@ namespace Oqtane.Infrastructure
jobLogs.UpdateJobLog(log); jobLogs.UpdateJobLog(log);
// update the job // update the job
job.NextExecution = CalculateNextExecution(NextExecution, job.Frequency, job.Interval); job.NextExecution = CalculateNextExecution(NextExecution, job);
job.IsExecuting = false; job.IsExecuting = false;
jobs.UpdateJob(job); jobs.UpdateJob(job);
@ -140,21 +140,31 @@ namespace Oqtane.Infrastructure
} }
private DateTime CalculateNextExecution(DateTime nextExecution, string frequency, int interval) private DateTime CalculateNextExecution(DateTime nextExecution, Job job)
{ {
switch (frequency) switch (job.Frequency)
{ {
case "m": // minutes case "m": // minutes
nextExecution = nextExecution.AddMinutes(interval); nextExecution = nextExecution.AddMinutes(job.Interval);
break; break;
case "H": // hours case "H": // hours
nextExecution = nextExecution.AddHours(interval); nextExecution = nextExecution.AddHours(job.Interval);
break; break;
case "d": // days case "d": // days
nextExecution = nextExecution.AddDays(interval); nextExecution = nextExecution.AddDays(job.Interval);
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
// set the start time
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
}
break; break;
case "M": // months case "M": // months
nextExecution = nextExecution.AddMonths(interval); nextExecution = nextExecution.AddMonths(job.Interval);
if (job.StartDate != null && job.StartDate.Value.TimeOfDay.TotalSeconds != 0)
{
// set the start time
nextExecution = nextExecution.Date.Add(job.StartDate.Value.TimeOfDay);
}
break; break;
} }
if (nextExecution < DateTime.UtcNow) if (nextExecution < DateTime.UtcNow)
@ -168,7 +178,6 @@ namespace Oqtane.Infrastructure
{ {
try try
{ {
// set IsExecuting to false in case this job was forcefully terminated previously
using (var scope = _serviceScopeFactory.CreateScope()) using (var scope = _serviceScopeFactory.CreateScope())
{ {
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName); string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
@ -176,15 +185,17 @@ namespace Oqtane.Infrastructure
Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault(); Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault();
if (job != null) if (job != null)
{ {
// reset in case this job was forcefully terminated previously
job.IsStarted = true; job.IsStarted = true;
job.IsExecuting = false; job.IsExecuting = false;
jobs.UpdateJob(job); jobs.UpdateJob(job);
} }
else else
{ {
// auto registration - does not run on initial installation but will run after restart // auto registration - job will not run on initial installation but will run after restart
job = new Job { JobType = jobTypeName }; job = new Job { JobType = jobTypeName };
// optional properties
// optional HostedServiceBase properties
var jobType = Type.GetType(jobTypeName); var jobType = Type.GetType(jobTypeName);
var jobObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType) as HostedServiceBase; var jobObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType) as HostedServiceBase;
if (jobObject.Name != "") if (jobObject.Name != "")
@ -203,6 +214,7 @@ namespace Oqtane.Infrastructure
job.IsEnabled = jobObject.IsEnabled; job.IsEnabled = jobObject.IsEnabled;
job.IsStarted = true; job.IsStarted = true;
job.IsExecuting = false; job.IsExecuting = false;
job.NextExecution = null;
jobs.AddJob(job); jobs.AddJob(job);
} }
} }
@ -224,13 +236,27 @@ namespace Oqtane.Infrastructure
public async Task StopAsync(CancellationToken cancellationToken) public async Task StopAsync(CancellationToken cancellationToken)
{ {
if (_executingTask == null)
{
return;
}
try try
{ {
using (var scope = _serviceScopeFactory.CreateScope())
{
string jobTypeName = Utilities.GetFullTypeName(GetType().AssemblyQualifiedName);
IJobRepository jobs = scope.ServiceProvider.GetRequiredService<IJobRepository>();
Job job = jobs.GetJobs().Where(item => item.JobType == jobTypeName).FirstOrDefault();
if (job != null)
{
// reset job
job.IsStarted = false;
job.IsExecuting = false;
jobs.UpdateJob(job);
}
}
if (_executingTask == null)
{
return;
}
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
} }
finally finally