Skip to content

Commit a393215

Browse files
Merge pull request #554 from TechnologyEnhancedLearning/HEEDLS-432-CentreCourseSetupSearchAndPagination
HEEDLS-432 - Centre Course Setup search sort filter and paginate impl…
2 parents e8a5697 + 5c0db1c commit a393215

File tree

27 files changed

+864
-25
lines changed

27 files changed

+864
-25
lines changed

DigitalLearningSolutions.Data.Tests/DataServices/CourseDataServiceTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ public void GetCourseStatisticsAtCentreForCategoryID_should_return_course_statis
242242
CompletedCount = 5,
243243
HideInLearnerPortal = false,
244244
CategoryName = "Office 2007",
245+
CourseTopic = "Microsoft Office",
245246
LearningMinutes = "N/A"
246247
};
247248

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
namespace DigitalLearningSolutions.Data.Tests.DataServices
2+
{
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using DigitalLearningSolutions.Data.DataServices;
6+
using DigitalLearningSolutions.Data.Models.Common;
7+
using DigitalLearningSolutions.Data.Tests.TestHelpers;
8+
using FluentAssertions;
9+
using NUnit.Framework;
10+
11+
public class CourseTopicsDataServiceTests
12+
{
13+
private CourseTopicsDataService courseTopicsDataService = null!;
14+
15+
[SetUp]
16+
public void Setup()
17+
{
18+
var connection = ServiceTestHelper.GetDatabaseConnection();
19+
courseTopicsDataService = new CourseTopicsDataService(connection);
20+
}
21+
22+
[Test]
23+
public void GetTopicsAvailableAtCentre_should_return_expected_items()
24+
{
25+
// Given
26+
var expectedTopics = new List<Topic>
27+
{
28+
new Topic { CourseTopic = "Digital Skills", CourseTopicID = 2, Active = true},
29+
new Topic { CourseTopic = "Excel", CourseTopicID = 5, Active = true},
30+
new Topic { CourseTopic = "Microsoft Office", CourseTopicID = 3, Active = true},
31+
new Topic { CourseTopic = "OneNote", CourseTopicID = 10, Active = true},
32+
new Topic { CourseTopic = "Outlook", CourseTopicID = 7, Active = true},
33+
new Topic { CourseTopic = "PowerPoint", CourseTopicID = 6, Active = true},
34+
new Topic { CourseTopic = "SharePoint", CourseTopicID = 9, Active = true},
35+
new Topic { CourseTopic = "Social Media", CourseTopicID = 8, Active = true},
36+
new Topic { CourseTopic = "Undefined", CourseTopicID = 1, Active = true},
37+
new Topic { CourseTopic = "Word", CourseTopicID = 4, Active = true}
38+
};
39+
40+
// When
41+
var result = courseTopicsDataService.GetCourseTopicsAvailableAtCentre(101).ToList();
42+
43+
// Then
44+
result.Should().BeEquivalentTo(expectedTopics);
45+
}
46+
}
47+
}

DigitalLearningSolutions.Data/DataServices/CourseDataService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,13 @@ public IEnumerable<CourseStatistics> GetCourseStatisticsAtCentreForCategoryId(in
187187
{AttemptsPassedQuery},
188188
cu.HideInLearnerPortal,
189189
cc.CategoryName,
190+
ct.CourseTopic,
190191
cu.LearningTimeMins AS LearningMinutes
191192
FROM dbo.Customisations AS cu
192193
INNER JOIN dbo.CentreApplications AS ca ON ca.ApplicationID = cu.ApplicationID
193194
INNER JOIN dbo.Applications AS ap ON ap.ApplicationID = ca.ApplicationID
194195
INNER JOIN dbo.CourseCategories AS cc ON cc.CourseCategoryID = ap.CourseCategoryID
196+
INNER JOIN dbo.CourseTopics AS ct ON ct.CourseTopicID = ap.CourseTopicId
195197
WHERE (ap.CourseCategoryID = @categoryId OR @categoryId = 0)
196198
AND (cu.CentreID = @centreId OR (cu.AllCentres = 1 AND ca.Active = 1))
197199
AND ca.CentreID = @centreId
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
namespace DigitalLearningSolutions.Data.DataServices
2+
{
3+
using System.Collections.Generic;
4+
using System.Data;
5+
using Dapper;
6+
using DigitalLearningSolutions.Data.Models.Common;
7+
8+
public interface ICourseTopicsDataService
9+
{
10+
IEnumerable<Topic> GetCourseTopicsAvailableAtCentre(int centreId);
11+
}
12+
13+
public class CourseTopicsDataService : ICourseTopicsDataService
14+
{
15+
private readonly IDbConnection connection;
16+
17+
public CourseTopicsDataService(IDbConnection connection)
18+
{
19+
this.connection = connection;
20+
}
21+
22+
public IEnumerable<Topic> GetCourseTopicsAvailableAtCentre(int centreId)
23+
{
24+
return connection.Query<Topic>(
25+
@"SELECT CourseTopicID, CourseTopic, Active
26+
FROM CourseTopics
27+
WHERE (CentreID = @CentreID OR CentreID = 0) AND (Active = 1)
28+
ORDER BY CourseTopic",
29+
new { centreId }
30+
);
31+
}
32+
}
33+
}

DigitalLearningSolutions.Data/Models/Courses/CourseStatistics.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{
33
using System;
44

5-
public class CourseStatistics
5+
public class CourseStatistics : BaseSearchableItem
66
{
77
public int CustomisationId { get; set; }
88
public int CentreId { get; set; }
@@ -17,11 +17,19 @@ public class CourseStatistics
1717
public int AttemptsPassed { get; set; }
1818
public bool HideInLearnerPortal { get; set; }
1919
public string CategoryName { get; set; }
20+
public string CourseTopic { get; set; }
2021
public string LearningMinutes { get; set; }
2122

2223
public string CourseName => string.IsNullOrWhiteSpace(CustomisationName)
2324
? ApplicationName
2425
: ApplicationName + " - " + CustomisationName;
26+
2527
public double PassRate => AllAttempts == 0 ? 0 : Math.Round(100 * AttemptsPassed / (double)AllAttempts);
28+
29+
public override string SearchableName
30+
{
31+
get => SearchableNameOverrideForFuzzySharp ?? CourseName;
32+
set => SearchableNameOverrideForFuzzySharp = value;
33+
}
2634
}
2735
}

DigitalLearningSolutions.Web.Tests/Controllers/TrackingSystem/Centre/Administrator/AdministratorControllerTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public void Index_with_null_filterBy_and_new_filter_query_parameter_add_new_cook
134134
public void Index_with_CLEAR_filterBy_and_new_filter_query_parameter_sets_new_cookie_value()
135135
{
136136
// Given
137-
const string? filterBy = null;
137+
const string? filterBy = "CLEAR";
138138
const string? newFilterValue = "Role|IsCmsManager|true";
139139

140140
// When
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
namespace DigitalLearningSolutions.Web.Tests.Controllers.TrackingSystem.CourseSetup
2+
{
3+
using System.Collections.Generic;
4+
using DigitalLearningSolutions.Data.DataServices;
5+
using DigitalLearningSolutions.Data.Models.Common;
6+
using DigitalLearningSolutions.Data.Models.Courses;
7+
using DigitalLearningSolutions.Data.Services;
8+
using DigitalLearningSolutions.Web.Controllers.TrackingSystem.CourseSetup;
9+
using DigitalLearningSolutions.Web.Tests.ControllerHelpers;
10+
using DigitalLearningSolutions.Web.ViewModels.TrackingSystem.CourseSetup;
11+
using FakeItEasy;
12+
using FluentAssertions;
13+
using Microsoft.AspNetCore.Http;
14+
using Microsoft.AspNetCore.Mvc;
15+
using NUnit.Framework;
16+
17+
public class CourseSetupControllerTests
18+
{
19+
private readonly List<Category> categories = new List<Category>
20+
{
21+
new Category { CategoryName = "Category 1" },
22+
new Category { CategoryName = "Category 2" }
23+
};
24+
25+
private readonly List<CourseStatistics> courses = new List<CourseStatistics>
26+
{
27+
new CourseStatistics
28+
{
29+
ApplicationName = "Course",
30+
CustomisationName = "Customisation",
31+
Active = true,
32+
CourseTopic = "Topic 1",
33+
CategoryName = "Category 1",
34+
HideInLearnerPortal = true,
35+
DelegateCount = 1,
36+
CompletedCount = 1
37+
}
38+
};
39+
40+
private readonly List<Topic> topics = new List<Topic>
41+
{
42+
new Topic { CourseTopic = "Topic 1" },
43+
new Topic { CourseTopic = "Topic 2" }
44+
};
45+
46+
private CourseSetupController controller = null!;
47+
private ICourseCategoriesDataService courseCategoryDataService = null!;
48+
private ICourseService courseService = null!;
49+
private ICourseTopicsDataService courseTopicsDataService = null!;
50+
private HttpRequest httpRequest = null!;
51+
private HttpResponse httpResponse = null!;
52+
53+
[SetUp]
54+
public void Setup()
55+
{
56+
courseCategoryDataService = A.Fake<ICourseCategoriesDataService>();
57+
courseTopicsDataService = A.Fake<ICourseTopicsDataService>();
58+
courseService = A.Fake<ICourseService>();
59+
60+
A.CallTo(() => courseService.GetCentreSpecificCourseStatistics(A<int>._, A<int>._)).Returns(courses);
61+
A.CallTo(() => courseCategoryDataService.GetCategoriesForCentreAndCentrallyManagedCourses(A<int>._))
62+
.Returns(categories);
63+
A.CallTo(() => courseTopicsDataService.GetCourseTopicsAvailableAtCentre(A<int>._)).Returns(topics);
64+
65+
httpRequest = A.Fake<HttpRequest>();
66+
httpResponse = A.Fake<HttpResponse>();
67+
const string cookieName = "CourseFilter";
68+
const string cookieValue = "Status|Active|false";
69+
70+
controller = new CourseSetupController(courseService, courseCategoryDataService, courseTopicsDataService)
71+
.WithMockHttpContextWithCookie(httpRequest, cookieName, cookieValue, httpResponse)
72+
.WithMockUser(true)
73+
.WithMockTempData();
74+
}
75+
76+
[Test]
77+
public void Index_with_no_query_parameters_uses_cookie_value_for_filterBy()
78+
{
79+
// When
80+
var result = controller.Index();
81+
82+
// Then
83+
result.As<ViewResult>().Model.As<CourseSetupViewModel>().FilterBy.Should()
84+
.Be("Status|Active|false");
85+
}
86+
87+
[Test]
88+
public void Index_with_query_parameters_uses_query_parameter_value_for_filterBy()
89+
{
90+
// Given
91+
const string filterBy = "Status|HideInLearnerPortal|true";
92+
A.CallTo(() => httpRequest.Query.ContainsKey("filterBy")).Returns(true);
93+
94+
// When
95+
var result = controller.Index(filterBy: filterBy);
96+
97+
// Then
98+
result.As<ViewResult>().Model.As<CourseSetupViewModel>().FilterBy.Should()
99+
.Be(filterBy);
100+
}
101+
102+
[Test]
103+
public void Index_with_CLEAR_filterBy_query_parameter_removes_cookie()
104+
{
105+
// Given
106+
const string filterBy = "CLEAR";
107+
108+
// When
109+
var result = controller.Index(filterBy: filterBy);
110+
111+
// Then
112+
A.CallTo(() => httpResponse.Cookies.Delete("CourseFilter")).MustHaveHappened();
113+
result.As<ViewResult>().Model.As<CourseSetupViewModel>().FilterBy.Should()
114+
.BeNull();
115+
}
116+
117+
[Test]
118+
public void Index_with_null_filterBy_and_new_filter_query_parameter_adds_new_cookie_value()
119+
{
120+
// Given
121+
const string? filterBy = null;
122+
const string newFilterValue = "Status|HideInLearnerPortal|true";
123+
A.CallTo(() => httpRequest.Query.ContainsKey("filterBy")).Returns(true);
124+
125+
// When
126+
var result = controller.Index(filterBy: filterBy, filterValue: newFilterValue);
127+
128+
// Then
129+
A.CallTo(() => httpResponse.Cookies.Append("CourseFilter", newFilterValue, A<CookieOptions>._))
130+
.MustHaveHappened();
131+
result.As<ViewResult>().Model.As<CourseSetupViewModel>().FilterBy.Should()
132+
.Be(newFilterValue);
133+
}
134+
135+
[Test]
136+
public void Index_with_CLEAR_filterBy_and_new_filter_query_parameter_sets_new_cookie_value()
137+
{
138+
// Given
139+
const string filterBy = "CLEAR";
140+
const string newFilterValue = "Status|HideInLearnerPortal|true";
141+
142+
// When
143+
var result = controller.Index(filterBy: filterBy, filterValue: newFilterValue);
144+
145+
// Then
146+
A.CallTo(() => httpResponse.Cookies.Append("CourseFilter", newFilterValue, A<CookieOptions>._))
147+
.MustHaveHappened();
148+
result.As<ViewResult>().Model.As<CourseSetupViewModel>().FilterBy.Should()
149+
.Be(newFilterValue);
150+
}
151+
152+
[Test]
153+
public void Index_with_no_filtering_should_default_to_Active_courses()
154+
{
155+
// Given
156+
var controllerWithNoCookies = new CourseSetupController(
157+
courseService,
158+
courseCategoryDataService,
159+
courseTopicsDataService
160+
)
161+
.WithDefaultContext()
162+
.WithMockUser(true);
163+
164+
// When
165+
var result = controllerWithNoCookies.Index();
166+
167+
// Then
168+
result.As<ViewResult>().Model.As<CourseSetupViewModel>().FilterBy.Should()
169+
.Be("Status|Active|true");
170+
}
171+
}
172+
}

0 commit comments

Comments
 (0)