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


##########
superset/mcp_service/chart/plugins/mixed_timeseries.py:
##########
@@ -0,0 +1,173 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Mixed timeseries chart type plugin."""
+
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, ClassVar
+
+from superset.mcp_service.chart.chart_utils import (
+    _mixed_timeseries_what,
+    _summarize_filters,
+    map_mixed_timeseries_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, 
MixedTimeseriesChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import 
DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class MixedTimeseriesChartPlugin(BaseChartPlugin):
+    """Plugin for mixed_timeseries chart type."""
+
+    chart_type = "mixed_timeseries"
+    display_name = "Mixed Timeseries"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "mixed_timeseries": "Mixed Timeseries Chart",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        missing_fields = []
+
+        if "x" not in config and "x_axis" not in config:
+            missing_fields.append("'x' (X-axis temporal column)")
+        if not config.get("y") and not config.get("metrics"):
+            missing_fields.append("'y' (primary Y-axis metrics)")
+        if not config.get("y_secondary") and not config.get("metrics_b"):
+            missing_fields.append("'y_secondary' (secondary Y-axis metrics)")
+
+        if missing_fields:
+            return ChartGenerationError(
+                error_type="missing_mixed_timeseries_fields",
+                message=(
+                    f"Mixed timeseries chart missing required fields: "
+                    f"{', '.join(missing_fields)}"
+                ),
+                details=(
+                    "Mixed timeseries charts require an x-axis, primary 
metrics, "
+                    "and secondary metrics"
+                ),
+                suggestions=[
+                    "Add 'x' field: {'name': 'date_column'}",
+                    "Add 'y' field: [{'name': 'revenue', 'aggregate': 'SUM'}]",
+                    "Add 'y_secondary': [{'name': 'orders', 'aggregate': 
'COUNT'}]",
+                    "Optional: 'primary_kind' and 'secondary_kind' for chart 
types",
+                ],
+                error_code="MISSING_MIXED_TIMESERIES_FIELDS",
+            )
+
+        for field_name in ["y", "y_secondary"]:
+            if not isinstance(config.get(field_name, []), list):
+                return ChartGenerationError(
+                    error_type=f"invalid_{field_name}_format",
+                    message=f"'{field_name}' must be a list of metrics",
+                    details=(
+                        f"The '{field_name}' field must be an array of metric "
+                        "specifications"
+                    ),
+                    suggestions=[
+                        f"Wrap in array: '{field_name}': "
+                        "[{'name': 'col', 'aggregate': 'SUM'}]",
+                    ],
+                    error_code=f"INVALID_{field_name.upper()}_FORMAT",
+                )
+
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, MixedTimeseriesChartConfig):
+            return []
+        refs: list[ColumnRef] = [config.x]
+        refs.extend(config.y)
+        refs.extend(config.y_secondary)
+        if config.group_by:
+            refs.extend(config.group_by)
+        if config.group_by_secondary:
+            refs.extend(config.group_by_secondary)
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_mixed_timeseries_config(config, dataset_id=dataset_id)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> 
str:
+        what = _mixed_timeseries_what(config)
+        context = _summarize_filters(config.filters)
+        return self._with_context(what, context)
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "mixed_timeseries"
+
+    def normalize_column_refs(self, config: Any, dataset_context: Any) -> Any:
+        config_dict = config.model_dump()
+
+        def _norm_single(key: str) -> None:
+            if config_dict.get(key):
+                config_dict[key]["name"] = 
DatasetValidator.get_canonical_column_name(
+                    config_dict[key]["name"], dataset_context
+                )
+
+        def _norm_list(key: str) -> None:
+            if config_dict.get(key):
+                for col in config_dict[key]:
+                    if col.get("sql_expression"):
+                        continue
+                    if col.get("saved_metric"):
+                        col["name"] = 
DatasetValidator.get_canonical_metric_name(
+                            col["name"], dataset_context
+                        )
+                    else:
+                        col["name"] = 
DatasetValidator.get_canonical_column_name(
+                            col["name"], dataset_context
+                        )
+
+        _norm_single("x")
+        _norm_list("y")
+        _norm_list("y_secondary")
+        _norm_list("group_by")
+        _norm_list("group_by_secondary")

Review Comment:
   **Suggestion:** The same list normalizer is applied to both metric fields 
and dimension fields (`group_by`, `group_by_secondary`), and it accepts 
`saved_metric=True` for all of them. Because saved metrics are excluded from 
normal column-existence checks, grouping dimensions can incorrectly pass 
validation even when they are not real columns, causing downstream query 
failures. Enforce that group-by fields are always canonicalized as columns and 
reject `saved_metric` there. [api mismatch]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Mixed timeseries groupby allows saved metrics as dimensions.
   - ❌ Dataset validator misses non-column groupbys in mixed timeseries.
   - ⚠️ LLM clients receive generic compile errors, fewer hints.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Use the MCP `generate_explore_link` tool in
   `superset/mcp_service/explore/tool/generate_explore_link.py` (lines 22-80) 
with a mixed
   timeseries config: `chart_type="mixed_timeseries"`, `x={"name": "ds"}`, 
`y=[{"name":
   "TotalRevenue", "saved_metric": true}]`, `y_secondary=[{"name": "OrderCount",
   "saved_metric": true}]`, and `group_by=[{"name": "TotalRevenue", 
"saved_metric": true}]`.
   This is parsed into `MixedTimeseriesChartConfig` defined in
   `superset/mcp_service/chart/schemas.py` (lines 117-190), where `group_by` and
   `group_by_secondary` are lists of `ColumnRef`.
   
   2. Column-name normalization runs via 
`DatasetValidator.normalize_column_names()`
   (`superset/mcp_service/chart/validation/dataset_validator.py:120-167`), 
called from
   `ValidationPipeline._normalize_column_names()`
   (`superset/mcp_service/chart/validation/pipeline.py:229-260`) or directly in
   `generate_explore_link` (lines 27-32). For chart_type `"mixed_timeseries"`, 
this
   dispatches to `MixedTimeseriesChartPlugin.normalize_column_refs()` in
   `superset/mcp_service/chart/plugins/mixed_timeseries.py` (lines 124-153). The
   `_norm_list()` helper (lines 133-145) is invoked for `"y"`, `"y_secondary"`, 
`"group_by"`,
   and `"group_by_secondary"`. For entries with `saved_metric=True`, including 
those in
   `group_by`, it calls `DatasetValidator.get_canonical_metric_name()` (lines 
138-141),
   explicitly treating those group-by dimensions as saved metrics.
   
   3. Tier-1 dataset validation via 
`DatasetValidator.validate_against_dataset()`
   (`superset/mcp_service/chart/validation/dataset_validator.py:95-154`) uses
   `MixedTimeseriesChartPlugin.extract_column_refs()` (lines 96-109) to collect 
refs from
   `x`, `y`, `y_secondary`, `group_by`, and `group_by_secondary`. In
   `_validate_saved_metrics()` (lines 254-37), all `saved_metric=True` refs, 
including the
   group-by entries, are checked only against `available_metrics`. Then
   `_validate_columns_exist()` (lines 156-198) explicitly skips any ref with
   `saved_metric=True` (line 179), so invalid grouping dimensions that 
reference saved
   metrics instead of real columns never undergo column-existence validation.
   
   4. `map_mixed_timeseries_config()` in 
`superset/mcp_service/chart/chart_utils.py` (lines
   459-520) converts the normalized config into form_data. It builds 
`form_data["groupby"]`
   and `form_data["groupby_b"]` from `[c.name for c in config.group_by]` and 
`[c.name for c
   in config.group_by_secondary]` (lines 507-518), so those arrays contain 
metric names when
   `saved_metric=True` was used on dimensions. When `generate_explore_link` 
calls
   `validate_and_compile()` (`superset/mcp_service/chart/compile.py:355-420`) 
with
   `run_compile_check=True`, `_compile_chart()` (lines 100-172) uses this 
form_data in
   `QueryContextFactory`, treating the metric names in `groupby` as column 
identifiers. The
   database query then fails or produces invalid SQL at compile time, even 
though Tier-1
   validation has already reported success and provided no column-suggestion 
hints for the
   misconfigured group-by fields.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=ac9965e797e44fd2b1421f43d362f97c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=ac9965e797e44fd2b1421f43d362f97c&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/chart/plugins/mixed_timeseries.py
   **Line:** 133:151
   **Comment:**
        *Api Mismatch: The same list normalizer is applied to both metric 
fields and dimension fields (`group_by`, `group_by_secondary`), and it accepts 
`saved_metric=True` for all of them. Because saved metrics are excluded from 
normal column-existence checks, grouping dimensions can incorrectly pass 
validation even when they are not real columns, causing downstream query 
failures. Enforce that group-by fields are always canonicalized as columns and 
reject `saved_metric` there.
   
   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.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39922&comment_hash=53675950b3c49e751bb777e82b9cdb8cb9659e01b5eaca015a0b8aac945061c2&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39922&comment_hash=53675950b3c49e751bb777e82b9cdb8cb9659e01b5eaca015a0b8aac945061c2&reaction=dislike'>👎</a>



##########
superset/mcp_service/chart/plugins/handlebars.py:
##########
@@ -0,0 +1,194 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+"""Handlebars chart type plugin."""
+
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, ClassVar
+
+from superset.mcp_service.chart.chart_utils import (
+    _handlebars_chart_what,
+    _summarize_filters,
+    map_handlebars_config,
+)
+from superset.mcp_service.chart.plugin import BaseChartPlugin
+from superset.mcp_service.chart.schemas import ColumnRef, HandlebarsChartConfig
+from superset.mcp_service.chart.validation.dataset_validator import 
DatasetValidator
+from superset.mcp_service.common.error_schemas import ChartGenerationError
+
+
+class HandlebarsChartPlugin(BaseChartPlugin):
+    """Plugin for handlebars chart type (custom HTML template charts)."""
+
+    chart_type = "handlebars"
+    display_name = "Handlebars (Custom Template)"
+    native_viz_types: ClassVar[Mapping[str, str]] = {
+        "handlebars": "Custom Template Chart",
+    }
+
+    def pre_validate(
+        self,
+        config: dict[str, Any],
+    ) -> ChartGenerationError | None:
+        if "handlebars_template" not in config:
+            return 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 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 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 not config.get("columns"):
+            return 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'",  # noqa: E501
+                ],
+                error_code="MISSING_RAW_COLUMNS",
+            )
+
+        if query_mode == "aggregate" and not config.get("metrics"):
+            return ChartGenerationError(
+                error_type="missing_aggregate_metrics",
+                message="Handlebars chart in 'aggregate' mode requires 
'metrics'",
+                details=(
+                    "When query_mode is 'aggregate' (default), you must 
specify "
+                    "at least one metric with an aggregate function"
+                ),
+                suggestions=[
+                    "Add 'metrics': [{'name': 'column', 'aggregate': 'SUM'}]",
+                    "Or use query_mode='raw' with 'columns' for individual 
rows",
+                ],
+                error_code="MISSING_AGGREGATE_METRICS",
+            )
+
+        return None
+
+    def extract_column_refs(self, config: Any) -> list[ColumnRef]:
+        if not isinstance(config, HandlebarsChartConfig):
+            return []
+        refs: list[ColumnRef] = []
+        if config.columns:
+            refs.extend(config.columns)
+        if config.metrics:
+            refs.extend(config.metrics)
+        if config.groupby:
+            refs.extend(config.groupby)
+        if config.filters:
+            for f in config.filters:
+                refs.append(ColumnRef(name=f.column))
+        return refs
+
+    def to_form_data(
+        self, config: Any, dataset_id: int | str | None = None
+    ) -> dict[str, Any]:
+        return map_handlebars_config(config)
+
+    def generate_name(self, config: Any, dataset_name: str | None = None) -> 
str:
+        what = _handlebars_chart_what(config)
+        context = _summarize_filters(getattr(config, "filters", None))
+        return self._with_context(what, context)
+
+    def resolve_viz_type(self, config: Any) -> str:
+        return "handlebars"
+
+    def normalize_column_refs(self, config: Any, dataset_context: Any) -> Any:
+        config_dict = config.model_dump()
+
+        def _norm_list(key: str) -> None:
+            if config_dict.get(key):
+                for col in config_dict[key]:
+                    if col.get("saved_metric"):
+                        col["name"] = 
DatasetValidator.get_canonical_metric_name(
+                            col["name"], dataset_context
+                        )
+                    elif not col.get("sql_expression"):
+                        col["name"] = 
DatasetValidator.get_canonical_column_name(
+                            col["name"], dataset_context
+                        )
+
+        _norm_list("columns")
+        _norm_list("metrics")
+        _norm_list("groupby")

Review Comment:
   **Suggestion:** The shared normalizer accepts `saved_metric=True` for 
`columns` and `groupby`, but those are dimension selections (especially 
`columns` in `raw` mode). This can bypass column validation and produce 
form_data containing metric names where physical columns are required, leading 
to late query errors. Restrict `saved_metric` handling to `metrics` only and 
reject it for `columns`/`groupby`. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Handlebars charts accept saved metrics in groupby dimensions.
   - ❌ Dataset validation skips invalid Handlebars groupby saved-metric refs.
   - ⚠️ MCP tools surface errors only at compile-time.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. In the MCP server, call the `generate_explore_link` tool implemented in
   `superset/mcp_service/explore/tool/generate_explore_link.py` (lines 22-80) 
with a
   Handlebars chart config: `chart_type="handlebars"`, `query_mode="aggregate"`,
   `groupby=[{"name": "TotalRevenue", "saved_metric": true}]`, and valid 
`metrics`. This
   config is parsed into `HandlebarsChartConfig` as defined in
   `superset/mcp_service/chart/schemas.py` (lines 210-323), where `groupby` is 
a list of
   `ColumnRef` objects.
   
   2. During column-name normalization, 
`DatasetValidator.normalize_column_names()` in
   `superset/mcp_service/chart/validation/dataset_validator.py` (lines 120-167) 
is invoked
   from `ValidationPipeline._normalize_column_names()` in
   `superset/mcp_service/chart/validation/pipeline.py` (lines 229-260) or 
directly in
   `generate_explore_link` (lines 27-32). For chart_type `"handlebars"`, this 
calls
   `HandlebarsChartPlugin.normalize_column_refs()` at
   `superset/mcp_service/chart/plugins/handlebars.py:155-174`. The helper 
`_norm_list()`
   (lines 158-168) runs for `"groupby"` and, seeing `saved_metric=True`, sets 
`col["name"]`
   via `DatasetValidator.get_canonical_metric_name()`, keeping the reference as 
a saved
   metric rather than a physical column.
   
   3. Tier-1 dataset validation then runs via 
`DatasetValidator.validate_against_dataset()`
   in `superset/mcp_service/chart/validation/dataset_validator.py` (lines 
95-154), using
   `HandlebarsChartPlugin.extract_column_refs()` (lines 127-140) to collect 
column refs from
   `columns`, `metrics`, and `groupby`. In `_validate_saved_metrics()` (lines 
254-37), the
   `groupby` ref with `saved_metric=True` is validated only against
   `dataset_context.available_metrics`. In `_validate_columns_exist()` (lines 
156-198), that
   ref is skipped entirely because `col_ref.saved_metric` is true (line 179), 
so no check is
   performed that the `groupby` dimension is a real dataset column.
   
   4. Form-data mapping in `map_handlebars_config()` at
   `superset/mcp_service/chart/chart_utils.py:338-373` builds 
`form_data["groupby"] =
   [col.name for col in config.groupby]`. Because the `groupby` entry was 
canonicalized as a
   metric name and never validated as a column, `form_data["groupby"]` contains 
a metric
   identifier where a physical column is required. When 
`validate_and_compile()` in
   `superset/mcp_service/chart/compile.py` (lines 355-420) runs with 
`run_compile_check=True`
   (as in `generate_explore_link`, lines 73-80), `_compile_chart()` (lines 
100-172) uses this
   `groupby` list via `QueryContextFactory`, producing SQL that groups by a 
metric name
   instead of a column. This leads to a late query/compile-time error rather 
than a
   structured column-validation error, despite Tier-1 validation having passed.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=479d9dc4995b4451b5e10388e5be8fe6&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=479d9dc4995b4451b5e10388e5be8fe6&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/mcp_service/chart/plugins/handlebars.py
   **Line:** 158:172
   **Comment:**
        *Logic Error: The shared normalizer accepts `saved_metric=True` for 
`columns` and `groupby`, but those are dimension selections (especially 
`columns` in `raw` mode). This can bypass column validation and produce 
form_data containing metric names where physical columns are required, leading 
to late query errors. Restrict `saved_metric` handling to `metrics` only and 
reject it for `columns`/`groupby`.
   
   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.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39922&comment_hash=1a5fe6528cb7bcbe5bcfef5a774dd67a9ba58057db5f5ef5bf5861c06bb8ac0d&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39922&comment_hash=1a5fe6528cb7bcbe5bcfef5a774dd67a9ba58057db5f5ef5bf5861c06bb8ac0d&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