diff --git a/.github/azure-pipeline-database-ci.yml b/.github/azure-pipeline-database-ci.yml index 827fa3977..db055c665 100644 --- a/.github/azure-pipeline-database-ci.yml +++ b/.github/azure-pipeline-database-ci.yml @@ -1,7 +1,13 @@ trigger: branches: - include: - - CI + exclude: + - '*' + +pr: + branches: + exclude: + - '*' + resources: repositories: - repository: self @@ -12,6 +18,7 @@ jobs: displayName: Agent job pool: vmImage: windows-2019 + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/CI')) steps: - checkout: self clean: true diff --git a/.github/azure-pipeline-openapi-reportapi-ci.yml b/.github/azure-pipeline-openapi-reportapi-ci.yml index fe44998ce..1b10679e6 100644 --- a/.github/azure-pipeline-openapi-reportapi-ci.yml +++ b/.github/azure-pipeline-openapi-reportapi-ci.yml @@ -8,12 +8,14 @@ trigger: branches: include: - CI + exclude: + - '*' name: $(date:yyyyMMdd)$(rev:.r) resources: repositories: - repository: self type: git - ref: refs/heads/RC + ref: refs/heads/CI jobs: - job: Job_1 displayName: Agent job diff --git a/.github/azure-pipeline-webui-ci.yml b/.github/azure-pipeline-webui-ci.yml index 936677328..21d9d6ddc 100644 --- a/.github/azure-pipeline-webui-ci.yml +++ b/.github/azure-pipeline-webui-ci.yml @@ -4,10 +4,7 @@ variables: value: '**/*.csproj' - name: BuildParameters.TestProjects value: '**/*[Tt]ests/*.csproj' -trigger: - branches: - include: - - CI +# Triggered Via Azure pipeline name: $(date:yyyyMMdd)$(rev:.r) resources: repositories: diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/ResourceController.cs b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/ResourceController.cs index 91c5052af..e2c191f11 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Controllers/ResourceController.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Controllers/ResourceController.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using LearningHub.Nhs.AdminUI.Configuration; using LearningHub.Nhs.AdminUI.Extensions; + using LearningHub.Nhs.AdminUI.Helpers; using LearningHub.Nhs.AdminUI.Interfaces; using LearningHub.Nhs.AdminUI.Models; using LearningHub.Nhs.Models.Common; @@ -16,6 +17,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Microsoft.FeatureManagement; /// /// Defines the . @@ -32,6 +34,11 @@ public class ResourceController : BaseController /// private readonly IOptions websettings; + /// + /// Defines the featureManager. + /// + private readonly IFeatureManager featureManager; + /// /// Defines the _logger. /// @@ -55,18 +62,21 @@ public class ResourceController : BaseController /// The logger. /// The resourceService. /// /// The websettings. + /// The featureManager. public ResourceController( IWebHostEnvironment hostingEnvironment, IOptions config, ILogger logger, IResourceService resourceService, - IOptions websettings) + IOptions websettings, + IFeatureManager featureManager) : base(hostingEnvironment) { this.logger = logger; this.websettings = websettings; this.config = config.Value; this.resourceService = resourceService; + this.featureManager = featureManager; } /// @@ -309,6 +319,33 @@ public async Task Unpublish(int resourceVersionId, string details } } + /// + /// The GetAVUnavailableView. + /// + /// partial view. + [Route("Resource/GetAVUnavailableView")] + [HttpGet("GetAVUnavailableView")] + public IActionResult GetAVUnavailableView() + { + return this.PartialView("_AudioVideoUnavailable"); + } + + /// + /// The GetAddAVFlag. + /// + /// Return AV Flag. + [Route("Resource/GetAddAVFlag")] + [HttpGet("GetAddAVFlag")] + public bool GetAddAVFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.AddAudioVideo).Result; + + /// + /// The GetDisplayAVFlag. + /// + /// Return display AV flag. + [Route("Resource/GetDisplayAVFlag")] + [HttpGet("GetDisplayAVFlag")] + public bool GetDisplayAVFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.DisplayAudioVideo).Result; + private static List FilterOptions() { List options = new List(); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/FeatureFlags.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/FeatureFlags.cs new file mode 100644 index 000000000..24e9e3be9 --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/FeatureFlags.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.AdminUI.Helpers +{ + /// + /// . + /// + public static class FeatureFlags + { + /// + /// The AddAudioVideo. + /// + public const string AddAudioVideo = "AddAudioVideo"; + + /// + /// The DisplayAudioVideo. + /// + public const string DisplayAudioVideo = "DisplayAudioVideo"; + } +} diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs index f421ac86c..b7646146e 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/Helpers/LearningActivityHelper.cs @@ -128,7 +128,7 @@ public static string GetResourceTypeVerb(this MyLearningDetailedItemViewModel my /// The . public static string GetActivityStatusDisplayText(this MyLearningDetailedItemViewModel myLearningDetailedItemViewModel) { - if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched + if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image @@ -137,7 +137,7 @@ public static string GetActivityStatusDisplayText(this MyLearningDetailedItemVie { return "Completed"; } - else if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched + else if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.GenericFile)) { return "Downloaded"; @@ -164,36 +164,33 @@ public static string GetActivityStatusDisplayText(this MyLearningDetailedItemVie /// /// The myLearningDetailedItemViewModel. /// The . - public static ActivityStatusEnum GetActivityStatus(this MyLearningDetailedItemViewModel myLearningDetailedItemViewModel) + public static string GetActivityStatus(this MyLearningDetailedItemViewModel myLearningDetailedItemViewModel) { - if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched + if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article - || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Html || myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Case)) { - return ActivityStatusEnum.Completed; + return "Viewed"; } - else if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched - && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.GenericFile)) + else if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed + && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink)) { - return ActivityStatusEnum.Downloaded; + return "Launched"; } - else if (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.Assessment) + else if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed + && (myLearningDetailedItemViewModel.ResourceType == ResourceTypeEnum.GenericFile)) { - if (myLearningDetailedItemViewModel.Complete) - { - return myLearningDetailedItemViewModel.ScorePercentage >= myLearningDetailedItemViewModel.AssessmentDetails.PassMark ? ActivityStatusEnum.Passed : ActivityStatusEnum.Failed; - } - else - { - return myLearningDetailedItemViewModel.ScorePercentage >= myLearningDetailedItemViewModel.AssessmentDetails.PassMark ? ActivityStatusEnum.Passed : ActivityStatusEnum.InProgress; - } + return ActivityStatusEnum.Downloaded.ToString(); + } + else if (myLearningDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Incomplete) + { + return ActivityStatusEnum.InProgress.ToString(); } else { - return myLearningDetailedItemViewModel.ActivityStatus; + return myLearningDetailedItemViewModel.ActivityStatus.ToString(); } } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj index 337bb21f5..a6109a60f 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj +++ b/AdminUI/LearningHub.Nhs.AdminUI/LearningHub.Nhs.AdminUI.csproj @@ -89,11 +89,13 @@ - + + + diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/cmsPageRow.vue b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/cmsPageRow.vue index 488402b62..79769cdf3 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/cmsPageRow.vue +++ b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/cmsPageRow.vue @@ -18,7 +18,7 @@
-
+
+
+
+
@@ -78,10 +81,14 @@ SectionTemplateType: SectionTemplateType, pageSectionDetail: null as PageSectionDetailModel, disableVideoControl: false, + displayAVFlag: false, + audioVideoUnavailableView : '' as string, }; }, created() { this.load(); + this.getDisplayAVFlag(); + this.getAudioVideoUnavailableView(); }, computed: { getStyle() { @@ -125,7 +132,7 @@ returnClass = "information-page__text-container--no-padding-right"; } } - return returnClass; + return returnClass; }, getDescription() { if (this.section.description) { @@ -138,50 +145,60 @@ }, isRightSectionLayout() { return this.section.sectionLayoutType == SectionLayoutType.Right; - } + }, }, methods: { + getDisplayAVFlag() { + contentData.getDisplayAVFlag().then(response => { + this.displayAVFlag = response; + }); + }, + getAudioVideoUnavailableView() { + contentData.getAVUnavailableView().then(response => { + this.audioVideoUnavailableView = response; + }); + }, load() { if (this.sectionTemplateType === SectionTemplateType.Video) { - contentData.getPageSectionDetailVideo(this.section.id).then(response => { - this.pageSectionDetail = response; + contentData.getPageSectionDetailVideo(this.section.id).then(response => { + this.pageSectionDetail = response; - if (!this.pageSectionDetail.videoAsset) - return; + if (!this.pageSectionDetail.videoAsset) + return; - const id = 'azureMediaPlayer' + this.pageSectionDetail.id; - let azureMediaPlayer = amp(id); + const id = 'azureMediaPlayer' + this.pageSectionDetail.id; + let azureMediaPlayer = amp(id); - if (this.pageSectionDetail.videoAsset.azureMediaAsset) { - $(`#${id}`).css({ 'height': '', 'border': '1px solid #768692' }); - this.disableVideoControl = false; - } else { - this.disableVideoControl = true; - } + if (this.pageSectionDetail.videoAsset.azureMediaAsset) { + $(`#${id}`).css({ 'height': '', 'border': '1px solid #768692' }); + this.disableVideoControl = false; + } else { + this.disableVideoControl = true; + } - if (this.pageSectionDetail.videoAsset.thumbnailImageFile) { - azureMediaPlayer.poster(`/file/download/${this.pageSectionDetail.videoAsset.thumbnailImageFile.filePath}/${this.pageSectionDetail.videoAsset.thumbnailImageFile.fileName}`); - } - if (this.pageSectionDetail.videoAsset.azureMediaAsset && this.pageSectionDetail.videoAsset.closedCaptionsFile) { - azureMediaPlayer.src([{ - type: "application/vnd.ms-sstr+xml", - src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri, - protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }] - }], - [{ kind: "captions", src: `/file/download/${this.pageSectionDetail.videoAsset.closedCaptionsFile.filePath}/${this.pageSectionDetail.videoAsset.closedCaptionsFile.fileName}`, srclang: "en", label: "english" }]); - } - else if (this.pageSectionDetail.videoAsset.azureMediaAsset && !this.pageSectionDetail.videoAsset.closedCaptionsFile) { - azureMediaPlayer.src([{ - type: "application/vnd.ms-sstr+xml", - src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri, - protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }] - }]); - } - }); - } else { - contentData.getPageSectionDetail(this.section.id).then(x => this.pageSectionDetail = x); - } - }, + if (this.pageSectionDetail.videoAsset.thumbnailImageFile) { + azureMediaPlayer.poster(`/file/download/${this.pageSectionDetail.videoAsset.thumbnailImageFile.filePath}/${this.pageSectionDetail.videoAsset.thumbnailImageFile.fileName}`); + } + if (this.pageSectionDetail.videoAsset.azureMediaAsset && this.pageSectionDetail.videoAsset.closedCaptionsFile) { + azureMediaPlayer.src([{ + type: "application/vnd.ms-sstr+xml", + src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri, + protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }] + }], + [{ kind: "captions", src: `/file/download/${this.pageSectionDetail.videoAsset.closedCaptionsFile.filePath}/${this.pageSectionDetail.videoAsset.closedCaptionsFile.fileName}`, srclang: "en", label: "english" }]); + } + else if (this.pageSectionDetail.videoAsset.azureMediaAsset && !this.pageSectionDetail.videoAsset.closedCaptionsFile) { + azureMediaPlayer.src([{ + type: "application/vnd.ms-sstr+xml", + src: this.pageSectionDetail.videoAsset.azureMediaAsset.locatorUri, + protectionInfo: [{ type: 'AES', authenticationToken: `Bearer=${this.pageSectionDetail.videoAsset.azureMediaAsset.authenticationToken}` }] + }]); + } + }); + } else { + contentData.getPageSectionDetail(this.section.id).then(x => this.pageSectionDetail = x); + } + }, getAESProtection(token: string): string { var aesProtectionInfo = '{"protectionInfo": [{"type": "AES", "authenticationToken":"Bearer=' + token + '"}], "streamingFormats":["SMOOTH","DASH"]}'; return aesProtectionInfo; @@ -198,7 +215,7 @@ }, watch: { section() { - this.load(); + this.load(); } } }) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/contentState.ts b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/contentState.ts index af39cb191..32777ec0b 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/contentState.ts +++ b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/contentState.ts @@ -25,7 +25,8 @@ export class State { isVideoFileValid: boolean; isTranscriptFileValid: boolean; isCaptionFileValid: boolean; - isThumbnailFileValid: boolean; + isThumbnailFileValid: boolean; + getAVUnavailableView: string = ''; } const state = new State(); @@ -37,7 +38,10 @@ class ApiRequest { const mutations = { async populateUploadSettings(state: State) { state.uploadSettings = await contentData.getUploadSettings(); - }, + }, + async populateAVUnavailableView(state: State) { + state.getAVUnavailableView = await contentData.getAVUnavailableView(); + }, setCurrentUserName(state: State, payload: string) { state.currentUserName = payload; }, diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/pageVideoSection.vue b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/pageVideoSection.vue index 216daecd7..74e0ab031 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/pageVideoSection.vue +++ b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/content/pageVideoSection.vue @@ -66,7 +66,12 @@
-
+
+ +
+
+ +
@@ -284,7 +289,8 @@ Vue.use(Vuelidate as any); deleteWarning: false, fileDeleteWarning: false, fileOrTypeToBeDeleted: 0, - videoErrorMessage: '' + videoErrorMessage: '', + addAVFlag: false } }, validations: { @@ -294,7 +300,9 @@ Vue.use(Vuelidate as any); } }, async created() { - this.$store.commit('populateUploadSettings'); + this.$store.commit('populateUploadSettings'); + this.$store.commit('populateAVUnavailableView'); + this.getAddAudioVideoFlag(); const pageSectionId = this.$route.params.sectionId; @@ -348,7 +356,10 @@ Vue.use(Vuelidate as any); }, videoAsset(): VideoAssetModel { return this.$store.state.pageSectionDetail.videoAsset; - } + }, + audioVideoUnavailableView(): string { + return this.$store.state.getAVUnavailableView; + }, }, methods: { setSectionLayoutType(sectionLayoutType: SectionLayoutType) { @@ -492,7 +503,12 @@ Vue.use(Vuelidate as any); this.fileErrorType = FileErrorTypeEnum.NoError this.fileUploadServerError = ''; $('#fileUpload').val(null); - }, + }, + getAddAudioVideoFlag() { + contentData.getAddAVFlag().then(response => { + this.addAVFlag = response; + }); + }, }, }); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/data/content.ts b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/data/content.ts index e5d06dbf3..4af805bad 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/data/content.ts +++ b/AdminUI/LearningHub.Nhs.AdminUI/Scripts/vuesrc/data/content.ts @@ -241,6 +241,38 @@ const updateVideoAsset = async function (videoAsset: VideoAssetModel): Promise { + return await axios.get('/Resource/GetAddAVFlag') + .then(response => { + return response.data; + }) + .catch(e => { + console.log('getAddAVFlag:' + e); + throw e; + }); +}; + +const getDisplayAVFlag = async function (): Promise { + return await axios.get('/Resource/GetDisplayAVFlag') + .then(response => { + return response.data; + }) + .catch(e => { + console.log('getDisplayAVFlag:' + e); + throw e; + }); +}; + +const getAVUnavailableView = async function (): Promise { + return await axios.get('/Resource/GetAVUnavailableView') + .then(response => { + return response.data; + }) + .catch(e => { + console.error('Error fetching shared partial view:', e) + throw e; + }); +}; export const contentData = { getUploadSettings, @@ -260,5 +292,8 @@ export const contentData = { createPageSection, updatePageSectionDetail, getPageSectionDetailVideo, - updateVideoAsset + updateVideoAsset, + getAddAVFlag, + getDisplayAVFlag, + getAVUnavailableView }; \ No newline at end of file diff --git a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs index 3b1cd833f..0b115922f 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs +++ b/AdminUI/LearningHub.Nhs.AdminUI/ServiceCollectionExtension.cs @@ -32,6 +32,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; + using Microsoft.FeatureManagement; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; @@ -219,6 +220,8 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur { options.Filters.Add(typeof(CheckInitialLogonFilter)); }); + + services.AddFeatureManagement(); } private static async Task UserSessionBegins(TokenValidatedContext context) diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml index eb2d3312c..15013bb91 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Catalogue/Edit.cshtml @@ -138,7 +138,6 @@
- You have 1800 characters remaining
@@ -413,13 +412,6 @@ var editorData = editor.getData(); var data = $("

").html(editorData).text(); var textData = data.replace(/\s\n\n/g, ' ').replace(/\n\n/g, ' ').replace(/\s\n/g, '').replace(/\n/g, ''); - var remaining = 1800 - textData.length; - - if (remaining >= 0) { - $('#with-hint-info').text("You have " + remaining + " characters remaining").removeClass('text-danger'); - } else { - $('#with-hint-info').text("You have " + (-1 * remaining) + " character too many").addClass('text-danger'); - } }); editor.fire('change'); diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/Shared/_AudioVideoUnavailable.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/Shared/_AudioVideoUnavailable.cshtml new file mode 100644 index 000000000..b8e71596e --- /dev/null +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/Shared/_AudioVideoUnavailable.cshtml @@ -0,0 +1,9 @@ +
+

+ + Important: + Video and audio unavailable + +

+

The video and audio upload and streaming services on the Learning Hub platform are temporarily unavailable. We are working to resolve this as quickly as possible.

+
diff --git a/AdminUI/LearningHub.Nhs.AdminUI/Views/User/_UserLearningRecord.cshtml b/AdminUI/LearningHub.Nhs.AdminUI/Views/User/_UserLearningRecord.cshtml index e47a5b66a..7dafcfd9b 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/Views/User/_UserLearningRecord.cshtml +++ b/AdminUI/LearningHub.Nhs.AdminUI/Views/User/_UserLearningRecord.cshtml @@ -81,36 +81,48 @@ - @if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Completed) + @if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Completed.ToString()) { Completed } - else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Passed) + else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == "Viewed") + { + + Viewed + + } + else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Passed.ToString()) { Passed } - else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Downloaded) + else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Downloaded.ToString()) { - + Downloaded } - else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.InProgress) + else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.InProgress.ToString()) { - Incomplete + In progress } - else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Failed) + else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Failed.ToString()) { Failed } + else if (LearningActivityHelper.GetActivityStatus(userLearningRecord) == ActivityStatusEnum.Launched.ToString()) + { + + Launched + + } diff --git a/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json b/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json index 1deeae8b4..4f68451d4 100644 --- a/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json +++ b/AdminUI/LearningHub.Nhs.AdminUI/appsettings.json @@ -51,5 +51,9 @@ "NLogDb": "", "LearningHubRedis": "" }, - "APPINSIGHTS_INSTRUMENTATIONKEY": "" + "APPINSIGHTS_INSTRUMENTATIONKEY": "", + "FeatureManagement": { + "AddAudioVideo": true, + "DisplayAudioVideo": true + } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Configuration/Settings.cs b/LearningHub.Nhs.WebUI/Configuration/Settings.cs index 0ef2e69af..7797a36c2 100644 --- a/LearningHub.Nhs.WebUI/Configuration/Settings.cs +++ b/LearningHub.Nhs.WebUI/Configuration/Settings.cs @@ -151,6 +151,16 @@ public Settings() /// public string AzureFileStorageConnectionString { get; set; } + /// + /// Gets or sets the AzureSourceFileStorageConnectionString. + /// + public string AzureSourceArchiveStorageConnectionString { get; set; } + + /// + /// Gets or sets the AzurePurgedFileStorageConnectionString. + /// + public string AzureContentArchiveStorageConnectionString { get; set; } + /// /// Gets or sets the AzureFileStorageResourceShareName. /// diff --git a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs index 2f000ea71..dac3a38e9 100644 --- a/LearningHub.Nhs.WebUI/Controllers/AccountController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/AccountController.cs @@ -953,7 +953,6 @@ public async Task CreateAccountWorkPlaceSearch() public async Task CreateAccountWorkPlace(AccountCreationViewModel accountCreationViewModel) { var accountCreation = await this.multiPageFormService.GetMultiPageFormData(MultiPageFormDataFeature.AddRegistrationPrompt, this.TempData); - if (string.IsNullOrWhiteSpace(accountCreationViewModel.FilterText)) { if (!string.IsNullOrWhiteSpace(accountCreation.LocationId)) @@ -1265,10 +1264,11 @@ private async Task GetAccountConfirmationDetails(Ac var employer = await this.locationService.GetByIdAsync(int.TryParse(accountCreationViewModel.LocationId, out int primaryEmploymentId) ? primaryEmploymentId : 0); var region = await this.regionService.GetAllAsync(); var specialty = await this.specialtyService.GetSpecialtiesAsync(); - var role = await this.jobRoleService.GetPagedFilteredAsync(accountCreationViewModel.CurrentRoleName, accountCreationViewModel.CurrentPageIndex, UserRegistrationContentPageSize); - if (role.Item1 > 0) + + var role = await this.jobRoleService.GetFilteredAsync(accountCreationViewModel.CurrentRoleName); + if (role.Count > 0) { - accountCreationViewModel.CurrentRoleName = role.Item2.FirstOrDefault(x => x.Id == int.Parse(accountCreationViewModel.CurrentRole)).NameWithStaffGroup; + accountCreationViewModel.CurrentRoleName = role.FirstOrDefault(x => x.Id == int.Parse(accountCreationViewModel.CurrentRole)).NameWithStaffGroup; } var grade = await this.gradeService.GetGradesForJobRoleAsync(int.TryParse(accountCreationViewModel.CurrentRole, out int roleId) ? roleId : 0); diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs index 5bacf44d1..279bda210 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ContributeController.cs @@ -1,6 +1,8 @@ namespace LearningHub.Nhs.WebUI.Controllers.Api { using System; + using System.Collections.Generic; + using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -222,7 +224,16 @@ public async Task DeleteResourceKeywordAsync([FromBody] KeywordDel [Route("DeleteResourceVersion/{resourceversionId}")] public async Task DeleteResourceVersion(int resourceVersionId) { + var associatedFile = await this.resourceService.GetObsoleteResourceFile(resourceVersionId, true); var validationResult = await this.contributeService.DeleteResourceVersionAsync(resourceVersionId); + if (validationResult.IsValid) + { + if (associatedFile.Any()) + { + _ = Task.Run(async () => { await this.fileService.PurgeResourceFile(null, associatedFile); }); + } + } + return this.Ok(validationResult); } @@ -330,7 +341,19 @@ public ActionResult GetSettings() [Route("PublishResourceVersion")] public async Task PublishResourceVersionAsync([FromBody] PublishViewModel publishViewModel) { + var associatedResource = await this.resourceService.GetResourceVersionExtendedAsync(publishViewModel.ResourceVersionId); var validationResult = await this.contributeService.SubmitResourceVersionForPublishAsync(publishViewModel); + if (validationResult.IsValid) + { + if (associatedResource.ResourceTypeEnum != ResourceTypeEnum.Scorm && associatedResource.ResourceTypeEnum != ResourceTypeEnum.Html) + { + var obsoleteFiles = await this.resourceService.GetObsoleteResourceFile(publishViewModel.ResourceVersionId); + if (obsoleteFiles.Any()) + { + await this.fileService.PurgeResourceFile(null, obsoleteFiles); + } + } + } return this.Ok(validationResult); } diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs index c61201d4c..7f0c1feee 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ResourceController.cs @@ -101,7 +101,7 @@ public async Task DownloadResourceAndRecordActivity(int resourceV ResourceVersionId = resourceVersionId, NodePathId = nodePathId, ActivityStart = DateTime.UtcNow, // TODO: What about user's timezone offset when Javascript is disabled? Needs JavaScript. - ActivityStatus = ActivityStatusEnum.Downloaded, + ActivityStatus = ActivityStatusEnum.Completed, }; await this.activityService.CreateResourceActivityAsync(activity); @@ -128,7 +128,7 @@ public async Task NavigateToWeblinkAndRecordActivity(int resource ResourceVersionId = resourceVersionId, NodePathId = nodePathId, ActivityStart = DateTime.UtcNow, // TODO: What about user's timezone offset when Javascript is disabled? Needs JavaScript. - ActivityStatus = ActivityStatusEnum.Launched, + ActivityStatus = ActivityStatusEnum.Completed, }; await this.activityService.CreateResourceActivityAsync(activity); diff --git a/LearningHub.Nhs.WebUI/Controllers/Api/ScormController.cs b/LearningHub.Nhs.WebUI/Controllers/Api/ScormController.cs index 6f053036c..66516430e 100644 --- a/LearningHub.Nhs.WebUI/Controllers/Api/ScormController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/Api/ScormController.cs @@ -230,6 +230,11 @@ private async Task Commit(SCO scoObject) try { var activeContent = this.userService.GetActiveContentAsync().Result; + ////if (activeContent.Count == 0) + ////{ + //// return false; + ////} + if (!activeContent.Any(ac => ac.ScormActivityId == scoObject.InstanceId)) { throw new Exception($"User does not have ActiveContent for ScormActivityId={scoObject.InstanceId}"); @@ -260,6 +265,11 @@ private async Task Commit(SCO scoObject) // Persist update. await this.activityService.UpdateScormActivityAsync(scoObject); + ////if (scoObject.LessonStatusId == ScormLessionStatus.ActivityStatusId(ScormLessionStatus.Completed) || scoObject.LessonStatusId == ScormLessionStatus.ActivityStatusId(ScormLessionStatus.Passed)) + ////{ + //// await this.activityService.CompleteScormActivity(scoObject); + ////} + return true; } catch (Exception ex) diff --git a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs index daa8612ee..f524fe458 100644 --- a/LearningHub.Nhs.WebUI/Controllers/HomeController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/HomeController.cs @@ -12,6 +12,7 @@ namespace LearningHub.Nhs.WebUI.Controllers using LearningHub.Nhs.Models.Extensions; using LearningHub.Nhs.WebUI.Configuration; using LearningHub.Nhs.WebUI.Filters; + using LearningHub.Nhs.WebUI.Helpers; using LearningHub.Nhs.WebUI.Interfaces; using LearningHub.Nhs.WebUI.Models; using Microsoft.ApplicationInsights.AspNetCore; @@ -23,6 +24,7 @@ namespace LearningHub.Nhs.WebUI.Controllers using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Microsoft.FeatureManagement; using Settings = LearningHub.Nhs.WebUI.Configuration.Settings; /// @@ -36,6 +38,7 @@ public class HomeController : BaseController private readonly IUserService userService; private readonly IDashboardService dashboardService; private readonly IContentService contentService; + private readonly IFeatureManager featureManager; /// /// Initializes a new instance of the class. @@ -49,6 +52,7 @@ public class HomeController : BaseController /// Auth config. /// Dashboard service. /// Content service. + /// featureManager. public HomeController( IHttpClientFactory httpClientFactory, IWebHostEnvironment hostingEnvironment, @@ -58,7 +62,8 @@ public HomeController( IResourceService resourceService, LearningHubAuthServiceConfig authConfig, IDashboardService dashboardService, - IContentService contentService) + IContentService contentService, + IFeatureManager featureManager) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.authConfig = authConfig; @@ -66,6 +71,7 @@ public HomeController( this.resourceService = resourceService; this.dashboardService = dashboardService; this.contentService = contentService; + this.featureManager = featureManager; } /// @@ -367,6 +373,7 @@ private async Task GetLandingPageContent(bool preview = fa var model = new LandingPageViewModel { PageSectionDetailViewModels = new List() }; var pageViewModel = await this.contentService.GetPageByIdAsync(1, preview); model.PageViewModel = pageViewModel; + model.DisplayAudioVideo = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.DisplayAudioVideoResource)).Result; if (pageViewModel != null && pageViewModel.PageSections.Any()) { foreach (var item in pageViewModel.PageSections) diff --git a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs index 1061fd44e..c583859bb 100644 --- a/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/MyLearningController.cs @@ -129,6 +129,8 @@ public async Task Index(MyLearningViewModel learningRequest = nul Passed = learningRequest.Passed, Failed = learningRequest.Failed, Downloaded = learningRequest.Downloaded, + Viewed = learningRequest.Viewed, + Launched = learningRequest.Launched, CertificateEnabled = learningRequest.CertificateEnabled, }; diff --git a/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs b/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs index 4ad026334..7a1245acc 100644 --- a/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs +++ b/LearningHub.Nhs.WebUI/Controllers/ResourceController.cs @@ -23,6 +23,7 @@ using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; + using Microsoft.FeatureManagement; /// /// Defines the . @@ -41,6 +42,7 @@ public class ResourceController : BaseController private readonly IMyLearningService myLearningService; private readonly IFileService fileService; private readonly ICacheService cacheService; + private readonly IFeatureManager featureManager; /// /// Initializes a new instance of the class. @@ -60,6 +62,7 @@ public class ResourceController : BaseController /// The hierarchyService. /// The fileService. /// The cacheService. + /// The Feature flag manager. public ResourceController( IWebHostEnvironment hostingEnvironment, ILogger logger, @@ -75,7 +78,8 @@ public ResourceController( IMyLearningService myLearningService, IHierarchyService hierarchyService, IFileService fileService, - ICacheService cacheService) + ICacheService cacheService, + IFeatureManager featureManager) : base(hostingEnvironment, httpClientFactory, logger, settings.Value) { this.azureMediaService = azureMediaService; @@ -89,6 +93,7 @@ public ResourceController( this.myLearningService = myLearningService; this.fileService = fileService; this.cacheService = cacheService; + this.featureManager = featureManager; } /// @@ -108,6 +113,8 @@ public async Task Index(int resourceReferenceId, bool? acceptSens this.ViewBag.MediaActivityPlayingEventIntervalSeconds = this.Settings.MediaActivityPlayingEventIntervalSeconds; this.ViewBag.KeepUserSessionAliveIntervalSeconds = Convert.ToInt32(this.Settings.KeepUserSessionAliveIntervalMins) * 60000; this.ViewBag.SupportUrl = this.Settings.SupportUrls.SupportForm; + var displayAVResourceFlag = Task.Run(() => this.featureManager.IsEnabledAsync(FeatureFlags.DisplayAudioVideoResource)).Result; + this.ViewBag.DisplayAVResourceFlag = displayAVResourceFlag; if (resourceReferenceId == 0) { @@ -173,14 +180,14 @@ public async Task Index(int resourceReferenceId, bool? acceptSens } // For article/image resources, immediately record the resource activity for this user. - if ((resource.ResourceTypeEnum == ResourceTypeEnum.Article || resource.ResourceTypeEnum == ResourceTypeEnum.Image) && ((resource.SensitiveContent && acceptSensitiveContent.HasValue && acceptSensitiveContent.Value) || !resource.SensitiveContent) && canAccessResource) + if ((resource.ResourceTypeEnum == ResourceTypeEnum.Article || resource.ResourceTypeEnum == ResourceTypeEnum.Image) && (!resource.SensitiveContent)) { var activity = new CreateResourceActivityViewModel() { ResourceVersionId = resource.ResourceVersionId, NodePathId = resource.NodePathId, ActivityStart = DateTime.UtcNow, // TODO: What about user's timezone offset when Javascript is disabled? Needs JavaScript. - ActivityStatus = ActivityStatusEnum.Launched, + ActivityStatus = ActivityStatusEnum.Completed, }; await this.activityService.CreateResourceActivityAsync(activity); } @@ -400,11 +407,17 @@ public IActionResult UnpublishConfirm(ResourceIndexViewModel viewModel) [Route("Resource/UnpublishConfirmPost")] public async Task UnpublishConfirm(ResourceUnpublishConfirmViewModel viewModel) { + var associatedFile = await this.resourceService.GetResourceVersionExtendedAsync(viewModel.ResourceVersionId); var validationResult = await this.resourceService.UnpublishResourceVersionAsync(viewModel.ResourceVersionId); var catalogue = await this.catalogueService.GetCatalogueAsync(viewModel.CatalogueNodeVersionId); if (validationResult.IsValid) { + if (associatedFile.ScormDetails != null || associatedFile.HtmlDetails != null) + { + _ = Task.Run(async () => { await this.fileService.PurgeResourceFile(associatedFile, null); }); + } + if (viewModel.CatalogueNodeVersionId == 1) { return this.Redirect("/my-contributions/unpublished"); @@ -472,7 +485,7 @@ public async Task HtmlResourceContent(int resourceReferenceId, st ResourceVersionId = resourceVersionId, NodePathId = nodePathId, ActivityStart = DateTime.UtcNow, // TODO: What about user's timezone offset when Javascript is disabled? Needs JavaScript. - ActivityStatus = ActivityStatusEnum.Launched, + ActivityStatus = ActivityStatusEnum.Completed, }; await this.activityService.CreateResourceActivityAsync(activity); } @@ -501,5 +514,32 @@ public async Task HtmlResourceContent(int resourceReferenceId, st return this.Ok(this.Content("No file found")); } + + /// + /// The GetAVUnavailableView. + /// + /// partial view. + [Route("Resource/GetAVUnavailableView")] + [HttpGet("GetAVUnavailableView")] + public IActionResult GetAVUnavailableView() + { + return this.PartialView("_AudioVideoUnavailable"); + } + + /// + /// The GetContributeAVResourceFlag. + /// + /// Return Contribute Resource AV Flag. + [Route("Resource/GetContributeAVResourceFlag")] + [HttpGet("GetContributeAVResourceFlag")] + public bool GetContributeResourceAVFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.ContributeAudioVideoResource).Result; + + /// + /// The GetDisplayAVResourceFlag. + /// + /// Return Display AV Resource Flag. + [Route("Resource/GetDisplayAVResourceFlag")] + [HttpGet("GetDisplayAVResourceFlag")] + public bool GetDisplayAVResourceFlag() => this.featureManager.IsEnabledAsync(FeatureFlags.DisplayAudioVideoResource).Result; } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Helpers/FeatureFlags.cs b/LearningHub.Nhs.WebUI/Helpers/FeatureFlags.cs new file mode 100644 index 000000000..f4b01af4d --- /dev/null +++ b/LearningHub.Nhs.WebUI/Helpers/FeatureFlags.cs @@ -0,0 +1,18 @@ +namespace LearningHub.Nhs.WebUI.Helpers +{ + /// + /// Defines the . + /// + public static class FeatureFlags + { + /// + /// The ContributeAudioVideoResource. + /// + public const string ContributeAudioVideoResource = "ContributeAudioVideoResource"; + + /// + /// The DisplayAudioVideoResource. + /// + public const string DisplayAudioVideoResource = "DisplayAudioVideoResource"; + } +} diff --git a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs index 3f42256e1..03e93fc07 100644 --- a/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/UtilityHelper.cs @@ -110,7 +110,7 @@ public static string GetResourceTypeIconClass(ResourceTypeEnum resourceType) /// The resource type. /// The media duration in milliseconds. /// The resource type name, and duration if applicable. - public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType, int? durationInMilliseconds) + public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType, int? durationInMilliseconds = 0) { switch (resourceType) { @@ -119,7 +119,9 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType case ResourceTypeEnum.Article: return "Article"; case ResourceTypeEnum.Audio: - return "Audio - " + GetDurationText(durationInMilliseconds.Value); + string durationText = GetDurationText(durationInMilliseconds ?? 0); + durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; + return "Audio" + durationText; case ResourceTypeEnum.Equipment: return "Equipment"; case ResourceTypeEnum.Image: @@ -127,7 +129,9 @@ public static string GetPrettifiedResourceTypeName(ResourceTypeEnum resourceType case ResourceTypeEnum.Scorm: return "elearning"; case ResourceTypeEnum.Video: - return "Video - " + GetDurationText(durationInMilliseconds.Value); + durationText = GetDurationText(durationInMilliseconds ?? 0); + durationText = string.IsNullOrEmpty(durationText) ? string.Empty : " - " + durationText; + return "Video" + durationText; case ResourceTypeEnum.WebLink: return "Web link"; case ResourceTypeEnum.GenericFile: diff --git a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs index 571cdfb5f..f77383f0c 100644 --- a/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs +++ b/LearningHub.Nhs.WebUI/Helpers/ViewActivityHelper.cs @@ -127,7 +127,7 @@ public static string GetResourceTypeVerb(this ActivityDetailedItemViewModel acti /// The . public static string GetActivityStatusDisplayText(this ActivityDetailedItemViewModel activityDetailedItemViewModel) { - if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched + if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image @@ -136,7 +136,7 @@ public static string GetActivityStatusDisplayText(this ActivityDetailedItemViewM { return "Completed"; } - else if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched + else if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.GenericFile)) { return "Downloaded"; @@ -163,36 +163,33 @@ public static string GetActivityStatusDisplayText(this ActivityDetailedItemViewM /// /// The activityDetailedItemViewModel. /// The . - public static ActivityStatusEnum GetActivityStatus(this ActivityDetailedItemViewModel activityDetailedItemViewModel) + public static string GetActivityStatus(this ActivityDetailedItemViewModel activityDetailedItemViewModel) { - if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched + if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Article - || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Image || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Html || activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Case)) { - return ActivityStatusEnum.Completed; + return "Viewed"; } - else if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Launched - && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.GenericFile)) + else if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed + && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.WebLink)) { - return ActivityStatusEnum.Downloaded; + return "Launched"; } - else if (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.Assessment) + else if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Completed + && (activityDetailedItemViewModel.ResourceType == ResourceTypeEnum.GenericFile)) { - if (activityDetailedItemViewModel.Complete) - { - return activityDetailedItemViewModel.ScorePercentage >= activityDetailedItemViewModel.AssessmentDetails.PassMark ? ActivityStatusEnum.Passed : ActivityStatusEnum.Failed; - } - else - { - return activityDetailedItemViewModel.ScorePercentage >= activityDetailedItemViewModel.AssessmentDetails.PassMark ? ActivityStatusEnum.Passed : ActivityStatusEnum.InProgress; - } + return ActivityStatusEnum.Downloaded.ToString(); + } + else if (activityDetailedItemViewModel.ActivityStatus == ActivityStatusEnum.Incomplete) + { + return ActivityStatusEnum.InProgress.ToString(); } else { - return activityDetailedItemViewModel.ActivityStatus; + return activityDetailedItemViewModel.ActivityStatus.ToString(); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IFileService.cs b/LearningHub.Nhs.WebUI/Interfaces/IFileService.cs index 8351dde97..c74a1681f 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IFileService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IFileService.cs @@ -1,8 +1,10 @@ namespace LearningHub.Nhs.WebUI.Interfaces { + using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using Azure.Storage.Files.Shares.Models; + using LearningHub.Nhs.Models.Resource; /// /// Defines the . @@ -41,5 +43,13 @@ public interface IFileService /// The directoryRef. /// The . Task ProcessFile(Stream fileBytes, string fileName, string directoryRef = ""); + + /// + /// The PurgeResourceFile. + /// + /// The vm.. + /// . + /// The . + Task PurgeResourceFile(ResourceVersionExtendedViewModel vm = null, List filePaths = null); } } diff --git a/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs b/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs index fb54a2777..c09ab0704 100644 --- a/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs +++ b/LearningHub.Nhs.WebUI/Interfaces/IResourceService.cs @@ -282,5 +282,13 @@ public interface IResourceService /// resource version id. /// The . Task DeleteAllResourceVersionProviderAsync(int resourceVersionId); + + /// + /// The GetObsoleteResourceFile. + /// + /// The resourceVersionId. + /// . + /// The . + Task> GetObsoleteResourceFile(int resourceVersionId, bool deletedResource = false); } } diff --git a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj index c052f2516..817199786 100644 --- a/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj +++ b/LearningHub.Nhs.WebUI/LearningHub.Nhs.WebUI.csproj @@ -28,10 +28,6 @@ - - - - @@ -112,7 +108,7 @@ - + @@ -122,6 +118,8 @@ + + diff --git a/LearningHub.Nhs.WebUI/Models/LandingPageViewModel.cs b/LearningHub.Nhs.WebUI/Models/LandingPageViewModel.cs index 1db8f021b..aa6666b55 100644 --- a/LearningHub.Nhs.WebUI/Models/LandingPageViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/LandingPageViewModel.cs @@ -46,5 +46,10 @@ public LandingPageViewModel() /// Gets or sets get or sets the pageSectionDetailViewModels. /// public List PageSectionDetailViewModels { get; set; } + + /// + /// Gets or sets a value indicating whether gets or sets the DisplayAVFromAMS. + /// + public bool DisplayAudioVideo { get; set; } } } \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs b/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs index b83a536e5..c7ae8692d 100644 --- a/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/MyLearningViewModel.cs @@ -184,11 +184,13 @@ public List StatusFilterCheckbox() { var checkboxes = new List() { - new CheckboxListItemViewModel("Complete", "Complete", null), - new CheckboxListItemViewModel("Incomplete", "Incomplete", null), + new CheckboxListItemViewModel("Complete", "Completed", null), + new CheckboxListItemViewModel("Incomplete", "In progress", null), new CheckboxListItemViewModel("Passed", "Passed", null), new CheckboxListItemViewModel("Failed", "Failed", null), new CheckboxListItemViewModel("Downloaded", "Downloaded", null), + new CheckboxListItemViewModel("Viewed", "Viewed", null), + new CheckboxListItemViewModel("Launched", "Launched", null), }; return checkboxes; } diff --git a/LearningHub.Nhs.WebUI/Models/Resource/EsrLinkViewModel.cs b/LearningHub.Nhs.WebUI/Models/Resource/EsrLinkViewModel.cs deleted file mode 100644 index 4af72e435..000000000 --- a/LearningHub.Nhs.WebUI/Models/Resource/EsrLinkViewModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace LearningHub.Nhs.WebUI.Models.Resource -{ - using LearningHub.Nhs.Models.Resource; - - /// - /// Defines the . - /// - public class EsrLinkViewModel - { - /// - /// Gets or sets the ScormContentDetails. - /// - public ScormContentDetailsViewModel ScormContentDetails { get; set; } - - /// - /// Gets or sets the ReturnUrl. - /// - public string ReturnUrl { get; set; } - } -} diff --git a/LearningHub.Nhs.WebUI/Models/RestrictedAccessBannerViewModel.cs b/LearningHub.Nhs.WebUI/Models/RestrictedAccessBannerViewModel.cs index 59176251f..d229a1a88 100644 --- a/LearningHub.Nhs.WebUI/Models/RestrictedAccessBannerViewModel.cs +++ b/LearningHub.Nhs.WebUI/Models/RestrictedAccessBannerViewModel.cs @@ -1,9 +1,11 @@ namespace LearningHub.Nhs.WebUI.Models { + using System.Collections.Generic; using LearningHub.Nhs.Models.Catalogue; using LearningHub.Nhs.Models.Entities.Hierarchy; + using LearningHub.Nhs.Models.User; - /// + /// /// Defines the . /// public class RestrictedAccessBannerViewModel @@ -37,5 +39,10 @@ public class RestrictedAccessBannerViewModel /// Gets or sets the CatalogueAccessRequest if there is one. /// public CatalogueAccessRequestViewModel CatalogueAccessRequest { get; set; } + + /// + /// Gets or sets the current user's usergroups. + /// + public List UserGroups { get; set; } } } diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/activity.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/activity.ts index bedfd27c5..bc5a08bbb 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/activity.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/activity.ts @@ -1,13 +1,12 @@ import AxiosWrapper from './axiosWrapper'; -import { ActivityStatus, MediaResourceActivityType } from './constants'; +import { ActivityStatus, MediaResourceActivityType, ResourceType } from './constants'; import { LearningHubValidationResultModel } from './models/learningHubValidationResultModel'; import 'navigator.sendbeacon'; import { AssessmentModel } from './models/contribute-resource/assessmentModel'; -import { QuestionBlockModel } from "./models/contribute-resource/blocks/questionBlockModel"; -import { BlockCollectionModel } from "./models/contribute-resource/blocks/blockCollectionModel"; import { MatchQuestionState } from "./models/mylearning/matchQuestionState"; const recordActivityLaunched = async function ( + resourceType: ResourceType, resourceVersionId: number, nodePathId: number, activityDatetime: Date, @@ -16,7 +15,8 @@ const recordActivityLaunched = async function ( var data = { resourceVersionId: resourceVersionId, nodePathId: nodePathId, - activityStatus: ActivityStatus.Launched, + activityStatus: (resourceType == ResourceType.ASSESSMENT || resourceType == ResourceType.VIDEO || resourceType == ResourceType.AUDIO || + resourceType == ResourceType.SCORM) ? ActivityStatus.Incomplete : ActivityStatus.Completed, activityStart: activityDatetime, extraAttemptReason }; @@ -37,32 +37,6 @@ const recordActivityLaunched = async function ( }); }; - -const recordActivityDownloaded = async function ( - resourceVersionId: number, - nodePathId: number, - activityDatetime: Date): Promise { - - var data = { - resourceVersionId: resourceVersionId, - nodePathId: nodePathId, - activityStatus: ActivityStatus.Downloaded, - activityStart: activityDatetime - }; - - return await AxiosWrapper.axios.post('/api/activity/CreateResourceActivity', data) - .then(response => { - if (!response.data.isValid) { - window.location.pathname = './Home/Error'; - } - return response.data; - }) - .catch(e => { - console.log('recordActivityDownloaded:' + e); - throw e; - }); -}; - const recordActivity = async function ( resourceVersionId: number, nodePathId: number, @@ -243,7 +217,6 @@ const recordAssessmentResourceActivityInteraction = async function ( export const activityRecorder = { recordActivityLaunched, - recordActivityDownloaded, recordActivity, recordMediaResourceActivity, recordMediaResourceActivityInteraction, diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/constants.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/constants.ts index 10a8d4486..4f3a44c0a 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/constants.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/constants.ts @@ -60,7 +60,8 @@ export enum ActivityStatus { Completed = 3, Failed = 4, Passed = 5, - Downloaded = 6 + Downloaded = 6, + Incomplete = 7 }; export enum AccessRequestStatus { diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/SelectResourceType.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/SelectResourceType.vue index 4fb3f6621..d43ed711a 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/SelectResourceType.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute-resource/SelectResourceType.vue @@ -6,19 +6,23 @@

{{title}}

{{description}}
-
-
- +
+
+
+
+ +
diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributeState.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributeState.ts index c75681e0e..89b5e0d27 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributeState.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributeState.ts @@ -55,7 +55,10 @@ export class State { counterInterval: any = undefined; hierarchyEdit: HierarchyEditModel = null; hierarchyEditLoaded: boolean = false; - userProviders : ProviderModel[] = null; + userProviders: ProviderModel[] = null; + contributeAVResourceFlag: boolean; + learnAVResourceFlag: boolean; + getAVUnavailableView: string = ''; get previousVersionExists(): boolean { if (this.resourceDetail.currentResourceVersionId) { @@ -242,6 +245,15 @@ const mutations = { async populateContributeSettings(state: State) { state.contributeSettings = await resourceData.getContributeSettings(); }, + async populateContributeAVResourceFlag(state: State) { + state.contributeAVResourceFlag = await resourceData.getContributeAVResourceFlag(); + }, + async populateDisplayAVResourceFlag(state: State) { + state.learnAVResourceFlag = await resourceData.getDisplayAVResourceFlag(); + }, + async populateAVUnavailableView(state: State) { + state.getAVUnavailableView = await resourceData.getAVUnavailableView(); + }, async populateScormDetails(state: State, payload: number) { const scormDetail = await resourceData.getScormDetail(payload); state.scormDetail.canDownload = scormDetail.canDownload; diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributecontainer.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributecontainer.ts index bec5583e5..23259accb 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributecontainer.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/contribute/contributecontainer.ts @@ -23,6 +23,8 @@ new Vue({ }, created() { this.$store.commit('populateContributeSettings'); + this.$store.commit('populateContributeAVResourceFlag'); + this.$store.commit('populateAVUnavailableView'); if (this.$route.params.rvId) { this.$store.commit('populateResource', this.$route.params.rvId); } diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts index 13523c021..738b53ca2 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/data/resource.ts @@ -502,6 +502,38 @@ const getMyContributions = async function (resourceType: ResourceType, status: V }); }; +const getContributeAVResourceFlag = async function (): Promise { + return await AxiosWrapper.axios.get('/Resource/GetContributeAVResourceFlag') + .then(response => { + return response.data; + }) + .catch(e => { + console.log('GetContributeAVResourceFlag:' + e); + throw e; + }); +}; + +const getDisplayAVResourceFlag = async function (): Promise { + return await AxiosWrapper.axios.get('/Resource/GetDisplayAVResourceFlag') + .then(response => { + return response.data; + }) + .catch(e => { + console.log('GetDisplayAVResourceFlag:' + e); + throw e; + }); +}; + +const getAVUnavailableView = async function (): Promise { + return await AxiosWrapper.axios.get('/Resource/GetAVUnavailableView') + .then(response => { + return response.data; + }) + .catch(e => { + console.error('Error fetching shared partial view:', e) + throw e; + }); +}; export const resourceData = { getContributeConfiguration, @@ -541,4 +573,7 @@ export const resourceData = { getAssessmentDetail, duplicateBlocks, getMyContributions, + getContributeAVResourceFlag, + getDisplayAVResourceFlag, + getAVUnavailableView }; \ No newline at end of file diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/globalcomponents/IconButton.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/globalcomponents/IconButton.vue index 2ff2c9110..7e644a318 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/globalcomponents/IconButton.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/globalcomponents/IconButton.vue @@ -48,7 +48,8 @@ }" v-bind:disabled="disabled"> + v-bind:aria-label="ariaLabel" + v-bind:role="iconRole"> {{ label }} @@ -65,6 +66,7 @@ color: String, shape: String, size: String, + iconRole: String, }, }); diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/cmiVocabulary.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/cmiVocabulary.ts index 902bc1f01..f5123d287 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/cmiVocabulary.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/cmiVocabulary.ts @@ -18,7 +18,8 @@ Entry = 16, Interaction = 17, Result = 18, - TimeLimitAction = 19 + TimeLimitAction = 19, + CMIString64000 = 20 }; export enum CMIVocabularyMode { diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/scormApiModel.ts b/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/scormApiModel.ts index 362aa0acb..8555d67d9 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/scormApiModel.ts +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/models/learningsessions/scormApiModel.ts @@ -480,13 +480,13 @@ export class ScormApiModel { } } else if (paramName === "cmi.suspend_data") { - if (this.isValidateDataType(paramValue, CMIDataType.CMIString4096)) { + if (this.isValidateDataType(paramValue, CMIDataType.CMIString64000)) { this.sco.suspendData = paramValue; result = true; } } else if (paramName === "cmi.comments") { - if (this.isValidateDataType(paramValue, CMIDataType.CMIString4096)) { + if (this.isValidateDataType(paramValue, CMIDataType.CMIString64000)) { this.sco.comments = paramValue; result = true; } @@ -1019,6 +1019,11 @@ export class ScormApiModel { response = true; } break; + case CMIDataType.CMIString64000: + if (value.length < 64000) { + response = true; + } + break; case CMIDataType.CMITime: if (value.match(/^\d{2}:\d{2}:\d{2}(.\d{1,2})?$/)) { response = true; diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/CaseOrAssessmentResource.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/CaseOrAssessmentResource.vue index e53397927..e3f75c8d8 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/CaseOrAssessmentResource.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/CaseOrAssessmentResource.vue @@ -307,7 +307,7 @@ // Only make a new activity if the latest activity is finished if (typeof latest.userScore === 'number') { - const result = await activityRecorder.recordActivityLaunched(this.resourceItem.resourceVersionId, this.resourceItem.nodePathId, new Date(), reason); + const result = await activityRecorder.recordActivityLaunched(this.resourceItem.resourceTypeEnum, this.resourceItem.resourceVersionId, this.resourceItem.nodePathId, new Date(), reason); this.shuffleMatchQuestionsState(); await activityRecorder.recordAssessmentResourceActivity(result.createdId, this.matchQuestionsState, reason); } diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/ResourceContent.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/ResourceContent.vue index 909fd8396..bfc9598f5 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/ResourceContent.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/ResourceContent.vue @@ -205,17 +205,13 @@ methods: { initialise(): void { // record activity on page created for resource article - if (this.userAuthenticated && ( - this.resourceItem.resourceTypeEnum === ResourceType.ARTICLE - || this.resourceItem.resourceTypeEnum === ResourceType.IMAGE - || this.resourceItem.resourceTypeEnum === ResourceType.CASE) && this.hasResourceAccess()) { - + if (this.userAuthenticated && this.resourceItem.resourceTypeEnum === ResourceType.CASE) { this.recordActivityLaunched(); } - else if (this.userAuthenticated && this.resourceItem.resourceTypeEnum === ResourceType.ASSESSMENT && this.hasResourceAccess()) { + else if (this.userAuthenticated && this.resourceItem.resourceTypeEnum === ResourceType.ASSESSMENT) { this.getCurrentAssessmentActivity(); } - if (this.resourceItem.resourceTypeEnum === ResourceType.AUDIO || this.resourceItem.resourceTypeEnum === ResourceType.VIDEO) { + else if (this.resourceItem.resourceTypeEnum === ResourceType.AUDIO || this.resourceItem.resourceTypeEnum === ResourceType.VIDEO) { window.setTimeout(this.handleResize, 100); } }, @@ -252,8 +248,8 @@ if (!this.activityLogged) { this.activityLogged = true; - - await activityRecorder.recordActivityLaunched(this.resourceItem.resourceVersionId, this.resourceItem.nodePathId, new Date()) + await activityRecorder.recordActivityLaunched(this.resourceItem.resourceTypeEnum, this.resourceItem.resourceVersionId, this.resourceItem.nodePathId, new Date()) + // await activityRecorder.recordActivityLaunched(this.resourceItem.resourceVersionId, this.resourceItem.nodePathId, new Date()) .then(response => { this.launchedResourceActivityId = response.createdId; this.checkUserCertificateAvailability(); diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/QuestionBlock.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/QuestionBlock.vue index 6366ec7fe..20945e6f2 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/QuestionBlock.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/QuestionBlock.vue @@ -9,7 +9,8 @@ + :ariaLabel="isOpen ? `hide content` : `reveal content`" + iconRole="img">
diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/SingleQuestionAnswerView.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/SingleQuestionAnswerView.vue index 21925aa52..c325b1446 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/SingleQuestionAnswerView.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/resource/blocks/SingleQuestionAnswerView.vue @@ -17,7 +17,8 @@ :class="getStyleFromAnswerType(answer.status)"/> + src="/images/medal-icon.svg" + alt="Medal Icon"/> {{ answer.blockCollection.blocks[0].textBlock.content }}
diff --git a/LearningHub.Nhs.WebUI/Scripts/vuesrc/roadmap/roadmap.vue b/LearningHub.Nhs.WebUI/Scripts/vuesrc/roadmap/roadmap.vue index 54d0c5f75..21307a74e 100644 --- a/LearningHub.Nhs.WebUI/Scripts/vuesrc/roadmap/roadmap.vue +++ b/LearningHub.Nhs.WebUI/Scripts/vuesrc/roadmap/roadmap.vue @@ -1,7 +1,7 @@