Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c856c78
student name is appearing on the cce minor progress table
Kafui123 Jan 14, 2026
321f9b9
hardcoding engagementcount information
Kafui123 Jan 16, 2026
26d5fa2
added my name as a test subject
Kafui123 Jan 16, 2026
afafe59
Added a function to allow students who have no individual requirement…
Kafui123 Jan 20, 2026
d12c524
Removed the print statements
Kafui123 Jan 20, 2026
519ce53
Checked to see if my tests worked
Kafui123 Jan 20, 2026
db749fb
Addressed PR comments
Kafui123 Jan 22, 2026
f241e33
Merge remote-tracking branch 'origin/development' into 1660CCE_MinorS…
Kafui123 Jan 22, 2026
3aa0245
Students with a requirement in the cceminor table now have a declared…
Kafui123 Jan 22, 2026
210a6e9
All students with a requirement also have a declared status
Kafui123 Jan 27, 2026
2ae342b
Merge remote-tracking branch 'origin/development' into 1660CCE_MinorS…
Kafui123 Jan 27, 2026
0621b36
Tests are all passing now
Kafui123 Jan 28, 2026
c1eddbc
added an actions column and a remove from minor button in the cce min…
Kafui123 Jan 29, 2026
77c6d50
Email logic and remove from minor logic works
Kafui123 Jan 29, 2026
9f4cb9a
reformatted the changes
Kafui123 Feb 2, 2026
5f5b1cc
addressed pr comments
Kafui123 Feb 2, 2026
cbdda37
changed remove from minor button to remove
Kafui123 Feb 5, 2026
22a694d
added a docstring comment
Kafui123 Feb 9, 2026
1788137
Merge remote-tracking branch 'origin' into 1660CCE_MinorStudents_Not_…
Kafui123 Feb 10, 2026
69c9f80
removed the resource reader import
Kafui123 Feb 10, 2026
07b58f0
Merge branch 'development' into 1660CCE_MinorStudents_Not_Showing
BrianRamsay Feb 17, 2026
7c66c9d
Fixed the semantic bug regarding the data aggregation for individual …
Feb 20, 2026
cdb2513
Implemented the corrected logic for calculating the summer engagement…
Feb 20, 2026
c687bfe
Implemented efficient set lookup in manageMinor()
Feb 20, 2026
0ad9ddb
Removed old getDeclaredMinorStudents() function
Feb 26, 2026
f33d9ba
fixed the -1 logic
Kafui123 Feb 27, 2026
ac5e855
refactored new changes
Kafui123 Mar 10, 2026
37a9768
Merge branch 'development' of https://github.com/BCStudentSoftwareDev…
JohnCox2211 Mar 10, 2026
7e5aee8
changes
Kafui123 Mar 10, 2026
7a1f4e2
changes
Kafui123 Mar 10, 2026
bd4da1e
changes added
Kafui123 Mar 10, 2026
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
15 changes: 7 additions & 8 deletions app/controllers/admin/minor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from app.controllers.admin import admin_bp

from app.logic.minor import getMinorInterest, getMinorProgress, toggleMinorInterest, getMinorSpreadsheet, getDeclaredMinorStudents
from app.logic.minor import getDeclaredMinorStudents, getMinorInterest, getMinorProgress, toggleMinorInterest, getMinorSpreadsheet

@admin_bp.route('/admin/cceMinor', methods=['GET','POST'])
def manageMinor():
Expand All @@ -20,20 +20,19 @@ def manageMinor():

return redirect(url_for("admin.manageMinor"))



interestedStudentsList = getMinorInterest()
interestedStudentEmailString = ';'.join([student['email'] for student in interestedStudentsList])
sustainedEngagement = getMinorProgress()
declaredStudentsList = getDeclaredMinorStudents()
declaredStudentEmailString = ';'.join([student['email'] for student in declaredStudentsList])
declaredStudentsDict = getDeclaredMinorStudents()
declaredStudentEmailString = ';'.join([student['email'] for student in declaredStudentsDict])

cceMinorStudents = declaredStudentsDict


return render_template('/admin/cceMinor.html',
cceMinorStudents = cceMinorStudents,
interestedStudentsList = interestedStudentsList,
declaredStudentsList = declaredStudentsList,
interestedStudentEmailString = interestedStudentEmailString,
declaredStudentEmailString = declaredStudentEmailString,
sustainedEngagement = sustainedEngagement,
)

@admin_bp.route("/admin/cceMinor/download")
Expand Down
74 changes: 69 additions & 5 deletions app/logic/minor.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ def getMinorProgress():
.join(IndividualRequirement, on=(User.username == IndividualRequirement.username))
.join(CertificationRequirement, on=(IndividualRequirement.requirement_id == CertificationRequirement.id))
.switch(User).join(CCEMinorProposal, JOIN.LEFT_OUTER, on= (User.username == CCEMinorProposal.student))
.where(CertificationRequirement.certification_id == Certification.CCE)
.where(
(CertificationRequirement.certification_id == Certification.CCE) &
(User.declaredMinor == True)
)
.group_by(User.firstName, User.lastName, User.username)
.order_by(SQL("engagementCount").desc())
)
Expand Down Expand Up @@ -184,13 +187,71 @@ def declareMinorInterest(username):

def getDeclaredMinorStudents():
"""
Get a list of the students who have declared minor
This function retrieves a list of students who have declared the CCE minor along with their engagement progress.
It returns a list of dictionaries containing student information and their engagement details and adds students who have no requirements but have declared the minor with 0 engagements.
Comment thread
Kafui123 marked this conversation as resolved.
"""
declaredStudents = User.select().where(User.isStudent & User.declaredMinor)
summerEngagementCount = fn.COUNT(
fn.DISTINCT(
Case(
None,
[(CCEMinorProposal.proposalType == "Summer Experience", CCEMinorProposal.id)],
None
)
)
).alias("summerEngagementCount")

# this returns the count of distinct engagements that have a certification requirement id.
# this is important because our join clause specifically joins individualrequirements with the certifications that match to CCE
# while leaving the rest as null
cceEngagementCount = fn.COUNT(
fn.DISTINCT(
Case(
None,
[(CertificationRequirement.id.is_null(False), IndividualRequirement.id)],
None
)
)
).alias("allEngagementCount")

q = (
User
.select(
User,
cceEngagementCount,
summerEngagementCount,
fn.IF(fn.COUNT(fn.DISTINCT(CCEMinorProposal.id)) > 0, True, False).alias("hasCCEMinorProposal"),
)
.join(IndividualRequirement, JOIN.LEFT_OUTER, on=(User.username == IndividualRequirement.username))
.join(CertificationRequirement, JOIN.LEFT_OUTER, on=(
(IndividualRequirement.requirement_id == CertificationRequirement.id) &
(CertificationRequirement.certification_id == Certification.CCE) # only cce minor certs are populated with non-null
))
.switch(User)
.join(CCEMinorProposal, JOIN.LEFT_OUTER, on=(User.username == CCEMinorProposal.student))
.where(
(User.declaredMinor == True) &
(User.isStudent == True)
)
.group_by(User.username)
.order_by(SQL("allEngagementCount").desc())
)

interestedStudentList = [model_to_dict(student) for student in declaredStudents]
result = []
for s in q:
engagementCount = int(s.allEngagementCount or 0)
Comment thread
Kafui123 marked this conversation as resolved.
result.append({
"firstName": s.firstName,
"lastName": s.lastName,
"username": s.username,
"B-Number": s.bnumber,
"email": s.email,
"hasGraduated": s.hasGraduated,
"engagementCount": engagementCount,
"hasCCEMinorProposal": bool(s.hasCCEMinorProposal),
"hasSummer": "Completed" if (s.summerEngagementCount and int(s.summerEngagementCount) > 0) else "Incomplete",
})
Comment on lines +239 to +252
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

The allEngagementCount computed in getDeclaredMinorStudents() counts all IndividualRequirement entries linked to any CCE CertificationRequirement, which includes the "Summer Program" requirement (id=16). However, the summer count is not subtracted from allEngagementCount before it is returned as engagementCount.

In getMinorProgress(), this is handled correctly by computing engagementCount - hasSummer (line 112 in the same file), where hasSummer counts summer CCEMinorProposal entries. Without the same subtraction here, a student who has a Summer Program IndividualRequirement record will have their engagementCount (displayed as "X/4 Sustained Engagements") inflated by 1, potentially incorrectly showing the completion icon (fa-circle) at the wrong threshold.

The fix is to subtract summerEngagementCount from allEngagementCount when building the result, similar to how getMinorProgress() does it: engagementCount = int(s.allEngagementCount or 0) - int(s.summerEngagementCount or 0).

Copilot uses AI. Check for mistakes.

return interestedStudentList
return result
Comment on lines 188 to +254
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

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

The PR description states: "Created a new query function called getDeclaredMinorStudentsWithProgress()", but the actual changes modify the existing getDeclaredMinorStudents() function rather than creating a new one with a different name. The PR description is inaccurate about what was implemented.

Copilot uses AI. Check for mistakes.

def getCourseInformation(id):
"""
Expand Down Expand Up @@ -269,6 +330,7 @@ def setCommunityEngagementForUser(action, engagementData, currentUser):
"requirement": requirement.get(),
"addedBy": currentUser,
})

# Thrown if there are no available engagement requirements left. Handled elsewhere.
except DoesNotExist as e:
raise e
Expand All @@ -279,6 +341,7 @@ def setCommunityEngagementForUser(action, engagementData, currentUser):
IndividualRequirement.username == engagementData['username'],
IndividualRequirement.term == engagementData['term']
).execute()

else:
raise Exception(f"Invalid action '{action}' sent to setCommunityEngagementForUser")

Expand Down Expand Up @@ -374,6 +437,7 @@ def saveSummerExperience(username, summerExperience, currentUser):
"requirement": requirement.get(),
"addedBy": currentUser,
})

return ""

def getSummerExperience(username):
Expand Down
6 changes: 3 additions & 3 deletions app/static/js/minorAdminPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ $('.remove_minor_candidate').on('click', function() {
emailMinorCandidates($("#declaredStudentEmails").val())
});

$('#emailAll').on('click', emailAll);

$(".updateMinorInterestButton").on("click", function(e){
$('#emailAll').on('click', emailAll);
// this was done to fix a bug where the declare student button would not work on the second page.
Comment thread
JohnCox2211 marked this conversation as resolved.
$(document).on("click", ".updateMinorInterestButton", function(e){
e.preventDefault();
let interestForm = $("#updateMinorInterestForm");
Comment thread
Kafui123 marked this conversation as resolved.
let url = $(this).data("url");
Expand Down
21 changes: 11 additions & 10 deletions app/templates/admin/cceMinor.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ <h2>CCE Minor Progress</h2>
<th>Sustained Engagements</th>
<th>Summer Experience</th>
<th>Requested Other Engagement</th>
<th>Remove from Minor</th>
</tr>
</thead>
<tbody>
{% for student in sustainedEngagement %}
{% for student in cceMinorStudents %}
<tr>
<td>
{% from 'macros/graduationIconMacro.html' import graduationIcon %}
Expand All @@ -57,6 +58,12 @@ <h2>CCE Minor Progress</h2>
No
{% endif %}
</td>
<td>
<button class="btn btn-danger btn-sm remove_minor_candidate"
id="{{student.username}}"
aria-label="remove minor candidate">Remove
</button>
</td>
</tr>
{% endfor %}
</tbody>
Expand Down Expand Up @@ -90,23 +97,17 @@ <h2>CCE Minor Candidates</h2>
'id': 'interested',
'label': 'Interested',
'students': interestedStudentsList,
'emailString': interestedStudentEmailString,
'actionLabel': 'Declare Student',
'alwaysShow': true
Comment thread
Kafui123 marked this conversation as resolved.
},
{
'id': 'declared',
'label': 'Declared',
'students': declaredStudentsList,
'emailString': declaredStudentEmailString,
'actionLabel': 'Move To Interested',
'alwaysShow': true
}
] %}
{% set activeTab = request.args.get('tab', 'interested') %}
{{ minorCandidatesTab(tabsInfo, defaultActiveTab=activeTab )}}
</div>

<input type="hidden" id="interestedStudentEmails" value="{{ interestedStudentEmailString }}">

<input type="hidden" id="declaredStudentEmails" value="{{ declaredStudentEmailString }}">
<!-- ################# Modal ################# -->

<div class="modal fade" id="addInterestedStudentsModal" tabindex="-1" aria-labelledby="addInterestedStudentsLbl" aria-hidden="true">
Expand Down
8 changes: 1 addition & 7 deletions app/templates/macros/minorCandidatesTabsMacro.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,10 @@
data-url="/profile/{{student.username}}/updateMinorDeclaration"
id="{{student.username}}"
data-username="{{student.username}}">{{ tab.actionLabel }}
</button>
<button type="button"
class="remove_minor_candidate btn btn-danger btn-sm"
id="{{student.username}}"
aria-label="remove minor candidate">Remove
</button>
</button>
</td>
</tr>
{% endfor %}
<input id="{{ tab.id }}StudentEmails" hidden value="{{ tab.emailString }}"/>
</tbody>
</table>
</div>
Expand Down
22 changes: 22 additions & 0 deletions database/test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@
"rawClassLevel": "Senior",
"minorInterest": None,
},
{
"username" : "glek",
"bnumber" : "B00792345",
"email": "glek@berea.edu",
"phoneNumber": "(555)579-5555",
"firstName" : "Kafui",
"lastName" : "Gle",
"isStudent": True,
"isFaculty": False,
"isCeltsAdmin": False,
"isCeltsStudentStaff": False,
"major": "Computer Science",
"rawClassLevel": "Junior",
"minorInterest": None,
},
{
"username" : "michels",
"bnumber" : "B00781963",
Expand Down Expand Up @@ -1027,7 +1042,14 @@
]

IndividualRequirement.insert_many(individualReqs).on_conflict_replace().execute()
# This ensures every user with an individual requirement has declaredMinor set to True so that when our query runs it can find users with a "declaredMinor = True"
req_usernames = {r["username"] for r in individualReqs if r.get("username")}

(User
.update({User.declaredMinor: True})
.where(User.username.in_(req_usernames))
.execute()
)

Comment thread
Kafui123 marked this conversation as resolved.
courseInstructorRecords = [
{
Expand Down
2 changes: 2 additions & 0 deletions tests/code/test_graduationManagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ def test_getGraduationManagementUsers():

IndividualRequirement.create(**sustainedEngagement)

testUser3.declaredMinor = True
testUser3.save()
actualResult = getGraduationManagementUsers()

# testUser4 is not a senior, graduating so they should not be shown.
Expand Down
Loading