Skip to content

Commit 707bb0a

Browse files
committed
Track student and class analytics events
1 parent f19575c commit 707bb0a

10 files changed

Lines changed: 160 additions & 9 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/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_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/jobs/create_students_job.rb

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ class ConcurrencyExceededForSchool < StandardError; end
2626

2727
queue_as :create_students_job
2828

29-
def self.attempt_perform_later(school_id:, students:, token:)
30-
perform_later(school_id:, students:, token:)
29+
def self.attempt_perform_later(school_id:, students:, token:, actor_user_id: nil)
30+
args = { school_id:, students:, token: }
31+
args[:actor_user_id] = actor_user_id if actor_user_id.present?
32+
33+
perform_later(**args)
3134
end
3235

33-
def perform(school_id:, students:, token:)
36+
def perform(school_id:, students:, token:, actor_user_id: nil)
3437
school = School.find(school_id)
3538
decrypted_students = StudentHelpers.decrypt_students(students)
3639
SafeguardingFlagService.create_for_token(token:, school:)
@@ -39,6 +42,22 @@ def perform(school_id:, students:, token:)
3942

4043
responses[:created].each do |user_id|
4144
Role.student.create!(school_id:, user_id:)
45+
track_student_created(school_id:, student_id: user_id, actor_user_id:)
4246
end
4347
end
48+
49+
private
50+
51+
def track_student_created(school_id:, student_id:, actor_user_id:)
52+
return if actor_user_id.blank?
53+
54+
EventTracker.track!(
55+
name: 'Student - Created',
56+
user_id: actor_user_id,
57+
properties: {
58+
school_id:,
59+
student_id:
60+
}
61+
)
62+
end
4463
end

lib/concepts/school_student/create_batch.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ class Error < StandardError; end
55

66
class CreateBatch
77
class << self
8-
def call(school:, school_students_params:, token:)
8+
def call(school:, school_students_params:, token:, actor_user_id: nil)
99
response = OperationResponse.new
10-
response[:job_id] = create_batch(school, school_students_params, token)
10+
response[:job_id] = create_batch(school, school_students_params, token, actor_user_id)
1111
response
1212
rescue CreateStudentsJob::ConcurrencyExceededForSchool => e
1313
response[:error] = e
@@ -22,8 +22,8 @@ def call(school:, school_students_params:, token:)
2222

2323
private
2424

25-
def create_batch(school, students, token)
26-
job = CreateStudentsJob.attempt_perform_later(school_id: school.id, students:, token:)
25+
def create_batch(school, students, token, actor_user_id)
26+
job = CreateStudentsJob.attempt_perform_later(school_id: school.id, students:, token:, actor_user_id:)
2727
job&.job_id
2828
end
2929
end

spec/features/class_member/creating_a_batch_of_class_members_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@
6161
expect(data).to include({ success: true, user_id: class_member[:user_id] })
6262
end
6363
end
64+
65+
it 'records class student added events for the student members only' do
66+
post("/api/schools/#{school.id}/classes/#{school_class.id}/members/batch", headers:, params:)
67+
68+
events = Event.where(name: 'Class - Student added').order(:time)
69+
expect(events.count).to eq(3)
70+
expect(events.map(&:user_id)).to all(eq(teacher.id))
71+
expect(events.map(&:properties)).to match_array(
72+
students.map do |student|
73+
{
74+
'school_id' => school.id,
75+
'class_id' => school_class.id,
76+
'student_id' => student.id
77+
}
78+
end
79+
)
80+
end
6481
end
6582

6683
context 'when adding an owner as another teacher' do

spec/features/class_member/creating_a_class_member_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,21 @@
6060
expect(response_student).to eq(student_attributes)
6161
end
6262

63+
it 'records a class student added event' do
64+
post("/api/schools/#{school.id}/classes/#{school_class.id}/members", headers:, params: student_params)
65+
66+
expect(Event.last).to have_attributes(
67+
name: 'Class - Student added',
68+
user_id: owner.id,
69+
properties: {
70+
'school_id' => school.id,
71+
'class_id' => school_class.id,
72+
'student_id' => student.id
73+
},
74+
time: be_within(1.second).of(Time.current)
75+
)
76+
end
77+
6378
context 'when the student is already in the class' do
6479
before do
6580
school_class.students.create!({ student_id: student.id })
@@ -126,6 +141,12 @@
126141
expect(response_teacher).to eq(teacher_attributes)
127142
end
128143

144+
it 'does not record a class student added event' do
145+
expect do
146+
post("/api/schools/#{school.id}/classes/#{school_class.id}/members", headers:, params: teacher_params)
147+
end.not_to change(Event, :count)
148+
end
149+
129150
context 'when the teacher is already in the class' do
130151
before do
131152
school_class.teachers.create!({ teacher_id: another_teacher.id })

spec/features/school_class/importing_a_school_class_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,20 @@
301301
# Class members should be assigned successfully
302302
expect(response_data[:class_members]).to be_an(Array)
303303
expect(response_data[:class_members].length).to eq(2)
304+
305+
student_events = Event.where(name: 'Student - Created')
306+
expect(student_events.map(&:properties)).to contain_exactly(
307+
{ 'school_id' => school.id, 'class_id' => response_data[:school_class][:id], 'student_id' => 'student-1-id' },
308+
{ 'school_id' => school.id, 'class_id' => response_data[:school_class][:id], 'student_id' => 'student-2-id' }
309+
)
310+
expect(student_events).to all(have_attributes(user_id: teacher.id, time: be_within(1.second).of(Time.current)))
311+
312+
class_member_events = Event.where(name: 'Class - Student added')
313+
expect(class_member_events.map(&:properties)).to contain_exactly(
314+
{ 'school_id' => school.id, 'class_id' => response_data[:school_class][:id], 'student_id' => 'student-1-id' },
315+
{ 'school_id' => school.id, 'class_id' => response_data[:school_class][:id], 'student_id' => 'student-2-id' }
316+
)
317+
expect(class_member_events).to all(have_attributes(user_id: teacher.id, time: be_within(1.second).of(Time.current)))
304318
end
305319

306320
it 'allows re-importing the same class (finds existing class)' do

spec/features/school_student/creating_a_school_student_spec.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,23 @@
3838
expect(response).to have_http_status(:no_content)
3939
end
4040

41+
it 'records a student created event' do
42+
student_id = SecureRandom.uuid
43+
stub_profile_api_create_school_student(user_id: student_id)
44+
45+
post("/api/schools/#{school.id}/students", headers:, params:)
46+
47+
expect(Event.last).to have_attributes(
48+
name: 'Student - Created',
49+
user_id: owner.id,
50+
properties: {
51+
'school_id' => school.id,
52+
'student_id' => student_id
53+
},
54+
time: be_within(1.second).of(Time.current)
55+
)
56+
end
57+
4158
it 'responds 204 No Content when the user is a school-teacher' do
4259
teacher = create(:teacher, school:)
4360
authenticated_in_hydra_as(teacher)

spec/jobs/create_students_job_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
let(:token) { UserProfileMock::TOKEN }
99
let(:school) { create(:verified_school) }
1010
let(:user_id) { create(:user).id }
11+
let(:actor_user_id) { create(:owner, school:).id }
1112

1213
let(:students) do
1314
[{
@@ -46,4 +47,18 @@
4647

4748
expect(Role.student.where(school:, user_id:)).to exist
4849
end
50+
51+
it 'records a student created event when the actor is provided' do
52+
described_class.perform_now(school_id: school.id, students:, token:, actor_user_id:)
53+
54+
expect(Event.last).to have_attributes(
55+
name: 'Student - Created',
56+
user_id: actor_user_id,
57+
properties: {
58+
'school_id' => school.id,
59+
'student_id' => user_id
60+
},
61+
time: be_within(1.second).of(Time.current)
62+
)
63+
end
4964
end

0 commit comments

Comments
 (0)