-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheventData.ts
More file actions
216 lines (195 loc) · 7.48 KB
/
eventData.ts
File metadata and controls
216 lines (195 loc) · 7.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import type { ItemDef } from './itemDefToSchema';
/**
* Get the possible event types for an item def.
* Falls back to eventTemplate().type if data.eventType is not set.
* For items with variations, includes all variation event types.
*/
function getEventTypes (itemDef: ItemDef): string[] {
if (itemDef.data.eventType) return [itemDef.data.eventType];
const variations = (itemDef.data as any).variations?.eventType;
if (variations?.options) {
return variations.options.map((o: any) => o.value).filter(Boolean);
}
const template = itemDef.eventTemplate();
if (template.type) return [template.type as string];
return [];
}
/**
* Prefill form values from existing Pryv events.
* Maps events back to form field values using itemDef keys.
*/
export function prefillFromEvents (
itemDefs: Array<{ key: string; itemDef: ItemDef }>,
events: Array<{ type: string; streamIds: string[]; content: any; time?: number }>
): Record<string, any> {
const result = matchEventsToItemDefs(itemDefs, events);
return result.values;
}
/**
* Match events to item defs and return both values and event references.
* Used for prefilling and for knowing which events to update/delete on submit.
*
* Uses `itemDef.matchesEvent(event)` when available (Plan 46 D3 — D3-aware
* walk-up resolution); falls back to plain `streamIds.includes(streamId)`
* when the lib-side helper isn't present (older models).
*/
export function matchEventsToItemDefs (
itemDefs: Array<{ key: string; itemDef: ItemDef }>,
events: Array<{ id?: string; type: string; streamIds: string[]; content: any; time?: number }>
): { values: Record<string, any>; eventIds: Record<string, string>; eventTypes: Record<string, string> } {
const values: Record<string, any> = {};
const eventIds: Record<string, string> = {};
const eventTypes: Record<string, string> = {};
for (const { key, itemDef } of itemDefs) {
const possibleTypes = getEventTypes(itemDef);
const streamId = itemDef.data.streamId;
if (possibleTypes.length === 0 || !streamId) continue;
// Find the most recent matching event (any of the variation types).
// Prefer D3-aware matchesEvent when the itemDef supports it.
const matchFn = typeof itemDef.matchesEvent === 'function'
? (e: { type: string; streamIds: string[] }) => itemDef.matchesEvent!(e)
: (e: { type: string; streamIds: string[] }) => possibleTypes.includes(e.type) && e.streamIds?.includes(streamId);
const matching = events
.filter(matchFn)
.sort((a, b) => (b.time || 0) - (a.time || 0));
if (matching.length > 0) {
const event = matching[0];
// activity/plain has null content — represent as checked checkbox
if (event.type === 'activity/plain') {
values[key] = true;
} else {
values[key] = event.content;
}
if (event.id) {
eventIds[key] = event.id;
}
// Track matched event type (useful for prefilling variation selectors)
eventTypes[key] = event.type;
}
}
return { values, eventIds, eventTypes };
}
/**
* Compute the list of API actions (create/update/delete) for a form submission.
* Compares current form data against existing event IDs to determine the right action.
*
* @param contexts Plan 46 D3 — optional per-itemKey context streamId to pass
* to `eventTemplate({ context })`. Lets a section emit events placed at
* descendant streams of the itemDef's canonical home (e.g. `treatment`
* itemDef → events at `treatment-fertility`).
*/
export function formDataToActions (
itemDefs: Array<{ key: string; itemDef: ItemDef }>,
formData: Record<string, any>,
existingEventIds: Record<string, string>,
time?: number,
contexts?: Record<string, string | undefined>
): Array<{ action: 'create' | 'update' | 'delete'; key: string; params: Record<string, any> }> {
const actions: Array<{ action: 'create' | 'update' | 'delete'; key: string; params: Record<string, any> }> = [];
const eventTime = time || Math.floor(Date.now() / 1000);
for (const { key, itemDef } of itemDefs) {
const value = formData[key];
const existingId = existingEventIds[key];
const ctx = contexts?.[key];
const template = itemDef.eventTemplate(ctx ? { context: ctx } : undefined);
// Use variation override from formData if present, otherwise default template type
const eventType = formData[`${key}__eventType`] || template.type as string;
// activity/plain: checkbox true = create/keep, false/undefined = delete if exists
if (eventType === 'activity/plain') {
if (value === true) {
if (existingId) {
// Already exists — no action needed
continue;
}
actions.push({
action: 'create',
key,
params: {
streamIds: template.streamIds,
type: eventType,
content: null,
time: eventTime
}
});
} else {
if (existingId) {
actions.push({ action: 'delete', key, params: { id: existingId } });
}
}
continue;
}
// Value cleared or empty — delete existing if any
if (value === undefined || value === null || value === '') {
if (existingId) {
actions.push({ action: 'delete', key, params: { id: existingId } });
}
continue;
}
// Has value
if (existingId) {
// Update existing event (include type if variation override is set)
const update: Record<string, any> = { content: value };
if (formData[`${key}__eventType`]) update.type = eventType;
// Pass `time` through only when caller provided an explicit timestamp
// (date picker). Otherwise leave the original event time untouched.
if (time != null) update.time = time;
actions.push({
action: 'update',
key,
params: { id: existingId, update }
});
} else {
// Create new event
actions.push({
action: 'create',
key,
params: {
streamIds: template.streamIds as string[],
type: eventType,
content: value,
time: eventTime
}
});
}
}
return actions;
}
/**
* Convert form submission data to a batch of Pryv event API calls.
* Returns an array of event objects ready for Pryv batch creation.
* @deprecated Use formDataToActions for create/update/delete support
*/
export function formDataToEventBatch (
itemDefs: Array<{ key: string; itemDef: ItemDef }>,
formData: Record<string, any>,
time?: number,
contexts?: Record<string, string | undefined>
): Array<{ streamIds: string[]; type: string; content: any; time?: number }> {
const events: Array<{ streamIds: string[]; type: string; content: any; time?: number }> = [];
const eventTime = time || Math.floor(Date.now() / 1000);
for (const { key, itemDef } of itemDefs) {
const value = formData[key];
if (value === undefined || value === null) continue;
const ctx = contexts?.[key];
const template = itemDef.eventTemplate(ctx ? { context: ctx } : undefined);
const eventType = template.type as string;
// activity/plain expects null content; checkbox true = create event, false = skip
if (eventType === 'activity/plain') {
if (value === false) continue;
events.push({
streamIds: template.streamIds as string[],
type: eventType,
content: null,
time: eventTime
});
continue;
}
events.push({
streamIds: template.streamIds as string[],
type: eventType,
content: value,
time: eventTime
});
}
return events;
}