Skip to content

Commit 33594e0

Browse files
authored
Merge pull request #2772 from TechnologyEnhancedLearning/Develop/Features/TD-4469-CourseReportDetail
TD-4469 Adds usage detail tab to centre course report export
2 parents fb3651e + bc85432 commit 33594e0

File tree

7 files changed

+221
-24
lines changed

7 files changed

+221
-24
lines changed

DigitalLearningSolutions.Data/DataServices/ActivityDataService.cs

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,24 @@ IEnumerable<ActivityLog> GetFilteredActivity(
1616
int? courseCategoryId,
1717
int? customisationId
1818
);
19-
19+
int GetActivityDetailRowCount(
20+
int centreId,
21+
DateTime startDate,
22+
DateTime? endDate,
23+
int? jobGroupId,
24+
int? courseCategoryId,
25+
int? customisationId
26+
);
27+
IEnumerable<ActivityLogDetail> GetFilteredActivityDetail(
28+
int centreId,
29+
DateTime startDate,
30+
DateTime? endDate,
31+
int? jobGroupId,
32+
int? courseCategoryId,
33+
int? customisationId,
34+
int exportQueryRowLimit,
35+
int currentRun
36+
);
2037
DateTime? GetStartOfActivityForCentre(int centreId, int? courseCategoryId = null);
2138
}
2239

@@ -47,10 +64,10 @@ public IEnumerable<ActivityLog> GetFilteredActivity(
4764
SUM(CAST(Registered AS Int)) AS Registered,
4865
SUM(CAST(Completed AS Int)) AS Completed,
4966
SUM(CAST(Evaluated AS Int)) AS Evaluated
50-
FROM tActivityLog AS al
67+
FROM tActivityLog AS al INNER JOIN DelegateAccounts AS da ON al.CandidateID = da.ID AND al.CentreID = da.CentreID
5168
WHERE (LogDate >= @startDate
5269
AND (@endDate IS NULL OR LogDate <= @endDate)
53-
AND CentreID = @centreId
70+
AND (al.CentreID = @centreId)
5471
AND (@jobGroupId IS NULL OR JobGroupID = @jobGroupId)
5572
AND (@customisationId IS NULL OR al.CustomisationID = @customisationId)
5673
AND (@courseCategoryId IS NULL OR al.CourseCategoryId = @courseCategoryId)
@@ -74,7 +91,101 @@ GROUP BY Cast(LogDate As Date), LogYear,
7491
}
7592
);
7693
}
77-
94+
public int GetActivityDetailRowCount(
95+
int centreId,
96+
DateTime startDate,
97+
DateTime? endDate,
98+
int? jobGroupId,
99+
int? courseCategoryId,
100+
int? customisationId
101+
)
102+
{
103+
return connection.QuerySingleOrDefault<int>(
104+
@"SELECT COUNT(1) FROM
105+
tActivityLog AS al INNER JOIN DelegateAccounts AS da ON al.CandidateID = da.ID AND al.CentreID = da.CentreID
106+
WHERE(al.LogDate >= @startDate) AND(@endDate IS NULL OR
107+
al.LogDate <= @endDate) AND(al.CentreID = @centreId) AND (@jobGroupId IS NULL OR
108+
al.JobGroupID = @jobGroupId) AND(@customisationId IS NULL OR
109+
al.CustomisationID = @customisationId) AND(@courseCategoryId IS NULL OR
110+
al.CourseCategoryID = @courseCategoryId) AND(al.Registered = 1 OR
111+
al.Completed = 1 OR
112+
al.Evaluated = 1) AND EXISTS
113+
(SELECT ApplicationID
114+
FROM Applications AS ap
115+
WHERE (ApplicationID = al.ApplicationID) AND
116+
(DefaultContentTypeID<> 4))",
117+
new
118+
{
119+
centreId,
120+
startDate,
121+
endDate,
122+
jobGroupId,
123+
customisationId,
124+
courseCategoryId
125+
}
126+
);
127+
}
128+
public IEnumerable<ActivityLogDetail> GetFilteredActivityDetail(
129+
int centreId,
130+
DateTime startDate,
131+
DateTime? endDate,
132+
int? jobGroupId,
133+
int? courseCategoryId,
134+
int? customisationId,
135+
int exportQueryRowLimit,
136+
int currentRun
137+
)
138+
{
139+
return connection.Query<ActivityLogDetail>(
140+
@"SELECT al.LogID,
141+
al.LogDate,
142+
a.ApplicationName AS CourseName,
143+
c.CustomisationName,
144+
u.FirstName,
145+
u.LastName,
146+
u.PrimaryEmail,
147+
da.CandidateNumber AS DelegateId,
148+
da.Answer1,
149+
da.Answer2,
150+
da.Answer3,
151+
da.Answer4,
152+
da.Answer5,
153+
da.Answer6,
154+
al.Registered,
155+
al.Completed,
156+
al.Evaluated
157+
FROM Applications AS a INNER JOIN
158+
tActivityLog AS al ON a.ApplicationID = al.ApplicationID INNER JOIN
159+
Users AS u INNER JOIN
160+
DelegateAccounts AS da ON u.ID = da.UserID ON al.CandidateID = da.ID AND al.CentreID = da.CentreID INNER JOIN
161+
Customisations AS c ON al.CustomisationID = c.CustomisationID
162+
WHERE (al.LogDate >= @startDate) AND (@endDate IS NULL OR
163+
al.LogDate <= @endDate) AND (al.CentreID = @centreId) AND (@jobGroupId IS NULL OR
164+
al.JobGroupID = @jobGroupId) AND (@customisationId IS NULL OR
165+
al.CustomisationID = @customisationId) AND (@courseCategoryId IS NULL OR
166+
al.CourseCategoryID = @courseCategoryId) AND (al.Registered = 1 OR
167+
al.Completed = 1 OR
168+
al.Evaluated = 1) AND EXISTS
169+
(SELECT ApplicationID
170+
FROM Applications AS ap
171+
WHERE (ApplicationID = al.ApplicationID) AND (DefaultContentTypeID <> 4))
172+
ORDER BY al.LogDate DESC
173+
OFFSET @exportQueryRowLimit * (@currentRun - 1) ROWS
174+
FETCH NEXT @exportQueryRowLimit ROWS ONLY"
175+
,
176+
new
177+
{
178+
centreId,
179+
startDate,
180+
endDate,
181+
jobGroupId,
182+
customisationId,
183+
courseCategoryId,
184+
exportQueryRowLimit,
185+
currentRun
186+
}
187+
);
188+
}
78189
public DateTime? GetStartOfActivityForCentre(int centreId, int? courseCategoryId = null)
79190
{
80191
return connection.QuerySingleOrDefault<DateTime?>(
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
namespace DigitalLearningSolutions.Data.Models.TrackingSystem
2+
{
3+
using System;
4+
5+
public class ActivityLogDetail
6+
{
7+
public DateTime LogDate { get; set; }
8+
public string? CourseName { get; set; }
9+
public string? CustomisationName { get; set; }
10+
public string? FirstName { get; set; }
11+
public string? LastName { get; set; }
12+
public string? PrimaryEmail { get; set; }
13+
public string? DelegateId { get; set; }
14+
public string? Answer1 { get; set; }
15+
public string? Answer2 { get; set; }
16+
public string? Answer3 { get; set; }
17+
public string? Answer4 { get; set; }
18+
public string? Answer5 { get; set; }
19+
public string? Answer6 { get; set; }
20+
public bool Registered { get; set; }
21+
public bool Completed { get; set; }
22+
public bool Evaluated { get; set; }
23+
}
24+
}

DigitalLearningSolutions.Web.Tests/Services/ActivityServiceTests.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using FluentAssertions;
1919
using FluentAssertions.Execution;
2020
using NUnit.Framework;
21+
using Microsoft.Extensions.Configuration;
2122

2223
public class ActivityServiceTests
2324
{
@@ -33,7 +34,8 @@ public class ActivityServiceTests
3334
private ICommonService commonService = null!;
3435
private IClockUtility clockUtility = null!;
3536
private IReportFilterService reportFilterService = null!;
36-
37+
private IConfiguration configuration = null!;
38+
private ICentreRegistrationPromptsService registrationPromptsService = null!;
3739
[SetUp]
3840
public void SetUp()
3941
{
@@ -46,12 +48,16 @@ public void SetUp()
4648
selfAssessmentDataService = A.Fake<ISelfAssessmentDataService>();
4749
commonService = A.Fake<ICommonService>();
4850
clockUtility = A.Fake<IClockUtility>();
51+
configuration = A.Fake<IConfiguration>();
52+
registrationPromptsService = A.Fake<ICentreRegistrationPromptsService>();
4953
activityService = new ActivityService(
5054
activityDataService,
5155
jobGroupsDataService,
5256
courseCategoriesDataService,
5357
courseDataService,
54-
clockUtility
58+
clockUtility,
59+
configuration,
60+
registrationPromptsService
5561
);
5662
reportFilterService = new ReportFilterService(
5763
courseCategoriesDataService,
Binary file not shown.

DigitalLearningSolutions.Web/Controllers/TrackingSystem/Delegates/ActivityDelegatesController.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,15 @@
1313
using DigitalLearningSolutions.Web.Models.Enums;
1414
using DigitalLearningSolutions.Web.ServiceFilter;
1515
using DigitalLearningSolutions.Web.Services;
16-
using DigitalLearningSolutions.Web.ViewModels.Supervisor;
1716
using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates;
1817
using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.Delegates.CourseDelegates;
1918
using Microsoft.AspNetCore.Authorization;
2019
using Microsoft.AspNetCore.Mvc;
2120
using Microsoft.Extensions.Configuration;
2221
using Microsoft.FeatureManagement.Mvc;
23-
using Pipelines.Sockets.Unofficial;
2422
using System;
2523
using System.Collections.Generic;
2624
using System.Linq;
27-
using System.Net;
2825

2926
[FeatureGate(FeatureFlags.RefactoredTrackingSystem)]
3027
[Authorize(Policy = CustomPolicies.UserCentreAdmin)]

DigitalLearningSolutions.Web/Services/ActivityService.cs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
using DigitalLearningSolutions.Data.DataServices;
99
using DigitalLearningSolutions.Data.Enums;
1010
using DigitalLearningSolutions.Data.Helpers;
11-
using DigitalLearningSolutions.Data.Models.Courses;
1211
using DigitalLearningSolutions.Data.Models.TrackingSystem;
1312
using DigitalLearningSolutions.Data.Utilities;
13+
using Microsoft.Extensions.Configuration;
1414

1515
public interface IActivityService
1616
{
@@ -27,28 +27,44 @@ int centreId
2727

2828
public class ActivityService : IActivityService
2929
{
30-
private const string SheetName = "Usage Statistics";
30+
private const string SheetName = "Usage summary";
3131
private static readonly XLTableTheme TableTheme = XLTableTheme.TableStyleLight9;
3232
private readonly IActivityDataService activityDataService;
3333
private readonly ICourseCategoriesDataService courseCategoriesDataService;
3434
private readonly ICourseDataService courseDataService;
3535
private readonly IJobGroupsDataService jobGroupsDataService;
3636
private readonly IClockUtility clockUtility;
37+
private readonly IConfiguration configuration;
38+
private readonly ICentreRegistrationPromptsService registrationPromptsService;
3739

3840
public ActivityService(
3941
IActivityDataService activityDataService,
4042
IJobGroupsDataService jobGroupsDataService,
4143
ICourseCategoriesDataService courseCategoriesDataService,
4244
ICourseDataService courseDataService,
43-
IClockUtility clockUtility
45+
IClockUtility clockUtility,
46+
IConfiguration configuration,
47+
ICentreRegistrationPromptsService registrationPromptsService
4448
)
4549
{
4650
this.activityDataService = activityDataService;
4751
this.jobGroupsDataService = jobGroupsDataService;
4852
this.courseCategoriesDataService = courseCategoriesDataService;
4953
this.courseDataService = courseDataService;
5054
this.clockUtility = clockUtility;
55+
this.configuration = configuration;
56+
this.registrationPromptsService = registrationPromptsService;
5157
}
58+
public int GetActivityDetailRowCount(int centreId, ActivityFilterData filterData)
59+
{
60+
return activityDataService.GetActivityDetailRowCount(centreId,
61+
filterData.StartDate,
62+
filterData.EndDate,
63+
filterData.JobGroupId,
64+
filterData.CourseCategoryId,
65+
filterData.CustomisationId);
66+
}
67+
5268
public IEnumerable<PeriodOfActivity> GetFilteredActivity(int centreId, ActivityFilterData filterData)
5369
{
5470
var activityData = activityDataService
@@ -146,7 +162,7 @@ public byte[] GetActivityDataFileForCentre(int centreId, ActivityFilterData filt
146162
var table = sheet.Cell(1, 1).InsertTable(workbookData);
147163
table.Theme = TableTheme;
148164
sheet.Columns().AdjustToContents();
149-
165+
AddActivityDetailSheet(workbook, centreId, filterData);
150166
using var stream = new MemoryStream();
151167
workbook.SaveAs(stream);
152168
return stream.ToArray();
@@ -207,6 +223,53 @@ ReportInterval interval
207223
)
208224
);
209225
}
226+
private void AddActivityDetailSheet(XLWorkbook workbook, int centreId, ActivityFilterData filterData)
227+
{
228+
var itemsPerPage = Data.Extensions.ConfigurationExtensions.GetExportQueryRowLimit(configuration);
229+
var resultCount = GetActivityDetailRowCount(centreId, filterData);
230+
if (resultCount == 0)
231+
{
232+
return;
233+
}
234+
int totalRun = (int)(resultCount / itemsPerPage) + ((resultCount % itemsPerPage) > 0 ? 1 : 0);
235+
int currentRun = 1;
236+
List<ActivityLogDetail> activityLogDetails = new List<ActivityLogDetail>();
237+
while (totalRun >= currentRun)
238+
{
239+
activityLogDetails.AddRange(activityDataService.GetFilteredActivityDetail(
240+
centreId,
241+
filterData.StartDate,
242+
filterData.EndDate,
243+
filterData.JobGroupId,
244+
filterData.CourseCategoryId,
245+
filterData.CustomisationId,
246+
itemsPerPage,
247+
currentRun));
248+
currentRun++;
249+
}
250+
var customRegistrationPrompts = registrationPromptsService.GetCentreRegistrationPromptsByCentreId(centreId);
251+
var sheet = workbook.Worksheets.Add("Usage detail");
252+
var table = sheet.Cell(1, 1).InsertTable(activityLogDetails);
253+
table.Theme = TableTheme;
254+
table.Field(0).Name = "Date";
255+
foreach (var prompt in customRegistrationPrompts.CustomPrompts)
256+
{
257+
var promptName = prompt.PromptText;
258+
var fieldNum = prompt.RegistrationField.ToString().Last();
259+
var promptLabel = "Answer" + fieldNum;
260+
table.Field(promptLabel).Name = promptName;
261+
}
262+
for (int i = 1; i < 7; i++)
263+
{
264+
var answerLabel = "Answer" + i.ToString();
265+
266+
if (table.Fields.Any(f => f.Name == answerLabel))
267+
{
268+
table.Field(answerLabel).Delete();
269+
}
270+
}
271+
sheet.Columns().AdjustToContents();
272+
}
210273
private static int GetFirstMonthOfQuarter(int quarter)
211274
{
212275
return quarter * 3 - 2;

0 commit comments

Comments
 (0)