aminghadersohi commented on code in PR #38402:
URL: https://github.com/apache/superset/pull/38402#discussion_r2932090523


##########
superset/mcp_service/chart/validation/schema_validator.py:
##########
@@ -282,6 +286,82 @@ def _pre_validate_pie_config(
 
         return True, None
 
+    @staticmethod
+    def _pre_validate_handlebars_config(
+        config: Dict[str, Any],
+    ) -> Tuple[bool, ChartGenerationError | None]:
+        """Pre-validate handlebars chart configuration."""
+        if "handlebars_template" not in config:
+            return False, ChartGenerationError(
+                error_type="missing_handlebars_template",
+                message="Handlebars chart missing required field: 
handlebars_template",
+                details="Handlebars charts require a 'handlebars_template' 
string "
+                "containing Handlebars HTML template markup",
+                suggestions=[
+                    "Add 'handlebars_template' with a Handlebars HTML 
template",
+                    "Data is available as {{data}} array in the template",
+                    "Example: '<ul>{{#each data}}<li>{{this.name}}: "
+                    "{{this.value}}</li>{{/each}}</ul>'",
+                ],
+                error_code="MISSING_HANDLEBARS_TEMPLATE",
+            )
+
+        template = config.get("handlebars_template")
+        if not isinstance(template, str) or not template.strip():
+            return False, ChartGenerationError(
+                error_type="invalid_handlebars_template",
+                message="Handlebars template must be a non-empty string",
+                details="The 'handlebars_template' field must be a non-empty 
string "
+                "containing valid Handlebars HTML template markup",
+                suggestions=[
+                    "Ensure handlebars_template is a non-empty string",
+                    "Example: '<ul>{{#each 
data}}<li>{{this.name}}</li>{{/each}}</ul>'",
+                ],
+                error_code="INVALID_HANDLEBARS_TEMPLATE",
+            )
+
+        query_mode = config.get("query_mode", "aggregate")
+        if query_mode not in ("aggregate", "raw"):
+            return False, ChartGenerationError(
+                error_type="invalid_query_mode",
+                message="Invalid query_mode for handlebars chart",
+                details="query_mode must be either 'aggregate' or 'raw'",
+                suggestions=[
+                    "Use 'aggregate' for aggregated data (default)",
+                    "Use 'raw' for individual rows",
+                ],
+                error_code="INVALID_QUERY_MODE",
+            )
+
+        if query_mode == "raw" and "columns" not in config:

Review Comment:
   The pre-validation intentionally checks for the key's presence as a quick 
reject. Pydantic's schema validation (with minItems=1) catches empty lists in 
the next validation layer. This two-layer approach is by design.



##########
superset/mcp_service/chart/schemas.py:
##########
@@ -690,6 +690,108 @@ class MixedTimeseriesChartConfig(BaseModel):
     filters: List[FilterConfig] | None = Field(None, description="Filters to 
apply")
 
 
+class HandlebarsChartConfig(BaseModel):
+    model_config = ConfigDict(extra="forbid")
+
+    chart_type: Literal["handlebars"] = Field(
+        ...,
+        description=(
+            "Chart type discriminator - MUST be 'handlebars' for custom HTML "
+            "template charts. Handlebars charts render query results using "
+            "Handlebars templates, enabling fully custom layouts like KPI 
cards, "
+            "leaderboards, and formatted reports."
+        ),
+    )
+    handlebars_template: str = Field(
+        ...,
+        description=(
+            "Handlebars HTML template string. Data is available as {{data}} 
array. "
+            "Built-in helpers: {{dateFormat val format='YYYY-MM-DD'}}, "
+            "{{formatNumber val}}, {{stringify obj}}. "
+            "Example: '<ul>{{#each data}}<li>{{this.name}}: 
{{this.value}}</li>"

Review Comment:
   The template example in the description renders correctly in the Pydantic 
JSON Schema output — it uses Handlebars syntax with double braces. The 
formatting is intentional for the JSON schema context.



##########
superset/mcp_service/chart/validation/schema_validator.py:
##########
@@ -282,6 +286,82 @@ def _pre_validate_pie_config(
 
         return True, None
 
+    @staticmethod
+    def _pre_validate_handlebars_config(
+        config: Dict[str, Any],
+    ) -> Tuple[bool, ChartGenerationError | None]:
+        """Pre-validate handlebars chart configuration."""
+        if "handlebars_template" not in config:
+            return False, ChartGenerationError(
+                error_type="missing_handlebars_template",
+                message="Handlebars chart missing required field: 
handlebars_template",
+                details="Handlebars charts require a 'handlebars_template' 
string "
+                "containing Handlebars HTML template markup",
+                suggestions=[
+                    "Add 'handlebars_template' with a Handlebars HTML 
template",
+                    "Data is available as {{data}} array in the template",
+                    "Example: '<ul>{{#each data}}<li>{{this.name}}: "
+                    "{{this.value}}</li>{{/each}}</ul>'",
+                ],
+                error_code="MISSING_HANDLEBARS_TEMPLATE",
+            )
+
+        template = config.get("handlebars_template")
+        if not isinstance(template, str) or not template.strip():
+            return False, ChartGenerationError(
+                error_type="invalid_handlebars_template",
+                message="Handlebars template must be a non-empty string",
+                details="The 'handlebars_template' field must be a non-empty 
string "
+                "containing valid Handlebars HTML template markup",
+                suggestions=[
+                    "Ensure handlebars_template is a non-empty string",
+                    "Example: '<ul>{{#each 
data}}<li>{{this.name}}</li>{{/each}}</ul>'",
+                ],
+                error_code="INVALID_HANDLEBARS_TEMPLATE",
+            )
+
+        query_mode = config.get("query_mode", "aggregate")
+        if query_mode not in ("aggregate", "raw"):
+            return False, ChartGenerationError(
+                error_type="invalid_query_mode",
+                message="Invalid query_mode for handlebars chart",
+                details="query_mode must be either 'aggregate' or 'raw'",
+                suggestions=[
+                    "Use 'aggregate' for aggregated data (default)",
+                    "Use 'raw' for individual rows",
+                ],
+                error_code="INVALID_QUERY_MODE",
+            )
+
+        if query_mode == "raw" and "columns" not in config:
+            return False, ChartGenerationError(
+                error_type="missing_raw_columns",
+                message="Handlebars chart in 'raw' mode requires 'columns'",
+                details="When query_mode is 'raw', you must specify which 
columns "
+                "to include in the query results",
+                suggestions=[
+                    "Add 'columns': [{'name': 'column_name'}] for raw mode",
+                    "Or use query_mode='aggregate' with 'metrics' "
+                    "and optional 'groupby'",
+                ],
+                error_code="MISSING_RAW_COLUMNS",
+            )
+
+        if query_mode == "aggregate" and "metrics" not in config:
+            return False, ChartGenerationError(

Review Comment:
   Same as above — the pre-validation checks for key presence as a fast-path 
rejection. Empty lists are caught by Pydantic's minItems=1 constraint in the 
schema layer. The two-layer validation is intentional.



-- 
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