diff --git a/Apps/JobScheduler/src/Jobs/AssignBetaFeatureAccessJob.cs b/Apps/JobScheduler/src/Jobs/AssignBetaFeatureAccessJob.cs
new file mode 100644
index 0000000000..1c5c849884
--- /dev/null
+++ b/Apps/JobScheduler/src/Jobs/AssignBetaFeatureAccessJob.cs
@@ -0,0 +1,135 @@
+// -------------------------------------------------------------------------
+// Copyright © 2019 Province of British Columbia
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// -------------------------------------------------------------------------
+namespace HealthGateway.JobScheduler.Jobs
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using Hangfire;
+ using HealthGateway.Common.Data.Utils;
+ using HealthGateway.Database.Constants;
+ using HealthGateway.Database.Context;
+ using HealthGateway.Database.Models;
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.Extensions.Configuration;
+ using Microsoft.Extensions.Logging;
+
+ ///
+ /// Assigns users access to a beta feature.
+ ///
+ /// The configuration to use.
+ /// The logger to use.
+ /// The database context to use.
+ public class AssignBetaFeatureAccessJob(IConfiguration configuration, ILogger logger, GatewayDbContext dbContext)
+ {
+ ///
+ /// Key used to identify this job in the config.
+ ///
+ public const string JobKey = "AssignBetaFeatureAccess";
+
+ private const string EnabledTemplateKey = "Enabled";
+ private const string MaxBatchSizeKey = "MaxBatchSize";
+ private const string UserCountKey = "UserCount";
+ private const string BetaFeatureKey = "BetaFeature";
+ private const int ConcurrencyTimeout = 5 * 60; // 5 Minutes
+
+ private readonly bool enabled = configuration.GetValue($"{JobKey}:{EnabledTemplateKey}", false);
+ private readonly int maxBatchSize = configuration.GetValue($"{JobKey}:{MaxBatchSizeKey}", 0);
+ private readonly int userCount = configuration.GetValue($"{JobKey}:{UserCountKey}", 0);
+ private readonly BetaFeature betaFeature = configuration.GetValue($"{JobKey}:{BetaFeatureKey}", null)
+ ?? throw new ArgumentNullException(nameof(configuration), $"{JobKey}:{BetaFeatureKey} is null");
+
+ ///
+ /// Assigns users access to a beta feature.
+ ///
+ /// to manage the async request.
+ /// A representing the result of the asynchronous operation.
+ [DisableConcurrentExecution(ConcurrencyTimeout)]
+ public async Task ProcessAsync(CancellationToken ct = default)
+ {
+ if (!this.enabled)
+ {
+ logger.LogInformation("{JobKey} is disabled", JobKey);
+ return;
+ }
+
+ if (this.userCount < 1)
+ {
+ logger.LogInformation("{JobKey}:{UserCountKey} must be at least 1", JobKey, UserCountKey);
+ return;
+ }
+
+ if (this.maxBatchSize < 1)
+ {
+ logger.LogInformation("{JobKey}:{MaxBatchSizeKey} must be at least 1", JobKey, MaxBatchSizeKey);
+ return;
+ }
+
+ logger.LogInformation("Starting process of assigning access to {BetaFeature} beta feature for up to {UserCount} user(s)", EnumUtility.ToEnumString(this.betaFeature), this.userCount);
+ int processedUserCount = 0;
+ while (processedUserCount < this.userCount)
+ {
+ int batchSize = Math.Min(this.maxBatchSize, this.userCount - processedUserCount);
+
+ logger.LogInformation("Retrieving up to {BatchSize} user(s) with verified email addresses who have recently logged in", batchSize);
+ IList userProfiles = await this.GetRecentUserProfiles(processedUserCount, batchSize, ct);
+
+ int retrievedCount = userProfiles.Count;
+ logger.LogInformation("Retrieved {RetrievedCount} user(s)", retrievedCount);
+ if (retrievedCount > 0)
+ {
+ logger.LogInformation("Adding beta feature access for retrieved user(s)");
+ IEnumerable associations = userProfiles.Select(this.GenerateBetaFeatureAccess);
+ dbContext.BetaFeatureAccess.AddRange(associations);
+ await dbContext.SaveChangesAsync(ct); // commit after each iteration
+ }
+
+ processedUserCount += retrievedCount;
+ if (retrievedCount < batchSize)
+ {
+ break;
+ }
+ }
+
+ logger.LogInformation("Completed processing, having assigned beta feature access to {ProcessedUserCount} user(s)", processedUserCount);
+ }
+
+ private async Task> GetRecentUserProfiles(int skip, int take, CancellationToken ct)
+ {
+ return await dbContext.UserProfile
+ .Where(u => !string.IsNullOrWhiteSpace(u.Email))
+ .Where(u => u.BetaFeatureCodes.All(c => c.Code != this.betaFeature))
+ .OrderByDescending(o => o.LastLoginDateTime)
+ .Skip(skip)
+ .Take(take)
+ .ToListAsync(ct);
+ }
+
+ private BetaFeatureAccess GenerateBetaFeatureAccess(UserProfile userProfile)
+ {
+ DateTime now = DateTime.UtcNow;
+ return new BetaFeatureAccess
+ {
+ Hdid = userProfile.HdId,
+ BetaFeatureCode = this.betaFeature,
+ CreatedDateTime = now,
+ UpdatedDateTime = now,
+ };
+ }
+ }
+}
diff --git a/Apps/JobScheduler/src/Startup.cs b/Apps/JobScheduler/src/Startup.cs
index 7f88927b8e..8cb8b47f21 100644
--- a/Apps/JobScheduler/src/Startup.cs
+++ b/Apps/JobScheduler/src/Startup.cs
@@ -190,6 +190,7 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
SchedulerHelper.ScheduleJobAsync(this.configuration, "CloseAccounts", j => j.ProcessAsync(CancellationToken.None));
SchedulerHelper.ScheduleJobAsync(this.configuration, "OneTime", j => j.ProcessAsync(CancellationToken.None));
SchedulerHelper.ScheduleJobAsync(this.configuration, "DeleteEmailJob", j => j.DeleteOldEmailsAsync(CancellationToken.None));
+ SchedulerHelper.ScheduleJobAsync(this.configuration, AssignBetaFeatureAccessJob.JobKey, j => j.ProcessAsync(CancellationToken.None));
}
}
}
diff --git a/Apps/JobScheduler/src/appsettings.json b/Apps/JobScheduler/src/appsettings.json
index 95ab18e7f4..4eda2e12d8 100644
--- a/Apps/JobScheduler/src/appsettings.json
+++ b/Apps/JobScheduler/src/appsettings.json
@@ -26,10 +26,7 @@
]
},
"RequestLogging": {
- "ExcludedPaths": [
- "/stats",
- "/health"
- ]
+ "ExcludedPaths": ["/stats", "/health"]
},
"OpenIdConnect": {
"Authority": "https://loginproxy.gov.bc.ca/auth/realms/health-gateway-gold",
@@ -72,9 +69,7 @@
"TraceConsoleExporterEnabled": false,
"ZipkinEnabled": false,
"ZipkinUri": "",
- "IgnorePathPrefixes": [
- "/health"
- ]
+ "IgnorePathPrefixes": ["/health"]
},
"AllowOrigins": "*",
"ConnectionStrings": {
@@ -197,6 +192,16 @@
"TargetFolder": "Resources",
"AppName": "PROV-DRUG"
},
+ "AssignBetaFeatureAccess": {
+ "Id": "Assign Beta Feature Access",
+ "Schedule": "30 16 * * *",
+ "Immediate": "false",
+ "Delay": 60,
+ "Enabled": true,
+ "MaxBatchSize": 500,
+ "UserCount": 100,
+ "BetaFeature": "Salesforce"
+ },
"PharmacyAssessmentFile": {
"Url": "https://raw.githubusercontent.com/bcgov/pharmacy-assessment/main/Pharmacy%20Assessment%20PIN.csv",
"TargetFolder": "Resources",