Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Workflow trimming (pruning) improvements #16565

Merged
merged 36 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dacdf69
Some clean-up
Piedone Aug 14, 2024
5122df6
Renaming workflow pruning to trimming
Piedone Aug 14, 2024
2197485
Removing incorrect AddDays(1) from Workflows and Audit Trail trimming
Piedone Aug 14, 2024
203a13e
Adding configuration for the batch size
Piedone Aug 14, 2024
574449b
Docs
Piedone Aug 14, 2024
5fbc41b
Formatting
Piedone Aug 14, 2024
eebab50
Making WorkflowStatusBuilder public because it's needed for template …
Piedone Aug 14, 2024
7e0f140
It can remain sealed, though
Piedone Aug 14, 2024
ba03b9e
Explicit localizable status names
Piedone Aug 15, 2024
52ac3cc
More prune rename
Piedone Aug 15, 2024
2f02cfa
Ordering workflows to make sure the oldest are deleted first
Piedone Aug 15, 2024
e9191d8
Moving trimming service registrations to root Startup
Piedone Aug 15, 2024
a26a27c
Formatting
Piedone Aug 15, 2024
3dacf27
Renaming IWorkflowTrimmingManager to IWorkflowTrimmingService
Piedone Aug 15, 2024
746f10b
Removing unneeded <section>
Piedone Aug 15, 2024
b2fa6f0
Using Html.IdFor()s everywhere
Piedone Aug 15, 2024
1ea28fc
Formatting
Piedone Aug 15, 2024
8fa134b
Shorthand GetSection()
Piedone Aug 15, 2024
3616643
Moving LastRunUtc to its own document
Piedone Aug 15, 2024
3d28440
Moving trimming service registrations to AddTrimmingServices()
Piedone Aug 15, 2024
c4817ca
Formatting
Piedone Aug 15, 2024
aad7b0d
Refactoring
Piedone Aug 15, 2024
da58975
Some margins on the form
Piedone Aug 15, 2024
2d915ad
Formatting
Piedone Aug 15, 2024
a61cf0a
Formatting
Piedone Aug 15, 2024
4451d0a
Formatting
Piedone Aug 15, 2024
549412e
Formatting
Piedone Aug 15, 2024
445581e
Formatting
Piedone Aug 15, 2024
0bd774b
Revert "Formatting"
Piedone Aug 15, 2024
51cd4e3
Formatting
Piedone Aug 15, 2024
0b9682d
Merge remote-tracking branch 'official/main' into issue/OCORE-194
Piedone Aug 15, 2024
5482353
Formatting
Piedone Aug 15, 2024
a4d6d24
Revert
Piedone Aug 15, 2024
62026a0
Ordering by Id
Piedone Aug 15, 2024
5568774
Clarifying comment
Piedone Aug 15, 2024
f42a9c3
Grammar
Piedone Aug 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/OrchardCore.Cms.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,11 @@
//},
//"OrchardCore_Tenants": {
// "TenantRemovalAllowed": true // Whether tenant removal is allowed, false by default.
//},
//"OrchardCore_Workflows": {
// "Trimming": {
// "BatchSize": 1000
// }
//}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public Task<AuditTrailEvent> GetEventAsync(string eventId) =>

public async Task<int> TrimEventsAsync(TimeSpan retentionPeriod)
{
var dateThreshold = _clock.UtcNow.AddDays(1) - retentionPeriod;
var dateThreshold = _clock.UtcNow - retentionPeriod;

var events = await _session.Query<AuditTrailEvent, AuditTrailEventIndex>(collection: AuditTrailEvent.Collection)
.Where(index => index.CreatedUtc <= dateThreshold)
Expand Down
7 changes: 7 additions & 0 deletions src/OrchardCore.Modules/OrchardCore.Workflows/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using OrchardCore.Data.Migration;
using OrchardCore.Deployment;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Liquid;
using OrchardCore.Modules;
using OrchardCore.Navigation;
Expand All @@ -29,6 +30,10 @@ namespace OrchardCore.Workflows;

public sealed class Startup : StartupBase
{
private readonly IShellConfiguration _shellConfiguration;

public Startup(IShellConfiguration shellConfiguration) => _shellConfiguration = shellConfiguration;

public override void ConfigureServices(IServiceCollection services)
{
services.Configure<TemplateOptions>(o =>
Expand Down Expand Up @@ -78,6 +83,8 @@ public override void ConfigureServices(IServiceCollection services)

services.AddRecipeExecutionStep<WorkflowTypeStep>();
services.AddTransient<IConfigureOptions<ResourceManagementOptions>, ResourceManagementOptionsConfiguration>();

services.AddTrimmingServices(_shellConfiguration);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Localization;
using OrchardCore.Navigation;
using OrchardCore.Workflows.WorkflowPruning.Drivers;
using OrchardCore.Workflows.Trimming.Drivers;

namespace OrchardCore.Workflows.WorkflowPruning;
namespace OrchardCore.Workflows.Trimming;

public sealed class AdminMenu : INavigationProvider
{
private static readonly RouteValueDictionary _routeValues = new()
{
{ "area", "OrchardCore.Settings" },
{ "groupId", WorkflowPruningDisplayDriver.GroupId },
{ "groupId", WorkflowTrimmingDisplayDriver.GroupId },
};

internal readonly IStringLocalizer S;
Expand All @@ -30,7 +30,7 @@ public Task BuildNavigationAsync(string name, NavigationBuilder builder)

builder.Add(S["Configuration"], configuration => configuration
.Add(S["Settings"], settings => settings
.Add(S["Workflow Pruning"], S["Workflow Pruning"], pruning => pruning
.Add(S["Workflow Trimming"], S["Workflow Trimming"], trimming => trimming
.Action("Index", "Admin", _routeValues)
.Permission(Permissions.ManageWorkflowSettings)
.LocalNav()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using OrchardCore.DisplayManagement.Entities;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;
using OrchardCore.Documents;
using OrchardCore.Settings;
using OrchardCore.Workflows.Trimming.Models;
using OrchardCore.Workflows.Trimming.ViewModels;

namespace OrchardCore.Workflows.Trimming.Drivers;

public sealed class WorkflowTrimmingDisplayDriver : SiteDisplayDriver<WorkflowTrimmingSettings>
{
public const string GroupId = "WorkflowTrimmingSettings";

private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IAuthorizationService _authorizationService;
private readonly IDocumentManager<WorkflowTrimmingState> _workflowTrimmingStateDocumentManager;

public WorkflowTrimmingDisplayDriver(
IAuthorizationService authorizationService,
IHttpContextAccessor httpContextAccessor,
IDocumentManager<WorkflowTrimmingState> workflowTrimmingStateDocumentManager)
{
_authorizationService = authorizationService;
_httpContextAccessor = httpContextAccessor;
_workflowTrimmingStateDocumentManager = workflowTrimmingStateDocumentManager;
}

protected override string SettingsGroupId
=> GroupId;

public override IDisplayResult Edit(ISite site, WorkflowTrimmingSettings settings, BuildEditorContext context)
{
return Initialize<WorkflowTrimmingViewModel>("WorkflowTrimming_Fields_Edit", async model =>
{
model.RetentionDays = settings.RetentionDays;
model.LastRunUtc = (await _workflowTrimmingStateDocumentManager.GetOrCreateImmutableAsync()).LastRunUtc;
model.Disabled = settings.Disabled;

foreach (var status in settings.Statuses ?? [])
{
model.Statuses.Single(statusItem => statusItem.Status == status).IsSelected = true;
}
}).Location("Content:5")
.OnGroup(GroupId);
}

public override async Task<IDisplayResult> UpdateAsync(ISite site, WorkflowTrimmingSettings settings, UpdateEditorContext context)
{
if (!await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext?.User, Permissions.ManageWorkflowSettings))
{
return null;
}

var viewModel = new WorkflowTrimmingViewModel();
await context.Updater.TryUpdateModelAsync(viewModel, Prefix);

settings.RetentionDays = viewModel.RetentionDays;
settings.Disabled = viewModel.Disabled;
settings.Statuses = viewModel.Statuses
.Where(statusItem => statusItem.IsSelected)
.Select(statusItem => statusItem.Status)
.ToArray();

return await EditAsync(site, settings, context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using OrchardCore.BackgroundTasks;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Navigation;
using OrchardCore.Settings;
using OrchardCore.Workflows.Trimming;
using OrchardCore.Workflows.Trimming.Drivers;
using OrchardCore.Workflows.Trimming.Services;

namespace Microsoft.Extensions.DependencyInjection;

internal static class ServiceCollectionExtensions
{
public static IServiceCollection AddTrimmingServices(this IServiceCollection services, IShellConfiguration shellConfiguration)
{
services.AddScoped<IWorkflowTrimmingService, WorkflowTrimmingService>();
services.AddSingleton<IBackgroundTask, WorkflowTrimmingBackgroundTask>();
services.AddScoped<IDisplayDriver<ISite>, WorkflowTrimmingDisplayDriver>();
services.AddScoped<INavigationProvider, AdminMenu>();
services.Configure<WorkflowTrimmingOptions>(shellConfiguration.GetSection("OrchardCore_Workflows:Trimming"));

return services;
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
using System;
using OrchardCore.Entities;
using OrchardCore.Workflows.Models;

namespace OrchardCore.Workflows.WorkflowPruning.Models;
namespace OrchardCore.Workflows.Trimming.Models;

public class WorkflowPruningSettings : Entity
public class WorkflowTrimmingSettings : Entity
{
public int RetentionDays { get; set; } = 90;

public DateTime? LastRunUtc { get; set; }

public bool Disabled { get; set; }

public WorkflowStatus[] Statuses { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;
using OrchardCore.Data.Documents;

namespace OrchardCore.Workflows.Trimming.Models;

public class WorkflowTrimmingState : Document
{
public DateTime? LastRunUtc { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrchardCore.BackgroundTasks;
using OrchardCore.Documents;
using OrchardCore.Modules;
using OrchardCore.Settings;
using OrchardCore.Workflows.Trimming.Models;

namespace OrchardCore.Workflows.Trimming.Services;

[BackgroundTask(
Schedule = "0 0 * * *",
Title = "Workflow Trimming Background Task",
Description = "Regularly deletes old workflow instances.",
LockTimeout = 3_000,
LockExpiration = 30_000
)]
public class WorkflowTrimmingBackgroundTask : IBackgroundTask
{
public async Task DoWorkAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken)
{
var siteService = serviceProvider.GetRequiredService<ISiteService>();

var workflowTrimmingSettings = await siteService.GetSettingsAsync<WorkflowTrimmingSettings>();
if (workflowTrimmingSettings.Disabled)
{
return;
}

var logger = serviceProvider.GetRequiredService<ILogger<WorkflowTrimmingBackgroundTask>>();

try
{
var clock = serviceProvider.GetRequiredService<IClock>();
var workflowTrimmingManager = serviceProvider.GetRequiredService<IWorkflowTrimmingService>();
var batchSize = serviceProvider.GetRequiredService<IOptions<WorkflowTrimmingOptions>>().Value.BatchSize;

logger.LogDebug("Starting trimming Workflow instances.");

var trimmedCount = await workflowTrimmingManager.TrimWorkflowInstancesAsync(
TimeSpan.FromDays(workflowTrimmingSettings.RetentionDays),
batchSize
);

logger.LogDebug("Trimmed {TrimmedCount} workflow instances.", trimmedCount);

var workflowTrimmingSateDocumentManager = serviceProvider.GetRequiredService<IDocumentManager<WorkflowTrimmingState>>();
var workflowTrimmingState = await workflowTrimmingSateDocumentManager.GetOrCreateMutableAsync();
workflowTrimmingState.LastRunUtc = clock.UtcNow;
await workflowTrimmingSateDocumentManager.UpdateAsync(workflowTrimmingState);
}
catch (Exception ex) when (!ex.IsFatal())
{
logger.LogError(ex, "Error while trimming workflow instances.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,32 @@
using OrchardCore.Settings;
using OrchardCore.Workflows.Indexes;
using OrchardCore.Workflows.Models;
using OrchardCore.Workflows.WorkflowPruning.Models;
using OrchardCore.Workflows.Trimming.Models;
using YesSql;
using YesSql.Services;

namespace OrchardCore.Workflows.WorkflowPruning.Services;
namespace OrchardCore.Workflows.Trimming.Services;

public class WorkflowPruningManager : IWorkflowPruningManager
public class WorkflowTrimmingService : IWorkflowTrimmingService
{
private readonly ISiteService _siteService;
private readonly ISession _session;
private readonly IClock _clock;

public WorkflowPruningManager(ISiteService siteService, ISession session, IClock clock)
public WorkflowTrimmingService(
ISiteService siteService,
ISession session,
IClock clock)
{
_siteService = siteService;
_session = session;
_clock = clock;
}

public async Task<int> PruneWorkflowInstancesAsync(TimeSpan retentionPeriod)
public async Task<int> TrimWorkflowInstancesAsync(TimeSpan retentionPeriod, int batchSize)
{
var dateThreshold = _clock.UtcNow.AddDays(1) - retentionPeriod;
var settings = await _siteService.GetSettingsAsync<WorkflowPruningSettings>();
var dateThreshold = _clock.UtcNow - retentionPeriod;
var settings = await _siteService.GetSettingsAsync<WorkflowTrimmingSettings>();

settings.Statuses ??=
[
Expand All @@ -49,6 +52,8 @@ public async Task<int> PruneWorkflowInstancesAsync(TimeSpan retentionPeriod)
var statuses = settings.Statuses.Select(x => (int)x).ToArray();
var workflowInstances = await _session
.Query<Workflow, WorkflowIndex>(x => x.WorkflowStatus.IsIn(statuses) && x.CreatedUtc <= dateThreshold)
.OrderBy(x => x.CreatedUtc)
.Take(batchSize)
.ListAsync();

var total = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using OrchardCore.Workflows.Models;

namespace OrchardCore.Workflows.Trimming.ViewModels;

public class WorkflowStatusItem
{
public WorkflowStatus Status { get; set; }

public bool IsSelected { get; set; }
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Linq;
using OrchardCore.Workflows.Models;

namespace OrchardCore.Workflows.Trimming.ViewModels;

public class WorkflowTrimmingViewModel
{
public DateTime? LastRunUtc { get; set; }

public bool Disabled { get; set; }

public int RetentionDays { get; set; }

public WorkflowStatusItem[] Statuses { get; set; }

public WorkflowTrimmingViewModel()
{
Statuses = Enum.GetValues(typeof(WorkflowStatus))
.Cast<WorkflowStatus>()
.Select(status => new WorkflowStatusItem
{
Status = status,
IsSelected = false
})
.ToArray();
}
}

This file was deleted.

Loading