diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/HangfireDbSchemaMigrator.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HangfireDbSchemaMigrator.cs new file mode 100644 index 0000000..b182da1 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/HangfireDbSchemaMigrator.cs @@ -0,0 +1,65 @@ +using System; +using System.Threading.Tasks; +using Hangfire.SqlServer; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Sozsoft.Platform.Data; +using Volo.Abp.DependencyInjection; +using static Sozsoft.Settings.SettingsConsts; + +namespace Sozsoft.Platform.DbMigrator; + +/// +/// DbMigrator çalışırken HangFire şemasını oluşturur. +/// EF Core migration'larından sonra (EntityFrameworkCorePlatformDbSchemaMigrator), +/// SqlDataSeeder'dan önce çalışarak HangFire tablolarının (HangFire.Job vb.) mevcut +/// olmasını sağlar. Bu sayede SqlData klasöründeki HangFire view'ları ilk çalışmada oluşturulabilir. +/// [ExposeServices] ABP'nin naming convention'ından bağımsız olarak IPlatformDbSchemaMigrator +/// listesine dahil edilmesini sağlar. +/// +[ExposeServices(typeof(IPlatformDbSchemaMigrator))] +public class HangfireDbSchemaMigrator : IPlatformDbSchemaMigrator, ITransientDependency +{ + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public HangfireDbSchemaMigrator( + IConfiguration configuration, + ILogger logger) + { + _configuration = configuration; + _logger = logger; + } + + public async Task MigrateAsync() + { + var connectionString = _configuration.GetConnectionString(DefaultDatabaseProvider); + if (string.IsNullOrWhiteSpace(connectionString)) + { + _logger.LogWarning("HangFire schema migration skipped: connection string '{Key}' not found.", DefaultDatabaseProvider); + return; + } + + try + { + await using var connection = new SqlConnection(connectionString); + await connection.OpenAsync(); + + // HangFire tablolarını oluştur (idempotent — mevcutsa atlar). + SqlServerObjectsInstaller.Install(connection, "HangFire", enableHeavyMigrations: true); + + _logger.LogInformation("HangFire schema initialized successfully."); + } + catch (SqlException sqlEx) when (sqlEx.Number == 4060) + { + // Veritabanı henüz oluşturulmamış (ilk migration dışında tenant DB senaryosu) + _logger.LogWarning("HangFire schema skipped: database does not exist yet (error 4060)."); + } + catch (Exception ex) + { + _logger.LogError(ex, "HangFire schema initialization FAILED."); + throw; + } + } +} diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json index 3469e40..e49e3b1 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/LanguagesData.json @@ -1830,6 +1830,20 @@ "en": "Background Workers", "tr": "Arkaplan İşleri" }, + + { + "resourceName": "Platform", + "key": "App.BackgroundWorkers.Definitions", + "en": "Background Worker Definitions", + "tr": "Arkaplan İşleri Tanımları" + }, + { + "resourceName": "Platform", + "key": "App.BackgroundWorkers.JobFlow", + "en": "Background Worker Job Flow", + "tr": "Arkaplan İşleri İş Akışı" + }, + { "resourceName": "Platform", "key": "App.Notifications", @@ -14492,6 +14506,18 @@ "en": "Id", "tr": "Kimlik" }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.JobName", + "en": "Job Name", + "tr": "İş Adı" + }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.ExecutionDate", + "en": "Execution Date", + "tr": "İşlem Tarihi" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.SessionId", @@ -16202,6 +16228,24 @@ "en": "Status", "tr": "Durum" }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.FailureReason", + "en": "Failure Reason", + "tr": "Hata Nedeni" + }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.ExceptionType", + "en": "Exception Type", + "tr": "Hata Türü" + }, + { + "resourceName": "Platform", + "key": "App.Listform.ListformField.ExceptionDetails", + "en": "Exception Details", + "tr": "Hata Detayları" + }, { "resourceName": "Platform", "key": "App.Listform.ListformField.StatusId", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs index 34820f0..a6f3518 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/ListFormSeeder_Saas.cs @@ -4720,7 +4720,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency #endregion #region Background Worker - listFormName = AppCodes.BackgroundWorkers; + listFormName = AppCodes.BackgroundWorkers.Definitions; if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName)) { var listForm = await _listFormRepository.InsertAsync( @@ -4957,6 +4957,181 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency } #endregion + #region Background Worker Job Flow + listFormName = AppCodes.BackgroundWorkers.JobFlow; + if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName)) + { + var listForm = await _listFormRepository.InsertAsync( + new ListForm() + { + ListFormType = ListFormTypeEnum.List, + PageSize = 10, + ExportJson = DefaultExportJson, + IsSubForm = false, + ShowNote = true, + LayoutJson = DefaultLayoutJson(), + CultureName = LanguageCodes.En, + ListFormCode = listFormName, + Name = listFormName, + Title = listFormName, + DataSourceCode = SeedConsts.DataSources.DefaultCode, + IsTenant = false, + IsBranch = false, + IsOrganizationUnit = false, + Description = listFormName, + SelectCommandType = SelectCommandTypeEnum.View, + SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker_JobFlow)), + KeyFieldName = "Id", + KeyFieldDbSourceType = DbType.Guid, + SortMode = GridOptions.SortModeSingle, + FilterRowJson = DefaultFilterRowJson, + HeaderFilterJson = DefaultHeaderFilterJson, + SearchPanelJson = DefaultSearchPanelJson, + GroupPanelJson = DefaultGroupPanelJson, + SelectionJson = DefaultSelectionSingleJson, + ColumnOptionJson = DefaultColumnOptionJson(), + PermissionJson = DefaultPermissionJson(listFormName), + PagerOptionJson = DefaultPagerOptionJson + } + ); + + #region Background Worker Job Flow Fields + await _listFormFieldRepository.InsertManyAsync([ + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Guid, + FieldName = "Id", + CaptionName = "App.Listform.ListformField.Id", + Width = 0, + ListOrderNo = 1, + Visible = false, + IsActive = true, + + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "JobName", + CaptionName = "App.Listform.ListformField.JobName", + Width = 0, + ListOrderNo = 2, + Visible = true, + IsActive = true, + + SortIndex = 1, + SortDirection = GridColumnOptions.SortOrderAsc, + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.DateTime, + FieldName = "ExecutionDate", + CaptionName = "App.Listform.ListformField.ExecutionDate", + Width = 0, + ListOrderNo = 3, + Visible = true, + IsActive = true, + + AllowSearch = true, + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.Int32, + FieldName = "Duration", + CaptionName = "App.Listform.ListformField.Duration", + Width = 0, + ListOrderNo = 4, + Visible = true, + IsActive = true, + + ValidationRuleJson = DefaultValidationRuleRequiredJson, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "Status", + CaptionName = "App.Listform.ListformField.Status", + Width = 0, + ListOrderNo = 5, + Visible = true, + IsActive = true, + + AllowSearch = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "FailureReason", + CaptionName = "App.Listform.ListformField.FailureReason", + Width = 0, + ListOrderNo = 6, + Visible = true, + IsActive = true, + + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "ExceptionType", + CaptionName = "App.Listform.ListformField.ExceptionType", + Width = 0, + ListOrderNo = 7, + Visible = true, + IsActive = true, + + AllowSearch = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + new() { + ListFormCode = listForm.ListFormCode, + CultureName = LanguageCodes.En, + SourceDbType = DbType.String, + FieldName = "ExceptionDetails", + CaptionName = "App.Listform.ListformField.ExceptionDetails", + Width = 0, + ListOrderNo = 8, + Visible = true, + IsActive = true, + + AllowSearch = true, + ColumnCustomizationJson = DefaultColumnCustomizationJson, + PermissionJson = DefaultFieldPermissionJson(listForm.Name), + PivotSettingsJson = DefaultPivotSettingsJson + }, + ]); + #endregion + } + #endregion + #region Route listFormName = AppCodes.Menus.Routes; if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName)) diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json index 8407c1a..2b5a122 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/MenusData.json @@ -152,27 +152,21 @@ "path": "/admin/menuManager", "componentPath": "@/views/menu/MenuManager", "routeType": "protected", - "authority": [ - "App.Menus.Manager" - ] + "authority": ["App.Menus.Manager"] }, { "key": "admin.listFormManagement.wizard", "path": "/admin/listform/wizard", "componentPath": "@/views/admin/listForm/wizard/Wizard", "routeType": "protected", - "authority": [ - "App.Listforms.Wizard" - ] + "authority": ["App.Listforms.Wizard"] }, { "key": "admin.listFormManagement.wizardManager", "path": "/admin/listform/wizardManager", "componentPath": "@/views/admin/listForm/wizard/WizardFileManager", "routeType": "protected", - "authority": [ - "App.Listforms.Wizard" - ] + "authority": ["App.Listforms.Wizard"] }, { "key": "admin.listFormManagement.edit", @@ -186,18 +180,14 @@ "path": "/admin/forumManagement", "componentPath": "@/views/forum/Management", "routeType": "protected", - "authority": [ - "App.ForumManagement" - ] + "authority": ["App.ForumManagement"] }, { "key": "admin.ai", "path": "/admin/ai", "componentPath": "@/views/ai/Assistant", "routeType": "protected", - "authority": [ - "App.Definitions.AiBot.Asistant" - ] + "authority": ["App.Definitions.AiBot.Asistant"] }, { "key": "admin.profile.general", @@ -239,45 +229,35 @@ "path": "/admin/settings", "componentPath": "@/views/settings/Settings", "routeType": "protected", - "authority": [ - "App.Setting" - ] + "authority": ["App.Setting"] }, { "key": "admin.identity.user.detail", "path": "/admin/users/detail/:userId", "componentPath": "@/views/admin/user-management/Details", "routeType": "protected", - "authority": [ - "AbpIdentity.Users.Update" - ] + "authority": ["AbpIdentity.Users.Update"] }, { "key": "admin.identity.ous", "path": "/admin/ous", "componentPath": "@/views/admin/organization-unit/OrganizationUnits", "routeType": "protected", - "authority": [ - "Abp.Identity.OrganizationUnits" - ] + "authority": ["Abp.Identity.OrganizationUnits"] }, { "key": "admin.hr.organization", "path": "/admin/organization", "componentPath": "@/views/admin/hr/OrgChart", "routeType": "protected", - "authority": [ - "App.Definitions.Department" - ] + "authority": ["App.Definitions.Department"] }, { "key": "admin.forum", "path": "/admin/forum", "componentPath": "@/views/forum/Forum", "routeType": "protected", - "authority": [ - "App.ForumManagement.Publish" - ] + "authority": ["App.ForumManagement.Publish"] }, { "key": "admin.list", @@ -319,90 +299,70 @@ "path": "/admin/sqlQueryManager", "componentPath": "@/views/developerKit/SqlQueryManager", "routeType": "protected", - "authority": [ - "App.SqlQueryManager" - ] + "authority": ["App.SqlQueryManager"] }, { "key": "admin.developerkit.endpoints", "path": "/admin/developerkit/endpoints", "componentPath": "@/views/developerKit/CrudEndpointManager", "routeType": "protected", - "authority": [ - "App.DeveloperKit.CrudEndpoints" - ] + "authority": ["App.DeveloperKit.CrudEndpoints"] }, { "key": "admin.developerkit.dynamic-services", "path": "/admin/developerkit/dynamic-services", "componentPath": "@/views/developerKit/DynamicServiceManager", "routeType": "protected", - "authority": [ - "App.DeveloperKit.DynamicServices" - ] + "authority": ["App.DeveloperKit.DynamicServices"] }, { "key": "admin.developerkit.dynamic-services.new", "path": "/admin/developerkit/dynamic-services/new", "componentPath": "@/views/developerKit/DynamicServiceEditor", "routeType": "protected", - "authority": [ - "App.DeveloperKit.DynamicServices" - ] + "authority": ["App.DeveloperKit.DynamicServices"] }, { "key": "admin.developerkit.dynamic-services.edit", "path": "/admin/developerkit/dynamic-services/edit/:id", "componentPath": "@/views/developerKit/DynamicServiceEditor", "routeType": "protected", - "authority": [ - "App.DeveloperKit.DynamicServices" - ] + "authority": ["App.DeveloperKit.DynamicServices"] }, { "key": "admin.developerkit.components", "path": "/admin/developerkit/components", "componentPath": "@/views/developerKit/ComponentManagerPage", "routeType": "protected", - "authority": [ - "App.DeveloperKit.Components" - ] + "authority": ["App.DeveloperKit.Components"] }, { "key": "admin.developerkit.components.new", "path": "/admin/developerkit/components/new", "componentPath": "@/views/developerKit/ComponentEditorPage", "routeType": "protected", - "authority": [ - "App.DeveloperKit.Components" - ] + "authority": ["App.DeveloperKit.Components"] }, { "key": "admin.developerkit.components.view", "path": "/admin/developerkit/components/view/:id", "componentPath": "@/views/developerKit/ComponentEditorPage", "routeType": "protected", - "authority": [ - "App.DeveloperKit.Components" - ] + "authority": ["App.DeveloperKit.Components"] }, { "key": "admin.developerkit.components.edit", "path": "/admin/developerkit/components/edit/:id", "componentPath": "@/views/developerKit/ComponentCodeLayout", "routeType": "protected", - "authority": [ - "App.DeveloperKit.Components" - ] + "authority": ["App.DeveloperKit.Components"] }, { "key": "admin.fileManagement", "path": "/admin/files", "componentPath": "@/views/admin/files/FileManager", "routeType": "protected", - "authority": [ - "App.Files" - ] + "authority": ["App.Files"] }, { "key": "admin.devexpressReportView", @@ -423,63 +383,49 @@ "path": "/admin/public/home/designer", "componentPath": "@/views/public/Home", "routeType": "protected", - "authority": [ - "App.Home" - ] + "authority": ["App.Home"] }, { "key": "aboutDesigner", "path": "/admin/public/about/designer", "componentPath": "@/views/public/About", "routeType": "protected", - "authority": [ - "App.About" - ] + "authority": ["App.About"] }, { "key": "servicesDesigner", "path": "/admin/public/services/designer", "componentPath": "@/views/public/Services", "routeType": "protected", - "authority": [ - "App.Services" - ] + "authority": ["App.Services"] }, { "key": "contactDesigner", "path": "/admin/public/contact/designer", "componentPath": "@/views/public/Contact", "routeType": "protected", - "authority": [ - "App.Contact" - ] + "authority": ["App.Contact"] }, { "key": "admin.videoroom.dashboard", "path": "/admin/videoroom/dashboard", "componentPath": "@/views/admin/videoroom/Dashboard", "routeType": "protected", - "authority": [ - "App.Videoroom.Dashboard" - ] + "authority": ["App.Videoroom.Dashboard"] }, { "key": "admin.videoroom.list", "path": "/admin/videoroom/list", "componentPath": "@/views/admin/videoroom/RoomList", "routeType": "protected", - "authority": [ - "App.Videoroom.List" - ] + "authority": ["App.Videoroom.List"] }, { "key": "admin.videoroom.roomdetail", "path": "/admin/videoroom/room/:id", "componentPath": "@/views/admin/videoroom/RoomDetail", "routeType": "protected", - "authority": [ - "App.Videoroom.RoomDetail" - ] + "authority": ["App.Videoroom.RoomDetail"] } ], "MenuGroups": [ @@ -732,14 +678,35 @@ "RequiredPermissionName": "App.Notifications.Notification", "IsDisabled": false }, + { "ParentCode": "App.Saas", "Code": "App.BackgroundWorkers", "DisplayName": "App.BackgroundWorkers", "Order": 11, - "Url": "/admin/list/App.BackgroundWorkers", - "Icon": "FcScatterPlot", - "RequiredPermissionName": "App.BackgroundWorkers", + "Url": null, + "Icon": "FcSynchronize", + "RequiredPermissionName": null, + "IsDisabled": false + }, + { + "ParentCode": "App.BackgroundWorkers", + "Code": "App.BackgroundWorkers.Definitions", + "DisplayName": "App.BackgroundWorkers.Definitions", + "Order": 1, + "Url": "/admin/list/App.BackgroundWorkers.Definitions", + "Icon": "FcDataConfiguration", + "RequiredPermissionName": "App.BackgroundWorkers.Definitions", + "IsDisabled": false + }, + { + "ParentCode": "App.BackgroundWorkers", + "Code": "App.BackgroundWorkers.JobFlow", + "DisplayName": "App.BackgroundWorkers.JobFlow", + "Order": 2, + "Url": "/admin/list/App.BackgroundWorkers.JobFlow", + "Icon": "FcFlowChart", + "RequiredPermissionName": "App.BackgroundWorkers.JobFlow", "IsDisabled": false }, { @@ -1314,4 +1281,4 @@ "IsDisabled": false } ] -} \ No newline at end of file +} diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json index 046c410..ba4b9e6 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/PermissionsData.json @@ -1364,17 +1364,17 @@ }, { "GroupName": "App.Saas", - "Name": "App.BackgroundWorkers", + "Name": "App.BackgroundWorkers.Definitions", "ParentName": null, - "DisplayName": "App.BackgroundWorkers", + "DisplayName": "App.BackgroundWorkers.Definitions", "IsEnabled": true, "MultiTenancySide": 2, "MenuGroup": "Erp|Kurs" }, { "GroupName": "App.Saas", - "Name": "App.BackgroundWorkers.Create", - "ParentName": "App.BackgroundWorkers", + "Name": "App.BackgroundWorkers.Definitions.Create", + "ParentName": "App.BackgroundWorkers.Definitions", "DisplayName": "Create", "IsEnabled": true, "MultiTenancySide": 2, @@ -1382,8 +1382,8 @@ }, { "GroupName": "App.Saas", - "Name": "App.BackgroundWorkers.Delete", - "ParentName": "App.BackgroundWorkers", + "Name": "App.BackgroundWorkers.Definitions.Delete", + "ParentName": "App.BackgroundWorkers.Definitions", "DisplayName": "Delete", "IsEnabled": true, "MultiTenancySide": 2, @@ -1391,8 +1391,8 @@ }, { "GroupName": "App.Saas", - "Name": "App.BackgroundWorkers.Export", - "ParentName": "App.BackgroundWorkers", + "Name": "App.BackgroundWorkers.Definitions.Export", + "ParentName": "App.BackgroundWorkers.Definitions", "DisplayName": "Export", "IsEnabled": true, "MultiTenancySide": 2, @@ -1400,8 +1400,8 @@ }, { "GroupName": "App.Saas", - "Name": "App.BackgroundWorkers.Import", - "ParentName": "App.BackgroundWorkers", + "Name": "App.BackgroundWorkers.Definitions.Import", + "ParentName": "App.BackgroundWorkers.Definitions", "DisplayName": "Import", "IsEnabled": true, "MultiTenancySide": 2, @@ -1409,13 +1409,69 @@ }, { "GroupName": "App.Saas", - "Name": "App.BackgroundWorkers.Update", - "ParentName": "App.BackgroundWorkers", + "Name": "App.BackgroundWorkers.Definitions.Update", + "ParentName": "App.BackgroundWorkers.Definitions", "DisplayName": "Update", "IsEnabled": true, "MultiTenancySide": 2, "MenuGroup": "Erp|Kurs" }, + + { + "GroupName": "App.Saas", + "Name": "App.BackgroundWorkers.JobFlow", + "ParentName": null, + "DisplayName": "App.BackgroundWorkers.JobFlow", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Saas", + "Name": "App.BackgroundWorkers.JobFlow.Create", + "ParentName": "App.BackgroundWorkers.JobFlow", + "DisplayName": "Create", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Saas", + "Name": "App.BackgroundWorkers.JobFlow.Delete", + "ParentName": "App.BackgroundWorkers.JobFlow", + "DisplayName": "Delete", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Saas", + "Name": "App.BackgroundWorkers.JobFlow.Export", + "ParentName": "App.BackgroundWorkers.JobFlow", + "DisplayName": "Export", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Saas", + "Name": "App.BackgroundWorkers.JobFlow.Import", + "ParentName": "App.BackgroundWorkers.JobFlow", + "DisplayName": "Import", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, + { + "GroupName": "App.Saas", + "Name": "App.BackgroundWorkers.JobFlow.Update", + "ParentName": "App.BackgroundWorkers.JobFlow", + "DisplayName": "Update", + "IsEnabled": true, + "MultiTenancySide": 2, + "MenuGroup": "Erp|Kurs" + }, + { "GroupName": "App.Saas", "Name": "App.Home", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Sas_H_BackgroundWorker_JobFlow.sql b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Sas_H_BackgroundWorker_JobFlow.sql new file mode 100644 index 0000000..38ac2c0 --- /dev/null +++ b/api/src/Sozsoft.Platform.DbMigrator/Seeds/SqlData/Sas_H_BackgroundWorker_JobFlow.sql @@ -0,0 +1,107 @@ +CREATE OR ALTER VIEW [dbo].[Sas_H_BackgroundWorker_JobFlow] AS + +SELECT + J.Id, + + ISNULL(P.RecurringJobId, N'OneTimeJob') AS [JobName], + + COALESCE(PRC.ProcessingAt, ENQ.EnqueuedAt, J.CreatedAt) AS [ExecutionDate], + + CASE + WHEN SUC.DurationMs IS NOT NULL + THEN SUC.DurationMs + + WHEN PRC.ProcessingAt IS NOT NULL AND SUC.SucceededAt IS NOT NULL + THEN DATEDIFF(MILLISECOND, PRC.ProcessingAt, SUC.SucceededAt) + + WHEN PRC.ProcessingAt IS NOT NULL AND FLD.FailedAt IS NOT NULL + THEN DATEDIFF(MILLISECOND, PRC.ProcessingAt, FLD.FailedAt) + + ELSE NULL + END AS [Duration], + + J.StateName AS [Status], + + CASE + WHEN J.StateName = 'Failed' + THEN COALESCE( + FLD.ExceptionMessage, + FLD.FailedReason, + FLD.ExceptionType, + N'Unknown error' + ) + ELSE NULL + END AS [FailureReason], + + FLD.ExceptionType AS [ExceptionType], + FLD.ExceptionDetails AS [ExceptionDetails] + +FROM HangFire.Job J + +LEFT JOIN HangFire.JobQueue JQ + ON JQ.JobId = J.Id + +OUTER APPLY +( + SELECT + MAX(CASE + WHEN JP.Name = 'RecurringJobId' + THEN REPLACE(JP.Value, '"', '') + END) AS RecurringJobId + FROM HangFire.JobParameter JP + WHERE JP.JobId = J.Id +) P + +OUTER APPLY +( + SELECT TOP 1 + S.CreatedAt AS EnqueuedAt, + S.Reason AS EnqueuedReason, + JSON_VALUE(S.Data, '$.Queue') AS QueueName + FROM HangFire.State S + WHERE S.JobId = J.Id + AND S.Name = 'Enqueued' + ORDER BY S.CreatedAt DESC +) ENQ + +OUTER APPLY +( + SELECT TOP 1 + S.CreatedAt AS ProcessingAt, + S.Reason AS ProcessingReason, + JSON_VALUE(S.Data, '$.ServerId') AS ServerName, + JSON_VALUE(S.Data, '$.WorkerId') AS WorkerId + FROM HangFire.State S + WHERE S.JobId = J.Id + AND S.Name = 'Processing' + ORDER BY S.CreatedAt DESC +) PRC + +OUTER APPLY +( + SELECT TOP 1 + S.CreatedAt AS SucceededAt, + S.Reason AS SucceededReason, + TRY_CONVERT(BIGINT, JSON_VALUE(S.Data, '$.PerformanceDuration')) AS DurationMs + FROM HangFire.State S + WHERE S.JobId = J.Id + AND S.Name = 'Succeeded' + ORDER BY S.CreatedAt DESC +) SUC + +OUTER APPLY +( + SELECT TOP 1 + S.CreatedAt AS FailedAt, + S.Reason AS FailedReason, + JSON_VALUE(S.Data, '$.ExceptionType') AS ExceptionType, + JSON_VALUE(S.Data, '$.ExceptionMessage') AS ExceptionMessage, + JSON_VALUE(S.Data, '$.ExceptionDetails') AS ExceptionDetails + FROM HangFire.State S + WHERE S.JobId = J.Id + AND S.Name = 'Failed' + ORDER BY S.CreatedAt DESC +) FLD + +WHERE JSON_VALUE(J.InvocationData, '$.Method') <> 'DoWorkAsync' +GO diff --git a/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj b/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj index 0f670dd..eeb435a 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj +++ b/api/src/Sozsoft.Platform.DbMigrator/Sozsoft.Platform.DbMigrator.csproj @@ -96,6 +96,7 @@ + diff --git a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs index 11864ed..85ebb42 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/Enums/TableNameEnum.cs @@ -13,6 +13,7 @@ public enum TableNameEnum BackgroundWorker_MailQueue, BackgroundWorker_MailQueueEvents, BackgroundWorker_MailQueueTableFormat, + BackgroundWorker_JobFlow, LogEntry, AiBot, Branch, diff --git a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs index 9591298..7007fc8 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/PlatformConsts.cs @@ -432,7 +432,13 @@ public static class PlatformConsts public const string Notification = Default + ".Notification"; } - public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers"; + public static class BackgroundWorkers + { + public const string Default = Prefix.App + ".BackgroundWorkers"; + + public const string Definitions = Default + ".Definitions"; + public const string JobFlow = Default + ".JobFlow"; + } public static class Menus { diff --git a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs index 02a0192..79b6abe 100644 --- a/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs +++ b/api/src/Sozsoft.Platform.Domain.Shared/TableNameResolver.cs @@ -42,6 +42,7 @@ public static class TableNameResolver { nameof(TableNameEnum.NotificationRule), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.NotificationType), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.BackgroundWorker), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, + { nameof(TableNameEnum.BackgroundWorker_JobFlow), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.BackgroundWorker_MailQueue), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.BackgroundWorker_MailQueueEvents), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, { nameof(TableNameEnum.BackgroundWorker_MailQueueTableFormat), (TablePrefix.PlatformByName, MenuPrefix.Saas) }, diff --git a/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs b/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs index 87c0aa4..1564546 100644 --- a/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs +++ b/api/src/Sozsoft.Platform.Domain/Data/SeedConsts.cs @@ -336,7 +336,7 @@ public static class SeedConsts public const string SocialPost = Default + ".SocialPost"; public const string SocialComment = Default + ".SocialComment"; - + public const string Events = Default + ".Events"; public const string EventType = Events + ".EventType"; public const string EventCategory = Events + ".EventCategory"; @@ -404,7 +404,13 @@ public static class SeedConsts public const string Notification = Default + ".Notification"; } - public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers"; + public static class BackgroundWorkers + { + public const string Default = Prefix.App + ".BackgroundWorkers"; + + public const string Definitions = Default + ".Definitions"; + public const string JobFlow = Default + ".JobFlow"; + } //Web Site public const string Home = Prefix.App + ".Home";