Skip to content
Open
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
1 change: 1 addition & 0 deletions superset-frontend/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export const DEFAULT_COMMON_BOOTSTRAP_DATA: CommonBootstrapData = {
},
extra_categorical_color_schemes: [],
extra_sequential_color_schemes: [],
extra_theme_tokens: [],
theme: {
default: {},
dark: {},
Expand Down
23 changes: 23 additions & 0 deletions superset-frontend/src/theme/utils/antdTokenNames.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ import {
getAllValidTokenNames,
} from './antdTokenNames';

// Simulate a deployment that registered a custom token via EXTRA_THEME_TOKENS
// (shipped in the common bootstrap payload).
jest.mock('src/utils/getBootstrapData', () => ({
__esModule: true,
default: () => ({
common: { extra_theme_tokens: ['customDeploymentToken'] },
}),
}));

test('isValidTokenName recognizes standard Ant Design tokens', () => {
expect(isValidTokenName('colorPrimary')).toBe(true);
expect(isValidTokenName('fontSize')).toBe(true);
Expand Down Expand Up @@ -141,6 +150,20 @@ test('dashboard tile tokens are recognized as valid Superset custom tokens', ()
});
});

test('isValidTokenName recognizes deployment-registered EXTRA_THEME_TOKENS', () => {
expect(isValidTokenName('customDeploymentToken')).toBe(true);
});

test('isSupersetCustomToken treats EXTRA_THEME_TOKENS as custom, not Ant Design', () => {
expect(isSupersetCustomToken('customDeploymentToken')).toBe(true);
});

test('getAllValidTokenNames lists EXTRA_THEME_TOKENS under supersetTokens', () => {
const result = getAllValidTokenNames();
expect(result.supersetTokens).toContain('customDeploymentToken');
expect(result.antdTokens).not.toContain('customDeploymentToken');
});

test('label variant tokens are recognized as valid Superset custom tokens', () => {
const labelTokens = [
// Published/Draft
Expand Down
31 changes: 28 additions & 3 deletions superset-frontend/src/theme/utils/antdTokenNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,25 @@
* under the License.
*/
import { theme } from 'antd';
import getBootstrapData from 'src/utils/getBootstrapData';

/**
* Custom token names registered by the deployment via the EXTRA_THEME_TOKENS
* config (shipped in the common bootstrap payload). Lets deployments and plugins
* validate their own theme tokens without forking this list.
*
* Snapshotted once so the cached valid-token set (isValidTokenName) and the live
* reads (isSupersetCustomToken, getAllValidTokenNames) all derive from the same
* list, rather than depending on getBootstrapData's memoization to stay in sync.
*/
let extraThemeTokensCache: readonly string[] | undefined;
function getExtraThemeTokens(): readonly string[] {
if (extraThemeTokensCache === undefined) {
extraThemeTokensCache =
getBootstrapData()?.common?.extra_theme_tokens ?? [];
}
return extraThemeTokensCache;
}

/**
* Superset-specific custom tokens that extend Ant Design's token system.
Expand Down Expand Up @@ -125,10 +144,11 @@ function getValidTokenNames(): Set<string> {
const antdTokens = theme.getDesignToken();
const antdTokenNames = Object.keys(antdTokens);

// Combine with Superset custom tokens
// Combine with Superset custom tokens + deployment-registered extras
validTokenNamesCache = new Set([
...antdTokenNames,
...SUPERSET_CUSTOM_TOKENS,
...getExtraThemeTokens(),
]);
}
return validTokenNamesCache;
Expand All @@ -149,7 +169,10 @@ export function isValidTokenName(tokenName: string): boolean {
* @returns true if it's a Superset-specific token
*/
export function isSupersetCustomToken(tokenName: string): boolean {
return SUPERSET_CUSTOM_TOKENS.has(tokenName);
return (
SUPERSET_CUSTOM_TOKENS.has(tokenName) ||
getExtraThemeTokens().includes(tokenName)
);
}

/**
Expand All @@ -165,7 +188,9 @@ export function getAllValidTokenNames(): {
const antdTokens = Array.from(allTokens).filter(
t => !isSupersetCustomToken(t),
);
const supersetTokens: string[] = Array.from(SUPERSET_CUSTOM_TOKENS);
const supersetTokens: string[] = Array.from(
new Set([...SUPERSET_CUSTOM_TOKENS, ...getExtraThemeTokens()]),
);

return {
antdTokens,
Expand Down
1 change: 1 addition & 0 deletions superset-frontend/src/types/bootstrapTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export interface CommonBootstrapData {
language_pack: LanguagePack;
extra_categorical_color_schemes: ColorSchemeConfig[];
extra_sequential_color_schemes: SequentialSchemeConfig[];
extra_theme_tokens: string[];
theme: BootstrapThemeDataConfig;
menu_data: MenuData;
d3_format: Partial<FormatLocaleDefinition>;
Expand Down
6 changes: 6 additions & 0 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,12 @@ class D3TimeFormat(TypedDict, total=False):
# This is merely a default
EXTRA_CATEGORICAL_COLOR_SCHEMES: list[dict[str, Any]] = []

# EXTRA_THEME_TOKENS lets a deployment register additional custom theme token
# names so they validate cleanly in the theme editor instead of being flagged as
# unknown Ant Design tokens. Deployments and plugins that consume their own
# tokens via useTheme add them here. This is merely a default.
EXTRA_THEME_TOKENS: list[str] = []

# -----------------------------------------------------------------------------
# Theme System Configuration
# -----------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,7 @@ def cached_common_bootstrap_data( # pylint: disable=unused-argument
"extra_categorical_color_schemes": app.config[
"EXTRA_CATEGORICAL_COLOR_SCHEMES"
],
"extra_theme_tokens": app.config["EXTRA_THEME_TOKENS"],
"menu_data": menu_data(g.user),
"pdf_compression_level": app.config["PDF_COMPRESSION_LEVEL"],
"user_subject_id": _get_user_subject_id(user_id),
Expand Down
Loading