Skip to content

Commit ead0f44

Browse files
authored
feat!: add route-based data recording with recordPageData (closes #244)
Introduces a new data recording API that organizes experiment data by route/page name and visit number, making it easier to track which data came from which part of an experiment and which visit. New features: - recordPageData(data, routeName?) - records to pageData_<routeName> with visit_N sub-objects for multi-visit tracking - computeCompletionCode() - moved from ThanksView to useAPI for reuse - getAllPageData getter in smilestore Data structure per route: pageData_<routeName>: { visit_0: { timestamps: [ts1, ts2], // parallel arrays data: [data1, data2] }, visit_1: { timestamps: [ts3], data: [data3] } } Each visit to a route creates a new visit_N object, keeping data from different visits cleanly separated. Deprecations (still functional with warnings): - recordForm() - use recordPageData() instead - recordData() - use recordPageData() instead BREAKING CHANGE: Completion code hash now uses pageData_* fields (falls back to studyData for backward compatibility) - Includes docs and tests * build: add prettier npm run command
1 parent 09d8d63 commit ead0f44

18 files changed

Lines changed: 1162 additions & 71 deletions

File tree

docs/api.md

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -293,24 +293,65 @@ using these methods, ensure your data objects are Firestore-safe:
293293
Complex JavaScript objects (like functions, classes, or objects with circular
294294
references) must be converted to Firestore-safe formats before saving.
295295

296-
- `recordForm(name, myFirestoreSafeObject)`: Saves form data for any arbitrarily
297-
named form (see [DemographicSurvey](/coding/views#demographic-survey) for an
298-
example). The object must be Firestore-safe.
296+
- `recordPageData(data, routeName?)`: **Recommended** - Records data organized
297+
by route/page name and visit number. Data is stored in `pageData_<routeName>`
298+
with separate `visit_N` objects for each time the route is visited (e.g.,
299+
`visit_0`, `visit_1`, etc.). Each visit contains `timestamps` and `data`
300+
arrays that grow with each call during that visit. If routeName is omitted,
301+
uses the current route name. Returns `true` on success, `false` on validation
302+
failure. The data must be Firestore-safe and will be validated before
303+
recording:
304+
- Must be an object (not an array) - arrays would create nested arrays which
305+
Firestore doesn't support
306+
- Cannot contain nested arrays (e.g., `[[1,2], [3,4]]`)
307+
- Cannot contain functions or symbols
308+
- Keys cannot contain `.`, `/`, `[`, `]`, or `*`
309+
310+
Example stored structure:
311+
312+
```javascript
313+
pageData_trial: {
314+
visit_0: { // First visit to this route
315+
timestamps: [
316+
1702300000000, // [0] first recordPageData() call
317+
1702300001000 // [1] second recordPageData() call
318+
],
319+
data: [
320+
{ response: 'A', rt: 450 }, // [0] first recordPageData() call
321+
{ response: 'B', rt: 520 } // [1] second recordPageData() call
322+
]
323+
},
324+
visit_1: { // Second visit to this route (e.g., participant returned)
325+
timestamps: [1702300100000],
326+
data: [
327+
{ response: 'C', rt: 380 }
328+
]
329+
}
330+
}
331+
```
332+
333+
Within each visit, `timestamps[i]` corresponds to `data[i]`. Each call to
334+
`recordPageData()` appends one timestamp and one data object to the current
335+
visit.
336+
299337
- `recordProperty(name, FirestoreSafeObject)`: Saves a Firestore-safe object at
300338
the top level of the data object. This does not save the data to the database,
301339
but it does record it in the local state. The next call to `saveData()` will
302340
save the data to the database.
303-
- `recordStep()`: Records the current step in the stepper. This does not save
304-
the data to the database, but it does record it in the local state. The next
305-
call to `saveData()` will save the data to the database.
306-
- `recordData(myFirestoreSafeObject)`: Records a Firestore-safe object in the
307-
trials. This does not save the data to the database, but it does record it in
308-
the local state. The next call to `saveData()` will save the data to the
309-
database.
341+
- `recordStep()`: Records the current step data using `recordPageData()`. This
342+
does not save the data to the database, but it does record it in the local
343+
state. The next call to `saveData()` will save the data to the database.
310344
- `recordWindowEvent(type, event_data = null)`: Records a window event. The
311345
event_data must be Firestore-safe if provided.
312346
- `saveData(force)`: Saves the current data to the database (force save if
313347
specified). All data must be Firestore-safe.
348+
- `computeCompletionCode()`: Generates a unique completion code by hashing all
349+
`pageData_*` fields (falls back to `studyData` for backward compatibility).
350+
351+
**Deprecated methods** (still functional but will log warnings):
352+
353+
- `recordForm(name, data)`: Use `recordPageData(data)` instead.
354+
- `recordData(data)`: Use `recordPageData(data)` instead.
314355

315356
### Data Query Methods
316357

docs/coding/autofill.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ function autofill() {
133133

134134
var t = api.faker.render(trials[step.index()])
135135
api.debug(t)
136-
api.recordData(t)
136+
api.recordPageData(t)
137137

138138
step.next()
139139
}
@@ -149,7 +149,7 @@ function is steps through each trial, rendering the data for that trial, saving
149149
it to the smilestore database, then advancing to the next step. The rendering
150150
step calls the `api.faker.*()` methods as defined and makes "fake" data for the
151151
trial. This data is then [recorded to the database](/coding/datastorage) using
152-
`api.recordData()`.
152+
`api.recordPageData()`.
153153

154154
## The Autofill API
155155

docs/coding/datastorage.md

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -204,22 +204,108 @@ Firebase/Firestore or the LocalStorage in the browser.
204204

205205
## Writing Data to the Global Store
206206

207-
The preferred way to write data to the store is to use the [Smile API](/api):
207+
The preferred way to write data to the store is to use the [Smile API](/api).
208+
209+
### Recording Page-Based Data (Recommended)
210+
211+
The `recordPageData()` method organizes data by route/page name, making it easy
212+
to track which data came from which part of your experiment:
208213

209214
```vue
210215
<script setup>
211-
import SmileAPI from '@/core/composables/useAPI'
212-
const api = SmileAPI()
216+
import useAPI from '@/core/composables/useAPI'
217+
const api = useAPI()
218+
219+
// Records to pageData_<current_route_name>
220+
api.recordPageData({
221+
trialNum: 1,
222+
response: 'correct',
223+
rt: 543,
224+
})
225+
226+
// Or specify explicit page name
227+
api.recordPageData({ data: 'value' }, 'custom_page')
228+
</script>
229+
```
230+
231+
Data is automatically organized by visit, so if a participant visits the same
232+
page multiple times, each visit's data is tracked separately:
233+
234+
```javascript
235+
// Example data structure in Firestore
236+
{
237+
pageData_consent: [
238+
{
239+
visitIndex: 0,
240+
timestamps: [1701619200000, 1701619205000], // timestamps[i] corresponds to data[i]
241+
data: [{ agreed: true }, { additionalInfo: "..." }]
242+
}
243+
],
244+
pageData_experiment: [
245+
{
246+
visitIndex: 0,
247+
timestamps: [1701619210000, 1701619215000, 1701619220000],
248+
data: [{ trial: 1, rt: 500 }, { trial: 2, rt: 450 }, { trial: 3, rt: 480 }]
249+
}
250+
]
251+
}
252+
```
253+
254+
### Deprecated Methods
255+
256+
:::warning Deprecated The following methods are deprecated and will be removed
257+
in a future version. They still work but will log deprecation warnings. Please
258+
migrate to `recordPageData()`. :::
213259

260+
**`recordData(data)`** - Previously used to append data to a generic `studyData`
261+
array:
262+
263+
```vue
264+
<script setup>
265+
// DEPRECATED - use recordPageData() instead
214266
api.recordData({
215-
trialnum: step_index.value,
216-
word: step.value.word,
217-
color: step.value.color,
218-
condition: step.value.condition,
219-
response: e.key,
220-
})
267+
trialnum: step_index.value,
268+
word: step.value.word,
269+
response: e.key,
270+
})
271+
</script>
221272
```
222273

274+
**`recordForm(name, data)`** - Previously used to record form submissions:
275+
276+
```vue
277+
<script setup>
278+
// DEPRECATED - use recordPageData() instead
279+
api.recordForm('demographicForm', formData)
280+
</script>
281+
```
282+
283+
### Completion Codes
284+
285+
The `computeCompletionCode()` method generates a unique completion code based on
286+
the recorded data. This is useful for verifying participant completion across
287+
recruitment platforms:
288+
289+
```vue
290+
<script setup>
291+
import useAPI from '@/core/composables/useAPI'
292+
const api = useAPI()
293+
294+
// Generate and set completion code
295+
const completionCode = api.computeCompletionCode()
296+
api.setCompletionCode(completionCode)
297+
</script>
298+
```
299+
300+
The completion code is a hash of all `pageData_*` fields (or `studyData` for
301+
backward compatibility) with a status suffix:
302+
303+
- `oo` - participant completed the study
304+
- `xx` - participant withdrew
305+
306+
This method is automatically called in the default `ThanksView.vue` component,
307+
but you can call it manually if needed elsewhere.
308+
223309
The API provides several useful methods for saving data to the store (see the
224310
[full docs](/api)).
225311

docs/styling/forms.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,17 +105,17 @@ experimental phase:
105105

106106
```javascript
107107
function finish() {
108-
api.recordForm('feedbackForm', api.persist.forminfo)
108+
api.recordPageData(api.persist.forminfo)
109109
api.saveData(true) // force a data save
110110
api.goNextView()
111111
}
112112
```
113113

114-
The recordForm method tags the form data with a specific identifier, making it
115-
easy to locate this data during analysis. The forced data save ensures that the
116-
form responses are immediately persisted to the backend, preventing any data
117-
loss. Only after the data is safely recorded does the function navigate to the
118-
next view in the experimental sequence.
114+
The `recordPageData` method automatically organizes the data by route name
115+
(e.g., `pageData_feedback`), making it easy to locate this data during analysis.
116+
The forced data save ensures that the form responses are immediately persisted
117+
to the backend, preventing any data loss. Only after the data is safely recorded
118+
does the function navigate to the next view in the experimental sequence.
119119

120120
### Development and Testing Support
121121

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@
4242
"setup_project": "sh scripts/setup_project.sh",
4343
"force_deploy": "sh scripts/force_deploy.sh",
4444
"upload_config": "sh scripts/update_config.sh",
45-
"release": "bumpp package.json"
45+
"release": "bumpp package.json",
46+
"prettier": "npx prettier --write ."
4647
},
4748
"dependencies": {
4849
"@codenamize/codenamize": "1.1.1",

0 commit comments

Comments
 (0)