Skip to content

feat(program): admin edit/delete program form responses, protecc program form responses & program offers - #1024

Merged
japsu merged 9 commits into
mainfrom
feat/delete-program-form-responses
Sep 2, 2026
Merged

feat(program): admin edit/delete program form responses, protecc program form responses & program offers#1024
japsu merged 9 commits into
mainfrom
feat/delete-program-form-responses

Conversation

@japsu

@japsu japsu commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Allow deleting responses to program forms (invite-purpose PROGRAM-app surveys), reusing the existing deleteSurveyResponses mutation — previously restricted to regular FORMS surveys.
  • Involvement.response is now SET_NULL instead of CASCADE, so an Involvement survives deletion of the response that produced it (expiration for stale Involvements is a separate future feature).
  • Add edit and delete actions to the program form response detail page, which queried canEdit/canDelete but rendered no actions.
  • Add an event-wide "protect program offers and program form responses" toggle (ProgramV2EventMeta.protect_responses, defaults to on) on the program preferences page, in response to an incident where a concom member accidentally deleted all program offers during a live event.
  • Reconcile the long-standing split (TODO #714) between Survey.can_responses_be_deleted_by and Workflow.response_can_be_deleted_by, which could give inconsistent answers — both now delegate to Workflow.responses_can_be_deleted_by/protect_responses, overridden per app (Forms: Survey.protect_responses; program offers/program forms: the new event-wide switch). This also closes a gap where CancelProgramOffer's DELETE resolution bypassed protection entirely.
  • Fix regressions surfaced by review: crashes in the People admin views for involvements whose response was deleted, a dead-code cleanup path for old response versions, fail-open protection when an event has no ProgramV2EventMeta, and undiscoverable protection messaging on the program-forms response list.

Test plan

  • docker compose -f docker-compose.test.yml run --rm test — 169 passed
  • prek run --all-files on touched files — clean
  • npm run build / npm run lint in kompassi-v2-frontend — clean
  • Manually exercise: delete program form responses (list + detail), edit a program form response, toggle "protect program offers and responses" on the program preferences page and confirm deletion is blocked/allowed accordingly

fixes #714
fixes #920
fixes #1005
improves #1020
causes #1025

@japsu japsu changed the title feat(program): allow deleting program form responses feat(program): admin edit/delete program form responses, protecc program form responses & program offers Sep 1, 2026
@japsu
japsu marked this pull request as ready for review September 1, 2026 21:35
japsu and others added 6 commits September 2, 2026 22:38
Remove the FORMS-only guard from deleteSurveyResponses so it also
works for program_v2 surveys; CBAC already keys off survey.app
generically. Change Involvement.response to SET_NULL so deleting a
response no longer cascade-deletes the Involvement it produced. Wire
up the existing bulk-delete UI on the program-forms response list
page, generalizing the redirect/revalidate path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…etail page

Add an edit page for individual program form responses (mirroring the
regular survey and program offer edit pages) and wire up canEdit/canDelete
action buttons on the detail page, which previously queried these fields
but rendered no actions at all. Generalize the shared edit-response
server action to take an explicit basePath so it redirects/revalidates
correctly for program-forms as well as regular survey routes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sponses

Add ProgramV2EventMeta.protect_responses (default True), settable from
the program admin preferences page, to guard against a repeat of an
incident where a concom member accidentally deleted all program offers
during an event. Unlike Survey.protect_responses (per-survey, used by
regular forms), this new switch is event-wide since program offers and
program form responses don't currently expose a per-survey toggle.

Reconcile the long-standing split between Survey.can_responses_be_deleted_by
and Workflow.response_can_be_deleted_by (TODO #714) by having both delegate
to a new Workflow.responses_can_be_deleted_by, with Workflow.protect_responses
overridden per app: the base implementation reads Survey.protect_responses
(forms), while ProgramOfferWorkflow and ProgramHostInvitationWorkflow read
the new event-wide switch (program offers, program form responses). Also
apply the switch to ProgramV2EventMeta.can_program_offers_be_deleted_by
(bulk program offer deletion) and to CancelProgramOffer's DELETE resolution,
which previously bypassed protection entirely.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An independent review of the branch found real regressions from changing
Involvement.response to SET_NULL:

- Involvement.profile_field_selector/get_title/admin_link crashed
  (NotImplementedError/TypeError) for PROGRAM_OFFER and SURVEY_RESPONSE
  involvements whose response had been deleted, taking down the People
  admin views. These now degrade gracefully instead.
- DeleteProgramOffers still had a comment/no-op update() written for the
  old CASCADE behavior; corrected to reflect that involvements now survive
  deletion (pending a future Involvement expiration feature) instead of
  being cascade-removed.
- DeleteSurveyResponses' old-version cleanup filtered current_responses
  (which requires superseded_by=None) by superseded_by, an impossible
  combination, and ran after the SET_NULL from deleting the current
  version had already cleared it anyway — so old versions were never
  actually deleted. Fixed by deleting old versions first, via
  all_responses.
- ProgramOfferWorkflow/ProgramHostInvitationWorkflow.protect_responses
  failed open (unprotected) when an event has no ProgramV2EventMeta;
  flipped to fail closed, consistent with the new toggle's safety-first
  default.
- The program-forms response list page checked Survey.protect_responses
  (always false for PROGRAM app, irrelevant since the event-wide switch
  governs deletion there) to explain why deletion was disabled, so the
  toggle was practically undiscoverable from that page. Now checks the
  event-wide switch and points at the program preferences page.
- Stale docstring on canRemoveResponses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sesBeDeleted enum

Replace the various boolean-returning permission checks
(Survey.can_responses_be_deleted_by, Workflow.responses_can_be_deleted_by/
response_can_be_deleted_by, ProgramV2EventMeta.can_program_offers_be_deleted_by)
with a single CanResponsesBeDeleted enum (YES, NO_PROTECTED, NO_OLD_VERSION,
NO_UNAUTHORIZED) computed once at the source. This pushes the reason a
deletion is disallowed right next to the logic that decides it, instead of
having callers (mutations, GraphQL boolean resolvers, frontend pages)
separately re-derive "why not" and risk disagreeing with the check itself
-- which is exactly the class of bug fixed for program-forms responses in
the previous commit.

The enum overrides __bool__ to agree with `== YES`, so `if not status:`
keeps working correctly for existing boolean-shaped call sites without
requiring every one of them to spell out the comparison, while GraphQL
`canRemoveResponses`/`canDelete`/`canDeleteProgramOffers` boolean fields
are now explicitly derived via `.can_delete` from the same computation.
New `responsesDeletionStatus`/`programOffersDeletionStatus` GraphQL fields
expose the enum itself; the two response-list pages that show a "why can't
I delete this" message now switch on it directly instead of separately
inferring the reason from `protectResponses`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…leted

Replace the nested if/else that mapped responsesDeletionStatus to a
human-readable reason with direct indexing into a new
deleteVisibleResponses.reasons translation object keyed by the enum's
members, in both the regular survey and program-forms response list
pages. The program-forms page overrides just the NO_PROTECTED entry
with its event-wide-toggle-specific message, reusing the rest.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@japsu
japsu force-pushed the feat/delete-program-form-responses branch from dce87b5 to 8d966d1 Compare September 2, 2026 19:38
@japsu
japsu merged commit e77ee0f into main Sep 2, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant