@explorer02/cfm-survey-sdk 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +18 -18
- package/dist/cli/index.mjs +14 -14
- package/dist/index.d.mts +465 -580
- package/dist/index.d.ts +465 -580
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +6 -4
- package/postinstall.js +96 -69
- package/templates/AGENT.md +18 -8
- package/templates/docs/00-integration/analytics-events-catalog.md +54 -0
- package/templates/docs/00-integration/answer-logic-and-navigation.md +50 -0
- package/templates/docs/00-integration/aws-deploy.md +281 -0
- package/templates/docs/00-integration/client-integration-guide.md +351 -0
- package/templates/docs/00-integration/component-checklist.md +55 -22
- package/templates/docs/00-integration/constraints.md +49 -4
- package/templates/docs/00-integration/custom-field-logic-and-navigation.md +174 -0
- package/templates/docs/00-integration/display-logic-and-navigation.md +64 -0
- package/templates/docs/00-integration/end-page-logic.md +64 -0
- package/templates/docs/00-integration/file-upload-aws.md +116 -0
- package/templates/docs/00-integration/logic-fields-catalog.md +105 -0
- package/templates/docs/00-integration/partial-save-and-recovery.md +98 -0
- package/templates/docs/00-integration/placeholders-and-tokens.md +116 -0
- package/templates/docs/00-integration/progress.md +61 -0
- package/templates/docs/00-integration/question-display-variants.md +65 -0
- package/templates/docs/00-integration/question-numbering.md +51 -0
- package/templates/docs/00-integration/question-type-sdk-matrix.md +244 -0
- package/templates/docs/00-integration/setup.md +84 -0
- package/templates/docs/00-integration/skip-logic-and-navigation.md +80 -0
- package/templates/docs/00-integration/survey-lifecycle-analytics.md +94 -0
- package/templates/docs/00-integration/useSurveySDK.md +151 -23
- package/templates/docs/01-components/01-survey-page.md +76 -54
- package/templates/docs/01-components/02-question.md +61 -58
- package/templates/docs/01-components/03-rating-scale.md +11 -1
- package/templates/docs/01-components/04-star-rating-scale.md +45 -0
- package/templates/docs/01-components/05-csat-matrix-scale.md +20 -11
- package/templates/docs/01-components/06-likert-matrix-scale.md +14 -4
- package/templates/docs/01-components/07-slider-matrix-scale.md +11 -2
- package/templates/docs/01-components/08-file-upload-scale.md +23 -51
- package/templates/docs/01-components/09-custom-slider-track.md +2 -2
- package/templates/docs/01-components/11-progress-bar.md +10 -4
- package/templates/docs/01-components/13-matrix-dropdown.md +29 -32
- package/templates/docs/01-components/14-intro-page.md +34 -0
- package/templates/docs/01-components/15-end-page.md +31 -0
- package/templates/docs/01-components/16-paused-page.md +27 -0
- package/templates/docs/01-components/17-heatmap-scale.md +75 -0
- package/templates/docs/01-components/18-rank-order-scale.md +93 -0
- package/templates/docs/01-components/README.md +42 -36
- package/templates/docs/02-reference/config-field-index.md +141 -139
- package/templates/docs/02-reference/exports.md +149 -0
- package/templates/docs/02-reference/question-types/01-rating.md +31 -32
- package/templates/docs/02-reference/question-types/04-csat.md +51 -42
- package/templates/docs/02-reference/question-types/05-rating-scale.md +16 -11
- package/templates/docs/02-reference/question-types/06-slider.md +2 -2
- package/templates/docs/02-reference/question-types/07-matrix-cfm.md +44 -30
- package/templates/docs/02-reference/question-types/08-matrix-csat.md +4 -26
- package/templates/docs/02-reference/question-types/09-matrix-rating.md +29 -13
- package/templates/docs/02-reference/question-types/10-slider-matrix.md +15 -4
- package/templates/docs/02-reference/question-types/11-file-upload.md +43 -17
- package/templates/docs/02-reference/question-types/12-heatmap.md +59 -0
- package/templates/docs/02-reference/question-types/13-rank-order.md +101 -0
- package/templates/docs/02-reference/question-types/README.md +96 -47
- package/templates/docs/02-reference/routing-table.md +33 -18
- package/templates/docs/02-reference/value-derivation.md +63 -0
- package/templates/docs/03-ui-specs/00-question-shell.md +19 -88
- package/templates/docs/03-ui-specs/01-rating.md +18 -52
- package/templates/docs/03-ui-specs/02-radio.md +80 -37
- package/templates/docs/03-ui-specs/03-text.md +2 -2
- package/templates/docs/03-ui-specs/04-csat.md +68 -33
- package/templates/docs/03-ui-specs/05-rating-scale.md +21 -18
- package/templates/docs/03-ui-specs/06-slider.md +3 -1
- package/templates/docs/03-ui-specs/07-matrix-cfm.md +44 -25
- package/templates/docs/03-ui-specs/08-matrix-csat-rating.md +19 -43
- package/templates/docs/03-ui-specs/09-slider-matrix.md +18 -1
- package/templates/docs/03-ui-specs/10-file-upload.md +42 -21
- package/templates/docs/03-ui-specs/11-text-and-media.md +29 -29
- package/templates/docs/03-ui-specs/12-survey-chrome.md +58 -14
- package/templates/docs/03-ui-specs/13-heatmap.md +80 -0
- package/templates/docs/03-ui-specs/14-rank-order.md +128 -0
- package/templates/docs/03-ui-specs/README.md +16 -14
- package/templates/docs/03-ui-specs/shared/custom-slider-track.md +28 -35
- package/templates/docs/03-ui-specs/shared/matrix-dropdown.md +5 -0
- package/templates/docs/MANIFEST.json +476 -69
- package/templates/docs/index.md +82 -30
- package/templates/docs/templates/CustomSliderTrack.tsx +144 -0
- package/templates/docs/templates/MatrixDropdown.tsx +216 -0
- package/templates/docs/templates/Question.tsx +221 -154
- package/templates/docs/templates/RankOrderScale.tsx +353 -0
- package/templates/docs/templates/deploy-to-aws.sh +114 -0
- package/templates/docs/templates/heatmapCoords.ts +58 -0
- package/templates/docs/templates/implementation_plan.md +126 -67
- package/templates/docs/templates/survey-inventory.schema.json +77 -0
- package/templates/docs/templates/surveyUiIcons.tsx +52 -0
- package/templates/docs/templates/verify-agent-build.sh +85 -0
- package/templates/docs/01-components/04-csat-scale.md +0 -40
- package/templates/docs/02-question-types/01-rating.md +0 -44
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Custom Field Logic and Navigation
|
|
2
|
+
|
|
3
|
+
> Integrator-facing contract for CRM/transaction custom fields used in skip, display, and answer logic. Evaluation runs inside the SDK — pass values via hook options; do not import internal `surveyLogic` paths.
|
|
4
|
+
|
|
5
|
+
## When this applies
|
|
6
|
+
|
|
7
|
+
Survey logic rules may reference **custom fields** instead of survey questions:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"conditionEntity": {
|
|
12
|
+
"id": "_c_6a436c1244764c9d4a6ce2fc",
|
|
13
|
+
"entityType": "CUSTOM_FIELD",
|
|
14
|
+
"fieldType": "TEXT"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Inspect fetched survey data: any `skipLogics`, `displayLogic`, or `answerLogic` with `entityType: 'CUSTOM_FIELD'` requires client-supplied values.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Client value map
|
|
24
|
+
|
|
25
|
+
Pass via `useSurveySDK({ options: { customFieldValues } })`. Keys are the `_c_…` ids from logic rules.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import type { CustomFieldValueMap } from '@explorer02/cfm-survey-sdk';
|
|
29
|
+
|
|
30
|
+
const CUSTOM_FIELD_VALUES: CustomFieldValueMap = {
|
|
31
|
+
'_c_6a436c1244764c9d4a6ce2fc': 'q5', // TEXT
|
|
32
|
+
'_c_6a436c3644764c9d4a6ce3f5': 2, // NUMBER
|
|
33
|
+
'_c_6a436c9e44764c9d4a6ce717': 'option-id-uuid', // PICKLIST
|
|
34
|
+
'_c_6a436cbb44764c9d4a6ce7e5': ['opt-a', 'opt-b'], // PICKLIST_MULTISELECT
|
|
35
|
+
'_c_6a4386dc44208eb8b08a9056': 1781029800000, // DATETIME (epoch ms)
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Define **outside** the component (stable ref — same rule as `placeholders`). Wrap hook `options` in `useMemo`.
|
|
40
|
+
|
|
41
|
+
| Custom field type (`fieldType`) | Client value shape |
|
|
42
|
+
|--------------------------------|-------------------|
|
|
43
|
+
| TEXT / TEXT_MULTI / TEXTAREA | `string` |
|
|
44
|
+
| NUMBER | `number` or numeric string (`0` is valid, not empty) |
|
|
45
|
+
| PICKLIST | `string` (option id) |
|
|
46
|
+
| PICKLIST_MULTISELECT | `string[]` (option ids) |
|
|
47
|
+
| DATETIME | epoch ms (`number` or numeric string); ISO date strings also accepted |
|
|
48
|
+
|
|
49
|
+
Missing keys in the map are treated as **empty** for logic evaluation.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Operator contract
|
|
54
|
+
|
|
55
|
+
| fieldType | Logic `field` | Operators |
|
|
56
|
+
|-----------|---------------|-----------|
|
|
57
|
+
| TEXT / TEXT_MULTI / TEXTAREA | `TEXT` or `CASE_SENSITIVE_TEXT` | EQUALS, NOT_EQUALS, CONTAINS, DOES_NOT_CONTAIN, MISSING, EXISTS |
|
|
58
|
+
| NUMBER | `NUMBER` | LT, LTE, GT, GTE, EQUALS, NOT_EQUALS, MISSING, EXISTS |
|
|
59
|
+
| PICKLIST / PICKLIST_MULTISELECT | `SELECTED_OPTIONS` | CONTAINS, DOES_NOT_CONTAIN, MISSING, EXISTS |
|
|
60
|
+
| DATETIME | `SELECTED_DATETIME` | LT, LTE, GT, GTE, EQUALS, NOT_EQUALS |
|
|
61
|
+
|
|
62
|
+
Filter `values` in API rules use strings (e.g. `'1781029800000'` for datetime). The SDK normalizes client datetime values to epoch ms for comparison.
|
|
63
|
+
|
|
64
|
+
Datetime filters may include `valueAdditional.timezone` (e.g. `"Asia/Kolkata"`) — reserved for future timezone-aware parsing; comparisons currently use epoch ms numbers.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Two wiring points (CRITICAL)
|
|
69
|
+
|
|
70
|
+
Custom field values must reach **both** the hook and the question dispatcher:
|
|
71
|
+
|
|
72
|
+
```mermaid
|
|
73
|
+
flowchart LR
|
|
74
|
+
CRM["CRM / transaction payload"]
|
|
75
|
+
Map["CUSTOM_FIELD_VALUES map"]
|
|
76
|
+
Hook["useSurveySDK options"]
|
|
77
|
+
Question["Question.tsx"]
|
|
78
|
+
Nav["Skip / display / validation / progress"]
|
|
79
|
+
AnswerUI["Answer-logic option/row filtering"]
|
|
80
|
+
|
|
81
|
+
CRM --> Map
|
|
82
|
+
Map --> Hook --> Nav
|
|
83
|
+
Map --> Question --> AnswerUI
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 1. Hook options (navigation layer)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
useSurveySDK({
|
|
90
|
+
options: {
|
|
91
|
+
customFieldValues: CUSTOM_FIELD_VALUES,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
SDK uses this for:
|
|
97
|
+
|
|
98
|
+
- Skip logic path simulation and jumps
|
|
99
|
+
- Question-level display logic (`state.currentQuestions`)
|
|
100
|
+
- Submit / Next validation on the active path
|
|
101
|
+
- Progress bar denominator
|
|
102
|
+
|
|
103
|
+
### 2. Question visibility helpers (answer-logic UI)
|
|
104
|
+
|
|
105
|
+
When answer logic conditions reference custom fields, pass the **same map** to exported helpers:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import {
|
|
109
|
+
getVisibleMcqOptionsForAnswers,
|
|
110
|
+
getVisibleMatrixItemsForAnswers,
|
|
111
|
+
getVisibleStatementRowsForAnswers,
|
|
112
|
+
getVisibleScaleColumnsForAnswers,
|
|
113
|
+
} from '@explorer02/cfm-survey-sdk';
|
|
114
|
+
|
|
115
|
+
getVisibleMcqOptionsForAnswers(question, allAnswers, allQuestions, customFieldValues);
|
|
116
|
+
getVisibleMatrixItemsForAnswers(question, allAnswers, allQuestions, customFieldValues);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Without the 4th argument, answer-logic UI defaults to an empty custom-field map — options/rows gated on CRM data may render incorrectly even though navigation logic works.
|
|
120
|
+
|
|
121
|
+
**SurveyPage → Question prop threading:**
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<Question
|
|
125
|
+
customFieldValues={CUSTOM_FIELD_VALUES}
|
|
126
|
+
allAnswers={state.answers}
|
|
127
|
+
allQuestions={allQuestions}
|
|
128
|
+
// ...
|
|
129
|
+
/>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Discovering field ids
|
|
135
|
+
|
|
136
|
+
After fetching the survey with `instanceId`, search logic payloads for `_c_` ids:
|
|
137
|
+
|
|
138
|
+
- `question.skipLogics[].condition.filters`
|
|
139
|
+
- `question.displayLogic.condition.filters`
|
|
140
|
+
- `question.options[].answerLogic` / matrix row `answerLogic` / column `answerLogic`
|
|
141
|
+
|
|
142
|
+
Record each id in `implementation_plan.md` §3c and populate `CUSTOM_FIELD_VALUES` from your CRM/transaction API.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Example — skip rule on custom field
|
|
147
|
+
|
|
148
|
+
Rule: if TEXT custom field `_c_abc` equals `"premium"`, skip to Q6.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const CUSTOM_FIELD_VALUES = { '_c_abc': 'premium' };
|
|
152
|
+
|
|
153
|
+
useSurveySDK({ options: { instanceId, customFieldValues: CUSTOM_FIELD_VALUES } });
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
User clicks Next on page 1 → SDK evaluates condition → jumps to Q6's page. No extra client code per question.
|
|
157
|
+
|
|
158
|
+
If the user later changes a CRM field value at runtime, update `customFieldValues` via a new stable map and remount or pass updated options through `useMemo` deps.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Multiple skip logic blocks
|
|
163
|
+
|
|
164
|
+
When a question has several `skipLogics[]` entries (including custom-field rules), the SDK evaluates them **top-to-bottom** and applies the **first match**. See [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md).
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Related docs
|
|
169
|
+
|
|
170
|
+
- [`useSurveySDK.md`](useSurveySDK.md) — hook options and stable refs
|
|
171
|
+
- [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md) — skip behavior with custom fields
|
|
172
|
+
- [`display-logic-and-navigation.md`](display-logic-and-navigation.md) — question-level gating
|
|
173
|
+
- [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) — option/row/column gating + visibility helpers
|
|
174
|
+
- [`client-integration-guide.md`](client-integration-guide.md) — full wiring map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Display Logic and Navigation
|
|
2
|
+
|
|
3
|
+
> Integrator-facing behavior contract. Display evaluation is handled inside the SDK — do not import internal `surveyLogic` paths directly unless using exported helpers.
|
|
4
|
+
|
|
5
|
+
## Shared engine with skip logic
|
|
6
|
+
|
|
7
|
+
Skip logic, display logic, and answer logic use the **same condition tree** (`LogicFilterGroup` with nested `AND` / `OR` groups) and the **same evaluator** (`evaluateCondition`). The SDK distinguishes them with `LOGIC_KIND`:
|
|
8
|
+
|
|
9
|
+
| Kind | When true |
|
|
10
|
+
|------|-----------|
|
|
11
|
+
| `SKIP` | Navigate to skip target or end of survey |
|
|
12
|
+
| `DISPLAY` | Show the gated **question** (whole question / page-level) |
|
|
13
|
+
| `ANSWER` | Show a gated **option, statement row, or scale column** within a question |
|
|
14
|
+
|
|
15
|
+
> **Note:** Statement-row and scale-column filtering is **answer logic**, not display logic. See [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md).
|
|
16
|
+
|
|
17
|
+
## Dynamic vs non-dynamic
|
|
18
|
+
|
|
19
|
+
| Mode | API flag | Behavior |
|
|
20
|
+
|------|----------|----------|
|
|
21
|
+
| **Dynamic** | `isDynamicAppearanceEnabled: true` | Gated question stays on the **same page** as trigger questions. Hidden until condition is true. Page stays on active path; hidden questions are excluded from progress denominator. |
|
|
22
|
+
| **Non-dynamic** | `isDynamicAppearanceEnabled: false` | Gated question is on a **separate page**. When condition is false, that page is skipped in navigation and excluded from active path. |
|
|
23
|
+
|
|
24
|
+
## Nested conditions
|
|
25
|
+
|
|
26
|
+
Both skip and display support nested AND/OR groups from the API. Flat skip rows (`Q3 = 1 OR Q3 = 2 OR Q3 > 6`) and nested display condition groups map to the same recursive tree.
|
|
27
|
+
|
|
28
|
+
## Client responsibilities
|
|
29
|
+
|
|
30
|
+
- Use `state.currentQuestions` from `useSurveySDK` — the SDK filters hidden questions automatically.
|
|
31
|
+
- Matrix UIs must filter statement rows with `getVisibleStatementRowsForAnswers(question, answers, allQuestions)` (or `getVisibleMatrixItemsForAnswers` for rows + scale columns).
|
|
32
|
+
- Wire `onAction({ type: 'CHANGE' })` so display conditions re-evaluate when answers change.
|
|
33
|
+
|
|
34
|
+
## Progress bar
|
|
35
|
+
|
|
36
|
+
Progress counts **visible answerable questions on the active path**. When display logic hides a question:
|
|
37
|
+
|
|
38
|
+
- **Dynamic:** page remains on path; hidden question excluded from total until visible.
|
|
39
|
+
- **Non-dynamic:** gated page excluded from path until condition is true.
|
|
40
|
+
|
|
41
|
+
## Custom field conditions
|
|
42
|
+
|
|
43
|
+
Display logic may gate questions on **custom field** values (`conditionEntity.entityType: 'CUSTOM_FIELD'`). See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md) for the full integration contract.
|
|
44
|
+
|
|
45
|
+
## Question display variants (distinct from show/hide)
|
|
46
|
+
|
|
47
|
+
Alternate question wording (`questionDisplayVariants`) uses the same condition engine but **does not hide** the question — the SDK resolves `questionText` on `state.currentQuestions`. See [`question-display-variants.md`](question-display-variants.md).
|
|
48
|
+
|
|
49
|
+
See also: [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md), [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`progress.md`](progress.md), [`logic-fields-catalog.md`](logic-fields-catalog.md).
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Worked example — dynamic display on same page
|
|
54
|
+
|
|
55
|
+
**Setup:** Page 1 has Q1 (MCQ trigger) and Q2 (display-gated, `isDynamicAppearanceEnabled: true`). Q2 hidden until Q1 selects option A.
|
|
56
|
+
|
|
57
|
+
1. User loads page → `currentQuestions` = [Q1] only; progress denominator excludes Q2.
|
|
58
|
+
2. User selects A → SDK evaluates display logic → `currentQuestions` = [Q1, Q2]; Q2 animates in; progress total increases.
|
|
59
|
+
3. User answers Q2 → normal CHANGE flow.
|
|
60
|
+
4. User changes Q1 away from A → SDK sanitizes Q2 answer; Q2 hidden again; progress credits adjust.
|
|
61
|
+
|
|
62
|
+
**Client:** render `state.currentQuestions` only — no manual show/hide. Re-run IntersectionObserver effect when `currentQuestions` changes.
|
|
63
|
+
|
|
64
|
+
**Back navigation:** if display logic hides all questions on a page, SDK skips empty pages — user never sees a blank screen (see skip/display integration tests).
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# End Page Logic
|
|
2
|
+
|
|
3
|
+
> Integrator-facing contract for surveys with **multiple end pages**. Selection runs inside the SDK at submit time — do not import internal `surveyLogic` paths.
|
|
4
|
+
|
|
5
|
+
## When it runs
|
|
6
|
+
|
|
7
|
+
After a **successful submit**, the SDK evaluates each end page's `displayLogic` (mapped from API `controlConfig.displayLogic`) and sets `state.phase` to `{ kind: 'end', endPageId }`. The client renders `state.specialPage` — no extra wiring.
|
|
8
|
+
|
|
9
|
+
## Selection rules
|
|
10
|
+
|
|
11
|
+
End pages are evaluated in **API order**:
|
|
12
|
+
|
|
13
|
+
1. **Conditional pages** — pages with `displayLogic`: return the **first** whose condition is true (same evaluator as question display logic: nested AND/OR, question answers, custom fields).
|
|
14
|
+
2. **Default page** — if no conditional page matches, use the **first page without** `displayLogic`.
|
|
15
|
+
3. **Safety fallback** — if every page has logic and none match, use `endPages[0]`.
|
|
16
|
+
|
|
17
|
+
`isDynamicAppearanceEnabled` on end pages is ignored (evaluation happens once at submit).
|
|
18
|
+
|
|
19
|
+
## Default vs conditional pages
|
|
20
|
+
|
|
21
|
+
Typical builder setup:
|
|
22
|
+
|
|
23
|
+
| End page | `displayLogic` | Role |
|
|
24
|
+
|----------|----------------|------|
|
|
25
|
+
| End Page | none | Default thank-you screen |
|
|
26
|
+
| End Page 2 | OR/AND conditions | Shown when rules match |
|
|
27
|
+
|
|
28
|
+
Example: show **End Page 2** when multiselect MCQ **contains** Option 1 **and** Option 3 (both must be selected). Otherwise show the default end page.
|
|
29
|
+
|
|
30
|
+
For `SELECTED_OPTIONS` / `CONTAINS`, multiple values in the rule mean **all** listed options must be selected (Sprinklr multiselect semantics). A single listed option still matches when only that option is selected.
|
|
31
|
+
|
|
32
|
+
## Custom fields
|
|
33
|
+
|
|
34
|
+
When end-page rules reference `conditionEntity.entityType: 'CUSTOM_FIELD'`, pass the same `options.customFieldValues` map used for skip/display logic:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
useSurveySDK(instanceId, {
|
|
38
|
+
customFieldValues: {
|
|
39
|
+
'_c_6a436c1244764c9d4a6ce2fc': 'end2',
|
|
40
|
+
'_c_6a436c3644764c9d4a6ce3f5': 2,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Without this map, custom-field branches evaluate as empty/missing.
|
|
46
|
+
|
|
47
|
+
### OR branches and static mock values
|
|
48
|
+
|
|
49
|
+
Conditional end pages often use **OR** groups (any branch true → show that page). If `customFieldValues` includes a value that satisfies one branch unconditionally—e.g. number field `= 2` while the survey has `NUMBER EQUALS 2` in an OR branch—that end page will **always** win at submit, even when MCQ/NPS answers would otherwise show the default page.
|
|
50
|
+
|
|
51
|
+
When testing answer-driven branches, omit unrelated custom-field keys or set them to values that do not match other OR branches.
|
|
52
|
+
|
|
53
|
+
## Client responsibilities
|
|
54
|
+
|
|
55
|
+
- Render `<EndPage page={state.specialPage} />` when `state.phase.kind === 'end'`.
|
|
56
|
+
- Use `state.phase.endPageId` (and `options.debug`) during development to verify selection when headers match.
|
|
57
|
+
- Pass `customFieldValues` via hook options when the survey's end pages reference custom fields.
|
|
58
|
+
- No logic evaluation in the client — the SDK resolves `endPageId` before setting phase.
|
|
59
|
+
|
|
60
|
+
## Supported condition fields
|
|
61
|
+
|
|
62
|
+
Same as skip/display logic: `SELECTED_OPTIONS`, `NUM_OF_SELECTED_OPTIONS`, `TEN_POINT_SCALE`, `TEXT`, `CASE_SENSITIVE_TEXT`, custom-field `NUMBER`, `PICKLIST`, `DATETIME`, etc.
|
|
63
|
+
|
|
64
|
+
See also: [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md), [`display-logic-and-navigation.md`](display-logic-and-navigation.md), [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md), [`01-components/15-end-page.md`](../01-components/15-end-page.md).
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# File Upload — Presigned S3 Flow
|
|
2
|
+
|
|
3
|
+
> Required for **submittable** file-upload answers. Raw `File[]` in `state.answers` is **dropped** at submit — SDK expects `UploadedFileAnswer[]` with `id` and `mediaUrl`.
|
|
4
|
+
|
|
5
|
+
Related: [`03-ui-specs/10-file-upload.md`](../03-ui-specs/10-file-upload.md) · [`02-reference/question-types/11-file-upload.md`](../02-reference/question-types/11-file-upload.md)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Answer contract
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
type UploadedFileAnswer = {
|
|
13
|
+
id: string;
|
|
14
|
+
mediaUrl: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
mimeType?: string;
|
|
17
|
+
size?: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
onAction({
|
|
21
|
+
type: 'CHANGE',
|
|
22
|
+
payload: { questionId, answerValue: UploadedFileAnswer[] },
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Submit formatter maps to `quesIdVsAttachmentDetails[questionId]`.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Upload API (EC2 reference — Day 22)
|
|
31
|
+
|
|
32
|
+
**Base URL:** configure via env (example: `http://43.204.26.213:3000` or project-specific upload service).
|
|
33
|
+
|
|
34
|
+
### Step 1 — Request presigned URL
|
|
35
|
+
|
|
36
|
+
```http
|
|
37
|
+
POST /get-upload-url
|
|
38
|
+
Content-Type: application/json
|
|
39
|
+
|
|
40
|
+
{
|
|
41
|
+
"fileName": "report.pdf",
|
|
42
|
+
"contentType": "application/pdf",
|
|
43
|
+
"questionId": "<question.id>"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Response:**
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"uploadUrl": "https://s3.amazonaws.com/...",
|
|
52
|
+
"fileId": "generated-id",
|
|
53
|
+
"mediaUrl": "https://cdn.../path/to/file"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Step 2 — PUT file to S3
|
|
58
|
+
|
|
59
|
+
```http
|
|
60
|
+
PUT {uploadUrl}
|
|
61
|
+
Content-Type: application/pdf
|
|
62
|
+
Body: <file binary>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 3 — Update SDK state
|
|
66
|
+
|
|
67
|
+
Append to answer array and call `onAction(CHANGE)` with `{ id: fileId, mediaUrl, name, mimeType, size }`.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Client implementation sketch
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
async function uploadFile(file: File, questionId: string): Promise<UploadedFileAnswer> {
|
|
75
|
+
const res = await fetch(`${UPLOAD_API_BASE}/get-upload-url`, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify({
|
|
79
|
+
fileName: file.name,
|
|
80
|
+
contentType: file.type,
|
|
81
|
+
questionId,
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
const { uploadUrl, fileId, mediaUrl } = await res.json();
|
|
85
|
+
await fetch(uploadUrl, { method: 'PUT', body: file, headers: { 'Content-Type': file.type } });
|
|
86
|
+
return { id: fileId, mediaUrl, name: file.name, mimeType: file.type, size: file.size };
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Wire `UPLOAD_API_BASE` from env — **never** embed AWS credentials in client code.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## UI states
|
|
95
|
+
|
|
96
|
+
| State | UX |
|
|
97
|
+
|-------|-----|
|
|
98
|
+
| Uploading | Spinner on file row; disable dropzone |
|
|
99
|
+
| Success | Show file in list; call `onAction(CHANGE)` |
|
|
100
|
+
| Failure | Red banner; keep local file uncommitted |
|
|
101
|
+
| Remove | Update array; `undefined` when empty |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Partial save
|
|
106
|
+
|
|
107
|
+
Only call `onAction(CHANGE)` after upload completes — partial save should not POST raw `File` blobs.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Agent checklist
|
|
112
|
+
|
|
113
|
+
- [ ] Local validation before upload (format, count, size)
|
|
114
|
+
- [ ] Presigned PUT wired per file
|
|
115
|
+
- [ ] Final answer is `UploadedFileAnswer[]` with `id` + `mediaUrl`
|
|
116
|
+
- [ ] Submit body includes attachments (verify in network tab)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Logic Fields Catalog
|
|
2
|
+
|
|
3
|
+
> Central reference for skip, display, answer, variant, and end-page logic fields by question type. Agents do **not** implement evaluators — wire UI + hook; SDK evaluates conditions.
|
|
4
|
+
|
|
5
|
+
Cross-links: [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md) · [`display-logic-and-navigation.md`](display-logic-and-navigation.md) · [`answer-logic-and-navigation.md`](answer-logic-and-navigation.md) · [`end-page-logic.md`](end-page-logic.md)
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## MCQ
|
|
10
|
+
|
|
11
|
+
| Field | Used in | Operators | Notes |
|
|
12
|
+
|-------|---------|-----------|-------|
|
|
13
|
+
| `SELECTED_OPTIONS` / option id | skip, display, end | contains, does not contain, equals | Single or multi option ids |
|
|
14
|
+
| `NUM_OF_SELECTED_OPTIONS` | skip, display, end | `EQUALS`, `GT`, `LT`, etc. | Count of selected option ids; zero = missing for EXISTS |
|
|
15
|
+
| Option-level answer logic | answer (UI) | same engine | `getVisibleMcqOptionsForAnswers` |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## TEXTFIELD / TEXT_AND_MEDIA
|
|
20
|
+
|
|
21
|
+
| Field | Used in | Operators |
|
|
22
|
+
|-------|---------|-----------|
|
|
23
|
+
| `TEXT` / `CASE_SENSITIVE_TEXT` | skip, display, end | empty, contains, equals, etc. |
|
|
24
|
+
| TEXT_AND_MEDIA | — | no answer stored |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## NPS_SCALE
|
|
29
|
+
|
|
30
|
+
| Field | Used in | Operators |
|
|
31
|
+
|-------|---------|-----------|
|
|
32
|
+
| `TEN_POINT_SCALE` / scale value | skip, display, end | numeric compare `<`, `>`, `==`, `!=` |
|
|
33
|
+
| Scale point answer logic | answer (UI) | `getVisibleScaleColumnsForAnswers` |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## CFM_MATRIX / CSAT_MATRIX / RATING_MATRIX
|
|
38
|
+
|
|
39
|
+
| Field | Used in | Operators |
|
|
40
|
+
|-------|---------|-----------|
|
|
41
|
+
| Per-row composite keys | skip, display | per statement row + column id |
|
|
42
|
+
| Matrix cell values | skip, display | numeric or column id match |
|
|
43
|
+
| Row/column answer logic | answer (UI) | `getVisibleMatrixItemsForAnswers` |
|
|
44
|
+
|
|
45
|
+
Store answers as column **`id`** via `matrixColumnStoredValue`.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## SLIDER_MATRIX
|
|
50
|
+
|
|
51
|
+
| Field | Used in | Operators |
|
|
52
|
+
|-------|---------|-----------|
|
|
53
|
+
| Row statement values | skip, display | numeric compare |
|
|
54
|
+
| Row answer logic | answer (UI) | `getVisibleStatementRowsForAnswers` |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## FILE_UPLOAD
|
|
59
|
+
|
|
60
|
+
| Field | Used in | Operators |
|
|
61
|
+
|-------|---------|-----------|
|
|
62
|
+
| Presence only | skip, display | `EXISTS`, `MISSING` |
|
|
63
|
+
| Evaluation | on page entry | no discrete value-change events |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## HEATMAP
|
|
68
|
+
|
|
69
|
+
| Field | Used in | Operators |
|
|
70
|
+
|-------|---------|-----------|
|
|
71
|
+
| `HEATMAP` | skip, display | spot count, presence |
|
|
72
|
+
| `HEATMAP_REGION` | skip, display | region label match |
|
|
73
|
+
|
|
74
|
+
Regions are **logic-only** — not rendered on respondent UI.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## RANK_ORDER
|
|
79
|
+
|
|
80
|
+
| Field | Used in | Operators |
|
|
81
|
+
|-------|---------|-----------|
|
|
82
|
+
| `RANK_ORDER` (per option) | skip, display, answer | ranked as (`EQUALS`), ranked more/less than (`GT`/`LT`), ranked above/below (`BEFORE`/`AFTER`) |
|
|
83
|
+
| Whole question | skip, display | `EXISTS`, `MISSING` |
|
|
84
|
+
|
|
85
|
+
See [`02-reference/question-types/13-rank-order.md`](../02-reference/question-types/13-rank-order.md).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## CUSTOM_FIELD (all types)
|
|
90
|
+
|
|
91
|
+
| Field | Used in | Value shapes |
|
|
92
|
+
|-------|---------|--------------|
|
|
93
|
+
| `_c_*` entity ids | skip, display, end, answer | TEXT, NUMBER, PICKLIST, DATETIME, multiselect |
|
|
94
|
+
|
|
95
|
+
Pass stable `customFieldValues` to hook **and** visibility helpers. See [`custom-field-logic-and-navigation.md`](custom-field-logic-and-navigation.md).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Client wiring summary
|
|
100
|
+
|
|
101
|
+
| Logic kind | Client action |
|
|
102
|
+
|------------|---------------|
|
|
103
|
+
| Skip / display / end / variants | Render `state.currentQuestions`; no client evaluators |
|
|
104
|
+
| Answer logic | `getVisible*ForAnswers(..., customFieldValues?)` before render |
|
|
105
|
+
| Custom fields | Two wiring points: hook options + Question visibility helpers |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Partial Save and Recovery
|
|
2
|
+
|
|
3
|
+
> Covers `sdkFeatures.partialSave` — debounced autosave of in-progress responses.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to enable
|
|
8
|
+
|
|
9
|
+
Pass in `useSurveySDK` options:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
useSurveySDK({
|
|
13
|
+
...surveyOptions,
|
|
14
|
+
savePartialResponse: true, // default debounce
|
|
15
|
+
// or
|
|
16
|
+
savePartialResponse: { debounceMs: 1500 },
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Enable when the survey config or client prompt requires resume/recovery UX. If not requested, omit the option.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## `partialSaveResults` shape
|
|
25
|
+
|
|
26
|
+
Same surface as submit results (type inferred from hook return):
|
|
27
|
+
|
|
28
|
+
| Field | Meaning |
|
|
29
|
+
|-------|---------|
|
|
30
|
+
| `partialSaveResults.data` | Last successful partial save payload (`SubmitResult \| null`) |
|
|
31
|
+
| `partialSaveResults.isLoading` | POST in flight |
|
|
32
|
+
| `partialSaveResults.error` | Last failure |
|
|
33
|
+
|
|
34
|
+
Monitor these for optional "Saving…" / error toasts. Do **not** reimplement the POST endpoint logic client-side.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Interaction with skip / display logic
|
|
39
|
+
|
|
40
|
+
When skip or display logic hides a question, the SDK **sanitizes** answers for questions no longer on the active path before partial save. Client components should not manually delete hidden answers — the SDK handles consistency on path changes.
|
|
41
|
+
|
|
42
|
+
After a partial save succeeds, reloading the survey instance may restore prior answers depending on backend behavior (outside SDK scope).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## When partial save starts
|
|
47
|
+
|
|
48
|
+
Partial saves begin only after the **first user interaction** (a real `CHANGE` action). MCQ default seeding or programmatic answer init does not trigger an immediate save.
|
|
49
|
+
|
|
50
|
+
Each subsequent debounced answer change triggers another partial save with `surveySubmitted: false`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Error UX patterns
|
|
55
|
+
|
|
56
|
+
| Scenario | Recommended UX |
|
|
57
|
+
|----------|----------------|
|
|
58
|
+
| Transient network error | Silent retry on next debounced change; optional subtle indicator |
|
|
59
|
+
| Persistent error | Non-blocking banner: "Unable to save progress" |
|
|
60
|
+
| Success | Usually no UI — optional checkmark on footer |
|
|
61
|
+
|
|
62
|
+
Do not block navigation (NEXT/PREVIOUS) on partial save failure unless product explicitly requires it.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Per-type debounce guidance
|
|
67
|
+
|
|
68
|
+
| Question type | Client UI note |
|
|
69
|
+
|---------------|----------------|
|
|
70
|
+
| TEXTFIELD (long) | Debounce `onAction(CHANGE)` 300–500 ms to reduce partial-save churn |
|
|
71
|
+
| SLIDER_MATRIX / graphics | Debounce slider `onChange` or rely on SDK debounce |
|
|
72
|
+
| FILE_UPLOAD | Call `CHANGE` only **after** presigned upload completes — see [`file-upload-aws.md`](file-upload-aws.md) |
|
|
73
|
+
| MCQ / matrix | Immediate `CHANGE` on selection is fine |
|
|
74
|
+
| TEXT_AND_MEDIA | No CHANGE — excluded from partial save payload |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Recovery (honest scope)
|
|
79
|
+
|
|
80
|
+
Reload behavior depends on **backend** + instance configuration — the SDK debounces POST to `responseV2` with `surveySubmitted: false` but does not implement client-side resume UI. If product requires resume:
|
|
81
|
+
|
|
82
|
+
- Enable `savePartialResponse`
|
|
83
|
+
- Optionally show `partialSaveResults` status
|
|
84
|
+
- Confirm with backend team whether refetch restores `state.answers`
|
|
85
|
+
|
|
86
|
+
Path-change sanitization (skip/display) clears off-path answers before partial save — see [`skip-logic-and-navigation.md`](skip-logic-and-navigation.md).
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Verify checklist
|
|
91
|
+
|
|
92
|
+
- [ ] `savePartialResponse` only set when required
|
|
93
|
+
- [ ] No duplicate fetch/submit logic outside SDK
|
|
94
|
+
- [ ] `TEXT_AND_MEDIA` questions produce no CHANGE (correct — no answer to save)
|
|
95
|
+
- [ ] Hidden-path answers not manually managed in client state
|
|
96
|
+
- [ ] Optional UI wired to `partialSaveResults.isLoading` / `.error`
|
|
97
|
+
|
|
98
|
+
See [`useSurveySDK.md`](useSurveySDK.md) for hook signature and [`client-integration-guide.md`](client-integration-guide.md) for full wiring map.
|