Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
2d45b57
TD-5153 Implements page changes and starts to implement download actions
kevwhitt-hee Dec 19, 2024
ab16c75
Update FrameworkDataService.cs
kevwhitt-hee Dec 19, 2024
691ed40
TD-5153 Implements download competencies and templates functions and …
kevwhitt-hee Dec 20, 2024
f17218e
Update ImportCompleted.cshtml
kevwhitt-hee Dec 20, 2024
ea9ca79
Implements models for handling upload
kevwhitt-hee Dec 20, 2024
219ef3c
Formatter changes
kevwhitt-hee Dec 20, 2024
e84a5ed
Fixes form input id
kevwhitt-hee Dec 20, 2024
8a98bee
TD-5154 Implements basic pre-process logic and view and adds validati…
kevwhitt-hee Dec 23, 2024
bafd87a
TD-5154 Implements upload results page with validation errors
kevwhitt-hee Dec 23, 2024
445ecff
TD-5154 Adds always show description validation
kevwhitt-hee Dec 23, 2024
51fae04
Formatter changes
kevwhitt-hee Dec 23, 2024
f3c4b62
Fixes query to avoid reliance on new SQL 2017 STRING_AGG function
kevwhitt-hee Dec 23, 2024
219f274
TD-5216 Moves to import namespace and separates form data in viewmodel
kevwhitt-hee Dec 30, 2024
5443f3c
TD-5216 Load FALSE into AlwaysShowDescription field for downloaded te…
kevwhitt-hee Dec 30, 2024
6beba80
TD-5216 Adds competency vocabulary to views
kevwhitt-hee Dec 30, 2024
b914c50
TD-5216 Uses custom competency vocabulary in Excel file processing
kevwhitt-hee Dec 30, 2024
23b9570
IDE formatter changes
kevwhitt-hee Dec 30, 2024
7b7e2b9
TD-5155 Begins to implement add questions view model
kevwhitt-hee Dec 30, 2024
ee10005
TD-5155 Adds view model and constructor
kevwhitt-hee Dec 31, 2024
47543f7
TD-5155 Implements conditional preselected checkboxes for default que…
kevwhitt-hee Dec 31, 2024
549fbc8
TD-5155 Implements add assessment questions form view
kevwhitt-hee Jan 2, 2025
a33c4dc
Tweaks post data processing to handle storing false values
kevwhitt-hee Jan 2, 2025
e14e53e
TD-5158 Implements add questions to which competencies form
kevwhitt-hee Jan 2, 2025
b8c3b9c
Removes spurious usings
kevwhitt-hee Jan 2, 2025
d80826c
TD-5219 Improvements to pre-processing summary and errors
kevwhitt-hee Jan 3, 2025
cba6f1f
Adds missing @ to indicate inline code
kevwhitt-hee Jan 3, 2025
0ba85e5
TD-5220 Implements store competency ordering option form
kevwhitt-hee Jan 3, 2025
396297d
TD-5220 passes ids of uploaded competencies to get order of stored co…
kevwhitt-hee Jan 6, 2025
dcdc17b
TD-5220 Implements CancelImport method to clear down uploaded file an…
kevwhitt-hee Jan 6, 2025
d85bdbd
TD-5162 Implements import summary screen
kevwhitt-hee Jan 6, 2025
c3feb48
formatter changes
kevwhitt-hee Jan 6, 2025
5da1bdd
TD-5163 Process imported competencies
kevwhitt-hee Jan 7, 2025
91593d1
TD-5163 Implements the upload results view
kevwhitt-hee Jan 7, 2025
ea6d9ff
TD-5163 Fixes flag assignment during upload processing
kevwhitt-hee Jan 8, 2025
cd4e4db
TD-5163 General tweaks to improve wording of buttons and body text
kevwhitt-hee Jan 8, 2025
2d009b5
TD-5163 Corrects heading
kevwhitt-hee Jan 8, 2025
d956c16
TD-5163 modifies the insert competency group method to take an option…
kevwhitt-hee Jan 8, 2025
a47d2ca
TD-5163 Clears down the uploaded sheet after processing
kevwhitt-hee Jan 8, 2025
caa020c
TD-5163 Commits competency order changes if required
kevwhitt-hee Jan 9, 2025
daa9561
TD-5163 Reorders competency groups if needed
kevwhitt-hee Jan 10, 2025
3f3e2b4
TD-5163 Fixes navigation and titles
kevwhitt-hee Jan 10, 2025
65932d9
TD-5163 Fixes competency group reordering by reloading the committed …
kevwhitt-hee Jan 10, 2025
a73f76a
Applies BR DLSFW1 to standard insert competency group functionality
kevwhitt-hee Jan 10, 2025
4a65bac
Adds published framework warning to summary page
kevwhitt-hee Jan 10, 2025
58356fb
TD-5233 Add a limitations paragraph to the start page
kevwhitt-hee Jan 17, 2025
9151e7d
Emphasises cannot
kevwhitt-hee Jan 17, 2025
7e3fb52
Moves single line inset text to view component
kevwhitt-hee Jan 17, 2025
32aeea1
TD-5233 Indicates the number of competencies reordered in the upload …
kevwhitt-hee Jan 17, 2025
59a91c3
Update ApplyCompetencyOrdering.cshtml
kevwhitt-hee Jan 17, 2025
1ae3863
TD-5233 added flags to the list of things that can't be removed and u…
kevwhitt-hee Jan 17, 2025
bf2b2af
Formatting changes
kevwhitt-hee Jan 17, 2025
ad4b3b0
Merge pull request #3065 from TechnologyEnhancedLearning/Develop/Feat…
kevwhitt-hee Jan 17, 2025
54f6fbe
Sets up controller, view and navigation for print layout
kevwhitt-hee Jan 20, 2025
968c78b
TD-5263 Implements "view for print" option for frameworks
kevwhitt-hee Jan 21, 2025
cb20049
Merge pull request #3070 from TechnologyEnhancedLearning/Develop/Feat…
kevwhitt-hee Jan 21, 2025
92ebb5f
TD-5138_ Staff count was incorrect on the dashboard and doesn't match…
ABSinhaa Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 114 additions & 45 deletions DigitalLearningSolutions.Data/DataServices/FrameworkDataService.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,35 @@ public SupervisorDataService(IDbConnection connection, ILogger<SupervisorDataSer
public DashboardData? GetDashboardDataForAdminId(int adminId)
{
return connection.Query<DashboardData>(
@" SELECT (SELECT COUNT(sd.ID) AS StaffCount
FROM SupervisorDelegates sd
LEFT OUTER JOIN users u
ON u.id = sd.DelegateUserID
AND u.Active = 1
WHERE (sd.SupervisorAdminID = @adminId)
AND (sd.Removed IS NULL)) AS StaffCount,
@" SELECT (SELECT COUNT(sd.ID) AS StaffCount
FROM CustomPrompts AS cp6
RIGHT OUTER JOIN CustomPrompts AS cp5
RIGHT OUTER JOIN DelegateAccounts AS da
RIGHT OUTER JOIN SupervisorDelegates AS sd
INNER JOIN AdminUsers AS au
ON sd.SupervisorAdminID = au.AdminID
INNER JOIN Centres AS ct
ON au.CentreID = ct.CentreID
ON da.CentreID = ct.CentreID
AND da.UserID = sd.DelegateUserID
LEFT OUTER JOIN Users AS u
LEFT OUTER JOIN JobGroups AS jg
ON u.JobGroupID = jg.JobGroupID
ON da.UserID = u.ID
LEFT OUTER JOIN CustomPrompts AS cp1
ON ct.CustomField1PromptID = cp1.CustomPromptID
LEFT OUTER JOIN CustomPrompts AS cp2
ON ct.CustomField2PromptID = cp2.CustomPromptID
LEFT OUTER JOIN CustomPrompts AS cp3
ON ct.CustomField3PromptID = cp3.CustomPromptID
LEFT OUTER JOIN CustomPrompts AS cp4
ON ct.CustomField4PromptID = cp4.CustomPromptID
ON cp5.CustomPromptID = ct.CustomField5PromptID
ON cp6.CustomPromptID = ct.CustomField6PromptID
LEFT OUTER JOIN AdminAccounts AS au2
ON da.UserID = au2.UserID AND da.CentreID = au2.CentreID
WHERE (sd.SupervisorAdminID = @adminId) AND (sd.Removed IS NULL) AND
(u.ID = da.UserID OR sd.DelegateUserID IS NULL)) AS StaffCount,
(SELECT COUNT(ID) AS StaffCount
FROM SupervisorDelegates AS SupervisorDelegates_1
WHERE (SupervisorAdminID = @adminId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class FrameworkCompetency : BaseSearchableItem
public int AssessmentQuestions { get; set; }
public int CompetencyLearningResourcesCount { get; set; }
public string? FrameworkName { get; set; }
public bool? AlwaysShowDescription { get; set; }

public override string SearchableName
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DigitalLearningSolutions.Data.Models.Frameworks.Import
{
public class BulkCompetency
{
public int? ID { get; set; }
public string? CompetencyGroup { get; set; }
public string? GroupDescription { get; set; }
public string? Competency { get; set; }
public string? CompetencyDescription { get; set; }
public bool? AlwaysShowDescription { get; set; }
public string? FlagsCsv { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,69 @@
namespace DigitalLearningSolutions.Data.Models.Frameworks.Import
{
using ClosedXML.Excel;
using System;
using System.Collections.Generic;
using System.Text;

public enum RowStatus
{
NotYetProcessed,
Skipped,
CompetencyGroupInserted,
CompetencyGroupAndCompetencyInserted,
CompetencyInserted
CompetencyInserted,
CompetencyUpdated,
CompetencyGroupInserted,
CompetencyGroupUpdated,
CompetencyGroupAndCompetencyUpdated,
InvalidAlwaysShowDescription,
InvalidId
}
public class CompetencyTableRow
public class CompetencyTableRow : BulkCompetency
{
public CompetencyTableRow(IXLTable table, IXLRangeRow row)
{
string? FindFieldValue(string name)
{
var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name).ColumnNumber();
var colNumber = table.FindColumn(col => col.FirstCell().Value.ToString()?.ToLower() == name.ToLower()).ColumnNumber();
return row.Cell(colNumber).GetValue<string?>();
}

RowNumber = row.RowNumber();
CompetencyGroupName = FindFieldValue("competency group");
CompetencyName = FindFieldValue("competency name");
CompetencyDescription = FindFieldValue("competency description");
ID = row.Cell(1).GetValue<int?>();
CompetencyGroup = row.Cell(2).GetValue<string?>();
GroupDescription = row.Cell(3).GetValue<string?>();
Competency = row.Cell(4).GetValue<string?>();
CompetencyDescription = row.Cell(5).GetValue<string?>();
AlwaysShowDescriptionRaw = FindFieldValue("AlwaysShowDescription");
AlwaysShowDescription = bool.TryParse(AlwaysShowDescriptionRaw, out var hasPrn) ? hasPrn : (bool?)null;
FlagsCsv = FindFieldValue("FlagsCSV");
RowStatus = RowStatus.NotYetProcessed;
}
public int RowNumber { get; set; }
public string? CompetencyGroupName { get; set; }
public string? CompetencyName { get; set; }
public string? CompetencyDescription { get; set; }
public int CompetencyOrderNumber { get; set; }
public string? AlwaysShowDescriptionRaw { get; set; }
public ImportCompetenciesResult.ErrorReason? Error { get; set; }
public RowStatus RowStatus { get; set; }
public bool Reordered { get; set; } = false;
public bool Validate()
{
if (string.IsNullOrEmpty(CompetencyName))
if (string.IsNullOrEmpty(Competency))
{
Error = ImportCompetenciesResult.ErrorReason.MissingCompetencyName;
}
else if (CompetencyGroupName?.Length > 255)
else if (CompetencyGroup?.Length > 255)
{
Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyGroupName;
}
else if (CompetencyName.Length > 500)
else if (Competency.Length > 500)
{
Error = ImportCompetenciesResult.ErrorReason.TooLongCompetencyName;
}
else if (!string.IsNullOrWhiteSpace(AlwaysShowDescriptionRaw) && !bool.TryParse(AlwaysShowDescriptionRaw, out _))
{
Error = ImportCompetenciesResult.ErrorReason.InvalidAlwaysShowDescription;
}
else if (RowStatus == RowStatus.InvalidId)
{
Error = ImportCompetenciesResult.ErrorReason.InvalidId;
}

return !Error.HasValue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ public class ImportCompetenciesResult
{
public enum ErrorReason
{
InvalidId,
MissingCompetencyName,
TooLongCompetencyGroupName,
TooLongCompetencyName,
AlreadyExists
TooLongCompetencyGroupName,
InvalidAlwaysShowDescription
}
public ImportCompetenciesResult() { }

Expand All @@ -20,16 +21,39 @@ IReadOnlyCollection<CompetencyTableRow> competencyTableRows
)
{
ProcessedCount = competencyTableRows.Count;
CompetenciesInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
CompetencyGroupsInsertedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped);
CompetencyAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
CompetencyUpdatedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyUpdated | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyUpdated);
GroupAddedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.CompetencyGroupInserted | dr.RowStatus == RowStatus.CompetencyGroupAndCompetencyInserted);
SkippedCount = competencyTableRows.Count(dr => dr.RowStatus == RowStatus.Skipped && dr.Reordered == false);
CompetencyReorderedCount = competencyTableRows.Count(dr => dr.Reordered == true);
Errors = competencyTableRows.Where(dr => dr.Error.HasValue).Select(dr => (dr.RowNumber, dr.Error!.Value));
FlagCount = competencyTableRows
.Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv))
.SelectMany(static row => row.FlagsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries))
.Count();
DistinctFlagsCount = competencyTableRows
.Where(row => !string.IsNullOrWhiteSpace(row.FlagsCsv))
.SelectMany(row => row.FlagsCsv.Split(',', StringSplitOptions.RemoveEmptyEntries))
.Select(flag => flag.Trim())
.Distinct()
.Count();
CompetencyGroupCount = competencyTableRows
.Where(row => !string.IsNullOrWhiteSpace(row.CompetencyGroup))
.Select(static row => row.CompetencyGroup)
.Distinct()
.Count();
}

public IEnumerable<(int RowNumber, ErrorReason Reason)>? Errors { get; set; }
public int ProcessedCount { get; set; }
public int CompetenciesInsertedCount { get; set; }
public int CompetencyGroupsInsertedCount { get; set; }
public int CompetencyAddedCount { get; set; }
public int CompetencyUpdatedCount { get; set; }
public int CompetencyReorderedCount { get; set; }
public int GroupAddedCount { get; set; }
public int GroupUpdatedCount { get; set; }
public int SkippedCount { get; set; }
public int FlagCount { get; set; }
public int DistinctFlagsCount { get; set; }
public int CompetencyGroupCount { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
using Microsoft.Extensions.Logging;
using NUnit.Framework;
using GDS.MultiPageFormData;
using DigitalLearningSolutions.Data.Utilities;
using Microsoft.AspNetCore.Hosting;

public partial class FrameworkControllerTests
{
Expand All @@ -28,6 +30,8 @@ public partial class FrameworkControllerTests
private ILearningHubApiClient learningHubApiClient = null!;
private ISearchSortFilterPaginateService searchSortFilterPaginateService = null!;
private IMultiPageFormService multiPageFormService = null!;
private IClockUtility clockUtility = null!;
private IWebHostEnvironment webHostEnvironment = null!;

[SetUp]
public void SetUp()
Expand All @@ -42,7 +46,8 @@ public void SetUp()
learningHubApiClient = A.Fake<ILearningHubApiClient>();
searchSortFilterPaginateService = A.Fake<ISearchSortFilterPaginateService>();
multiPageFormService = A.Fake<IMultiPageFormService>();

clockUtility = A.Fake<ClockUtility>();
webHostEnvironment = A.Fake<IWebHostEnvironment>();
A.CallTo(() => config["CurrentSystemBaseUrl"]).Returns(BaseUrl);

var user = new ClaimsPrincipal(
Expand All @@ -65,7 +70,9 @@ public void SetUp()
competencyLearningResourcesService,
learningHubApiClient,
searchSortFilterPaginateService,
multiPageFormService
multiPageFormService,
clockUtility,
webHostEnvironment
)
{
ControllerContext = new ControllerContext { HttpContext = new DefaultHttpContext { User = user } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
using NUnit.Framework;
using GDS.MultiPageFormData;
using GDS.MultiPageFormData.Enums;
using DigitalLearningSolutions.Data.Utilities;
using Microsoft.AspNetCore.Hosting;

public class RedirectToErrorEmptySessionDataTests
{
Expand All @@ -41,7 +43,9 @@ public void Setup()
A.Fake<ICompetencyLearningResourcesService>(),
A.Fake<ILearningHubApiClient>(),
A.Fake<ISearchSortFilterPaginateService>(),
A.Fake<IMultiPageFormService>()
A.Fake<IMultiPageFormService>(),
A.Fake<IClockUtility>(),
A.Fake<IWebHostEnvironment>()
)
.WithDefaultContext().WithMockTempData();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public IActionResult AddEditFrameworkCompetencyGroup(int frameworkId, Competency
(competencyGroupBase.Description), adminId);
return new RedirectResult(Url.Action("ViewFramework", new { tabname = "Structure", frameworkId }) + "#fcgroup-" + frameworkCompetencyGroupId.ToString());
}
var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData(competencyGroupBase.Description), adminId);
var newCompetencyGroupId = frameworkService.InsertCompetencyGroup(competencyGroupBase.Name, SanitizerHelper.SanitizeHtmlData(competencyGroupBase.Description), adminId, frameworkId);
if (newCompetencyGroupId > 0)
{
var newFrameworkCompetencyGroupId = frameworkService.InsertFrameworkCompetencyGroup(newCompetencyGroupId, frameworkId, adminId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,20 @@ public IActionResult ViewFramework(string tabname, int frameworkId, int? framewo
return View("Developer/Framework", model);
}

[Route("/Framework/{frameworkId}/Structure/PrintLayout")]
public IActionResult PrintLayout(int frameworkId) {
var adminId = GetAdminId();
var detailFramework = frameworkService.GetFrameworkDetailByFrameworkId(frameworkId, adminId);
var routeData = new Dictionary<string, string> { { "frameworkId", detailFramework?.ID.ToString() } };
var model = new FrameworkViewModel()
{
DetailFramework = detailFramework,
};
model.FrameworkCompetencyGroups = frameworkService.GetFrameworkCompetencyGroups(frameworkId).ToList();
model.CompetencyFlags = frameworkService.GetCompetencyFlagsByFrameworkId(frameworkId, null, selected: true);
model.FrameworkCompetencies = frameworkService.GetFrameworkCompetenciesUngrouped(frameworkId);
return View("Developer/FrameworkPrintLayout", model);
}
[ResponseCache(CacheProfileName = "Never")]
public IActionResult InsertFramework()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using GDS.MultiPageFormData;
using DigitalLearningSolutions.Data.Utilities;
using Microsoft.AspNetCore.Hosting;

[Authorize(Policy = CustomPolicies.UserFrameworksAdminOnly)]
[SetDlsSubApplication(nameof(DlsSubApplication.Frameworks))]
Expand All @@ -25,7 +27,8 @@ public partial class FrameworksController : Controller
private readonly ILearningHubApiClient learningHubApiClient;
private readonly ISearchSortFilterPaginateService searchSortFilterPaginateService;
private readonly IMultiPageFormService multiPageFormService;

private readonly IClockUtility clockUtility;
private readonly IWebHostEnvironment webHostEnvironment;
public FrameworksController(
IFrameworkService frameworkService,
ICommonService commonService,
Expand All @@ -35,7 +38,9 @@ public FrameworksController(
ICompetencyLearningResourcesService competencyLearningResourcesService,
ILearningHubApiClient learningHubApiClient,
ISearchSortFilterPaginateService searchSortFilterPaginateService,
IMultiPageFormService multiPageFormService
IMultiPageFormService multiPageFormService,
IClockUtility clockUtility,
IWebHostEnvironment webHostEnvironment
)
{
this.frameworkService = frameworkService;
Expand All @@ -47,6 +52,8 @@ IMultiPageFormService multiPageFormService
this.learningHubApiClient = learningHubApiClient;
this.searchSortFilterPaginateService = searchSortFilterPaginateService;
this.multiPageFormService = multiPageFormService;
this.clockUtility = clockUtility;
this.webHostEnvironment = webHostEnvironment;
}

private int? GetCentreId()
Expand Down
Loading
Loading