Skip to content

Commit f34f8cb

Browse files
authored
Add remaining tracking events (#877)
## Status - Closes RaspberryPiFoundation/digital-editor-issues#1486 ### What have changed? This PR introduces a centralized EventTracker service and instruments multiple API endpoints/jobs to record consistent analytics events (project lifecycle events within classroom context, and school/class membership events). It extends student batch creation to attribute “Student - Created” events to the initiating user, and adds broad spec coverage to assert the new event records. Changes: Added EventTracker service with helper logic to derive classroom metadata (school_id, class_id, lesson_id, project_type, user_role, optional student_id) and record Event rows. Instrumented controllers and CreateStudentsJob to emit events such as “Project - Opened/Saved/Created”, “Project - Submitted for review/Marked as completed/Made visible”, “Student - Created”, and “Class - Student added”. Added/updated request/feature/job/service specs to validate recorded event names, users, properties, and timestamps. ## Events not tracked: - Student - Log in Login likely needs to be tracked where authentication/session creation actually happens. - Project - Ran code - Explicitly excluded because it is covered by a separate issue. - School - Created- Explicitly excluded from this PR. - School - Created metadata for landing page / campaign parameters - Not implemented because the issue notes that data is not currently captured and needs a separate story. ## Events Tracked Implemented: - `Project - Created` - `Project - Made visible` - `Student - Created` - `Class - Student added` - `Project - Feedback given` - `Project - Marked as completed` - `Project - Opened` - `Project - Saved` - `Project - Submitted for review` Metadata coverage: - All new events go through `EventTracker`, so they get `user_id` and `time`. - All include `school_id`. - Project events include `class_id`, `lesson_id`, and `project_type`. - Project events also include `user_role` where relevant. - Teacher/educator interactions with student projects include `student_id`. - `Student - Created` includes `student_id`; it includes `class_id` when created through a class import, but standalone/batch student creation has no class context available.
1 parent 47f1b61 commit f34f8cb

32 files changed

Lines changed: 600 additions & 14 deletions

app/controllers/api/class_members_controller.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def create
3737

3838
if result.success?
3939
@class_member = result[:class_members].first
40+
track_class_students_added([@class_member])
4041
render :show, formats: [:json], status: :created
4142
else
4243
render json: result.slice(:error, :errors), status: :unprocessable_content
@@ -55,6 +56,7 @@ def create_batch
5556
if result.failure? && result[:error].include?('No valid school members provided')
5657
render json: result, status: :unprocessable_content
5758
else
59+
track_class_students_added(result[:class_members])
5860
successful = result[:class_members].map { |m| { success: true, user_id: m.user_id } }
5961
errors = result[:errors].map { |user_id, error| { success: false, user_id:, error: } }
6062
render json: successful + errors
@@ -115,5 +117,19 @@ def list_teachers(school, teacher_ids)
115117
{ school_teachers: [] }
116118
end
117119
end
120+
121+
def track_class_students_added(class_members)
122+
Array(class_members).grep(ClassStudent).each do |class_student|
123+
student_id = class_student.student_id || class_student.student&.id
124+
next if student_id.blank?
125+
126+
track_event(
127+
'Class - Student added',
128+
school_id: @school.id,
129+
class_id: @school_class.id,
130+
student_id:
131+
)
132+
end
133+
end
118134
end
119135
end

app/controllers/api/feedback_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def create
2424

2525
if result.success?
2626
@feedback = result[:feedback]
27+
track_project_event('Project - Feedback given', project)
2728
render :show, formats: [:json], status: :created
2829
else
2930
render json: { error: result[:error] }, status: :unprocessable_content

app/controllers/api/lessons/batch_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def create_batch
1818
lessons_params: batch_lessons_params
1919
)
2020
@user = current_user
21+
@results.select(&:success?).each { |result| track_project_event('Project - Created', result[:lesson].project) }
2122
render :create_batch, formats: [:json], status: :created
2223
end
2324

app/controllers/api/lessons_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def show
3333
def create
3434
result = Lesson::Create.call(lesson_params: create_params)
3535
if result.success?
36+
track_project_event('Project - Created', result[:lesson].project)
3637
@lesson_with_user = result[:lesson].with_user
3738
render :show, formats: [:json], status: :created
3839
else
@@ -54,9 +55,11 @@ def create_copy
5455
def update
5556
# TODO: Consider removing user_id from the lesson_params for update so users can update other users' lessons without changing ownership
5657
# OR consider dropping user_id on lessons and using teacher id/ids on the class instead
58+
previous_visibility = @lesson.visibility
5759
result = Lesson::Update.call(lesson: @lesson, lesson_params: update_params)
5860

5961
if result.success?
62+
track_project_event('Project - Made visible', result[:lesson].project) if previous_visibility != 'students' && result[:lesson].visibility == 'students'
6063
@lesson_with_user = result[:lesson].with_user
6164
render :show, formats: [:json], status: :ok
6265
else

app/controllers/api/projects/remixes_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def create
3636

3737
if result.success?
3838
@project = result[:project]
39+
track_project_event('Project - Saved', @project)
3940
render '/api/projects/show', formats: [:json]
4041
else
4142
render json: { error: result[:error] }, status: :bad_request

app/controllers/api/projects_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ def index
1717
end
1818

1919
def show
20+
track_project_event('Project - Opened', @project) if current_user.present?
21+
2022
if !@project.school_id.nil? && @project.lesson_id.nil?
2123
project_with_user = @project.with_student(current_user)
2224
@user = project_with_user[1]
@@ -31,6 +33,7 @@ def create
3133

3234
if result.success?
3335
@project = result[:project]
36+
track_project_event('Project - Created', @project)
3437
render :show, formats: [:json], status: :created
3538
else
3639
render json: { error: result[:error] }, status: :unprocessable_content
@@ -41,6 +44,7 @@ def update
4144
result = Project::Update.call(project: @project, update_hash: project_params, current_user:)
4245

4346
if result.success?
47+
track_project_event('Project - Saved', @project)
4448
render :show, formats: [:json]
4549
else
4650
render json: { error: result[:error] }, status: :unprocessable_content

app/controllers/api/school_classes_controller.rb

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def create_school_students(school_students_params, school_class)
157157
{
158158
school_students: school_students_result[:school_students],
159159
errors: school_students_result[:errors]
160-
}
160+
}.tap { track_created_school_students(school_students_result[:school_students], class_id: school_class.id) }
161161
end
162162

163163
def assign_students_to_class(school_class, school_students)
@@ -172,6 +172,7 @@ def assign_students_to_class(school_class, school_students)
172172
{ success: false, student_id: user_id, school_class_id: school_class.id, error: error }
173173
end
174174

175+
track_imported_class_students_added(school_class, class_members_result[:class_members])
175176
class_members_result[:class_members] + class_members_errors
176177
end
177178

@@ -221,5 +222,32 @@ def import_school_students_params
221222
def school_owner?
222223
current_user.school_owner?(@school)
223224
end
225+
226+
def track_created_school_students(school_students, class_id: nil)
227+
Array(school_students).each do |school_student|
228+
next unless school_student[:success] && school_student[:created]
229+
230+
track_event(
231+
'Student - Created',
232+
school_id: @school.id,
233+
class_id:,
234+
student_id: school_student[:student].id
235+
)
236+
end
237+
end
238+
239+
def track_imported_class_students_added(school_class, class_members)
240+
Array(class_members).grep(ClassStudent).each do |class_student|
241+
student_id = class_student.student_id || class_student.student&.id
242+
next if student_id.blank?
243+
244+
track_event(
245+
'Class - Student added',
246+
school_id: @school.id,
247+
class_id: school_class.id,
248+
student_id:
249+
)
250+
end
251+
end
224252
end
225253
end

app/controllers/api/school_projects_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ def unsubmit
2323

2424
def submit
2525
authorize! :submit, school_project
26+
previous_status = school_project.status
2627
result = SchoolProject::SetStatus.call(school_project:, status: :submitted, user_id: current_user.id)
2728
if result.success?
2829
@school_project = result[:school_project]
30+
track_project_event('Project - Submitted for review', @school_project.project) if previous_status != 'submitted'
2931
render :show_status, formats: [:json], status: :ok
3032
else
3133
render json: { error: result[:error] }, status: :unprocessable_content
@@ -45,9 +47,11 @@ def return
4547

4648
def complete
4749
authorize! :complete, school_project
50+
previous_status = school_project.status
4851
result = SchoolProject::SetStatus.call(school_project:, status: :complete, user_id: current_user.id)
4952
if result.success?
5053
@school_project = result[:school_project]
54+
track_project_event('Project - Marked as completed', @school_project.project) if previous_status != 'complete'
5155
render :show_status, formats: [:json], status: :ok
5256
else
5357
render json: { error: result[:error] }, status: :unprocessable_content

app/controllers/api/school_students_controller.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def create
2727
)
2828

2929
if result.success?
30+
track_event('Student - Created', school_id: @school.id, student_id: result[:student_id]) if result[:student_id].present?
3031
head :no_content
3132
else
3233
render json: { error: result[:error] }, status: :unprocessable_content
@@ -86,7 +87,10 @@ def enqueue_batches(students)
8687
@batch.enqueue do
8788
students.each_slice(MAX_BATCH_CREATION_SIZE) do |student_batch|
8889
SchoolStudent::CreateBatch.call(
89-
school: @school, school_students_params: student_batch, token: current_user.token
90+
school: @school,
91+
school_students_params: student_batch,
92+
token: current_user.token,
93+
actor_user_id: current_user.id
9094
)
9195
end
9296
end

app/controllers/api/scratch/projects_controller.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def create
2727
scratch_component.content = scratch_content_params
2828
existing_remix.save!
2929
move_assets_uploaded_by_current_user_before_remix(original_project:, remix_project: existing_remix)
30+
track_project_event('Project - Saved', existing_remix)
3031

3132
return render json: { status: 'ok', 'content-name': existing_remix.identifier }, status: :ok
3233
end
@@ -42,6 +43,7 @@ def create
4243

4344
if result.success?
4445
move_assets_uploaded_by_current_user_before_remix(original_project:, remix_project: result[:project])
46+
track_project_event('Project - Saved', result[:project])
4547
render json: { status: 'ok', 'content-name': result[:project].identifier }, status: :ok
4648
else
4749
render json: { error: result[:error] }, status: :bad_request
@@ -51,6 +53,7 @@ def create
5153
def update
5254
@project.scratch_component&.content = scratch_content_params
5355
@project.save!
56+
track_project_event('Project - Saved', @project)
5457
render json: { status: 'ok' }, status: :ok
5558
end
5659

0 commit comments

Comments
 (0)