Show 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 throughgetSurveyResponsesFilter, 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 omitsisDraftsubmits, 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-checkedid, 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 pastAZURE_MAX_PAGE_SIZEand returns the count plus anisCappedflag — that extra key is what distinguishes exactly-cap from beyond-cap, and onlyisCappedrenders the1000+form.
Procedures
| Procedure | Auth | Input | Purpose |
|---|---|---|---|
readSurveyResponsesCount | owner | { id } | capped response count + isCapped |
deleteSurveyResponse | owner | { id, rowKey } | remove one response entity |
readSurveyResponseRecords | owner | { id } | the blade's rows, each with its key |
Key files
| File | Role |
|---|---|
apps/web/server/services/survey/getSurveyResponsesFilter.ts | submitted rows only |
apps/web/server/services/survey/readSurveyResponsesCount.ts | the capped count |
apps/web/server/services/survey/readSurveyResponseRecords.ts | rows + their keys from one read |
apps/web/app/components/Resource/Survey/Responses.vue | row actions |
apps/web/app/components/Resource/Survey/ResponseDetailDialog.vue | the detail dialog |
apps/web/app/components/Resource/Survey/ResponseDeleteDialog.vue | the destructive confirm |
apps/web/app/components/Resource/Survey/Overview.vue | count + 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 —readSurveyResponseRecordsreturns each row already carrying its own key, from the onereadSurveyResponseDatasetSourceread 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.
Scroll to top