ForceCancel
This commit is contained in:
@@ -97,7 +97,8 @@ public class WorkflowController : Controller, ILoggable
|
|||||||
{
|
{
|
||||||
_logger.Log("Start of Cancel action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
|
_logger.Log("Start of Cancel action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(input));
|
||||||
|
|
||||||
var result = await _workflowService.CancelInstanceAsync(input);
|
//var result = await _workflowService.CancelInstanceAsync(input);
|
||||||
|
var result = await _workflowService.ForceCancelAsync(input);
|
||||||
|
|
||||||
_logger.Log("End of Cancel action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result));
|
_logger.Log("End of Cancel action", WFInstanceId: input.WorkflowInstanceId, JsonConvert.SerializeObject(result));
|
||||||
|
|
||||||
@@ -160,4 +161,11 @@ public class WorkflowController : Controller, ILoggable
|
|||||||
return Json(result);
|
return Json(result);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
[HttpPost("GetNextPossibleEvents")]
|
||||||
|
public async Task<ActionResult<WorkflowResponse<WorkflowModel>>> GetNextPossibleEvents(string workflowInstanceId)
|
||||||
|
{
|
||||||
|
var result = await _workflowService.GetNextPossibleEventsAsync(workflowInstanceId);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ public interface IWorkflowService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Task<WorkflowResponse<dynamic>> CancelInstanceAsync(CancelWorkflowInstanceModel input);
|
Task<WorkflowResponse<dynamic>> CancelInstanceAsync(CancelWorkflowInstanceModel input);
|
||||||
|
|
||||||
|
Task<WorkflowResponse<dynamic>> ForceCancelAsync(CancelWorkflowInstanceModel input);
|
||||||
|
|
||||||
|
Task<WorkflowResponse<WorkflowModel>> GetNextPossibleEventsAsync(string workflowInstanceId);
|
||||||
|
|
||||||
///// <summary>
|
///// <summary>
|
||||||
///// Evicts a workflow definition from cache (optional, commented out in service).
|
///// Evicts a workflow definition from cache (optional, commented out in service).
|
||||||
///// </summary>
|
///// </summary>
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace Caspian.WorkflowFramework.Models;
|
||||||
|
public sealed class PossibleEventDto
|
||||||
|
{
|
||||||
|
public string EventName { get; set; } = default!;
|
||||||
|
public string? BookmarkId { get; set; }
|
||||||
|
public string? ActivityId { get; set; }
|
||||||
|
public string? ActivityNodeId { get; set; }
|
||||||
|
public string? ActivityInstanceId { get; set; }
|
||||||
|
public DateTimeOffset? CreatedAt { get; set; }
|
||||||
|
public bool? AutoBurn { get; set; }
|
||||||
|
public string? CallbackMethodName { get; set; }
|
||||||
|
public IDictionary<string, string>? Metadata { get; set; }
|
||||||
|
}
|
||||||
@@ -11,7 +11,10 @@ using Elsa.Workflows.Management;
|
|||||||
using Elsa.Workflows.Management.Filters;
|
using Elsa.Workflows.Management.Filters;
|
||||||
using Elsa.Workflows.Models;
|
using Elsa.Workflows.Models;
|
||||||
using Elsa.Workflows.Runtime;
|
using Elsa.Workflows.Runtime;
|
||||||
|
using Elsa.Workflows.Runtime.Filters;
|
||||||
using Elsa.Workflows.Runtime.Messages;
|
using Elsa.Workflows.Runtime.Messages;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
@@ -30,6 +33,9 @@ public class WorkflowService : IWorkflowService, ILoggable
|
|||||||
private readonly IEventPublisher _eventPublisher;
|
private readonly IEventPublisher _eventPublisher;
|
||||||
private readonly IWorkflowDefinitionCacheManager _workflowDefinitionCacheManager;
|
private readonly IWorkflowDefinitionCacheManager _workflowDefinitionCacheManager;
|
||||||
private readonly IMdbLogger<WorkflowService> _logger;
|
private readonly IMdbLogger<WorkflowService> _logger;
|
||||||
|
|
||||||
|
private readonly IBookmarkStore _bookmarkStore;
|
||||||
|
private readonly IActivityExecutionStore _activityExecutionStore;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Ctor
|
#region Ctor
|
||||||
@@ -44,6 +50,8 @@ public class WorkflowService : IWorkflowService, ILoggable
|
|||||||
_workflowInstanceStore = _serviceProvider.GetRequiredService<IWorkflowInstanceStore>();
|
_workflowInstanceStore = _serviceProvider.GetRequiredService<IWorkflowInstanceStore>();
|
||||||
_eventPublisher = _serviceProvider.GetRequiredService<IEventPublisher>();
|
_eventPublisher = _serviceProvider.GetRequiredService<IEventPublisher>();
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_bookmarkStore = _serviceProvider.GetRequiredService<IBookmarkStore>();
|
||||||
|
_activityExecutionStore = _serviceProvider.GetRequiredService<IActivityExecutionStore>();
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -725,6 +733,189 @@ public class WorkflowService : IWorkflowService, ILoggable
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region ForceCancelAsync
|
||||||
|
public async Task<WorkflowResponse<dynamic>> ForceCancelAsync(CancelWorkflowInstanceModel input)
|
||||||
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Log("Start ForceCancelAsync", WFInstanceId: input.WorkflowInstanceId, input);
|
||||||
|
|
||||||
|
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
|
||||||
|
|
||||||
|
if (instance == null)
|
||||||
|
return new WorkflowResponse<dynamic> { Status = 1, Message = "Instance not found", Data = false };
|
||||||
|
|
||||||
|
// جلوگری از Resume شدن دوباره
|
||||||
|
instance.Status = WorkflowStatus.Finished;
|
||||||
|
instance.SubStatus = WorkflowSubStatus.Cancelled;
|
||||||
|
|
||||||
|
// پاکسازی bookmarks از دیتابیس واقعی
|
||||||
|
var bookmarkFilter = new BookmarkFilter()
|
||||||
|
{
|
||||||
|
WorkflowInstanceId = input.WorkflowInstanceId
|
||||||
|
};
|
||||||
|
await _bookmarkStore.DeleteAsync(bookmarkFilter);
|
||||||
|
|
||||||
|
// پاکسازی scheduled activities (در صورتی که runtime distributed باشد)
|
||||||
|
var activityFilter = new ActivityExecutionRecordFilter()
|
||||||
|
{
|
||||||
|
WorkflowInstanceId = input.WorkflowInstanceId
|
||||||
|
};
|
||||||
|
await _activityExecutionStore.DeleteManyAsync(activityFilter);
|
||||||
|
|
||||||
|
//// پاکسازی incidents (در پروژههای بزرگ ضروری است)
|
||||||
|
//await _incidentStore.DeleteManyAsync(
|
||||||
|
// x => x.WorkflowInstanceId == input.WorkflowInstanceId
|
||||||
|
//);
|
||||||
|
|
||||||
|
// ذخیره instance تغییر یافته
|
||||||
|
await _workflowInstanceManager.SaveAsync(instance);
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
var response = new WorkflowResponse<dynamic>
|
||||||
|
{
|
||||||
|
Status = 0,
|
||||||
|
Message = "Force cancel completed",
|
||||||
|
Data = true
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.Log("End ForceCancelAsync", WFInstanceId: instance.Id, response, sw.ElapsedMilliseconds);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
var response = new WorkflowResponse<dynamic>
|
||||||
|
{
|
||||||
|
Status = 100,
|
||||||
|
Message = ex.Message,
|
||||||
|
Data = false
|
||||||
|
};
|
||||||
|
|
||||||
|
_logger.Log("Exception in ForceCancelAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public async Task<WorkflowResponse<WorkflowModel>> GetNextPossibleEventsAsync(string workflowInstanceId)
|
||||||
|
{
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.Log("Start of GetNextPossibleEventsAsync", WFInstanceId: workflowInstanceId, workflowInstanceId);
|
||||||
|
|
||||||
|
var instance = await _workflowInstanceManager.FindByIdAsync(workflowInstanceId);
|
||||||
|
if (instance == null)
|
||||||
|
{
|
||||||
|
sw.Stop();
|
||||||
|
return new WorkflowResponse<WorkflowModel>
|
||||||
|
{
|
||||||
|
Status = 1,
|
||||||
|
Message = $"Workflow instance with id '{workflowInstanceId}' not found",
|
||||||
|
Data = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(workflowInstanceId);
|
||||||
|
var dict = variables.ToDictionary(a => a.Variable.Name, a => a.Value);
|
||||||
|
|
||||||
|
var model = dict.ConvertToWorkflowModel();
|
||||||
|
model.InstanceId = instance.Id;
|
||||||
|
model.UserId = instance.ExternalUserId;
|
||||||
|
model.InstanceMode = "exist";
|
||||||
|
|
||||||
|
var bookmarks = instance.WorkflowState?.Bookmarks ?? new List<Bookmark>();
|
||||||
|
|
||||||
|
var possible = bookmarks
|
||||||
|
.Select(b => new PossibleEventDto
|
||||||
|
{
|
||||||
|
EventName = ExtractEventName(b) ?? "",
|
||||||
|
BookmarkId = b.Id,
|
||||||
|
ActivityId = b.ActivityId,
|
||||||
|
ActivityNodeId = b.ActivityNodeId,
|
||||||
|
ActivityInstanceId = b.ActivityInstanceId,
|
||||||
|
CreatedAt = b.CreatedAt,
|
||||||
|
AutoBurn = b.AutoBurn,
|
||||||
|
CallbackMethodName = b.CallbackMethodName,
|
||||||
|
Metadata = b.Metadata
|
||||||
|
})
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.EventName))
|
||||||
|
.GroupBy(x => x.EventName, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(g => g.First())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// fallback
|
||||||
|
if (possible.Count == 0 && !string.IsNullOrWhiteSpace(model.NextPageEventName))
|
||||||
|
{
|
||||||
|
possible.Add(new PossibleEventDto
|
||||||
|
{
|
||||||
|
EventName = model.NextPageEventName!
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
model.NextPageData = System.Text.Json.JsonSerializer.SerializeToElement(new { possibleEvents = possible });
|
||||||
|
|
||||||
|
sw.Stop();
|
||||||
|
_logger.Log("End of GetNextPossibleEventsAsync", WFInstanceId: workflowInstanceId, model, sw.ElapsedMilliseconds);
|
||||||
|
|
||||||
|
return new WorkflowResponse<WorkflowModel>
|
||||||
|
{
|
||||||
|
Status = 0,
|
||||||
|
Message = "Success",
|
||||||
|
Data = model
|
||||||
|
};
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
sw.Stop();
|
||||||
|
_logger.Log("Exception in GetNextPossibleEventsAsync", WFInstanceId: workflowInstanceId, ex.Message, sw.ElapsedMilliseconds, ex);
|
||||||
|
|
||||||
|
return new WorkflowResponse<WorkflowModel>
|
||||||
|
{
|
||||||
|
Status = 100,
|
||||||
|
Message = ex.Message,
|
||||||
|
Data = null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private static string? ExtractEventName(Elsa.Workflows.Models.Bookmark b)
|
||||||
|
{
|
||||||
|
// حالت غالب: خود Name همان eventName است
|
||||||
|
//if (!string.IsNullOrWhiteSpace(b.Name))
|
||||||
|
// return b.Name;
|
||||||
|
//var eventName = ((Elsa.Workflows.Runtime.Stimuli.EventStimulus)b.Payload)?.EventName;
|
||||||
|
|
||||||
|
// اگر جایی Name خالی بود، از Payload هم تلاش میکنیم.
|
||||||
|
var payload = b.Payload;
|
||||||
|
if (payload == null) return null;
|
||||||
|
|
||||||
|
// اگر payload دیکشنری باشد:
|
||||||
|
if (payload is IDictionary<string, object> dict)
|
||||||
|
{
|
||||||
|
if (dict.TryGetValue("EventName", out var v) && v is string s1 && !string.IsNullOrWhiteSpace(s1)) return s1;
|
||||||
|
if (dict.TryGetValue("eventName", out v) && v is string s2 && !string.IsNullOrWhiteSpace(s2)) return s2;
|
||||||
|
if (dict.TryGetValue("Name", out v) && v is string s3 && !string.IsNullOrWhiteSpace(s3)) return s3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر payload شیء باشد و property داشته باشد (Reflection سبک):
|
||||||
|
var t = payload.GetType();
|
||||||
|
foreach (var prop in new[] { "EventName", "eventName", "Name" })
|
||||||
|
{
|
||||||
|
var p = t.GetProperty(prop);
|
||||||
|
if (p?.GetValue(payload) is string s && !string.IsNullOrWhiteSpace(s))
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
//#region EvictDefFromCacheAsync
|
//#region EvictDefFromCacheAsync
|
||||||
///// <inheritdoc />
|
///// <inheritdoc />
|
||||||
//public async Task<WorkflowResponse<WorkflowModel>> EvictDefFromCacheAsync(string definitionId)
|
//public async Task<WorkflowResponse<WorkflowModel>> EvictDefFromCacheAsync(string definitionId)
|
||||||
|
|||||||
Reference in New Issue
Block a user