From d42b410050645dcac54a3bbefa6f20e219b7a807 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 18 Jun 2024 11:53:20 +0200 Subject: [PATCH] chore: initial pass at implementation from mob with simple test Signed-off-by: blam --- .../src/wiring/createExtensionKind.test.tsx | 44 +++++ .../src/wiring/createExtensionKind.ts | 164 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 packages/frontend-plugin-api/src/wiring/createExtensionKind.test.tsx create mode 100644 packages/frontend-plugin-api/src/wiring/createExtensionKind.ts diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionKind.test.tsx b/packages/frontend-plugin-api/src/wiring/createExtensionKind.test.tsx new file mode 100644 index 0000000000..d841ea2c62 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtensionKind.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { coreExtensionData } from './coreExtensionData'; +import { createExtensionKind } from './createExtensionKind'; + +describe('createExtensionKind', () => { + it('should allow creation of extension kinds', () => { + const TestExtension = createExtensionKind({ + kind: 'test-extension', + attachTo: { id: 'test', input: 'default' }, + output: { + element: coreExtensionData.reactElement, + }, + factory(_, props: { text: string }) { + return { + element:

{props.text}

, + }; + }, + }); + + const extension = TestExtension.new({ + name: 'my-extension', + props: { + text: 'Hello, world!', + }, + }); + + expect(extension).toBeDefined(); + }); +}); diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionKind.ts b/packages/frontend-plugin-api/src/wiring/createExtensionKind.ts new file mode 100644 index 0000000000..31a8e5da64 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtensionKind.ts @@ -0,0 +1,164 @@ +import { AppNode } from '../apis'; +import { PortableSchema } from '../schema'; +import { Expand } from '../types'; +import { + AnyExtensionDataMap, + AnyExtensionInputMap, + ExtensionDataValues, + ResolvedExtensionInputs, + createExtension, +} from './createExtension'; + +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +interface ExtensionKindOptions< + TProps, + TInputs extends AnyExtensionInputMap, + TOutput extends AnyExtensionDataMap, + TConfig, +> { + kind: string; + namespace?: string; + name?: string; + attachTo: { id: string; input: string }; + disabled?: boolean; + inputs?: TInputs; + output: TOutput; + configSchema?: PortableSchema; + factory( + options: { + node: AppNode; + config: TConfig; + inputs: Expand>; + }, + props: TProps, + ): Expand>; +} + +class ExtensionKind< + TProps, + TInputs extends AnyExtensionInputMap, + TOutput extends AnyExtensionDataMap, + TConfig, +> { + static create< + TProps, + TInputs extends AnyExtensionInputMap, + TOutput extends AnyExtensionDataMap, + TConfig, + >( + options: ExtensionKindOptions, + ): ExtensionKind { + return new ExtensionKind(options); + } + + private constructor( + private readonly options: ExtensionKindOptions< + TProps, + TInputs, + TOutput, + TConfig + >, + ) {} + + public new(options: { + namespace?: string; + name?: string; + attachTo?: { id: string; input: string }; + disabled?: boolean; + inputs?: TInputs; + output?: TOutput; + configSchema?: PortableSchema; + props: TProps; + factory?( + options: { + node: AppNode; + config: TConfig; + inputs: Expand>; + orignalFactory( + options?: { + node?: AppNode; + config?: TConfig; + inputs?: Expand>; + }, + props?: TProps, + ): Expand>; + }, + props: TProps, + ): Expand>; + }) { + return createExtension({ + kind: this.options.kind, + namespace: options.namespace ?? this.options.namespace, + name: options.name ?? this.options.name, + attachTo: options.attachTo ?? this.options.attachTo, + disabled: options.disabled ?? this.options.disabled, + inputs: options.inputs ?? this.options.inputs, + output: options.output ?? this.options.output, + configSchema: options.configSchema ?? this.options.configSchema, // TODO: some config merging or smth + factory: ({ node, config, inputs }) => { + if (options.factory) { + return options.factory( + { + node, + config, + inputs, + orignalFactory: ( + innerOptions?: { + node?: AppNode; + config?: TConfig; + inputs?: Expand>; + }, + innerProps?: TProps, + ) => + this.options.factory( + { + node: innerOptions?.node ?? node, + config: innerOptions?.config ?? config, + inputs: innerOptions?.inputs ?? inputs, + }, + innerProps ?? options.props, + ), + }, + options.props, + ); + } + + return this.options.factory( + { + node, + config, + inputs, + }, + options.props, + ); + }, + }); + } +} + +/** + * A simpler replacement for wrapping up `createExtension` inside a kind or type. This allows for a cleaner API for creating + * types and instances of those types. + */ +export function createExtensionKind< + TProps, + TInputs extends AnyExtensionInputMap, + TOutput extends AnyExtensionDataMap, + TConfig, +>(options: ExtensionKindOptions) { + return ExtensionKind.create(options); +}