Skip to content

Commit e58f41d

Browse files
authored
Merge pull request #5581 from lyttam/BOAC-6703
BOAC-6703: limits peer advisor search to PA notes and comments
2 parents f4b4269 + 496c661 commit e58f41d

7 files changed

Lines changed: 206 additions & 18 deletions

File tree

boac/api/notes_controller.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,10 @@ def add_note_comment():
130130
parent_note = Note.find_by_id(note_id=parent_note_id)
131131
if not parent_note or not can_current_user_access_note(parent_note):
132132
raise ResourceNotFoundError('Note not found')
133-
pam_membership = get_department_membership_with_role(current_user, 'peer_advisor_manager')
134-
peer_advising_department_id = pam_membership.get('peerAdvisingDepartmentId', None) if pam_membership else None
133+
peer_advising_department_id = None
134+
if (parent_note.peer_advising_department_id):
135+
pam_membership = get_department_membership_with_role(current_user, 'peer_advisor_manager')
136+
peer_advising_department_id = pam_membership.get('peerAdvisingDepartmentId', None) if pam_membership else None
135137
comment = create_note_comment(parent_note, params, peer_advising_department_id)
136138
return tolerant_jsonify(get_boac_note_as_compatible_json(note=comment, note_read=True))
137139

boac/api/search_controller.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,27 @@ def search_peer_advising_notes():
166166
)
167167

168168
# The front-end needs full-blown note objects because they are editable by the current-user.
169-
note_ids = [n['id'] for n in search_results.get('notes', [])]
169+
note_ids = []
170+
for n in search_results.get('notes', []):
171+
note_ids.append(n['id'])
172+
if n['parentNoteId']:
173+
note_ids.append(n['parentNoteId'])
170174
notes_json = []
171-
if note_ids:
172-
notes = Note.find_by_ids(note_ids)
173-
all_sids = [note.sid for note in notes]
175+
if len(note_ids):
176+
notes_and_comments = Note.find_peer_notes_by_ids(peer_advising_department_id, note_ids)
177+
notes = []
178+
comments_by_note_id = {}
179+
append_note = notes.append
180+
181+
def append_comment(comment):
182+
comment_json = comment.to_api_json()
183+
comments_by_note_id.setdefault(comment.parent_note_id, []).append({
184+
**comment_json,
185+
'author': get_note_author_summary(comment_json),
186+
})
187+
for item in notes_and_comments:
188+
(append_comment if item.parent_note_id else append_note)(item)
189+
all_sids = list({note.sid for note in notes})
174190
students_by_sid = {s['sid']: s for s in get_basic_student_data(sids=all_sids)}
175191

176192
for note in notes:
@@ -179,6 +195,7 @@ def search_peer_advising_notes():
179195
notes_json.append({
180196
**note_json,
181197
'author': get_note_author_summary(note_json),
198+
'comments': comments_by_note_id.get(note.id, []),
182199
'student': {
183200
'sid': student['sid'],
184201
'uid': student['uid'],

boac/models/note.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,15 @@ def find_by_ids(cls, note_ids):
130130
criteria = and_(cls.id.in_(note_ids), cls.deleted_at == None) # noqa: E711
131131
return cls.query.filter(criteria).all()
132132

133+
@classmethod
134+
def find_peer_notes_by_ids(cls, peer_advising_department_id, note_ids):
135+
criteria = and_(
136+
cls.id.in_(note_ids),
137+
cls.peer_advising_department_id == peer_advising_department_id,
138+
cls.deleted_at == None, # noqa: E711
139+
)
140+
return cls.query.filter(criteria).all()
141+
133142
@classmethod
134143
def get_notes_by_parent_id(cls, parent_note_id):
135144
criteria = and_(cls.parent_note_id == parent_note_id, cls.deleted_at == None) # noqa: E711

src/stores/search.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export const useSearchStore: StoreDefinition = defineStore('search', {
5454
this.includeCourses = currentUser.canAccessCanvasData
5555
this.includeNotes = currentUser.canAccessAdvisingData
5656
this.includeStudents = true
57+
this.isSearching = false
5758
},
5859
resetAutocompleteInput() {this.autocompleteInputResetKey++},
5960
setAuthor(value: string | null) {this.author = value},

tests/conftest.py

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,25 @@ def mock_advising_note(app, db):
291291
std_commit(allow_test_environment=True)
292292

293293

294+
@pytest.fixture
295+
def mock_ce3_advising_note(app, db):
296+
"""Create CE3 advising note with attachment (mock s3)."""
297+
note = _create_mock_note(
298+
app=app,
299+
attachment='fixtures/mock_advising_note_attachment_1.txt',
300+
author_dept_codes=['ZCEEE'],
301+
author_uid='5405613',
302+
db=db,
303+
topics=['Other / Reason not listed'],
304+
)
305+
Note.refresh_search_index()
306+
std_commit(allow_test_environment=True)
307+
yield note
308+
Note.delete(note_id=note.id)
309+
Note.refresh_search_index()
310+
std_commit(allow_test_environment=True)
311+
312+
294313
@pytest.fixture
295314
def mock_advising_note_with_comments(app, db, fake_auth, mock_advising_note):
296315
"""Create advising note with comments."""
@@ -365,6 +384,7 @@ def mock_note_draft(app, db):
365384
)
366385
yield note
367386
Note.delete(note_id=note.id)
387+
Note.refresh_search_index()
368388
std_commit(allow_test_environment=True)
369389

370390

@@ -446,19 +466,21 @@ def mock_eop_peer_advising_note(fake_auth, db):
446466
topics=['topic1', 'topic2', 'topic3'],
447467
)
448468
db.session.add(note)
469+
Note.refresh_search_index()
449470
std_commit(allow_test_environment=True)
450471
logout_user()
451472

452473
yield Note.find_by_id(note.id)
453474

454475
Note.delete(note.id)
476+
Note.refresh_search_index()
455477
std_commit(allow_test_environment=True)
456478

457479

458480
@pytest.fixture
459481
def mock_navcal_peer_advising_note(fake_auth, db):
460482
"""Create a CE3 NAVCAL Peer Advising Note."""
461-
peer_advisor_author_uid = '1133400'
483+
peer_advisor_author_uid = '188444'
462484
fake_auth.login(peer_advisor_author_uid)
463485

464486
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
@@ -475,20 +497,53 @@ def mock_navcal_peer_advising_note(fake_auth, db):
475497
topics=['topic1', 'topic2', 'topic3'],
476498
)
477499
db.session.add(note)
500+
Note.refresh_search_index()
478501
std_commit(allow_test_environment=True)
479502
logout_user()
480503

481504
yield Note.find_by_id(note.id)
482505

483506
Note.delete(note.id)
507+
Note.refresh_search_index()
508+
std_commit(allow_test_environment=True)
509+
510+
511+
@pytest.fixture
512+
def mock_navcal_peer_advising_manager_note(fake_auth, db):
513+
"""CE3 NAVCAL Peer Advising Note created by a Peer Advising Manager."""
514+
peer_advising_manager_uid = '2525'
515+
fake_auth.login(peer_advising_manager_uid)
516+
517+
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
518+
# Create the note
519+
note = Note.create(
520+
author_uid=peer_advising_manager_uid,
521+
author_name='Grigsby Columbo',
522+
author_role='peer_advisor_manager',
523+
author_dept_codes=[],
524+
sid='9000000000',
525+
body='Whoopsy daisy yoo hoo!',
526+
peer_advising_department_id=navcal_department.id,
527+
subject='Test Note',
528+
topics=['topic1', 'topic2', 'topic3'],
529+
)
530+
db.session.add(note)
531+
Note.refresh_search_index()
532+
std_commit(allow_test_environment=True)
533+
logout_user()
534+
535+
yield Note.find_by_id(note.id)
536+
537+
Note.delete(note.id)
538+
Note.refresh_search_index()
484539
std_commit(allow_test_environment=True)
485540

486541

487542
@pytest.fixture
488543
def mock_navcal_peer_advising_note_with_comments(fake_auth, db, mock_navcal_peer_advising_note):
489544
"""Create a NAVCAL Peer Advising Note with comments."""
490-
navcal_peer_advisor_author_uid = '1133400'
491-
navcal_peer_advisor_uid = '188444'
545+
navcal_peer_advisor_author_uid = '188444'
546+
navcal_peer_advisor_uid = '1133400'
492547
navcal_peer_advising_manager_uid = '2525'
493548
mech_eng_peer_advising_manager_uid = '1133399'
494549
non_pam_advisor_uid = '242881'
@@ -559,26 +614,27 @@ def mock_navcal_peer_advising_note_with_comments(fake_auth, db, mock_navcal_peer
559614
db.session.add(advisor_comment)
560615
logout_user()
561616

562-
# Add a comment from non-Peer Advising Manager
617+
# Add a comment from an advisor who is not a Peer Advisor Manager
563618
fake_auth.login(non_pam_advisor_uid)
564619
advisor_comment = Note.create(
565620
author_uid=non_pam_advisor_uid,
566-
author_name='Joni Mitchell',
567-
author_role='peer_advisor_manager',
621+
author_name='Mort Korn',
622+
author_role='advisor',
568623
author_dept_codes=[],
569624
sid=mock_navcal_peer_advising_note.sid,
570-
body='A comment from a different department.',
625+
body='A comment from a regular non-PAM advisor.',
571626
parent_note_id=mock_navcal_peer_advising_note.id,
572627
peer_advising_department_id=None,
573628
subject='',
574629
)
575630
db.session.add(advisor_comment)
576631
logout_user()
577-
632+
Note.refresh_search_index()
578633
std_commit(allow_test_environment=True)
579634
yield Note.find_by_id(mock_navcal_peer_advising_note.id)
580635

581636
Note.delete(mock_navcal_peer_advising_note.id)
637+
Note.refresh_search_index()
582638
std_commit(allow_test_environment=True)
583639

584640

@@ -593,8 +649,11 @@ def mock_private_advising_note(app, db):
593649
db=db,
594650
is_private=True,
595651
)
652+
Note.refresh_search_index()
653+
std_commit(allow_test_environment=True)
596654
yield note
597655
Note.delete(note_id=note.id)
656+
Note.refresh_search_index()
598657
std_commit(allow_test_environment=True)
599658

600659

@@ -668,6 +727,7 @@ def _create_mock_note(
668727
)
669728
author_id = AuthorizedUser.get_id_per_uid(author_uid)
670729
note_reads = NoteRead.find_or_create(author_id, [note.id])
730+
Note.refresh_search_index()
671731
db.session.add(note)
672732
db.session.add(note_reads[0])
673733
std_commit(allow_test_environment=True)

tests/test_api/test_peer_advising_notes_controller.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,7 @@ def test_peer_advisor_note_comment(self, client, fake_auth, mock_navcal_peer_adv
374374

375375
# Another Peer Advisor comments on the note
376376
body = 'A very interesting comment!'
377-
fake_auth.login(ce3_navcal_peer_advisor_2_uid)
377+
fake_auth.login(ce3_navcal_peer_advisor_uid)
378378
api_json = self._api_add_note_comment(
379379
body=body,
380380
client=client,
@@ -498,8 +498,8 @@ def test_authorized_peer_advisor_note_comment(self, client, fake_auth, mock_navc
498498
# Comment author edits the comment
499499
body = 'A very interesting comment!'
500500
comments = Note.get_notes_by_parent_id(mock_navcal_peer_advising_note_with_comments.id)
501-
fake_auth.login(ce3_navcal_peer_advisor_2_uid)
502-
comment = next((c for c in comments if c.author_uid == ce3_navcal_peer_advisor_2_uid), None)
501+
fake_auth.login(ce3_navcal_peer_advisor_uid)
502+
comment = next((c for c in comments if c.author_uid == ce3_navcal_peer_advisor_uid), None)
503503
api_json = self._api_edit_note_comment(
504504
comment_id=comment.id,
505505
body=body,

tests/test_api/test_peer_advising_search_controller.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
from boac.models.peer_advising_department import PeerAdvisingDepartment
2929

3030
ce3_navcal_peer_advisor_uid = '1133400'
31+
ce3_pam_advisor_uid = '2525'
32+
ce3_non_pam_advisor_uid = '5405613'
33+
coe_mech_peer_advisor_uid = '1913062'
34+
eop_peer_advisor_uid = '1563405'
3135

3236

3337
class TestPeerAdvisingNoteSearch:
@@ -49,13 +53,108 @@ def test_peer_advising_search_with_missing_input_no_options(self, client, fake_a
4953
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
5054
_api_search(client, ' \t ', peer_advising_department_id=navcal_department.id, expected_status_code=400)
5155

52-
def test_peer_advising_search_includes_notes_if_requested(self, client, fake_auth):
56+
def test_peer_advising_search_notes_no_notes(self, client, fake_auth):
5357
"""Does not include any notes if the notes do not exist."""
5458
fake_auth.login(ce3_navcal_peer_advisor_uid)
5559
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
60+
api_json = _api_search(client, 'Anastasia', peer_advising_department_id=navcal_department.id)
61+
self._assert(api_json, note_count=0, student_count=0)
62+
63+
def test_peer_advising_search_notes_no_match(
64+
self,
65+
client,
66+
fake_auth,
67+
mock_navcal_peer_advising_note, # noqa: ARG002
68+
mock_navcal_peer_advising_note_with_comments, # noqa: ARG002
69+
):
70+
"""Does not include any notes if none match the search query."""
71+
fake_auth.login(ce3_navcal_peer_advisor_uid)
72+
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
5673
api_json = _api_search(client, 'Brigitte', peer_advising_department_id=navcal_department.id)
5774
self._assert(api_json, note_count=0, student_count=0)
5875

76+
def test_peer_advising_search_includes_peer_notes(
77+
self,
78+
client,
79+
fake_auth,
80+
mock_navcal_peer_advising_note, # noqa: ARG002
81+
mock_navcal_peer_advising_note_with_comments, # noqa: ARG002
82+
):
83+
"""Includes notes created by other peer advisors in the same department."""
84+
fake_auth.login(ce3_navcal_peer_advisor_uid)
85+
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
86+
api_json = _api_search(client, 'Anastasia', peer_advising_department_id=navcal_department.id)
87+
self._assert(api_json, note_count=1, student_count=0)
88+
89+
api_json = _api_search(client, 'a comment', peer_advising_department_id=navcal_department.id)
90+
self._assert(api_json, note_count=1, student_count=0)
91+
92+
def test_peer_advising_search_includes_pam_notes(self, client, fake_auth, mock_navcal_peer_advising_manager_note): # noqa: ARG002
93+
"""Includes notes created by a Peer Advisor Manager in the same department."""
94+
fake_auth.login(ce3_navcal_peer_advisor_uid)
95+
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
96+
api_json = _api_search(client, 'daisy', peer_advising_department_id=navcal_department.id)
97+
self._assert(api_json, note_count=1, student_count=0)
98+
99+
def test_search_excludes_foreign_dept_peer_notes(
100+
self,
101+
client,
102+
fake_auth,
103+
mock_navcal_peer_advising_note, # noqa: ARG002
104+
mock_navcal_peer_advising_note_with_comments, # noqa: ARG002
105+
):
106+
"""Does not include notes or comments created by peer advisors outside the department."""
107+
fake_auth.login(eop_peer_advisor_uid)
108+
eop_department = PeerAdvisingDepartment.get_department_by_name('Educational Opportunity Program')
109+
api_json = _api_search(client, 'Anastasia', peer_advising_department_id=eop_department.id)
110+
self._assert(api_json, note_count=0, student_count=0)
111+
112+
api_json = _api_search(client, 'comment', peer_advising_department_id=eop_department.id)
113+
self._assert(api_json, note_count=0, student_count=0)
114+
115+
def test_excludes_foreign_dept_pam_notes(self, client, fake_auth, mock_navcal_peer_advising_manager_note): # noqa: ARG002
116+
"""Does not include a note created by Peer Advisor Manager outside the department."""
117+
fake_auth.login(eop_peer_advisor_uid)
118+
eop_department = PeerAdvisingDepartment.get_department_by_name('Educational Opportunity Program')
119+
api_json = _api_search(client, 'daisy', peer_advising_department_id=eop_department.id)
120+
self._assert(api_json, note_count=0, student_count=0)
121+
122+
def test_search_excludes_same_dept_non_pam_note(self, client, fake_auth, mock_ce3_advising_note): # noqa: ARG002
123+
"""Does not include a peer advising note created by a non-PAM advisor in the same department."""
124+
fake_auth.login(ce3_navcal_peer_advisor_uid)
125+
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
126+
api_json = _api_search(client, 'darling', peer_advising_department_id=navcal_department.id)
127+
self._assert(api_json, note_count=0, student_count=0)
128+
129+
fake_auth.login(eop_peer_advisor_uid)
130+
eop_department = PeerAdvisingDepartment.get_department_by_name('Educational Opportunity Program')
131+
api_json = _api_search(client, 'darling', peer_advising_department_id=eop_department.id)
132+
self._assert(api_json, note_count=0, student_count=0)
133+
134+
def test_search_excludes_pam_comment_foreign_dept_peer_note(
135+
self,
136+
client,
137+
fake_auth,
138+
mock_navcal_peer_advising_note_with_comments, # noqa: ARG002
139+
):
140+
"""Does not include a peer advising note from a different department and commented on by Peer Advisor Manager in the same department."""
141+
fake_auth.login(coe_mech_peer_advisor_uid)
142+
mech_eng_department = PeerAdvisingDepartment.get_department_by_name('Mechanical Engineering')
143+
api_json = _api_search(client, 'a comment', peer_advising_department_id=mech_eng_department.id)
144+
self._assert(api_json, note_count=0, student_count=0)
145+
146+
def test_search_excludes_pam_comment_non_peer_note(
147+
self,
148+
client,
149+
fake_auth,
150+
mock_advising_note_with_comments, # noqa: ARG002
151+
):
152+
"""Does not include a non-peer note commented on by a Peer Advisor Manager in the same department."""
153+
fake_auth.login(ce3_navcal_peer_advisor_uid)
154+
navcal_department = PeerAdvisingDepartment.get_department_by_name('NAVCAL')
155+
api_json = _api_search(client, 'a comment', peer_advising_department_id=navcal_department.id)
156+
self._assert(api_json, note_count=0, student_count=0)
157+
59158

60159
def _api_search(
61160
client,

0 commit comments

Comments
 (0)