{core,frontend}-app-api: fix route binding prio + disable through code
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/core-app-api': minor
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Added the ability to explicitly disable routes through the `bindRoutes` option by passing `false` as the route target. This also fixes a bug where route bindings in config were incorrectly prioritized above the ones in code in certain situations.
|
||||
@@ -91,19 +91,29 @@ There could be situations where you would like to disable the
|
||||
|
||||

|
||||
|
||||
To do so, you will un-register / remove the `catalogImportPlugin.routes.importPage`
|
||||
from `backstage/packages/app/src/App.tsx`:
|
||||
To do so, you need to explicitly disable the default route binding from the `scaffolderPlugin.registerComponent` to the Catalog Import page.
|
||||
|
||||
This can be done in `backstage/packages/app/src/App.tsx`:
|
||||
|
||||
```diff
|
||||
const app = createApp({
|
||||
apis,
|
||||
bindRoutes({ bind }) {
|
||||
- bind(scaffolderPlugin.externalRoutes, {
|
||||
bind(scaffolderPlugin.externalRoutes, {
|
||||
+ registerComponent: false,
|
||||
- registerComponent: catalogImportPlugin.routes.importPage,
|
||||
- });
|
||||
bind(orgPlugin.externalRoutes, {
|
||||
catalogIndex: catalogPlugin.routes.catalogIndex,
|
||||
viewTechDoc: techdocsPlugin.routes.docRoot,
|
||||
});
|
||||
})
|
||||
```
|
||||
|
||||
OR in `app-config.yaml`:
|
||||
|
||||
```yaml
|
||||
app:
|
||||
routes:
|
||||
bindings:
|
||||
scaffolder.registerComponent: false
|
||||
```
|
||||
|
||||
After the change, you should no longer see the button.
|
||||
|
||||
@@ -251,6 +251,8 @@ app:
|
||||
# point to the Catalog details page when the Scaffolder component details ref is used
|
||||
# highlight-next-line
|
||||
scaffolder.componentDetails: catalog.details
|
||||
# explicitly disable the default route binding from the scaffolder to the catalog import page
|
||||
scaffolder.registerComponent: false
|
||||
```
|
||||
|
||||
We also have the ability to express this in code as an option to `createApp`, but you of course only need to use one of these two methods:
|
||||
@@ -268,6 +270,7 @@ const app = createApp({
|
||||
});
|
||||
bind(scaffolder.externalRoutes, {
|
||||
componentDetails: catalog.routes.details,
|
||||
registerComponent: false,
|
||||
});
|
||||
},
|
||||
// highlight-end
|
||||
|
||||
@@ -75,6 +75,55 @@ describe('resolveRouteBindings', () => {
|
||||
expect(result.get(mySource)).toBe(myTarget);
|
||||
});
|
||||
|
||||
it('prioritizes callback routes over config', () => {
|
||||
const mySource = createExternalRouteRef({ id: 'test', optional: true });
|
||||
const myTarget = createRouteRef({ id: 'test' });
|
||||
|
||||
expect(
|
||||
resolveRouteBindings(
|
||||
({ bind }) => {
|
||||
bind({ mySource }, { mySource: false });
|
||||
},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': 'myTarget' } } },
|
||||
}),
|
||||
[
|
||||
createPlugin({
|
||||
id: 'test',
|
||||
routes: {
|
||||
myTarget,
|
||||
},
|
||||
externalRoutes: {
|
||||
mySource,
|
||||
},
|
||||
}),
|
||||
],
|
||||
).get(mySource),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(
|
||||
resolveRouteBindings(
|
||||
({ bind }) => {
|
||||
bind({ mySource }, { mySource: myTarget });
|
||||
},
|
||||
new MockConfigApi({
|
||||
app: { routes: { bindings: { 'test.mySource': false } } },
|
||||
}),
|
||||
[
|
||||
createPlugin({
|
||||
id: 'test',
|
||||
routes: {
|
||||
myTarget,
|
||||
},
|
||||
externalRoutes: {
|
||||
mySource,
|
||||
},
|
||||
}),
|
||||
],
|
||||
).get(mySource),
|
||||
).toBe(myTarget);
|
||||
});
|
||||
|
||||
it('throws on invalid config', () => {
|
||||
expect(() =>
|
||||
resolveRouteBindings(
|
||||
|
||||
@@ -73,6 +73,7 @@ export function resolveRouteBindings(
|
||||
) {
|
||||
const routesById = collectRouteIds(plugins);
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
const disabledExternalRefs = new Set<ExternalRouteRef>();
|
||||
|
||||
// Perform callback bindings first with highest priority
|
||||
if (bindRoutes) {
|
||||
@@ -87,11 +88,15 @@ export function resolveRouteBindings(
|
||||
}
|
||||
if (!value && !externalRoute.optional) {
|
||||
throw new Error(
|
||||
`External route ${key} is required but was undefined`,
|
||||
`External route ${key} is required but was ${
|
||||
value === false ? 'disabled' : 'not provided'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (value) {
|
||||
result.set(externalRoute, value);
|
||||
} else if (value === false) {
|
||||
disabledExternalRefs.add(externalRoute);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -102,7 +107,6 @@ 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 (!isValidTargetRefId(targetRefId)) {
|
||||
@@ -118,11 +122,14 @@ export function resolveRouteBindings(
|
||||
);
|
||||
}
|
||||
|
||||
// Skip if binding was already defined in code
|
||||
if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetRefId === false) {
|
||||
disabledExternalRefs.add(externalRef);
|
||||
|
||||
result.delete(externalRef);
|
||||
} else if (!result.has(externalRef)) {
|
||||
} else {
|
||||
const targetRef = routesById.routes.get(targetRefId);
|
||||
if (!targetRef) {
|
||||
throw new Error(
|
||||
|
||||
@@ -160,7 +160,7 @@ type TargetRouteMap<
|
||||
infer Params,
|
||||
any
|
||||
>
|
||||
? RouteRef<Params> | SubRouteRef<Params>
|
||||
? RouteRef<Params> | SubRouteRef<Params> | false
|
||||
: never;
|
||||
};
|
||||
|
||||
|
||||
@@ -69,6 +69,41 @@ describe('resolveRouteBindings', () => {
|
||||
expect(result.get(mySource)).toBe(myTarget);
|
||||
});
|
||||
|
||||
it('prioritizes callback routes over config', () => {
|
||||
const mySource = createExternalRouteRef();
|
||||
const myTarget = createRouteRef();
|
||||
|
||||
expect(
|
||||
resolveRouteBindings(
|
||||
({ bind }) => {
|
||||
bind({ mySource }, { mySource: false });
|
||||
},
|
||||
new ConfigReader({
|
||||
app: { routes: { bindings: { mySource: 'myTarget' } } },
|
||||
}),
|
||||
{
|
||||
routes: new Map([['myTarget', myTarget]]),
|
||||
externalRoutes: new Map([['mySource', mySource]]),
|
||||
},
|
||||
).get(mySource),
|
||||
).toBe(undefined);
|
||||
|
||||
expect(
|
||||
resolveRouteBindings(
|
||||
({ bind }) => {
|
||||
bind({ mySource }, { mySource: myTarget });
|
||||
},
|
||||
new ConfigReader({
|
||||
app: { routes: { bindings: { mySource: false } } },
|
||||
}),
|
||||
{
|
||||
routes: new Map([['myTarget', myTarget]]),
|
||||
externalRoutes: new Map([['mySource', mySource]]),
|
||||
},
|
||||
).get(mySource),
|
||||
).toBe(myTarget);
|
||||
});
|
||||
|
||||
it('throws on invalid config', () => {
|
||||
expect(() =>
|
||||
resolveRouteBindings(
|
||||
|
||||
@@ -55,7 +55,7 @@ type TargetRouteMap<
|
||||
[name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef<
|
||||
infer Params
|
||||
>
|
||||
? RouteRef<Params> | SubRouteRef<Params>
|
||||
? RouteRef<Params> | SubRouteRef<Params> | false
|
||||
: never;
|
||||
};
|
||||
|
||||
@@ -82,6 +82,7 @@ export function resolveRouteBindings(
|
||||
routesById: RouteRefsById,
|
||||
): Map<ExternalRouteRef, RouteRef | SubRouteRef> {
|
||||
const result = new Map<ExternalRouteRef, RouteRef | SubRouteRef>();
|
||||
const disabledExternalRefs = new Set<ExternalRouteRef>();
|
||||
|
||||
// Perform callback bindings first with highest priority
|
||||
if (bindRoutes) {
|
||||
@@ -96,6 +97,8 @@ export function resolveRouteBindings(
|
||||
}
|
||||
if (value) {
|
||||
result.set(externalRoute, value);
|
||||
} else if (value === false) {
|
||||
disabledExternalRefs.add(externalRoute);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -106,7 +109,6 @@ 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 (!isValidTargetRefId(targetRefId)) {
|
||||
@@ -122,11 +124,14 @@ export function resolveRouteBindings(
|
||||
);
|
||||
}
|
||||
|
||||
// Skip if binding was already defined in code
|
||||
if (result.has(externalRef) || disabledExternalRefs.has(externalRef)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (targetRefId === false) {
|
||||
disabledExternalRefs.add(externalRef);
|
||||
|
||||
result.delete(externalRef);
|
||||
} else if (!result.has(externalRef)) {
|
||||
} else {
|
||||
const targetRef = routesById.routes.get(targetRefId);
|
||||
if (!targetRef) {
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user