From 27a4054dce842e21cd9e2b01b0d49fed9e6668ad Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Thu, 19 Feb 2026 09:12:10 -0700 Subject: [PATCH 01/15] Add wiki_page_published/unpublished live event methods Co-Authored-By: Claude Sonnet 4.6 --- lib/canvas/live_events.rb | 14 ++++++++++++++ spec/lib/canvas/live_events_spec.rb | 30 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/canvas/live_events.rb b/lib/canvas/live_events.rb index b3c4c6592bdca..50811690e2921 100644 --- a/lib/canvas/live_events.rb +++ b/lib/canvas/live_events.rb @@ -630,6 +630,20 @@ def self.wiki_page_deleted(page) }) end + def self.wiki_page_published(page) + post_event_stringified("wiki_page_published", { + wiki_page_id: page.global_id, + title: LiveEvents.truncate(page.title) + }) + end + + def self.wiki_page_unpublished(page) + post_event_stringified("wiki_page_unpublished", { + wiki_page_id: page.global_id, + title: LiveEvents.truncate(page.title) + }) + end + def self.attachment_created(attachment) post_event_stringified("attachment_created", get_attachment_data(attachment)) end diff --git a/spec/lib/canvas/live_events_spec.rb b/spec/lib/canvas/live_events_spec.rb index be5205990fa9b..46e5afe78a5a9 100644 --- a/spec/lib/canvas/live_events_spec.rb +++ b/spec/lib/canvas/live_events_spec.rb @@ -331,6 +331,36 @@ def wiki_page_updated end end + describe ".wiki_page_published" do + before do + course_with_teacher + @page = @course.wiki_pages.create(title: "a page", body: "some body", workflow_state: "unpublished") + end + + it "posts a wiki_page_published event with the page id and title" do + expect_event("wiki_page_published", { + wiki_page_id: @page.global_id.to_s, + title: "a page" + }) + Canvas::LiveEvents.wiki_page_published(@page) + end + end + + describe ".wiki_page_unpublished" do + before do + course_with_teacher + @page = @course.wiki_pages.create(title: "a page", body: "some body") + end + + it "posts a wiki_page_unpublished event with the page id and title" do + expect_event("wiki_page_unpublished", { + wiki_page_id: @page.global_id.to_s, + title: "a page" + }) + Canvas::LiveEvents.wiki_page_unpublished(@page) + end + end + describe ".conversation_forwarded" do before do @user1 = user_model From 2df06d379453fd8d283cc30a05fb9fe996141236 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Thu, 19 Feb 2026 09:19:18 -0700 Subject: [PATCH 02/15] Trigger live events on wiki page publish/unpublish Wire up workflow_state change detection in the after_update observer callback so publishing or unpublishing a wiki page fires the appropriate wiki_page_published/wiki_page_unpublished events. Co-Authored-By: Claude Sonnet 4.6 --- lib/canvas/live_events_callbacks.rb | 9 +++++++++ spec/observers/live_events_observer_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index e531b773edbbc..de640ae585e2c 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -135,6 +135,15 @@ def self.after_update(obj, changes) changes["title"]&.first, changes["body"]&.first) end + if changes["workflow_state"] + _old_state, new_state = changes["workflow_state"] + case new_state + when "active" + Canvas::LiveEvents.wiki_page_published(obj) + when "unpublished" + Canvas::LiveEvents.wiki_page_unpublished(obj) + end + end when Assignment Canvas::LiveEvents.assignment_updated(obj) when AssignmentGroup diff --git a/spec/observers/live_events_observer_spec.rb b/spec/observers/live_events_observer_spec.rb index e69696ba42858..4d54278144572 100644 --- a/spec/observers/live_events_observer_spec.rb +++ b/spec/observers/live_events_observer_spec.rb @@ -106,6 +106,26 @@ expect(Canvas::LiveEvents).to receive(:wiki_page_deleted).once @page.destroy_permanently! end + + it "posts published event when page is published" do + wiki_page_model(workflow_state: "unpublished") + expect(Canvas::LiveEvents).to receive(:wiki_page_published).once + @page.publish! + end + + it "posts unpublished event when page is unpublished" do + wiki_page_model + expect(Canvas::LiveEvents).to receive(:wiki_page_unpublished).once + @page.unpublish! + end + + it "does not post published/unpublished event when only title changes" do + wiki_page_model + expect(Canvas::LiveEvents).not_to receive(:wiki_page_published) + expect(Canvas::LiveEvents).not_to receive(:wiki_page_unpublished) + @page.title = "new title" + @page.save + end end describe "attachment" do From 29e1765149d29879b56896c165365828d39049ba Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Fri, 20 Feb 2026 17:01:47 -0700 Subject: [PATCH 03/15] Removed wiki_page_published and wiki_page_unpublished methods. When wiki page workflow_state is detected, wiki_page_updated is called instead. --- lib/canvas/live_events.rb | 14 -------------- lib/canvas/live_events_callbacks.rb | 7 +------ 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/lib/canvas/live_events.rb b/lib/canvas/live_events.rb index 50811690e2921..b3c4c6592bdca 100644 --- a/lib/canvas/live_events.rb +++ b/lib/canvas/live_events.rb @@ -630,20 +630,6 @@ def self.wiki_page_deleted(page) }) end - def self.wiki_page_published(page) - post_event_stringified("wiki_page_published", { - wiki_page_id: page.global_id, - title: LiveEvents.truncate(page.title) - }) - end - - def self.wiki_page_unpublished(page) - post_event_stringified("wiki_page_unpublished", { - wiki_page_id: page.global_id, - title: LiveEvents.truncate(page.title) - }) - end - def self.attachment_created(attachment) post_event_stringified("attachment_created", get_attachment_data(attachment)) end diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index de640ae585e2c..54fc7833e3c6a 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -136,12 +136,7 @@ def self.after_update(obj, changes) changes["body"]&.first) end if changes["workflow_state"] - _old_state, new_state = changes["workflow_state"] - case new_state - when "active" - Canvas::LiveEvents.wiki_page_published(obj) - when "unpublished" - Canvas::LiveEvents.wiki_page_unpublished(obj) + Canvas::LiveEvents.wiki_page_updated(obj) end end when Assignment From 51b9b144366ef59fd12b1a5d518b5d4f820a52f8 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Fri, 20 Feb 2026 17:07:57 -0700 Subject: [PATCH 04/15] Updated wiki_page_update call with appropriate parameters. Removed stray 'end' in conditional statement. --- lib/canvas/live_events_callbacks.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index 54fc7833e3c6a..ddb3aca414013 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -136,8 +136,9 @@ def self.after_update(obj, changes) changes["body"]&.first) end if changes["workflow_state"] - Canvas::LiveEvents.wiki_page_updated(obj) - end + Canvas::LiveEvents.wiki_page_updated(obj, + changes["title"]&.first, + changes["body"]&.first) end when Assignment Canvas::LiveEvents.assignment_updated(obj) From c8655f0d40e7545071b9567c497f77844d6e9de5 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Fri, 20 Feb 2026 17:13:57 -0700 Subject: [PATCH 05/15] Replaced changes to title and body parameters with nil for when calling wiki_page_updated due to workflow state change (publish/unpublish) as these values do not change. --- lib/canvas/live_events_callbacks.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index ddb3aca414013..b808f12ee3166 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -137,8 +137,8 @@ def self.after_update(obj, changes) end if changes["workflow_state"] Canvas::LiveEvents.wiki_page_updated(obj, - changes["title"]&.first, - changes["body"]&.first) + nil, + nil end when Assignment Canvas::LiveEvents.assignment_updated(obj) From 653c642714831569acfb6ffd96def94ae5d511db Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Fri, 20 Feb 2026 17:16:36 -0700 Subject: [PATCH 06/15] Add missing ')' --- lib/canvas/live_events_callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index b808f12ee3166..91ebf51382fbb 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -138,7 +138,7 @@ def self.after_update(obj, changes) if changes["workflow_state"] Canvas::LiveEvents.wiki_page_updated(obj, nil, - nil + nil) end when Assignment Canvas::LiveEvents.assignment_updated(obj) From a3312bc20887d30e41f2f320a6a4bfa26bb3c943 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Fri, 20 Feb 2026 17:18:19 -0700 Subject: [PATCH 07/15] Fixed minor indentation issue so style is consistent. --- lib/canvas/live_events_callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index 91ebf51382fbb..2647517281e2e 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -136,7 +136,7 @@ def self.after_update(obj, changes) changes["body"]&.first) end if changes["workflow_state"] - Canvas::LiveEvents.wiki_page_updated(obj, + Canvas::LiveEvents.wiki_page_updated(obj, nil, nil) end From 72d00ee80ab444f4bd3e44929e9caaf962644ac7 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Thu, 26 Feb 2026 15:10:53 -0700 Subject: [PATCH 08/15] Updated tests for publishing/unpublishing wiki page. --- spec/lib/canvas/live_events_spec.rb | 30 --------------------- spec/observers/live_events_observer_spec.rb | 13 +++++---- 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/spec/lib/canvas/live_events_spec.rb b/spec/lib/canvas/live_events_spec.rb index 46e5afe78a5a9..be5205990fa9b 100644 --- a/spec/lib/canvas/live_events_spec.rb +++ b/spec/lib/canvas/live_events_spec.rb @@ -331,36 +331,6 @@ def wiki_page_updated end end - describe ".wiki_page_published" do - before do - course_with_teacher - @page = @course.wiki_pages.create(title: "a page", body: "some body", workflow_state: "unpublished") - end - - it "posts a wiki_page_published event with the page id and title" do - expect_event("wiki_page_published", { - wiki_page_id: @page.global_id.to_s, - title: "a page" - }) - Canvas::LiveEvents.wiki_page_published(@page) - end - end - - describe ".wiki_page_unpublished" do - before do - course_with_teacher - @page = @course.wiki_pages.create(title: "a page", body: "some body") - end - - it "posts a wiki_page_unpublished event with the page id and title" do - expect_event("wiki_page_unpublished", { - wiki_page_id: @page.global_id.to_s, - title: "a page" - }) - Canvas::LiveEvents.wiki_page_unpublished(@page) - end - end - describe ".conversation_forwarded" do before do @user1 = user_model diff --git a/spec/observers/live_events_observer_spec.rb b/spec/observers/live_events_observer_spec.rb index 4d54278144572..008a7cfb7273e 100644 --- a/spec/observers/live_events_observer_spec.rb +++ b/spec/observers/live_events_observer_spec.rb @@ -107,22 +107,21 @@ @page.destroy_permanently! end - it "posts published event when page is published" do + it "posts update event when page is published" do wiki_page_model(workflow_state: "unpublished") - expect(Canvas::LiveEvents).to receive(:wiki_page_published).once + expect(Canvas::LiveEvents).to receive(:wiki_page_updated).with(@page, nil, nil).once @page.publish! end - it "posts unpublished event when page is unpublished" do + it "posts update event when page is unpublished" do wiki_page_model - expect(Canvas::LiveEvents).to receive(:wiki_page_unpublished).once + expect(Canvas::LiveEvents).to receive(:wiki_page_updated).with(@page, nil, nil).once @page.unpublish! end - it "does not post published/unpublished event when only title changes" do + it "does not post workflow_state update event when only title changes" do wiki_page_model - expect(Canvas::LiveEvents).not_to receive(:wiki_page_published) - expect(Canvas::LiveEvents).not_to receive(:wiki_page_unpublished) + expect(Canvas::LiveEvents).not_to receive(:wiki_page_updated).with(anything, nil, nil) @page.title = "new title" @page.save end From 27ae538408aa23011af7c01e1f6667d4d9c7aba7 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Wed, 4 Mar 2026 15:41:08 -0700 Subject: [PATCH 09/15] Formatted Canvas::LiveEvents.wiki_page_updated function so arguments are on the same line. --- lib/canvas/live_events_callbacks.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index 2647517281e2e..d02bdea835a00 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -136,9 +136,7 @@ def self.after_update(obj, changes) changes["body"]&.first) end if changes["workflow_state"] - Canvas::LiveEvents.wiki_page_updated(obj, - nil, - nil) + Canvas::LiveEvents.wiki_page_updated(obj, nil, nil) end when Assignment Canvas::LiveEvents.assignment_updated(obj) From 269ba0b04e98681130a8097cfd864db73e12c3a3 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Wed, 4 Mar 2026 15:51:47 -0700 Subject: [PATCH 10/15] Removed incorrectly written test. --- spec/observers/live_events_observer_spec.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/spec/observers/live_events_observer_spec.rb b/spec/observers/live_events_observer_spec.rb index 008a7cfb7273e..031b0cb40f886 100644 --- a/spec/observers/live_events_observer_spec.rb +++ b/spec/observers/live_events_observer_spec.rb @@ -118,13 +118,6 @@ expect(Canvas::LiveEvents).to receive(:wiki_page_updated).with(@page, nil, nil).once @page.unpublish! end - - it "does not post workflow_state update event when only title changes" do - wiki_page_model - expect(Canvas::LiveEvents).not_to receive(:wiki_page_updated).with(anything, nil, nil) - @page.title = "new title" - @page.save - end end describe "attachment" do From 455aa9536e8c1d8aae0bdabc1502f843d6c86fbf Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Wed, 4 Mar 2026 16:57:29 -0700 Subject: [PATCH 11/15] Merged wiki page title/body and workflow_state conditions. --- lib/canvas/live_events_callbacks.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index d02bdea835a00..3171d3459d841 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -130,14 +130,11 @@ def self.after_update(obj, changes) when GroupMembership Canvas::LiveEvents.group_membership_updated(obj) when WikiPage - if changes["title"] || changes["body"] + if changes["title"] || changes["body"] || changes["workflow_state"] Canvas::LiveEvents.wiki_page_updated(obj, changes["title"]&.first, changes["body"]&.first) end - if changes["workflow_state"] - Canvas::LiveEvents.wiki_page_updated(obj, nil, nil) - end when Assignment Canvas::LiveEvents.assignment_updated(obj) when AssignmentGroup From 759649837271648f6959fe491d2cad5884dc494e Mon Sep 17 00:00:00 2001 From: Joshua Bothell Date: Thu, 12 Mar 2026 15:21:19 -0600 Subject: [PATCH 12/15] Live events are working but tests are crashing --- app/observers/live_events_observer.rb | 1 + lib/canvas/live_events.rb | 28 +++++++++++ lib/canvas/live_events_callbacks.rb | 4 ++ spec/lib/canvas/live_events_spec.rb | 53 +++++++++++++++++++++ spec/observers/live_events_observer_spec.rb | 21 ++++++++ 5 files changed, 107 insertions(+) diff --git a/app/observers/live_events_observer.rb b/app/observers/live_events_observer.rb index eab4298248bb0..1b516d61901f1 100644 --- a/app/observers/live_events_observer.rb +++ b/app/observers/live_events_observer.rb @@ -53,6 +53,7 @@ class LiveEventsObserver < ActiveRecord::Observer :user_account_association, :user, :wiki_page, + "Quizzes::Quiz", "MasterCourses::MasterTemplate", "MasterCourses::MasterMigration", "MasterCourses::ChildSubscription", diff --git a/lib/canvas/live_events.rb b/lib/canvas/live_events.rb index b3c4c6592bdca..ffebad46c444a 100644 --- a/lib/canvas/live_events.rb +++ b/lib/canvas/live_events.rb @@ -337,6 +337,34 @@ def self.assignment_updated(assignment) post_event_stringified("assignment_updated", get_assignment_data(assignment)) end + def self.get_quiz_data(quiz) + { + assignment_group_id: quiz.global_assignment_group_id, + context_id: quiz.global_context_id, + context_type: "Course", + context_uuid: quiz.context.uuid, + description: LiveEvents.truncate(quiz.description), + due_at: quiz.due_at, + lock_at: quiz.lock_at, + points_possible: quiz.points_possible, + quiz_id: quiz.global_id, + quiz_type: quiz.quiz_type, + submission_types: "online_quiz", + title: LiveEvents.truncate(quiz.title), + unlock_at: quiz.unlock_at, + updated_at: quiz.updated_at, + workflow_state: quiz.workflow_state + } + end + + def self.quiz_created(quiz) + post_event_stringified("assignment_created", get_quiz_data(quiz)) + end + + def self.quiz_updated(quiz) + post_event_stringified("assignment_updated", get_quiz_data(quiz)) + end + def self.assignment_group_created(assignment_group) post_event_stringified("assignment_group_created", get_assignment_group_data(assignment_group)) end diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index 3171d3459d841..a64c3a80cf0cf 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -48,6 +48,8 @@ def self.after_create(obj) Canvas::LiveEvents.wiki_page_created(obj) when Assignment Canvas::LiveEvents.assignment_created(obj) + when Quizzes::Quiz + Canvas::LiveEvents.quiz_created(obj) if obj.assignment_id.nil? when AssignmentGroup Canvas::LiveEvents.assignment_group_created(obj) when AssignmentOverride @@ -137,6 +139,8 @@ def self.after_update(obj, changes) end when Assignment Canvas::LiveEvents.assignment_updated(obj) + when Quizzes::Quiz + Canvas::LiveEvents.quiz_updated(obj) if obj.assignment_id.nil? when AssignmentGroup Canvas::LiveEvents.assignment_group_updated(obj) when AssignmentOverride diff --git a/spec/lib/canvas/live_events_spec.rb b/spec/lib/canvas/live_events_spec.rb index be5205990fa9b..3016ab537279d 100644 --- a/spec/lib/canvas/live_events_spec.rb +++ b/spec/lib/canvas/live_events_spec.rb @@ -1317,6 +1317,59 @@ def wiki_page_updated end end + describe ".quiz_created" do + before do + course_factory + @quiz = @course.quizzes.create!( + title: "Practice Quiz", + quiz_type: "practice_quiz", + workflow_state: "available" + ) + end + + it "triggers assignment_created event with quiz details" do + expect_event("assignment_created", + hash_including({ + quiz_id: @quiz.global_id.to_s, + quiz_type: "practice_quiz", + context_id: @course.global_id.to_s, + context_type: "Course", + context_uuid: @course.uuid, + workflow_state: @quiz.workflow_state, + title: @quiz.title, + submission_types: "online_quiz" + })).once + + Canvas::LiveEvents.quiz_created(@quiz) + end + end + + describe ".quiz_updated" do + before do + course_factory + @quiz = @course.quizzes.create!( + title: "Practice Quiz", + quiz_type: "practice_quiz", + workflow_state: "available" + ) + end + + it "triggers assignment_updated event with quiz details" do + expect_event("assignment_updated", + hash_including({ + quiz_id: @quiz.global_id.to_s, + quiz_type: "practice_quiz", + context_id: @course.global_id.to_s, + context_type: "Course", + workflow_state: @quiz.workflow_state, + title: @quiz.title, + submission_types: "online_quiz" + })).once + + Canvas::LiveEvents.quiz_updated(@quiz) + end + end + describe "assignment_group_updated" do let(:course) do course_with_student_submissions diff --git a/spec/observers/live_events_observer_spec.rb b/spec/observers/live_events_observer_spec.rb index 031b0cb40f886..33a04be598047 100644 --- a/spec/observers/live_events_observer_spec.rb +++ b/spec/observers/live_events_observer_spec.rb @@ -259,6 +259,27 @@ end end + describe "quiz" do + before { course_factory } + + it "posts quiz_created for practice_quiz" do + expect(Canvas::LiveEvents).to receive(:quiz_created).once + @course.quizzes.create!(title: "Practice Quiz", quiz_type: "practice_quiz") + end + + it "posts quiz_updated for practice_quiz" do + quiz = @course.quizzes.create!(title: "Practice Quiz", quiz_type: "practice_quiz") + expect(Canvas::LiveEvents).to receive(:quiz_updated).once + quiz.title = "Updated Title" + quiz.save! + end + + it "does not post quiz_created for graded quiz with assignment" do + expect(Canvas::LiveEvents).not_to receive(:quiz_created) + @course.quizzes.create!(title: "Graded Quiz", quiz_type: "assignment") + end + end + describe "assignment overrides" do it "posts create events" do expect(Canvas::LiveEvents).to receive(:assignment_override_created).once From fbbd195fb4d98ab8f55db0dfaf4bd99d01f2c6f8 Mon Sep 17 00:00:00 2001 From: Joshua Bothell Date: Thu, 12 Mar 2026 15:37:03 -0600 Subject: [PATCH 13/15] added more test coverage, all tests pass now. --- spec/observers/live_events_observer_spec.rb | 25 ++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/spec/observers/live_events_observer_spec.rb b/spec/observers/live_events_observer_spec.rb index 33a04be598047..cec924161878e 100644 --- a/spec/observers/live_events_observer_spec.rb +++ b/spec/observers/live_events_observer_spec.rb @@ -262,22 +262,41 @@ describe "quiz" do before { course_factory } - it "posts quiz_created for practice_quiz" do + it "posts quiz_created when creating a practice_quiz" do expect(Canvas::LiveEvents).to receive(:quiz_created).once @course.quizzes.create!(title: "Practice Quiz", quiz_type: "practice_quiz") end - it "posts quiz_updated for practice_quiz" do + it "posts quiz_created when creating an ungraded survey" do + expect(Canvas::LiveEvents).to receive(:quiz_created).once + @course.quizzes.create!(title: "Survey", quiz_type: "survey") + end + + it "posts quiz_updated when updating a practice_quiz" do quiz = @course.quizzes.create!(title: "Practice Quiz", quiz_type: "practice_quiz") expect(Canvas::LiveEvents).to receive(:quiz_updated).once quiz.title = "Updated Title" quiz.save! end - it "does not post quiz_created for graded quiz with assignment" do + it "posts quiz_updated when updating an ungraded survey" do + quiz = @course.quizzes.create!(title: "Survey", quiz_type: "survey") + expect(Canvas::LiveEvents).to receive(:quiz_updated).once + quiz.title = "Updated Title" + quiz.save! + end + + it "does not post quiz_created or quiz_updated for a graded quiz" do expect(Canvas::LiveEvents).not_to receive(:quiz_created) + expect(Canvas::LiveEvents).not_to receive(:quiz_updated) @course.quizzes.create!(title: "Graded Quiz", quiz_type: "assignment") end + + it "does not post quiz_created or quiz_updated for a graded survey" do + expect(Canvas::LiveEvents).not_to receive(:quiz_created) + expect(Canvas::LiveEvents).not_to receive(:quiz_updated) + @course.quizzes.create!(title: "Graded Survey", quiz_type: "graded_survey") + end end describe "assignment overrides" do From 80899d443d5b5754fd54314dcb4877d8427afeb3 Mon Sep 17 00:00:00 2001 From: Bradan Schwanke Date: Thu, 12 Mar 2026 14:21:01 -0600 Subject: [PATCH 14/15] Classic quiz with assignment. When creating or updating a quiz question, live event fires. Verified with manual testing using fake kinesis server. Created spec tests for automated testing. --- app/observers/live_events_observer.rb | 1 + lib/canvas/live_events_callbacks.rb | 18 ++++++++ spec/observers/live_events_observer_spec.rb | 51 +++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/app/observers/live_events_observer.rb b/app/observers/live_events_observer.rb index 1b516d61901f1..9486004a48860 100644 --- a/app/observers/live_events_observer.rb +++ b/app/observers/live_events_observer.rb @@ -54,6 +54,7 @@ class LiveEventsObserver < ActiveRecord::Observer :user, :wiki_page, "Quizzes::Quiz", + "Quizzes::QuizQuestion", "MasterCourses::MasterTemplate", "MasterCourses::MasterMigration", "MasterCourses::ChildSubscription", diff --git a/lib/canvas/live_events_callbacks.rb b/lib/canvas/live_events_callbacks.rb index a64c3a80cf0cf..93587087cfae3 100644 --- a/lib/canvas/live_events_callbacks.rb +++ b/lib/canvas/live_events_callbacks.rb @@ -46,6 +46,15 @@ def self.after_create(obj) Canvas::LiveEvents.group_membership_created(obj) when WikiPage Canvas::LiveEvents.wiki_page_created(obj) + when Quizzes::QuizQuestion + quiz = obj.quiz + if quiz + if quiz.assignment + Canvas::LiveEvents.assignment_updated(quiz.assignment) + else + Canvas::LiveEvents.quiz_updated(quiz) + end + end when Assignment Canvas::LiveEvents.assignment_created(obj) when Quizzes::Quiz @@ -137,6 +146,15 @@ def self.after_update(obj, changes) changes["title"]&.first, changes["body"]&.first) end + when Quizzes::QuizQuestion + quiz = obj.quiz + if quiz + if quiz.assignment + Canvas::LiveEvents.assignment_updated(quiz.assignment) + else + Canvas::LiveEvents.quiz_updated(quiz) + end + end when Assignment Canvas::LiveEvents.assignment_updated(obj) when Quizzes::Quiz diff --git a/spec/observers/live_events_observer_spec.rb b/spec/observers/live_events_observer_spec.rb index cec924161878e..cd9f9e19b2117 100644 --- a/spec/observers/live_events_observer_spec.rb +++ b/spec/observers/live_events_observer_spec.rb @@ -485,6 +485,57 @@ def enable_quizzes_next(course) end end + describe "quiz_question" do + context "quiz with assignment" do + it "posts assignment_updated when a question is created" do + allow(Canvas::LiveEvents).to receive(:assignment_updated) + quiz_model(quiz_type: "assignment") + assignment = @quiz.reload.assignment + expect(Canvas::LiveEvents).to receive(:assignment_updated).with(assignment) + @quiz.quiz_questions.create!(question_data: { name: "Q1", question_type: "true_false_question", points_possible: 1 }) + end + + it "posts assignment_updated when a question is updated" do + allow(Canvas::LiveEvents).to receive(:assignment_updated) + quiz_model(quiz_type: "assignment") + question = @quiz.quiz_questions.create!(question_data: { name: "Q1", question_type: "true_false_question", points_possible: 1 }) + assignment = @quiz.reload.assignment + expect(Canvas::LiveEvents).to receive(:assignment_updated).with(assignment) + question.update!(question_data: { name: "Q1 updated", question_type: "true_false_question", points_possible: 1 }) + end + end + + context "quiz without assignment (practice_quiz)" do + before { course_factory } + + it "posts quiz_updated when a question is created" do + allow(Canvas::LiveEvents).to receive(:quiz_updated) + quiz = @course.quizzes.create!(title: "Practice Quiz", quiz_type: "practice_quiz") + expect(Canvas::LiveEvents).to receive(:quiz_updated).with(quiz) + quiz.quiz_questions.create!(question_data: { name: "Q1", question_type: "true_false_question", points_possible: 1 }) + end + + it "posts quiz_updated when a question is updated" do + allow(Canvas::LiveEvents).to receive(:quiz_updated) + quiz = @course.quizzes.create!(title: "Practice Quiz", quiz_type: "practice_quiz") + question = quiz.quiz_questions.create!(question_data: { name: "Q1", question_type: "true_false_question", points_possible: 1 }) + expect(Canvas::LiveEvents).to receive(:quiz_updated).with(quiz) + question.update!(question_data: { name: "Q1 updated", question_type: "true_false_question", points_possible: 1 }) + end + end + + context "quiz without assignment (survey)" do + before { course_factory } + + it "posts quiz_updated when a question is created" do + allow(Canvas::LiveEvents).to receive(:quiz_updated) + quiz = @course.quizzes.create!(title: "Survey", quiz_type: "survey") + expect(Canvas::LiveEvents).to receive(:quiz_updated).with(quiz) + quiz.quiz_questions.create!(question_data: { name: "Q1", question_type: "true_false_question", points_possible: 1 }) + end + end + end + describe "content_migration_completed" do it "posts update events" do expect(Canvas::LiveEvents).to receive(:content_migration_completed).once From d83c5e9e65c82d30f5e3986831ef7e84bc3fba6e Mon Sep 17 00:00:00 2001 From: Joshua Bothell Date: Mon, 16 Mar 2026 18:18:02 -0600 Subject: [PATCH 15/15] Use quiz_created/quiz_updated event types for quizzes without assignments Instead of reusing assignment_created/assignment_updated, introduce distinct event types so consumers aren't confused by events with no associated assignment. Co-Authored-By: Claude Sonnet 4.6 --- lib/canvas/live_events.rb | 4 ++-- spec/lib/canvas/live_events_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/canvas/live_events.rb b/lib/canvas/live_events.rb index ffebad46c444a..28756451c6881 100644 --- a/lib/canvas/live_events.rb +++ b/lib/canvas/live_events.rb @@ -358,11 +358,11 @@ def self.get_quiz_data(quiz) end def self.quiz_created(quiz) - post_event_stringified("assignment_created", get_quiz_data(quiz)) + post_event_stringified("quiz_created", get_quiz_data(quiz)) end def self.quiz_updated(quiz) - post_event_stringified("assignment_updated", get_quiz_data(quiz)) + post_event_stringified("quiz_updated", get_quiz_data(quiz)) end def self.assignment_group_created(assignment_group) diff --git a/spec/lib/canvas/live_events_spec.rb b/spec/lib/canvas/live_events_spec.rb index 3016ab537279d..b87c1c572fbc7 100644 --- a/spec/lib/canvas/live_events_spec.rb +++ b/spec/lib/canvas/live_events_spec.rb @@ -1327,8 +1327,8 @@ def wiki_page_updated ) end - it "triggers assignment_created event with quiz details" do - expect_event("assignment_created", + it "triggers quiz_created event with quiz details" do + expect_event("quiz_created", hash_including({ quiz_id: @quiz.global_id.to_s, quiz_type: "practice_quiz", @@ -1354,8 +1354,8 @@ def wiki_page_updated ) end - it "triggers assignment_updated event with quiz details" do - expect_event("assignment_updated", + it "triggers quiz_updated event with quiz details" do + expect_event("quiz_updated", hash_including({ quiz_id: @quiz.global_id.to_s, quiz_type: "practice_quiz",