From 4164dda6dfb1a7f4590522509ef6235dfd55abd3 Mon Sep 17 00:00:00 2001 From: Stanislaw Szolkowski Date: Tue, 9 Jul 2024 20:36:03 +0200 Subject: [PATCH 1/3] Add example Hangfire integration with Optimizely CMS --- .../Features/Hangfire/ExampleRecurringJob.cs | 34 ++++++++++++++++ .../Hangfire/HangfireAuthorizationFilter.cs | 12 ++++++ .../Hangfire/HangfireCmsController.cs | 12 ++++++ .../Features/Hangfire/HangfireMenuProvider.cs | 23 +++++++++++ src/Foundation/Features/Hangfire/Index.cshtml | 39 +++++++++++++++++++ src/Foundation/Foundation.csproj | 4 ++ src/Foundation/Startup.cs | 28 +++++++++++++ 7 files changed, 152 insertions(+) create mode 100644 src/Foundation/Features/Hangfire/ExampleRecurringJob.cs create mode 100644 src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs create mode 100644 src/Foundation/Features/Hangfire/HangfireCmsController.cs create mode 100644 src/Foundation/Features/Hangfire/HangfireMenuProvider.cs create mode 100644 src/Foundation/Features/Hangfire/Index.cshtml diff --git a/src/Foundation/Features/Hangfire/ExampleRecurringJob.cs b/src/Foundation/Features/Hangfire/ExampleRecurringJob.cs new file mode 100644 index 000000000..880ec9bb7 --- /dev/null +++ b/src/Foundation/Features/Hangfire/ExampleRecurringJob.cs @@ -0,0 +1,34 @@ +using Hangfire.Console; +using Hangfire.Server; +using System.Threading; + +namespace Foundation.Features.Hangfire; + +public class ExampleRecurringJob +{ + private readonly IContentTypeRepository _contentTypeRepository; + + + public ExampleRecurringJob(IContentTypeRepository contentTypeRepository) + { + _contentTypeRepository = contentTypeRepository; + } + + public void Execute(PerformContext context) + { + context.WriteLine("Hello, world!"); + Thread.Sleep(TimeSpan.FromSeconds(1)); + + context.SetTextColor(ConsoleTextColor.Red); + context.WriteLine("Error! Just joking :)"); + Thread.Sleep(TimeSpan.FromSeconds(0.2)); + context.ResetTextColor(); + + var bar = context.WriteProgressBar(); + foreach (var contentType in _contentTypeRepository.List().WithProgress(bar)) + { + context.WriteLine(contentType.Name); + Thread.Sleep(TimeSpan.FromSeconds(0.3)); + } + } +} diff --git a/src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs b/src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs new file mode 100644 index 000000000..0aedec305 --- /dev/null +++ b/src/Foundation/Features/Hangfire/HangfireAuthorizationFilter.cs @@ -0,0 +1,12 @@ +using Hangfire.Annotations; +using Hangfire.Dashboard; + +namespace Foundation.Features.Hangfire; + +public class HangfireAuthorizationFilter : IDashboardAuthorizationFilter +{ + public bool Authorize([NotNull] DashboardContext context) + { + return EPiServer.Security.PrincipalInfo.CurrentPrincipal.IsInRole("CmsAdmins"); + } +} \ No newline at end of file diff --git a/src/Foundation/Features/Hangfire/HangfireCmsController.cs b/src/Foundation/Features/Hangfire/HangfireCmsController.cs new file mode 100644 index 000000000..45e36492f --- /dev/null +++ b/src/Foundation/Features/Hangfire/HangfireCmsController.cs @@ -0,0 +1,12 @@ +namespace Foundation.Features.Hangfire; + +[Authorize(Roles = "CmsAdmin,WebAdmins,Administrators")] +[Route("[controller]")] +public class HangfireCmsController : Controller +{ + [Route("[action]")] + public ActionResult Index() + { + return View(); + } +} \ No newline at end of file diff --git a/src/Foundation/Features/Hangfire/HangfireMenuProvider.cs b/src/Foundation/Features/Hangfire/HangfireMenuProvider.cs new file mode 100644 index 000000000..ae4c34a3f --- /dev/null +++ b/src/Foundation/Features/Hangfire/HangfireMenuProvider.cs @@ -0,0 +1,23 @@ +using EPiServer.Authorization; + +namespace Foundation.Features.Hangfire; + +[MenuProvider] +public class HangfireMenuProvider: IMenuProvider +{ + public IEnumerable GetMenuItems() + { + var hangFireMenuItem = new UrlMenuItem("Hangfire", MenuPaths.Global + "/cms" + "/cmsMenuItem", + "/HangfireCms/index") + { + IsAvailable = request => EPiServer.Security.PrincipalInfo.CurrentPrincipal.IsInRole("CmsAdmins"), + AuthorizationPolicy = CmsPolicyNames.CmsAdmin, + SortIndex = SortIndex.First + 25 + }; + + return new MenuItem[] + { + hangFireMenuItem + }; + } +} \ No newline at end of file diff --git a/src/Foundation/Features/Hangfire/Index.cshtml b/src/Foundation/Features/Hangfire/Index.cshtml new file mode 100644 index 000000000..8bda39236 --- /dev/null +++ b/src/Foundation/Features/Hangfire/Index.cshtml @@ -0,0 +1,39 @@ +@using EPiServer.Framework.Web.Resources +@using EPiServer.Shell.Navigation + +@{ + Layout = string.Empty; +} + + + + + + Hangfire Dashboard + @ClientResources.RenderResources("ShellCore") + @ClientResources.RenderResources("ShellCoreLightTheme") + + + + + + @Html.CreatePlatformNavigationMenu() +
+ +
+ + + \ No newline at end of file diff --git a/src/Foundation/Foundation.csproj b/src/Foundation/Foundation.csproj index 2ea82037e..7274fc1df 100644 --- a/src/Foundation/Foundation.csproj +++ b/src/Foundation/Foundation.csproj @@ -81,6 +81,10 @@ + + + + diff --git a/src/Foundation/Startup.cs b/src/Foundation/Startup.cs index 361611e4a..70c570c52 100644 --- a/src/Foundation/Startup.cs +++ b/src/Foundation/Startup.cs @@ -15,6 +15,7 @@ using EPiServer.ServiceApi; using EPiServer.Shell.Modules; using Foundation.Features.Checkout.Payments; +using Foundation.Features.Hangfire; using Foundation.Infrastructure.Cms.ModelBinders; using Foundation.Infrastructure.Cms.Users; using Foundation.Infrastructure.Display; @@ -24,6 +25,8 @@ using Geta.Optimizely.Categories.Configuration; using Geta.Optimizely.Categories.Find.Infrastructure.Initialization; using Geta.Optimizely.Categories.Infrastructure.Initialization; +using Hangfire; +using Hangfire.Console; using Mediachase.Commerce.Anonymous; using Mediachase.Commerce.Orders; using Microsoft.AspNetCore.Builder; @@ -253,6 +256,17 @@ public void ConfigureServices(IServiceCollection services) // Adds the DAM selector button services.AddDamSelectButton(); + + // Add Hangfire services. + services.AddHangfire(configuration => configuration + .SetDataCompatibilityLevel(CompatibilityLevel.Version_180) + .UseSimpleAssemblyNameTypeSerializer() + .UseRecommendedSerializerSettings() + .UseSqlServerStorage(_configuration.GetConnectionString("EcfSqlConnection")) + .UseConsole()); + + // Add the processing server as IHostedService + services.AddHangfireServer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -280,7 +294,21 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) endpoints.MapControllers(); endpoints.MapRazorPages(); endpoints.MapContent(); + endpoints.MapHangfireDashboard(); }); + + var dashboardOptions = new DashboardOptions + { + Authorization = new[] + { + new HangfireAuthorizationFilter() + }, + AppPath = null + }; + // Order of middlewares is important! Add it after Authentication and Authorization in order to have a user in the context. + app.UseHangfireDashboard("/episerver/backoffice/Plugins/hangfire", dashboardOptions); + + RecurringJob.AddOrUpdate(nameof(ExampleRecurringJob) + "_Id", x => x.Execute(null), Cron.Daily); } } } From a30296f53da20499a100684e26b7036647ab0fac Mon Sep 17 00:00:00 2001 From: Stanislaw Szolkowski Date: Tue, 9 Jul 2024 20:48:17 +0200 Subject: [PATCH 2/3] Bump JDK to version 17 --- .github/workflows/ci-episerver.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-episerver.yml b/.github/workflows/ci-episerver.yml index 0df0f55c6..43da2a2c3 100644 --- a/.github/workflows/ci-episerver.yml +++ b/.github/workflows/ci-episerver.yml @@ -13,11 +13,11 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v2 with: distribution: 'adopt' - java-version: '11' + java-version: '17' - uses: actions/checkout@v2 with: From dba70cbbbc37deecd7b9830168bb14a76bff4614 Mon Sep 17 00:00:00 2001 From: Stanislaw Szolkowski Date: Tue, 9 Jul 2024 21:13:49 +0200 Subject: [PATCH 3/3] Remove not needed anymore SonarCloud manual CI analysis --- .github/workflows/ci-episerver.yml | 49 ++---------------------------- 1 file changed, 2 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci-episerver.yml b/.github/workflows/ci-episerver.yml index 43da2a2c3..c4ea5e4c6 100644 --- a/.github/workflows/ci-episerver.yml +++ b/.github/workflows/ci-episerver.yml @@ -12,13 +12,7 @@ jobs: runs-on: ubuntu-latest - steps: - - name: Set up JDK 17 - uses: actions/setup-java@v2 - with: - distribution: 'adopt' - java-version: '17' - + steps: - uses: actions/checkout@v2 with: fetch-depth: 0 @@ -36,39 +30,9 @@ jobs: - name: Setup Episerver/Optimizely nuget feed shell: pwsh run: dotnet nuget add source https://nuget.optimizely.com/feed/packages.svc -n Optimizely - - - name: Cache SonarCloud packages - uses: actions/cache@v1 - with: - path: ~/sonar/cache - key: ${{ runner.os }}-sonar - restore-keys: ${{ runner.os }}-sonar - - - name: Cache SonarCloud scanner - id: cache-sonar-scanner - uses: actions/cache@v1 - with: - path: ./.sonar/scanner - key: ${{ runner.os }}-sonar-scanner - restore-keys: ${{ runner.os }}-sonar-scanner - - - name: Install SonarCloud scanner - if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: pwsh - run: | - New-Item -Path ./.sonar/scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path ./.sonar/scanner - name: Restore dependencies run: dotnet restore - - - name: Sonar Begin - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: pwsh - run: | - ./.sonar/scanner/dotnet-sonarscanner begin /k:"szolkowski_Foundation" /o:"szolkowski" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - name: Dotnet build shell: pwsh @@ -82,13 +46,4 @@ jobs: - name: NPM build shell: pwsh working-directory: ./src/Foundation - run: npm run dev - - - name: Sonar End - if: always() - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: pwsh - run: | - ./.sonar/scanner/dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file + run: npm run dev \ No newline at end of file