-
Notifications
You must be signed in to change notification settings - Fork 16k
feat(themes): add enhanced validation and error handling with fallback mechanisms #35349
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat(themes): add enhanced validation and error handling with fallback mechanisms #35349
Conversation
Based on your review schedule, I'll hold off on reviewing this PR until it's marked as ready for review. If you'd like me to take a look now, comment
|
95089e9
to
a66aea1
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #35349 +/- ##
===========================================
+ Coverage 0 71.86% +71.86%
===========================================
Files 0 589 +589
Lines 0 43607 +43607
Branches 0 4719 +4719
===========================================
+ Hits 0 31336 +31336
- Misses 0 11032 +11032
- Partials 0 1239 +1239
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review by Korbit AI
Korbit automatically attempts to detect when you fix issues in new commits.
Category | Issue | Status |
---|---|---|
Enhanced theme validation disabled by default ▹ view | 🧠 Not in standard | |
Repeated Default Object Structure ▹ view | 🧠 Not in standard | |
Duplicated Feature Flag Logic ▹ view | 🧠 Not in standard | |
Validation results lost on JSON errors ▹ view | 🧠 Not in scope | |
Inefficient Set recreation in color validation ▹ view | 🧠 Incorrect | |
Sequential API calls in theme fallback logic ▹ view | ||
Unnecessary validation hook execution ▹ view | 🧠 Incorrect | |
Repeated JSON parsing without memoization ▹ view | 🧠 Incorrect | |
Inefficient regex pattern creation ▹ view | 🧠 Not in standard | |
Validation Logic Not Separated from State Management ▹ view | 🧠 Not in standard |
Files scanned
File Path | Reviewed |
---|---|
superset-frontend/packages/superset-ui-core/src/utils/featureFlags.ts | ✅ |
superset-frontend/src/theme/hooks/useThemeValidation.ts | ✅ |
superset-frontend/src/features/themes/ThemeModal.tsx | ✅ |
superset-frontend/src/theme/utils/themeTokenValidation.ts | ✅ |
superset-frontend/src/theme/ThemeController.ts | ✅ |
superset/config.py | ✅ |
Explore our documentation to understand the languages and file types we support and the files we ignore.
Check out our docs on how you can make Korbit work best for you and your team.
"EMBEDDABLE_CHARTS": True, | ||
# Enhanced theme validation in theme editor with real-time token validation | ||
# and partial theme loading for invalid tokens | ||
"ENHANCED_THEME_VALIDATION": False, |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
const isEmptyTheme = (jsonData?: string) => { | ||
if (!jsonData?.trim()) return true; | ||
try { | ||
const parsed = JSON.parse(jsonData); | ||
// Check if it's an empty object or array | ||
return Object.keys(parsed).length === 0; | ||
} catch (e) { | ||
return false; // If it's not valid JSON, let other validation handle it | ||
} | ||
}; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
const themeValidation = useThemeValidation(currentTheme?.json_data || '', { | ||
enabled: !isReadOnly && Boolean(currentTheme?.json_data), | ||
themeName: currentTheme?.theme_name || 'Unknown Theme', | ||
}); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
const validate = () => { | ||
if (isReadOnly) { | ||
if (isReadOnly || !currentTheme) { | ||
setDisableSave(true); | ||
return; | ||
} | ||
|
||
if ( | ||
currentTheme?.theme_name.length && | ||
currentTheme?.json_data?.length && | ||
isValidJson(currentTheme.json_data) | ||
) { | ||
setDisableSave(false); | ||
const hasValidName = Boolean(currentTheme?.theme_name?.length); | ||
const hasValidJsonData = Boolean(currentTheme?.json_data?.length); | ||
const isValidJsonSyntax = isValidJson(currentTheme?.json_data); | ||
const isNotEmpty = !isEmptyTheme(currentTheme?.json_data); | ||
|
||
// Basic validation requirements | ||
const basicValidation = | ||
hasValidName && hasValidJsonData && isValidJsonSyntax && isNotEmpty; | ||
|
||
if (isEnhancedValidationEnabled && currentTheme) { | ||
// Enhanced validation: allow saving even with token warnings, but block on JSON syntax errors | ||
const enhancedValidation = basicValidation && !themeValidation.hasErrors; | ||
setDisableSave(!enhancedValidation); | ||
} else { | ||
setDisableSave(true); | ||
// Original validation logic | ||
setDisableSave(!basicValidation); | ||
} | ||
}; |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
const enhancedValidation = useMemo(() => { | ||
// Skip if basic validation is disabled or JSON has syntax errors | ||
if (!enabled || jsonAnnotations.length > 0 || !debouncedJsonValue?.trim()) { | ||
return { | ||
annotations: [], | ||
validTokenCount: 0, | ||
invalidTokenCount: 0, | ||
errorMessages: [], | ||
}; | ||
} | ||
|
||
// Only run enhanced validation if feature flag is enabled | ||
try { | ||
const isEnabled = isFeatureEnabled(FeatureFlag.EnhancedThemeValidation); | ||
if (!isEnabled) { | ||
return { | ||
annotations: [], | ||
validTokenCount: 0, | ||
invalidTokenCount: 0, | ||
errorMessages: [], | ||
}; | ||
} | ||
} catch (error) { | ||
// Feature flag check failed - assume disabled | ||
return { | ||
annotations: [], | ||
validTokenCount: 0, | ||
invalidTokenCount: 0, | ||
errorMessages: [], | ||
}; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
export function useIsEnhancedValidationEnabled(): boolean { | ||
return useMemo(() => { | ||
try { | ||
return isFeatureEnabled(FeatureFlag.EnhancedThemeValidation); | ||
} catch (error) { | ||
// Feature flag check failed - assume disabled | ||
return false; | ||
} | ||
}, []); | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
if (!enabled || jsonAnnotations.length > 0 || !debouncedJsonValue?.trim()) { | ||
return { | ||
annotations: [], | ||
validTokenCount: 0, | ||
invalidTokenCount: 0, | ||
errorMessages: [], | ||
}; | ||
} |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
const propertyPattern = new RegExp( | ||
`"${tokenName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}"\\s*:`, | ||
); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
const namedColors = new Set([ | ||
'transparent', | ||
'inherit', | ||
'currentColor', | ||
'initial', | ||
'unset', | ||
'black', | ||
'white', | ||
'red', | ||
'green', | ||
'blue', | ||
'yellow', | ||
'cyan', | ||
'magenta', | ||
'gray', | ||
'grey', | ||
'darkgray', | ||
'darkgrey', | ||
'lightgray', | ||
'lightgrey', | ||
'orange', | ||
'purple', | ||
'brown', | ||
'pink', | ||
'lime', | ||
'navy', | ||
'teal', | ||
'silver', | ||
'maroon', | ||
'olive', | ||
'aqua', | ||
'fuchsia', | ||
]); |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
// Try to fetch theme marked as system default (is_system_default=true) | ||
const defaultResponse = await fetch( | ||
'/api/v1/theme/?q=(filters:!((col:is_system_default,opr:eq,value:!t)))', | ||
); | ||
if (defaultResponse.ok) { | ||
const data = await defaultResponse.json(); | ||
if (data.result?.length > 0) { | ||
const themeConfig = JSON.parse(data.result[0].json_data); | ||
if (themeConfig && typeof themeConfig === 'object') { | ||
return themeConfig; | ||
} | ||
} | ||
} | ||
|
||
// Fallback: Try to fetch system theme named 'THEME_DEFAULT' | ||
const fallbackResponse = await fetch( | ||
'/api/v1/theme/?q=(filters:!((col:theme_name,opr:eq,value:THEME_DEFAULT),(col:is_system,opr:eq,value:!t)))', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sequential API calls in theme fallback logic 
Tell me more
What is the issue?
The fallback mechanism makes two sequential API calls even when the first call succeeds but returns an empty result set, creating unnecessary network overhead.
Why this matters
When the first API call returns successfully but with no matching themes, the code still makes a second API call. This doubles the network latency and server load during theme recovery scenarios, which are already error conditions that should be handled efficiently.
Suggested change ∙ Feature Preview
Combine both queries into a single API call using OR logic, or add an early return when the first call indicates no themes exist:
// Single API call with OR condition for both system default and named fallback
const response = await fetch(
'/api/v1/theme/?q=(filters:!((col:is_system_default,opr:eq,value:!t),(col:theme_name,opr:eq,value:THEME_DEFAULT)))',
);
if (response.ok) {
const data = await response.json();
if (data.result?.length > 0) {
// Prioritize system default if available
const systemDefault = data.result.find(theme => theme.is_system_default);
const theme = systemDefault || data.result[0];
const themeConfig = JSON.parse(theme.json_data);
if (themeConfig && typeof themeConfig === 'object') {
return themeConfig;
}
}
}
Provide feedback to improve future suggestions
💬 Looking for more details? Reply to this comment to chat with Korbit.
Do we really need the feature flag? I think everyone is in favor of more validation :) Seems like it should be default behavior. |
My thought process was because is not a small iteration and also the rules could require a little more refinement it was safer to put it behind a FF but if everyone agrees I can remove it. |
4204948
to
61323a0
Compare
I really appreciate the effort to improve theme validation, but I’m concerned about the long-term maintainability of this approach. Here are my thoughts:
By introducing custom validators (
So I think we should keep it simple and only have structural validations:
So the To sum up, I think we should just simplify to:
This gives users clear feedback while avoiding the complexity of duplicating What do you think? Happy to discuss trade-offs! |
SUMMARY
Implements enhanced theme validation and error handling for Superset's theme system with comprehensive fallback mechanisms. This PR introduces:
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TESTING INSTRUCTIONS
To test live token validation:
- Set
ENHANCED_THEME_VALIDATION: True
in feature flagsADDITIONAL INFORMATION