Populate parent_post_id in HPPS progress table#7916
Conversation
…terfaces Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ementations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lesson progress gets course_id from _lesson_course meta, quiz progress gets lesson_id from the comment's post ID, course progress stays NULL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backfills NULL parent_post_id values in sensei_lms_progress. Lesson progress gets the course ID, quiz progress gets the lesson ID. Runs in batches via Action Scheduler after the existing migrations complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds support for persisting hierarchy relationships in HPPS progress rows by populating sensei_lms_progress.parent_post_id (course for lesson progress; lesson for quiz progress) and introduces a backfill migration to populate existing rows.
Changes:
- Extends lesson/quiz progress repository
create()APIs to accept an optional$parent_post_idand stores it in table-based progress rows. - Updates key progress-creation callers and the comments-to-tables
Student_Progress_Migrationto populateparent_post_id. - Adds a new
Parent_Post_Id_Migrationand corresponding unit tests for backfilling existing HPPS rows.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit-tests/internal/student-progress/quiz-progress/repositories/test-class-tables-based-quiz-progress-repository.php | Adds coverage that quiz progress create() persists parent_post_id. |
| tests/unit-tests/internal/student-progress/lesson-progress/repositories/test-class-tables-based-lesson-progress-repository.php | Adds coverage that lesson progress create() persists parent_post_id. |
| tests/unit-tests/internal/migration/migrations/test-class-student-progress-migration.php | Adds test ensuring comments-to-tables migration sets parent_post_id for course/lesson/quiz progress. |
| tests/unit-tests/internal/migration/migrations/test-class-parent-post-id-migration.php | Introduces unit tests for the new parent_post_id backfill migration behavior. |
| includes/internal/student-progress/quiz-progress/repositories/class-tables-based-quiz-progress-repository.php | Adds optional $parent_post_id to create() and writes it to the HPPS table. |
| includes/internal/student-progress/quiz-progress/repositories/class-table-reading-aggregate-quiz-progress-repository.php | Threads $parent_post_id through aggregate create() and ensures lesson progress creation passes course parent. |
| includes/internal/student-progress/quiz-progress/repositories/class-quiz-progress-repository-interface.php | Updates repository interface to accept optional $parent_post_id. |
| includes/internal/student-progress/quiz-progress/repositories/class-comments-based-quiz-progress-repository.php | Updates method signature for interface compatibility (parameter unused). |
| includes/internal/student-progress/quiz-progress/repositories/class-comment-reading-aggregate-quiz-progress-repository.php | Threads $parent_post_id through aggregate create(). |
| includes/internal/student-progress/lesson-progress/repositories/class-tables-based-lesson-progress-repository.php | Adds optional $parent_post_id to create() and writes it to the HPPS table. |
| includes/internal/student-progress/lesson-progress/repositories/class-table-reading-aggregate-lesson-progress-repository.php | Threads $parent_post_id through aggregate create(). |
| includes/internal/student-progress/lesson-progress/repositories/class-lesson-progress-repository-interface.php | Updates repository interface to accept optional $parent_post_id. |
| includes/internal/student-progress/lesson-progress/repositories/class-comments-based-lesson-progress-repository.php | Updates method signature for interface compatibility (parameter unused). |
| includes/internal/student-progress/lesson-progress/repositories/class-comment-reading-aggregate-lesson-progress-repository.php | Threads $parent_post_id through aggregate create(). |
| includes/internal/migration/migrations/class-student-progress-migration.php | Populates parent_post_id during initial comments-to-tables migration. |
| includes/internal/migration/migrations/class-parent-post-id-migration.php | Adds new backfill migration to populate missing parent_post_id for lesson/quiz progress rows. |
| includes/class-sensei.php | Registers the new migration job with the migration scheduler. |
| includes/class-sensei-utils.php | Passes course ID into lesson progress creation to populate parent_post_id. |
| includes/class-sensei-quiz.php | Passes course/lesson parents into progress creation paths for grading/quiz start. |
| includes/class-sensei-grading.php | Passes course ID into lesson progress creation during grading flow. |
| changelog/add-hpps-parent-post-id | Adds changelog entry for the new HPPS parent_post_id population behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Rename options to follow the sensei_migrated_ convention used by other migrations. Add both options to Migration_Job_Scheduler::clear_state() so they are properly cleaned up. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check wpdb->update() return value and log errors via add_error(), matching the pattern used by Student_Progress_Migration and Quiz_Migration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match the pattern used in student progress migration: echo SELECT queries and UPDATE statements during dry runs for inspection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sites that already completed the HPPS migration won't re-run the scheduler on plugin update. Add an upgrade handler that detects this case and schedules just the parent_post_id_migration job. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Follow the existing naming convention for version-gated update methods. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove misleading comments, redundant run() call, and add failure messages to batch assertions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ogging - Replace per-row get_post_meta with bulk SELECT ... WHERE post_id IN - Distinguish get_results null (DB error) from empty array (no rows) - Remove wpdb->last_error gate on update error check - Log skipped orphaned rows via add_error Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Sensei_Utils::get_lesson_course_id and get_quiz_lesson_id helpers - Update callers to use helpers instead of inline get_post_meta - Guard against 0 being stored as parent_post_id in tables-based repos Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Wrap schedule_job_by_name in try-catch for RuntimeException - Schedule job before resetting status so failure preserves COMPLETE state - Add 5 tests covering all guard clauses and failure path Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Clean up LESSONS_COMPLETE_OPTION_NAME in setUp to prevent test pollution - Add testRun_OrphanedQuiz_RemainsNull - Add testRun_LessonAndQuizRows_BackfillsBothPhases for two-phase transition Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Returning batch_size on a null $wpdb->get_results() never advanced the cursor, causing the scheduler to re-run the same failing query forever. Throwing RuntimeException lets Action Scheduler's retry mechanism handle it (up to 3 retries, then STATUS_FAILED). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…vention Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both methods were nearly identical, differing only in progress type and meta key. Consolidated into a single parameterized private method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use %s placeholder for meta_key instead of string interpolation in SQL - Add @throws tag to run() docblock since RuntimeException propagates - Add clarifying comment for quiz parent_post_id in student progress migration - Add @var docblock for $factory property in migration test - Extract set_backfill_test_context helper to reduce duplication in update tests Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…uery errors Add max(1, ...) guard on batch size to prevent zero/negative values from filters causing an infinite no-op loop. Add null check on the second get_results() call so a DB error fetching post meta throws a RuntimeException instead of silently treating every row as "no meta found." Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ress migration Extract (int) $comment->comment_post_ID into $parent_lesson_id to match the pattern used for lesson progress ($course_id) a few lines above. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apply the same max(1, ...) guard to Student_Progress_Migration and Quiz_Migration that was added to Parent_Post_Id_Migration, preventing filters from setting batch size to zero or negative which would cause an infinite no-op loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cast int|bool values from get_lesson_id() to int before passing to get_lesson_course_id() and create(). Remove redundant (int) cast in sensei_start_lesson() where $lesson_id is already int. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 25 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
Reset migration status to NOT_STARTED before scheduling the job rather than after, so concurrent requests (admin page + AJAX heartbeat) that all see is_complete() === true don't each schedule a separate job. Restore status to COMPLETE on failure so the backfill can be retried on the next upgrade. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Exception messages are not rendered as HTML — escaping belongs at render time, not throw time. Also remove noisy logging for orphaned rows (no post meta) which are expected stale data from deleted posts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
👋🏻 @merkushin |
|
Sorry, almost missed the notification in my inbox 😅 The changes look good. But I haven't tested it locally. It's interesting to notice that tests (with clear structure and names) become a better tool for understanding code. The generated code, on the other hand, becomes less readable (code nesting really affects it). A couple of weeks ago, I read about an observation that in the modern world, code could be entirely open since the cost of writing it is extremely low, but the tests' value increases, and they might become closed source. |
mdawaffe
left a comment
There was a problem hiding this comment.
A couple super minor things, but looks good to me. I didn't test locally :(
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
This prepares the data model for reusable lessons by storing hierarchy relationships directly in the
sensei_lms_progresstable, eliminating the need forpostmetajoins.This data wasn't populated prior to shipping HPPS, so it needs to be backfilled for those sites that have it enabled and are actively syncing between comments and custom tables.
$parent_post_idparameter to all progress repositorycreate()interfaces and implementations (backward-compatible, defaults tonull)Sensei_Utils::user_start_lesson(),Sensei_Quiz,Sensei_Grading) to look up and pass the parent ID: course ID for lesson progress, lesson ID for quiz progressStudent_Progress_Migrationto populateparent_post_idduring initial migrationParent_Post_Id_Migrationbackfill job for existing HPPS sites that already have progress rows withNULLparent_post_idTest plan
Automated tests
vendor/bin/phpunit --filter=testCreate_WhenParentPostIdProvided— verifies both lesson and quiz repositories store parent_post_idvendor/bin/phpunit --filter=Parent_Post_Id_Migration_Test— 8 tests covering backfill migration (batching, idempotency, orphaned rows, completion)vendor/bin/phpunit --filter=testRun_WithLessonAndQuiz_PopulatesParentPostId— verifies comments-to-tables migration populates parent_post_idvendor/bin/phpunit --filter=testBackfillParentPostId— 5 tests covering upgrade handler conditionsManual testing — new progress creation
Sensei > Settings > Experimental Features > High Performance Progress Storage)sensei_lms_progresstable:parent_post_id= the course IDparent_post_id= the lesson IDparent_post_id= NULLManual testing — backfill migration
parent_post_idto simulate pre-existing rows:run_updates()fires and schedules the Action Scheduler job (sensei_lms_migration_job_parent_post_id_migration)parent_post_idmatches the expected value from postmeta in both queries🤖 Generated with Claude Code