@@ -26,6 +26,7 @@ import {
|
||||
coreExtensionData,
|
||||
createExtension,
|
||||
createExtensionInput,
|
||||
createExtensionKind,
|
||||
createExtensionOverrides,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { getComponentData } from '@backstage/core-plugin-api';
|
||||
@@ -128,21 +129,80 @@ export function convertLegacyApp(
|
||||
};
|
||||
},
|
||||
});
|
||||
const CoreNavOverride = createExtension({
|
||||
namespace: 'app',
|
||||
name: 'nav',
|
||||
attachTo: { id: 'app/layout', input: 'nav' },
|
||||
output: {},
|
||||
|
||||
const CoreNavOverride = CurrentCoreNav.override({
|
||||
// namespace: 'app',
|
||||
// name: 'nav',
|
||||
// attachTo: { id: 'app/layout', input: 'nav' },
|
||||
// output: {},
|
||||
factory: () => ({}),
|
||||
disabled: true,
|
||||
});
|
||||
|
||||
createExtensionOverride({
|
||||
extension: CurrentCoreNav,
|
||||
factory: () => null,
|
||||
});
|
||||
|
||||
const collectedRoutes = collectLegacyRoutes(routesEl);
|
||||
|
||||
return [
|
||||
...collectedRoutes,
|
||||
createExtensionOverrides({
|
||||
extensions: [CoreLayoutOverride, CoreNavOverride],
|
||||
extensions: [CoreNavOverride, CoreNavOverride],
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
const EntityCardExtension = createExtensionKind({
|
||||
kind: 'entity-card',
|
||||
attachTo: { id: 'entity-card', input: 'default' },
|
||||
inputs: {
|
||||
loader: createExtensionInput({
|
||||
element: coreExtensionData.reactElement,
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
factory({ inputs }, props: { title: string }) {
|
||||
console.log(inputs.loader);
|
||||
return {
|
||||
element: React.createElement('h1'),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const GithubCard = EntityCardExtension.new({
|
||||
props: {
|
||||
title: 'GitHub Card',
|
||||
},
|
||||
factory({ inputs: { loader } }) {
|
||||
console.log(loader);
|
||||
return {
|
||||
element: React.createElement('h2'),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
GithubCard.override({
|
||||
attachTo: { id: 'entity-card', input: 'github' },
|
||||
inputs: {
|
||||
loader: createExtensionInput(
|
||||
{
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
{ singleton: false },
|
||||
),
|
||||
loader2: createExtensionInput({
|
||||
element: coreExtensionData.reactElement,
|
||||
}),
|
||||
},
|
||||
factory({ originalFactory, inputs }) {
|
||||
inputs.loader2;
|
||||
inputs.loader;
|
||||
return originalFactory({
|
||||
inputsOverride: inputs,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -291,4 +291,37 @@ describe('createExtension', () => {
|
||||
'ExtensionDefinition{namespace=test,attachTo=root@default}',
|
||||
);
|
||||
});
|
||||
|
||||
describe('override', () => {
|
||||
it('should create an extension override', () => {
|
||||
const extension = createExtension({
|
||||
namespace: 'test',
|
||||
attachTo: { id: 'root', input: 'default' },
|
||||
output: {
|
||||
foo: stringData,
|
||||
},
|
||||
factory() {
|
||||
return {
|
||||
foo: 'bar',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const override = extension.override({
|
||||
attachTo: {
|
||||
id: 'root',
|
||||
input: 'default2',
|
||||
},
|
||||
factory() {
|
||||
return {
|
||||
foo: 'baz',
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
expect(String(override)).toBe(
|
||||
'ExtensionDefinition{namespace=test,attachTo=root@default2}',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,7 +99,33 @@ export interface CreateExtensionOptions<
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ExtensionDefinition<TConfig> {
|
||||
export interface ExtensionDefinitionOverrides<
|
||||
TOriginalConfig,
|
||||
TOriginalInputs extends AnyExtensionInputMap,
|
||||
TOverrideInputs extends AnyExtensionInputMap,
|
||||
> {
|
||||
readonly disabled?: boolean;
|
||||
readonly attachTo?: { id: string; input: string };
|
||||
// TODO: inputs (only adding to the list? or redefine arity and superset of old type on existing input?)
|
||||
// TODO: config (any merging needed on there?)
|
||||
inputs?: TOverrideInputs;
|
||||
factory?(options: {
|
||||
node: AppNode;
|
||||
config: TOriginalConfig;
|
||||
inputs: Expand<ResolvedExtensionInputs<TOriginalInputs & TOverrideInputs>>;
|
||||
originalFactory: (originalFactoryOptions?: {
|
||||
inputsOverride?: ResolvedExtensionInputs<
|
||||
TOriginalInputs & TOverrideInputs
|
||||
>;
|
||||
}) => ExtensionDataValues<any>;
|
||||
}): ExtensionDataValues<any>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ExtensionDefinition<
|
||||
TConfig,
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
> {
|
||||
$$type: '@backstage/ExtensionDefinition';
|
||||
readonly kind?: string;
|
||||
readonly namespace?: string;
|
||||
@@ -107,13 +133,18 @@ export interface ExtensionDefinition<TConfig> {
|
||||
readonly attachTo: { id: string; input: string };
|
||||
readonly disabled: boolean;
|
||||
readonly configSchema?: PortableSchema<TConfig>;
|
||||
override<TOverrideInputs extends AnyExtensionInputMap>(
|
||||
overrides: ExtensionDefinitionOverrides<TConfig, TInputs, TOverrideInputs>,
|
||||
): ExtensionDefinition<TConfig, TInputs>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface InternalExtensionDefinition<TConfig>
|
||||
extends ExtensionDefinition<TConfig> {
|
||||
export interface InternalExtensionDefinition<
|
||||
TConfig,
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
> extends ExtensionDefinition<TConfig, TInputs> {
|
||||
readonly version: 'v1';
|
||||
readonly inputs: AnyExtensionInputMap;
|
||||
readonly inputs: TInputs;
|
||||
readonly output: AnyExtensionDataMap;
|
||||
factory(context: {
|
||||
node: AppNode;
|
||||
@@ -123,10 +154,13 @@ export interface InternalExtensionDefinition<TConfig>
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function toInternalExtensionDefinition<TConfig>(
|
||||
overrides: ExtensionDefinition<TConfig>,
|
||||
): InternalExtensionDefinition<TConfig> {
|
||||
const internal = overrides as InternalExtensionDefinition<TConfig>;
|
||||
export function toInternalExtensionDefinition<
|
||||
TConfig,
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
>(
|
||||
overrides: ExtensionDefinition<TConfig, TInputs>,
|
||||
): InternalExtensionDefinition<TConfig, TInputs> {
|
||||
const internal = overrides as InternalExtensionDefinition<TConfig, TInputs>;
|
||||
if (internal.$$type !== '@backstage/ExtensionDefinition') {
|
||||
throw new Error(
|
||||
`Invalid extension definition instance, bad type '${internal.$$type}'`,
|
||||
@@ -147,38 +181,69 @@ export function createExtension<
|
||||
TConfig = never,
|
||||
>(
|
||||
options: CreateExtensionOptions<TOutput, TInputs, TConfig>,
|
||||
): ExtensionDefinition<TConfig> {
|
||||
): ExtensionDefinition<TConfig, TInputs> {
|
||||
const {
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
attachTo,
|
||||
disabled = false,
|
||||
inputs = {} as TInputs,
|
||||
output,
|
||||
configSchema,
|
||||
factory,
|
||||
} = options;
|
||||
|
||||
return {
|
||||
$$type: '@backstage/ExtensionDefinition',
|
||||
version: 'v1',
|
||||
kind: options.kind,
|
||||
namespace: options.namespace,
|
||||
name: options.name,
|
||||
attachTo: options.attachTo,
|
||||
disabled: options.disabled ?? false,
|
||||
inputs: options.inputs ?? {},
|
||||
output: options.output,
|
||||
configSchema: options.configSchema,
|
||||
factory({ inputs, ...rest }) {
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
attachTo,
|
||||
disabled,
|
||||
inputs,
|
||||
output,
|
||||
configSchema,
|
||||
factory({ inputs: factoryInputs, ...rest }) {
|
||||
// TODO: Simplify this, but TS wouldn't infer the input type for some reason
|
||||
return options.factory({
|
||||
inputs: inputs as Expand<ResolvedExtensionInputs<TInputs>>,
|
||||
return factory({
|
||||
inputs: factoryInputs as Expand<ResolvedExtensionInputs<TInputs>>,
|
||||
...rest,
|
||||
});
|
||||
},
|
||||
toString() {
|
||||
const parts: string[] = [];
|
||||
if (options.kind) {
|
||||
parts.push(`kind=${options.kind}`);
|
||||
if (kind) {
|
||||
parts.push(`kind=${kind}`);
|
||||
}
|
||||
if (options.namespace) {
|
||||
parts.push(`namespace=${options.namespace}`);
|
||||
if (namespace) {
|
||||
parts.push(`namespace=${namespace}`);
|
||||
}
|
||||
if (options.name) {
|
||||
parts.push(`name=${options.name}`);
|
||||
if (name) {
|
||||
parts.push(`name=${name}`);
|
||||
}
|
||||
parts.push(`attachTo=${options.attachTo.id}@${options.attachTo.input}`);
|
||||
parts.push(`attachTo=${attachTo.id}@${attachTo.input}`);
|
||||
return `ExtensionDefinition{${parts.join(',')}}`;
|
||||
},
|
||||
} as InternalExtensionDefinition<TConfig>;
|
||||
override<TOverrideInputs extends AnyExtensionInputMap>(
|
||||
overrides: ExtensionDefinitionOverrides<
|
||||
TConfig,
|
||||
TInputs,
|
||||
TOverrideInputs
|
||||
>,
|
||||
): ExtensionDefinition<TConfig, TInputs> {
|
||||
return createExtension({
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
attachTo: overrides.attachTo ?? attachTo,
|
||||
disabled: overrides.disabled ?? disabled,
|
||||
inputs: { ...inputs, ...overrides.inputs },
|
||||
output,
|
||||
configSchema,
|
||||
factory,
|
||||
});
|
||||
},
|
||||
} as InternalExtensionDefinition<TConfig, TInputs>;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AppNode } from '../apis';
|
||||
import { PortableSchema } from '../schema';
|
||||
import { Expand } from '../types';
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
AnyExtensionDataMap,
|
||||
AnyExtensionInputMap,
|
||||
ExtensionDataValues,
|
||||
ExtensionDefinition,
|
||||
ResolvedExtensionInputs,
|
||||
createExtension,
|
||||
} from './createExtension';
|
||||
@@ -107,7 +107,7 @@ export class ExtensionKind<
|
||||
},
|
||||
options: TOptions,
|
||||
): Expand<ExtensionDataValues<TOutput>>;
|
||||
}) {
|
||||
}): ExtensionDefinition<TConfig, TInputs> {
|
||||
return createExtension({
|
||||
kind: this.options.kind,
|
||||
namespace: args.namespace ?? this.options.namespace,
|
||||
|
||||
Reference in New Issue
Block a user