Skip to content

Commit 750f491

Browse files
committed
TD-5044: Implement changes for automated accessibility unit testing project to the LH web UI repository
2 parents 1f24c67 + a9c85bd commit 750f491

File tree

372 files changed

+15177
-37831
lines changed

Some content is hidden

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

372 files changed

+15177
-37831
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
namespace LearningHub.Nhs.AdminUI.Configuration
2+
{
3+
/// <summary>
4+
/// Config AzureMediaSettings.
5+
/// </summary>
6+
public class MediaKindSettings
7+
{
8+
/// <summary>
9+
/// Gets or sets subscription name.
10+
/// </summary>
11+
public string SubscriptionName { get; set; }
12+
13+
/// <summary>
14+
/// Gets or sets token.
15+
/// </summary>
16+
public string Token { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets storage media account name.
20+
/// </summary>
21+
public string StorageAccountName { get; set; }
22+
23+
/// <summary>
24+
/// Gets or sets media kind media service issuer.
25+
/// </summary>
26+
public string Issuer { get; set; }
27+
28+
/// <summary>
29+
/// Gets or sets media kind media service audience.
30+
/// </summary>
31+
public string Audience { get; set; }
32+
33+
/// <summary>
34+
/// Gets or sets the contentkey policyname.
35+
/// </summary>
36+
public string ContentKeyPolicyName { get; set; }
37+
38+
/// <summary>
39+
/// Gets or sets media kind media service jwt primary key secret.
40+
/// </summary>
41+
public string JWTPrimaryKeySecret { get; set; }
42+
43+
/// <summary>
44+
/// Gets or sets the media kind media kind MKPlayer licence key.
45+
/// </summary>
46+
public string MKPlayerLicence { get; set; }
47+
48+
/// <summary>
49+
/// Gets or sets the media kind blob connection string.
50+
/// </summary>
51+
public string MediaKindStorageConnectionString { get; set; }
52+
}
53+
}

AdminUI/LearningHub.Nhs.AdminUI/Configuration/WebSettings.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ public class WebSettings
7777
/// </summary>
7878
public string AzureFileStorageConnectionString { get; set; }
7979

80+
/// <summary>
81+
/// Gets or sets the AzureSourceFileStorageConnectionString.
82+
/// </summary>
83+
public string AzureSourceArchiveStorageConnectionString { get; set; }
84+
85+
/// <summary>
86+
/// Gets or sets the AzurePurgedFileStorageConnectionString.
87+
/// </summary>
88+
public string AzureContentArchiveStorageConnectionString { get; set; }
89+
8090
/// <summary>
8191
/// Gets or sets the azure file storage resource share name.
8292
/// </summary>
@@ -146,5 +156,10 @@ public class WebSettings
146156
/// Gets or sets the FileUploadSettings.
147157
/// </summary>
148158
public FileUploadSettingsModel FileUploadSettings { get; set; } = new FileUploadSettingsModel();
159+
160+
/// <summary>
161+
/// Gets or sets the MediaKindSettings.
162+
/// </summary>
163+
public MediaKindSettings MediaKindSettings { get; set; } = new MediaKindSettings();
149164
}
150165
}

AdminUI/LearningHub.Nhs.AdminUI/Controllers/ResourceController.cs

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Threading.Tasks;
77
using LearningHub.Nhs.AdminUI.Configuration;
88
using LearningHub.Nhs.AdminUI.Extensions;
9+
using LearningHub.Nhs.AdminUI.Helpers;
910
using LearningHub.Nhs.AdminUI.Interfaces;
1011
using LearningHub.Nhs.AdminUI.Models;
1112
using LearningHub.Nhs.Models.Common;
@@ -16,6 +17,7 @@
1617
using Microsoft.AspNetCore.Mvc;
1718
using Microsoft.Extensions.Logging;
1819
using Microsoft.Extensions.Options;
20+
using Microsoft.FeatureManagement;
1921

2022
/// <summary>
2123
/// Defines the <see cref="ResourceController" />.
@@ -32,6 +34,11 @@ public class ResourceController : BaseController
3234
/// </summary>
3335
private readonly IOptions<WebSettings> websettings;
3436

37+
/// <summary>
38+
/// Defines the featureManager.
39+
/// </summary>
40+
private readonly IFeatureManager featureManager;
41+
3542
/// <summary>
3643
/// Defines the _logger.
3744
/// </summary>
@@ -47,37 +54,52 @@ public class ResourceController : BaseController
4754
/// </summary>
4855
private IResourceService resourceService;
4956

57+
/// <summary>
58+
/// Defines the _fileService.
59+
/// </summary>
60+
private IFileService fileService;
61+
5062
/// <summary>
5163
/// Initializes a new instance of the <see cref="ResourceController"/> class.
5264
/// </summary>
5365
/// <param name="hostingEnvironment">The hostingEnvironment<see cref="IWebHostEnvironment"/>.</param>
5466
/// <param name="config">The config<see cref="IOptions{WebSettings}"/>.</param>
5567
/// <param name="logger">The logger<see cref="ILogger{HomeController}"/>.</param>
5668
/// <param name="resourceService">The resourceService<see cref="IResourceService"/>.</param>
69+
/// <param name="fileService">The fileService<see cref="IResourceService"/>.</param>
5770
/// /// <param name="websettings">The websettings<see cref="IOptions{WebSettings}"/>.</param>
71+
/// <param name="featureManager">The featureManager<see cref="IFeatureManager"/>.</param>
5872
public ResourceController(
5973
IWebHostEnvironment hostingEnvironment,
6074
IOptions<WebSettings> config,
6175
ILogger<HomeController> logger,
6276
IResourceService resourceService,
63-
IOptions<WebSettings> websettings)
77+
IFileService fileService,
78+
IOptions<WebSettings> websettings,
79+
IFeatureManager featureManager)
6480
: base(hostingEnvironment)
6581
{
6682
this.logger = logger;
6783
this.websettings = websettings;
6884
this.config = config.Value;
6985
this.resourceService = resourceService;
86+
this.fileService = fileService;
87+
this.featureManager = featureManager;
7088
}
7189

7290
/// <summary>
7391
/// The Details.
7492
/// </summary>
7593
/// <param name="id">The id<see cref="int"/>.</param>
94+
/// <param name="activeTab">The activeTab<see cref="string"/>.</param>
95+
/// <param name="status">The status<see cref="string"/>.</param>
7696
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
7797
[HttpGet]
78-
public async Task<IActionResult> Details(int id)
98+
public async Task<IActionResult> Details(int id, string activeTab = "details", string status = "")
7999
{
80100
var resource = await this.resourceService.GetResourceVersionExtendedViewModelAsync(id);
101+
this.ViewBag.ActiveTab = activeTab;
102+
this.ViewBag.Status = status;
81103
return this.View(resource);
82104
}
83105

@@ -120,6 +142,41 @@ public async Task<IActionResult> GetValidationResults(int resourceVersionId)
120142
return this.PartialView("_ValidationResults", vm);
121143
}
122144

145+
/// <summary>
146+
/// The GetDevIdDetails.
147+
/// </summary>
148+
/// <param name="resourceVersionId">The resourceVersionId<see cref="int"/>.</param>
149+
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
150+
[HttpPost]
151+
public async Task<IActionResult> GetDevIdDetails(int resourceVersionId)
152+
{
153+
var vm = await this.resourceService.GetResourceVersionDevIdDetailsAsync(resourceVersionId);
154+
155+
return this.PartialView("_DevIdDetails", vm);
156+
}
157+
158+
/// <summary>
159+
/// The update the dev Id details.
160+
/// </summary>
161+
/// <param name="model">The model.</param>
162+
/// <returns>The <see cref="Task{IActionResult}"/>.</returns>
163+
[HttpPost]
164+
public async Task<IActionResult> UpdateDevIdDetails(ResourceVersionDevIdViewModel model)
165+
{
166+
var message = string.Empty;
167+
if (await this.resourceService.DoesDevIdExistsAsync(model.DevId))
168+
{
169+
message = "Duplicate";
170+
}
171+
else
172+
{
173+
await this.resourceService.UpdateDevIdDetailsAsync(model);
174+
message = "Success";
175+
}
176+
177+
return this.RedirectToAction("Details", new { id = model.ResourceVersionId, activeTab = "devId", status = message });
178+
}
179+
123180
/// <summary>
124181
/// The Index.
125182
/// </summary>
@@ -288,11 +345,17 @@ public async Task<IActionResult> TransferResourceOwnership(int resourceId, strin
288345
[HttpPost]
289346
public async Task<IActionResult> Unpublish(int resourceVersionId, string details)
290347
{
348+
var associatedFile = await this.resourceService.GetResourceVersionExtendedViewModelAsync(resourceVersionId);
291349
var vr = await this.resourceService.UnpublishResourceVersionAsync(resourceVersionId, details);
292350
await this.resourceService.CreateResourceVersionEvent(resourceVersionId, Nhs.Models.Enums.ResourceVersionEventTypeEnum.UnpublishedByAdmin, "Unpublish using Admin UI", 0);
293351

294352
if (vr.IsValid)
295353
{
354+
if (associatedFile.ScormDetails != null || associatedFile.HtmlDetails != null)
355+
{
356+
_ = Task.Run(async () => { await this.fileService.PurgeResourceFile(associatedFile, null); });
357+
}
358+
296359
return this.Json(new
297360
{
298361
success = true,
@@ -309,6 +372,41 @@ public async Task<IActionResult> Unpublish(int resourceVersionId, string details
309372
}
310373
}
311374

375+
/// <summary>
376+
/// The GetAVUnavailableView.
377+
/// </summary>
378+
/// <returns> partial view. </returns>
379+
[Route("Resource/GetAVUnavailableView")]
380+
[HttpGet("GetAVUnavailableView")]
381+
public IActionResult GetAVUnavailableView()
382+
{
383+
return this.PartialView("_AudioVideoUnavailable");
384+
}
385+
386+
/// <summary>
387+
/// The GetAddAVFlag.
388+
/// </summary>
389+
/// <returns> Return AV Flag.</returns>
390+
[Route("Resource/GetAddAVFlag")]
391+
[HttpGet("GetAddAVFlag")]
392+
public bool GetAddAVFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.AddAudioVideo).Result;
393+
394+
/// <summary>
395+
/// The GetDisplayAVFlag.
396+
/// </summary>
397+
/// <returns> Return display AV flag.</returns>
398+
[Route("Resource/GetDisplayAVFlag")]
399+
[HttpGet("GetDisplayAVFlag")]
400+
public bool GetDisplayAVFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.DisplayAudioVideo).Result;
401+
402+
/// <summary>
403+
/// The GetMKPlayerKey.
404+
/// </summary>
405+
/// <returns>Mediakind MK Player Key.</returns>
406+
[Route("Resource/GetMKPlayerKey")]
407+
[HttpGet("GetMKPlayerKey")]
408+
public string GetMKPlayerKey() => this.websettings.Value.MediaKindSettings.MKPlayerLicence;
409+
312410
private static List<PagingOptionPair> FilterOptions()
313411
{
314412
List<PagingOptionPair> options = new List<PagingOptionPair>();

AdminUI/LearningHub.Nhs.AdminUI/Controllers/UserGroupController.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ public async Task<IActionResult> AddUsersToUserGroup(int userGroupId, string use
257257
var vr = await this.userGroupService.AddUsersToUserGroup(userGroupId, userIdList);
258258
if (vr.IsValid)
259259
{
260+
this.ClearUserCachedPermissions(userIdList);
260261
return this.Json(new
261262
{
262263
success = true,
@@ -527,5 +528,16 @@ public async Task<IActionResult> UserGroupCatalogues(int id)
527528

528529
return this.PartialView("_UserGroupCatalogues", catalogues);
529530
}
531+
532+
private void ClearUserCachedPermissions(string userIdList)
533+
{
534+
if (!string.IsNullOrWhiteSpace(userIdList))
535+
{
536+
foreach (var userId in userIdList.Split(","))
537+
{
538+
_ = Task.Run(async () => { await this.userService.ClearUserCachedPermissions(int.Parse(userId)); });
539+
}
540+
}
541+
}
530542
}
531543
}

AdminUI/LearningHub.Nhs.AdminUI/Controllers/api/MediaManifestProxyController.cs

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,27 @@ public string Get(string playBackUrl, string token)
5151
{
5252
using (var reader = new StreamReader(stream))
5353
{
54-
const string qualityLevelRegex = @"(QualityLevels\(\d+\))";
54+
const string qualityLevelRegex = @"(|)([^""\s]+\.m3u8\(encryption=cbc\))";
5555
const string fragmentsRegex = @"(Fragments\([\w\d=-]+,[\w\d=-]+\))";
56-
const string urlRegex = @"("")(https?:\/\/[\da-z\.-]+\.[a-z\.]{2,6}[\/\w \.-]*\/?[\?&][^&=]+=[^&=#]*)("")";
56+
const string urlRegex = @"(https?:\/\/[\da-z\.-]+\.[a-z\.]{2,6}[\/\w \.-]*\?[^,\s""]*)";
5757

5858
var baseUrl = playBackUrl.Substring(0, playBackUrl.IndexOf(".ism", System.StringComparison.OrdinalIgnoreCase)) + ".ism";
5959
this.logger.LogDebug($"baseUrl={baseUrl}");
6060

6161
var content = reader.ReadToEnd();
6262

63-
var newContent = Regex.Replace(content, urlRegex, string.Format(CultureInfo.InvariantCulture, "$1$2&token={0}$3", token));
63+
content = ReplaceUrisWithProxy(content, baseUrl);
64+
var newContent = Regex.Replace(content, urlRegex, match =>
65+
{
66+
string baseUrlWithQuery = match.Groups[1].Value; // URL including the query string
67+
68+
// Append the token correctly without modifying surrounding characters
69+
string newUrl = baseUrlWithQuery.Contains("?") ?
70+
$"{baseUrlWithQuery}&token={token}" :
71+
$"{baseUrlWithQuery}?token={token}";
72+
73+
return newUrl;
74+
});
6475
this.logger.LogDebug($"newContent={newContent}");
6576

6677
var match = Regex.Match(playBackUrl, qualityLevelRegex);
@@ -87,5 +98,33 @@ public string Get(string playBackUrl, string token)
8798

8899
return null;
89100
}
101+
102+
private static string ReplaceUrisWithProxy(string playlistContent, string proxyUrl)
103+
{
104+
// Split the playlist content into lines
105+
var lines = playlistContent.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None);
106+
107+
// Process each line to replace media or map URIs
108+
for (int i = 0; i < lines.Length; i++)
109+
{
110+
if (lines[i].StartsWith("#EXT-X-MAP:URI=", StringComparison.OrdinalIgnoreCase))
111+
{
112+
// Extract the URI from the current line for EXT-X-MAP
113+
var existingUri = lines[i].Substring(lines[i].IndexOf('=') + 1).Trim('"');
114+
var newUri = $"{proxyUrl}/{existingUri}";
115+
lines[i] = lines[i].Replace(existingUri, newUri);
116+
}
117+
else if (lines[i].StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase) && i + 1 < lines.Length)
118+
{
119+
// Get the URI from the next line for EXTINF
120+
var existingUri = lines[i + 1].Trim();
121+
var newUri = $"{proxyUrl}/{existingUri}";
122+
lines[i + 1] = newUri;
123+
}
124+
}
125+
126+
// Join the modified lines back into a single string
127+
return string.Join("\r\n", lines);
128+
}
90129
}
91130
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
namespace LearningHub.Nhs.AdminUI.Helpers
2+
{
3+
/// <summary>
4+
/// <see cref="FeatureFlags"/>.
5+
/// </summary>
6+
public static class FeatureFlags
7+
{
8+
/// <summary>
9+
/// The AddAudioVideo.
10+
/// </summary>
11+
public const string AddAudioVideo = "AddAudioVideo";
12+
13+
/// <summary>
14+
/// The DisplayAudioVideo.
15+
/// </summary>
16+
public const string DisplayAudioVideo = "DisplayAudioVideo";
17+
}
18+
}

AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ public static string GetResourceTypeVerb(this MyLearningDetailedItemViewModel my
116116
return "Played " + GetDurationText(myLearningDetailedItemViewModel.ActivityDurationSeconds * 1000);
117117
case ResourceTypeEnum.WebLink:
118118
return "Visited";
119+
case ResourceTypeEnum.Html:
120+
return "Viewed";
121+
case ResourceTypeEnum.Case:
122+
return "Accessed";
123+
case ResourceTypeEnum.Assessment:
124+
return "Accessed";
119125
default:
120126
return string.Empty;
121127
}

0 commit comments

Comments
 (0)