diff --git a/src/OrchardCore.Modules/OrchardCore.Workflows/Controllers/WorkflowTypeController.cs b/src/OrchardCore.Modules/OrchardCore.Workflows/Controllers/WorkflowTypeController.cs index a04edd4e613..12d62eae65e 100644 --- a/src/OrchardCore.Modules/OrchardCore.Workflows/Controllers/WorkflowTypeController.cs +++ b/src/OrchardCore.Modules/OrchardCore.Workflows/Controllers/WorkflowTypeController.cs @@ -99,7 +99,8 @@ public async Task Index(WorkflowTypeIndexOptions options, PagerPa options ??= new WorkflowTypeIndexOptions(); - var query = _session.Query(); + var query = _session.Query() + .Where(x => x.Latest); if (!string.IsNullOrWhiteSpace(options.Search)) { diff --git a/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowIndexProvider.cs b/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowIndexProvider.cs index 1ddc23dd5aa..8aa83daf9b7 100644 --- a/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowIndexProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowIndexProvider.cs @@ -9,6 +9,7 @@ public class WorkflowIndex : MapIndex { public long DocumentId { get; set; } public string WorkflowTypeId { get; set; } + public string WorkflowTypeVersionId { get; set; } public string WorkflowId { get; set; } public int WorkflowStatus { get; set; } public DateTime CreatedUtc { get; set; } @@ -22,6 +23,7 @@ public class WorkflowBlockingActivitiesIndex : MapIndex public string WorkflowTypeId { get; set; } public string WorkflowId { get; set; } public string WorkflowCorrelationId { get; set; } + public string WorkflowTypeVersionId { get; set; } } public class WorkflowIndexProvider : IndexProvider @@ -33,6 +35,7 @@ public override void Describe(DescribeContext context) new WorkflowIndex { WorkflowTypeId = workflow.WorkflowTypeId, + WorkflowTypeVersionId = workflow.WorkflowTypeVersionId, WorkflowId = workflow.WorkflowId, CreatedUtc = workflow.CreatedUtc, WorkflowStatus = (int)workflow.Status @@ -45,6 +48,7 @@ public override void Describe(DescribeContext context) new WorkflowBlockingActivitiesIndex { ActivityId = x.ActivityId, + WorkflowTypeVersionId = workflow.WorkflowTypeVersionId, ActivityName = x.Name, ActivityIsStart = x.IsStart, WorkflowTypeId = workflow.WorkflowTypeId, diff --git a/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowTypeIndexProvider.cs b/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowTypeIndexProvider.cs index 4b5554061d8..959a77b5c89 100644 --- a/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowTypeIndexProvider.cs +++ b/src/OrchardCore.Modules/OrchardCore.Workflows/Indexes/WorkflowTypeIndexProvider.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using OrchardCore.Workflows.Models; using YesSql.Indexes; @@ -11,6 +12,13 @@ public class WorkflowTypeIndex : MapIndex public string Name { get; set; } public bool IsEnabled { get; set; } public bool HasStart { get; set; } + public string DisplayName { get; set; } + public string WorkflowTypeVersionId { get; set; } + public bool Latest { get; set; } + public string UpdatedBy { get; set; } + public string CreatedBy { get; set; } + public DateTime CreatedUtc { get; set; } + public DateTime ModifiedUtc { get; set; } } public class WorkflowTypeStartActivitiesIndex : MapIndex @@ -33,7 +41,14 @@ public override void Describe(DescribeContext context) WorkflowTypeId = workflowType.WorkflowTypeId, Name = workflowType.Name, IsEnabled = workflowType.IsEnabled, - HasStart = workflowType.Activities.Any(x => x.IsStart) + HasStart = workflowType.Activities.Any(x => x.IsStart), + DisplayName = workflowType.DisplayName, + WorkflowTypeVersionId = workflowType.WorkflowTypeVersionId, + Latest = workflowType.Latest, + CreatedUtc = workflowType.CreatedUtc, + ModifiedUtc = workflowType.ModifiedUtc, + UpdatedBy = workflowType.UpdatedBy, + CreatedBy = workflowType.CreatedBy } ); diff --git a/src/OrchardCore.Modules/OrchardCore.Workflows/Migrations.cs b/src/OrchardCore.Modules/OrchardCore.Workflows/Migrations.cs index 78a9a89bbde..2cae0a9f826 100644 --- a/src/OrchardCore.Modules/OrchardCore.Workflows/Migrations.cs +++ b/src/OrchardCore.Modules/OrchardCore.Workflows/Migrations.cs @@ -1,13 +1,30 @@ using System; using System.Threading.Tasks; using OrchardCore.Data.Migration; +using OrchardCore.Modules; using OrchardCore.Workflows.Indexes; +using OrchardCore.Workflows.Models; +using OrchardCore.Workflows.Services; +using YesSql; using YesSql.Sql; namespace OrchardCore.Workflows { public class Migrations : DataMigration { + private readonly IWorkflowStore _workflowStore; + private readonly IWorkflowTypeStore _workflowTypeStore; + private readonly ISession _session; + private readonly IClock _clock; + + public Migrations(IWorkflowStore workflowStore, IWorkflowTypeStore workflowTypeStore, ISession session, IClock clock) + { + _workflowStore = workflowStore; + _workflowTypeStore = workflowTypeStore; + _session = session; + _clock = clock; + } + public async Task CreateAsync() { await SchemaBuilder.CreateMapIndexTableAsync(table => table @@ -147,5 +164,95 @@ public async Task UpdateFrom2Async() return 3; } + public async Task UpdateFrom3Async() + { + await SchemaBuilder.AlterIndexTableAsync(table => + { + table.AddColumn("DisplayName", c => c.WithLength(255)); + table.AddColumn("WorkflowTypeVersionId", c => c.WithLength(26)); + table.AddColumn("Latest"); + table.AddColumn("UpdatedBy"); + table.AddColumn("CreatedUtc"); + table.AddColumn("ModifiedUtc"); + }); + + await SchemaBuilder.AlterIndexTableAsync(table => table + .CreateIndex("IDX_WorkflowTypeIndex_DocumentId", + "DocumentId", + "WorkflowTypeId", + "Name", + "HasStart", + "IsEnabled", + "WorkflowTypeVersionId", + "DisplayName", + "Latest", + "CreatedUtc", + "ModifiedUtc", + "UpdatedBy") + ); + + //await SchemaBuilder.AlterIndexTableAsync(table => + //{ + // table.AddColumn("WorkflowTypeVersionId", c => c.WithLength(26)); + // table.CreateIndex("IDX_WorkflowTypeStartActivitiesIndex_DocumentId", + // "DocumentId", + // "WorkflowTypeId", + // "WorkflowTypeVersionId", + // "StartActivityId", + // "StartActivityName", + // "IsEnabled"); + //}); + + await SchemaBuilder.AlterIndexTableAsync(table => + { + table.AddColumn("WorkflowTypeVersionId", c => c.WithLength(26)); + table.CreateIndex("IDX_WorkflowIndex_DocumentId", + "DocumentId", + "WorkflowTypeId", + "WorkflowTypeVersionId", + "WorkflowId", + "WorkflowStatus", + "CreatedUtc"); + }); + + + await SchemaBuilder.AlterIndexTableAsync(table => + { + table.AddColumn("WorkflowTypeVersionId", c => c.WithLength(26)); + }); + + await SchemaBuilder.AlterIndexTableAsync(table => table + .CreateIndex("IDX_WFBlockingActivities_DocumentId_ActivityId", + "DocumentId", + "ActivityId", + "WorkflowTypeId", + "WorkflowTypeVersionId", + "WorkflowId")); + + await SchemaBuilder.AlterIndexTableAsync(table => table + .CreateIndex("IDX_WFBlockingActivities_DocumentId_ActivityName", + "DocumentId", + "ActivityName", + "WorkflowTypeId", + "WorkflowTypeVersionId", + "WorkflowCorrelationId")); + + var existsedTypes = await _session.Query().ListAsync(); + + foreach (var workflowType in existsedTypes) + { + workflowType.DisplayName = workflowType.Name; + workflowType.Latest = true; + workflowType.CreatedUtc = _clock.UtcNow; + workflowType.ModifiedUtc = _clock.UtcNow; + + await _workflowTypeStore.SaveAsync(workflowType); + } + + return 4; + } + + + } } diff --git a/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeIdGenerator.cs b/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeIdGenerator.cs index 829a41e0881..ff894545311 100644 --- a/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeIdGenerator.cs +++ b/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeIdGenerator.cs @@ -16,5 +16,10 @@ public string GenerateUniqueId(WorkflowType workflowType) { return _idGenerator.GenerateUniqueId(); } + + public string GenerateVersionUniqueId(WorkflowType workflowType) + { + return _idGenerator.GenerateUniqueId(); + } } } diff --git a/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeStore.cs b/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeStore.cs index 8f3ec49bd12..f9b24d06e78 100644 --- a/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeStore.cs +++ b/src/OrchardCore.Modules/OrchardCore.Workflows/Services/WorkflowTypeStore.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using OrchardCore.Modules; using OrchardCore.Workflows.Indexes; @@ -11,15 +13,20 @@ namespace OrchardCore.Workflows.Services { public class WorkflowTypeStore : IWorkflowTypeStore { - private readonly ISession _session; + private readonly YesSql.ISession _session; private readonly IEnumerable _handlers; private readonly ILogger _logger; - - public WorkflowTypeStore(ISession session, IEnumerable handlers, ILogger logger) + private readonly IClock _clock; + private readonly IWorkflowTypeIdGenerator _idGenerator; + private readonly IHttpContextAccessor _contextAccessor; + public WorkflowTypeStore(YesSql.ISession session, IEnumerable handlers, ILogger logger, IWorkflowTypeIdGenerator idGenerator, IClock clock, IHttpContextAccessor contextAccessor) { _session = session; _handlers = handlers; _logger = logger; + _idGenerator = idGenerator; + _clock = clock; + _contextAccessor = contextAccessor; } public Task GetAsync(long id) @@ -34,35 +41,53 @@ public Task> GetAsync(IEnumerable ids) public Task GetAsync(string workflowTypeId) { - return _session.Query(x => x.WorkflowTypeId == workflowTypeId).FirstOrDefaultAsync(); + return _session.Query(x => x.WorkflowTypeId == workflowTypeId && x.Latest == true) + .FirstOrDefaultAsync(); } public Task> ListAsync() { - return _session.Query().ListAsync(); + return _session.Query(x => x.Latest).ListAsync(); } - public Task> GetByStartActivityAsync(string activityName) + public async Task> GetByStartActivityAsync(string activityName) { - return _session + return await _session .Query(index => index.StartActivityName == activityName && index.IsEnabled) + .With(x => x.Latest) .ListAsync(); } public async Task SaveAsync(WorkflowType workflowType) { - var isNew = workflowType.Id == 0; + var existsedEntity = await GetAsync(workflowType.WorkflowTypeId); + var isNew = existsedEntity is null; + + // reset to new entity. + workflowType.Id = 0; + workflowType.WorkflowTypeVersionId = _idGenerator.GenerateVersionUniqueId(workflowType); + workflowType.DisplayName ??= workflowType.Name; + workflowType.Latest = true; + workflowType.ModifiedUtc = _clock.UtcNow; + workflowType.UpdatedBy = _contextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + await _session.SaveAsync(workflowType); if (isNew) { + workflowType.CreatedUtc = _clock.UtcNow; + workflowType.CreatedBy = workflowType.UpdatedBy; + var context = new WorkflowTypeCreatedContext(workflowType); await _handlers.InvokeAsync((handler, context) => handler.CreatedAsync(context), context, _logger); } else { + existsedEntity.Latest = false; + await _session.SaveAsync(existsedEntity); + var context = new WorkflowTypeUpdatedContext(workflowType); await _handlers.InvokeAsync((handler, context) => handler.UpdatedAsync(context), context, _logger); } @@ -70,16 +95,10 @@ public async Task SaveAsync(WorkflowType workflowType) public async Task DeleteAsync(WorkflowType workflowType) { - // Delete workflows first. - var workflows = await _session.Query(x => x.WorkflowTypeId == workflowType.WorkflowTypeId).ListAsync(); - - foreach (var workflow in workflows) - { - _session.Delete(workflow); - } - - // Then delete the workflow type. - _session.Delete(workflowType); + workflowType.Latest = false; + workflowType.ModifiedUtc = _clock.UtcNow; + workflowType.UpdatedBy = _contextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier); + await _session.SaveAsync(workflowType); var context = new WorkflowTypeDeletedContext(workflowType); await _handlers.InvokeAsync((handler, context) => handler.DeletedAsync(context), context, _logger); } diff --git a/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/Workflow.cs b/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/Workflow.cs index 00f3169bce4..c0ba6085992 100644 --- a/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/Workflow.cs +++ b/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/Workflow.cs @@ -53,5 +53,7 @@ public class Workflow /// Whether this workflow instance needs to be resumed atomically. /// public bool IsAtomic => LockTimeout > 0 && LockExpiration > 0; + + public string WorkflowTypeVersionId { get; set; } } } diff --git a/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/WorkflowType.cs b/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/WorkflowType.cs index f9e0f3f5e45..ef81030fbd2 100644 --- a/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/WorkflowType.cs +++ b/src/OrchardCore/OrchardCore.Workflows.Abstractions/Models/WorkflowType.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using OrchardCore.Entities; @@ -15,11 +16,23 @@ public class WorkflowType : Entity /// public string WorkflowTypeId { get; set; } + /// + /// Workflow type version. + /// + public string WorkflowTypeVersionId { get; set; } + + /// + /// Is the latest version. + /// + public bool Latest { get; set; } + /// /// The name of this workflow type. /// public string Name { get; set; } + public string DisplayName { get; set; } + /// /// Whether this workflow definition is enabled or not. /// @@ -54,5 +67,9 @@ public class WorkflowType : Entity /// A complete list of the transitions between the activities on this workflow. /// public IList Transitions { get; set; } = []; + public DateTime CreatedUtc { get; set; } + public DateTime ModifiedUtc { get; set; } + public string UpdatedBy { get; set; } + public string CreatedBy { get; set; } } } diff --git a/src/OrchardCore/OrchardCore.Workflows.Abstractions/Services/IWorkflowTypeIdGenerator.cs b/src/OrchardCore/OrchardCore.Workflows.Abstractions/Services/IWorkflowTypeIdGenerator.cs index 397c3e0528e..11d529aafd2 100644 --- a/src/OrchardCore/OrchardCore.Workflows.Abstractions/Services/IWorkflowTypeIdGenerator.cs +++ b/src/OrchardCore/OrchardCore.Workflows.Abstractions/Services/IWorkflowTypeIdGenerator.cs @@ -5,5 +5,6 @@ namespace OrchardCore.Workflows.Services public interface IWorkflowTypeIdGenerator { string GenerateUniqueId(WorkflowType workflowType); + string GenerateVersionUniqueId(WorkflowType workflowType); } }