Skip to content

PR7b — feat(marketplace): preview attempt (Phase 2)#8511

Draft
LWS49 wants to merge 11 commits into
lws49/feat-marketplace-pr7a-attempt-base-recordfrom
lws49/feat-marketplace-pr7b-preview-attempt
Draft

PR7b — feat(marketplace): preview attempt (Phase 2)#8511
LWS49 wants to merge 11 commits into
lws49/feat-marketplace-pr7a-attempt-base-recordfrom
lws49/feat-marketplace-pr7b-preview-attempt

Conversation

@LWS49

@LWS49 LWS49 commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

PR7b — Marketplace preview attempt (Phase 2)

Stacked on #8510 (pr7a-attempt-base-record). Adds the marketplace "Try it out" preview feature on the base-record foundation from PR7a.

A preview is a bare Attempt with no Submission extension row (Attempt.previews == where.missing(:submission)) — no course_user, no EXP, throwaway. Eligible users rehearse a published assessment interactively (answer → submit → auto-grade → view-as-grader) without creating a real submission, joining a course, or earning EXP.

What's in it

  • Lifecycle: Attempt#preview?, #reset_attempt!, TTL reaping job (Increment A)
  • Serving: Marketplace::PreviewAttemptsController (create-or-reuse keyed on Attempt.previews; edit / update / auto_grade / reset + live-feedback / scribing) reusing the platform submission pipeline with the Attempt standing in as @submission; PreviewUpdateService, courseless PreviewAutoGradingService, preview jbuilders (+ shared _attempt partial extracted from _submission)
  • Authorization: block-form preview? && creator_id == user.id abilities; published-listing gate on create (:preview_in_marketplace); (assessment, creator) collision → clean 409

Notable fixes surfaced during this work

  • Latent Phase-1 bug fixed: the shared auto-grading EXP tail crashed on the bare Attempt (points_awarded= lives only on the extension) — affected real and preview autograded finalise; now extension-aware + preview-safe (Submission::AutoGradingService).
  • Hardened a pre-auth external-call oracle in the preview live-feedback status endpoint (authorize before the Codaveri call).

Testing

All backend specs green (consolidated sweep: 92 examples, 0 failures, 11 pending). Every task independently reviewed; final whole-branch review passed after the two hardening fixes above.

Not yet included (next increment): the frontend (~10 ported files) and the launch→answer→grade feature/Capybara specs.

@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from 562e8a5 to d9d825a Compare July 23, 2026 13:05
render partial: 'jobs/submitted', locals: { job: job }
end

def generate_live_feedback # rubocop:disable Metrics/AbcSize, Metrics/MethodLength

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Lint/RedundantCopDisableDirective: Unnecessary disabling of Metrics/MethodLength.

# frozen_string_literal: true
json.submission do
json.partial! 'course/assessment/submission/submissions/attempt', attempt: preview_attempt, assessment: assessment,
can_grade: can_grade, can_update: can_update

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.

json.partial! 'course/assessment/submission/submissions/answers', submission: @submission, answers: answers
json.partial! 'course/assessment/submission/submissions/topics', submission: @submission,
submission_questions: submission_questions,
can_grade: can_grade

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.

answers: answers
json.partial! 'course/assessment/submission/submissions/answers', submission: @submission, answers: answers
json.partial! 'course/assessment/submission/submissions/topics', submission: @submission,
submission_questions: submission_questions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.

json.partial! 'course/assessment/submission/submissions/questions', assessment: @assessment, submission: @submission,
can_grade: can_grade,
submission_questions: submission_questions,
answers: answers

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.


json.partial! 'course/assessment/submission/submissions/questions', assessment: @assessment, submission: @submission,
can_grade: can_grade,
submission_questions: submission_questions,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.

where(question: @submission.questions).includes({ discussion_topic: :posts })

json.partial! 'course/assessment/submission/submissions/questions', assessment: @assessment, submission: @submission,
can_grade: can_grade,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Correctable] Layout/HashAlignment: Align the keys of a hash literal if they span more than one line.

@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch 2 times, most recently from 68546f0 to b1b31c1 Compare July 24, 2026 05:03
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from d9d825a to f850512 Compare July 24, 2026 05:03
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch from b1b31c1 to 7afa40f Compare July 24, 2026 05:29
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from f850512 to 3900460 Compare July 24, 2026 05:29
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch from 7afa40f to 15b1eee Compare July 24, 2026 07:21
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from 3900460 to 46fb69c Compare July 24, 2026 07:22
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch from 15b1eee to fbed966 Compare July 24, 2026 07:35
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from 46fb69c to 4c04c36 Compare July 24, 2026 07:35
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch from fbed966 to f35dd0c Compare July 24, 2026 07:37
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from 4c04c36 to bec2ee1 Compare July 24, 2026 07:37
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch from f35dd0c to eb015b3 Compare July 24, 2026 09:29
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from bec2ee1 to a466080 Compare July 24, 2026 09:29
LWS49 added 11 commits July 24, 2026 17:55
Design-C marketplace preview groundwork, no schema change:
- Attempt#preview? (true when the attempt has no Submission extension row) — the single
  source of truth for preview-only branching.
- Attempt#reset_attempt! — destroys answers, returns to attempting, rebuilds answers
  (backs the preview 'Reset attempt' action).
- Course::Assessment::PreviewAttemptReapingJob (TTL 7d) + daily schedule — deletes only
  extension-less previews past TTL; real submissions never reaped (mutation-verified).
- validate_unique_submission left unchanged: the DB unique index already forbids two
  attempts per (assessment, creator); normal preview creation hits no prior attempt, and
  the unscoped rule yields the friendly collision error create-or-reuse relies on.

Plan: docs/superpowers/plans/2026-07-23-marketplace-preview-increment-a-lifecycle.md
…review serving

Increment B foundation: an Attempt must stand in as @submission when serving a marketplace
preview, but the shared submission jbuilders call course_user/current_points_awarded/
experience_points_record/publisher on the served object — the EXP/course-coupled slice the
extension owns. Delegate those to the (optional) extension with allow_nil, so a preview
(no extension) returns nil and a real submission returns the extension's values. Exercised
only by previews; real submissions serve via the extension directly. Interface set audited
against app/views/course/assessment/submission/submissions/*.jbuilder.
…, jbuilders

Increment B: PreviewAttemptsController serves marketplace preview attempts by reusing the
platform submission edit/update pipeline, treating a bare Course::Assessment::Attempt (no
Submission extension) as @submission. Covers create/edit/update/auto_grade/reset (core routes);
live-feedback/scribing actions are authored here (single-file authoring) but wired up by a
later task.

PreviewUpdateService subclasses Submission::UpdateService to drop the EXP-only
draft_points_awarded/points_awarded params an Attempt has no columns for, and to build
SubmissionQuestion rows against the Attempt directly (the parent assumes a Submission
extension with its own #attempt association).

Also covers a repo-wide deferred-workflow-state-persistence gotcha in the new spec: a bare
`attempt.finalise!` only transitions workflow_state in memory (lib/extensions/
deferred_workflow_state_persistence) and needs an explicit save to be visible to a
subsequent reload/re-find, same as Submission#finalise! already does internally.
…ubmission -> gradable.submission)

The dual-typed param (Attempt in production, Submission in legacy specs) read as submission.submission
when resolving the extension. Rename to `gradable` so the extension access reads gradable.submission.
Behavior-preserving.
The preview reuses the submission edit UI, whose per-answer autosave and
per-question submit hit dedicated answer endpoints. Add the preview equivalents
so saving/submitting an answer in a preview no longer 404s on the platform
/assessments/:aid/submissions/... route.

- New shallow routes + #save_draft / #submit_answer actions on the preview
  attempts controller, reusing the platform answer write path
  (UpdateAnswerConcern) and the extracted SubmitAnswerConcern.
- Extract the submit auto-grade helpers from AnswersController into
  Course::Assessment::Answer::SubmitAnswerConcern (renamed auto_grade ->
  auto_grade_answer to avoid colliding with the preview controller's
  whole-attempt #auto_grade action).
- Grant :save_draft/:submit_answer on a preview-owned Attempt.
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7a-attempt-base-record branch from eb015b3 to e8b9992 Compare July 24, 2026 09:56
@LWS49
LWS49 force-pushed the lws49/feat-marketplace-pr7b-preview-attempt branch from a466080 to 4c28286 Compare July 24, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant