Skip to content

Commit 51005d8

Browse files
authored
Merge pull request #1365 from TechnologyEnhancedLearning/Develop/Features/TD-3132-Unified-platform-interface
Merge Unified platform changes to Test
2 parents abb1816 + 8f5c990 commit 51005d8

File tree

135 files changed

+8284
-1620
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+8284
-1620
lines changed

AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@
8383
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
8484
<PackageReference Include="Azure.Storage.Files.Shares" Version="12.8.0" />
8585
<PackageReference Include="BuildWebCompiler" Version="1.12.405" />
86-
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.9" />
86+
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.11" />
8787
<PackageReference Include="FluentValidation" Version="11.11.0" />
8888
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.0" />
8989
<PackageReference Include="HtmlSanitizer" Version="6.0.453" />
9090
<PackageReference Include="IdentityModel" Version="4.6.0" />
9191
<PackageReference Include="LearningHub.Nhs.Caching" Version="2.0.2" />
92-
<PackageReference Include="LearningHub.Nhs.Models" Version="3.0.48" />
92+
<PackageReference Include="LearningHub.Nhs.Models" Version="3.0.52" />
9393
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.19.0" />
9494
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.36" />
9595
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.36" />

LearningHub.Nhs.WebUI.AutomatedUiTests/LearningHub.Nhs.WebUI.AutomatedUiTests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
44
<TargetFramework>net8.0</TargetFramework>
@@ -11,7 +11,9 @@
1111
</PropertyGroup>
1212

1313
<ItemGroup>
14+
<PackageReference Include="elfhHub.Nhs.Models" Version="3.0.11" />
1415
<PackageReference Include="FluentAssertions" Version="6.12.0" />
16+
<PackageReference Include="LearningHub.Nhs.Models" Version="3.0.52" />
1517
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.13" />
1618
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
1719
<PackageReference Include="Selenium.Axe" Version="4.0.19" />
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace LearningHub.Nhs.WebUI.Configuration
2+
{
3+
/// <summary>
4+
/// The Moodle Settings.
5+
/// </summary>
6+
public class MoodleApiConfig
7+
{
8+
/// <summary>
9+
/// Gets or sets the base url for the Moodle service.
10+
/// </summary>
11+
public string BaseUrl { get; set; } = null!;
12+
13+
/// <summary>
14+
/// Gets or sets the Web service Rest Format.
15+
/// </summary>
16+
public string MoodleWSRestFormat { get; set; } = null!;
17+
18+
/// <summary>
19+
/// Gets or sets the token.
20+
/// </summary>
21+
public string WSToken { get; set; } = null!;
22+
23+
/// <summary>
24+
/// Gets or sets the token.
25+
/// </summary>
26+
public string ApiPath { get; set; } = "webservice/rest/server.php";
27+
28+
/// <summary>
29+
/// Gets or sets the token.
30+
/// </summary>
31+
public string CoursePath { get; set; } = "course/view.php";
32+
}
33+
}

LearningHub.Nhs.WebUI/Controllers/Api/MyLearningController.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,34 @@ public async Task<ActionResult> GetActivityDetailed([FromBody] MyLearningRequest
4444
return this.Ok(activity);
4545
}
4646

47+
/// <summary>
48+
/// Gets the detailed activity data.
49+
/// </summary>
50+
/// <param name="requestModel">The request model - filter settings.</param>
51+
/// <returns>The <see cref="Task"/>.</returns>
52+
[HttpPost]
53+
[Route("GetUserRecentMyLearningActivities")]
54+
public async Task<ActionResult> GetUserRecentMyLearningActivities([FromBody] MyLearningRequestModel requestModel)
55+
{
56+
var activity = await this.myLearningService.GetUserRecentMyLearningActivities(requestModel);
57+
58+
return this.Ok(activity);
59+
}
60+
61+
/// <summary>
62+
/// Gets the detailed activity data.
63+
/// </summary>
64+
/// <param name="requestModel">The request model - filter settings.</param>
65+
/// <returns>The <see cref="Task"/>.</returns>
66+
[HttpPost]
67+
[Route("GetUserLearningHistory")]
68+
public async Task<ActionResult> GetUserLearningHistory([FromBody] MyLearningRequestModel requestModel)
69+
{
70+
var activity = await this.myLearningService.GetUserLearningHistory(requestModel);
71+
72+
return this.Ok(activity);
73+
}
74+
4775
/// <summary>
4876
/// Gets the played segment data for the progress modal in My Learning screen.
4977
/// </summary>

LearningHub.Nhs.WebUI/Controllers/ContributeController.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class ContributeController : BaseController
2828
private readonly IFileService fileService;
2929
private readonly IResourceService resourceService;
3030
private readonly IUserService userService;
31+
private readonly IUserGroupService userGroupService;
3132

3233
/// <summary>
3334
/// Initializes a new instance of the <see cref="ContributeController"/> class.
@@ -37,6 +38,7 @@ public class ContributeController : BaseController
3738
/// <param name="logger">Logger.</param>
3839
/// <param name="settings">Settings.</param>
3940
/// <param name="userService">User service.</param>
41+
/// <param name="userGroupService"> userGroupService.</param>
4042
/// <param name="fileService">File service.</param>
4143
/// <param name="resourceService">Resource service.</param>
4244
/// <param name="azureMediaService">Azure media service.</param>
@@ -48,6 +50,7 @@ public ContributeController(
4850
ILogger<ContributeController> logger,
4951
IOptions<Settings> settings,
5052
IUserService userService,
53+
IUserGroupService userGroupService,
5154
IFileService fileService,
5255
IResourceService resourceService,
5356
IAzureMediaService azureMediaService,
@@ -58,6 +61,7 @@ public ContributeController(
5861
this.authConfig = authConfig;
5962

6063
this.userService = userService;
64+
this.userGroupService = userGroupService;
6165
this.fileService = fileService;
6266
this.resourceService = resourceService;
6367
this.azureMediaService = azureMediaService;
@@ -167,7 +171,8 @@ public async Task<IActionResult> CreateVersion(int resourceId)
167171
[Route("my-contributions/{selectedTab}/{catalogueId}/{nodeId}")]
168172
public async Task<IActionResult> MyContributions()
169173
{
170-
if ((this.User.IsInRole("ReadOnly") || this.User.IsInRole("BasicUser")) && !await this.resourceService.UserHasPublishedResourcesAsync())
174+
bool catalogueContributionPermission = await this.userGroupService.UserHasCatalogueContributionPermission();
175+
if ((this.User.IsInRole("ReadOnly") || this.User.IsInRole("BasicUser")) || (!catalogueContributionPermission && (!await this.resourceService.UserHasPublishedResourcesAsync())))
171176
{
172177
return this.RedirectToAction("AccessDenied", "Home");
173178
}

LearningHub.Nhs.WebUI/Controllers/HomeController.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace LearningHub.Nhs.WebUI.Controllers
1111
using LearningHub.Nhs.Models.Content;
1212
using LearningHub.Nhs.Models.Enums.Content;
1313
using LearningHub.Nhs.Models.Extensions;
14+
using LearningHub.Nhs.Models.Moodle.API;
1415
using LearningHub.Nhs.WebUI.Configuration;
1516
using LearningHub.Nhs.WebUI.Filters;
1617
using LearningHub.Nhs.WebUI.Helpers;
@@ -218,13 +219,12 @@ public async Task<IActionResult> Index(string myLearningDashboard = "my-in-progr
218219
var cataloguesTask = this.dashboardService.GetCataloguesAsync(catalogueDashboard, 1);
219220
var userGroupsTask = this.userGroupService.UserHasCatalogueContributionPermission();
220221

221-
var enrolledCoursesTask = Task.FromResult(new List<MoodleCourseResponseViewModel>());
222-
var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result;
223-
this.ViewBag.EnableMoodle = enableMoodle;
224-
this.ViewBag.ValidMoodleUser = this.CurrentMoodleUserId > 0;
222+
var enrolledCoursesTask = Task.FromResult(new List<MoodleCourseResponseModel>());
223+
(bool enableMoodle, int currentMoodleUserId) = await this.GetMoodleFeatureStateAsync();
224+
225225
if (enableMoodle && myLearningDashboard == "my-enrolled-courses")
226226
{
227-
enrolledCoursesTask = this.dashboardService.GetEnrolledCoursesFromMoodleAsync(this.CurrentMoodleUserId, 1);
227+
enrolledCoursesTask = this.dashboardService.GetEnrolledCoursesFromMoodleAsync(currentMoodleUserId, 1);
228228
}
229229

230230
await Task.WhenAll(learningTask, resourcesTask, cataloguesTask, userGroupsTask);
@@ -280,9 +280,7 @@ public async Task<IActionResult> LoadPage(string dashBoardTray = "my-learning",
280280
Catalogues = new Nhs.Models.Dashboard.DashboardCatalogueResponseViewModel { Type = catalogueDashBoard },
281281
};
282282

283-
var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result;
284-
this.ViewBag.EnableMoodle = enableMoodle;
285-
this.ViewBag.ValidMoodleUser = this.CurrentMoodleUserId > 0;
283+
(bool enableMoodle, int currentMoodleUserId) = await this.GetMoodleFeatureStateAsync();
286284

287285
bool isAjax = this.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
288286

@@ -449,5 +447,28 @@ private async Task<LandingPageViewModel> GetLandingPageContent(bool preview = fa
449447
return new LandingPageViewModel { PageSectionDetailViewModels = new List<PageSectionDetailViewModel>(), PageViewModel = new PageViewModel { PageSections = new List<PageSectionViewModel> { } } };
450448
}
451449
}
450+
451+
/// <summary>
452+
/// Asynchronously retrieves the state of the Moodle feature and the current Moodle user ID.
453+
/// </summary>
454+
/// <remarks>The method checks if the Moodle feature is enabled and retrieves the current Moodle
455+
/// user ID. If the user ID is not already set, it attempts to obtain it asynchronously from the dashboard
456+
/// service.</remarks>
457+
/// <returns>A tuple containing a boolean indicating whether the Moodle feature is enabled and an integer representing
458+
/// the current Moodle user ID.</returns>
459+
private async Task<(bool enableMoodle, int currentMoodleUserId)> GetMoodleFeatureStateAsync()
460+
{
461+
var enableMoodle = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.EnableMoodle)).Result;
462+
this.ViewBag.EnableMoodle = enableMoodle;
463+
int currentMoodleUserId = this.CurrentMoodleUserId;
464+
465+
if (currentMoodleUserId == 0)
466+
{
467+
currentMoodleUserId = await this.dashboardService.GetMoodleUserIdAsync(this.CurrentUserId);
468+
}
469+
470+
this.ViewBag.ValidMoodleUser = currentMoodleUserId > 0;
471+
return (enableMoodle, currentMoodleUserId);
472+
}
452473
}
453474
}

LearningHub.Nhs.WebUI/Controllers/LoginWizardController.cs

Lines changed: 109 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -144,31 +144,7 @@ public async Task<IActionResult> Index(string returnUrl)
144144

145145
if (currentStage?.Id == (int)LoginWizardStageEnum.SecurityQuestions)
146146
{
147-
SecurityQuestionsViewModel securityQuestions = await this.loginWizardService.GetSecurityQuestionsModel(this.CurrentUserId);
148-
149-
while (securityQuestions.UserSecurityQuestions.Count < this.Settings.SecurityQuestionsToAsk)
150-
{
151-
securityQuestions.UserSecurityQuestions.Add(new UserSecurityQuestionViewModel());
152-
}
153-
154-
foreach (var answer in securityQuestions.UserSecurityQuestions)
155-
{
156-
if (!string.IsNullOrEmpty(answer.SecurityQuestionAnswerHash))
157-
{
158-
answer.SecurityQuestionAnswerHash = "********";
159-
}
160-
}
161-
162-
this.TempData.Clear();
163-
var securityViewModel = new SecurityViewModel()
164-
{
165-
SecurityQuestions = securityQuestions.SecurityQuestions,
166-
UserSecurityQuestions = securityQuestions.UserSecurityQuestions,
167-
};
168-
169-
await this.multiPageFormService.SetMultiPageFormData(securityViewModel, MultiPageFormDataFeature.EditRegistrationPrompt, this.TempData);
170-
171-
return this.RedirectToAction("SelectSecurityQuestion", new RouteValueDictionary { { "questionIndex", 0 }, { "returnUrl", returnUrl } });
147+
return this.RedirectToAction("SelectSecurityQuestions", new { returnUrl });
172148
}
173149

174150
if (currentStage?.Id == (int)LoginWizardStageEnum.JobRole || currentStage?.Id == (int)LoginWizardStageEnum.PlaceOfWork || currentStage?.Id == (int)LoginWizardStageEnum.PersonalDetails)
@@ -213,11 +189,19 @@ await this.multiPageFormService.SetMultiPageFormData(
213189
{
214190
return this.RedirectToAction("AccountInformationNeeded");
215191
}
192+
else if (currentStage?.Id == (int)LoginWizardStageEnum.JobRole || currentStage?.Id == (int)LoginWizardStageEnum.PlaceOfWork)
193+
{
194+
return this.RedirectToAction("MyEmploymentDetails", "MyAccount", new { returnUrl, checkDetails = true });
195+
}
216196
else
217197
{
218198
return this.RedirectToAction("Index", "MyAccount", new { returnUrl, checkDetails = true });
219199
}
220200
}
201+
else if (currentStage?.Id == (int)LoginWizardStageEnum.JobRole || currentStage?.Id == (int)LoginWizardStageEnum.PlaceOfWork)
202+
{
203+
return this.RedirectToAction("MyEmploymentDetails", "MyAccount", new { returnUrl, checkDetails = true });
204+
}
221205
else
222206
{
223207
return this.RedirectToAction("Index", "MyAccount", new { returnUrl, checkDetails = true });
@@ -585,6 +569,106 @@ public async Task<ActionResult> AccountConfirmationPost()
585569
return this.RedirectToAction("Index", new RouteValueDictionary { { "returnUrl", accountModel.WizardReturnUrl } });
586570
}
587571

572+
/// <summary>
573+
/// Action for starting the security question multiPageForm stage of the wizard.
574+
/// </summary>
575+
/// <param name="returnUrl">The URL to return to after the login wizard has been completed.</param>
576+
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
577+
[ResponseCache(CacheProfileName = "Never")]
578+
public async Task<IActionResult> SelectSecurityQuestions(string returnUrl)
579+
{
580+
MyAcountSecurityQuestionsViewModel securityViewModel = new MyAcountSecurityQuestionsViewModel();
581+
var result = await this.loginWizardService.GetSecurityQuestionsModel(this.CurrentUserId);
582+
583+
if (result != null)
584+
{
585+
securityViewModel.FirstSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedFirstQuestionId));
586+
securityViewModel.SecondSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedSecondQuestionId));
587+
}
588+
589+
this.ViewBag.ReturnUrl = returnUrl;
590+
return this.View("SecurityQuestionsDetails", securityViewModel);
591+
}
592+
593+
/// <summary>
594+
/// Action for choosing security questions.
595+
/// </summary>
596+
/// <param name="model">The MyAcountSecurityQuestionsViewModel.</param>
597+
/// <param name="returnUrl">The URL to return to after the login wizard has been completed.</param>
598+
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
599+
[HttpPost]
600+
[ResponseCache(CacheProfileName = "Never")]
601+
public async Task<IActionResult> UpdateSecurityQuestionPost(MyAcountSecurityQuestionsViewModel model, string returnUrl)
602+
{
603+
MyAcountSecurityQuestionsViewModel securityViewModel = new MyAcountSecurityQuestionsViewModel();
604+
var result = await this.loginWizardService.GetSecurityQuestionsModel(this.CurrentUserId);
605+
606+
if (result != null)
607+
{
608+
securityViewModel.FirstSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedFirstQuestionId));
609+
securityViewModel.SecondSecurityQuestions = SelectListHelper.MapSelectListWithSelection(result.SecurityQuestions, Convert.ToString(securityViewModel.SelectedSecondQuestionId));
610+
}
611+
612+
if (model != null)
613+
{
614+
if (model.SelectedFirstQuestionId == model.SelectedSecondQuestionId)
615+
{
616+
this.ModelState.AddModelError("DuplicateQuestion", CommonValidationErrorMessages.DuplicateQuestion);
617+
}
618+
619+
if (model.SelectedFirstQuestionId > 0 && string.IsNullOrEmpty(model.SecurityFirstQuestionAnswerHash))
620+
{
621+
this.ModelState.AddModelError(nameof(model.SecurityFirstQuestionAnswerHash), CommonValidationErrorMessages.InvalidSecurityQuestionAnswer);
622+
}
623+
624+
if (model.SelectedSecondQuestionId > 0 && string.IsNullOrEmpty(model.SecuritySecondQuestionAnswerHash))
625+
{
626+
this.ModelState.AddModelError(nameof(model.SecuritySecondQuestionAnswerHash), CommonValidationErrorMessages.InvalidSecurityQuestionAnswer);
627+
}
628+
629+
if (this.ModelState.IsValid)
630+
{
631+
var userSecurityQuestions = new List<UserSecurityQuestionViewModel>
632+
{
633+
new UserSecurityQuestionViewModel
634+
{
635+
SecurityQuestionId = model.SelectedFirstQuestionId,
636+
SecurityQuestionAnswerHash = model.SecurityFirstQuestionAnswerHash,
637+
UserId = this.CurrentUserId,
638+
},
639+
new UserSecurityQuestionViewModel
640+
{
641+
SecurityQuestionId = model.SelectedSecondQuestionId,
642+
SecurityQuestionAnswerHash = model.SecuritySecondQuestionAnswerHash,
643+
UserId = this.CurrentUserId,
644+
},
645+
};
646+
647+
await this.userService.UpdateUserSecurityQuestions(userSecurityQuestions);
648+
649+
// Mark stage complete.
650+
var (cacheExists, loginWizard) = await this.cacheService.TryGetAsync<LoginWizardViewModel>(this.LoginWizardCacheKey);
651+
652+
if (cacheExists)
653+
{
654+
await this.CompleteLoginWizardStageAsync(loginWizard, LoginWizardStageEnum.SecurityQuestions);
655+
this.TempData.Clear();
656+
return this.RedirectToAction("Index", new RouteValueDictionary { { "returnUrl", returnUrl } });
657+
}
658+
659+
this.TempData.Clear();
660+
return this.Redirect("/");
661+
}
662+
else
663+
{
664+
this.ViewBag.ReturnUrl = returnUrl;
665+
return this.View("SecurityQuestionsDetails", securityViewModel);
666+
}
667+
}
668+
669+
return this.View("SecurityQuestionsDetails", securityViewModel);
670+
}
671+
588672
/// <summary>
589673
/// The complete login wizard stage.
590674
/// </summary>

0 commit comments

Comments
 (0)