Address review feedback for feature flag isolation
Deduplicate the plugin/module feature flag registration loops and distinguish the error source (Plugin vs Module). Treat FEATURE_FLAG_INVALID as a warning in frontend-defaults. Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-defaults': patch
|
||||
---
|
||||
|
||||
Invalid feature flag declarations are now treated as warnings rather than errors, letting the app load normally.
|
||||
@@ -177,6 +177,8 @@ describe('registerFeatureFlagDeclarationsInHolder', () => {
|
||||
code: 'FEATURE_FLAG_INVALID',
|
||||
context: { pluginId: 'my-plugin', flagName: 'mod/invalid' },
|
||||
});
|
||||
expect(errors[0].message).toContain("Module for plugin 'my-plugin'");
|
||||
expect(errors[0].message).toContain("'mod/invalid'");
|
||||
});
|
||||
|
||||
it('should isolate non-validation errors thrown by registerFlag', () => {
|
||||
|
||||
@@ -183,28 +183,22 @@ function registerFeatureFlagDeclarations(
|
||||
collector: ErrorCollector,
|
||||
) {
|
||||
for (const feature of features) {
|
||||
let pluginId: string | undefined;
|
||||
let flags: Array<{ name: string; description?: string }> | undefined;
|
||||
let source: string | undefined;
|
||||
|
||||
if (OpaqueFrontendPlugin.isType(feature)) {
|
||||
const pluginId = feature.id;
|
||||
for (const flag of OpaqueFrontendPlugin.toInternal(feature)
|
||||
.featureFlags) {
|
||||
try {
|
||||
featureFlagApi.registerFlag({
|
||||
name: flag.name,
|
||||
description: flag.description,
|
||||
pluginId,
|
||||
});
|
||||
} catch (error) {
|
||||
collector.report({
|
||||
code: 'FEATURE_FLAG_INVALID',
|
||||
message: `Plugin '${pluginId}' declared invalid feature flag '${flag.name}': ${error}`,
|
||||
context: { pluginId, flagName: flag.name, error: error as Error },
|
||||
});
|
||||
}
|
||||
}
|
||||
pluginId = feature.id;
|
||||
flags = OpaqueFrontendPlugin.toInternal(feature).featureFlags;
|
||||
source = 'Plugin';
|
||||
} else if (isInternalFrontendModule(feature)) {
|
||||
pluginId = feature.pluginId;
|
||||
flags = toInternalFrontendModule(feature).featureFlags;
|
||||
source = 'Module for plugin';
|
||||
}
|
||||
if (isInternalFrontendModule(feature)) {
|
||||
const pluginId = feature.pluginId;
|
||||
for (const flag of toInternalFrontendModule(feature).featureFlags) {
|
||||
|
||||
if (pluginId && flags && source) {
|
||||
for (const flag of flags) {
|
||||
try {
|
||||
featureFlagApi.registerFlag({
|
||||
name: flag.name,
|
||||
@@ -214,7 +208,7 @@ function registerFeatureFlagDeclarations(
|
||||
} catch (error) {
|
||||
collector.report({
|
||||
code: 'FEATURE_FLAG_INVALID',
|
||||
message: `Plugin '${pluginId}' declared invalid feature flag '${flag.name}': ${error}`,
|
||||
message: `${source} '${pluginId}' declared invalid feature flag '${flag.name}': ${error}`,
|
||||
context: { pluginId, flagName: flag.name, error: error as Error },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const DEFAULT_WARNING_CODES: Array<keyof AppErrorTypes> = [
|
||||
'EXTENSION_OUTPUT_IGNORED',
|
||||
'EXTENSION_BOOTSTRAP_PREDICATE_IGNORED',
|
||||
'EXTENSION_BOOTSTRAP_API_UNAVAILABLE',
|
||||
'FEATURE_FLAG_INVALID',
|
||||
];
|
||||
|
||||
function AppErrorItem(props: { error: AppError }): JSX.Element {
|
||||
|
||||
Reference in New Issue
Block a user