diff --git a/devdocs/todo.txt b/devdocs/todo.txt index 51328581..aad39bb8 100644 --- a/devdocs/todo.txt +++ b/devdocs/todo.txt @@ -20,7 +20,7 @@ Done! License agreement page for subscription vs perpetual ("Quickly, quickly, there's no time" must use a new format as this is a subscription "SERVICE" not a software product so maybe look at quickbooks license or service agreement I guess it would be called) Must be clear customer is responsible for their data and must remain within reasonable usage levels or we will cancel the service - +TODO: must change the "View license agreement" link in About form to "View service agreement" and use Service agreement text to describe it - Launch a trialing server, can start small and work up but keep all users on it so to save money and not any of our own corporate infrastructure servers "All free trial subscriptions are located in New York data center. Paid subscriptions can be in any of the following data centers of your choice ...blah blah blah." diff --git a/server/AyaNova/Controllers/JobOperationsController.cs b/server/AyaNova/Controllers/JobOperationsController.cs index f5c2773b..4b25f0c0 100644 --- a/server/AyaNova/Controllers/JobOperationsController.cs +++ b/server/AyaNova/Controllers/JobOperationsController.cs @@ -99,7 +99,20 @@ namespace AyaNova.Api.Controllers return Ok(ApiOkResponse.Response(await JobsBiz.GetJobStatusAsync(gid))); } - + /// + /// Get current job status and progress for a job + /// + /// + /// A single job's current status and progress + [HttpGet("progress/{gid}")] + public async Task GetJobProgress([FromRoute] Guid gid) + { + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + return Ok(ApiOkResponse.Response(await JobsBiz.GetJobProgressAsync(gid))); + } + + /// @@ -240,6 +253,23 @@ namespace AyaNova.Api.Controllers } + + /// + /// Request cancellation of Job. Not all jobs can be cancelled. + /// + /// + /// Accepted + [HttpPost("request-cancel/{gid}")] + public async Task RequestCancelJob([FromRoute] Guid gid) + { + if (!ModelState.IsValid) + return BadRequest(new ApiErrorResponse(ModelState)); + + await JobsBiz.RequestCancelAsync(gid); + return Accepted(); + } + + //------------ diff --git a/server/AyaNova/biz/CustomerBiz.cs b/server/AyaNova/biz/CustomerBiz.cs index fa2bb7a1..c1d4d7e4 100644 --- a/server/AyaNova/biz/CustomerBiz.cs +++ b/server/AyaNova/biz/CustomerBiz.cs @@ -596,10 +596,29 @@ namespace AyaNova.Biz else idList = await ct.Customer.AsNoTracking().Select(z => z.Id).ToListAsync(); bool SaveIt = false; + + //case 4192 + TimeSpan ProgressAndCancelCheckSpan = new TimeSpan(0, 0, ServerBootConfig.JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS); + DateTime LastProgressCheck = DateTime.UtcNow.Subtract(new TimeSpan(1, 1, 1, 1, 1)); + var TotalRecords = idList.LongCount(); + long CurrentRecord = -1; + + foreach (long id in idList) { try { + //Update progress / cancel requested? + CurrentRecord++; + if (DateUtil.IsAfterDuration(LastProgressCheck, ProgressAndCancelCheckSpan)) + { + await JobsBiz.UpdateJobProgressAsync(job.GId, $"{CurrentRecord}/{TotalRecords}"); + if (await JobsBiz.GetJobStatusAsync(job.GId) == JobStatus.CancelRequested) + { + break; + } + } + SaveIt = false; ClearErrors(); Customer o = null; @@ -635,7 +654,7 @@ namespace AyaNova.Biz FailedObjectCount++; } } - //TEMPORARY MEASURE AWAITING BETTER SOLUTION AND CANCELLABLE EXTENSION JOBS + //TEMPORARY MEASURE AWAITING BETTER SOLUTION AND CANCELLABLE EXTENSION JOBS //delay so we're not tying up all the resources in a tight loop await Task.Delay(AyaNova.Util.ServerBootConfig.JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY); } diff --git a/server/AyaNova/biz/JobStatus.cs b/server/AyaNova/biz/JobStatus.cs index 68d32bb7..a41247ed 100644 --- a/server/AyaNova/biz/JobStatus.cs +++ b/server/AyaNova/biz/JobStatus.cs @@ -11,7 +11,8 @@ namespace AyaNova.Biz Sleeping = 1, Running = 2, Completed = 3, - Failed = 4 + Failed = 4, + CancelRequested = 5 } diff --git a/server/AyaNova/biz/JobsBiz.cs b/server/AyaNova/biz/JobsBiz.cs index c808ce03..7669a28b 100644 --- a/server/AyaNova/biz/JobsBiz.cs +++ b/server/AyaNova/biz/JobsBiz.cs @@ -73,6 +73,16 @@ namespace AyaNova.Biz } } + /// + /// Request the cancellation of a job, not all jobs honour this + /// + /// + internal static async Task RequestCancelAsync(Guid jobId) + { + await UpdateJobStatusAsync(jobId, JobStatus.CancelRequested); + await LogJobAsync(jobId, "LT:Cancel"); + } + /// /// Remove the job and it's logs @@ -152,6 +162,37 @@ namespace AyaNova.Biz return o.JobStatus; } } + + + /// + /// Update the progress of a job + /// + /// + /// + internal static async Task UpdateJobProgressAsync(Guid jobId, string progress) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var oFromDb = await ct.OpsJob.SingleOrDefaultAsync(z => z.GId == jobId); + if (oFromDb == null) return; + oFromDb.Progress = progress; + await ct.SaveChangesAsync(); + } + } + + /// + /// Get the progress and status of a job + /// + /// + internal static async Task GetJobProgressAsync(Guid jobId) + { + using (AyContext ct = ServiceProviderProvider.DBContext) + { + var o = await ct.OpsJob.AsNoTracking().SingleOrDefaultAsync(z => z.GId == jobId); + if (o == null) return new JobProgress() { JobStatus = JobStatus.Absent, Progress = string.Empty }; + return new JobProgress() { JobStatus = o.JobStatus, Progress = o.Progress }; + } + } #endregion Job ops #region PROCESSOR diff --git a/server/AyaNova/models/OpsJob.cs b/server/AyaNova/models/OpsJob.cs index 24e85986..61cc0214 100644 --- a/server/AyaNova/models/OpsJob.cs +++ b/server/AyaNova/models/OpsJob.cs @@ -39,6 +39,7 @@ namespace AyaNova.Models /// public string JobInfo { get; set; }//json as string of any required extra info for job + public string Progress {get;set;}//any type of text digestible by client showing progress of job, typically just a string i.e. "133/344" public OpsJob() { @@ -53,6 +54,7 @@ namespace AyaNova.Models AType = AyaType.NoType; JobStatus = JobStatus.Sleeping; JobInfo = null; + Progress=string.Empty; } diff --git a/server/AyaNova/models/dto/JobProgress.cs b/server/AyaNova/models/dto/JobProgress.cs new file mode 100644 index 00000000..531c0b7d --- /dev/null +++ b/server/AyaNova/models/dto/JobProgress.cs @@ -0,0 +1,26 @@ +using AyaNova.Biz; +using System; + +namespace AyaNova.Models +{ + /// + /// Job Progress + /// + public class JobProgress + { + + + /// + /// Progress string + /// + /// string + public string Progress { get; set; } + /// + /// Status of the job + /// + /// Job status + public JobStatus JobStatus { get; set; } + + } + +} diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index 5f7cc14c..cf4a6e23 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -20,9 +20,9 @@ namespace AyaNova.Util /////////// CHANGE THIS ON NEW SCHEMA UPDATE //////////////////// //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImportingAsync WHEN NEW TABLES ADDED!!!! - private const int DESIRED_SCHEMA_LEVEL = 7; + private const int DESIRED_SCHEMA_LEVEL = 8; - internal const long EXPECTED_COLUMN_COUNT = 1377; + internal const long EXPECTED_COLUMN_COUNT = 1378; internal const long EXPECTED_INDEX_COUNT = 161; internal const long EXPECTED_CHECK_CONSTRAINTS = 561; internal const long EXPECTED_FOREIGN_KEY_CONSTRAINTS = 204; @@ -1571,6 +1571,21 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); } + ////////////////////////////////////////////////// + // + // 8.0.13 additions for job feedback + // + if (currentSchema < 8) + { + LogUpdateMessage(log); + + await ExecQueryAsync("ALTER TABLE aopsjob ADD column progress TEXT"); + + await SetSchemaLevelAsync(++currentSchema); + + } + + //######################################### //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! diff --git a/server/AyaNova/util/ServerBootConfig.cs b/server/AyaNova/util/ServerBootConfig.cs index 350de791..6e9b4fec 100644 --- a/server/AyaNova/util/ServerBootConfig.cs +++ b/server/AyaNova/util/ServerBootConfig.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.IO; using Microsoft.Extensions.Configuration; @@ -15,7 +16,7 @@ namespace AyaNova.Util //STATIC HARD CODED COMPILE TIME DEFAULTS NOT SET THROUGH CONFIG internal const int FAILED_AUTH_DELAY = 3000;//ms internal const int JOB_OBJECT_HANDLE_BATCH_JOB_LOOP_DELAY = 200;//ms this delay is a temporary measure to ensure super big time consuming batch jobs don't use all server CPU resources - + internal const int JOB_PROGRESS_UPDATE_AND_CANCEL_CHECK_SECONDS = 15;//seconds between progress updates and checks for cancellation of long running jobs //UPLOAD LIMITS 1048576 = 1MiB for testing 10737420000 10737418240 10,737,418,240 internal const long MAX_ATTACHMENT_UPLOAD_BYTES = 10737420000;//slight bit of overage as 10737418241=10GiB