aminghadersohi commented on code in PR #44560:
URL: https://github.com/apache/superset/pull/44560#discussion_r4117186176
##########
superset/mcp_service/dashboard/schemas.py:
##########
@@ -2344,6 +2348,246 @@ class ManageNativeFiltersResponse(BaseModel):
)
+# ---------------------------------------------------------------------------
+# manage_dashboard_markdown schemas
+# ---------------------------------------------------------------------------
+
+
+class BaseNewDashboardComponentSpec(BaseModel):
+ """Common placement fields shared by all new markdown/header/divider
specs."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ target_tab: str | None = Field(
+ None,
+ description=(
+ "Tab to add the component to, matched by display name or "
+ "component ID (see get_dashboard_layout for available tabs). "
+ "Omit to use the first tab, or the grid if there are no tabs; "
+ "specify a target when the component should land in a "
+ "specific one rather than the first tab."
+ ),
+ )
+
+
+class MarkdownComponentSpec(BaseNewDashboardComponentSpec):
+ """Spec for a new markdown/text tile.
+
+ Placed in its own new row (a MARKDOWN component sits alongside charts,
+ not as a full-width band), so it composes with existing rows/charts on
+ the target grid or tab.
+ """
+
+ component_type: Literal["markdown"] = Field(
+ ..., description="Discriminator - must be 'markdown'"
+ )
+ code: str = Field(
+ ..., min_length=1, description="Markdown (and safe inline HTML) source"
+ )
Review Comment:
Not applicable: storing the original Markdown source is intentional, not an
HTML-sanitization bypass. The UI writes unmodified source to meta.code
([superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx:226-238](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx#L226-L238));
REST PUT validates position_json as JSON
([superset/dashboards/schemas.py:513-517](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset/dashboards/schemas.py#L513-L517))
and repairs/re-serializes the layout, not its Markdown HTML
([superset/commands/dashboard/update.py:101-107](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset/commands/dashboard/update.py#L101-L107)).
Dashboard rendering uses SafeMarkdown
([superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx:346-356](https:
//github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx#L346-L356)),
which applies rehype-sanitize after raw-HTML parsing, or leaves HTML escaped
when ESCAPE_MARKDOWN_HTML is enabled
([superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown/SafeMarkdown.tsx:156-201](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown/SafeMarkdown.tsx#L156-L201)).
HTML_SANITIZATION defaults to True; disabling it or extending the allowlist is
operator configuration
([superset/config.py:1531-1553](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset/config.py#L1531-L1553)),
shared by UI/REST/MCP. No tool-specific bypass or additional storage sanitizer
is warranted.
##########
superset/mcp_service/dashboard/schemas.py:
##########
@@ -2344,6 +2348,246 @@ class ManageNativeFiltersResponse(BaseModel):
)
+# ---------------------------------------------------------------------------
+# manage_dashboard_markdown schemas
+# ---------------------------------------------------------------------------
+
+
+class BaseNewDashboardComponentSpec(BaseModel):
+ """Common placement fields shared by all new markdown/header/divider
specs."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ target_tab: str | None = Field(
+ None,
+ description=(
+ "Tab to add the component to, matched by display name or "
+ "component ID (see get_dashboard_layout for available tabs). "
+ "Omit to use the first tab, or the grid if there are no tabs; "
+ "specify a target when the component should land in a "
+ "specific one rather than the first tab."
+ ),
+ )
+
+
+class MarkdownComponentSpec(BaseNewDashboardComponentSpec):
+ """Spec for a new markdown/text tile.
+
+ Placed in its own new row (a MARKDOWN component sits alongside charts,
+ not as a full-width band), so it composes with existing rows/charts on
+ the target grid or tab.
+ """
+
+ component_type: Literal["markdown"] = Field(
+ ..., description="Discriminator - must be 'markdown'"
+ )
+ code: str = Field(
+ ..., min_length=1, description="Markdown (and safe inline HTML) source"
+ )
+ width: int = Field(
+ GRID_DEFAULT_CHART_WIDTH,
+ ge=1,
+ le=GRID_COLUMN_COUNT,
+ description=(
+ f"Tile width in grid columns (1-{GRID_COLUMN_COUNT}, "
+ f"default {GRID_DEFAULT_CHART_WIDTH})"
+ ),
+ )
+ height: int = Field(
+ 50,
+ ge=1,
+ description="Tile height in grid units (one unit is 8 pixels; default
50)",
+ )
+
+
+class HeaderComponentSpec(BaseNewDashboardComponentSpec):
+ """Spec for a new section header band.
+
+ Placed directly on the target grid/tab (not inside a row) so it spans
+ the full dashboard width, matching how the dashboard builder places
+ dragged header components.
+ """
+
+ component_type: Literal["header"] = Field(
+ ..., description="Discriminator - must be 'header'"
+ )
+ text: str = Field(..., min_length=1, description="Header display text")
+ header_size: Literal["SMALL_HEADER", "MEDIUM_HEADER", "LARGE_HEADER"] =
Field(
+ "MEDIUM_HEADER", description="Header text size"
+ )
+ background: Literal["BACKGROUND_TRANSPARENT", "BACKGROUND_WHITE"] = Field(
+ "BACKGROUND_TRANSPARENT", description="Header band background"
+ )
+
+ @field_validator("text")
+ @classmethod
+ def sanitize_text(cls, v: str) -> str:
+ """Sanitize header text to prevent XSS; it renders as plain title
text."""
+ sanitized: str | None = sanitize_user_input(
+ v, "text", max_length=500, allow_empty=True
+ )
+ if not sanitized:
+ raise ValueError("text has no content left after sanitization.")
+ return sanitized
+
+
+class DividerComponentSpec(BaseNewDashboardComponentSpec):
+ """Spec for a new horizontal divider.
+
+ Placed directly on the target grid/tab (not inside a row), same as
+ ``HeaderComponentSpec``. Carries no content — only placement.
+ """
+
+ component_type: Literal["divider"] = Field(
+ ..., description="Discriminator - must be 'divider'"
+ )
+
+
+NewDashboardComponentSpec = Annotated[
+ MarkdownComponentSpec | HeaderComponentSpec | DividerComponentSpec,
+ Field(discriminator="component_type"),
+]
+
+
+class DashboardComponentUpdateSpec(BaseModel):
+ """Partial update for an existing markdown/header/divider component.
+
+ ``id`` and at least one non-null update field are required. Provided
+ fields are merged into the existing component. Fields that only apply to
+ one component type (e.g. ``code`` for markdown, ``text``/``header_size``
+ for header) are rejected when used against the wrong component type.
+ A component's type cannot be changed; remove and re-add instead.
+ """
+
+ model_config = ConfigDict(extra="forbid")
+
+ id: str = Field(..., min_length=1, description="ID of the component to
update")
+ code: str | None = Field(
+ None, min_length=1, description="New markdown source (markdown only)"
+ )
Review Comment:
Not applicable: updating Markdown uses the same source-storage/rendering
contract as creating it and as the UI editor. The UI writes unmodified source
to meta.code
([superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx:226-238](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx#L226-L238));
REST PUT validates position_json as JSON
([superset/dashboards/schemas.py:513-517](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset/dashboards/schemas.py#L513-L517))
and repairs/re-serializes the layout, not its Markdown HTML
([superset/commands/dashboard/update.py:101-107](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset/commands/dashboard/update.py#L101-L107)).
Dashboard rendering uses SafeMarkdown
([superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.ts
x:346-356](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset-frontend/src/dashboard/components/gridComponents/Markdown/Markdown.tsx#L346-L356)),
which applies rehype-sanitize after raw-HTML parsing, or leaves HTML escaped
when ESCAPE_MARKDOWN_HTML is enabled
([superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown/SafeMarkdown.tsx:156-201](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset-frontend/packages/superset-ui-core/src/components/SafeMarkdown/SafeMarkdown.tsx#L156-L201)).
HTML_SANITIZATION defaults to True; disabling it or extending the allowlist is
operator configuration
([superset/config.py:1531-1553](https://github.com/apache/superset/blob/2cb3e845442320e889bdbbea76b27ef05a9b287b/superset/config.py#L1531-L1553)),
shared by UI/REST/MCP. No tool-specific bypass or additional storage sanitizer
is warranted.
--
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]