Merge pull request #25377 from backstage/freben/the-route-is-false

declaratively disable external routes
This commit is contained in:
Fredrik Adelöw
2024-06-24 16:32:07 +02:00
committed by GitHub
6 changed files with 139 additions and 27 deletions
+15
View File
@@ -0,0 +1,15 @@
---
'@backstage/core-app-api': minor
'@backstage/frontend-app-api': patch
---
Allow for the disabling of external routes through config, which was rendered impossible after the introduction of default targets.
```yaml
app:
routes:
bindings:
# This has the effect of removing the button for registering new
# catalog entities in the scaffolder template list view
scaffolder.registerComponent: false
```
@@ -194,4 +194,35 @@ describe('collectRouteIds', () => {
'test.extRef': extRef,
});
});
it('can disable external routes that have defaults', () => {
const source = createExternalRouteRef({
id: 'test',
defaultTarget: 'test.target1',
});
const target1 = createRouteRef({ id: 'test' });
const plugin = createPlugin({
id: 'test',
routes: { target1 },
externalRoutes: { source },
});
// resolves normally with no config
let result = resolveRouteBindings(() => {}, new MockConfigApi({}), [
plugin,
]);
expect(result.get(source)).toBe(target1);
// can be disabled
result = resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.source': false } } },
}),
[plugin],
);
expect(result.get(source)).toBe(undefined);
});
});
@@ -102,11 +102,12 @@ export function resolveRouteBindings(
const bindings = config
.getOptionalConfig('app.routes.bindings')
?.get<JsonObject>();
const disabledExternalRefs = new Set<ExternalRouteRef>();
if (bindings) {
for (const [externalRefId, targetRefId] of Object.entries(bindings)) {
if (typeof targetRefId !== 'string' || targetRefId === '') {
if (!isValidTargetRefId(targetRefId)) {
throw new Error(
`Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string`,
`Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,
);
}
@@ -116,23 +117,27 @@ export function resolveRouteBindings(
`Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,
);
}
if (result.has(externalRef)) {
continue;
}
const targetRef = routesById.routes.get(targetRefId);
if (!targetRef) {
throw new Error(
`Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`,
);
}
result.set(externalRef, targetRef);
if (targetRefId === false) {
disabledExternalRefs.add(externalRef);
result.delete(externalRef);
} else if (!result.has(externalRef)) {
const targetRef = routesById.routes.get(targetRefId);
if (!targetRef) {
throw new Error(
`Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`,
);
}
result.set(externalRef, targetRef);
}
}
}
// Finally fall back to attempting to map defaults, at lowest priority
for (const externalRef of routesById.externalRoutes.values()) {
if (!result.has(externalRef)) {
if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) {
const defaultRefId =
'getDefaultTarget' in externalRef
? (externalRef.getDefaultTarget as () => string | undefined)()
@@ -148,3 +153,15 @@ export function resolveRouteBindings(
return result;
}
function isValidTargetRefId(value: unknown): value is string | false {
if (value === false) {
return true;
}
if (typeof value === 'string' && value) {
return true;
}
return false;
}
+6 -1
View File
@@ -26,9 +26,14 @@ export interface Config {
routes?: {
/**
* Maps external route references to regular route references. Both the
* key and the value is expected to be on the form `<pluginId>.<routeId>`.
* If the value is `false`, the route will be disabled even if it has a
* default mapping.
*
* @deepVisibility frontend
*/
bindings?: { [externalRouteRefId: string]: string };
bindings?: { [externalRouteRefId: string]: string | false };
};
/**
@@ -161,4 +161,31 @@ describe('resolveRouteBindings', () => {
expect(result.get(source)).toBe(target2);
});
it('can disable external routes that have defaults', () => {
const source = createExternalRouteRef({ defaultTarget: 'target1' });
const target1 = createRouteRef();
const routesById = {
routes: new Map([['target1', target1]]),
externalRoutes: new Map([['source', source]]),
};
// resolves normally with no config
let result = resolveRouteBindings(
() => {},
new ConfigReader({}),
routesById,
);
expect(result.get(source)).toBe(target1);
// can be disabled
result = resolveRouteBindings(
() => {},
new ConfigReader({ app: { routes: { bindings: { source: false } } } }),
routesById,
);
expect(result.get(source)).toBe(undefined);
});
});
@@ -112,11 +112,12 @@ export function resolveRouteBindings(
const bindings = config
.getOptionalConfig('app.routes.bindings')
?.get<JsonObject>();
const disabledExternalRefs = new Set<ExternalRouteRef>();
if (bindings) {
for (const [externalRefId, targetRefId] of Object.entries(bindings)) {
if (typeof targetRefId !== 'string' || targetRefId === '') {
if (!isValidTargetRefId(targetRefId)) {
throw new Error(
`Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string`,
`Invalid config at app.routes.bindings['${externalRefId}'], value must be a non-empty string or false`,
);
}
@@ -126,23 +127,27 @@ export function resolveRouteBindings(
`Invalid config at app.routes.bindings, '${externalRefId}' is not a valid external route`,
);
}
if (result.has(externalRef)) {
continue;
}
const targetRef = routesById.routes.get(targetRefId);
if (!targetRef) {
throw new Error(
`Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`,
);
}
result.set(externalRef, targetRef);
if (targetRefId === false) {
disabledExternalRefs.add(externalRef);
result.delete(externalRef);
} else if (!result.has(externalRef)) {
const targetRef = routesById.routes.get(targetRefId);
if (!targetRef) {
throw new Error(
`Invalid config at app.routes.bindings['${externalRefId}'], '${targetRefId}' is not a valid route`,
);
}
result.set(externalRef, targetRef);
}
}
}
// Finally fall back to attempting to map defaults, at lowest priority
for (const externalRef of routesById.externalRoutes.values()) {
if (!result.has(externalRef)) {
if (!result.has(externalRef) && !disabledExternalRefs.has(externalRef)) {
const defaultRefId =
toInternalExternalRouteRef(externalRef).getDefaultTarget();
if (defaultRefId) {
@@ -156,3 +161,15 @@ export function resolveRouteBindings(
return result;
}
function isValidTargetRefId(value: unknown): value is string | false {
if (value === false) {
return true;
}
if (typeof value === 'string' && value) {
return true;
}
return false;
}