Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,28 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
- [35062](https://github.com/apache/superset/pull/35062): Changed the function signature of `setupExtensions` to `setupCodeOverrides` with options as arguments.

### Breaking Changes
- [37370](https://github.com/apache/superset/pull/37370): The `APP_NAME` configuration variable no longer controls the browser window/tab title or other frontend branding. Application names should now be configured using the theme system with the `brandAppName` token. The `APP_NAME` config is still used for backend contexts (MCP service, logs, etc.) and serves as a fallback if `brandAppName` is not set.
- **Migration:**
```python
# Before (Superset 5.x)
APP_NAME = "My Custom App"

# After (Superset 6.x) - Option 1: Use theme system (recommended)
THEME_DEFAULT = {
"token": {
"brandAppName": "My Custom App", # Window titles
"brandLogoAlt": "My Custom App", # Logo alt text
"brandLogoUrl": "/static/assets/images/custom_logo.png"
}
}

# After (Superset 6.x) - Option 2: Temporary fallback
# Keep APP_NAME for now (will be used as fallback for brandAppName)
APP_NAME = "My Custom App"
# But you should migrate to THEME_DEFAULT.token.brandAppName
```
- **Note:** For dark mode, set the same tokens in `THEME_DARK` configuration.

- [36317](https://github.com/apache/superset/pull/36317): The `CUSTOM_FONT_URLS` configuration option has been removed. Use the new per-theme `fontUrls` token in `THEME_DEFAULT` or database-managed themes instead.
- **Before:**
```python
Expand All @@ -177,7 +199,7 @@ See `superset/mcp_service/PRODUCTION.md` for deployment guides.
"fontUrls": [
"https://fonts.example.com/myfont.css",
],
# ... other tokens
# ... other tokens
}
}
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ export interface SupersetSpecificTokens {

// Brand-related
brandIconMaxWidth: number;
brandAppName?: string;
brandLogoAlt: string;
brandLogoUrl: string;
brandLogoMargin: string;
Expand Down
19 changes: 16 additions & 3 deletions superset-frontend/src/dashboard/containers/DashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -223,15 +223,28 @@ export const DashboardPage: FC<PageProps> = ({ idOrSlug }: PageProps) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [readyToRender]);

// Capture original title before any effects run
const originalTitle = useMemo(() => document.title, []);

// Update document title when dashboard title changes
useEffect(() => {
if (dashboard_title) {
document.title = dashboard_title;
}
return () => {
document.title = 'Superset';
};
}, [dashboard_title]);

// Restore original title on unmount
useEffect(
() => () => {
document.title =
originalTitle ||
theme?.brandAppName ||
theme?.brandLogoAlt ||
'Superset';
},
[originalTitle, theme?.brandAppName, theme?.brandLogoAlt],
);

useEffect(() => {
if (typeof css === 'string') {
// returning will clean up custom css
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,15 +291,28 @@ function ExploreViewContainer(props) {

const theme = useTheme();

// Capture original title before any effects run
const originalTitle = useMemo(() => document.title, []);

// Update document title when slice name changes
useEffect(() => {
if (props.sliceName) {
document.title = props.sliceName;
}
return () => {
document.title = 'Superset';
};
}, [props.sliceName]);

// Restore original title on unmount
useEffect(
() => () => {
document.title =
originalTitle ||
theme?.brandAppName ||
theme?.brandLogoAlt ||
'Superset';
},
[originalTitle, theme?.brandAppName, theme?.brandLogoAlt],
);

const addHistory = useCallback(
async ({ isReplace = false, title } = {}) => {
const formData = props.dashboardId
Expand Down
2 changes: 2 additions & 0 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,8 @@ class D3TimeFormat(TypedDict, total=False):
THEME_DEFAULT: Theme = {
"token": {
# Brand
# Application name for window titles
"brandAppName": APP_NAME,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Backwards-compatibility/config-order issue: brandAppName is evaluated at module import time and will not reflect APP_NAME overrides applied later by a local superset_config.py; allow an environment-based override that can be set externally (and keep a fallback) so deployments that set an app name via env var get the correct value without relying on import order. [possible bug]

Severity Level: Critical 🚨
- ❌ Browser title shows wrong app name on login.
- ❌ Dashboard/tab titles show stale/default name.
- ⚠️ Breaks backward compatibility for APP_NAME overrides.
Suggested change
"brandAppName": APP_NAME,
"brandAppName": os.environ.get("SUPERSET_APP_NAME", APP_NAME or "Superset"),
Steps of Reproduction ✅
1. Inspect `superset/config.py` where THEME_DEFAULT is declared; the token
`"brandAppName"` is assigned at `superset/config.py:905-906` to the value of `APP_NAME` at
module import time.

2. Provision a local override by creating `superset_config.py` with `APP_NAME = "My Custom
App"` and ensure it is discoverable on PYTHONPATH. The project loads `superset_config`
later in the file (the import block that applies local overrides executes after
THEME_DEFAULT is defined).

3. Start Superset. The module-level `APP_NAME` is updated by the `superset_config` import,
but THEME_DEFAULT["token"]["brandAppName"] remains the earlier bound value because it was
set during module import at `superset/config.py:902-912`.

4. Observe the browser tab/window titles (login page, dashboards) still showing the
old/default name instead of the `APP_NAME` from `superset_config.py`. This reproduces the
import-order issue where the theme token was captured too early. The suggested change
makes the token read from an env override or fallback so deployments that set app name via
env or later config still surface correctly in the theme.
Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset/config.py
**Line:** 906:906
**Comment:**
	*Possible Bug: Backwards-compatibility/config-order issue: `brandAppName` is evaluated at module import time and will not reflect `APP_NAME` overrides applied later by a local `superset_config.py`; allow an environment-based override that can be set externally (and keep a fallback) so deployments that set an app name via env var get the correct value without relying on import order.

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.

"brandLogoAlt": "Apache Superset",
"brandLogoUrl": APP_ICON,
"brandLogoMargin": "18px 0",
Expand Down
2 changes: 2 additions & 0 deletions superset/templates/superset/spa.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
{% block title %}
{% if title %}
{{ title }}
{% else %}
{{ default_title | default('Superset') }}
{% endif %}
{% endblock %}
</title>
Expand Down
40 changes: 37 additions & 3 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# under the License.
from __future__ import annotations

import copy
import functools
import logging
import os
Expand Down Expand Up @@ -567,10 +568,39 @@ def get_spa_template_context(
"""
payload = get_spa_payload(extra_bootstrap_data)

# Extract theme data for template access
theme_data = get_theme_bootstrap_data().get("theme", {})
# Deep copy theme data to avoid mutating cached bootstrap payload
theme_data = copy.deepcopy(payload.get("common", {}).get("theme", {}))
default_theme = theme_data.get("default", {})
theme_tokens = default_theme.get("token", {})
dark_theme = theme_data.get("dark", {})

# Apply brandAppName fallback to both default and dark themes
# Priority: theme brandAppName > APP_NAME config > "Superset" default
app_name_from_config = app.config.get("APP_NAME", "Superset")
for theme_config in [default_theme, dark_theme]:
if not theme_config:
continue
# Get or create token dict
if "token" not in theme_config:
theme_config["token"] = {}
theme_tokens = theme_config["token"]

if (
not theme_tokens.get("brandAppName")
or theme_tokens.get("brandAppName") == "Superset"
):
# If brandAppName not set or is default, check if APP_NAME customized
if app_name_from_config != "Superset":
# User has customized APP_NAME, use it as brandAppName
theme_tokens["brandAppName"] = app_name_from_config

# Write the modified theme data back to payload
if "common" not in payload:
payload["common"] = {}
payload["common"]["theme"] = theme_data

# Extract theme tokens for template access (after fallback applied)
# Use the direct reference to ensure we get the modified token dict
theme_tokens = default_theme.get("token", {}) if default_theme else {}

# Determine spinner content with precedence: theme SVG > theme URL > default SVG
spinner_svg = None
Expand All @@ -581,13 +611,17 @@ def get_spa_template_context(
# No custom URL either, use default SVG
spinner_svg = get_default_spinner_svg()

# Determine default title using the (potentially updated) brandAppName
default_title = theme_tokens.get("brandAppName", "Superset")

return {
"entry": entry,
"bootstrap_data": json.dumps(
payload, default=json.pessimistic_json_iso_dttm_ser
),
"theme_tokens": theme_tokens,
"spinner_svg": spinner_svg,
"default_title": default_title,
**template_kwargs,
}

Expand Down
Loading
Loading