frontend-plugin-api: refactor extension "at" option to "attachTo"
Co-authored-by: Camila Belo <camilaibs@gmail.com> Co-authored-by: Fredrik Adelöw <freben@gmail.com> Co-authored-by: Vincenzo Scamporlino <vincenzos@spotify.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': minor
|
||||
---
|
||||
|
||||
Extension attachment point is now configured via `attachTo: { id, input }` instead of `at: 'id/input'`.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-search-react': patch
|
||||
'@backstage/plugin-graphiql': patch
|
||||
---
|
||||
|
||||
Updated `/alpha` exports to use new `attachTo` option.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Updates for `at` -> `attachTo` refactor.
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
|
||||
export const Core = createExtension({
|
||||
id: 'core',
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
inputs: {
|
||||
apis: createExtensionInput({
|
||||
api: coreExtensionData.apiFactory,
|
||||
|
||||
@@ -24,7 +24,7 @@ import { SidebarPage } from '@backstage/core-components';
|
||||
|
||||
export const CoreLayout = createExtension({
|
||||
id: 'core.layout',
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
inputs: {
|
||||
nav: createExtensionInput(
|
||||
{
|
||||
|
||||
@@ -73,7 +73,7 @@ const SidebarNavItem = (props: NavTarget) => {
|
||||
|
||||
export const CoreNav = createExtension({
|
||||
id: 'core.nav',
|
||||
at: 'core.layout/nav',
|
||||
attachTo: { id: 'core.layout', input: 'nav' },
|
||||
inputs: {
|
||||
items: createExtensionInput({
|
||||
target: coreExtensionData.navTarget,
|
||||
|
||||
@@ -24,7 +24,7 @@ import { useRoutes } from 'react-router-dom';
|
||||
|
||||
export const CoreRoutes = createExtension({
|
||||
id: 'core.routes',
|
||||
at: 'core.layout/content',
|
||||
attachTo: { id: 'core.layout', input: 'content' },
|
||||
inputs: {
|
||||
routes: createExtensionInput({
|
||||
path: coreExtensionData.routePath,
|
||||
|
||||
@@ -40,13 +40,15 @@ const refOrder = [ref1, ref2, ref3, ref4, ref5];
|
||||
|
||||
function createTestExtension(options: {
|
||||
id: string;
|
||||
at?: string;
|
||||
parent?: string;
|
||||
path?: string;
|
||||
routeRef?: RouteRef;
|
||||
}) {
|
||||
return createExtension({
|
||||
id: options.id,
|
||||
at: options.at ?? 'core.routes/children',
|
||||
attachTo: options.parent
|
||||
? { id: options.parent, input: 'children' }
|
||||
: { id: 'core.routes', input: 'children' },
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
path: coreExtensionData.routePath.optional(),
|
||||
@@ -126,13 +128,13 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
at: 'page1/children',
|
||||
parent: 'page1',
|
||||
path: 'bar/:id',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
at: 'page2/children',
|
||||
parent: 'page2',
|
||||
path: 'baz',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
@@ -143,7 +145,7 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
at: 'page1/children',
|
||||
parent: 'page1',
|
||||
path: 'blop',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
@@ -194,7 +196,7 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
at: 'page1/children',
|
||||
parent: 'page1',
|
||||
path: 'bar/:id',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
@@ -205,13 +207,13 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
at: 'page3/children',
|
||||
parent: 'page3',
|
||||
path: 'divsoup',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
at: 'page3/children',
|
||||
parent: 'page3',
|
||||
path: 'blop',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
@@ -242,7 +244,7 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
at: 'page1/children',
|
||||
parent: 'page1',
|
||||
path: '/bar/:id',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
@@ -253,13 +255,13 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
at: 'page3/children',
|
||||
parent: 'page3',
|
||||
path: '/divsoup',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
at: 'page3/children',
|
||||
parent: 'page3',
|
||||
path: '/blop',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
@@ -289,16 +291,16 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
at: 'foo/children',
|
||||
parent: 'foo',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'fooChild',
|
||||
at: 'foo/children',
|
||||
parent: 'foo',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
at: 'fooChild/children',
|
||||
parent: 'fooChild',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
@@ -311,17 +313,17 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3Child',
|
||||
at: 'page3/children',
|
||||
parent: 'page3',
|
||||
path: '',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
at: 'page3Child/children',
|
||||
parent: 'page3Child',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
at: 'page4/children',
|
||||
parent: 'page4',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
]);
|
||||
@@ -361,29 +363,29 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1Child',
|
||||
at: 'page1/children',
|
||||
parent: 'page1',
|
||||
path: 'bar',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
at: 'page1Child/children',
|
||||
parent: 'page1Child',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
at: 'page2/children',
|
||||
parent: 'page2',
|
||||
path: 'baz',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
at: 'page3/children',
|
||||
parent: 'page3',
|
||||
path: '/blop',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page5',
|
||||
at: 'page2/children',
|
||||
parent: 'page2',
|
||||
routeRef: ref5,
|
||||
}),
|
||||
]);
|
||||
@@ -445,30 +447,30 @@ describe('discovery', () => {
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page1',
|
||||
at: 'r/children',
|
||||
parent: 'r',
|
||||
path: 'x',
|
||||
routeRef: ref1,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'y',
|
||||
path: 'y',
|
||||
at: 'r/children',
|
||||
parent: 'r',
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page2',
|
||||
at: 'y/children',
|
||||
parent: 'y',
|
||||
path: '1',
|
||||
routeRef: ref2,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page3',
|
||||
at: 'page2/children',
|
||||
parent: 'page2',
|
||||
path: 'a',
|
||||
routeRef: ref3,
|
||||
}),
|
||||
createTestExtension({
|
||||
id: 'page4',
|
||||
at: 'page2/children',
|
||||
parent: 'page2',
|
||||
path: 'b',
|
||||
routeRef: ref4,
|
||||
}),
|
||||
|
||||
@@ -32,9 +32,7 @@ describe('createInstances', () => {
|
||||
app: {
|
||||
extensions: [
|
||||
{
|
||||
root: {
|
||||
at: '',
|
||||
},
|
||||
root: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -58,7 +56,7 @@ describe('createInstances', () => {
|
||||
extensions: [
|
||||
createExtension({
|
||||
id: 'root',
|
||||
at: 'core.routes/route',
|
||||
attachTo: { id: 'core.routes', input: 'route' },
|
||||
inputs: {},
|
||||
output: {},
|
||||
factory() {},
|
||||
|
||||
@@ -202,8 +202,8 @@ export function createInstances(options: {
|
||||
Map<string, ExtensionInstanceParameters[]>
|
||||
>();
|
||||
for (const instanceParams of extensionParams) {
|
||||
const [extensionId, pointId = 'default'] = instanceParams.at.split('/');
|
||||
|
||||
const extensionId = instanceParams.attachTo.id;
|
||||
const pointId = instanceParams.attachTo.input;
|
||||
let pointMap = attachmentMap.get(extensionId);
|
||||
if (!pointMap) {
|
||||
pointMap = new Map();
|
||||
|
||||
@@ -28,7 +28,7 @@ const inputMirrorDataRef = createExtensionDataRef<unknown>('mirror');
|
||||
|
||||
const simpleExtension = createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: {
|
||||
test: testDataRef,
|
||||
other: otherDataRef.optional(),
|
||||
@@ -101,7 +101,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
inputs: {
|
||||
optionalSingletonPresent: createExtensionInput(
|
||||
{
|
||||
@@ -166,7 +166,7 @@ describe('createExtensionInstance', () => {
|
||||
config: { other: 'not-a-number' },
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: {},
|
||||
factory() {
|
||||
const error = new Error('NOPE');
|
||||
@@ -188,7 +188,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: {
|
||||
test1: testDataRef,
|
||||
test2: testDataRef,
|
||||
@@ -211,7 +211,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
output: {
|
||||
test: testDataRef,
|
||||
},
|
||||
@@ -232,7 +232,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
inputs: {
|
||||
singleton: createExtensionInput(
|
||||
{
|
||||
@@ -273,7 +273,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
inputs: {
|
||||
singleton: createExtensionInput(
|
||||
{
|
||||
@@ -314,7 +314,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
inputs: {
|
||||
singleton: createExtensionInput(
|
||||
{
|
||||
@@ -350,7 +350,7 @@ describe('createExtensionInstance', () => {
|
||||
config: undefined,
|
||||
extension: createExtension({
|
||||
id: 'core.test',
|
||||
at: 'ignored',
|
||||
attachTo: { id: 'ignored', input: 'ignored' },
|
||||
inputs: {
|
||||
singleton: createExtensionInput(
|
||||
{
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
function makeExt(id: string, status: 'disabled' | 'enabled' = 'enabled') {
|
||||
return {
|
||||
id,
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
disabled: status === 'disabled',
|
||||
} as Extension<unknown>;
|
||||
}
|
||||
@@ -52,8 +52,8 @@ describe('mergeExtensionParameters', () => {
|
||||
parameters: [],
|
||||
}),
|
||||
).toEqual([
|
||||
{ extension: a, at: 'root' },
|
||||
{ extension: b, at: 'root' },
|
||||
{ extension: a, attachTo: { id: 'root', input: 'default' } },
|
||||
{ extension: b, attachTo: { id: 'root', input: 'default' } },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -68,13 +68,17 @@ describe('mergeExtensionParameters', () => {
|
||||
parameters: [
|
||||
{
|
||||
id: 'b',
|
||||
at: 'derp',
|
||||
attachTo: { id: 'derp', input: 'default' },
|
||||
},
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
{ extension: a, at: 'root', source: pluginA },
|
||||
{ extension: b, at: 'derp' },
|
||||
{
|
||||
extension: a,
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
source: pluginA,
|
||||
},
|
||||
{ extension: b, attachTo: { id: 'derp', input: 'default' } },
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -102,8 +106,18 @@ describe('mergeExtensionParameters', () => {
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
{ extension: a, at: 'root', source: plugin, config: { foo: { bar: 1 } } },
|
||||
{ extension: b, at: 'root', source: plugin, config: { foo: { qux: 3 } } },
|
||||
{
|
||||
extension: a,
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
source: plugin,
|
||||
config: { foo: { bar: 1 } },
|
||||
},
|
||||
{
|
||||
extension: b,
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
source: plugin,
|
||||
config: { foo: { qux: 3 } },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -126,8 +140,8 @@ describe('mergeExtensionParameters', () => {
|
||||
],
|
||||
}),
|
||||
).toEqual([
|
||||
{ extension: b, at: 'root' },
|
||||
{ extension: a, at: 'root' },
|
||||
{ extension: b, attachTo: { id: 'root', input: 'default' } },
|
||||
{ extension: a, attachTo: { id: 'root', input: 'default' } },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -315,14 +329,18 @@ describe('expandShorthandExtensionParameters', () => {
|
||||
expect(() =>
|
||||
run({ 'core.router': { id: 'some.id' } }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'at', 'disabled', 'config'"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('supports object at', () => {
|
||||
expect(run({ 'core.router': { at: 'other.root/inputs' } })).toEqual({
|
||||
it('supports object attachTo', () => {
|
||||
expect(
|
||||
run({
|
||||
'core.router': { attachTo: { id: 'other.root', input: 'inputs' } },
|
||||
}),
|
||||
).toEqual({
|
||||
id: 'core.router',
|
||||
at: 'other.root/inputs',
|
||||
attachTo: { id: 'other.root', input: 'inputs' },
|
||||
});
|
||||
expect(() =>
|
||||
run({
|
||||
@@ -331,7 +349,7 @@ describe('expandShorthandExtensionParameters', () => {
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'at', 'disabled', 'config'"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].id, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -369,7 +387,7 @@ describe('expandShorthandExtensionParameters', () => {
|
||||
expect(() =>
|
||||
run({ 'core.router': { foo: { settings: true } } }),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].foo, unknown parameter; expected one of 'at', 'disabled', 'config'"`,
|
||||
`"Invalid extension configuration at app.extensions[1][core.router].foo, unknown parameter; expected one of 'attachTo', 'disabled', 'config'"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,12 +20,12 @@ import { JsonValue } from '@backstage/types';
|
||||
|
||||
export interface ExtensionParameters {
|
||||
id: string;
|
||||
at?: string;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
config?: unknown;
|
||||
}
|
||||
|
||||
const knownExtensionParameters = ['at', 'disabled', 'config'];
|
||||
const knownExtensionParameters = ['attachTo', 'disabled', 'config'];
|
||||
|
||||
// Since we'll never merge arrays in config the config reader context
|
||||
// isn't too much of a help. Fall back to manual config reading logic
|
||||
@@ -143,15 +143,33 @@ export function expandShorthandExtensionParameters(
|
||||
throw new Error(errorMsg('value must be a boolean or object', id));
|
||||
}
|
||||
|
||||
const at = value.at;
|
||||
const attachTo = value.attachTo as { id: string; input: string } | undefined;
|
||||
const disabled = value.disabled;
|
||||
const config = value.config;
|
||||
|
||||
if (at !== undefined && typeof at !== 'string') {
|
||||
throw new Error(errorMsg('must be a string', id, 'at'));
|
||||
} else if (disabled !== undefined && typeof disabled !== 'boolean') {
|
||||
if (attachTo !== undefined) {
|
||||
if (
|
||||
attachTo === null ||
|
||||
typeof attachTo !== 'object' ||
|
||||
Array.isArray(attachTo)
|
||||
) {
|
||||
throw new Error(errorMsg('must be an object', id, 'attachTo'));
|
||||
}
|
||||
if (typeof attachTo.id !== 'string' || attachTo.id === '') {
|
||||
throw new Error(
|
||||
errorMsg('must be a non-empty string', id, 'attachTo.id'),
|
||||
);
|
||||
}
|
||||
if (typeof attachTo.input !== 'string' || attachTo.input === '') {
|
||||
throw new Error(
|
||||
errorMsg('must be a non-empty string', id, 'attachTo.input'),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (disabled !== undefined && typeof disabled !== 'boolean') {
|
||||
throw new Error(errorMsg('must be a boolean', id, 'disabled'));
|
||||
} else if (
|
||||
}
|
||||
if (
|
||||
config !== undefined &&
|
||||
(typeof config !== 'object' || config === null || Array.isArray(config))
|
||||
) {
|
||||
@@ -175,7 +193,7 @@ export function expandShorthandExtensionParameters(
|
||||
|
||||
return {
|
||||
id,
|
||||
at,
|
||||
attachTo,
|
||||
disabled,
|
||||
config,
|
||||
};
|
||||
@@ -184,7 +202,7 @@ export function expandShorthandExtensionParameters(
|
||||
export interface ExtensionInstanceParameters {
|
||||
extension: Extension<unknown>;
|
||||
source?: BackstagePlugin;
|
||||
at: string;
|
||||
attachTo: { id: string; input: string };
|
||||
config?: unknown;
|
||||
}
|
||||
|
||||
@@ -217,7 +235,7 @@ export function mergeExtensionParameters(options: {
|
||||
extension,
|
||||
params: {
|
||||
source,
|
||||
at: extension.at,
|
||||
attachTo: extension.attachTo,
|
||||
disabled: extension.disabled,
|
||||
config: undefined as unknown,
|
||||
},
|
||||
@@ -226,7 +244,7 @@ export function mergeExtensionParameters(options: {
|
||||
extension,
|
||||
params: {
|
||||
source: undefined,
|
||||
at: extension.at,
|
||||
attachTo: extension.attachTo,
|
||||
disabled: extension.disabled,
|
||||
config: undefined as unknown,
|
||||
},
|
||||
@@ -283,8 +301,8 @@ export function mergeExtensionParameters(options: {
|
||||
);
|
||||
if (existingIndex !== -1) {
|
||||
const existing = overrides[existingIndex];
|
||||
if (overrideParam.at) {
|
||||
existing.params.at = overrideParam.at;
|
||||
if (overrideParam.attachTo) {
|
||||
existing.params.attachTo = overrideParam.attachTo;
|
||||
}
|
||||
if (overrideParam.config) {
|
||||
// TODO: merge config?
|
||||
@@ -309,7 +327,7 @@ export function mergeExtensionParameters(options: {
|
||||
.filter(override => !override.params.disabled)
|
||||
.map(param => ({
|
||||
extension: param.extension,
|
||||
at: param.params.at,
|
||||
attachTo: param.params.attachTo,
|
||||
source: param.params.source,
|
||||
config: param.params.config,
|
||||
}));
|
||||
|
||||
@@ -136,7 +136,10 @@ export interface CreateExtensionOptions<
|
||||
TConfig,
|
||||
> {
|
||||
// (undocumented)
|
||||
at: string;
|
||||
attachTo: {
|
||||
id: string;
|
||||
input: string;
|
||||
};
|
||||
// (undocumented)
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
// (undocumented)
|
||||
@@ -182,7 +185,10 @@ export function createPageExtension<
|
||||
}
|
||||
) & {
|
||||
id: string;
|
||||
at?: string;
|
||||
attachTo?: {
|
||||
id: string;
|
||||
input: string;
|
||||
};
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
routeRef?: RouteRef;
|
||||
@@ -209,7 +215,10 @@ export interface Extension<TConfig> {
|
||||
// (undocumented)
|
||||
$$type: '@backstage/Extension';
|
||||
// (undocumented)
|
||||
at: string;
|
||||
attachTo: {
|
||||
id: string;
|
||||
input: string;
|
||||
};
|
||||
// (undocumented)
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
// (undocumented)
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('createApiExtension', () => {
|
||||
expect(extension).toEqual({
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'apis.test',
|
||||
at: 'core/apis',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
disabled: false,
|
||||
configSchema: undefined,
|
||||
inputs: {},
|
||||
@@ -67,7 +67,7 @@ describe('createApiExtension', () => {
|
||||
expect(extension).toEqual({
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'apis.test',
|
||||
at: 'core/apis',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
disabled: false,
|
||||
configSchema: undefined,
|
||||
inputs: {},
|
||||
|
||||
@@ -51,7 +51,7 @@ export function createApiExtension<
|
||||
|
||||
return createExtension({
|
||||
id: `apis.${apiRef.id}`,
|
||||
at: 'core/apis',
|
||||
attachTo: { id: 'core', input: 'apis' },
|
||||
inputs: extensionInputs,
|
||||
configSchema,
|
||||
output: {
|
||||
|
||||
@@ -31,7 +31,7 @@ export function createNavItemExtension(options: {
|
||||
const { id, routeRef, title, icon } = options;
|
||||
return createExtension({
|
||||
id,
|
||||
at: 'core.nav/items',
|
||||
attachTo: { id: 'core.nav', input: 'items' },
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({
|
||||
title: z.string().default(title),
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('createPageExtension', () => {
|
||||
).toEqual({
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'test',
|
||||
at: 'core.routes/routes',
|
||||
attachTo: { id: 'core.routes', input: 'routes' },
|
||||
configSchema: expect.anything(),
|
||||
disabled: false,
|
||||
inputs: {},
|
||||
@@ -50,7 +50,7 @@ describe('createPageExtension', () => {
|
||||
expect(
|
||||
createPageExtension({
|
||||
id: 'test',
|
||||
at: 'other/place',
|
||||
attachTo: { id: 'other', input: 'place' },
|
||||
disabled: true,
|
||||
configSchema,
|
||||
inputs: {
|
||||
@@ -63,7 +63,7 @@ describe('createPageExtension', () => {
|
||||
).toEqual({
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'test',
|
||||
at: 'other/place',
|
||||
attachTo: { id: 'other', input: 'place' },
|
||||
configSchema: expect.anything(),
|
||||
disabled: true,
|
||||
inputs: {
|
||||
@@ -88,7 +88,7 @@ describe('createPageExtension', () => {
|
||||
).toEqual({
|
||||
$$type: '@backstage/Extension',
|
||||
id: 'test',
|
||||
at: 'core.routes/routes',
|
||||
attachTo: { id: 'core.routes', input: 'routes' },
|
||||
configSchema: expect.anything(),
|
||||
disabled: false,
|
||||
inputs: {},
|
||||
|
||||
@@ -44,7 +44,7 @@ export function createPageExtension<
|
||||
}
|
||||
) & {
|
||||
id: string;
|
||||
at?: string;
|
||||
attachTo?: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
routeRef?: RouteRef;
|
||||
@@ -63,7 +63,7 @@ export function createPageExtension<
|
||||
|
||||
return createExtension({
|
||||
id: options.id,
|
||||
at: options.at ?? 'core.routes/routes',
|
||||
attachTo: options.attachTo ?? { id: 'core.routes', input: 'routes' },
|
||||
disabled: options.disabled,
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
|
||||
@@ -21,7 +21,7 @@ import { AppTheme } from '@backstage/core-plugin-api';
|
||||
export function createThemeExtension(theme: AppTheme) {
|
||||
return createExtension({
|
||||
id: `themes.${theme.id}`,
|
||||
at: 'core/themes',
|
||||
attachTo: { id: 'core', input: 'themes' },
|
||||
output: {
|
||||
theme: coreExtensionData.theme,
|
||||
},
|
||||
|
||||
@@ -26,7 +26,7 @@ describe('createExtension', () => {
|
||||
it('should create an extension with a simple output', () => {
|
||||
const extension = createExtension({
|
||||
id: 'test',
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
output: {
|
||||
foo: stringData,
|
||||
},
|
||||
@@ -56,7 +56,7 @@ describe('createExtension', () => {
|
||||
it('should create an extension with a some optional output', () => {
|
||||
const extension = createExtension({
|
||||
id: 'test',
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
output: {
|
||||
foo: stringData,
|
||||
bar: stringData.optional(),
|
||||
@@ -94,7 +94,7 @@ describe('createExtension', () => {
|
||||
it('should create an extension with input', () => {
|
||||
const extension = createExtension({
|
||||
id: 'test',
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
inputs: {
|
||||
mixed: createExtensionInput({
|
||||
required: stringData,
|
||||
|
||||
@@ -80,7 +80,7 @@ export interface CreateExtensionOptions<
|
||||
TConfig,
|
||||
> {
|
||||
id: string;
|
||||
at: string;
|
||||
attachTo: { id: string; input: string };
|
||||
disabled?: boolean;
|
||||
inputs?: TInputs;
|
||||
output: TOutput;
|
||||
@@ -97,7 +97,7 @@ export interface CreateExtensionOptions<
|
||||
export interface Extension<TConfig> {
|
||||
$$type: '@backstage/Extension';
|
||||
id: string;
|
||||
at: string;
|
||||
attachTo: { id: string; input: string };
|
||||
disabled: boolean;
|
||||
inputs: AnyExtensionInputMap;
|
||||
output: AnyExtensionDataMap;
|
||||
|
||||
@@ -30,7 +30,7 @@ const nameExtensionDataRef = createExtensionDataRef<string>('name');
|
||||
|
||||
const TechRadarPage = createExtension({
|
||||
id: 'plugin.techradar.page',
|
||||
at: 'test.output/names',
|
||||
attachTo: { id: 'test.output', input: 'names' },
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
@@ -41,7 +41,7 @@ const TechRadarPage = createExtension({
|
||||
|
||||
const CatalogPage = createExtension({
|
||||
id: 'plugin.catalog.page',
|
||||
at: 'test.output/names',
|
||||
attachTo: { id: 'test.output', input: 'names' },
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
@@ -55,7 +55,7 @@ const CatalogPage = createExtension({
|
||||
|
||||
const TechDocsAddon = createExtension({
|
||||
id: 'plugin.techdocs.addon.example',
|
||||
at: 'plugin.techdocs.page/addons',
|
||||
attachTo: { id: 'plugin.techdocs.page', input: 'addons' },
|
||||
output: {
|
||||
name: nameExtensionDataRef,
|
||||
},
|
||||
@@ -69,7 +69,7 @@ const TechDocsAddon = createExtension({
|
||||
|
||||
const TechDocsPage = createExtension({
|
||||
id: 'plugin.techdocs.page',
|
||||
at: 'test.output/names',
|
||||
attachTo: { id: 'test.output', input: 'names' },
|
||||
inputs: {
|
||||
addons: createExtensionInput({
|
||||
name: nameExtensionDataRef,
|
||||
@@ -85,7 +85,7 @@ const TechDocsPage = createExtension({
|
||||
|
||||
const outputExtension = createExtension({
|
||||
id: 'test.output',
|
||||
at: 'root',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
inputs: {
|
||||
names: createExtensionInput({
|
||||
name: nameExtensionDataRef,
|
||||
|
||||
@@ -86,7 +86,7 @@ export function createEndpointExtension<TConfig extends {}>(options: {
|
||||
}) {
|
||||
return createExtension({
|
||||
id: `apis.plugin.graphiql.browse.${options.id}`,
|
||||
at: 'apis.plugin.graphiql.browse/endpoints',
|
||||
attachTo: { id: 'apis.plugin.graphiql.browse', input: 'endpoints' },
|
||||
configSchema: options.configSchema,
|
||||
disabled: options.disabled ?? false,
|
||||
output: {
|
||||
|
||||
@@ -48,7 +48,10 @@ export type SearchResultItemExtensionOptions<
|
||||
},
|
||||
> = {
|
||||
id: string;
|
||||
at?: string;
|
||||
attachTo?: {
|
||||
id: string;
|
||||
input: string;
|
||||
};
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
component: (options: {
|
||||
config: TConfig;
|
||||
|
||||
@@ -58,7 +58,7 @@ describe('createSearchResultListItemExtension', () => {
|
||||
const TechDocsSearchResultItemExtension =
|
||||
createSearchResultListItemExtension({
|
||||
id: 'techdocs',
|
||||
at: 'plugin.search.page/items',
|
||||
attachTo: { id: 'plugin.search.page', input: 'items' },
|
||||
configSchema: createSchemaFromZod(z =>
|
||||
z.object({
|
||||
noTrack: z.boolean().default(true),
|
||||
@@ -79,7 +79,7 @@ describe('createSearchResultListItemExtension', () => {
|
||||
const ExploreSearchResultItemExtension =
|
||||
createSearchResultListItemExtension({
|
||||
id: 'explore',
|
||||
at: 'plugin.search.page/items',
|
||||
attachTo: { id: 'plugin.search.page', input: 'items' },
|
||||
predicate: result => result.type === 'explore',
|
||||
component: async () => ExploreSearchResultItemComponent,
|
||||
});
|
||||
|
||||
@@ -65,7 +65,7 @@ export type SearchResultItemExtensionOptions<
|
||||
/**
|
||||
* The extension attachment point (e.g., search modal or page).
|
||||
*/
|
||||
at?: string;
|
||||
attachTo?: { id: string; input: string };
|
||||
/**
|
||||
* Optional extension config schema.
|
||||
*/
|
||||
@@ -97,7 +97,7 @@ export function createSearchResultListItemExtension<
|
||||
) as PortableSchema<TConfig>);
|
||||
return createExtension({
|
||||
id: `plugin.search.result.item.${options.id}`,
|
||||
at: options.at ?? 'plugin.search.page/items',
|
||||
attachTo: options.attachTo ?? { id: 'plugin.search.page', input: 'items' },
|
||||
configSchema,
|
||||
output: {
|
||||
item: searchResultItemExtensionData,
|
||||
|
||||
Reference in New Issue
Block a user