Skip to content

Commit c8018a9

Browse files
Return project status and unread feedback count (#633)
## Status - API part for RaspberryPiFoundation/digital-editor-issues#955 ## What's changed? - Return `status` and `has_unread_feedback` on student lessons - Add tests
1 parent 2f69649 commit c8018a9

File tree

5 files changed

+168
-16
lines changed

5 files changed

+168
-16
lines changed

app/controllers/api/lessons_controller.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,16 @@ def user_remixes(lessons)
9191
end
9292

9393
def user_remix(lesson)
94-
lesson.project&.remixes
95-
&.where(user_id: current_user.id)
96-
&.accessible_by(current_ability)
97-
&.order(created_at: :asc)
98-
&.first
94+
remixes = lesson&.project&.remixes
95+
96+
remixes = remixes
97+
.where(user_id: current_user.id)
98+
.accessible_by(current_ability)
99+
.order(created_at: :asc)
100+
101+
remixes = remixes.includes(school_project: :feedback) if current_user&.school_student?(school)
102+
103+
remixes.first
99104
end
100105

101106
def lesson_params

app/models/school_project.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ def transition_status_to!(new_status, user_id)
1919
state_machine.transition_to!(new_status, metadata: { changed_by: user_id })
2020
end
2121

22+
def unread_feedback?
23+
feedback.exists?(read_at: nil)
24+
end
25+
2226
# Add convenience methods for each state
2327
def unsubmitted?
2428
state_machine.in_state?(:unsubmitted)

app/views/api/lessons/student_index.json.jbuilder

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ json.array!(@lessons_with_users_and_remixes) do |lesson_with_user, remix|
44
lesson, user = lesson_with_user
55
json.partial! 'lesson', lesson: lesson, user: user
66
json.remix_identifier(remix.identifier) if remix.present?
7+
if remix.present?
8+
json.status(remix.school_project&.status)
9+
json.has_unread_feedback(remix.school_project&.unread_feedback?)
10+
end
711
end

spec/features/lesson/listing_lessons_spec.rb

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
before do
77
authenticated_in_hydra_as(owner)
88
stub_user_info_api_for(teacher)
9+
create(:class_student, school_class:, student_id: student.id)
910
end
1011

1112
let(:headers) { { Authorization: UserProfileMock::TOKEN } }
@@ -199,6 +200,19 @@
199200
let!(:lesson) { create(:lesson, school_class:, name: 'Test Lesson', visibility: 'students', user_id: teacher.id) }
200201
let(:teacher) { create(:teacher, school:) }
201202

203+
let(:student_project) do
204+
create(
205+
:project,
206+
school:,
207+
lesson:,
208+
parent: lesson.project,
209+
remixed_from_id: lesson.project.id,
210+
user_id: student.id
211+
)
212+
end
213+
214+
let(:school_project) { student_project.school_project }
215+
202216
it 'includes the lesson when the user owns the lesson' do
203217
another_teacher = create(:teacher, school:)
204218
authenticated_in_hydra_as(another_teacher)
@@ -212,8 +226,6 @@
212226

213227
it "includes the lesson when the user is a school-student within the lesson's class" do
214228
authenticated_in_hydra_as(student)
215-
create(:class_student, school_class:, student_id: student.id)
216-
217229
get('/api/lessons', headers:)
218230
data = JSON.parse(response.body, symbolize_names: true)
219231

@@ -222,26 +234,23 @@
222234

223235
it 'does not include the submitted_count when the user is a school-student within the lesson\'s class' do
224236
authenticated_in_hydra_as(student)
225-
create(:class_student, school_class:, student_id: student.id)
226-
227237
get('/api/lessons', headers:)
228238
data = JSON.parse(response.body, symbolize_names: true)
229239
expect(data.first).not_to have_key(:submitted_count)
230240
end
231241

232242
it "includes the remix identifier when the user has remixed the lesson's project" do
233-
student = create(:student, school:)
234243
authenticated_in_hydra_as(student)
235-
create(:class_student, school_class:, student_id: student.id)
236-
student_project = create(:project, school:, lesson:, parent: lesson.project, user_id: student.id)
244+
student_project = create(:project, school:, lesson:, parent: lesson.project, remixed_from_id: lesson.project.id, user_id: student.id)
237245

238246
get('/api/lessons', headers:)
239247
data = JSON.parse(response.body, symbolize_names: true)
240248
expect(data.first[:remix_identifier]).to eq(student_project.identifier)
241249
end
242250

243251
it "does not include the lesson when the user is not a school-student within the lesson's class" do
244-
authenticated_in_hydra_as(student)
252+
another_student = create(:student, school:)
253+
authenticated_in_hydra_as(another_student)
245254

246255
get('/api/lessons', headers:)
247256
data = JSON.parse(response.body, symbolize_names: true)
@@ -258,5 +267,91 @@
258267

259268
expect(data.size).to eq(0)
260269
end
270+
271+
it 'includes has_unread_feedback as true when there is unread feedback' do
272+
authenticated_in_hydra_as(student)
273+
create(
274+
:feedback,
275+
school_project: school_project,
276+
user_id: teacher.id,
277+
content: 'Unread',
278+
read_at: nil
279+
)
280+
281+
create(
282+
:feedback,
283+
school_project: school_project,
284+
user_id: teacher.id,
285+
content: 'Read',
286+
read_at: Time.current
287+
)
288+
289+
get('/api/lessons', headers:)
290+
data = JSON.parse(response.body, symbolize_names: true)
291+
292+
expect(data.first[:has_unread_feedback]).to be(true)
293+
end
294+
295+
it 'includes has_unread_feedback as false when there is no unread feedback' do
296+
authenticated_in_hydra_as(student)
297+
create(
298+
:feedback,
299+
school_project: school_project,
300+
user_id: teacher.id,
301+
content: 'Read',
302+
read_at: Time.current
303+
)
304+
305+
get('/api/lessons', headers:)
306+
data = JSON.parse(response.body, symbolize_names: true)
307+
308+
expect(data.first[:has_unread_feedback]).to be(false)
309+
end
310+
311+
it 'includes status when the user is a student' do
312+
authenticated_in_hydra_as(student)
313+
school_project.transition_status_to!(:submitted, teacher.id)
314+
315+
get('/api/lessons', headers:)
316+
data = JSON.parse(response.body, symbolize_names: true)
317+
318+
expect(data.size).to eq(1)
319+
expect(data.first[:status]).to eq('submitted')
320+
end
321+
322+
it 'includes the default status when no transitions have happened' do
323+
authenticated_in_hydra_as(student)
324+
create(
325+
:project,
326+
school:,
327+
lesson:,
328+
parent: lesson.project,
329+
remixed_from_id: lesson.project.id,
330+
user_id: student.id
331+
)
332+
333+
get('/api/lessons', headers:)
334+
data = JSON.parse(response.body, symbolize_names: true)
335+
336+
expect(data.first[:status]).to eq('unsubmitted')
337+
end
338+
339+
it 'does not include the status when the user is a teacher' do
340+
authenticated_in_hydra_as(teacher)
341+
342+
get('/api/lessons', headers:)
343+
data = JSON.parse(response.body, symbolize_names: true)
344+
345+
expect(data.first).not_to have_key(:status)
346+
end
347+
348+
it 'does not include has_unread_feedback when the user is a teacher' do
349+
authenticated_in_hydra_as(teacher)
350+
351+
get('/api/lessons', headers:)
352+
data = JSON.parse(response.body, symbolize_names: true)
353+
354+
expect(data.first).not_to have_key(:has_unread_feedback)
355+
end
261356
end
262357
end

spec/models/school_project_spec.rb

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44

55
RSpec.describe SchoolProject do
66
let(:state_machine) { instance_double(SchoolProjectStateMachine) }
7-
let(:school_project) { create(:school_project, school:, project:) }
8-
let(:project) { create(:project, school:, user_id: student.id) }
7+
let(:school_class) { create(:school_class, school:, teacher_ids: [teacher.id]) }
8+
let(:lesson) { create(:lesson, school:, school_class:, user_id: teacher.id) }
9+
let(:school_project) { create(:school_project, school:, project: remix) }
910
let(:student) { create(:student, school:) }
1011
let(:school) { create(:school) }
12+
let(:teacher) { create(:teacher, school:) }
13+
let(:teacher_project) { create(:project, school:, user_id: teacher.id, lesson:) }
14+
let(:remix) { create(:project, school:, user_id: student.id, remixed_from_id: teacher_project.id) }
1115

1216
before do
1317
allow(school_project).to receive(:state_machine).and_return(state_machine)
@@ -20,7 +24,7 @@
2024

2125
describe '#status' do
2226
it 'defaults to unsubmitted' do
23-
new_school_project = create(:school_project, school:, project:)
27+
new_school_project = create(:school_project, school:, project: teacher_project)
2428
expect(new_school_project.status).to eq('unsubmitted')
2529
end
2630

@@ -73,4 +77,44 @@
7377
expect(state_machine).to have_received(:history)
7478
end
7579
end
80+
81+
describe '#unread_feedback?' do
82+
it 'returns true if there is unread feedback for the school project' do
83+
create_list(
84+
:feedback,
85+
3,
86+
school_project: school_project,
87+
read_at: nil,
88+
content: 'Unread',
89+
user_id: teacher.id
90+
)
91+
92+
create_list(
93+
:feedback,
94+
2,
95+
school_project: school_project,
96+
read_at: Time.current,
97+
content: 'Read',
98+
user_id: teacher.id
99+
)
100+
101+
expect(school_project.unread_feedback?).to be true
102+
end
103+
104+
it 'returns false if all feedback is read' do
105+
create_list(
106+
:feedback,
107+
2,
108+
school_project: school_project,
109+
read_at: Time.current,
110+
content: 'Read',
111+
user_id: teacher.id
112+
)
113+
expect(school_project.unread_feedback?).to be false
114+
end
115+
116+
it 'returns false when there is no feedback' do
117+
expect(school_project.unread_feedback?).to be false
118+
end
119+
end
76120
end

0 commit comments

Comments
 (0)