Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0f1711e
HEEDLS-555 Create DelegateCourseInfo model; write GetDelegateCourseIn…
ibrahimmunir14 Jul 20, 2021
89fcb17
HEEDLS-555 Create DelegateCourseInfoViewModel; write tests
ibrahimmunir14 Jul 20, 2021
7800fc2
HEEDLS-555 Add CourseInfoViewModel list to ViewDelegateViewModel; upd…
ibrahimmunir14 Jul 20, 2021
649b221
HEEDLS-555 Add CustomisationId to DelegateCourseInfo
ibrahimmunir14 Jul 20, 2021
4b230ed
HEEDLS-555 Create _DelegateCourseInfoCard partial; display course car…
ibrahimmunir14 Jul 20, 2021
f0879e8
HEEDLS-555 Add custom Answer1-3 to DelegateCourseInfo; add custom ans…
ibrahimmunir14 Jul 21, 2021
1156114
HEEDLS-555 Move CategoryID and ArchivedDate filtering from dataservic…
ibrahimmunir14 Jul 21, 2021
69baeb7
HEEDLS-555 Add custom prompts to DelegateCourseInfo
ibrahimmunir14 Jul 21, 2021
49c6a88
HEEDLS-555 Add attempts stats to DelegateCourseInfo VM, view, control…
ibrahimmunir14 Jul 21, 2021
1c24175
HEEDLS-555 Add Edit and Set completed date links to DelegateCourseInf…
ibrahimmunir14 Jul 21, 2021
06b21e6
HEEDLS-555 Use CustomPromptWithAnswer instead of CustomPrompt
ibrahimmunir14 Jul 22, 2021
feb5c4b
HEEDLS-555 Review markups
ibrahimmunir14 Jul 22, 2021
201f7c6
HEEDLS-555 Use pass rate percentage instead of pass ratio in Delegate…
ibrahimmunir14 Jul 22, 2021
eff0f8e
HEEDLS-555 Update pass rate field name; use alternative delegate id f…
ibrahimmunir14 Jul 22, 2021
7f1b13e
HEEDLS-555 Move pass rate double to percentage string conversion into VM
ibrahimmunir14 Jul 22, 2021
63c681a
HEEDLS-555 Fix misspelling of enrolment
ibrahimmunir14 Jul 23, 2021
9cdeff7
HEEDLS-555 Add CourseName derived field on DelegateCourseInfoVM
ibrahimmunir14 Jul 26, 2021
46f103f
HEEDLS-555 Minor refactoring and renaming
ibrahimmunir14 Jul 26, 2021
7365ac3
HEEDLS-555 Select Supervisor Forename and Surname separately from db;…
ibrahimmunir14 Jul 26, 2021
a1d4291
Merge branch 'master' into HEEDLS-555-view-delegate-page-course-section
ibrahimmunir14 Jul 26, 2021
6534359
HEEDLS-555 Move delegate course info fetching logic into service method
ibrahimmunir14 Jul 28, 2021
72bafe5
HEEDLS-555 Put ArchivedDate check back into SQL query; remove isArchi…
ibrahimmunir14 Jul 28, 2021
63bf216
HEEDLS-555 View delegate summary style change; add comment
ibrahimmunir14 Jul 28, 2021
f3f9b87
Merge branch 'master' into HEEDLS-555-view-delegate-page-course-section
ibrahimmunir14 Jul 28, 2021
5ef43f9
HEEDLS-555 Remove remaining ArchivedDate
ibrahimmunir14 Jul 28, 2021
55fc34c
HEEDLS-555 Create DelegateCourseDetails model for course info, attemp…
ibrahimmunir14 Jul 28, 2021
807aa16
HEEDLS-555 Fix DelegateCourseInfo VM tests
ibrahimmunir14 Jul 28, 2021
5d682dd
HEEDLS-555 Add ArchivedDate IS NULL check to GetDelegateCoursesInfo
ibrahimmunir14 Jul 28, 2021
284ceaf
HEEDLS-555 Add CourseService tests for GetAllCoursesForDelegate
ibrahimmunir14 Jul 28, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -272,5 +272,48 @@ public void GetCourseDetailsByIdAtCentreForCategoryId_should_return_course_detai
// Then
result.Should().BeEquivalentTo(expectedCourseDetails);
}

[Test]
public void GetDelegateCoursesInfo_should_return_delegate_course_info_correctly()
{
// When
var results = courseDataService.GetDelegateCoursesInfo(20).ToList();

// Then
var enrollmentDate = new DateTime(2019, 04, 11, 14, 33, 37).AddMilliseconds(140);
var expected = new DelegateCourseInfo(
27915,
"LinkedIn",
"Cohort Testing",
"Kevin",
"Whittaker (Developer)",
enrollmentDate,
enrollmentDate,
null,
null,
null,
3,
0,
0,
null,
true,
null,
null,
null
);
results.Should().HaveCount(4);
results[3].Should().BeEquivalentTo(expected);
}

[Test]
public void GetDelegateCoursesAttemptStats_should_return_delegate_course_info_correctly()
{
// When
var (totalAttempts, attemptsPassed) = courseDataService.GetDelegateCourseAttemptStats(11, 100);

// Then
totalAttempts.Should().Be(23);
attemptsPassed.Should().Be(11);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,12 @@ public void GetCourseCustomPrompts_returns_populated_CourseCustomPromptsResult()
"Yes\nNo\nNot sure",
true,
null,
"Yes\nNo\nNot sure"
"Yes\nNo\nNot sure",
courseCategoryId: 2
);

// When
var returnedCourseCustomPromptsResult = customPromptsDataService.GetCourseCustomPrompts(1379, 101, 0);
var returnedCourseCustomPromptsResult = customPromptsDataService.GetCourseCustomPrompts(1379, 101);

// Then
returnedCourseCustomPromptsResult.Should().BeEquivalentTo(expectedCourseCustomPromptsResult);
Expand Down
69 changes: 67 additions & 2 deletions DigitalLearningSolutions.Data.Tests/Services/CourseServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ public class CourseServiceTests
private const int AdminCategoryId = 0;
private ICourseDataService courseDataService = null!;
private CourseService courseService = null!;
private ICustomPromptsService customPromptsService = null!;

[SetUp]
public void Setup()
{
courseDataService = A.Fake<ICourseDataService>();
A.CallTo(() => courseDataService.GetCourseStatisticsAtCentreForCategoryId(CentreId, AdminCategoryId))
.Returns(GetSampleCourses());

courseService = new CourseService(courseDataService);
customPromptsService = A.Fake<ICustomPromptsService>();
courseService = new CourseService(courseDataService, customPromptsService);
}

[Test]
Expand Down Expand Up @@ -84,5 +85,69 @@ private IEnumerable<CourseStatistics> GetSampleCourses()
}
};
}

[Test]
public void GetAllCoursesForDelegate_should_call_correct_data_service_and_helper_methods()
{
// Given
const int delegateId = 20;
const int customisationId = 111;
var attemptStats = (7, 4);
var info = new DelegateCourseInfo
{ CustomisationId = customisationId, IsAssessed = true };
A.CallTo(() => courseDataService.GetDelegateCoursesInfo(delegateId))
.Returns(new List<DelegateCourseInfo> { info });
A.CallTo(() => courseDataService.GetDelegateCourseAttemptStats(delegateId, customisationId))
.Returns(attemptStats);

// When
var results = courseService.GetAllCoursesForDelegate(delegateId, CentreId).ToList();

// Then
A.CallTo(() => courseDataService.GetDelegateCoursesInfo(delegateId)).MustHaveHappened(1, Times.Exactly);
A.CallTo(
() => customPromptsService.GetCustomPromptsWithAnswersForCourse(
info,
customisationId,
CentreId,
0
)
).MustHaveHappened(1, Times.Exactly);
A.CallTo(() => courseDataService.GetDelegateCourseAttemptStats(delegateId, customisationId))
.MustHaveHappened(1, Times.Exactly);
results.Should().HaveCount(1);
results[0].DelegateCourseInfo.Should().BeEquivalentTo(info);
results[0].AttemptStats.Should().Be(attemptStats);
}

[Test]
public void GetAllCoursesForDelegate_should_not_fetch_attempt_stats_if_course_not_assessed()
{
// Given
const int delegateId = 20;
const int customisationId = 111;
var info = new DelegateCourseInfo
{ CustomisationId = customisationId, IsAssessed = false };
A.CallTo(() => courseDataService.GetDelegateCoursesInfo(delegateId))
.Returns(new List<DelegateCourseInfo> { info });

// When
var results = courseService.GetAllCoursesForDelegate(delegateId, CentreId).ToList();

// Then
A.CallTo(() => courseDataService.GetDelegateCoursesInfo(delegateId)).MustHaveHappened(1, Times.Exactly);
A.CallTo(
() => customPromptsService.GetCustomPromptsWithAnswersForCourse(
info,
customisationId,
CentreId,
0
)
).MustHaveHappened(1, Times.Exactly);
A.CallTo(() => courseDataService.GetDelegateCourseAttemptStats(A<int>._, A<int>._)).MustNotHaveHappened();
results.Should().HaveCount(1);
results[0].DelegateCourseInfo.Should().BeEquivalentTo(info);
results[0].AttemptStats.Should().Be((0, 0));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using DigitalLearningSolutions.Data.DataServices;
using DigitalLearningSolutions.Data.Models.Courses;
using DigitalLearningSolutions.Data.Models.CustomPrompts;
using DigitalLearningSolutions.Data.Services;
using DigitalLearningSolutions.Data.Tests.TestHelpers;
Expand Down Expand Up @@ -306,18 +307,48 @@ public void RemoveCustomPromptFromCentre_calls_data_service_with_correct_values(
public void GetCustomPromptsForCourse_Returns_Populated_CourseCustomPrompts()
{
// Given
var expectedPrompt1 = CustomPromptsTestHelper.GetDefaultCustomPrompt(1, "System Access Granted", "Yes\r\nNo");
var expectedPrompt1 =
CustomPromptsTestHelper.GetDefaultCustomPrompt(1, "System Access Granted", "Yes\r\nNo");
var expectedPrompt2 = CustomPromptsTestHelper.GetDefaultCustomPrompt(2, "Access Permissions");
var customPrompts = new List<CustomPrompt> { expectedPrompt1, expectedPrompt2 };
var expectedCoursePrompts = CustomPromptsTestHelper.GetDefaultCourseCustomPrompts(customPrompts);
A.CallTo(() => customPromptsDataService.GetCourseCustomPrompts(27920, 101, 0))
A.CallTo(() => customPromptsDataService.GetCourseCustomPrompts(27920, 101))
.Returns(CustomPromptsTestHelper.GetDefaultCourseCustomPromptsResult());

// When
var result = customPromptsService.GetCustomPromptsForCourse(27920, 101, 0);
var result = customPromptsService.GetCustomPromptsForCourse(27920, 101);

// Then
result.Should().BeEquivalentTo(expectedCoursePrompts);
}

[Test]
public void GetCustomPromptsWithAnswersForCourse_Returns_Populated_List_of_CustomPromptWithAnswer()
{
// Given
const string answer1 = "ans1";
const string answer2 = "ans2";
var expected1 = CustomPromptsTestHelper.GetDefaultCustomPromptWithAnswer(
1,
"System Access Granted",
"Yes\r\nNo",
answer: answer1
);
var expected2 = CustomPromptsTestHelper.GetDefaultCustomPromptWithAnswer(
2,
"Access Permissions",
answer: answer2
);
var expected = new List<CustomPromptWithAnswer> { expected1, expected2 };
A.CallTo(() => customPromptsDataService.GetCourseCustomPrompts(27920, 101))
.Returns(CustomPromptsTestHelper.GetDefaultCourseCustomPromptsResult());
var delegateCourseInfo = new DelegateCourseInfo { Answer1 = answer1, Answer2 = answer2 };

// When
var result = customPromptsService.GetCustomPromptsWithAnswersForCourse(delegateCourseInfo, 27920, 101);

// Then
result.Should().BeEquivalentTo(expected);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
namespace DigitalLearningSolutions.Data.Tests.TestHelpers
{
using System;
using System.Collections.Generic;
using DigitalLearningSolutions.Data.Models.CustomPrompts;

public static class CustomPromptsTestHelper
{
public static CentreCustomPrompts GetDefaultCentreCustomPrompts
(
public static CentreCustomPrompts GetDefaultCentreCustomPrompts(
List<CustomPrompt> customPrompts,
int centreId = 29
)
{
return new CentreCustomPrompts(centreId, customPrompts);
}

public static CourseCustomPrompts GetDefaultCourseCustomPrompts
(
public static CourseCustomPrompts GetDefaultCourseCustomPrompts(
List<CustomPrompt> customPrompts,
int customisationId = 27920,
int centreId = 101
Expand All @@ -24,8 +23,7 @@ public static CourseCustomPrompts GetDefaultCourseCustomPrompts
return new CourseCustomPrompts(customisationId, centreId, customPrompts);
}

public static CustomPrompt GetDefaultCustomPrompt
(
public static CustomPrompt GetDefaultCustomPrompt(
int promptNumber,
string text = "Custom Prompt",
string? options = "",
Expand All @@ -35,17 +33,15 @@ public static CustomPrompt GetDefaultCustomPrompt
return new CustomPrompt(promptNumber, text, options, mandatory);
}

public static CentreCustomPromptsWithAnswers GetDefaultCentreCustomPromptsWithAnswers
(
public static CentreCustomPromptsWithAnswers GetDefaultCentreCustomPromptsWithAnswers(
List<CustomPromptWithAnswer> customPrompts,
int centreId = 29
)
{
return new CentreCustomPromptsWithAnswers(centreId, customPrompts);
}

public static CustomPromptWithAnswer GetDefaultCustomPromptWithAnswer
(
public static CustomPromptWithAnswer GetDefaultCustomPromptWithAnswer(
int promptNumber,
string text = "Custom Prompt",
string? options = "",
Expand All @@ -56,8 +52,7 @@ public static CustomPromptWithAnswer GetDefaultCustomPromptWithAnswer
return new CustomPromptWithAnswer(promptNumber, text, options, mandatory, answer);
}

public static CentreCustomPromptsResult GetDefaultCentreCustomPromptsResult
(
public static CentreCustomPromptsResult GetDefaultCentreCustomPromptsResult(
int centreId = 29,
string? customField1Prompt = "Group",
string? customField1Options = "Clinical\r\nNon-Clinical",
Expand Down Expand Up @@ -112,7 +107,8 @@ public static CourseCustomPromptsResult GetDefaultCourseCustomPromptsResult(
bool customField2Mandatory = false,
string? customField3Prompt = null,
string? customField3Options = "",
bool customField3Mandatory = false
bool customField3Mandatory = false,
int courseCategoryId = 0
)
{
return new CourseCustomPromptsResult
Expand All @@ -125,7 +121,8 @@ public static CourseCustomPromptsResult GetDefaultCourseCustomPromptsResult(
CustomField2Mandatory = customField2Mandatory,
CustomField3Prompt = customField3Prompt,
CustomField3Options = customField3Options,
CustomField3Mandatory = customField3Mandatory
CustomField3Mandatory = customField3Mandatory,
CourseCategoryId = courseCategoryId
};
}
}
Expand Down
54 changes: 53 additions & 1 deletion DigitalLearningSolutions.Data/DataServices/CourseDataService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace DigitalLearningSolutions.Data.DataServices
namespace DigitalLearningSolutions.Data.DataServices
{
using System;
using System.Collections.Generic;
Expand All @@ -18,6 +18,8 @@ public interface ICourseDataService
void EnrolOnSelfAssessment(int selfAssessmentId, int candidateId);
int GetNumberOfActiveCoursesAtCentreForCategory(int centreId, int categoryId);
IEnumerable<CourseStatistics> GetCourseStatisticsAtCentreForCategoryId(int centreId, int categoryId);
IEnumerable<DelegateCourseInfo> GetDelegateCoursesInfo(int delegateId);
(int totalAttempts, int attemptsPassed) GetDelegateCourseAttemptStats(int delegateId, int customisationId);
CourseDetails? GetCourseDetails(int customisationId, int centreId, int categoryId);
}

Expand Down Expand Up @@ -198,6 +200,56 @@ FROM dbo.Customisations AS cu
);
}

public IEnumerable<DelegateCourseInfo> GetDelegateCoursesInfo(int delegateId)
{
return connection.Query<DelegateCourseInfo>(
@"SELECT
cu.CustomisationID AS CustomisationId,
ap.ApplicationName,
cu.CustomisationName,
au.Forename AS SupervisorForename,
au.Surname AS SupervisorSurname,
pr.FirstSubmittedTime AS Enrolled,
pr.SubmittedTime AS LastUpdated,
pr.CompleteByDate AS CompleteBy,
pr.Completed AS Completed,
pr.Evaluated AS Evaluated,
pr.EnrollmentMethodID AS EnrolmentMethodId,
pr.LoginCount,
pr.Duration AS LearningTime,
pr.DiagnosticScore,
cu.IsAssessed,
pr.Answer1,
pr.Answer2,
pr.Answer3
FROM Customisations cu
INNER JOIN Applications ap ON ap.ApplicationID = cu.ApplicationID
INNER JOIN Progress pr ON pr.CustomisationID = cu.CustomisationID
LEFT OUTER JOIN AdminUsers au ON au.AdminID = pr.SupervisorAdminId
WHERE pr.CandidateID = @delegateId
AND ap.ArchivedDate IS NULL
AND pr.RemovedDate IS NULL",
new { delegateId }
);
}

public (int totalAttempts, int attemptsPassed) GetDelegateCourseAttemptStats(
int delegateId,
int customisationId
)
{
return connection.QueryFirstOrDefault<(int, int)>(
@"SELECT COUNT(aa.Status) AS TotalAttempts,
COUNT(CASE WHEN aa.Status=1 THEN 1 END) AS AttemptsPassed
Comment on lines +242 to +243
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it makes sense pulling these two numbers together with other course data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it would make the query too complicated. Also these are not needed in many cases any where IsAssessed=0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a bit more refactoring needed in that case (also see my comment on the controller). If these aren't needed because IsAssessed=0, why are we still always calling it?

FROM AssessAttempts aa
INNER JOIN Progress AS pr ON pr.ProgressID = aa.ProgressID
WHERE pr.CustomisationID = @customisationId
AND pr.CandidateID = @delegateId
AND pr.RemovedDate IS NULL",
new { delegateId, customisationId }
);
}

public CourseDetails? GetCourseDetails(int customisationId, int centreId, int categoryId)
{
return connection.Query<CourseDetails>(
Expand Down
Loading