diff --git a/Caspian.WorkflowFramework.Package/Caspian.WorkflowFramework.Package.csproj b/Caspian.WorkflowFramework.Package/Caspian.WorkflowFramework.Package.csproj index 6a69900..43f5e33 100644 --- a/Caspian.WorkflowFramework.Package/Caspian.WorkflowFramework.Package.csproj +++ b/Caspian.WorkflowFramework.Package/Caspian.WorkflowFramework.Package.csproj @@ -5,7 +5,7 @@ enable enable Caspian.WorkflowFramework - 1.1.0-preview.6 + 1.1.0-preview.7 Digital banking team Caspian Caspian.WorkflowFramework diff --git a/Caspian.WorkflowFramework.Package/Contracts/IWorkflowService.cs b/Caspian.WorkflowFramework.Package/Contracts/IWorkflowService.cs index 8b5cc83..a20639f 100644 --- a/Caspian.WorkflowFramework.Package/Contracts/IWorkflowService.cs +++ b/Caspian.WorkflowFramework.Package/Contracts/IWorkflowService.cs @@ -7,30 +7,46 @@ namespace Caspian.WorkflowFramework.Contracts /// /// Create and executes a new workflow instance. /// - /// - /// A new object. - /// Create a new workflow instance and executes it and waits for it to complete or reach a suspend point. Task> CreateAndExecuteAsync(CreateAndExecuteWorkflowModel input); /// - /// Create and get a last workflow running instance. + /// Create a new workflow instance if it does not exist or get the running instance. /// - /// - /// Task> CreateOrGetInstanceAsync(CreateAndExecuteWorkflowModel input); /// - /// Publishes the specified event. + /// Publishes the specified event to a workflow instance. /// - /// - /// A new object. Task> PublishEventAsync(PublishEventModel input); /// /// Inquiry last state of workflow instance. /// - /// - /// A new object. Task> InquiryStateAsync(InquiryStateModel input); + + /// + /// Gets all active workflow instances for a specific user. + /// + Task>> GetActiveInstancesAsync(GetActiveInstancesModel input); + + /// + /// Gets all active workflow instance IDs for a specific user. + /// + Task>> GetActiveInstanceIdsAsync(GetActiveInstancesModel input); + + /// + /// Gets the latest active workflow instance for a specific user and workflow name. + /// + Task> GetLatestActiveInstanceAsync(GetLatestActiveInstanceModel input); + + /// + /// Gets the ID of the latest active workflow instance for a specific user and workflow name. + /// + Task> GetLatestActiveInstanceIdAsync(GetLatestActiveInstanceModel input); + + /// + /// Cancels the specified workflow instance. + /// + Task> CancelInstanceAsync(CancelWorkflowInstanceModel input); } } diff --git a/Caspian.WorkflowFramework.Package/Models/CancelWorkflowInstanceModel.cs b/Caspian.WorkflowFramework.Package/Models/CancelWorkflowInstanceModel.cs new file mode 100644 index 0000000..c878efc --- /dev/null +++ b/Caspian.WorkflowFramework.Package/Models/CancelWorkflowInstanceModel.cs @@ -0,0 +1,6 @@ +namespace Caspian.WorkflowFramework.Models; +public class CancelWorkflowInstanceModel +{ + public string WorkflowInstanceId { get; set; } + public long UserId { get; set; } +} diff --git a/Caspian.WorkflowFramework.Package/Models/CreateAndExecuteWorkflowModel.cs b/Caspian.WorkflowFramework.Package/Models/CreateAndExecuteWorkflowModel.cs index a156031..0a09acf 100644 --- a/Caspian.WorkflowFramework.Package/Models/CreateAndExecuteWorkflowModel.cs +++ b/Caspian.WorkflowFramework.Package/Models/CreateAndExecuteWorkflowModel.cs @@ -1,33 +1,26 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; using Elsa.Common.Models; using JetBrains.Annotations; -namespace Caspian.WorkflowFramework.Models +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class CreateAndExecuteWorkflowModel { - [PublicAPI] - public class CreateAndExecuteWorkflowModel - { - /// - /// Workflow name - /// - [Required] - public string? Name { get; set; } + /// + /// Workflow name + /// + [Required] + public string? Name { get; set; } - /// - /// Workflow version - /// - [Required] - public VersionOptions Version { get; set; } + /// + /// Workflow version + /// + [Required] + public VersionOptions Version { get; set; } - /// - /// Current user's id - /// - [Required] - public long? UserId { get; set; } - } + /// + /// Current user's id + /// + [Required] + public long? UserId { get; set; } } diff --git a/Caspian.WorkflowFramework.Package/Models/GetActiveInstancesModel.cs b/Caspian.WorkflowFramework.Package/Models/GetActiveInstancesModel.cs new file mode 100644 index 0000000..c7e21a6 --- /dev/null +++ b/Caspian.WorkflowFramework.Package/Models/GetActiveInstancesModel.cs @@ -0,0 +1,5 @@ +namespace Caspian.WorkflowFramework.Models; +public class GetActiveInstancesModel +{ + public long UserId { get; set; } +} diff --git a/Caspian.WorkflowFramework.Package/Models/GetLatestActiveInstanceModel.cs b/Caspian.WorkflowFramework.Package/Models/GetLatestActiveInstanceModel.cs new file mode 100644 index 0000000..6d495fe --- /dev/null +++ b/Caspian.WorkflowFramework.Package/Models/GetLatestActiveInstanceModel.cs @@ -0,0 +1,6 @@ +namespace Caspian.WorkflowFramework.Models; +public class GetLatestActiveInstanceModel +{ + public long UserId { get; set; } + public string WorkflowName { get; set; } +} diff --git a/Caspian.WorkflowFramework.Package/Models/InquiryStateModel.cs b/Caspian.WorkflowFramework.Package/Models/InquiryStateModel.cs index bd24ded..32f9aed 100644 --- a/Caspian.WorkflowFramework.Package/Models/InquiryStateModel.cs +++ b/Caspian.WorkflowFramework.Package/Models/InquiryStateModel.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; -namespace Caspian.WorkflowFramework.Models +namespace Caspian.WorkflowFramework.Models; +public class InquiryStateModel { - public class InquiryStateModel - { - [Required] - public string WorkflowInstanceId { get; set; } - } + [Required] + public string WorkflowInstanceId { get; set; } } diff --git a/Caspian.WorkflowFramework.Package/Models/PublishEventModel.cs b/Caspian.WorkflowFramework.Package/Models/PublishEventModel.cs index 66b8bc5..cd8674a 100644 --- a/Caspian.WorkflowFramework.Package/Models/PublishEventModel.cs +++ b/Caspian.WorkflowFramework.Package/Models/PublishEventModel.cs @@ -1,19 +1,17 @@ -using JetBrains.Annotations; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using System.Text.Json; +using JetBrains.Annotations; -namespace Caspian.WorkflowFramework.Models +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class PublishEventModel { - [PublicAPI] - public class PublishEventModel - { - [Required] - public string WorkflowInstanceId { get; set; } + [Required] + public string WorkflowInstanceId { get; set; } - [Required] - public string EventName { get; set; } + [Required] + public string EventName { get; set; } - [Required] - public JsonElement EventData { get; set; } - } + [Required] + public JsonElement EventData { get; set; } } diff --git a/Caspian.WorkflowFramework.Package/Models/WorkflowModel.cs b/Caspian.WorkflowFramework.Package/Models/WorkflowModel.cs index 2413314..a57a111 100644 --- a/Caspian.WorkflowFramework.Package/Models/WorkflowModel.cs +++ b/Caspian.WorkflowFramework.Package/Models/WorkflowModel.cs @@ -1,57 +1,55 @@ -using JetBrains.Annotations; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading.Tasks; +using System.Text.Json; +using JetBrains.Annotations; -namespace Caspian.WorkflowFramework.Models +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class WorkflowModel { - [PublicAPI] - public class WorkflowModel - { - /// - /// شناسه گردش کار - /// - public string? InstanceId { get; set; } + /// + /// شناسه گردش کار + /// + public string? InstanceId { get; set; } - /// - /// نام گردش کار - /// - public string? Name { get; set; } + /// + /// نام گردش کار + /// + public string? Name { get; set; } - /// - /// اتمام گردش کار؟ - /// - public bool IsComplete { get; set; } + /// + /// اتمام گردش کار؟ + /// + public bool IsComplete { get; set; } - /// - /// برای کدام کاربر؟ - /// - public long? UserId { get; set; } + /// + /// برای کدام کاربر؟ + /// + public long? UserId { get; set; } - /// - /// داده صفحه جاری - /// - public JsonElement? CurrentPageData { get; set; } + /// + /// داده صفحه جاری + /// + public JsonElement? CurrentPageData { get; set; } - /// - /// داده صفحه بعد - /// - /// فعلا استفاده ای نداریم - /// - /// - public JsonElement? NextPageData { get; set; } + /// + /// داده صفحه بعد + /// + /// فعلا استفاده ای نداریم + /// + /// + public JsonElement? NextPageData { get; set; } - /// - /// آدرس صفحه بعدی - /// - public string? NextPageUrl { get; set; } + /// + /// آدرس صفحه بعدی + /// + public string? NextPageUrl { get; set; } - /// - /// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود - /// - public string? NextPageEventName { get; set; } - } + /// + /// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود + /// + public string? NextPageEventName { get; set; } + + /// + /// جای ذخیره‌ی موقت برای استفاده های بعدی + /// + public JsonElement? WorkflowStash { get; set; } } diff --git a/Caspian.WorkflowFramework.Package/Models/WorkflowResponse.cs b/Caspian.WorkflowFramework.Package/Models/WorkflowResponse.cs index 9d6aff4..483b143 100644 --- a/Caspian.WorkflowFramework.Package/Models/WorkflowResponse.cs +++ b/Caspian.WorkflowFramework.Package/Models/WorkflowResponse.cs @@ -1,37 +1,30 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using JetBrains.Annotations; +using JetBrains.Annotations; -namespace Caspian.WorkflowFramework.Models +namespace Caspian.WorkflowFramework.Models; +[PublicAPI] +public class WorkflowResponse : WorkflowResponseBase where T : class, new() { - [PublicAPI] - public class WorkflowResponse : WorkflowResponseBase where T : class, new() + public WorkflowResponse() { - public WorkflowResponse() - { - Data = new T(); - } - - /// - /// پاسخ - /// - public T? Data { get; set; } + Data = new T(); } - [PublicAPI] - public class WorkflowResponseBase - { - /// - /// وضعیت پاسخ - /// - public int? Status { get; set; } - - /// - /// پیام پاسخ - /// - public string? Message { get; set; } - } + /// + /// پاسخ + /// + public T? Data { get; set; } +} + +[PublicAPI] +public class WorkflowResponseBase +{ + /// + /// وضعیت پاسخ + /// + public int? Status { get; set; } + + /// + /// پیام پاسخ + /// + public string? Message { get; set; } } diff --git a/Caspian.WorkflowFramework.Package/Services/WorkflowService.cs b/Caspian.WorkflowFramework.Package/Services/WorkflowService.cs index 1603bdf..485ae1a 100644 --- a/Caspian.WorkflowFramework.Package/Services/WorkflowService.cs +++ b/Caspian.WorkflowFramework.Package/Services/WorkflowService.cs @@ -1,6 +1,7 @@ using Caspian.WorkflowFramework.Contracts; using Caspian.WorkflowFramework.Models; using Caspian.WorkflowFramework.Utility; +using Elsa.Common.Entities; using Elsa.Common.Models; using Elsa.Workflows; using Elsa.Workflows.Management; @@ -26,7 +27,9 @@ namespace Caspian.WorkflowFramework.Services private readonly IWorkflowDefinitionService _workflowDefinitionService; private readonly IWorkflowInstanceVariableManager _workflowInstanceVariableManager; private readonly IWorkflowInstanceManager _workflowInstanceManager; + private readonly IWorkflowInstanceStore _workflowInstanceStore; private readonly IEventPublisher _eventPublisher; + private readonly IWorkflowDefinitionCacheManager _workflowDefinitionCacheManager; private readonly IMdbLogger _logger; #endregion @@ -39,6 +42,7 @@ namespace Caspian.WorkflowFramework.Services _workflowDefinitionService = _serviceProvider.GetRequiredService(); _workflowInstanceVariableManager = _serviceProvider.GetRequiredService(); _workflowInstanceManager = _serviceProvider.GetRequiredService(); + _workflowInstanceStore = _serviceProvider.GetRequiredService(); _eventPublisher = _serviceProvider.GetRequiredService(); _logger = logger; } @@ -167,13 +171,25 @@ namespace Caspian.WorkflowFramework.Services return response; } - var runningWorkflowInstance = await _workflowInstanceManager - .FindAsync(new WorkflowInstanceFilter() - { - ExternalUserId = input.UserId, - DefinitionId = definition.DefinitionId, - WorkflowStatus = WorkflowStatus.Running - }); + var instances = await _workflowInstanceStore.FindManyAsync( + new WorkflowInstanceFilter + { + ExternalUserId = input.UserId, + DefinitionId = definition.DefinitionId, + WorkflowStatus = WorkflowStatus.Running + }, + new WorkflowInstanceOrder(x => x.UpdatedAt!, OrderDirection.Descending) + ); + + var runningWorkflowInstance = instances.FirstOrDefault(); + + //var runningWorkflowInstance = await _workflowInstanceManager + // .FindAsync(new WorkflowInstanceFilter() + // { + // ExternalUserId = input.UserId, + // DefinitionId = definition.DefinitionId, + // WorkflowStatus = WorkflowStatus.Running + // }); var workflowInstanceId = string.Empty; if (runningWorkflowInstance != null) @@ -195,14 +211,15 @@ namespace Caspian.WorkflowFramework.Services workflowInstanceId = result.WorkflowInstanceId; } + var id = workflowInstanceId; var variables = await _workflowInstanceVariableManager - .GetVariablesAsync(workflowInstanceId); + .GetVariablesAsync(id); // تبدیل به دیکشنری var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value); var workflowModel = dictionary.ConvertToWorkflowModel(); - workflowModel.InstanceId = workflowClient.WorkflowInstanceId; + workflowModel.InstanceId = workflowInstanceId; workflowModel.UserId = input.UserId; response = new WorkflowResponse() @@ -282,6 +299,7 @@ namespace Caspian.WorkflowFramework.Services payload = JsonConvert.DeserializeObject(json, settings); } + //await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, input.EventData, asynchronous: true); await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, payload); var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId); @@ -393,5 +411,311 @@ namespace Caspian.WorkflowFramework.Services } } #endregion + + #region GetActiveInstancesAsync + public async Task>> GetActiveInstancesAsync(GetActiveInstancesModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of GetActiveInstancesAsync", input); + + var instances = await _workflowInstanceStore.FindManyAsync( + new WorkflowInstanceFilter + { + ExternalUserId = input.UserId, + WorkflowStatus = WorkflowStatus.Running + }); + + var result = new List(); + + foreach (var instance in instances) + { + var variables = await _workflowInstanceVariableManager.GetVariablesAsync(instance.Id); + var dict = variables.ToDictionary(x => x.Variable.Name, x => x.Value); + + var model = dict.ConvertToWorkflowModel(); + model.InstanceId = instance.Id; + model.UserId = instance.ExternalUserId; + + result.Add(model); + } + + var response = new WorkflowResponse> + { + Status = 0, + Message = "Success", + Data = result + }; + + sw.Stop(); + _logger.Log("End of GetActiveInstancesAsync", response, sw.ElapsedMilliseconds); + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse> + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + _logger.Log("Exception in GetActiveInstancesAsync", response, sw.ElapsedMilliseconds, ex); + return response; + } + } + #endregion + + #region GetActiveInstanceIdsAsync + public async Task>> GetActiveInstanceIdsAsync(GetActiveInstancesModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of GetActiveInstanceIdsAsync", input); + + var instances = await _workflowInstanceStore.FindManyAsync( + new WorkflowInstanceFilter + { + ExternalUserId = input.UserId, + WorkflowStatus = WorkflowStatus.Running + }); + + var instanceIds = instances.Select(i => i.Id).ToList(); + + var response = new WorkflowResponse> + { + Status = 0, + Message = "Success", + Data = instanceIds + }; + + sw.Stop(); + _logger.Log("End of GetActiveInstanceIdsAsync", response, sw.ElapsedMilliseconds); + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse> + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + _logger.Log("Exception in GetActiveInstanceIdsAsync", response, sw.ElapsedMilliseconds, ex); + return response; + } + } + #endregion + + #region GetLatestActiveInstanceAsync + public async Task> GetLatestActiveInstanceAsync(GetLatestActiveInstanceModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of GetLatestActiveInstanceAsync", input); + + var definition = await _workflowDefinitionService.FindWorkflowDefinitionAsync( + new WorkflowDefinitionFilter { Name = input.WorkflowName }); + + if (definition == null) + { + return new WorkflowResponse + { + Status = 1, + Message = $"Workflow '{input.WorkflowName}' not found", + Data = null + }; + } + + var instances = await _workflowInstanceStore.FindManyAsync( + new WorkflowInstanceFilter + { + ExternalUserId = input.UserId, + DefinitionId = definition.DefinitionId, + WorkflowStatus = WorkflowStatus.Running + }, + new WorkflowInstanceOrder(x => x.UpdatedAt!, OrderDirection.Descending) + ); + + var latest = instances.FirstOrDefault(); + + if (latest == null) + { + return new WorkflowResponse + { + Status = 1, + Message = "No active workflow instance found", + Data = null + }; + } + + var variables = await _workflowInstanceVariableManager.GetVariablesAsync(latest.Id); + var dict = variables.ToDictionary(x => x.Variable.Name, x => x.Value); + + var model = dict.ConvertToWorkflowModel(); + model.InstanceId = latest.Id; + model.UserId = latest.ExternalUserId; + + var response = new WorkflowResponse + { + Status = 0, + Message = "Success", + Data = model + }; + + sw.Stop(); + _logger.Log("End of GetLatestActiveInstanceAsync", response, sw.ElapsedMilliseconds); + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + _logger.Log("Exception in GetLatestActiveInstanceAsync", response, sw.ElapsedMilliseconds, ex); + return response; + } + } + #endregion + + #region GetLatestActiveInstanceIdAsync + public async Task> GetLatestActiveInstanceIdAsync(GetLatestActiveInstanceModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of GetLatestActiveInstanceIdAsync", input); + + var definition = await _workflowDefinitionService.FindWorkflowDefinitionAsync( + new WorkflowDefinitionFilter { Name = input.WorkflowName }); + + if (definition == null) + { + return new WorkflowResponse + { + Status = 1, + Message = $"Workflow '{input.WorkflowName}' not found", + Data = null + }; + } + + var instances = await _workflowInstanceStore.FindManyAsync( + new WorkflowInstanceFilter + { + ExternalUserId = input.UserId, + DefinitionId = definition.DefinitionId, + WorkflowStatus = WorkflowStatus.Running + }, + new WorkflowInstanceOrder(x => x.UpdatedAt!, OrderDirection.Descending) + ); + + var latest = instances.FirstOrDefault(); + + if (latest == null) + { + return new WorkflowResponse + { + Status = 1, + Message = "No active workflow instance found", + Data = null + }; + } + + var response = new WorkflowResponse + { + Status = 0, + Message = "Success", + Data = latest.Id + }; + + sw.Stop(); + _logger.Log("End of GetLatestActiveInstanceIdAsync", response, sw.ElapsedMilliseconds); + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse + { + Status = 100, + Message = ex.Message, + Data = null + }; + + sw.Stop(); + _logger.Log("Exception in GetLatestActiveInstanceIdAsync", response, sw.ElapsedMilliseconds, ex); + return response; + } + } + #endregion + + #region CancelInstanceAsync + public async Task> CancelInstanceAsync(CancelWorkflowInstanceModel input) + { + var sw = Stopwatch.StartNew(); + try + { + _logger.Log("Start of CancelInstanceAsync", WFInstanceId: input.WorkflowInstanceId, input); + + // پیدا کردن instance + var instance = await _workflowInstanceStore.FindAsync( + new WorkflowInstanceFilter + { + ExternalUserId = input.UserId, + WorkflowStatus = WorkflowStatus.Running + }); + + if (instance == null) + { + return new WorkflowResponse + { + Status = 1, + Message = "Workflow instance not found", + Data = false + }; + } + + // ایجاد client روی workflow instance موردنظر + var client = await _workflowRuntime.CreateClientAsync(instance.Id); + + // لغو workflow + await client.CancelAsync(); + + var response = new WorkflowResponse + { + Status = 0, + Message = "Success", + Data = true + }; + + sw.Stop(); + _logger.Log("End of CancelInstanceAsync", WFInstanceId: instance.Id, response, sw.ElapsedMilliseconds); + return response; + } + catch (Exception ex) + { + var response = new WorkflowResponse + { + Status = 100, + Message = ex.Message, + Data = false + }; + + sw.Stop(); + _logger.Log("Exception in CancelInstanceAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex); + return response; + } + } + #endregion } }