Skip to content

Commit 5a3c94d

Browse files
authored
Merge pull request #2127 from TechnologyEnhancedLearning/Develop/feature/TD-2186-Refactor-CourseDelegatesList-export
TD-2186 - feature 'Refactor the Tracking System - Course delegates list export functionality...' added
2 parents 653ab5e + 8b73a86 commit 5a3c94d

File tree

6 files changed

+319
-63
lines changed

6 files changed

+319
-63
lines changed

DigitalLearningSolutions.Data/DataServices/CourseDataService.cs

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace DigitalLearningSolutions.Data.DataServices
1111
using System.Collections.Generic;
1212
using System.Data;
1313
using System.Linq;
14+
using System.Threading.Tasks;
1415

1516
public interface ICourseDataService
1617
{
@@ -96,6 +97,12 @@ int diagCompletionThreshold
9697

9798
IEnumerable<CourseDelegateForExport> GetDelegatesOnCourseForExport(int customisationId, int centreId);
9899

100+
int GetCourseDelegatesCountForExport(string searchString, string sortBy, string sortDirection,
101+
int customisationId, int centreId, bool? isDelegateActive, bool? isProgressLocked, bool? removed, bool? hasCompleted, string? answer1, string? answer2, string? answer3);
102+
103+
Task<IEnumerable<CourseDelegateForExport>> GetCourseDelegatesForExport(string searchString, int offSet, int itemsPerPage, string sortBy, string sortDirection,
104+
int customisationId, int centreId, bool? isDelegateActive, bool? isProgressLocked, bool? removed, bool? hasCompleted, string? answer1, string? answer2, string? answer3);
105+
99106
int EnrolOnActivitySelfAssessment(int selfAssessmentId, int candidateId, int supervisorId, string adminEmail,
100107
int selfAssessmentSupervisorRoleId, DateTime? completeByDate, int delegateUserId, int centreId);
101108

@@ -1358,6 +1365,192 @@ FROM DelegateAccounts AS da
13581365
);
13591366
}
13601367

1368+
public int GetCourseDelegatesCountForExport(string searchString, string sortBy, string sortDirection,
1369+
int customisationId, int centreId, bool? isDelegateActive, bool? isProgressLocked, bool? removed, bool? hasCompleted, string? answer1, string? answer2, string? answer3)
1370+
{
1371+
1372+
var fromTableQuery = $@" FROM Customisations cu WITH (NOLOCK)
1373+
INNER JOIN Applications AS ap WITH (NOLOCK) ON ap.ApplicationID = cu.ApplicationID
1374+
INNER JOIN Progress AS pr WITH (NOLOCK) ON pr.CustomisationID = cu.CustomisationID
1375+
LEFT OUTER JOIN AdminAccounts AS aaSupervisor WITH (NOLOCK) ON aaSupervisor.ID = pr.SupervisorAdminId
1376+
LEFT OUTER JOIN Users AS uSupervisor WITH (NOLOCK) ON uSupervisor.ID = aaSupervisor.UserID
1377+
LEFT OUTER JOIN AdminAccounts AS aaEnrolledBy WITH (NOLOCK) ON aaEnrolledBy.ID = pr.EnrolledByAdminID
1378+
LEFT OUTER JOIN Users AS uEnrolledBy WITH (NOLOCK) ON uEnrolledBy.ID = aaEnrolledBy.UserID
1379+
INNER JOIN DelegateAccounts AS da WITH (NOLOCK) ON da.ID = pr.CandidateID
1380+
INNER JOIN Users AS u WITH (NOLOCK) ON u.ID = da.UserID
1381+
LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = da.UserID AND ucd.centreID = da.CentreID
1382+
1383+
WHERE (cu.CentreID = @centreId OR
1384+
(cu.AllCentres = 1 AND
1385+
EXISTS (SELECT CentreApplicationID
1386+
FROM CentreApplications cap
1387+
WHERE cap.ApplicationID = cu.ApplicationID AND
1388+
cap.CentreID = @centreID AND
1389+
cap.Active = 1)))
1390+
AND da.CentreID = @centreId
1391+
AND pr.CustomisationID = @customisationId
1392+
AND ap.DefaultContentTypeID <> 4
1393+
1394+
AND ( u.FirstName + ' ' + u.LastName + ' ' + COALESCE(ucd.Email, u.PrimaryEmail) + ' ' + COALESCE(CandidateNumber, '') LIKE N'%' + @searchString + N'%')
1395+
AND ((@isDelegateActive IS NULL) OR (@isDelegateActive = 1 AND (da.Active = 1)) OR (@isDelegateActive = 0 AND (da.Active = 0)))
1396+
AND ((@isProgressLocked IS NULL) OR (@isProgressLocked = 1 AND (pr.PLLocked = 1)) OR (@isProgressLocked = 0 AND (pr.PLLocked = 0)))
1397+
AND ((@removed IS NULL) OR (@removed = 1 AND (pr.RemovedDate IS NOT NULL)) OR (@removed = 0 AND (pr.RemovedDate IS NULL)))
1398+
AND ((@hasCompleted IS NULL) OR (@hasCompleted = 1 AND pr.Completed IS NOT NULL) OR (@hasCompleted = 0 AND pr.Completed IS NULL))
1399+
1400+
AND ((@answer1 IS NULL) OR ((@answer1 = 'No option selected' OR @answer1 = 'FREETEXTBLANKVALUE') AND (pr.Answer1 IS NULL OR TRIM(pr.Answer1) = ''))
1401+
OR (@answer1 = 'FREETEXTNOTBLANKVALUE' AND (pr.Answer1 IS NOT NULL OR pr.Answer1 = @answer1)))
1402+
1403+
AND ((@answer2 IS NULL) OR ((@answer2 = 'No option selected' OR @answer2 = 'FREETEXTBLANKVALUE') AND (pr.Answer2 IS NULL OR TRIM(pr.Answer2) = ''))
1404+
OR (@answer2 = 'FREETEXTNOTBLANKVALUE' AND (pr.Answer2 IS NOT NULL OR pr.Answer2 = @answer2)))
1405+
1406+
AND ((@answer3 IS NULL) OR ((@answer3 = 'No option selected' OR @answer3 = 'FREETEXTBLANKVALUE') AND (pr.Answer3 IS NULL OR TRIM(pr.Answer3) = ''))
1407+
OR (@answer3 = 'FREETEXTNOTBLANKVALUE' AND (pr.Answer3 IS NOT NULL OR pr.Answer3 = @answer3)))
1408+
1409+
AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%'";
1410+
1411+
1412+
var mainSql = "SELECT COUNT(*) AS TotalRecords " + fromTableQuery;
1413+
1414+
return connection.ExecuteScalar<int>(
1415+
mainSql,
1416+
new
1417+
{
1418+
searchString,
1419+
sortBy,
1420+
sortDirection,
1421+
customisationId,
1422+
centreId,
1423+
isDelegateActive,
1424+
isProgressLocked,
1425+
removed,
1426+
hasCompleted,
1427+
answer1,
1428+
answer2,
1429+
answer3
1430+
},
1431+
commandTimeout: 3000
1432+
);
1433+
}
1434+
1435+
1436+
public async Task<IEnumerable<CourseDelegateForExport>> GetCourseDelegatesForExport(string searchString, int offSet, int itemsPerPage, string sortBy, string sortDirection,
1437+
int customisationId, int centreId, bool? isDelegateActive, bool? isProgressLocked, bool? removed, bool? hasCompleted, string? answer1, string? answer2, string? answer3)
1438+
{
1439+
1440+
var selectColumnQuery = $@"SELECT
1441+
ap.ApplicationName,
1442+
cu.CustomisationName,
1443+
da.ID AS DelegateId,
1444+
da.CandidateNumber,
1445+
u.FirstName AS DelegateFirstName,
1446+
u.LastName AS DelegateLastName,
1447+
COALESCE(ucd.Email, u.PrimaryEmail) AS DelegateEmail,
1448+
da.Active AS IsDelegateActive,
1449+
da.Answer1 AS RegistrationAnswer1,
1450+
da.Answer2 AS RegistrationAnswer2,
1451+
da.Answer3 AS RegistrationAnswer3,
1452+
da.Answer4 AS RegistrationAnswer4,
1453+
da.Answer5 AS RegistrationAnswer5,
1454+
da.Answer6 AS RegistrationAnswer6,
1455+
pr.ProgressID,
1456+
pr.PLLocked AS IsProgressLocked,
1457+
pr.SubmittedTime AS LastUpdated,
1458+
pr.FirstSubmittedTime AS Enrolled,
1459+
pr.CompleteByDate AS CompleteBy,
1460+
pr.RemovedDate,
1461+
pr.Completed,
1462+
pr.CustomisationId,
1463+
pr.LoginCount,
1464+
pr.Duration,
1465+
pr.DiagnosticScore,
1466+
pr.Answer1,
1467+
pr.Answer2,
1468+
pr.Answer3,
1469+
{DelegateAllAttemptsQuery},
1470+
{DelegateAttemptsPassedQuery},
1471+
{DelegatePassRateQuery}";
1472+
1473+
var fromTableQuery = $@" FROM Customisations cu WITH (NOLOCK)
1474+
INNER JOIN Applications AS ap WITH (NOLOCK) ON ap.ApplicationID = cu.ApplicationID
1475+
INNER JOIN Progress AS pr WITH (NOLOCK) ON pr.CustomisationID = cu.CustomisationID
1476+
LEFT OUTER JOIN AdminAccounts AS aaSupervisor WITH (NOLOCK) ON aaSupervisor.ID = pr.SupervisorAdminId
1477+
LEFT OUTER JOIN Users AS uSupervisor WITH (NOLOCK) ON uSupervisor.ID = aaSupervisor.UserID
1478+
LEFT OUTER JOIN AdminAccounts AS aaEnrolledBy WITH (NOLOCK) ON aaEnrolledBy.ID = pr.EnrolledByAdminID
1479+
LEFT OUTER JOIN Users AS uEnrolledBy WITH (NOLOCK) ON uEnrolledBy.ID = aaEnrolledBy.UserID
1480+
INNER JOIN DelegateAccounts AS da WITH (NOLOCK) ON da.ID = pr.CandidateID
1481+
INNER JOIN Users AS u WITH (NOLOCK) ON u.ID = da.UserID
1482+
LEFT JOIN UserCentreDetails AS ucd WITH (NOLOCK) ON ucd.UserID = da.UserID AND ucd.centreID = da.CentreID
1483+
1484+
WHERE (cu.CentreID = @centreId OR
1485+
(cu.AllCentres = 1 AND
1486+
EXISTS (SELECT CentreApplicationID
1487+
FROM CentreApplications cap
1488+
WHERE cap.ApplicationID = cu.ApplicationID AND
1489+
cap.CentreID = @centreID AND
1490+
cap.Active = 1)))
1491+
AND da.CentreID = @centreId
1492+
AND pr.CustomisationID = @customisationId
1493+
AND ap.DefaultContentTypeID <> 4
1494+
1495+
AND ( u.FirstName + ' ' + u.LastName + ' ' + COALESCE(ucd.Email, u.PrimaryEmail) + ' ' + COALESCE(CandidateNumber, '') LIKE N'%' + @searchString + N'%')
1496+
AND ((@isDelegateActive IS NULL) OR (@isDelegateActive = 1 AND (da.Active = 1)) OR (@isDelegateActive = 0 AND (da.Active = 0)))
1497+
AND ((@isProgressLocked IS NULL) OR (@isProgressLocked = 1 AND (pr.PLLocked = 1)) OR (@isProgressLocked = 0 AND (pr.PLLocked = 0)))
1498+
AND ((@removed IS NULL) OR (@removed = 1 AND (pr.RemovedDate IS NOT NULL)) OR (@removed = 0 AND (pr.RemovedDate IS NULL)))
1499+
AND ((@hasCompleted IS NULL) OR (@hasCompleted = 1 AND pr.Completed IS NOT NULL) OR (@hasCompleted = 0 AND pr.Completed IS NULL))
1500+
1501+
AND ((@answer1 IS NULL) OR ((@answer1 = 'No option selected' OR @answer1 = 'FREETEXTBLANKVALUE') AND (pr.Answer1 IS NULL OR TRIM(pr.Answer1) = ''))
1502+
OR (@answer1 = 'FREETEXTNOTBLANKVALUE' AND (pr.Answer1 IS NOT NULL OR pr.Answer1 = @answer1)))
1503+
1504+
AND ((@answer2 IS NULL) OR ((@answer2 = 'No option selected' OR @answer2 = 'FREETEXTBLANKVALUE') AND (pr.Answer2 IS NULL OR TRIM(pr.Answer2) = ''))
1505+
OR (@answer2 = 'FREETEXTNOTBLANKVALUE' AND (pr.Answer2 IS NOT NULL OR pr.Answer2 = @answer2)))
1506+
1507+
AND ((@answer3 IS NULL) OR ((@answer3 = 'No option selected' OR @answer3 = 'FREETEXTBLANKVALUE') AND (pr.Answer3 IS NULL OR TRIM(pr.Answer3) = ''))
1508+
OR (@answer3 = 'FREETEXTNOTBLANKVALUE' AND (pr.Answer3 IS NOT NULL OR pr.Answer3 = @answer3)))
1509+
1510+
AND COALESCE(ucd.Email, u.PrimaryEmail) LIKE '%_@_%.__%'";
1511+
1512+
string orderBy;
1513+
string sortOrder;
1514+
1515+
if (sortDirection == "Ascending")
1516+
sortOrder = " ASC ";
1517+
else
1518+
sortOrder = " DESC ";
1519+
1520+
if (sortBy == "SearchableName" || sortBy == "FullNameForSearchingSorting")
1521+
orderBy = " ORDER BY LTRIM(u.LastName) " + sortOrder + ", LTRIM(u.FirstName) ";
1522+
else
1523+
orderBy = " ORDER BY " + sortBy + sortOrder;
1524+
1525+
orderBy += " OFFSET " + offSet + " ROWS FETCH NEXT " + itemsPerPage + " ROWS ONLY ";
1526+
1527+
1528+
var mainSql = selectColumnQuery + fromTableQuery + orderBy;
1529+
1530+
IEnumerable<CourseDelegateForExport> courseDelegates = connection.Query<CourseDelegateForExport>(
1531+
mainSql,
1532+
new
1533+
{
1534+
searchString,
1535+
sortBy,
1536+
sortDirection,
1537+
customisationId,
1538+
centreId,
1539+
isDelegateActive,
1540+
isProgressLocked,
1541+
removed,
1542+
hasCompleted,
1543+
answer1,
1544+
answer2,
1545+
answer3
1546+
},
1547+
commandTimeout: 3000
1548+
);
1549+
1550+
1551+
return courseDelegates;
1552+
}
1553+
13611554
public bool IsCourseCompleted(int candidateId, int customisationId)
13621555
{
13631556
return connection.ExecuteScalar<bool>(

DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/Delegates/CourseDelegatesControllerTests.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Microsoft.AspNetCore.Http;
1818
using Microsoft.AspNetCore.Mvc;
1919
using Microsoft.AspNetCore.Mvc.ViewFeatures;
20+
using Microsoft.Extensions.Configuration;
2021
using NUnit.Framework;
2122

2223
public class CourseDelegatesControllerTests
@@ -26,18 +27,21 @@ public class CourseDelegatesControllerTests
2627
private ICourseDelegatesDownloadFileService courseDelegatesDownloadFileService = null!;
2728
private ICourseDelegatesService courseDelegatesService = null!;
2829
private IPaginateService paginateService = null!;
30+
private IConfiguration configuration = null!;
2931

3032
[SetUp]
3133
public void SetUp()
3234
{
3335
courseDelegatesService = A.Fake<ICourseDelegatesService>();
3436
courseDelegatesDownloadFileService = A.Fake<ICourseDelegatesDownloadFileService>();
3537
paginateService = A.Fake<IPaginateService>();
38+
configuration = A.Fake<IConfiguration>();
3639

3740
controller = new CourseDelegatesController(
3841
courseDelegatesService,
3942
courseDelegatesDownloadFileService,
40-
paginateService
43+
paginateService,
44+
configuration
4145
)
4246
.WithDefaultContext()
4347
.WithMockHttpContextSession()
@@ -129,7 +133,8 @@ public void Index_should_default_to_Active_filter()
129133
var courseDelegatesController = new CourseDelegatesController(
130134
courseDelegatesService,
131135
courseDelegatesDownloadFileService,
132-
paginateService
136+
paginateService,
137+
configuration
133138
)
134139
.WithMockHttpContext(httpRequest, cookieName, cookieValue, httpResponse)
135140
.WithMockUser(true, UserCentreId)

DigitalLearningSolutions.Web.Tests/Services/CourseDelegatesDownloadFileServiceTests.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@
22
{
33
using System;
44
using System.Collections.Generic;
5+
using System.Globalization;
56
using System.IO;
67
using System.Linq;
8+
using System.Threading.Tasks;
79
using ClosedXML.Excel;
810
using DigitalLearningSolutions.Data.DataServices;
11+
using DigitalLearningSolutions.Data.Extensions;
912
using DigitalLearningSolutions.Data.Models.CourseDelegates;
1013
using DigitalLearningSolutions.Data.Models.Courses;
1114
using DigitalLearningSolutions.Data.Models.CustomPrompts;
1215
using DigitalLearningSolutions.Data.Tests.TestHelpers;
1316
using DigitalLearningSolutions.Web.Services;
1417
using FakeItEasy;
18+
using Microsoft.Extensions.Configuration;
1519
using NUnit.Framework;
1620

1721
public class CourseDelegatesDownloadFileServiceTests
@@ -189,17 +193,18 @@ public void Setup()
189193
courseDataService = A.Fake<ICourseDataService>();
190194
registrationPromptsService = A.Fake<ICentreRegistrationPromptsService>();
191195
courseService = A.Fake<ICourseService>();
192-
196+
193197
courseDelegatesDownloadFileService = new CourseDelegatesDownloadFileService(
194198
courseDataService,
195199
courseAdminFieldsService,
196200
registrationPromptsService,
197201
courseService
198202
);
203+
199204
}
200205

201206
[Test]
202-
public void GetDelegateDownloadFileForCourse_returns_expected_excel_data()
207+
public async Task GetDelegateDownloadFileForCourse_returns_expected_excel_data()
203208
{
204209
// Given
205210
const int customisationId = 1;
@@ -208,8 +213,13 @@ public void GetDelegateDownloadFileForCourse_returns_expected_excel_data()
208213
TestContext.CurrentContext.TestDirectory + CourseDelegateExportCurrentDataDownloadRelativeFilePath
209214
);
210215

211-
A.CallTo(() => courseDataService.GetDelegatesOnCourseForExport(customisationId, centreId))
212-
.Returns(courseDelegates.Where(c => c.ApplicationName == "Course One"));
216+
A.CallTo(() => courseDataService.GetCourseDelegatesCountForExport(string.Empty,"SearchableName", "Ascending",
217+
customisationId, centreId, null, null, null, null, null, null, null))
218+
.Returns(3);
219+
220+
A.CallTo(() => courseDataService.GetCourseDelegatesForExport(string.Empty,0,250, "SearchableName", "Ascending",
221+
customisationId, centreId, null, null, null, null, null, null, null))
222+
.Returns(courseDelegates.Where(c => c.ApplicationName == "Course One"));
213223

214224
var centreRegistrationPrompts = new List<CentreRegistrationPrompt>
215225
{
@@ -230,12 +240,11 @@ public void GetDelegateDownloadFileForCourse_returns_expected_excel_data()
230240
.Returns(new CourseAdminFields(customisationId, adminFields));
231241

232242
// When
233-
var resultBytes = courseDelegatesDownloadFileService.GetCourseDelegateDownloadFileForCourse(
234-
customisationId,
235-
centreId,
236-
null,
237-
null
243+
244+
var resultBytes = await courseDelegatesDownloadFileService.GetCourseDelegateDownloadFileForCourse(string.Empty,0,250, "SearchableName", "Ascending",
245+
customisationId, centreId, null, null, null, null, null, null, null
238246
);
247+
239248
using var resultsStream = new MemoryStream(resultBytes);
240249
using var resultWorkbook = new XLWorkbook(resultsStream);
241250

0 commit comments

Comments
 (0)