Skip to content

Commit 28dd9ca

Browse files
authored
Merge pull request #3423 from TechnologyEnhancedLearning/Develop/feature/TD-5402-Configure-navigation-options-and-labels
TD-5402-New feature 'Configure navigation options and labels' added
2 parents e7bd292 + ff9a159 commit 28dd9ca

File tree

6 files changed

+823
-5
lines changed

6 files changed

+823
-5
lines changed

DigitalLearningSolutions.Data/DataServices/CompetencyAssessmentDataService.cs

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ int categoryId
5959
bool UpdateOptionalCompetenciesTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
6060
bool UpdateRoleRequirementsTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
6161
bool UpdateWorkingGroupTaskStatus(int assessmentId, bool taskStatus, bool? previousStatus);
62+
bool UpdateCompetencyAssessmentOptions(
63+
bool includeLearnerDeclarationPrompt,
64+
bool includesSignposting,
65+
bool linearNavigation,
66+
bool useDescriptionExpanders,
67+
string? questionLabelText,
68+
string? reviewerCommentsLabelText,
69+
int competencyAssessmentId, int adminId);
70+
bool UpdateCompetencyAssessmentOptionsTaskStatus(int assessmentId, bool taskStatus);
6271
void MoveCompetencyInSelfAssessment(int competencyAssessmentId,
6372
int competencyId,
6473
string direction
@@ -84,6 +93,7 @@ public bool UpdateCompetencyAssessmentFeaturesTaskStatus(int id, bool descriptio
8493
int AddCollaboratorToCompetencyAssessment(int competencyAssessmentId, string? userEmail, bool canModify, int? centreID);
8594
void RemoveCollaboratorFromCompetencyAssessment(int competencyAssessmentId, int id);
8695
CompetencyAssessmentCollaboratorNotification? GetCollaboratorNotification(int id, int invitedByAdminId);
96+
bool HasCompetencyWithSignpostedLearning(int competencyAssessmentId);
8797
}
8898

8999
public class CompetencyAssessmentDataService : ICompetencyAssessmentDataService
@@ -92,9 +102,11 @@ public class CompetencyAssessmentDataService : ICompetencyAssessmentDataService
92102
sa.ParentSelfAssessmentID,
93103
sa.[National], sa.[Public], sa.CreatedByAdminID AS OwnerAdminID,
94104
sa.NRPProfessionalGroupID,
95-
sa.NRPSubGroupID,
96-
sa.NRPRoleID,
97-
sa.PublishStatusID, sa.Vocabulary, CASE WHEN sa.CreatedByAdminID = @adminId THEN 3 WHEN sac.CanModify = 1 THEN 2 WHEN sac.CanModify = 0 THEN 1 ELSE 0 END AS UserRole";
105+
sa.NRPSubGroupID,
106+
sa.NRPRoleID,
107+
sa.PublishStatusID, sa.Vocabulary, CASE WHEN sa.CreatedByAdminID = @adminId THEN 3 WHEN sac.CanModify = 1 THEN 2 WHEN sac.CanModify = 0 THEN 1 ELSE 0 END AS UserRole,
108+
sa.IncludeLearnerDeclarationPrompt, sa.IncludesSignposting, sa.LinearNavigation, sa.UseDescriptionExpanders, sa.QuestionLabel, sa.ReviewerCommentsLabel,
109+
sa.SupervisorSelfAssessmentReview, sa.SupervisorResultsReview ";
98110

99111
private const string SelfAssessmentFields =
100112
@", sa.CategoryID, sa.CreatedDate,
@@ -1057,5 +1069,84 @@ FROM SelfAssessmentCollaborators AS sc
10571069
new { invitedByAdminId, id }
10581070
).FirstOrDefault();
10591071
}
1072+
1073+
public bool HasCompetencyWithSignpostedLearning(int competencyAssessmentId)
1074+
{
1075+
int hasSignpostedLearning = connection.QueryFirstOrDefault<int>(
1076+
@"SELECT count(*) FROM SelfAssessmentStructure sas INNER JOIN
1077+
CompetencyLearningResources clr ON sas.CompetencyID = clr.CompetencyID AND
1078+
clr.RemovedDate IS NULL
1079+
WHERE sas.SelfAssessmentID = @competencyAssessmentId",
1080+
new { competencyAssessmentId });
1081+
1082+
return hasSignpostedLearning > 0;
1083+
}
1084+
public bool UpdateCompetencyAssessmentOptions(
1085+
bool includeLearnerDeclarationPrompt,
1086+
bool includesSignposting,
1087+
bool linearNavigation,
1088+
bool useDescriptionExpanders,
1089+
string? questionLabelText,
1090+
string? reviewerCommentsLabelText,
1091+
int competencyAssessmentId, int adminId)
1092+
{
1093+
if ((adminId < 1) | (competencyAssessmentId < 1))
1094+
{
1095+
logger.LogWarning(
1096+
$"Not updating role profile name as it failed server side validation. AdminId: {adminId}, competencyAssessmentId: {competencyAssessmentId}"
1097+
);
1098+
return false;
1099+
}
1100+
1101+
var numberOfAffectedRows = connection.Execute(
1102+
@"UPDATE SelfAssessments SET IncludeLearnerDeclarationPrompt = @includeLearnerDeclarationPrompt,
1103+
IncludesSignposting = @includesSignposting,
1104+
LinearNavigation = @linearNavigation,
1105+
UseDescriptionExpanders = @useDescriptionExpanders,
1106+
QuestionLabel = @questionLabelText,
1107+
ReviewerCommentsLabel = @reviewerCommentsLabelText,
1108+
UpdatedByAdminID = @adminId
1109+
WHERE ID = @competencyAssessmentId ",
1110+
new
1111+
{
1112+
includeLearnerDeclarationPrompt,
1113+
includesSignposting,
1114+
linearNavigation,
1115+
useDescriptionExpanders,
1116+
questionLabelText,
1117+
reviewerCommentsLabelText,
1118+
adminId,
1119+
competencyAssessmentId
1120+
}
1121+
);
1122+
if (numberOfAffectedRows < 1)
1123+
{
1124+
logger.LogWarning(
1125+
"Not updating options/labels as db update failed. " +
1126+
$"admin id: {adminId}, competencyAssessmentId: {competencyAssessmentId}"
1127+
);
1128+
return false;
1129+
}
1130+
1131+
return true;
1132+
}
1133+
1134+
public bool UpdateCompetencyAssessmentOptionsTaskStatus(int assessmentId, bool taskStatus)
1135+
{
1136+
var numberOfAffectedRows = connection.Execute(
1137+
@"UPDATE SelfAssessmentTaskStatus SET SelfAssessmentOptionsTaskStatus = @taskStatus
1138+
WHERE SelfAssessmentId = @assessmentId",
1139+
new { assessmentId, taskStatus }
1140+
);
1141+
if (numberOfAffectedRows < 1)
1142+
{
1143+
logger.LogWarning(
1144+
"Not updating SelfAssessmentOptionsTaskStatus as db update failed. " +
1145+
$"assessmentId: {assessmentId}, taskStatus: {taskStatus}"
1146+
);
1147+
return false;
1148+
}
1149+
return true;
1150+
}
10601151
}
10611152
}

DigitalLearningSolutions.Data/Models/CompetencyAssessments/CompetencyAssessmentBase.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,14 @@ public class CompetencyAssessmentBase
2020
public int PublishStatusID { get; set; }
2121
public int UserRole { get; set; }
2222
public string? Vocabulary { get; set; }
23-
23+
public bool IncludeLearnerDeclarationPrompt { get; set; }
24+
public bool IncludesSignposting { get; set; }
25+
public bool LinearNavigation { get; set; }
26+
public bool UseDescriptionExpanders { get; set; }
27+
public string? QuestionLabel { get; set; }
28+
public string? ReviewerCommentsLabel { get; set; }
29+
public bool SupervisorSelfAssessmentReview { get; set; }
30+
public bool SupervisorResultsReview { get; set; }
2431
}
2532
}
2633

DigitalLearningSolutions.Web/Controllers/CompetencyAssessmentsController/CompetencyAssessments.cs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,165 @@ public IActionResult AssessmentWorkingGroup(WorkingGroupCollaboratorsViewModel m
809809
}
810810
}
811811

812+
[Route("/CompetencyAssessments/{competencyAssessmentId}/ConfigureOptions")]
813+
public IActionResult ConfigureOptions(int competencyAssessmentId)
814+
{
815+
var data = new OptionsLabelsViewModel();
816+
var adminId = GetAdminID();
817+
var result = ValidateCompetencyAssessmentAndRole(competencyAssessmentId, adminId, "competency assessment options");
818+
if (result.StatusCode != 200)
819+
return result;
820+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
821+
822+
data.CompetencyAssessmentID = competencyAssessmentId;
823+
data.Vocabulary = competencyAssessmentBase.Vocabulary;
824+
data.IncludeLearnerDeclarationPrompt = competencyAssessmentBase.IncludeLearnerDeclarationPrompt;
825+
data.IncludesSignposting = competencyAssessmentBase.IncludesSignposting;
826+
data.LinearNavigation = competencyAssessmentBase.LinearNavigation;
827+
data.UseDescriptionExpanders = competencyAssessmentBase.UseDescriptionExpanders;
828+
data.QuestionLabel = string.IsNullOrWhiteSpace(competencyAssessmentBase.QuestionLabel) ? false : true;
829+
data.QuestionLabelText = competencyAssessmentBase.QuestionLabel?.Trim();
830+
data.ReviewerCommentsLabel = string.IsNullOrWhiteSpace(competencyAssessmentBase.ReviewerCommentsLabel) ? false : true;
831+
data.ReviewerCommentsLabelText = competencyAssessmentBase.ReviewerCommentsLabel?.Trim();
832+
data.IsSupervisionSwitchedOn = competencyAssessmentBase.SupervisorSelfAssessmentReview && competencyAssessmentBase.SupervisorResultsReview;
833+
data.IsSignpostedLearning = competencyAssessmentService.HasCompetencyWithSignpostedLearning(competencyAssessmentId);
834+
835+
var taskStatus = competencyAssessmentService.GetCompetencyAssessmentTaskStatus(competencyAssessmentId, null);
836+
data.SelfAssessmentOptionsTaskStatus = taskStatus.SelfAssessmentOptionsTaskStatus;
837+
838+
SetOptionsLabelsData(data);
839+
840+
var step = (int)OptionLabel.Declaration;
841+
if (taskStatus.SelfAssessmentOptionsTaskStatus != null)
842+
step = (int)OptionLabel.Summary;
843+
844+
ValidateStep(data, ref step);
845+
return RedirectToAction("OptionsLabels", "CompetencyAssessments", new { competencyAssessmentId, step });
846+
}
847+
848+
[Route("/CompetencyAssessments/{competencyAssessmentId}/OptionsLabels/{step}")]
849+
public IActionResult OptionsLabels(int competencyAssessmentId, int step)
850+
{
851+
if (step < (int)OptionLabel.Declaration || step > (int)OptionLabel.Summary)
852+
return StatusCode(500);
853+
854+
var adminId = GetAdminID();
855+
var result = ValidateCompetencyAssessmentAndRole(competencyAssessmentId, adminId, "competency assessment options");
856+
if (result.StatusCode != 200)
857+
return result;
858+
859+
var data = GetOptionsLabelslData();
860+
861+
if (ValidateStep(data, ref step))
862+
{
863+
return RedirectToAction("OptionsLabels", "CompetencyAssessments", new { competencyAssessmentId, step });
864+
}
865+
866+
data.CurrentStep = step;
867+
var model = new OptionsLabelsViewModel(data);
868+
869+
return View("CompetencyAssessmentOptions", model);
870+
}
871+
872+
[HttpPost]
873+
[Route("/CompetencyAssessments/{competencyAssessmentId}/OptionsLabels/{step}")]
874+
public IActionResult OptionsLabels(OptionsLabelsViewModel model)
875+
{
876+
var adminId = GetAdminID();
877+
var data = GetOptionsLabelslData();
878+
data.CurrentStep = model.CurrentStep;
879+
ModelState.Remove("VocabularySingular");
880+
ModelState.Remove("VocabularyPlural");
881+
882+
if (model.CurrentStep == (int)OptionLabel.Declaration)
883+
{
884+
data.IncludeLearnerDeclarationPrompt = model.IncludeLearnerDeclarationPrompt;
885+
}
886+
else if (model.CurrentStep == (int)OptionLabel.Signposting)
887+
{
888+
data.IncludesSignposting = model.IncludesSignposting;
889+
}
890+
else if (model.CurrentStep == (int)OptionLabel.LinearNavigation)
891+
{
892+
data.LinearNavigation = model.LinearNavigation;
893+
}
894+
else if (model.CurrentStep == (int)OptionLabel.DescriptionExpanders)
895+
{
896+
data.UseDescriptionExpanders = model.UseDescriptionExpanders;
897+
}
898+
else if (model.CurrentStep == (int)OptionLabel.QuestionLabels)
899+
{
900+
data.QuestionLabel = model.QuestionLabel;
901+
if (model.QuestionLabel)
902+
{
903+
var label = model.QuestionLabelText?.Trim();
904+
if (string.IsNullOrEmpty(label))
905+
ModelState.AddModelError(nameof(model.QuestionLabelText), "Please enter a question label");
906+
else if (label.Length > 50)
907+
ModelState.AddModelError(nameof(model.QuestionLabelText), "Question label must be 50 characters or fewer");
908+
909+
if (!ModelState.IsValid)
910+
{
911+
SetOptionsLabelsData(data);
912+
model = new OptionsLabelsViewModel(data);
913+
model.Error = true;
914+
return View("CompetencyAssessmentOptions", model);
915+
}
916+
}
917+
data.QuestionLabelText = model.QuestionLabel ? model.QuestionLabelText.Trim() : null;
918+
}
919+
else if (model.CurrentStep == (int)OptionLabel.CommentsLabel)
920+
{
921+
data.ReviewerCommentsLabel = model.ReviewerCommentsLabel;
922+
if (model.ReviewerCommentsLabel)
923+
{
924+
var label = model.ReviewerCommentsLabelText?.Trim();
925+
if (string.IsNullOrEmpty(label))
926+
ModelState.AddModelError(nameof(model.ReviewerCommentsLabelText), "Please enter a reviewer comment");
927+
else if (label.Length > 50)
928+
ModelState.AddModelError(nameof(model.ReviewerCommentsLabelText), "Reviewer comment must be 50 characters or fewer");
929+
930+
if (!ModelState.IsValid)
931+
{
932+
SetOptionsLabelsData(data);
933+
model = new OptionsLabelsViewModel(data);
934+
model.Error = true;
935+
return View("CompetencyAssessmentOptions", model);
936+
}
937+
}
938+
data.ReviewerCommentsLabelText = model.ReviewerCommentsLabel ? model.ReviewerCommentsLabelText.Trim() : null;
939+
}
940+
else if (model.CurrentStep == (int)OptionLabel.Summary)
941+
{
942+
var isUpdate = competencyAssessmentService.UpdateCompetencyAssessmentOptions(
943+
data.IncludeLearnerDeclarationPrompt,
944+
data.IncludesSignposting,
945+
data.LinearNavigation,
946+
data.UseDescriptionExpanders,
947+
data.QuestionLabel ? data.QuestionLabelText?.Trim() : null,
948+
data.ReviewerCommentsLabel ? data.ReviewerCommentsLabelText?.Trim() : null,
949+
data.CompetencyAssessmentID,
950+
adminId);
951+
if (!isUpdate)
952+
{
953+
ModelState.AddModelError("", "Update failed. Please try again.");
954+
return View("CompetencyAssessmentOptions", model);
955+
}
956+
competencyAssessmentService.UpdateCompetencyAssessmentOptionsTaskStatus(model.CompetencyAssessmentID, model.SelfAssessmentOptionsTaskStatus ?? false);
957+
return RedirectToAction("ManageCompetencyAssessment", "CompetencyAssessments", new { model.CompetencyAssessmentID });
958+
}
959+
960+
if (data.SelfAssessmentOptionsTaskStatus != null)
961+
data.CurrentStep = (int)OptionLabel.Summary;
962+
else
963+
data.CurrentStep = model.CurrentStep + 1;
964+
965+
SetOptionsLabelsData(data);
966+
967+
var newModel = new OptionsLabelsViewModel(data);
968+
return RedirectToAction("OptionsLabels", "CompetencyAssessments", new { model.CompetencyAssessmentID, step = newModel.CurrentStep });
969+
}
970+
812971
public IActionResult RemoveCollaborator(int competencyAssessmentId, int id, string actionName)
813972
{
814973
competencyAssessmentService.RemoveCollaboratorFromCompetencyAssessment(competencyAssessmentId, id);
@@ -832,5 +991,54 @@ private CompetencyAssessmentFeaturesViewModel GetcompetencyAssessmentFeaturesDat
832991
).GetAwaiter().GetResult();
833992
return data;
834993
}
994+
995+
private void SetOptionsLabelsData(OptionsLabelsViewModel data)
996+
{
997+
multiPageFormService.SetMultiPageFormData(
998+
data,
999+
MultiPageFormDataFeature.AddCustomWebForm("OptionsLabelsCWF"),
1000+
TempData
1001+
);
1002+
}
1003+
1004+
private OptionsLabelsViewModel GetOptionsLabelslData()
1005+
{
1006+
var data = multiPageFormService.GetMultiPageFormData<OptionsLabelsViewModel>(
1007+
MultiPageFormDataFeature.AddCustomWebForm("OptionsLabelsCWF"),
1008+
TempData
1009+
).GetAwaiter().GetResult();
1010+
return data;
1011+
}
1012+
private bool ValidateStep(OptionsLabelsViewModel data, ref int step)
1013+
{
1014+
int original = step;
1015+
if (step == (int)OptionLabel.Declaration && !data.IsSupervisionSwitchedOn) step = (int)OptionLabel.Signposting;
1016+
if (step == (int)OptionLabel.Signposting && !data.IsSignpostedLearning) step = (int)OptionLabel.LinearNavigation;
1017+
if (step == (int)OptionLabel.CommentsLabel && !data.IsSupervisionSwitchedOn) step = (int)OptionLabel.Summary;
1018+
1019+
return step != original;
1020+
}
1021+
1022+
private StatusCodeResult ValidateCompetencyAssessmentAndRole(int competencyAssessmentId, int adminId, string pageName)
1023+
{
1024+
if (competencyAssessmentId > 0)
1025+
{
1026+
var competencyAssessmentBase = competencyAssessmentService.GetCompetencyAssessmentBaseById(competencyAssessmentId, adminId);
1027+
if (competencyAssessmentBase == null)
1028+
{
1029+
logger.LogWarning($"Failed to load {pageName} page for competencyAssessmentId: {competencyAssessmentId} adminId: {adminId}");
1030+
return StatusCode(500);
1031+
}
1032+
if (competencyAssessmentBase.UserRole < 2)
1033+
{
1034+
return StatusCode(403);
1035+
}
1036+
}
1037+
else
1038+
{
1039+
return StatusCode(500);
1040+
}
1041+
return StatusCode(200);
1042+
}
8351043
}
8361044
}

0 commit comments

Comments
 (0)