Navigation

Survey Response Management

Real collection runs accumulate test submissions before launch and junk after. The Responses blade's Individual view — beside its Summary — carries the minimum owner-side operations over that data: a detail view of one respondent's full answers, a per-response delete, and a response count on the Overview so the number is visible without opening the blade. All of it sits on the existing Azure Table data — no new services.

How it works

flowchart LR
  BLADE["Responses blade<br/>response table + row actions"] -->|row action| DETAIL["Response detail dialog<br/>question → answer list"]
  BLADE -->|"delete action → confirm"| DEL["deleteSurveyResponse<br/>owner-gated"]
  DEL --> AT[("SurveyResponseEntity")]
  BLADE -->|readSurveyResponseRecords| RECORDS["columns + rows, each row<br/>carrying its own rowKey"]
  OV["Survey Overview Essentials"] -->|readSurveyResponsesCount| COUNT["N responses → Responses blade"]
  • Submitted responses only — the respondent page autosaves as it is answered, so a row exists from the first answer. Until the respondent submits, that row carries isDraft: true; the submit writes the whole row back without it (a Merge cannot drop a key), and a later save can never make it a draft again. Every read that counts, lists or joins responses — the count, the Responses blade, the dataset and the program funnel — filters through getSurveyResponsesFilter, which asks for the key to be absent, so a respondent who answered one question and left is a response on no surface. A row stored before drafts were marked has no key and reads as submitted, so no backfill was needed; an input that omits isDraft submits, so a tab still running the earlier bundle keeps its meaning.
  • Detail dialog — a per-row action opening the response as a question → answer list, the dataset row rendered vertically. Answers already arrive flattened through the dataset, so there is no new read path. One singleton dialog serves the whole table, driven by the target row key.
  • Delete — deleteSurveyResponse({ id, rowKey }). The partition key is the survey id derived server-side from the owner-checked id, never accepted from the caller, so one owner's survey id can never reach another survey's rows. Existence is proven before deleting, so a second delete of the same key errors rather than silently passing. The confirm follows the resource page parity guard patterns.
  • Count — readSurveyResponsesCount({ id }), owner-gated, surfaced on the Survey Overview Essentials grid as an "N responses" link to the Responses blade. Azure Table has no cheap server-side count, so this counts keys-only pages up to one past AZURE_MAX_PAGE_SIZE and returns the count plus an isCapped flag — that extra key is what distinguishes exactly-cap from beyond-cap, and only isCapped renders the 1000+ form.

Procedures

ProcedureAuthInputPurpose
readSurveyResponsesCountowner{ id }capped response count + isCapped
deleteSurveyResponseowner{ id, rowKey }remove one response entity
readSurveyResponseRecordsowner{ id }the blade's rows, each with its key

Key files

FileRole
apps/web/server/services/survey/getSurveyResponsesFilter.tssubmitted rows only
apps/web/server/services/survey/readSurveyResponsesCount.tsthe capped count
apps/web/server/services/survey/readSurveyResponseRecords.tsrows + their keys from one read
apps/web/app/components/Resource/Survey/Responses.vuerow actions
apps/web/app/components/Resource/Survey/ResponseDetailDialog.vuethe detail dialog
apps/web/app/components/Resource/Survey/ResponseDeleteDialog.vuethe destructive confirm
apps/web/app/components/Resource/Survey/Overview.vuecount + blade link

Notes

  • The dataset contract stays untouched — delete and count are survey-specific owner tooling, not dataset capabilities (single consumer; the admission rule is in resources).
  • Deleting a response needs its rowKey, which a dataset row doesn't carry. Rather than reading keys and rows as two lists and matching them by index — which a response submitted or deleted between the reads shifts out of alignment, pointing a delete at the wrong response — readSurveyResponseRecords returns each row already carrying its own key, from the one readSurveyResponseDatasetSource read the dataset provider also uses.
  • Bulk delete-all ("reset before launch") is one confirmation away from the same procedure in a loop; it stays out until single delete proves insufficient.
  • Editing a respondent's answers is deliberately out — owners moderate, they don't rewrite answers.

Details

Command palette

Keyboard shortcuts