diff --git a/uSync.BackOffice/Configuration/uSyncSettings.cs b/uSync.BackOffice/Configuration/uSyncSettings.cs index d5661fc68..79ab064ae 100644 --- a/uSync.BackOffice/Configuration/uSyncSettings.cs +++ b/uSync.BackOffice/Configuration/uSyncSettings.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.ComponentModel; +using System.Text.Json.Serialization; namespace uSync.BackOffice.Configuration; @@ -97,15 +98,15 @@ public class uSyncSettings /// /// Export when an item is saved in Umbraco /// - [DefaultValue("All")] - public string ExportOnSave { get; set; } = "All"; + [DefaultValue(uSync.EverythingGroupName)] + public string ExportOnSave { get; set; } = uSync.EverythingGroupName; /// /// The handler groups that are enabled in the UI. /// - [DefaultValue("All")] - public string UIEnabledGroups { get; set; } = "All"; + [DefaultValue(uSync.EverythingGroupName)] + public string UIEnabledGroups { get; set; } = uSync.EverythingGroupName; /// /// Debug reports (creates an export into a temp folder for comparison) @@ -176,8 +177,8 @@ public class uSyncSettings /// /// Handler group(s) to run on first boot, default is All (so full import) /// - [DefaultValue("All")] - public string FirstBootGroup { get; set; } = "All"; + [DefaultValue(uSync.EverythingGroupName)] + public string FirstBootGroup { get; set; } = uSync.EverythingGroupName; /// /// Disable the default dashboard (so people can't accidently press the buttons). @@ -264,6 +265,7 @@ public class uSyncSettings /// /// uSync's folder mode - normal, root or production /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum SyncFolderMode { /// @@ -285,6 +287,7 @@ public enum SyncFolderMode /// /// Mode is where the processing happens - normal or background /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum SyncProcessingMode { /// diff --git a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs index a32027a21..8c29aad9c 100644 --- a/uSync.BackOffice/Extensions/uSyncActionExtensions.cs +++ b/uSync.BackOffice/Extensions/uSyncActionExtensions.cs @@ -41,7 +41,7 @@ public static int CountChanges(this IEnumerable actions) public static bool IsValidAction(this HandlerActions requestedAction, IEnumerable actions) => requestedAction == HandlerActions.None || !actions.Any() || - actions.InvariantContains("all") || + actions.InvariantContains(uSync.EverythingGroupName) || actions.InvariantContains(requestedAction.ToString()); /// diff --git a/uSync.BackOffice/Models/SyncActionOptions.cs b/uSync.BackOffice/Models/SyncActionOptions.cs index 18cca1f87..1350ab953 100644 --- a/uSync.BackOffice/Models/SyncActionOptions.cs +++ b/uSync.BackOffice/Models/SyncActionOptions.cs @@ -29,6 +29,12 @@ public class SyncActionOptions /// public string? Set { get; set; } + + /// + /// the group within the set that is being processed - e.g settings, content, media etc. + /// + public string? Group { get; set; } + /// /// SyncActions to use as the source for all individual actions /// diff --git a/uSync.BackOffice/Notifications/uSyncNotifications/CancelableuSyncBulkNotification.cs b/uSync.BackOffice/Notifications/uSyncNotifications/CancelableuSyncBulkNotification.cs index 039d0c1a2..e160dba79 100644 --- a/uSync.BackOffice/Notifications/uSyncNotifications/CancelableuSyncBulkNotification.cs +++ b/uSync.BackOffice/Notifications/uSyncNotifications/CancelableuSyncBulkNotification.cs @@ -13,7 +13,7 @@ public class CancelableuSyncBulkNotification : uSyncBulkNotification, ICancelabl /// Notification constructor /// public CancelableuSyncBulkNotification() - : base(Enumerable.Empty()) + : base(Enumerable.Empty(), null) { } /// diff --git a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncBulkNotification.cs b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncBulkNotification.cs index 813a9677d..2cef06642 100644 --- a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncBulkNotification.cs +++ b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncBulkNotification.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Cms.Core.Notifications; @@ -13,11 +14,23 @@ public class uSyncBulkNotification : INotification /// generate new BulkNotificationObject /// /// + public uSyncBulkNotification(IEnumerable actions, string? group) + { + this.Group = group; + this.Actions = actions; + } + + [Obsolete("Use the constructor with group and actions instead - will be removed in v19")] public uSyncBulkNotification(IEnumerable actions) { this.Actions = actions; } + /// + /// The group e.g settings, content that this action ran under. + /// + public string? Group { get; set; } + /// /// actions that have occured during the bulk operation /// diff --git a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncExportCompletedNotification.cs b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncExportCompletedNotification.cs index c2d6b3c17..add72f1a3 100644 --- a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncExportCompletedNotification.cs +++ b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncExportCompletedNotification.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace uSync.BackOffice; @@ -7,8 +8,12 @@ namespace uSync.BackOffice; /// public class uSyncExportCompletedNotification : uSyncBulkNotification { + /// + public uSyncExportCompletedNotification(IEnumerable actions, string? group) + : base(actions, group) { } /// + [Obsolete("Use the constructor with the group parameter instead will be removed in v19")] public uSyncExportCompletedNotification(IEnumerable actions) - : base(actions) { } + : base(actions, null) { } } diff --git a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncImportCompletedNotification.cs b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncImportCompletedNotification.cs index 9adf64b19..e50dfb8f4 100644 --- a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncImportCompletedNotification.cs +++ b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncImportCompletedNotification.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace uSync.BackOffice; @@ -7,7 +8,13 @@ namespace uSync.BackOffice; /// public class uSyncImportCompletedNotification : uSyncBulkNotification { + + /// + public uSyncImportCompletedNotification(IEnumerable actions, string? group) + : base(actions, group) { } + /// + [Obsolete("Use the constructor with the group parameter instead will be removed in v19")] public uSyncImportCompletedNotification(IEnumerable actions) - : base(actions) { } + : base(actions, null) { } } diff --git a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncReportCompletedNotification.cs b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncReportCompletedNotification.cs index a361483e2..0846e9b77 100644 --- a/uSync.BackOffice/Notifications/uSyncNotifications/uSyncReportCompletedNotification.cs +++ b/uSync.BackOffice/Notifications/uSyncNotifications/uSyncReportCompletedNotification.cs @@ -7,7 +7,12 @@ namespace uSync.BackOffice; /// public class uSyncReportCompletedNotification : uSyncBulkNotification { + /// + public uSyncReportCompletedNotification(IEnumerable actions, string? group ) + : base(actions, group) { } + + /// public uSyncReportCompletedNotification(IEnumerable actions) - : base(actions) { } + : base(actions, null) { } } diff --git a/uSync.BackOffice/Services/ISyncService.cs b/uSync.BackOffice/Services/ISyncService.cs index 9ccce0492..84937fae2 100644 --- a/uSync.BackOffice/Services/ISyncService.cs +++ b/uSync.BackOffice/Services/ISyncService.cs @@ -152,14 +152,20 @@ public interface ISyncService /// Task StartBulkProcessAsync(HandlerActions action); + /// /// trigger the end of the bulk process /// + [Obsolete("Use StartBulkProcessAsync(HandlerActions action, string group) instead")] Task FinishBulkProcessAsync(HandlerActions action, IEnumerable actions); + /// + /// trigger the end of the bulk process + /// + Task FinishBulkProcessAsync(HandlerActions action, string? group, IEnumerable actions); + /// /// merge the given folders in single 'production' files for each handler. /// Task MergeExportFolder(string[] paths, IEnumerable handlers); - } \ No newline at end of file diff --git a/uSync.BackOffice/Services/SyncActionService.cs b/uSync.BackOffice/Services/SyncActionService.cs index 988b14f15..def996b78 100644 --- a/uSync.BackOffice/Services/SyncActionService.cs +++ b/uSync.BackOffice/Services/SyncActionService.cs @@ -206,7 +206,7 @@ public async Task StartProcessAsync(HandlerActions action) /// public async Task FinishProcessAsync(SyncFinalActionRequest request) { - await _uSyncService.FinishBulkProcessAsync(request.HandlerAction, request.Actions); + await _uSyncService.FinishBulkProcessAsync(request.HandlerAction, request.ActionOptions.Group, request.Actions); _timer?.Stop(); var elapsed = _timer?.ElapsedMilliseconds ?? 0; diff --git a/uSync.BackOffice/Services/SyncService.cs b/uSync.BackOffice/Services/SyncService.cs index 6332db6be..3f5dd0f96 100644 --- a/uSync.BackOffice/Services/SyncService.cs +++ b/uSync.BackOffice/Services/SyncService.cs @@ -205,7 +205,7 @@ public async Task> ImportAsync(string[] folders, bool f var results = await SyncService.PerformPostImportAsync(handlers, actions); // fire complete - await _mutexService.FireBulkCompleteAsync(new uSyncImportCompletedNotification(actions)); + await _mutexService.FireBulkCompleteAsync(new uSyncImportCompletedNotification(actions, handlerOptions.Group)); _logger.LogInformation("uSync Import: {handlerCount} handlers, processed {itemCount} items, {changeCount} changes in {ElapsedMilliseconds}ms", handlers.Count(), @@ -402,7 +402,7 @@ public async Task> ExportAsync(string folder, IEnumerab summary.UpdateMessage("Export Completed"); callbacks?.Callback?.Invoke(summary); - await _mutexService.FireBulkCompleteAsync(new uSyncExportCompletedNotification(actions)); + await _mutexService.FireBulkCompleteAsync(new uSyncExportCompletedNotification(actions, null)); sw.Stop(); diff --git a/uSync.BackOffice/Services/SyncService_Handlers.cs b/uSync.BackOffice/Services/SyncService_Handlers.cs index 99958a7f7..60886d403 100644 --- a/uSync.BackOffice/Services/SyncService_Handlers.cs +++ b/uSync.BackOffice/Services/SyncService_Handlers.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; @@ -154,19 +155,24 @@ public async Task StartBulkProcessAsync(HandlerActions action) } /// > + [Obsolete("Use the overload with the group parameter instead")] public async Task FinishBulkProcessAsync(HandlerActions action, IEnumerable actions) + => await FinishBulkProcessAsync(action, uSync.EverythingGroupName, actions); + + /// > + public async Task FinishBulkProcessAsync(HandlerActions action, string? group, IEnumerable actions) { switch (action) { case HandlerActions.Export: await WriteVersionFileAsync(_uSyncConfig.GetWorkingFolder()); - await _mutexService.FireBulkCompleteAsync(new uSyncExportCompletedNotification(actions)); + await _mutexService.FireBulkCompleteAsync(new uSyncExportCompletedNotification(actions, group)); break; case HandlerActions.Import: - await _mutexService.FireBulkCompleteAsync(new uSyncImportCompletedNotification(actions)); + await _mutexService.FireBulkCompleteAsync(new uSyncImportCompletedNotification(actions, group)); break; case HandlerActions.Report: - await _mutexService.FireBulkCompleteAsync(new uSyncReportCompletedNotification(actions)); + await _mutexService.FireBulkCompleteAsync(new uSyncReportCompletedNotification(actions, group)); break; } } diff --git a/uSync.BackOffice/SyncHandlers/Models/HandlerActionNames.cs b/uSync.BackOffice/SyncHandlers/Models/HandlerActionNames.cs index 2282081e0..7c5d4e406 100644 --- a/uSync.BackOffice/SyncHandlers/Models/HandlerActionNames.cs +++ b/uSync.BackOffice/SyncHandlers/Models/HandlerActionNames.cs @@ -1,10 +1,12 @@ using System; +using System.Text.Json.Serialization; namespace uSync.BackOffice.SyncHandlers.Models; /// /// Possible actions a handler can do (stored in config) /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum HandlerActions { /// @@ -40,7 +42,7 @@ public enum HandlerActions /// /// All actions /// - [SyncActionName("All")] + [SyncActionName(uSync.EverythingGroupName)] All } diff --git a/uSync.BackOffice/SyncHandlers/Models/HandlerConfigPair.cs b/uSync.BackOffice/SyncHandlers/Models/HandlerConfigPair.cs index 0f2957a31..1f02e983e 100644 --- a/uSync.BackOffice/SyncHandlers/Models/HandlerConfigPair.cs +++ b/uSync.BackOffice/SyncHandlers/Models/HandlerConfigPair.cs @@ -38,7 +38,7 @@ public static bool IsEnabled(this HandlerConfigPair handlerAndConfig) public static bool IsValidGroup(this HandlerConfigPair handlerAndConfig, string group) { // empty means all as does 'all' - if (string.IsNullOrWhiteSpace(group) || group.InvariantEquals("all")) return true; + if (string.IsNullOrWhiteSpace(group) || group.InvariantEquals(uSync.EverythingGroupName)) return true; var handlerGroup = handlerAndConfig.Handler.Group; if (!string.IsNullOrWhiteSpace(handlerAndConfig.Settings.Group)) diff --git a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs index c32451040..38b0fe0ce 100644 --- a/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs +++ b/uSync.BackOffice/SyncHandlers/SyncHandlerRoot.cs @@ -1370,7 +1370,7 @@ protected bool ShouldProcessEvent() var group = !string.IsNullOrWhiteSpace(DefaultConfig.Group) ? DefaultConfig.Group : this.Group; - if (uSyncConfig.Settings.ExportOnSave.InvariantContains("All") || + if (uSyncConfig.Settings.ExportOnSave.InvariantContains(uSync.EverythingGroupName) || uSyncConfig.Settings.ExportOnSave.InvariantContains(group)) { return HandlerActions.Save.IsValidAction(DefaultConfig.Actions); diff --git a/uSync.BackOffice/Tracker/ISyncTrackerService.cs b/uSync.BackOffice/Tracker/ISyncTrackerService.cs new file mode 100644 index 000000000..f8f108ab9 --- /dev/null +++ b/uSync.BackOffice/Tracker/ISyncTrackerService.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace uSync.BackOffice.Tracker; + +public interface ISyncTrackerService +{ + Task GetLastSync(string group); + Task SaveLastSync(string group); +} \ No newline at end of file diff --git a/uSync.BackOffice/Tracker/SyncTrackerBuilderExtensions.cs b/uSync.BackOffice/Tracker/SyncTrackerBuilderExtensions.cs new file mode 100644 index 000000000..2fbd36adb --- /dev/null +++ b/uSync.BackOffice/Tracker/SyncTrackerBuilderExtensions.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; + +using Umbraco.Cms.Core.DependencyInjection; + +namespace uSync.BackOffice.Tracker; + +internal static class SyncTrackerBuilderExtensions +{ + public static IUmbracoBuilder AddSyncTracker(this IUmbracoBuilder builder) + { + builder.Services.AddSingleton(); + builder.AddNotificationAsyncHandler(); + return builder; + } +} diff --git a/uSync.BackOffice/Tracker/SyncTrackerNotificationHandler.cs b/uSync.BackOffice/Tracker/SyncTrackerNotificationHandler.cs new file mode 100644 index 000000000..b3ad51019 --- /dev/null +++ b/uSync.BackOffice/Tracker/SyncTrackerNotificationHandler.cs @@ -0,0 +1,22 @@ +using System.Threading; +using System.Threading.Tasks; + +using Umbraco.Cms.Core.Events; + +namespace uSync.BackOffice.Tracker; + +internal class SyncTrackerNotificationHandler + : INotificationAsyncHandler +{ + private readonly ISyncTrackerService _syncTrackerService; + + public SyncTrackerNotificationHandler(ISyncTrackerService syncTrackerService) + { + _syncTrackerService = syncTrackerService; + } + + public async Task HandleAsync(uSyncImportCompletedNotification notification, CancellationToken cancellationToken) + { + await _syncTrackerService.SaveLastSync(notification.Group ?? uSync.EverythingGroupName); + } +} diff --git a/uSync.BackOffice/Tracker/SyncTrackerService.cs b/uSync.BackOffice/Tracker/SyncTrackerService.cs new file mode 100644 index 000000000..28de5f55a --- /dev/null +++ b/uSync.BackOffice/Tracker/SyncTrackerService.cs @@ -0,0 +1,64 @@ +using Microsoft.Extensions.Logging; + +using System; +using System.Threading.Tasks; + +using Umbraco.Cms.Core.Services; + +namespace uSync.BackOffice.Tracker; + + +internal class SyncTrackerService : ISyncTrackerService +{ + const string _keyPrefix = "uSync.SyncTracker."; + + private readonly ILogger _logger; + private readonly IKeyValueService _keyValueService; + + public SyncTrackerService(IKeyValueService keyValueService, ILogger logger) + { + _keyValueService = keyValueService; + _logger = logger; + } + + public Task SaveLastSync(string group) + { + try + { + var key = $"{_keyPrefix}{group}"; + _keyValueService.SetValue(key, DateTime.UtcNow.ToString("o")); + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error saving last sync time for group {Group}", group); + } + return Task.CompletedTask; + + } + + public async Task GetLastSync(string group) + { + var lastSync = await InternalGetLastSync(group); + var lastAllSync = await InternalGetLastSync(uSync.EverythingGroupName); + return lastSync > lastAllSync ? lastSync : lastAllSync; + } + + private Task InternalGetLastSync(string group) + { + try + { + var key = $"{_keyPrefix}{group}"; + var value = _keyValueService.GetValue(key); + if (DateTime.TryParse(value, null, System.Globalization.DateTimeStyles.RoundtripKind, out var result)) + { + return Task.FromResult(result); + } + } + catch(Exception ex) + { + _logger.LogWarning(ex, "Error retrieving last sync time for group {Group}", group); + } + + return Task.FromResult(null); + } +} diff --git a/uSync.BackOffice/uSyncBackOffice.cs b/uSync.BackOffice/uSyncBackOffice.cs index 5fdc9ae8b..b28790257 100644 --- a/uSync.BackOffice/uSyncBackOffice.cs +++ b/uSync.BackOffice/uSyncBackOffice.cs @@ -24,6 +24,11 @@ public class uSync /// public const string EventPausedKey = "uSync.PausedKey"; + /// + /// the name we use internally for the 'everything' group. + /// + public const string EverythingGroupName = "All"; + internal class Trees { internal const string uSync = "usync"; diff --git a/uSync.BackOffice/uSyncBackOfficeBuilderExtensions.cs b/uSync.BackOffice/uSyncBackOfficeBuilderExtensions.cs index eefa99efe..cf7b6513f 100644 --- a/uSync.BackOffice/uSyncBackOfficeBuilderExtensions.cs +++ b/uSync.BackOffice/uSyncBackOfficeBuilderExtensions.cs @@ -25,6 +25,7 @@ using uSync.BackOffice.SyncHandlers; using uSync.BackOffice.SyncHandlers.Handlers; using uSync.BackOffice.SyncHandlers.Interfaces; +using uSync.BackOffice.Tracker; using uSync.Core; namespace uSync.BackOffice; @@ -89,6 +90,8 @@ public static IUmbracoBuilder AdduSync(this IUmbracoBuilder builder, Action(); builder.Services.AddSingleton(); diff --git a/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncActionsController.cs b/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncActionsController.cs index b4bfb55ae..b0229b7e7 100644 --- a/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncActionsController.cs +++ b/uSync.Backoffice.Management.Api/Controllers/Actions/uSyncActionsController.cs @@ -32,7 +32,7 @@ public async Task> GetActions() [ProducesResponseType(typeof(List), 200)] public async Task> GetActionsBySet(string setName) { - return await Task.FromResult(_syncManagementService.GetActions(setName)); + return await _syncManagementService.GetActionsAsync(setName); } diff --git a/uSync.Backoffice.Management.Api/Models/SyncActionGroup.cs b/uSync.Backoffice.Management.Api/Models/SyncActionGroup.cs index b21fa47e2..5cfb1fcde 100644 --- a/uSync.Backoffice.Management.Api/Models/SyncActionGroup.cs +++ b/uSync.Backoffice.Management.Api/Models/SyncActionGroup.cs @@ -6,6 +6,7 @@ public class SyncActionGroup public string Icon { get; set; } = "icon-box"; public string GroupName { get; set; } = "settings"; public List Buttons { get; set; } = []; + public DateTime? LastSync { get; set; } } diff --git a/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs b/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs index 0240959a5..f67c4a0b8 100644 --- a/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs +++ b/uSync.Backoffice.Management.Api/Services/ISyncManagementService.cs @@ -12,7 +12,12 @@ public interface ISyncManagementService [Obsolete("Use GetActions(string setName) instead, this will be removed in v18")] List GetActions(); + + [Obsolete("Use GetActionsAsync(string setName) instead, this will be removed in v19")] List GetActions(string setName); + + Task> GetActionsAsync(string setName); + Func> GetHandlerMethodAsync(HandlerActions action); Task PerformActionAsync(PerformActionRequest actionRequest, IUser? user); diff --git a/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs b/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs index 932386b43..c4527274e 100644 --- a/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs +++ b/uSync.Backoffice.Management.Api/Services/uSyncManagementService.cs @@ -13,6 +13,7 @@ using uSync.BackOffice.Services; using uSync.BackOffice.SyncHandlers; using uSync.BackOffice.SyncHandlers.Models; +using uSync.BackOffice.Tracker; namespace uSync.Backoffice.Management.Api.Services; @@ -30,13 +31,16 @@ internal class uSyncManagementService : ISyncManagementService private readonly ILongRunningOperationService _longRunningOperationService; + private readonly ISyncTrackerService _syncTrackerService; + public uSyncManagementService( ISyncActionService syncActionService, ISyncConfigService configService, ISyncManagementCache syncManagementCache, IHubContext hubContext, ISyncHandlerFactory handlerFactory, - ILongRunningOperationService longRunningOperationService) + ILongRunningOperationService longRunningOperationService, + ISyncTrackerService syncTrackerService) { _syncActionService = syncActionService; _configService = configService; @@ -44,19 +48,22 @@ public uSyncManagementService( _hubContext = hubContext; _handlerFactory = handlerFactory; _longRunningOperationService = longRunningOperationService; + _syncTrackerService = syncTrackerService; } [Obsolete("Use GetActions(string setName) instead, this will be removed in v18")] public List GetActions() => GetActions(_configService.Settings.DefaultSet); + [Obsolete("Use GetActionsAsync(string setName) instead, this will be removed in v19")] + public List GetActions(string setName) + => GetActionsAsync(setName).Result; + /// /// Gets the list of available actions /// - public List GetActions(string setName) + public async Task> GetActionsAsync(string setName) { - // TODO: Load the actions based on the handlers, and the config, (so they can be turned on and off) - var defaultReport = new SyncActionButton() { Key = HandlerActions.Report.ToString(), @@ -128,7 +135,6 @@ public List GetActions(string setName) ] }; - // TODO: we need to load in additional action groups as needed from plugins. List defaultButtons = [defaultReport, defaultImport, defaultExport]; List everythingButtons = [defaultReport, everythingImport, everythingExport]; @@ -143,24 +149,29 @@ public List GetActions(string setName) foreach (var group in groups) { + var groupAlias = group.Key.ToLowerInvariant(); + actionGroups.Add(new SyncActionGroup { GroupName = $"{group.Key}", Icon = group.Value, Key = group.Key.ToLowerInvariant(), - Buttons = defaultButtons + Buttons = defaultButtons, + LastSync = await _syncTrackerService.GetLastSync(groupAlias) }); } if (string.IsNullOrWhiteSpace(_configService.Settings.UIEnabledGroups) || - _configService.Settings.UIEnabledGroups.InvariantContains("all")) + _configService.Settings.UIEnabledGroups.InvariantContains(BackOffice.uSync.EverythingGroupName)) { actionGroups.Add(new SyncActionGroup { GroupName = "Everything", Icon = "icon-paper-plane-alt", - Key = "all", - Buttons = everythingButtons + Key = BackOffice.uSync.EverythingGroupName, + Buttons = everythingButtons, + LastSync = await _syncTrackerService.GetLastSync(BackOffice.uSync.EverythingGroupName) + }); } @@ -247,6 +258,7 @@ await _syncActionService.StartProcessAsync(new SyncStartActionRequest { Folders = _configService.GetFolders(), Set = actionRequest.Options?.Set ?? _configService.Settings.DefaultSet, + Group = actionRequest.Options?.Group ?? "all", Force = actionRequest.Options?.Force ?? false, Actions = [], }; diff --git a/uSync.Backoffice.Management.Client/usync-assets/openapi-ts.config.ts b/uSync.Backoffice.Management.Client/usync-assets/openapi-ts.config.ts index 3ab25f3cc..d4cb6781b 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/openapi-ts.config.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/openapi-ts.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from '@hey-api/openapi-ts'; import { defaultPlugins } from '@hey-api/openapi-ts'; export default defineConfig({ - input: 'http://localhost:28580/umbraco/swagger/uSync/swagger.json', + input: 'http://localhost:16903/umbraco/swagger/uSync/swagger.json', output: { format: 'prettier', path: 'src/api', diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/api/client.gen.ts b/uSync.Backoffice.Management.Client/usync-assets/src/api/client.gen.ts index 13ad9c9e0..3aceefc03 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/api/client.gen.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/api/client.gen.ts @@ -14,6 +14,6 @@ import type { ClientOptions as ClientOptions2 } from './types.gen'; export type CreateClientConfig = (override?: Config) => Config & T>; export const client = createClient(createConfig({ - baseUrl: 'http://localhost:28580', + baseUrl: 'http://localhost:16903', throwOnError: true })); diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts b/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts index 1435410d0..b92ef7618 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/api/types.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts export type ClientOptions = { - baseUrl: 'http://localhost:28580' | (string & {}); + baseUrl: 'http://localhost:16903' | (string & {}); }; export type Assembly = { @@ -553,6 +553,7 @@ export type SyncActionGroup = { icon: string; groupName: string; buttons: Array; + lastSync?: string | null; }; export enum SyncFolderMode { @@ -866,6 +867,7 @@ export type USyncSettings = { lockRoot: boolean; stopFile: string; onceFile: string; + ignoreStopIfOnceExists: boolean; lockRootTypes: Array; backgroundStartup: boolean; defaultSet: string; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-action-box.ts b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-action-box.ts index 666bd8b8d..2177a75bb 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-action-box.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/components/usync-action-box.ts @@ -1,23 +1,23 @@ import { - LitElement, customElement, html, css, property, ifDefined, } from '@umbraco-cms/backoffice/external/lit'; -import { SyncActionGroup } from '@jumoo/uSync'; +import { SyncActionGroup, uSyncTimeFormatOptions } from '@jumoo/uSync'; import { UUIButtonState } from '@umbraco-cms/backoffice/external/uui'; import { uSyncActionButtonClickEvent, uSyncActionPerformEvent, } from '../workspace/components/events'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; /** * displays the action buttons for a given group */ @customElement('usync-action-box') -export class uSyncActionBox extends LitElement { +export class uSyncActionBox extends UmbLitElement { /** * Collection of buttons to display. */ @@ -62,7 +62,9 @@ export class uSyncActionBox extends LitElement { return html`
-

${this.group?.groupName}

+

+ ${this.group?.groupName} +

${dropdownButtons}
@@ -70,6 +72,11 @@ export class uSyncActionBox extends LitElement { `; } + getLastSyncDate() { + if (!this.group?.lastSync) return ''; + return `Last imported: ${this.localize.date(this.group.lastSync, uSyncTimeFormatOptions)}`; + } + static styles = css` :host { flex-grow: 1; @@ -86,7 +93,8 @@ export class uSyncActionBox extends LitElement { } .box-heading { - font-size: var(--uui-size-8); + font-size: var(--uui-type-h3-size); + cursor: help; margin: 0; } @@ -108,6 +116,12 @@ export class uSyncActionBox extends LitElement { .disabled { opacity: 0.4; } + + .last-sync { + color: var(--uui-color-text-alt); + font-style: italic; + font-size: var(--uui-size-5); + } `; } diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/constants.ts b/uSync.Backoffice.Management.Client/usync-assets/src/constants.ts index da9985f48..ea33026c9 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/constants.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/constants.ts @@ -34,3 +34,12 @@ const _constants = { }; export const uSyncConstants = _constants; + +export const uSyncTimeFormatOptions: Intl.DateTimeFormatOptions = { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: 'numeric', + minute: 'numeric', + second: 'numeric', +}; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/views/default/default.element.ts b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/views/default/default.element.ts index f1667f57d..0343c5bca 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/views/default/default.element.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/views/default/default.element.ts @@ -353,8 +353,9 @@ export class uSyncDefaultViewElement extends UmbLitElement { } .action-buttons-box { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(430px, 1fr)); position: relative; - display: flex; gap: var(--uui-size-space-4); flex-wrap: wrap; align-content: stretch; diff --git a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts index f3cb9253a..781144817 100644 --- a/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts +++ b/uSync.Backoffice.Management.Client/usync-assets/src/workspace/workspace.context.ts @@ -47,6 +47,7 @@ export class uSyncWorkspaceContext */ #actions = new UmbArrayState([], (x) => x.key); public readonly actions = this.#actions.asObservable(); + private _currentSetName: string = ''; /** * the working group name @@ -134,6 +135,7 @@ export class uSyncWorkspaceContext if (data) { this.#actions.setValue(data); + this._currentSetName = setName; } } @@ -250,6 +252,7 @@ export class uSyncWorkspaceContext if (complete) { this.#results.setValue(data?.actions ?? []); + this.getActions(this._currentSetName); } } else { complete = true; diff --git a/uSync.Core/ChangeType.cs b/uSync.Core/ChangeType.cs index c3670e6c0..0af6c3acf 100644 --- a/uSync.Core/ChangeType.cs +++ b/uSync.Core/ChangeType.cs @@ -1,10 +1,12 @@ using System.Runtime.Serialization; +using System.Text.Json.Serialization; namespace uSync.Core; /// /// Type of change performed /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ChangeType : int { [EnumMember(Value = "Clean")] diff --git a/uSync.Core/Dependency/uSyncDependency.cs b/uSync.Core/Dependency/uSyncDependency.cs index 551606015..9123d9194 100644 --- a/uSync.Core/Dependency/uSyncDependency.cs +++ b/uSync.Core/Dependency/uSyncDependency.cs @@ -1,4 +1,6 @@ -using Umbraco.Cms.Core; +using System.Text.Json.Serialization; + +using Umbraco.Cms.Core; namespace uSync.Core.Dependency; @@ -90,6 +92,7 @@ private static void FireUpdate(string message, int count, int total) /// /// the match mode for the dependency (reserved) /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum DependencyMode { MustMatch, diff --git a/uSync.Core/Models/uSyncChange.cs b/uSync.Core/Models/uSyncChange.cs index 2a19f9e5d..4277171e7 100644 --- a/uSync.Core/Models/uSyncChange.cs +++ b/uSync.Core/Models/uSyncChange.cs @@ -100,6 +100,7 @@ public static uSyncChange Warning(string path, string name, string warning) }; } +[JsonConverter(typeof(JsonStringEnumConverter))] public enum ChangeDetailType { NoChange, diff --git a/uSync.Core/Sync/SyncTreeType.cs b/uSync.Core/Sync/SyncTreeType.cs index 0349d56ec..86f5538ef 100644 --- a/uSync.Core/Sync/SyncTreeType.cs +++ b/uSync.Core/Sync/SyncTreeType.cs @@ -1,4 +1,6 @@ -namespace uSync.Core.Sync; +using System.Text.Json.Serialization; + +namespace uSync.Core.Sync; /// /// The type of tree item this is. @@ -15,6 +17,7 @@ /// we can treat it as a 'settings' tree and show the /// menus when they are valid. /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum SyncTreeType { /// diff --git a/uSync.Core/SyncActionType.cs b/uSync.Core/SyncActionType.cs index 81530a177..d4e70e271 100644 --- a/uSync.Core/SyncActionType.cs +++ b/uSync.Core/SyncActionType.cs @@ -1,9 +1,12 @@ -namespace uSync.Core; +using System.Text.Json.Serialization; + +namespace uSync.Core; /// /// indicates what happened to an item to cause a ghost file /// to be present. /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum SyncActionType { None = 0, diff --git a/uSync.Core/Tracking/SyncXmlTracker.cs b/uSync.Core/Tracking/SyncXmlTracker.cs index 43a826a15..ba1bdb9c8 100644 --- a/uSync.Core/Tracking/SyncXmlTracker.cs +++ b/uSync.Core/Tracking/SyncXmlTracker.cs @@ -1,4 +1,5 @@ using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using System.Xml.Linq; using System.Xml.XPath; @@ -414,6 +415,7 @@ public class TrackingKey public bool IsAttribute { get; set; } } +[JsonConverter(typeof(JsonStringEnumConverter))] public enum TrackingDirection { TargetToSource, diff --git a/uSync.Core/uSyncContentState.cs b/uSync.Core/uSyncContentState.cs index d38715f2d..94b2110e0 100644 --- a/uSync.Core/uSyncContentState.cs +++ b/uSync.Core/uSyncContentState.cs @@ -1,8 +1,11 @@ -namespace uSync.Core; +using System.Text.Json.Serialization; + +namespace uSync.Core; /// /// the state we thing a culture / content item should be in. /// +[JsonConverter(typeof(JsonStringEnumConverter))] public enum uSyncContentState { Saved, Unpublished, Published