Skip to content

JM: Record which submission changed data on every form - #2710

Open
maebeale wants to merge 3 commits into
maebeale/other-answer-sourcesfrom
maebeale/organization-answers-form-linking
Open

maebeale wants to merge 3 commits into
maebeale/other-answer-sourcesfrom
maebeale/organization-answers-form-linking

Conversation

@maebeale

@maebeale maebeale commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 suggested review level: 3 Read 📖 turns on change tracking for signed-out bulk payers and ticket-form submitters; moves the submission id into the services

Every form now records which submission made each change, so the change-log links from #2719 work everywhere.

Stacked on #2719. That PR adds the change-log links and the callout capture. This one covers the Current-based recording.

  • New paths: bulk payments, ticket callout forms, and linking an organization from a standalone submission's page now record the submission id. Their changes show up on that submission's "What this submission changed" page.
  • The submitting services set the id: PublicRegistration, PublicFormSubmission, BulkPayment and CalloutFormSubmission set Current.form_submission_id on success, so a new caller can't forget it. The controller lines are gone.
  • Signed-out submitters are now tracked: signed-out bulk payers and callout submitters get Current.source (bulk_payment / ticket_callout). Without a source or a signed-in user, nothing is logged. Public forms and registrations already work this way.

Optional: link older changes (run by hand after deploy)

Paste into a production Rails console. It's a dry run as written: check the counts, then run again with dry_run = false. It never touches an event that already has an id, so re-running is safe.

  • How it matches: every change from one request is logged at the same moment under the same visit. A request that created a submission, or changed only its link fields (metadata), passes that id to its other unstamped changes.
  • What it skips: requests that touched more than one submission, and other submission updates such as a person merge.
  • Can't be recovered: signed-out submitters before this PR, since nothing was logged for them.
  • Tested: I ran it against the backfill's 7 specs before removing them from the PR.
# Stamp form_submission_id onto older Ahoy change events so change logs link them
# to their submission. A request's events share a visit and an instant; a request
# that created a submission, or changed only its metadata (org linking, callout
# re-submit), names the rest. Requests touching several submissions are skipped.
# Run as-is for a dry run; set dry_run = false to write.
dry_run = true
window = 1.second
seen = Set.new
stamped = 0
ambiguous = 0

stamp_of = ->(event) { event.properties&.dig("form_submission_id").presence&.to_i }
named_submission = lambda do |event|
  next stamp_of.(event) if stamp_of.(event)
  next unless event.resource_type == "FormSubmission"

  changed = event.properties&.dig("changes")&.keys
  event.resource_id if event.name == "create.form_submission" || (changed.present? && (changed - %w[metadata]).empty?)
end

Ahoy::Event.where(resource_type: "FormSubmission", name: %w[create.form_submission update.form_submission])
  .where.not(visit_id: nil).find_each do |anchor|
  next if seen.include?(anchor.id)

  events = Ahoy::Event.mutations.where(visit_id: anchor.visit_id, time: (anchor.time - window)..(anchor.time + window)).to_a
  seen.merge(events.map(&:id))
  unstamped = events.reject { |event| stamp_of.(event) }
  next if unstamped.empty?

  submission_ids = events.filter_map { |event| named_submission.(event) }.uniq
  if submission_ids.many?
    ambiguous += 1
  elsif submission_ids.one?
    stamped += unstamped.size
    unless dry_run
      unstamped.each { |event| event.update_columns(properties: event.properties.to_h.merge("form_submission_id" => submission_ids.first)) }
    end
  end
end

puts "events #{dry_run ? "to stamp" : "stamped"}: #{stamped}"
puts "requests skipped (ambiguous): #{ambiguous}"

🤖 Generated with Claude Code

# see prior state, not the row we're about to create.
existing = @event.event_registrations.find_by(registrant: person)
event_registration = existing || create_event_registration(person)
submission = create_form_submission(person)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: The submission and its answers are now saved before the org capture, so the org-type "Other" can point at its answer. If saving an answer fails, the whole transaction still rolls back. The resubmission diff only compares this submission with the previous one, so it doesn't depend on this order.

@maebeale
maebeale marked this pull request as ready for review October 17, 2026 13:13
@maebeale maebeale changed the title Record the form behind organization-type Other answers Record where Other answers came from, and capture them from ticket forms Oct 17, 2026
@maebeale maebeale changed the title Record where Other answers came from, and capture them from ticket forms Trace data changes back to the form submission that made them Oct 17, 2026
end
return redirect_to registration_ce_path(@event_registration.slug) if ce_form_locked?(@callout)

Current.source = "ticket_callout"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Setting a source makes signed-out callout submitters (and bulk payers) generate lifecycle events for the first time, the same as public forms. Expect more Ahoy rows per submission: the submission, its answers, and any profile write-through.

@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from 6270b51 to 72e3e31 Compare October 17, 2026 14:37
return event.properties["form_submission_id"].to_i if stamped?(event)
return unless event.resource_type == "FormSubmission"

event.resource_id if event.name == "create.form_submission" || relink?(event)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 From Claude: Matching only uses a submission that was created or had just its metadata changed in that request. That way a person merge, which reassigns person_id, can't credit its own deletes and edits to a submission. Run it as a dry run first in production and check the counts.

@maebeale maebeale changed the title Trace data changes back to the form submission that made them Record which submission changed data on every form, and backfill older changes Oct 17, 2026
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from 3b40c67 to 8282570 Compare October 17, 2026 15:58
@maebeale
maebeale changed the base branch from main to maebeale/other-answer-sources October 17, 2026 15:58
@maebeale
maebeale force-pushed the maebeale/other-answer-sources branch from 71e5e0f to 75e0646 Compare October 17, 2026 16:14
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch 2 times, most recently from e138950 to 6402366 Compare October 17, 2026 16:37
@maebeale
maebeale force-pushed the maebeale/other-answer-sources branch from 75e0646 to 8a29c7a Compare October 17, 2026 16:43
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from 6402366 to ee604ba Compare October 17, 2026 16:54
@maebeale
maebeale force-pushed the maebeale/other-answer-sources branch from 8a29c7a to 6d427ce Compare October 17, 2026 20:07
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from ee604ba to d9e1061 Compare October 17, 2026 20:07
@maebeale maebeale changed the title Record which submission changed data on every form, and backfill older changes Record which submission changed data on every form Oct 17, 2026
return
end

Current.source = "bulk_payment"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

@jmilljr24 are you comfortable w using Current for this?

@maebeale
maebeale requested a review from jmilljr24 October 17, 2026 20:39
@maebeale maebeale changed the title Record which submission changed data on every form JM: Record which submission changed data on every form Oct 17, 2026
@maebeale
maebeale force-pushed the maebeale/other-answer-sources branch from 4823712 to 9ce8ef0 Compare October 17, 2026 21:31
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from d9e1061 to 2de40cc Compare October 17, 2026 21:31
@maebeale
maebeale force-pushed the maebeale/other-answer-sources branch 2 times, most recently from 4843437 to 3691787 Compare October 18, 2026 01:58
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from 2de40cc to 698afad Compare October 18, 2026 02:36
@maebeale
maebeale force-pushed the maebeale/other-answer-sources branch from 53001ed to 41ba248 Compare October 18, 2026 12:45
maebeale and others added 2 commits October 18, 2026 08:58
…org linking

Those paths never set the submission id on their change events, and
signed-out bulk payers and callout submitters weren't logged at all, so
their changes couldn't be traced to a submission.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each controller had to remember to set Current.form_submission_id after
calling its service; the service already has the submission, so setting
it there means a new caller can't forget it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Older changes get linked only if someone runs the console backfill, so
the feature card shouldn't promise it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@maebeale
maebeale force-pushed the maebeale/organization-answers-form-linking branch from 698afad to e1728b5 Compare October 18, 2026 13:06
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