codeant-ai-for-open-source[bot] commented on code in PR #38167:
URL: https://github.com/apache/superset/pull/38167#discussion_r2838319639


##########
superset-extensions-cli/src/superset_extensions_cli/cli.py:
##########
@@ -403,14 +416,123 @@ def backend_watcher() -> None:
         click.secho("❌ No directories to watch. Exiting.", fg="red")
 
 
+def prompt_for_extension_name(
+    display_name_opt: str | None, id_opt: str | None
+) -> ExtensionNames:
+    """
+    Prompt for extension name with graceful validation and re-prompting.
+
+    Args:
+        display_name_opt: Display name provided via CLI option (if any)
+        id_opt: Extension ID provided via CLI option (if any)
+
+    Returns:
+        ExtensionNames: Validated extension name variants
+    """
+
+    # Case 1: Both provided via CLI - validate they work together
+    if display_name_opt and id_opt:
+        try:
+            # Generate all names from display name for consistency
+            temp_names = generate_extension_names(display_name_opt)
+            # Check if the provided ID matches what we'd generate
+            if temp_names["id"] == id_opt:
+                return temp_names
+            else:
+                # If IDs don't match, use the provided ID but validate it
+                validate_extension_id(id_opt)
+                validate_python_package_name(to_snake_case(id_opt))
+                validate_npm_package_name(id_opt)
+                # Create names with the provided ID (derive technical names 
from ID)
+                return ExtensionNames(
+                    name=display_name_opt,
+                    id=id_opt,
+                    mf_name=kebab_to_camel_case(id_opt),
+                    backend_name=kebab_to_snake_case(id_opt),
+                    
backend_package=f"superset_extensions.{kebab_to_snake_case(id_opt)}",
+                    
backend_entry=f"superset_extensions.{kebab_to_snake_case(id_opt)}.entrypoint",
+                )
+        except ExtensionNameError as e:
+            click.secho(f"❌ {e}", fg="red")
+            sys.exit(1)
+
+    # Case 2: Only display name provided - suggest ID
+    if display_name_opt and not id_opt:
+        display_name = display_name_opt
+        try:
+            suggested_names = generate_extension_names(display_name)
+            suggested_id = suggested_names["id"]
+        except ExtensionNameError:
+            suggested_id = to_kebab_case(display_name)
+
+        extension_id = click.prompt("Extension ID", default=suggested_id, 
type=str)
+
+    # Case 3: Only ID provided - ask for display name
+    elif id_opt and not display_name_opt:
+        extension_id = id_opt
+        # Validate the provided ID first
+        try:
+            validate_extension_id(id_opt)
+        except ExtensionNameError as e:
+            click.secho(f"❌ {e}", fg="red")
+            sys.exit(1)
+
+        # Suggest display name from kebab ID
+        suggested_display = " ".join(word.capitalize() for word in 
id_opt.split("-"))
+        display_name = click.prompt(
+            "Extension name", default=suggested_display, type=str
+        )
+
+    # Case 4: Neither provided - ask for both
+    else:
+        display_name = click.prompt("Extension name (e.g. Hello World)", 
type=str)
+        try:
+            suggested_names = generate_extension_names(display_name)
+            suggested_id = suggested_names["id"]
+        except ExtensionNameError:
+            suggested_id = to_kebab_case(display_name)
+
+        extension_id = click.prompt("Extension ID", default=suggested_id, 
type=str)
+
+    # Final validation loop - try to use generate_extension_names for 
consistent results
+    while True:
+        try:
+            # First try to generate from display name if possible
+            if display_name:
+                temp_names = generate_extension_names(display_name)
+                if temp_names["id"] == extension_id:
+                    # Perfect match - use generated names
+                    return temp_names
+
+            # If no match, validate manually and construct
+            validate_extension_id(extension_id)
+            validate_python_package_name(to_snake_case(extension_id))
+            validate_npm_package_name(extension_id)
+
+            return ExtensionNames(
+                name=display_name,
+                id=extension_id,
+                mf_name=kebab_to_camel_case(extension_id),
+                backend_name=kebab_to_snake_case(extension_id),
+                
backend_package=f"superset_extensions.{kebab_to_snake_case(extension_id)}",
+                
backend_entry=f"superset_extensions.{kebab_to_snake_case(extension_id)}.entrypoint",
+            )
+
+        except ExtensionNameError as e:
+            click.secho(f"❌ {e}", fg="red")
+            extension_id = click.prompt("Extension ID", type=str)

Review Comment:
   **Suggestion:** In the final validation loop of the prompt function, if the 
display name is invalid and causes `generate_extension_names` to raise 
`ExtensionNameError`, the code keeps retrying with the same invalid display 
name while only re-prompting the extension ID, leading to an unfixable loop 
where the user can never progress without restarting the command. Adding a 
guard so that once an error occurs, the code stops re-calling 
`generate_extension_names` and falls back to validating only the extension ID 
fixes this logic and avoids an effective infinite loop. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ `superset-extensions init` can hang on invalid names.
   - ⚠️ User cannot recover without restarting CLI command.
   ```
   </details>
   
   ```suggestion
       can_generate_from_display = True
       while True:
           try:
               # First try to generate from display name if possible
               if display_name and can_generate_from_display:
                   temp_names = generate_extension_names(display_name)
                   if temp_names["id"] == extension_id:
                       # Perfect match - use generated names
                       return temp_names
   
               # If no match, validate manually and construct
               validate_extension_id(extension_id)
               validate_python_package_name(to_snake_case(extension_id))
               validate_npm_package_name(extension_id)
   
               return ExtensionNames(
                   name=display_name,
                   id=extension_id,
                   mf_name=kebab_to_camel_case(extension_id),
                   backend_name=kebab_to_snake_case(extension_id),
                   
backend_package=f"superset_extensions.{kebab_to_snake_case(extension_id)}",
                   
backend_entry=f"superset_extensions.{kebab_to_snake_case(extension_id)}.entrypoint",
               )
   
           except ExtensionNameError as e:
               click.secho(f"❌ {e}", fg="red")
               extension_id = click.prompt("Extension ID", type=str)
               can_generate_from_display = False
   ```
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Run the CLI command `superset-extensions init --name "<invalid name>"` 
which invokes
   the `init` command in
   `superset-extensions-cli/src/superset_extensions_cli/cli.py:init(...)` 
(around lines
   214–250) with `name_opt` set and `id_opt` as `None`.
   
   2. Inside `init`, `prompt_for_extension_name(name_opt, id_opt)` is called 
(same file,
   around line 200). Because only `name_opt` is provided, execution follows 
"Case 2: Only
   display name provided", setting `display_name = display_name_opt` and 
computing/suggesting
   an ID, then eventually reaching the final validation loop shown at lines 
498–523.
   
   3. In the final validation loop, `generate_extension_names(display_name)` is 
called on
   each iteration when `display_name` is truthy (`cli.py` lines 502–505). For 
any display
   name that causes `generate_extension_names()` (from 
`superset_extensions_cli/utils.py`) to
   raise `ExtensionNameError`—a scenario explicitly anticipated by earlier 
`try/except
   ExtensionNameError` blocks in this same function—the exception is caught by 
the `except
   ExtensionNameError` block at lines 518–523.
   
   4. The `except` block only prompts the user for a new `extension_id` 
(`extension_id =
   click.prompt("Extension ID", type=str)`) and never updates `display_name`. 
Control then
   returns to the start of the `while True` loop where
   `generate_extension_names(display_name)` is called again with the same 
invalid
   `display_name`, raising `ExtensionNameError` again. This creates an 
effective infinite
   loop where the user can change the ID indefinitely but has no way to correct 
the invalid
   `display_name` without aborting the command.
   ```
   </details>
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset-extensions-cli/src/superset_extensions_cli/cli.py
   **Line:** 499:523
   **Comment:**
        *Logic Error: In the final validation loop of the prompt function, if 
the display name is invalid and causes `generate_extension_names` to raise 
`ExtensionNameError`, the code keeps retrying with the same invalid display 
name while only re-prompting the extension ID, leading to an unfixable loop 
where the user can never progress without restarting the command. Adding a 
guard so that once an error occurs, the code stops re-calling 
`generate_extension_names` and falls back to validating only the extension ID 
fixes this logic and avoids an effective infinite loop.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38167&comment_hash=0cf893f2909f08f33b4ea4133d8bc53e1827dced4093f686a875b0bfd22e29e5&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F38167&comment_hash=0cf893f2909f08f33b4ea4133d8bc53e1827dced4093f686a875b0bfd22e29e5&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to