-
Notifications
You must be signed in to change notification settings - Fork 34
Add collapsible sections to tool parameters #306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -542,6 +542,7 @@ def input_TOPP( | |||||||||||||||||||||||||||||||||||
| display_subsections: bool = True, | ||||||||||||||||||||||||||||||||||||
| display_subsection_tabs: bool = False, | ||||||||||||||||||||||||||||||||||||
| custom_defaults: dict = {}, | ||||||||||||||||||||||||||||||||||||
| conditional_sections: dict = {}, | ||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| Generates input widgets for TOPP tool parameters dynamically based on the tool's | ||||||||||||||||||||||||||||||||||||
|
|
@@ -557,6 +558,12 @@ def input_TOPP( | |||||||||||||||||||||||||||||||||||
| display_subsections (bool, optional): Whether to split parameters into subsections based on the prefix. Defaults to True. | ||||||||||||||||||||||||||||||||||||
| display_subsection_tabs (bool, optional): Whether to display main subsections in separate tabs (if more than one main section). Defaults to False. | ||||||||||||||||||||||||||||||||||||
| custom_defaults (dict, optional): Dictionary of custom defaults to use. Defaults to an empty dict. | ||||||||||||||||||||||||||||||||||||
| conditional_sections (dict, optional): Dictionary controlling section visibility based on parameter values. | ||||||||||||||||||||||||||||||||||||
| Keys are section names (or prefixes that match subsections). Values are dicts with: | ||||||||||||||||||||||||||||||||||||
| - "param": The controlling parameter name (must NOT be inside the controlled section). | ||||||||||||||||||||||||||||||||||||
| - "value": Value or list of values that make the section visible. | ||||||||||||||||||||||||||||||||||||
| Example: {"algorithm:mtd": {"param": "algorithm:common:enable_mtd", "value": "true"}} | ||||||||||||||||||||||||||||||||||||
| Sections not in this dict are always displayed normally. | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if not display_subsections: | ||||||||||||||||||||||||||||||||||||
|
|
@@ -691,6 +698,84 @@ def show_subsection_header(section: str, display_subsections: bool): | |||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def get_param_value(param_name: str): | ||||||||||||||||||||||||||||||||||||
| """Get the current value of a parameter by its short name (e.g., 'algorithm:common:param').""" | ||||||||||||||||||||||||||||||||||||
| for p in params: | ||||||||||||||||||||||||||||||||||||
| short_name = p["key"].decode().split(":1:")[1] | ||||||||||||||||||||||||||||||||||||
| if short_name == param_name: | ||||||||||||||||||||||||||||||||||||
| return p["value"] | ||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| def get_controlling_section(section: str) -> dict | None: | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| Check if a section (or its parent) is controlled by conditional_sections. | ||||||||||||||||||||||||||||||||||||
| Returns the control config dict if found, None otherwise. | ||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||
| if not conditional_sections: | ||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||
| # Check exact match first | ||||||||||||||||||||||||||||||||||||
| if section in conditional_sections: | ||||||||||||||||||||||||||||||||||||
| return conditional_sections[section] | ||||||||||||||||||||||||||||||||||||
| # Check prefix matches (e.g., "algorithm" controls "algorithm:common") | ||||||||||||||||||||||||||||||||||||
| for controlled_section in conditional_sections: | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+719
to
+720
|
||||||||||||||||||||||||||||||||||||
| # Check prefix matches (e.g., "algorithm" controls "algorithm:common") | |
| for controlled_section in conditional_sections: | |
| # Check prefix matches (e.g., "algorithm" controls "algorithm:common"). | |
| # When multiple keys share a prefix (e.g., "algorithm" and "algorithm:common"), | |
| # prefer the most specific (longest) match by sorting keys by length descending. | |
| for controlled_section in sorted(conditional_sections, key=len, reverse=True): |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When current_value is None (parameter not found), the function still converts it to string "none" and checks against expected values. This could lead to unintended matches if "none" or "None" is in the expected values list. Consider explicitly handling the None case before performing comparisons.
| # Check if current value matches any expected value | |
| if current_value in expected_values: | |
| return (True, True) # Show in expander | |
| # Handle string comparison for booleans | |
| # If the controlling parameter is not found (None), do not match any expected value | |
| if current_value is None: | |
| return (False, False) # Hide entirely when controlling parameter is missing | |
| # Check if current value matches any expected value | |
| if current_value in expected_values: | |
| return (True, True) # Show in expander | |
| # Handle string comparison for booleans and case-insensitive matches |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The label formatting logic creates markdown with bold formatting for the last part. However, when there's only one part (len(parts) == 1), it returns **part** without any prefix, but when there are multiple parts, it adds a trailing colon before the bold part. This creates inconsistent formatting. Consider simplifying to always return the formatted string consistently regardless of the number of parts.
| return ":".join(parts[:-1]) + (":" if len(parts) > 1 else "") + f"**{parts[-1]}**" | |
| if len(parts) == 1: | |
| # Single-part section: just bold the name | |
| return f"**{parts[0]}**" | |
| # Multi-part section: show prefix, then bold the last part | |
| prefix = ":".join(parts[:-1]) | |
| return f"{prefix}:**{parts[-1]}**" |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the controlling parameter name is empty (when "param" key is missing from the control dict), controlling_param_section will also be empty, and the validation check on lines 771-772 may not detect all invalid configurations. Consider adding validation to ensure both "param" and "value" keys exist in control configs before proceeding.
| controlling_param = control.get("param", "") | |
| # Ensure control config contains required keys | |
| if not isinstance(control, dict) or "param" not in control or "value" not in control: | |
| st.error( | |
| f"Configuration error: Conditional section '{controlled_section}' " | |
| "must define both 'param' and 'value' keys." | |
| ) | |
| return | |
| controlling_param = control.get("param") | |
| # controlling_param must be a non-empty string | |
| if not isinstance(controlling_param, str) or not controlling_param.strip(): | |
| st.error( | |
| f"Configuration error: Controlling parameter for section " | |
| f"'{controlled_section}' must be a non-empty string." | |
| ) | |
| return |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parameter params is annotated as dict but is actually a list of parameter dictionaries based on how it's called (e.g., section_params on line 878). The type annotation should be list instead of dict.
| def display_TOPP_params(params: dict, num_cols): | |
| def display_TOPP_params(params: list[dict[str, Any]], num_cols): |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new conditional section visibility logic lacks test coverage. The feature includes multiple code paths (exact match, prefix match, value matching, validation errors) that should be tested to ensure correct behavior, especially the validation logic on lines 765-777 and the section visibility decisions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
get_param_valuefunction performs a linear search through all parameters each time it's called. This is invoked for every section viashould_show_section(). Consider building a parameter lookup dictionary once before the loop to avoid O(n) lookups for each section, especially when there are many sections.