Skip to content

Commit 4b40401

Browse files
Generator: Add customChoices generation in the widgets.
Fix: #1056 Improve widget import logic with smaller function Enhance tests for import statements and choices generation, put them in a class. Make sure that lower and capitalize case works for the custom imports. Create an ImportFlags class for the generate_widgets. Rebase with #1097
1 parent 448a270 commit 4b40401

File tree

2 files changed

+291
-109
lines changed

2 files changed

+291
-109
lines changed

packages/evolution-generator/src/scripts/generate_widgets.py

Lines changed: 164 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,36 @@
1111
)
1212
import re # Regular expression module for pattern matching
1313
from typing import TypedDict
14+
from dataclasses import dataclass
15+
16+
17+
@dataclass
18+
class ImportFlags:
19+
"""
20+
Dataclass to hold the import flags for widget generation.
21+
This will make the code more maintainable and less error-prone when adding new flags.
22+
"""
23+
24+
# Flags for imports
25+
has_choices_import: bool = False
26+
has_custom_choices_import: bool = False
27+
has_conditionals_import: bool = False
28+
has_input_range_import: bool = False
29+
has_custom_widgets_import: bool = False
30+
has_validations_import: bool = (
31+
True # Default to True for validations.requiredValidation
32+
)
33+
has_custom_validations_import: bool = False
34+
has_custom_conditionals_import: bool = False
35+
has_help_popup_import: bool = False
36+
has_helper_import: bool = False
37+
has_formatter_import: bool = False
38+
has_custom_formatter_import: bool = False
39+
40+
# Flags for specific labels
41+
has_nickname_label: bool = False
42+
has_persons_count_label: bool = False
43+
has_gendered_suffix_label: bool = False
1444

1545

1646
class RadioNumberParametersResult(TypedDict):
@@ -49,8 +79,8 @@ class WidgetResult(TypedDict):
4979

5080
statement: str
5181
has_helper_import: bool
52-
has_formatter_import: bool | None
53-
has_custom_formatter_import: bool | None
82+
has_formatter_import: bool
83+
has_custom_formatter_import: bool
5484

5585

5686
# Function to generate widgets.tsx for each section
@@ -84,83 +114,27 @@ def convert_excel_to_typescript(section):
84114
# Filter rows based on section
85115
section_rows = [row for row in section_rows if row["section"] == section]
86116

87-
# Loop the section rows to check if we need to import choices, custom conditionals, input_range, or custom widgets
88-
has_choices_import = False
89-
has_conditionals_import = False
90-
has_input_range_import = False
91-
has_custom_widgets_import = False
92-
has_custom_validations_import = False
93-
has_custom_conditionals_import = False
94-
has_help_popup_import = False
95-
has_nickname_label = False
96-
has_persons_count_label = False
97-
has_gendered_suffix_label = False
98-
for row in section_rows:
99-
if row["choices"]:
100-
has_choices_import = True
101-
if row["validation"].endswith("CustomValidation"):
102-
# Check to see if the validation finish with 'CustomValidation'
103-
has_custom_validations_import = True
104-
if row["conditional"] and row["conditional"].endswith(
105-
"CustomConditional"
106-
):
107-
# Check to see if the conditional finish with 'CustomConditional'
108-
has_custom_conditionals_import = True
109-
elif row["conditional"] and not row["conditional"].endswith(
110-
"CustomConditional"
111-
):
112-
# Check to see if the conditional is not empty and does not finish with 'CustomConditional'
113-
has_conditionals_import = True
114-
if row["inputRange"]:
115-
has_input_range_import = True
116-
if row["inputType"] == "Custom":
117-
has_custom_widgets_import = True
118-
if row["help_popup"] or row["confirm_popup"]:
119-
has_help_popup_import = True
120-
121-
# Check all rows for label context
122-
for row in section_rows:
123-
label_fr = row.get("label::fr", "")
124-
label_en = row.get("label::en", "")
125-
if "{{nickname}}" in label_fr or "{{nickname}}" in label_en:
126-
# Check if the label contains '{{nickname}}'
127-
has_nickname_label = True
128-
if "{{count}}" in label_fr or "{{count}}" in label_en:
129-
# Check if the label contains '{{count}}'
130-
has_persons_count_label = True
131-
if "{{genderedSuffix" in label_fr or "{{genderedSuffix:" in label_en:
132-
# Check if the label contains '{{genderedSuffix:...}}'
133-
has_gendered_suffix_label = True
117+
# Get the widgets file import flags
118+
import_flags = get_widgets_file_import_flags(section_rows)
134119

135120
# Generate widgets statements
136121
widget_results = [generate_widget_statement(row) for row in section_rows]
137-
has_helper_import = any(
122+
123+
# Check if any widget has specific import flags and update import_flags accordingly
124+
import_flags.has_helper_import = any(
138125
result["has_helper_import"] for result in widget_results
139126
)
140-
has_formatter_import = any(
127+
import_flags.has_formatter_import = any(
141128
result.get("has_formatter_import") for result in widget_results
142129
)
143-
has_custom_formatter_import = any(
130+
import_flags.has_custom_formatter_import = any(
144131
result.get("has_custom_formatter_import") for result in widget_results
145132
)
146133

147134
# Generate import statements
148-
import_statements = generate_import_statements(
149-
has_choices_import,
150-
has_conditionals_import,
151-
has_input_range_import,
152-
has_custom_widgets_import,
153-
has_custom_validations_import,
154-
has_custom_conditionals_import,
155-
has_help_popup_import,
156-
has_nickname_label,
157-
has_persons_count_label,
158-
has_gendered_suffix_label,
159-
has_helper_import,
160-
has_formatter_import,
161-
has_custom_formatter_import,
162-
)
135+
import_statements = generate_import_statements(import_flags=import_flags)
163136

137+
# Generate the widgets statements
164138
widgets_statements = [result["statement"] for result in widget_results]
165139
widgets_statements = (
166140
f"{import_statements}\n{'\n\n'.join(widgets_statements)}\n"
@@ -376,72 +350,87 @@ def generate_widgets_names_statements(section_rows):
376350

377351

378352
# Generate import statement if needed
379-
def generate_import_statements(
380-
has_choices_import,
381-
has_conditionals_import,
382-
has_input_range_import,
383-
has_custom_widgets_import,
384-
has_custom_validations_import,
385-
has_custom_conditionals_import,
386-
has_help_popup_import,
387-
has_nickname_label,
388-
has_persons_count_label,
389-
has_gendered_suffix_label,
390-
has_helper_import=False,
391-
has_formatter_import=False,
392-
has_custom_formatter_import=False,
393-
):
394-
od_survey_helpers_import = ""
395-
if has_nickname_label or has_gendered_suffix_label or has_persons_count_label:
396-
od_survey_helpers_import = "import * as odSurveyHelpers from 'evolution-common/lib/services/odSurvey/helpers';\n"
397-
353+
def generate_import_statements(import_flags: ImportFlags) -> str:
354+
od_survey_helpers_import = (
355+
"import * as odSurveyHelpers from 'evolution-common/lib/services/odSurvey/helpers';\n"
356+
if (
357+
import_flags.has_nickname_label
358+
or import_flags.has_gendered_suffix_label
359+
or import_flags.has_persons_count_label
360+
)
361+
else ""
362+
)
398363
choices_import = (
399-
"// " if not has_choices_import else ""
400-
) + "import * as choices from '../../common/choices';\n"
364+
"import * as choices from '../../common/choices';\n"
365+
if import_flags.has_choices_import
366+
else ""
367+
)
368+
custom_choices_import = (
369+
"import * as customChoices from './customChoices';\n"
370+
if import_flags.has_custom_choices_import
371+
else ""
372+
)
401373
conditionals_import = (
402-
"// " if not has_conditionals_import else ""
403-
) + "import * as conditionals from '../../common/conditionals';\n"
374+
"import * as conditionals from '../../common/conditionals';\n"
375+
if import_flags.has_conditionals_import
376+
else ""
377+
)
404378
custom_widgets_import = (
405-
"// " if not has_custom_widgets_import else ""
406-
) + "import * as customWidgets from './customWidgets';\n"
379+
"import * as customWidgets from './customWidgets';\n"
380+
if import_flags.has_custom_widgets_import
381+
else ""
382+
)
383+
validations_import = (
384+
"import * as validations from 'evolution-common/lib/services/widgets/validations/validations';\n"
385+
if import_flags.has_validations_import
386+
else ""
387+
)
407388
custom_validations_import = (
408-
"// " if not has_custom_validations_import else ""
409-
) + "import * as customValidations from '../../common/customValidations';\n"
389+
"import * as customValidations from '../../common/customValidations';\n"
390+
if import_flags.has_custom_validations_import
391+
else ""
392+
)
410393
custom_conditionals_import = (
411-
"// " if not has_custom_conditionals_import else ""
412-
) + "import * as customConditionals from '../../common/customConditionals';\n"
394+
"import * as customConditionals from '../../common/customConditionals';\n"
395+
if import_flags.has_custom_conditionals_import
396+
else ""
397+
)
413398
custom_help_popup_import = (
414-
"// " if not has_help_popup_import else ""
415-
) + "import * as customHelpPopup from '../../common/customHelpPopup';\n"
399+
"import * as customHelpPopup from '../../common/customHelpPopup';\n"
400+
if import_flags.has_help_popup_import
401+
else ""
402+
)
416403
input_range_import = (
417-
"// " if not has_input_range_import else ""
418-
) + "import * as inputRange from '../../common/inputRange';\n"
404+
"import * as inputRange from '../../common/inputRange';\n"
405+
if import_flags.has_input_range_import
406+
else ""
407+
)
419408
gendered_suffix_import = (
420409
"import { getGenderedSuffixes } from '../../helperFunctions/frontendHelper';\n"
421-
if has_gendered_suffix_label == True
410+
if import_flags.has_gendered_suffix_label
422411
else ""
423412
)
424413
survey_helper_import = (
425414
"import * as surveyHelper from 'evolution-common/lib/utils/helpers';\n"
426-
if has_helper_import == True
415+
if import_flags.has_helper_import
427416
else ""
428417
)
429418
formatter_import = (
430419
"import * as formatters from 'evolution-common/lib/utils/formatters';\n"
431-
if has_formatter_import == True
420+
if import_flags.has_formatter_import
432421
else ""
433422
)
434423
custom_formatter_import = (
435424
"import * as customFormatters from '../../common/customFormatters';\n"
436-
if has_custom_formatter_import == True
425+
if import_flags.has_custom_formatter_import
437426
else ""
438427
)
439428
return (
440429
f"import {{ TFunction }} from 'i18next';\n"
441430
f"import * as defaultInputBase from 'evolution-frontend/lib/components/inputs/defaultInputBase';\n"
442431
f"import {{ defaultConditional }} from 'evolution-common/lib/services/widgets/conditionals/defaultConditional';\n"
443432
f"import * as WidgetConfig from 'evolution-common/lib/services/questionnaire/types';\n"
444-
f"import * as validations from 'evolution-common/lib/services/widgets/validations/validations';\n"
433+
f"{validations_import}"
445434
f"{od_survey_helpers_import}"
446435
f"{survey_helper_import}"
447436
f"{choices_import}"
@@ -451,6 +440,7 @@ def generate_import_statements(
451440
f"{gendered_suffix_import}"
452441
f"{custom_conditionals_import}"
453442
f"{custom_widgets_import}"
443+
f"{custom_choices_import}"
454444
f"{custom_help_popup_import}"
455445
f"{custom_validations_import}"
456446
f"{custom_formatter_import}"
@@ -581,6 +571,20 @@ def generate_confirm_popup(confirm_popup, comma=True, skip_line=True):
581571

582572

583573
def generate_choices(choices):
574+
"""
575+
Generates the TypeScript 'choices' property for a widget.
576+
577+
If the choices string ends with 'CustomChoices' (case-insensitive), returns 'choices: customChoices.{choices}'.
578+
Otherwise, returns 'choices: choices.{choices}'.
579+
580+
Args:
581+
choices (str): The choices identifier from the Excel row.
582+
583+
Returns:
584+
str: The TypeScript code for the choices property.
585+
"""
586+
if choices.lower().endswith("customchoices"):
587+
return f"{INDENT}choices: customChoices.{choices}"
584588
return f"{INDENT}choices: choices.{choices}"
585589

586590

@@ -1014,8 +1018,8 @@ def get_string_parameters(row) -> StringParametersResult:
10141018
Returns a dict with keys: formatter.
10151019
Prints warnings for invalid or unrecognized parameters.
10161020
Example valid formats:
1017-
- "formatter=eightDigitsAccessCodeFormatter"
1018-
- "formatter=someFieldCustomFormatter"
1021+
- "formatter=eightDigitsAccessCodeFormatter"
1022+
- "formatter=someFieldCustomFormatter"
10191023
"""
10201024
parameters = row.get("parameters", "")
10211025

@@ -1033,3 +1037,56 @@ def get_string_parameters(row) -> StringParametersResult:
10331037
)
10341038

10351039
return result
1040+
1041+
1042+
def get_widgets_file_import_flags(section_rows) -> ImportFlags:
1043+
"""
1044+
Analyze the section_rows and determine which import flags should be set to True.
1045+
1046+
Returns an ImportFlags dataclass with all the import flag booleans.
1047+
"""
1048+
import_flags = ImportFlags()
1049+
1050+
# Check all rows for import flags
1051+
for row in section_rows:
1052+
if row["choices"]:
1053+
# Check to see if the choices finish with 'CustomChoices'
1054+
if row["choices"].lower().endswith("customchoices"):
1055+
import_flags.has_custom_choices_import = True
1056+
else:
1057+
import_flags.has_choices_import = True
1058+
if row["validation"]:
1059+
# Check to see if the validation finish with 'CustomValidation'
1060+
if row["validation"].lower().endswith("customvalidation"):
1061+
import_flags.has_custom_validations_import = True
1062+
else:
1063+
import_flags.has_validations_import = True
1064+
if row["conditional"] and row["conditional"].lower().endswith(
1065+
"customconditional"
1066+
):
1067+
# Check to see if the conditional finish with 'CustomConditional'
1068+
import_flags.has_custom_conditionals_import = True
1069+
elif row["conditional"] and not row["conditional"].lower().endswith(
1070+
"customconditional"
1071+
):
1072+
# Check to see if the conditional is not empty and does not finish with 'CustomConditional'
1073+
import_flags.has_conditionals_import = True
1074+
if row["inputRange"]:
1075+
import_flags.has_input_range_import = True
1076+
if row["inputType"] == "Custom":
1077+
import_flags.has_custom_widgets_import = True
1078+
if row["help_popup"] or row["confirm_popup"]:
1079+
import_flags.has_help_popup_import = True
1080+
1081+
# Check all rows for label context
1082+
label_fr = row.get("label::fr", "")
1083+
label_en = row.get("label::en", "")
1084+
# Check for {{nickname}}, {{count}}, and {{genderedSuffix:...}} in labels
1085+
if "{{nickname}}" in label_fr or "{{nickname}}" in label_en:
1086+
import_flags.has_nickname_label = True
1087+
if "{{count}}" in label_fr or "{{count}}" in label_en:
1088+
import_flags.has_persons_count_label = True
1089+
if "{{genderedSuffix" in label_fr or "{{genderedSuffix" in label_en:
1090+
import_flags.has_gendered_suffix_label = True
1091+
1092+
return import_flags

0 commit comments

Comments
 (0)