forked from eyra/port
-
Notifications
You must be signed in to change notification settings - Fork 2
/
props.py
428 lines (343 loc) · 11.8 KB
/
props.py
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
from dataclasses import dataclass
from typing import TypedDict, Optional, Literal
import pandas as pd
class Translations(TypedDict):
"""Typed dict containing text that is display in a speficic language
Attributes:
en: English string to display
nl: Dutch string to display
"""
en: str
nl: str
@dataclass
class Translatable:
"""Wrapper class for Translations"""
translations: Translations
def toDict(self):
return self.__dict__.copy()
@dataclass
class PropsUIHeader:
"""Page header
Attributes:
title: title of the page
"""
title: Translatable
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIHeader"
dict["title"] = self.title.toDict()
return dict
@dataclass
class PropsUIFooter:
"""Page footer
Attributes:
progressPercentage: float indicating the progress in the flow
"""
progressPercentage: float
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIFooter"
dict["progressPercentage"] = self.progressPercentage
return dict
@dataclass
class PropsUIPromptConfirm:
"""Retry submitting a file page
Prompt the user if they want to submit a new file.
This can be used in case a file could not be processed.
Attributes:
text: message to display
ok: message to display if the user wants to try again
cancel: message to display if the user wants to continue regardless
"""
text: Translatable
ok: Translatable
cancel: Translatable
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPromptConfirm"
dict["text"] = self.text.toDict()
dict["ok"] = self.ok.toDict()
dict["cancel"] = self.cancel.toDict()
return dict
@dataclass
class PropsUIChartGroup:
"""Grouping variable for aggregating the data
Attributes:
column: name of the column to aggregate
label: Optionally, a label to display in the visualization (default is the column name)
dateFormat: if given, transforms a data column to the specified date format for aggregation
"""
column: str
label: Optional[str] = None
dateFormat: Optional[str] = None
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIChartGroup"
dict["column"] = self.column
dict["label"] = self.label
dict["dateFormat"] = self.dateFormat
return dict
@dataclass
class PropsUIChartValue:
"""Value to aggregate
Attributes:
column: name of the column to aggregate
label: Optionally, a label to display in the visualization (default is the column name)
aggregate: function for aggregating the values
addZeroes: if true, add zeroes for missing values
"""
column: str
label: Optional[str] = None
aggregate: Optional[str] = "count"
addZeroes: Optional[bool] = False
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIChartValue"
dict["column"] = self.column
dict["label"] = self.label
dict["aggregate"] = self.aggregate
dict["addZeroes"] = self.addZeroes
return dict
@dataclass
class PropsUIChartVisualization:
"""Data visualization
Attributes:
title: title of the visualization
type: type of visualization
group: grouping variable for aggregating the data
values: list of values to aggregate
"""
title: Translatable
type: Literal["bar", "line", "area"]
group: PropsUIChartGroup
values: list[PropsUIChartValue]
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIChartVisualization"
dict["title"] = self.title.toDict()
dict["type"] = self.type
dict["group"] = self.group.toDict()
dict["values"] = [value.toDict() for value in self.values]
return dict
@dataclass
class PropsUITextVisualization:
"""Word cloud visualization
"""
title: Translatable
type: Literal["wordcloud"]
text_column: str
value_column: Optional[str] = None
tokenize: Optional[bool] = False
extract: Optional[Literal["url_domain"]] = None
def toDict(self):
dict = {}
dict["__type__"] = "PropsUITextVisualization"
dict["title"] = self.title.toDict()
dict["type"] = self.type
dict["textColumn"] = self.text_column
dict["valueColumn"] = self.value_column
dict["tokenize"] = self.tokenize
dict["extract"] = self.extract
return dict
@dataclass
class PropsUIPromptConsentFormTable:
"""Table to be shown to the participant prior to donation
Attributes:
id: a unique string to itentify the table after donation
title: title of the table
data_frame: table to be shown
editable: determines whether the table has an editable mode that can be toggled with a button
visualizations: optional list of visualizations to be shown
"""
id: str
title: Translatable
data_frame: pd.DataFrame
description: Optional[Translatable] = None
visualizations: Optional[list[PropsUIChartVisualization | PropsUITextVisualization]] = None
def translate_visualizations(self):
if self.visualizations is None:
return None
return [vis.toDict() for vis in self.visualizations]
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPromptConsentFormTable"
dict["id"] = self.id
dict["title"] = self.title.toDict()
dict["data_frame"] = self.data_frame.to_json()
dict["description"] = self.description.toDict() if self.description is not None else None
dict["visualizations"] = self.translate_visualizations()
return dict
@dataclass
class PropsUIPromptConsentForm:
"""Tables and Visualization to be shown to the participant prior to donation
Attributes:
tables: a list of tables
meta_tables: a list of optional tables, for example for logging data
"""
tables: list[PropsUIPromptConsentFormTable]
meta_tables: list[PropsUIPromptConsentFormTable]
def translate_tables(self):
output = []
for table in self.tables:
output.append(table.toDict())
return output
def translate_meta_tables(self):
output = []
for table in self.meta_tables:
output.append(table.toDict())
return output
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPromptConsentForm"
dict["tables"] = self.translate_tables()
dict["metaTables"] = self.translate_meta_tables()
return dict
@dataclass
class PropsUIPromptFileInput:
"""Prompt the user to submit a file
Attributes:
description: text with an explanation
extensions: accepted mime types, example: "application/zip, text/plain"
"""
description: Translatable
extensions: str
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPromptFileInput"
dict["description"] = self.description.toDict()
dict["extensions"] = self.extensions
return dict
class RadioItem(TypedDict):
"""Radio button
Attributes:
id: id of radio button
value: text to be displayed
"""
id: int
value: str
@dataclass
class PropsUIPromptRadioInput:
"""Radio group
This radio group can be used get a mutiple choice answer from a user
Attributes:
title: title of the radio group
description: short description of the radio group
items: a list of radio buttons
"""
title: Translatable
description: Translatable
items: list[RadioItem]
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPromptRadioInput"
dict["title"] = self.title.toDict()
dict["description"] = self.description.toDict()
dict["items"] = self.items
return dict
@dataclass
class PropsUIQuestionOpen:
"""Question: Open Question
This class can be used to ask an open question to a user.
The user can type the answer in the a text field
Attributes:
id: Should be a unique ID to identify the question, example: "1"
question: The question that will be asked
"""
id: int
question: Translatable
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIQuestionOpen"
dict["id"] = self.id
dict["question"] = self.question.toDict()
return dict
@dataclass
class PropsUIQuestionMultipleChoiceCheckbox:
"""Question: Multiple choice checkbox
This class can be used to ask an multiple choice question to a user.
Multiple answers can be given
Attributes:
id: Should be a unique ID to identify the question, example: "1"
question: The question that will be asked
"""
id: int
question: Translatable
choices: list[Translatable]
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIQuestionMultipleChoiceCheckbox"
dict["id"] = self.id
dict["question"] = self.question.toDict()
dict["choices"] = [c.toDict() for c in self.choices]
return dict
@dataclass
class PropsUIQuestionMultipleChoice:
"""Question: Multiple choice
This class can be used to ask an multiple choice question to a user.
Only one answer can be given
Attributes:
id: Should be a unique ID to identify the question, example: "1"
question: The question that will be asked
"""
id: int
question: Translatable
choices: list[Translatable]
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIQuestionMultipleChoice"
dict["id"] = self.id
dict["question"] = self.question.toDict()
dict["choices"] = [c.toDict() for c in self.choices]
return dict
@dataclass
class PropsUIPromptQuestionnaire:
"""A class to collection questions
This class can be used to assemble question in a questionnaire.
This class can be used as a body in PropsUIPageDonation
* All questions are optional
* Results are returned to the script after the user clicks the continue button
The results will be in your_results.value, example:
{"1": "answer 1", "2": ["answer 1", "answer 2"], "3": "open answer"}
Attributes:
description: Short descrition
questions: List of questions that need to be asked
"""
description: Translatable
questions: list[PropsUIQuestionMultipleChoice | PropsUIQuestionMultipleChoiceCheckbox | PropsUIQuestionOpen]
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPromptQuestionnaire"
dict["description"] = self.description.toDict()
dict["questions"] = [q.toDict() for q in self.questions]
return dict
@dataclass
class PropsUIPageDonation:
"""A multi-purpose page that gets shown to the user
Attributes:
platform: the platform name the user is curently in the process of donating data from
header: page header
body: main body of the page, see the individual classes for an explanation
footer: page footer
"""
platform: str
header: PropsUIHeader
body: PropsUIPromptRadioInput | PropsUIPromptConsentForm | PropsUIPromptFileInput | PropsUIPromptConfirm | PropsUIPromptQuestionnaire
footer: Optional[PropsUIFooter]
def translate_footer(self):
if self.footer is None:
return None
return self.footer.toDict()
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPageDonation"
dict["platform"] = self.platform
dict["header"] = self.header.toDict()
dict["body"] = self.body.toDict()
dict["footer"] = self.translate_footer()
return dict
class PropsUIPageEnd:
"""An ending page to show the user they are done"""
def toDict(self):
dict = {}
dict["__type__"] = "PropsUIPageEnd"
return dict